JohnLyu的blog

橙汁事务所艾欧泽亚分部

0%

First, create maven project

1
mvn archetype:generate -DgroupId=com.johnlyu.app -DartifactId=design-pattern-example -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.4 -DinteractiveMode=false

作者: Chonghui Jiang a , Yongkai Ma a , Yunbi An

还在施工中

摘要

本文基于行为投资组合理论(BPT),分析了具有汇率风险的国际投资组合选择。我们对单个国外市场的BPT问题具有最优解的条件进行了刻画,并表明最优投资组合包含传统的均方差有效投资组合而不考虑汇率风险,并且构建了不相关的组件来对冲汇率风险。我们说明,最优投资组合必须具有汇率风险的均方差效率,而除非满足特定条件,否则从本地投资者的角度来看并非如此。我们进一步确定,具有多个国外市场的BPT中的国际投资组合选择包括两个顺序的决定。投资者首先在每个市场中选择最佳的BPT投资组合,忽略市场之间的协方差,然后根据特定规则在各个市场之间分配资金,以实现均值方差效率或将效率损失最小化。

引言

根据Shefrin和Statman(2000)提出的行为投资组合理论(BPT),投资者将总财富分为具有不同风险态度和目标的多个心理账户。接下来,投资者通过尝试实现该帐户的特定投资目标,并忽略心理帐户之间的协方差,来选择每个帐户中的子投资组合。因此,最佳的BPT投资组合仅仅是这些子投资组合的组合,而不是Markowitz(1952)的所有资产的最佳投资组合。另外,在BPT模型中,风险是通过投资组合收益小于预定阈值水平的概率(失败概率)来衡量的。虽然BPT的投资者没有遵循两支基金的分离原则,但他们的最佳投资组合与Friedman and Savage(1948)的难题相一致。根据Statman (2000) and Das et al. (2010)提出了一个新的脑内账户(MA)框架,每个子账户在服从其账户目标的前提下追求最大化账户期望收益。 Das et al. (2010)表明,这些子投资组合实际上是均值方差有效的,由这些有效子投资组合构成的总投资组合也是如此。

考虑一个希望在国外市场进行多元化经营的国内证券投资人。值得注意的是,投资者面对的外国市场具有许多结构和制度上的区别,包括市场法规,交易机制和交易时间。各个国外市场由于其不同的经济和政治体系以及特定的发展阶段,也可能表现出独特的风险回报特征和信息处理能力。此外,在国外市场上,政治和经济风险是不同的。结果,投资者的风险态度可能会因市场而异。因此,投资者没有确定在多个不同的国外市场上要实现的共同目标,而是根据其在该市场中的风险态度来指定一个市场中的特定投资目标,然后做出在该市场中实现特定目标的投资决策, 就好像没有其他投资组合风险敞口一样。相应地,投资者将整个投资组合视为每个市场中所选投资组合的组合,而不是来自所有市场的单个资产的组合。 Jorion(1994)提供的经验证据支持了国际投资组合选择的概念,这与Tversky和Kahneman(1986)中描述的投资组合的分层金字塔结构是一致的。它也具有实际意义,并指出专业基金经理通常推荐投资者将投资组合构建为资产组的金字塔(Fisher和Statman,1997)。因此,在我们的问题中,投资者的行为本质上符合BPT,将来自一个市场的资产放在一个特定的心理账户中,该账户有一个具体的目标。显然,在将投资组合优化分为子投资组合优化的意义上,国际投资组合选择类似于BPT问题。这个特殊问题涉及两个独立的决策:每个单独国外市场的投资组合选择和各个国外市场的资金分配。

但是,国际证券投资不仅涉及证券风险,还涉及汇率风险。投资组合风险来自以当地货币计量的单个资产的价格变动,而汇率风险则是由于汇率波动而导致投资组合的本币收益变动。每个单独市场中存在明显的汇率风险为投资者提供了进一步的经济原理,使投资者可以将来自不同市场的资产放入不同的心理账户中,并在国际投资组合选择中遵循两个决策的分离过程。考虑到汇率收益与投资组合的本币收益是相关的(Kaplanis和Schaefer,1991),而本国货币收益是主要关注点,因此,人们认为汇率风险会极大地影响国外市场的投资组合选择决策。因此,在不考虑汇率风险的情况下,在国外市场选择的最优投资组合可能会明显偏离有效投资组合(Jiang等,2010)。如果投资者在选择外国投资组合时遵循BPT策略,那么我们必须询问汇率风险如何影响投资者的决定,以及为什么要按原样构建最佳的BPT投资组合。 Das等人的BPT分析。 (2010)只考虑投资组合风险。使用达斯等人的框架。 (2010)和Baptista(2012)处理在每个账户中都存在背景风险的情况下,多个心理账户的投资组合选择问题。值得注意的是,汇率风险可以被视为国际投资组合选择中的背景风险(Finkelshtain等,1999; Franke等,2006)。

受Baptista(2012)和Das等人的激励。 (2010年),本文旨在从BPT的角度考虑汇率风险,为国际投资组合选择提供理论分析。 鉴于上述论点,BPT方法在分析国际投资组合选择方面具有实际意义和相关性。 此外,BPT方法允许投资者的风险态度和投资目标随市场而变化。 例如,国际投资者可以选择一个市场来主要降低风险,而选择另一个市场来获得相对较高的预期收益。 因此,BPT方法允许投资者构建一个子组合,该子组合可以满足任何给定外国市场的投资目标。 此外,与收益的标准差相反,BPT中的失败概率是一种与风险价值(VaR)密切相关的风险度量,可直接应用于风险管理中。

我们的论文从三个方面扩展了先前的工作。首先,与Baptista(2012)和Das等人相反。 (2010),我们的模型不仅包括风险资产,还包括无风险资产。在存在背景风险的情况下,风险资产和无风险资产之间的资金分配反映了投资者的预防动机(Malevergne和Rey,2010年; Menegatti,2009年; Tzeng和Wang,2002年)。因此,我们的模型使我们能够研究投资者的风险资产选择及其汇率风险的预防性储蓄行为。其次,在我们的分析中,不同市场的投资组合不同,而Baptista(2012)和Das等人的所有心理账户中的投资组合都是相同的。 (2010)。由于这种差异,在我们的设置中,关于具有多个帐户的典型BPT中的总投资组合的一般结论并不正确。例如,Das等人的总投资组合。 (2010)仍然处于均值-方差有效前沿,而除非满足特定条件,否则在我们的BPT环境中具有多个外国市场的总投资组合并非均值-方差有效。我们的发现与近期有关心理账户投资组合选择的研究相吻合。 Alexander和Baptista(2011)开发了一种委托的心理账户设置,其中每个账户中的最优投资组合和总投资组合通常远离均值-方差边界。这是因为(1)假定投资者将模型中的资产之间的财富分配任务委托给模型中的管理者,并且(2)管理者选择通常远离均值-方差边界的投资组合。 Das和Statman(即将发表)发现,如果资产收益具有非正态分布,则账户内的最佳投资组合可能会明显偏离均值-方差边界上的投资组合。我们的论文与两项研究的不同之处在于,我们考虑了每个账户的背景风险。 Baptista(2012)证明存在心理账户设置,其中总投资组合为均值-方差效率低,这是由于心理账户的总体背景风险所致。但是,本文中总投资组合的均方差效率低主要是由于以下事实:各市场的投资组合各不相同,这些市场中的投资决策之间缺乏整合。第三,Baptista(2012)和Das等人的研究认为,账户之间的财富分配是外生的。 (2010年),而在我们的设定中, 它是内生的,它代表了国际投资组合选择中的一个重要后续决定。

更具体地说,在本文中,我们探讨了BPT投资者如何在单个国外市场中选择最佳投资组合,以及汇率风险如何影响此类投资组合的存在。我们不仅关注汇率风险对外国市场投资组合选择的影响,而且关注投资者的对冲行为。为了深入了解,我们研究了最佳BPT投资组合的属性和组成,这对于管理汇率风险也具有实际意义。通过对无风险资产中总资金比例的理论和数值研究,进一步分析了投资者的预防性储蓄行为。与Alexander and Baptista(2011)和Baptista(2012)相似,我们得出了总投资组合处于我们设定的有效边界上的条件。此外,在不满足此条件的情况下,我们研究了BPT投资者在各个市场上进行资金分配的最佳决策,并调查了总投资组合的效率损失。

在背景风险文献中,许多投资组合选择模型都是为了在效用函数框架或均方差框架下,为背景风险对有效投资组合的构成或对投资者风险厌恶程度的影响提供理论见解(Gollier和Pratt,1996年)。 ; Kimball,1993; Pratt和Zeckhauser,1987; Tsetlin和Winkler,2005)(Baptista,2008; Eichner和Wagener,2009; Jiang等,2010)。通过将背景风险纳入框架,这些模型比传统的投资组合理论(例如Markowitz,1952; Merton,1969,1971; Samuelson,1969)可以更好地解释和预测投资者的实际投资组合选择决策。我们的工作通过从BPT角度考察汇率风险作为一种特定类型的背景风险如何影响国际投资组合的选择,进一步丰富了背景风险的文献,这尤其引起了人们的关注。此外,先前有关国际投资组合选择和资产分配的研究主要是从国际多元化的收益出发,例如降低风险和提高夏普比率(De Roon等,2001; Driessen和Laeven,2007; Eun和Resnick (1988年)。本文通过分析最佳国际投资组合的属性来补充这一研究流,重点是投资者的汇率风险对冲和预防性储蓄行为。

我们的贡献如下。我们推导了存在汇率风险的BPT问题解决方案的条件,并表明最优的BPT投资组合包含传统的均值-方差有效投资组合和用来对冲汇率风险的成分。我们还将探讨最佳BPT投资组合的属性,并解释为什么套期保值成分可以减轻汇率风险的影响。我们证明了最优的BPT投资组合必须是均值-方差有效,而从本地投资者的角度来看,除非满足一定的条件,否则它不是均值-方差有效的(We show that the optimal BPT portfolio must be mean–variance efficient, while it is not mean–variance efficient from the perspective of local investors unless certain conditions are met.)。我们进一步确定,具有多个国外市场的BPT中的国际投资组合选择包括两个顺序的决定。投资者首先在每个市场中选择最佳的BPT投资组合,而忽略市场之间的协方差,然后根据特定规则在所有市场中分配资金,以实现均值方差效率或将效率损失最小化。我们说明,风险规避程度特别高或特别低的投资者,其效率损失相对较大。我们的结果对全球风险管理和投资策略的形成具有实际意义。

本文的其余部分安排如下。 第2节介绍了该模型,并描述了最优投资组合的存在和组成。 第三部分从局部角度分析了最佳投资组合的性质以及最佳BPT组合在均值方差有效的条件。 第四部分描述了具有多个国外市场的BPT问题中总投资组合的均值方差效率。 第5节提供了数值分析,而第6节总结了本文。

模型和最佳BPT产品组合

模型

在本文中,我们首先考虑一个国外市场的问题,它对应于Das等人的BPT中的一个心理账户。 (2010)。然后,我们进入具有多个国外市场的BPT问题,并研究总投资组合的均值方差效率。假设在国外市场上有n种风险资产可用列收益矢量$\boldsymbol{r}$(以本国货币计),无风险资产收益为$\boldsymbol{r}_f$。这n个风险资产的投资组合是向量$\boldsymbol{q} =(q_1,q_2,…,q_n)^T$,其中$q_i$是投资在资产i上的投资组合的比例,上标T表示转置操作。因此,国际投资组合是q和无风险资产的组合。因此,投资组合的本币收益为$r_p = r_f + \boldsymbol{q}^T(E(\boldsymbol{r})-\boldsymbol{1}_n r_f)$,其中$1_n$是一个n列向量,所有元素都等于1。投资组合收益的期望和方差以等式

$$
\begin{align*}
E(r_p) &= r_f + \boldsymbol{q}^T(E(\boldsymbol{r})-\boldsymbol{1}_n r_f)
\
\sigma^2_p &= \boldsymbol{q}^T\boldsymbol{V}\boldsymbol{q}
\end{align*}
$$

注: $$\boldsymbol{q}^T\boldsymbol{V}\boldsymbol{q} = \sum_{i=1}^n \sum_{j=1}^n q_i q_j c_{ij}$$

疑问: 这里$\boldsymbol{q}$是风险资产的配置比例, 那无风险资产和风险资产之间的配置比例怎么没有啊, 直接就把$r_f$和后面的风险资产的收益相加了.
解答: 注意最后有$1_nr_f$项, 期望E可写作:
$$
\begin{align*}
E &= q_f r_f + q_1 r_1 + q_2 r_2 + …\
&= q_f r_f + q_1 r_1 + q_1 r_f - q_1 r_f + q_2 r_2 + q_2 r_f - q_2 r_f + …\
&= (q_f + q_1 + q_2 + … +q_n)r_f + q_1(r_1 - r_f) + q_2(r_2 - r_f) + … \
&= 原式
\end{align*}
$$

给出,其中V代表风险资产收益的协方差矩阵,并且不是奇异的。

但是,在这种情况下,以本币计价的投资组合收益是主要问题。 如果外币相对于本币的升值(折旧)率为$r_e$,则以本币$r_D$表示的投资组合收益表示为
$$
\begin{align*}
r_D = (1+r_p)(1+r_e)-1 = r_p + r_r + r_p r_e
\tag{1}
\end{align*}
$$
等式(1)表示外币投资组合的本币收益包括三个组成部分:外币投资组合收益,外汇收益及其产品。 正如Eun和Resnick(1988)指出的那样,当投资期短时,$r_p$和$r_e$都很小,因此$r_p r_e$可以忽略不计。 为了简化分析,我们将外国投资组合的本币收益近似为$r_p$和$r_e$之和。 即
$$
r_D \approx r_p + r_e
\tag{2}
$$
显然,汇率风险的存在(通过$r_e$的可变性来衡量)直接影响本币收益。 如果外币兑本币升值/贬值,则本币收益增加/减少。

本国货币收益率的期望及其方差由下式给出
$$
\begin{align*}
E(r_D) &= r_f + q^T(E(r)-1_n r_f) + E(r_e)
\tag{3} \
\sigma^2_D &= q^TVq + 2q^T cov(r, r_e) + \sigma^2_e
\tag{4} \
\end{align*}
$$
其中$\sigma^2_e$是$r_e$的方差。 如果在给定的预期收益水平上将其方差最小化,则从国内投资者的角度来看,投资组合q是均方差有效的。

为什么是均方差有效的?

BPT投资者通过解决以下问题来选择最佳投资组合:
$$
\begin{align*}
\max_{\boldsymbol{q} \in R^n}E(r_D) &= r_f + q^T(E(r)-1_n r_f)+E(r_e)
\tag{5}
\
\mathrm{s.t.\qquad Prob}(r_D &\leq H) \leq \alpha
\end{align*}
$$
其中$H$是预先设定的阈值回报或期望水平,最大失败概率为$\alpha \in (0,0.5)$。 H和a的选择反映了投资者在国外市场上的风险态度和动机,这取决于市场的风险收益特征和外汇风险。 模型(5)中的约束定义了对证券投资组合证券的期望,指出证券投资组合收益下降到阈值水平以下的概率不超过预定概率a。 BPT投资者在市场上的目标是通过考虑对安全性的期望,期望水平和达到期望水平的可能性来最大化预期收益。

已知如果$r_p$和$r_e$具有二元正态分布,则处于置信水平1-a的投资组合的风险价值(VaR)为
$$
\mathrm{VaR}(1-\alpha, r_D) = z_\alpha \sigma_D - E(r_D)\tag{6}
$$

假定$\sigma_D$已知, 则用$\bar{X}$估计μ的单侧置信区间上界为$\bar{X}-z_\alpha \sigma_D/\sqrt{n}$
意味着该投资的亏损$r_D$的概率小于α

其中$z_\alpha = -\Phi^{-1}(\alpha)> 0$,这是因为$\alpha \in (0,0.5)$且$\Phi()$是累积标准正态分布函数。 在正态性假设下,问题(5)中的约束等价于VaR类型约束 $\mathrm{VaR}(1-\alpha, r_D) \leqslant -H$。正态性假设遵循Baptista(2012)和Das等。 (2010)。 2因此,BPT问题(5)变为
$$
\begin{align*}
\max_{\boldsymbol{q} \in R^n}E(r_D) &= r_f + q^T(E(r)-1_n r_f)+E(r_e)
\tag{7}
\
\mathrm{s.t.\qquad} E(r_D) &\geqslant H + z_\alpha \sigma_D
\end{align*}
$$

模型求解

现在我们解决问题(7),以获得最佳的BPT产品组合。 为了方便起见,我们表示
$$
\begin{align*}
a &= (E(\boldsymbol{r})-\boldsymbol{1}_n r_f)^T\boldsymbol{V}^{-1}(E(\boldsymbol{r})-\boldsymbol{1}_n r_f)\
b &= \boldsymbol{1}_n^T \boldsymbol{V}^{-1}(E(\boldsymbol{r})-\boldsymbol{1}_n r_f)\
f &= \boldsymbol{1}_n^T \boldsymbol{V}^{-1}cov(\boldsymbol{r}, r_e)\
g &= (E(\boldsymbol{r}-\boldsymbol{1}_n r_f)^T\boldsymbol{V}^{-1}cov(\boldsymbol{r}, r_e))\
A &= cov(\boldsymbol{r}, r_e)\boldsymbol{V}^{-1}cov(\boldsymbol{r}, r_e)\
\end{align*}
$$
另外,在传统的均值-方差模型中,$\boldsymbol{q}_T = \frac{1}{b}\boldsymbol{V}^{-1}(E(\boldsymbol{r}-\boldsymbol{1}_n r_f)$是相切组合,期望超额收益为$a/b$。

Das等(2010年)表明,在具有VaR约束的心理账户框架中的投资组合优化产生了基于均值-方差有效前沿的最优投资组合。 同样,如果存在问题(7)的解决方案,那么从国内投资者的角度来看,最优投资组合是均值-方差有效的。 因此,为了获得最佳的BPT投资组合,我们需要在存在汇率风险的情况下求解均方差有效投资组合。

我们首先通过最小化$\sigma^2_D$来获得具有汇率风险的最小方差投资组合。该投资组合如下

$$
\begin{align*}
\boldsymbol{q}_\min = -f \boldsymbol{q}_f
\tag{8}
\end{align*}
$$

其中$\boldsymbol{q}_f = \frac{1}{f}\boldsymbol{V}^{-1} cov(\boldsymbol{r}, r_e)$。容易证明,超出无风险利率的$\boldsymbol{q}_f$的预期收益为g/f。这意味着,为了在有汇率风险的情况下使投资组合达到最低的风险,只要f> 0,投资者应出售f单位$\boldsymbol{q}_f$,然后将所有收益连同其原始资金一起投资于无风险资产。下一节将阐明其原因。投资组合的本币收益的期望值$(E_\min)$和方差$\sigma^2_\min$为
$$
\begin{align*}
E_\min &= r_f - g + E(r_e)
\tag{9}
\
\sigma^2_\min &= \sigma_e^2 - A
\tag{10}
\end{align*}
$$

这一段的数学推导完全没看懂

定理1.对于任何给定的预期本币收益率$\pi \geq E_{min}$,从国内投资者的角度来看,传统的均方差有效投资组合为
$$
\boldsymbol{q}_\pi = \boldsymbol{q}_\min + \frac{b}{a}(\pi - E_\min)\boldsymbol{q}_T
\tag{11}
$$
有效边界表示为
$$
\sigma^2_\pi = \frac{1}{a}(\pi - E_\min)^2 + \sigma^2_\min
\tag{12}
$$

问题: π是什么意思? 本国的最大有效投资组合的回报? 还是汇率变化带来的回报?
π应该是任意给定的, 在均值方差曲线上的期望收益值

在平均标准偏差平面中,问题(7)的约束表示直线$E(r_D)=H + z_\alpha \sigma_D$或上方的区域。如果存在问题(7)的解,则它必须是$E(r_D)=H + z_\alpha \sigma_D$与方程式(12)给出的有效边界的交点。如图1所示(有关类似说明,请参见Baptista(2012)中的图2)。以下定理描述了解存在的条件,还描述了最优投资组合的组成。

定理2。如果$z_\alpha \gt \sqrt{a}$且$H \leq E_\min - \sigma_\min \sqrt{z^2_\alpha - a}$,则问题(7)具有最优解,由下式给出:

$$
\begin{align*}
& \boldsymbol{q}{opt} = \boldsymbol{q}\min + \frac{b}{a}(\pi{opt}-E_\min)\boldsymbol{q}_T
\tag{13}
\
& \text{where } \pi
{opt} = \frac{z^2_\alpha E_\min - aH + z_\alpha \sqrt{a((E_\min - H)^2 - (z^2_\alpha - a)\sigma^2_\min)}}{z^2_\alpha - a}
\end{align*}
$$

其中的$\pi_{opt}$应该如何理解, 是否有一种类似期望收益的实际意义呢?

该定理与Alexander和Baptista(2011)中的定理2和3和Baptista(2012)中的定理1密切相关。 Alexander and Baptista(2011)假设投资者将自己的财富分配给每个账户的各个经理,其中每个经理最佳地选择了单个资产的投资组合。

他们论文中的定理2和3描述了帐户内最优分配的存在条件以及这种分配的组成。请注意,他们的问题不涉及背景风险。 Baptista(2012)将背景风险纳入了心理账户框架,定理1在他的论文中描述了具有背景风险的账户中最优投资组合的存在和构成。在两篇论文中,由于投资组合委托或背景风险假设,最优投资组合通常均不具有均方差效率。另一方面,我们对定理2的推导是基于以下观察:从国内投资者的角度来看,最佳BPT投资组合位于均值-方差有效前沿。结果,在存在汇率风险的情况下,每个外国市场中定理2中描述的最优投资组合仍然是有效的。而且,正如我们不久将要显示的那样,在不考虑汇率风险的情况下,该投资组合明显偏离了均方差有效投资组合。此外,Alexander and Baptista(2011)和Baptista(2012)中设定的机会仅包括风险资产,而本文中既包括风险资产又包括无风险资产。等式中最优投资组合的组成。 (13)还反映了风险资产和无风险资产之间的资金分配。因此,定理2扩展了Alexander和Baptista(2011)和Baptista(2012)中的结果。

定理2以及亚历山大和巴普蒂斯塔(2011)和巴普蒂斯塔(2012)中描述的最优条件存在于账户内的最佳条件反映了失败概率与阈值回报之间的权衡,无论背景风险是否存在存在。具体来说,对于满足$z_a \gt \sqrt{a}$的给定失效概率,阈值旧收益必须低于一定水平,以确保存在最佳BPT投资组合。另一方面,对于满足$H \lt E_min$的给定阈值回报,失败概率一定会高于一定水平,以确保存在最佳BPT投资组合。这些条件背后的直觉很明显。由于投资者无法增加单个资产和外汇收益或降低其在给定市场中的风险,因此很难实现高门槛收益和低失败概率。但是,值得注意的是,失败概率和阈值返回的选择可能由不同模型设置中的不同因素决定。在我们的论文中,这种选择不仅取决于单个市场的风险收益特征,还取决于汇率风险,因为它取决于典型的心理账户设置中的账户动机。

最佳BPT产品组合的属性

汇率风险对冲

为了检验投资者的汇率风险对冲行为,我们现在关注最优BPT投资组合的组成。利用等式(13),有:
$$
\begin{align*}
& \boldsymbol{q}_{opt} = -f \boldsymbol{q}f + \frac{b}{a}(\pi{opt}-r_f + g - E(r_e))\boldsymbol{q}T
\
&= \frac{b}{a}(\pi
{opt}-r_f - E(r_e))\boldsymbol{q}T + f(\frac{g}{f} \frac{b}{a} \boldsymbol{q}_T - \boldsymbol{q}_f)=w\boldsymbol{q}_T + f\boldsymbol{q}_H
\tag{14}
\end{align*}
$$
其中,$w = \frac{b}{a}(\pi
{opt}-E(r_e)-r_f)$和$\boldsymbol{q}H = \frac{g}{f} \frac{b}{a} \boldsymbol{q}_T - \boldsymbol{q}_f$。请注意,如果投资者在不考虑汇率风险的情况下选择国外市场的投资组合,则$\pi{opt}-E(r_e)$是预期的本币收益,而$w\boldsymbol{q}_T$是传统的有效投资组合(Huang and Litzenberger,1988)。因此,等式(14)表明最优的BPT投资组合有两个组成部分。在没有汇率风险的情况下,第一个对应于传统的有效投资组合,这对于所有投资者而言都必须相同,无论他们来自哪个国家。该投资组合需要将总资金的w%投资于相切投资组合$\boldsymbol{q}_T$中,并将$1-w$投资于无风险资产中。换句话说,在这种情况下,两个基金分离定理成立。第二部分表示由于汇率风险的存在,最佳BPT投资组合与传统有效投资组合之间的偏差。它是自融资的,即如果$f > 0(f <0)$,则投资者卖空(买入)无风险资产,占总资金的f%,然后买入(卖空)相同数量的投资组合$\boldsymbol{q}_H$。有趣的是,来自不同国家,具有不同汇率风险的投资者之间的这一组成部分差异很大。

我们注意到,超过无风险利率的$\boldsymbol{q}_H$的预期收益为零,并且与$\boldsymbol{q}_T$不相关。 这是因为
$$
\tag{15}
$$

$$
\tag{16}
$$

怎么注意到的, 完全没看懂
$\boldsymbol{q}_H^T又是什么?$

这些结果与Jiang等人的结果相似。 (2010)。 等式 (15)暗示最优BPT投资组合提供的预期超额收益完全由相应的传统均值-方差投资组合产生。 等式 (16)也是直观的,因为最优投资组合的第一部分是本地投资者在不考虑汇率风险的情况下构建的均方差有效投资组合,而第二部分是由于外汇敞口而存在。 因此,两个组件是不相关的。

现在,我们解释$\boldsymbol{q}_H$在管理汇率风险中扮演的角色。 由于$\boldsymbol{q}_T$和无风险资产是传统均值-方差模型中两支基金分离定理中的两支基金,因此我们关注$\boldsymbol{q}_f$,看看该成分如何帮助对冲汇率风险。 为此,请考虑以下回归
$$
r_e = \theta_0 + \boldsymbol{\theta}^T(\boldsymbol{r} - \boldsymbol{1}_n r_f) + \epsilon
\tag{17}
$$
其中$\theta$是回归系数向量,$\epsilon$是误差项。 其中$E(\epsilon) = 0$且$cov(\boldsymbol{r},e)=\boldsymbol{0}$。而且,$\theta_0 + \epsilon$和$\boldsymbol{\theta}^T(\boldsymbol{r} - \boldsymbol{1}_n r_f)$是外汇收益的一部分, 特别的, 前者不能而后者能被当地货币超额资产收益解释。 从等式 (17),我们看到
$$
\boldsymbol{q}_f = \frac{1}{f}\boldsymbol{V}^{-1} cov(\boldsymbol{r}, r_e) = … = \frac{1}{\boldsymbol{\theta}^T\boldsymbol{1}_n}\boldsymbol{\theta}
\tag{18}
$$
因此,投资组合$\boldsymbol{q}_f$是归一化回归系数向量。 如式 (17)可以表示为
$$
r_e = \theta_0 + (\boldsymbol{\theta}^T \boldsymbol{1}_n)\boldsymbol{q}^T_f (\boldsymbol{r} - \boldsymbol{1}_n r_f) + \epsilon
\tag{19}
$$
$\boldsymbol{\theta}^T \boldsymbol{1}_n$(或f)衡量外汇收益对投资组合$\boldsymbol{q}_f$变化的敏感性收益超过无风险利率。 这就解释了为什么精心构建的投资组合$\boldsymbol{q}_f$可以帮助减轻汇率风险的影响。 特别是,国内投资者在选择外国投资组合时可以出售f个单位的$\boldsymbol{q}_f$来抵消汇率风险,这就是最佳BPT投资组合中有一个$-f \boldsymbol{q}_f$项的原因。 同时,由$\boldsymbol{q}_f$产生的预期超额收益被$\boldsymbol{q}_H$中的其他分量抵消,导致没有来自$\boldsymbol{q}_H$的预期超额收益。

从本地投资者的角度出发,最佳BPT投资组合的效率

投资组合$\boldsymbol{q}_f$通常远离传统的均方差有效边界。 结果,除非满足以下定理中的条件,否则从本地投资者的角度来看,最佳的BPT投资组合通常并非均方差有效组合。 在这种情况下,本地投资者是那些在不考虑汇率风险的情况下做出投资组合决策的投资者。

定理3。从当地投资者的角度来看,最佳BPT投资组合是均方差有效的,当且仅当
$$
cov(\boldsymbol{r}, r_e) = \kappa(E(\boldsymbol{r}) - \boldsymbol{1}_n r_f)
\tag{20}
$$
其中$\kappa$是一个常数。

定理3表示,只要$cov(\boldsymbol{r}, r_e)$与期望的超额收益向量$E(\boldsymbol{r}) - \boldsymbol{1}_n r_f$成正比,对于给定的本地货币,从本地投资者的角度来看,最佳BPT投资组合也是均值-方差有效的 货币回报$\pi_{opt} - E(r_e)$。 在这种情况下,预期的本币收益及其方差由下式给出:
$$
E(r_D) = r_f + \boldsymbol{q}^T(E(\boldsymbol{r}) - \boldsymbol{1}_n r_f) + E(r_e)
\tag{21}
$$

$$
\sigma^2_D = \boldsymbol{q}^T\boldsymbol{V}\boldsymbol{q} + 2 \boldsymbol{q}^T \kappa(E(\boldsymbol{r}) - \boldsymbol{1}_n r_f) + \sigma^2_e
\tag{22}
$$

由于$E(r_e)$和$\sigma^2_e$都是外生于基于等式(21)和(22)的均方差问题,汇率风险的存在不影响有效投资组合的确定性。 我们知道,在任何特定国外市场上都有本地和国际投资者(在我们的上下文中为国内投资者)。 因此,在这种情况下,本地和国际投资者选择的最优投资组合都满足两基金分离定理,其中两个基金是相切组合和无风险资产。

给定式 (17)可以看出,预期外汇收益和方差为
$$
\tag{23}
$$

$$
\tag{24}
$$
等式 (23)表明预期外汇收益包括两个成分:h T(E(r)-1 nrf)和h 0,而第一个成分是收益的一部分,可以用超过 无风险利率。 类似地,等式。 (24)说,方差包括h T Vh和r 2,h T Vh是e收益的方差,可以用超过无风险利率的外币收益来解释。 特别地,如果cov(r,$r_e$)= j(E(r)-1 n r f),则h = jV -1(E(r)-1 n r f)。 在这种情况下,外汇的预期收益和方差可以改写为
$$
E(r_e) = \kappa a + \theta_0
\tag{25}
$$

$$
\sigma^2_e = \kappa^2 a + \sigma^2_\epsilon
\tag{26}
$$
在这种情况下,最小方差投资组合的预期收益和方差为
$$
E_\min = r_f + \theta_0
\tag{27}
$$

$$
\sigma^2_\min = \sigma_\epsilon^2
\tag{28}
$$

问题: 此处等式28相对于等式26, 应该是$\kappa$和a中一个为0, 那么代入回等式25中, 不应当存在一个$r_f$项啊

因此,具有汇率风险的外国风险资产产生的有效边界是
$$
\sigma^2_\min = \frac{1}{a}(\pi - (r_f + \theta_0))^2 + \sigma^2_\epsilon
\tag{29}
$$

等式 (29)表明,汇率风险的存在只是使本地投资者的有效边界向右移动$\sigma^2_\epsilon$个单位,而使e向上或向下移动$\theta_0$个单位。 如果可以用有风险的资产收益完全解释汇率风险,则$\sigma^2_\epsilon = \theta_0 = 0$。在这种特殊情况下,国内和本地投资者的有效边界是相同的

无风险资产中资金总额的比例

如模型中所述,在存在背景风险的情况下,对无风险资产的投资代表了投资者的预防性储蓄。 因此,投资者的对冲行为不仅通过将投资组合$\boldsymbol{q}_f$包含在$\boldsymbol{q}_opt$中来反映,而且还通过对无风险资产进行投资来反映。 等式(13)意味着最优投资组合在无风险资产的投资比例$w_f$为

$$
\tag{30}
$$
对于给定的预期本币收益p opt E($r_e$),无风险资产中的传统有效投资组合权重是等式右边的第一项。 (30)。 因此,有和没有汇率风险的有效投资组合之间无风险资产的投资组合权重之差等于T。当f> 0且qf的预期超额收益也低于预期超额时 在$\boldsymbol{q}_T$的回报率上,与本地投资者相比,国内投资者应在投资组合中的无风险资产中投入更多资金

国外市场资产配置

让我们继续讨论一下投资者在多个外国市场之间分配总财富的情况。与具有多个帐户的BPT(Baptista,2012; Das等人,2010)相反,在不同的心理帐户中,投资机会集是相同的,在我们的分析中,可用资产集因市场而异。鉴于每个单独的市场都表现出独特的风险收益特性和汇率风险,因此投资者在各个国外市场可能会有不同的风险偏好。因此,投资者在各个市场设定不同的最大失败概率和阈值回报以实现不同的目标。我们框架的一个显著特征是BPT投资者不会将其投资组合视为来自所有市场的单个资产的整体。相反,他们将其投资组合视为各个市场中最佳BPT投资组合的组合。因此,我们问题中的一个重要问题是,如何在这些最佳BPT子组合中分配总资金。

在本节中,我们将重点放在跨国外市场的资产分配上,并描述总投资组合的均值方差效率。正如Shefrin和Statman(2000)所述,拥有多个账户的BPT投资者将其投资组合分为不同的心理账户,而忽略了这些心理账户之间的协方差。与BPT中的这一重要假设一致,我们的投资者在特定的外国市场中选择了最佳的BPT投资组合,就好像该市场与其他市场无关。在后续的资产分配决策中仍然如此。正如特维尔斯基和卡尼曼(Tversky and Kahneman,1986)所指出的,心智账户之间协方差的存在给分析带来了很大的困难。这就是为什么人们将联合分布简单地划分为假定的不相关的心理因素的原因。尽管如此,这种模型的建立还是有实际意义的,因为有强有力的实验和实践证据表明,投资者在构建投资组合时会忽略协方差(Kroll等,1988)。因此,BPT投资者通常最终持有的资产组合不同于均值方差投资者。此外,BPT投资组合本质上不是最理想的。

在不失一般性的前提下,我们考虑选择在m个外国市场进行投资的国际投资者。 重要的是要注意,从国内投资者的角度来看,外国市场上的所有资产都是有风险的,因为即使是市场上无风险的资产也会由于汇率风险而产生可变的本币收益。 如果第i个市场上总财富比例的向量是$\boldsymbol{q}_i$,而本国货币收益向量r i,则总投资组合的收益是
$$
\tag{31}
$$
其中$\boldsymbol{q}_i$ T 1ði¼1和1(i)是一个列向量,其中所有元素i¼1等于1,并且元素数量等于第i个市场中的资产数量。 因此,总投资组合的预期收益和方差如下:
$$
\tag{32}
$$
$$
\tag{33}
$$

其中,V i是第i个市场中本币资产收益率的协方差矩阵,而V ij = cov(r i,r j)。 因此,对于v的预期收益,这m个市场中所有资产的有效投资组合解决了以下优化问题:

$$
\tag{34}
$$

一个有趣的问题是,所有资产的有效投资组合是否仅仅是单个市场中的有效投资组合。

引理1。如果对于任何i – j的V ij = 0,则总有效边界上的任何投资组合是单个外国市场上资产所产生的子边界上的有效投资组合的组合,而投放在第i个市场中的资金百分比为 由
$$
\tag{35}
$$
其中v是投资组合的预期收益。 此外,第i个市场中的投资组合为$\boldsymbol{q}_i$ / x i,其中
$$
\tag{36}
$$
如果引理1中的条件成立,则任何有效的投资组合都可以分解为子投资组合$\boldsymbol{q}_i$ / x i(i = 1,2,…,m),这在各自市场上均值方差有效。 有效投资组合$\boldsymbol{q}_i$ / x i的预期收益如下:
$$
\tag{37}
$$
等式 式(37)表明,一旦给出了总有效投资组合的预期收益,就可以确定每个子组合的预期收益。 因此,任何有效组合的分解都是唯一的。

使用引理1,我们可以得出BPT投资者的总效率前沿,这是由所有外国市场中的资产产生的。 特别是,给定预期收益v的总投资组合收益的方差由下式给出:
$$
\tag{38}
$$
在等式中改变v。 (38)得出了一组方差,以图形方式描绘了均方差空间中的总均方差有效边界。

为了说明引理1,我们考虑只有两个市场的情况。 图2绘制了总有效边界,每个市场的有效边界以及两个子边界上的两个有效投资组合产生的有效边界。 引理1表明,任何总有效投资组合C都可以分解为子投资组合A和B。在这种情况下,A和B产生的有效边界与点C处的总有效边界内切。

现在,我们从相反的方向研究相同的问题,并考虑最优BPT子组合的总投资组合是否仍位于总有效边界上。 事实证明,除非满足特定条件,否则总投资组合通常不是均方差有效的。

定理4.假设第i个外国市场中的最佳BPT投资组合的预期收益为$p_i$,这是该市场中阈值收益和失败概率的函数。 当且仅当存在一个总投资组合,它位于总有效边界上
$$
\tag{39}
$$
P是一个常数,第i个市场x i中的投资组合的权重为
$$
\tag{40}
$$
定理4背后的直觉很明显。我们注意到,每个市场中的最佳BPT投资组合都对应于预定的阈值回报和失败概率。这个投资目标从一个市场到另一个市场是不同的。另一方面,总投资组合决策仅表示所有市场中的一个特定目标。条件(39)要求各个市场中的预定BPT参数满足精确的关系,以使总投资组合有效。但是,每个市场中的这些BPT参数都是根据市场的独特风险收益特性确定的,而没有考虑其他市场中的风险敞口,因此在各个市场中没有关联。因此,这些BPT子投资组合具有多个投资目标的总投资组合通常效率低下,原因是各个市场的动机参数之间缺乏整合。此外,尽管任何有效组合都可以分解为子边界上的有效组合,但是这些子组合通常不是与预定阈值收益和个别市场中失败概率相关的最佳BPT子组合。

定理4有两个重要的实际含义。 首先,如果等式(39)为真,则BPT投资者首先要在各个市场中选择最佳投资组合,然后根据等式将它们组合起来。 (40)。 该总投资组合具有均方差效率,并且假设市场之间的协方差为零,BPT投资者的决策与均方差投资者的决策一致。

其次,由于BPT方法允许投资者指定不同的BPT投资目标并根据每个外国市场中的风险和回报特征选择最佳投资组合,因此BPT资产组合的总和通常不在有效边界上。 除非等式 (39)认为,即使投资者可以准确地说明他们在不同市场中的风险态度,最优BPT投资组合的总投资组合也会导致效率下降。 这种观察结果与Das等人(2010年)的结论相反,后者的结论是,在他们的模型框架中,总投资组合具有均值-方差有效且卖空。 在这种情况下,BPT投资者应在这些最佳BPT投资组合中分配总财富,以最大程度地降低效率损失。

为了获得第二种情况下每个最佳BPT投资组合的权重,我们将第i个市场中BPT投资组合的收益表示为r Di,将方差表示为r 2,并将预期收益表示为p i。 由于在第i个市场中,最佳的Di BPT投资组合是有效的,因此我们拥有

$$
\tag{41}
$$

这些最佳BPT投资组合的预期收益向量为p =(p 1,p 2,…,p m)T。 给定假设cov(r i,r j)= 0,这些投资组合收益的协方差矩阵R是对角矩阵,即ÀR¼diag r 2; r 2; 。 。 。 ; 2。 设1 m为m列向量,其中所有D1 D2 Dm元素均等于1,A p¼p T RÀ1p; B p¼1 m T R ‑1 p和C p¼1 m T R ‑1 1 m,则这m个最优投资组合产生的有效前沿为

$$
\tag{42}
$$

其中v是投资组合的预期收益。

定理5.如果最佳BPT投资组合的组合都不位于总有效边界上,则BPT投资者选择以下总投资组合:
$$
\tag{43}
$$
其中v ⁄是最优总投资组合的预期收益,以及
$$
\tag{44}
$$
最佳的总投资组合是使效率损失最小化的投资组合。 对于预期收益为v的总投资组合,效率损失定义为等式之间的差。 (42)和(38)。 即
$$
\tag{45}
$$
容易证明d在v ⁄处最小。 因此,最优总投资组合权重由等式给出。 (43)。

定理5对于国际证券投资人来说特别有意义,因为它提供了一种在最佳BPT投资组合中进行资金分配的策略。 投资者可以使用其他方法来选择最佳的总投资组合。 例如,考虑到对外国市场的整体风险规避程度,投资者可以再次应用BPT方法或使用均值-方差效用框架。 但是,基于这些替代方法的最优集合投资组合通常不会使效率损失最小化。

给定每个市场中最佳的BPT产品组合, (45)表明,期望收益太高或太低都会导致效率损失相对较高。 相反,假设投资者根据均值-方差效用函数vÀC 2 r 2 p选择总投资组合,其中C表示风险规避的程度。 在这种情况下,如果

$$

$$

Das等。 (2010)发现,效率损失可能是由于对投资者的风险规避的错误指定或由于实施了卖空限制而造成的。 通过一个数字示例,他们证明了在两种情况下规避风险的投资者的损失都较高。 在我们的情况下,总投资组合的效率损失主要是由于机会集不同的各个市场中目标之间缺乏整合。 当效率损失由等式中定义的d度量时。 (45),我们发现,尽管对外国资产的风险厌恶程度非常低(C <C ⁄)的投资者可能会遭受很大的效率损失,但那些非常厌恶风险的投资者(C> C ⁄) 效率的损失相对较大。

总体而言,定理4和5证明,具有多个国外市场的BPT中的国际投资组合选择包括两个顺序的决定:首先,投资者在给定阈值收益和最大失败概率的情况下,在每个市场中选择最佳BPT投资组合,而忽略了市场之间的协方差 。 然后,他们根据等式在这些最佳投资组合中分配资金。 (40)式中得到一个平均均方差有效投资组合。 (39)是真的。 如果等式 (39)不持有,必须根据等式分配资金。 (43)尽量减少效率损失。

数值分析

为了说明本文的主要结果,我们现在提供一个数值示例。 这个例子涉及一个希望在美国市场和欧元区投资的中国投资者。 为此,我们选择四个美国行业组合作为美国市场中的风险资产,即Cnsmr,Manuf,HiTec和Hlth。 4这些行业投资组合的每月数据以及2005年7月至2011年6月期间的无风险利率可从Kenneth R. French的个人网站获得。 对于欧元区,我们将法国,德国,荷兰和西班牙的股票市场指数用作风险资产,而将无风险利率设为零。 这些市场指数的数据从2005年7月至2011年6月以欧元计价,可从Morgan Stanley Capital International(MSCI)网站获得。 人民币和美元之间的汇率,以及同期人民币和欧元之间的汇率,可从中国人民银行网站获得。

“而将无风险利率设为零”, 为什么, 不应当用当期国债或者银行利率吗?

表1报告了数据的摘要统计信息。 来自美国市场的四个行业投资组合均产生超过10%的年化收益率,标准差在23.9%至26.5%之间。 此外,它们是高度相关的,任何一对投资组合收益之间的相关系数都高于0.84。 在此样本期内,无风险利率约为0.1775%,而人民币兑美元则每年升值4.1%。 欧元区这四个市场指数的平均回报率在1.35%至5.98%之间,标准差在17.74%至21.56%之间。 在此样本期内,人民币兑欧元每年升值0.37%。 但是,与在美国投资相比,中国投资者在这些欧元区市场投资时面临更高的汇率风险。 从数据中,我们获得许多模型参数的值,并将它们显示在表2中。

失败概率与阈值回报之间的权衡

从定理2可以得出,假设在美国市场上失效概率a <1ü0:3678¼0:2721,并且

如果在欧元区中p ffiffiffiffiffiffiffiffiffiffiffiffiffiffiffiffi a <1ü0:4915¼0:2416,则当且仅当H 6 E min≤r min z 2àa时,每个市场中才存在最优投资组合。 a。图3显示了给定失败概率的最大阈值回报,它确保了最佳投资组合的存在(类似插图,请参见Baptista(2012)中的图4)。投资者只有在曲线上或曲线下选择一对(a,H)时,才能找到最佳投资组合。该图清楚地表明,在失败概率与最大阈值返回水平之间需要进行权衡。换句话说,低/高阈值回报对应于两个市场的低/高失败概率,反之亦然。另外,欧元区的曲线低于美国市场的曲线,并且在一定范围的失败概率下(例如,当失败概率低于20%时)陡峭,这表明为了确保存在在最佳投资组合中,我们需要选择欧元区的门槛收益低于美国的门槛收益。对于相对较低的失败概率,这尤其明显。

无风险资产中的比例,(a,H)的选择和汇率风险

为了检验失败概率和阈值回报对无风险资产中总资金比例的影响,图4绘制了在失败概率的情况下,无风险资产的权重作为阈值回报的函数 a = 0.05、0.10和0.15。 请注意,失败概率越低或阈值收益越高,则无风险资产中放置的资金越多。 这不足为奇,因为较低的失败概率或较高的阈值回报意味着对总风险的更严格控制,并且对无风险资产的更多投资有助于实现这一投资目标。 但是,这限制了投资者的资本获利潜力。 图4中的期望收益图通过表明随着失败概率下降或阈值收益上升,期望收益下降而清楚地证明了这一点。

最优投资组合在无风险资产中的比例不仅反映了投资目标,还反映了存在汇率风险时投资者的预防性储蓄行为。为了研究汇率风险如何通过针对给定(a,H)组的无风险资产的预防性动机持有,我们考虑两种情况,其中(a,H)=(0.05,À0.10)和( 0.10,À0.05)。在这两种情况下,我们都将$r_e$设置为2%或6%,并允许E($r_e$)在4%和4%之间变化,同时保持汇率收益率与资产收益率之间的相关性不变。 6图5绘制了无风险资产中的比例,以及最优投资组合的收益和E($r_e$)的标准差。显然,随着预期汇率收益的增加,对于给定的$r_e$值,无论投资目标(a,H)是多少,最优投资组合中无风险资产的比例都会减少。直觉是,当外币兑本币升值时,应将更多资金分配给风险资产,以更好地获取外币收益。在这种情况下,最优投资组合的预期收益和风险都会上升。另一方面,较高的汇率风险导致给定E($r_e$)满足模型中的风险控制约束的无风险资产中的比例更高。这与文献中的发现一致,即较高的背景风险会提高投资者对保护性储蓄的需求(Courbage和Rey,2007年; Fei和Schlesinger,2008年; Malevergne和Rey,2010年; Menegatti,2009年; Tzeng和Wang,2002年)。

资产分配和效率损失

为了说明本例中总投资组合的效率损失,我们在美国和欧元区分别将(a,H)设置为等于(0.15,,0.10)和(0.10,0.150.15)。 使用表2中的参数,我们发现,美国的最佳BPT投资组合比欧元区的最佳投资组合具有更低的预期收益和风险。 (39)不成立。 因此,两个最佳BPT投资组合的组合都没有位于总有效边界上。 在这种情况下,当将总资金的54.44%分配给美国市场时,效率损失将降至最低,损失为相应总有效投资组合方差的0.4%。 图6显示了在美国市场上,总投资组合的效率损失百分比和预期收益与投资组合权重的关系。

总结

本文研究了基于BPT的具有汇率风险的国际投资组合选择。我们推导了一个外国市场的BPT问题具有最优解的条件,并描述了最优投资组合的特征。我们证明最佳的BPT资产组合包含传统的均方差有效资产组合,而没有考虑汇率风险,以及构造为对冲汇率风险的不相关组件。我们发现,除非满足某些条件,否则从本地投资者的角度来看,最佳的BPT投资组合通常没有均值-方差有效。我们进一步证明,具有多个国外市场的BPT问题中的总投资组合通常不位于总有效边界上,因此应在最佳BPT投资组合中分配资金,以最大程度地降低效率损失。我们表明,效率的损失取决于BPT投资者的风险规避程度。这些结果提供了对国际投资组合选择的见解,可以补充以前的论文。

通过一个数值示例,我们说明随着失败概率的增加,投资者可以为给定的市场设置相对较高的门槛收益。但是,对于具有较高汇率风险水平的市场,阈值回报必须较低,以确保可获得最佳投资组合,尤其是对于相对较低的失败概率。此外,最佳国际投资组合中无风险资产的比例还受到投资目标,预期外汇收益和汇率风险的影响。特别是,汇率风险越高,考虑到失败的可能性和阈值回报水平,放置在无风险资产中的资金总额就越高。

PyPI 镜像
简介
PyPI (Python Package Index) 是 Python 编程语言的软件存储库。开发者可以通过 PyPI 查找和安装由 Python 社区开发和共享的软件,也可以将自己开发的库上传至 PyPI 。

a. 找到下列文件

1
~/.pip/pip.conf

b. 在上述文件中添加或修改:

1
2
3
4
5
[global]
index-url = https://mirrors.aliyun.com/pypi/simple/

[install]
trusted-host=mirrors.aliyun.com

或者直接运行:

1
pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/

refer to https://mirrors.tuna.tsinghua.edu.cn/help/anaconda/

在国内使用anaconda, 建议使用清华源

Anaconda 镜像使用帮助 Anaconda 是一个用于科学计算的 Python 发行版,支持 Linux, Mac, Windows, 包含了众多流行的科学计算、数据分析的Python 包。

Anaconda 安装包可以到 https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/ 下载。

TUNA 还提供了 Anaconda 仓库与第三方源(conda-forge、msys2、pytorch等,查看完整列表)的镜像,各系统都可以通过修改用户目录下的 .condarc 文件。Windows 用户无法直接创建名为 .condarc 的文件,可先执行 conda config --set >show_channel_urls yes 生成该文件之后再修改。

注:由于更新过快难以同步,我们不同步pytorch-nightly, pytorch-nightly-cpu, ignite-nightly这三个包。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
channels:
- defaults
show_channel_urls: true
channel_alias: https://mirrors.tuna.tsinghua.edu.cn/anaconda
default_channels:
- https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main
- https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free
- https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/r
- https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/pro
- https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/msys2
custom_channels:
conda-forge: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
msys2: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
bioconda: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
menpo: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
pytorch: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
simpleitk: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud

即可添加 Anaconda Python 免费仓库。

运行 conda clean -i 清除索引缓存,保证用的是镜像站提供的索引。

运行 conda create -n myenv numpy 测试一下吧。

说实话, 没能完全理解老师的要求. 自己从头实现一个类似wireshark的工具工程量实在是有点过于庞大了.
如果从dumpcap层面实现倒还好, 但是也很麻烦. 因此本文专注于从链路层一步步解析到传输层的过程.

代码偏重演示性和可读性, 不代表实际使用和项目中应采用的结构.

pyshark不是wireshark, 所以不算直接使用wireshark

pyshark原理简介

pyshark实际上是调用了系统中的dumpcap将指定network interface的packet流输出到了指定的管道中.
接下来调用tshark从管道中读取二进制的数据流, 并解析成capture结构.

本实验致力于实现其中tshark的部分, 仅从pyshark的接口拿到二进制的packet包, 然后自己完成解析工作.

实验过程及代码

注意, 我只实现了IPv6部分的网络层, 请同学们自己实现IPv4的网络层

实现了Ethernet_II parser, IPv6 parser, IPv4 parser, TCP parser, 以及最后的包分析工具.

cap_lo0.pyview raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
# /*
# * @Author: John Lyu
# * @Date: 2020-10-10 19:44:59
# * @Last Modified by: John Lyu
# * @Last Modified time: 2020-10-10 19:44:59
# */
"""Create dynamic network layers of
Application Layer
Transport Layer
Network Layer
Data Link Layer
Physical Layer (No Need to implement)
"""

import ipaddress


class LayerParser(object):
"""
Base layer class
"""

def __init__(self, data):
"""
docstring
"""
self.data = data
self.header_struct = self.get_header_struct()
self.sliced_data = {}
self.analysis()

def get_header_struct(self):
# struck lenght is bits
# this will keep init order only with python version > 3.5
return {}

def pretty_print(self):
for part, data in self.sliced_data.items():
print(f"{part}: {hex(int(data, 2))}")

def analysis(self):
current_pos = 0
for part, length in self.header_struct.items():
end_pos = current_pos + length
self.sliced_data[part] = self.data[current_pos:end_pos]
current_pos = end_pos

def to_upper_layer(self):
pass


class EthernetParser(LayerParser):
"""
Ethernet_II Layer
refer to https://en.wikipedia.org/wiki/Ethernet_frame
in this layer, MAC address is used as id
"""

def get_header_struct(self):
return {
"DMAC": 6 * 8,
"SMAC": 6 * 8,
"Type": 2 * 8,
# there may be extended header here, but not implemented today
}

def next_protocal(self):
type_id = int(self.sliced_data["Type"], 2)
if type_id == 0x0800:
return "ipv4"
elif type_id == 0x0806:
return "arp"
elif type_id == 0x86DD:
return "ipv6"

def to_upper_layer(self):
return self.data[14 * 8:]


class LoopbackParser(LayerParser):
"""
Virtual Ethernet Layer, implemented for localhost
the first 4 bytes are
0000 1e 00 00 00
"""
HINT = "00011110" + "0" * (3 * 8)

def to_upper_layer(self):
return self.data[4 * 8:]

def next_protocal(self):
return "ipv6"


class IPv6Parser(LayerParser):
"""
NetworkParser, deal with IPv6
refer to https://en.wikipedia.org/wiki/IPv6_packet
"""

def get_header_struct(self):
return {
"protocol_version": 1 * 4,
"traffic_class": 2 * 4,
"flow_label": 5 * 4,
"payload_length": 4 * 4,
"next_protocol": 2 * 4, # 0x06 is TCP and 0x11 is UDP
"hop_limit": 2 * 4,
"source_address": 32 * 4,
"dst_address": 32 * 4,
# there may be extended header here, but not implemented today
}

def to_upper_layer(self):
return self.data[40 * 8:]

@property
def s_ip(self):
return ipaddress.IPv6Address(
int(self.sliced_data["source_address"], 2))

@property
def d_ip(self):
return ipaddress.IPv6Address(
int(self.sliced_data["dst_address"], 2))


class IPv4Parser(LayerParser):
"""
NetworkParser, deal with IPv4
refer to https://en.wikipedia.org/wiki/IPv4#Header
"""

def get_header_struct(self):
return {
"protocol_version": 1 * 4,
"IHL": 1 * 4,
"DSCP": 6,
"ECN": 2,
"Total_Length": 2 * 8,
"Identification": 2 * 8,
"Flags": 3,
"Fragment Offset": 2 * 8 - 3,
"Time To Live": 8,
"next_protocol": 8,
"Header_Checksum": 2 * 8,
"source_address": 4 * 8,
"dst_address": 4 * 8,
# there may be extended header here, but not implemented today
}

def analysis(self):
super().analysis()
self.header_length = int(self.sliced_data["IHL"], 2) * 32

def to_upper_layer(self):
return self.data[self.header_length:]

@property
def s_ip(self):
return ipaddress.IPv4Address(
int(self.sliced_data["source_address"], 2))

@property
def d_ip(self):
return ipaddress.IPv4Address(
int(self.sliced_data["dst_address"], 2))


class TCPParser(LayerParser):
"""
TCP layer data Parser
refer to https://en.wikipedia.org/wiki/Transmission_Control_Protocol
"""
# todo get full tcp request instead of one packet
def get_header_struct(self):
return {
"source_port": 8 * 2,
"dst_port": 8 * 2,
"sequence_number": 8 * 4,
"ack_number": 8 * 4,
"data_offset": 4,
"reserved": 3,
"ns": 1,
"cwr": 1,
"ece": 1,
"urg": 1,
"ack": 1,
"psh": 1,
"rst": 1,
"syn": 1,
"fin": 1,
"window_size": 8 * 2,
"checksum": 8 * 2,
"urgent_pointer": 8 * 2
}

def analysis(self):
super().analysis()
self.header_length = int(self.sliced_data["data_offset"], 2) * 32

def to_upper_layer(self):
return self.data[header_length:]


class MyShark(object):
"""
Oh My WireShark!
"""

def __init__(self, packets):
self.packets = packets

def get_stream(self, packet):
stream = [packet]
s_ip = packet.s_ip
d_ip = packet.d_ip
s_port = packet.s_port
d_port = packet.d_port
# todo detect FIN
for p in self.packets:
if all([
s_ip == p.s_ip,
d_ip == p.d_ip,
s_port == p.s_port,
d_port == p.d_port,
]):
stream.append(p)
return stream

def summary(self):
"""pretty print summarize info"""
# could use tree here for better performance
tag_dict = {}
for p in shark.packets:
src = str(p.s_ip) + ':' + str(p.s_port)
dst = str(p.d_ip) + ':' + str(p.d_port)
tag = f"source: {src:<23} dst: {dst:<23} protocol: {p.protocol}"
if tag in tag_dict.keys():
tag_dict[tag] += 1
else:
tag_dict[tag] = 1
for t, c in tag_dict.items():
print(t, " Count: ", c)


class MyPacket(object):
"""
Store all layer info in this class
"""

def __init__(self, pyshark_packet):
"""
init packet with bin string
"""
self.packet = pyshark_packet
rb = self.packet.get_raw_packet()
raw_hex = rb.hex()
raw_bin = bin(int(raw_hex, base=16))[2:].zfill(len(rb) * 8)

# I am not sure how to identity ethernet layer protocol
if raw_bin.startswith(LoopbackParser.HINT):
self.ethernet_layer = LoopbackParser(raw_bin)
else:
self.ethernet_layer = EthernetParser(raw_bin)

# switch ip layer version
if self.ethernet_layer.next_protocal() == "ipv6":
data = self.ethernet_layer.to_upper_layer()
self.ip_layer = IPv6Parser(data)
elif self.ethernet_layer.next_protocal() == "ipv4":
data = self.ethernet_layer.to_upper_layer()
self.ip_layer = IPv4Parser(data)
else:
raise ValueError("protrocal {} is not implemented".format(
self.ethernet_layer.next_protocal))

tcp_data = self.ip_layer.to_upper_layer()
self.tcp_layer = TCPParser(tcp_data)

@property
def s_ip(self):
return self.ip_layer.s_ip

@property
def d_ip(self):
return self.ip_layer.d_ip

@property
def s_port(self):
return int(self.tcp_layer.sliced_data["source_port"], 2)

@property
def d_port(self):
return int(self.tcp_layer.sliced_data["dst_port"], 2)

@property
def protocol(self):
code = self.ip_layer.sliced_data["next_protocol"]
code = int(code, 2)
if code == 0x06:
return "TCP"
elif code == 0x11:
return "UDP"
else:
return "Unknown"


if __name__ == "__main__":
import pyshark
# from bitstring import BitArray
from pathlib import Path
import os

fp = str(Path.home()) + '/Downloads/lo0_1.pcapng.gz'
if os.path.exists(fp):
capture = pyshark.FileCapture(fp,
use_json=True,
include_raw=True)
else:
# use this code to capture packet from lo0
capture = pyshark.LiveCapture(interface='lo0', output_file=fp)
capture.set_debug()
capture.sniff(timeout=10)

packets = []
for p in capture:
try:
packets.append(MyPacket(p))
except ValueError as identifier:
pass
shark = MyShark(packets)
shark.summary()

实验结果

ipv6抓包结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Python 3.8.5 (default, Sep 15 2020, 15:27:46) 
Type 'copyright', 'credits' or 'license' for more information
IPython 7.18.1 -- An enhanced Interactive Python. Type '?' for help.
source: ::1:53415 dst: ::1:4000 protocol: TCP Count: 13
source: ::1:4000 dst: ::1:53415 protocol: TCP Count: 16
source: ::1:53416 dst: ::1:4000 protocol: TCP Count: 9
source: ::1:4000 dst: ::1:53416 protocol: TCP Count: 9
source: ::1:53418 dst: ::1:4000 protocol: TCP Count: 12
source: ::1:4000 dst: ::1:53418 protocol: TCP Count: 12
source: ::1:53419 dst: ::1:4000 protocol: TCP Count: 8
source: ::1:4000 dst: ::1:53419 protocol: TCP Count: 8
source: ::1:53420 dst: ::1:4000 protocol: TCP Count: 9
source: ::1:4000 dst: ::1:53420 protocol: TCP Count: 9
source: ::1:53421 dst: ::1:4000 protocol: TCP Count: 11
source: ::1:4000 dst: ::1:53421 protocol: TCP Count: 15

ipv4抓包结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
source: 192.168.199.163:59324   dst: 202.160.128.238:443     protocol: TCP     Count:  1
source: 192.168.199.163:59325 dst: 202.160.128.238:443 protocol: TCP Count: 1
source: 192.168.199.163:59335 dst: 202.160.128.238:443 protocol: TCP Count: 1
source: 192.168.199.163:59318 dst: 176.122.148.37:443 protocol: TCP Count: 1
source: 192.168.199.163:49160 dst: 203.208.43.97:443 protocol: UDP Count: 1
source: 203.208.43.97:443 dst: 192.168.199.163:49160 protocol: UDP Count: 1
source: 192.168.199.163:59165 dst: 101.200.85.151:23446 protocol: TCP Count: 1
source: 192.168.199.163:59337 dst: 202.108.23.152:443 protocol: TCP Count: 1
source: 192.168.199.163:59352 dst: 202.38.64.11:80 protocol: TCP Count: 6
source: 192.168.199.163:59353 dst: 202.38.64.11:80 protocol: TCP Count: 2
source: 202.38.64.11:80 dst: 192.168.199.163:59352 protocol: TCP Count: 5
source: 202.38.64.11:80 dst: 192.168.199.163:59353 protocol: TCP Count: 1
source: 192.168.199.163:59001 dst: 113.96.12.217:8080 protocol: TCP Count: 3
source: 192.168.199.163:59083 dst: 81.70.95.109:22 protocol: TCP Count: 2
source: 192.168.199.163:59075 dst: 52.139.250.253:443 protocol: TCP Count: 3
source: 81.70.95.109:22 dst: 192.168.199.163:59083 protocol: TCP Count: 1
source: 113.96.12.217:8080 dst: 192.168.199.163:59001 protocol: TCP Count: 2
source: 101.200.85.151:23446 dst: 192.168.199.163:59272 protocol: TCP Count: 5
source: 192.168.199.163:59272 dst: 101.200.85.151:23446 protocol: TCP Count: 5
source: 101.200.85.151:23446 dst: 192.168.199.163:59276 protocol: TCP Count: 3
source: 192.168.199.163:59276 dst: 101.200.85.151:23446 protocol: TCP Count: 3
source: 192.168.199.163:59326 dst: 216.58.200.46:443 protocol: TCP Count: 1
source: 52.139.250.253:443 dst: 192.168.199.163:59075 protocol: TCP Count: 1
source: 192.168.199.163:59327 dst: 216.58.200.46:443 protocol: TCP Count: 1

TPC/UDP 通信实验

实验环境准备

Python3

1
2
>>> python3 --version
Python 3.8.5

直接调用socketserver包

直接通过官方库调用socketserver.TCPServersocketserver.UDPServer即可实现实验要求.

TCP部分

tcp_server.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import socketserver

class MyTCPHandler(socketserver.BaseRequestHandler):
"""
The request handler class for our server.

It is instantiated once per connection to the server, and must
override the handle() method to implement communication to the
client.
"""

def handle(self):
# self.request is the TCP socket connected to the client
self.data = self.request.recv(1024).strip()
print("{} wrote:".format(self.client_address[0]))
print(self.data)
# just send back the same data, but upper-cased
self.request.sendall(self.data.upper())

if __name__ == "__main__":
HOST, PORT = "localhost", 9999

# Create the server, binding to localhost on port 9999
with socketserver.TCPServer((HOST, PORT), MyTCPHandler) as server:
# Activate the server; this will keep running until you
# interrupt the program with Ctrl-C
server.serve_forever()

tcp_client.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import socket
import sys

HOST, PORT = "localhost", 9999
data = " ".join(sys.argv[1:])

# Create a socket (SOCK_STREAM means a TCP socket)
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
# Connect to server and send data
sock.connect((HOST, PORT))
sock.sendall(bytes(data + "\n", "utf-8"))

# Receive data from the server and shut down
received = str(sock.recv(1024), "utf-8")

print("Sent: {}".format(data))
print("Received: {}".format(received))

启动server:

1
>>> python3 tcp_server.py 

启动client:

1
2
3
>>> python3 tcp_client.py hello_world
Sent: hello_world
Received: HELLO_WORLD

同时, server的terminal会打印:

1
2
127.0.0.1 wrote:
b'hello_world'

UDP部分

udp_server.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import socketserver

class MyUDPHandler(socketserver.BaseRequestHandler):
"""
This class works similar to the TCP handler class, except that
self.request consists of a pair of data and client socket, and since
there is no connection the client address must be given explicitly
when sending data back via sendto().
"""

def handle(self):
data = self.request[0].strip()
socket = self.request[1]
print("{} wrote:".format(self.client_address[0]))
print(data)
socket.sendto(data.upper(), self.client_address)

if __name__ == "__main__":
HOST, PORT = "localhost", 9999
with socketserver.UDPServer((HOST, PORT), MyUDPHandler) as server:
server.serve_forever()

udp_client.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import socket
import sys

HOST, PORT = "localhost", 9999
data = " ".join(sys.argv[1:])

# SOCK_DGRAM is the socket type to use for UDP sockets
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# As you can see, there is no connect() call; UDP has no connections.
# Instead, data is directly sent to the recipient via sendto().
sock.sendto(bytes(data + "\n", "utf-8"), (HOST, PORT))
received = str(sock.recv(1024), "utf-8")

print("Sent: {}".format(data))
print("Received: {}".format(received))

启动server:

1
>>> python3 udp_server.py 

启动client:

1
2
3
>>> python3 udp_client.py hello_udp
Sent: hello_udp
Received: HELLO_UDP

同时, server的terminal会打印:

1
2
127.0.0.1 wrote:
b'hello_udp'

利用socket模块手写server/client

参考

The Open Group Base Specifications Issue 7, 2018 edition
IEEE Std 1003.1-2017 (Revision of IEEE Std 1003.1-2008)

SOCK_STREAM

Provides sequenced, reliable, bidirectional, connection-mode byte streams, and may provide a transmission mechanism for out-of-band data.

重写socketserver

mysocketserver.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
"""
a minimal socket server

refer
https://docs.python.org/3/library/socket.html
"""

# Copyright (c) 2020. John Lyu


import socket
import logging


_logger = logging.getLogger()

class MyBaseServer():
"""
refer to https://pubs.opengroup.org/onlinepubs/9699919799/functions/socket.html
> The Open Group Base Specifications Issue 7, 2018 edition
> IEEE Std 1003.1-2017 (Revision of IEEE Std 1003.1-2008)

> SOCK_STREAM
> Provides sequenced, reliable, bidirectional, connection-mode byte streams, and may provide a transmission mechanism for out-of-band data.

> SOCK_DGRAM
> Provides datagrams, which are connectionless-mode, unreliable messages of fixed maximum length.

A TCP connection is in fact a socket which use SOCK_STREAM type.
"""

SOCKET_TYPE = None

def __init__(self, host_and_port):
"""
Constructor for MyTCPServe
"""
self.server_address = host_and_port
self.socket_type = self.SOCKET_TYPE
self.socket = socket.socket(socket.AF_INET,
self.socket_type)

try:
self.bind()
self.active_server()
_logger.info(f"strat server at {self.server_address}")
except Exception as ex:
_logger.error(ex)

def active_server(self):
pass

def bind(self):
self.socket.bind(self.server_address)
self.server_address = self.socket.getsockname()

def handle(self, request, client_address):
pass

def serve_forever(self):
"""first, just allow one active connection, backlog is set to 0"""
pass

def server_close(self):
self.socket.close()

def __enter__(self):
return self

def __exit__(self, *args):
self.server_close()

包含了最基础的要素和流程, 在init中构造socket并bind端口, 预留active_server接口来封装TCP模式中的socket.listen.

接下来用handle函数处理数据, 函数将会在其子类中实现.

tcp_server.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#  Copyright (c) 2020. John Lyu

import socket

import mysocketserver


class MyTCPServer(mysocketserver.MyBaseServer):
"""Documents for MyTCPServer"""
SOCKET_TYPE = socket.SOCK_STREAM

def __init__(self, address_port):
"""Constructor for MyTCPServer"""
super(MyTCPServer, self).__init__(address_port)

def active_server(self):
self.socket.listen(0)

def serve_forever(self):
while True:
try:
conn, addr = self.socket.accept()
except KeyboardInterrupt as ex:
break
except Exception as ex:
break
if conn:
with conn:
self.handle(conn, addr)


def handle(self, request, client_address):
# self.request is the TCP socket connected to the client
data = request.recv(1024) # max length is 1024

print("{} wrote:".format(client_address[0]))
print(data)
# just send back the same data, but upper-cased
request.sendall(data.upper()[::-1])
self.shutdown_request(request)


def shutdown_request(self, request):
"""Called to shutdown and close an individual request."""
try:
#explicitly shutdown. socket.close() merely releases
#the socket and waits for GC to perform the actual close.
request.shutdown(socket.SHUT_WR)
except OSError:
pass #some platforms may raise ENOTCONN here

if __name__ == "__main__":
HOST, PORT = "localhost", 9999

# Create the server, binding to localhost on port 9999
with MyTCPServer((HOST, PORT)) as server:
# Activate the server; this will keep running until you
# interrupt the program with Ctrl-C
server.serve_forever()

继承MyBaseServer, 完善serve_forever函数, 完善active_server函数, 注意listenaccept都是TCP特有的方法.

重写handle函数, 循环读取request中的数据(此处限制最大只能读取1024字节!), 大写然后反向排列之后返回.

测试结果:

启动server:

1
>>> python3 tcp_server.py 

启动client:

1
2
3
4
5
6
>>> python3 tcp_client.py dog
Sent: dog
Received:
GOD

//注意到换行符\n的位置在最前

同时, server的terminal会打印:

1
2
127.0.0.1 wrote:
b'dog\n'

udp_server.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#  Copyright (c) 2020. John Lyu

import socket

import mysocketserver


class MyUDPServer(mysocketserver.MyBaseServer):
"""Documents for MyUDPServer"""
SOCKET_TYPE = socket.SOCK_DGRAM

def serve_forever(self):
while True:
data, addr = self.socket.recvfrom(1024)

self.handle(data, addr)


def handle(self, request, client_address):
# self.request is the UDP socket connected to the client
data = request # max length is 1024

print("{} wrote:".format(client_address[0]))
print(data)
# just send back the same data, but upper-cased
self.socket.sendto(data.upper()[::-1], client_address)


if __name__ == "__main__":
HOST, PORT = "localhost", 9999

# Create the server, binding to localhost on port 9999
with MyUDPServer((HOST, PORT)) as server:
# Activate the server; this will keep running until you
# interrupt the program with Ctrl-C
server.serve_forever()

UDP简单很多, 无需listen和accept, 也不会产生新的socket. 注意因为没有连接, 所以返回结果的时候是做了和客户端相同的操作, 直接针对ip和port又sendto了一遍. 测试结果同上.

多客户端测试

如果在以上客户端的源代码中增加一个sleep, 并且多次调用会怎么样呢?

multi_tcp_client.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#  Copyright (c) 2020. John Lyu

import socket
import time
from multiprocessing import Pool


HOST, PORT = "localhost", 9999


def tcp_send(data):
# Create a socket (SOCK_STREAM means a TCP socket)
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
# Connect to server and send data
sock.connect((HOST, PORT))
time.sleep(5)
data = str(data)
sock.sendall(bytes(data, "utf-8"))
# Receive data from the server and shut down
received = str(sock.recv(1024), "utf-8")

print("Sent: {}".format(data))
print("Received: {}".format(received))

if __name__ == '__main__':
with Pool(8) as p:
p.map(tcp_send, list(range(16)))

我对服务端的输出略作修改, 打印accept函数结束和handle函数结束的时间:

1
2
3
4
5
6
7
8
9
10
11
12
13
def serve_forever(self):
while True:
try:
conn, addr = self.socket.accept()
print(f"[{datetime.datetime.today()}] start connect {addr}")
except KeyboardInterrupt as ex:
break
except Exception as ex:
break
if conn:
with conn:
self.handle(conn, addr)
print(f"[{datetime.datetime.today()}] finish handle {addr}")

服务端的输出结果是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
[2020-09-28 19:47:21.988152] start connect ('127.0.0.1', 61652)
127.0.0.1 wrote:
b'0'
[2020-09-28 19:47:26.994171] finish handle ('127.0.0.1', 61652)
[2020-09-28 19:47:26.994515] start connect ('127.0.0.1', 61653)
127.0.0.1 wrote:
b'1'
[2020-09-28 19:47:27.009974] finish handle ('127.0.0.1', 61653)
[2020-09-28 19:47:27.010101] start connect ('127.0.0.1', 61654)
127.0.0.1 wrote:
b'2'
[2020-09-28 19:47:27.010227] finish handle ('127.0.0.1', 61654)
[2020-09-28 19:47:27.010302] start connect ('127.0.0.1', 61655)
127.0.0.1 wrote:
b'3'
[2020-09-28 19:47:27.020250] finish handle ('127.0.0.1', 61655)
[2020-09-28 19:47:27.020644] start connect ('127.0.0.1', 61656)
127.0.0.1 wrote:
b'4'
[2020-09-28 19:47:27.024446] finish handle ('127.0.0.1', 61656)
[2020-09-28 19:47:27.025111] start connect ('127.0.0.1', 61657)
127.0.0.1 wrote:
b'6'
[2020-09-28 19:47:27.025381] finish handle ('127.0.0.1', 61657)
[2020-09-28 19:47:27.026690] start connect ('127.0.0.1', 61658)
127.0.0.1 wrote:
b'5'
[2020-09-28 19:47:27.027257] finish handle ('127.0.0.1', 61658)
[2020-09-28 19:47:27.027485] start connect ('127.0.0.1', 61659)
127.0.0.1 wrote:
b'7'
[2020-09-28 19:47:27.027582] finish handle ('127.0.0.1', 61659)
[2020-09-28 19:47:27.027634] start connect ('127.0.0.1', 61660)
127.0.0.1 wrote:
b'8'
[2020-09-28 19:47:32.000856] finish handle ('127.0.0.1', 61660)
[2020-09-28 19:47:32.001133] start connect ('127.0.0.1', 61662)
127.0.0.1 wrote:
b'10'
[2020-09-28 19:47:32.016493] finish handle ('127.0.0.1', 61662)
[2020-09-28 19:47:32.016773] start connect ('127.0.0.1', 61661)
127.0.0.1 wrote:
b'9'
[2020-09-28 19:47:32.017032] finish handle ('127.0.0.1', 61661)
[2020-09-28 19:47:32.017282] start connect ('127.0.0.1', 61663)
127.0.0.1 wrote:
b'11'
[2020-09-28 19:47:32.024383] finish handle ('127.0.0.1', 61663)
[2020-09-28 19:47:32.024795] start connect ('127.0.0.1', 61664)
127.0.0.1 wrote:
b'13'
[2020-09-28 19:47:32.029014] finish handle ('127.0.0.1', 61664)
[2020-09-28 19:47:32.029147] start connect ('127.0.0.1', 61665)
127.0.0.1 wrote:
b'12'
[2020-09-28 19:47:32.029308] finish handle ('127.0.0.1', 61665)
[2020-09-28 19:47:32.029422] start connect ('127.0.0.1', 61666)
127.0.0.1 wrote:
b'14'
[2020-09-28 19:47:32.029518] finish handle ('127.0.0.1', 61666)
[2020-09-28 19:47:32.029600] start connect ('127.0.0.1', 61667)
127.0.0.1 wrote:
b'15'
[2020-09-28 19:47:32.029682] finish handle ('127.0.0.1', 61667)

令人惊讶的是, 仅有在第一次accept到第一次finish handle中, 间隔了5秒的时间, 然后立刻处理了15个当前已经发生的请求.

可见accept函数仅仅是从建立连接的队列中取出一个连接, 而并不会阻塞连接的产生.

我们修改listen(0)为listen(1), 再次观察输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
[2020-09-28 19:50:59.085068] start connect ('127.0.0.1', 61694)
127.0.0.1 wrote:
b'0'
[2020-09-28 19:51:04.085859] finish handle ('127.0.0.1', 61694)
[2020-09-28 19:51:04.086169] start connect ('127.0.0.1', 61695)
127.0.0.1 wrote:
b'1'
[2020-09-28 19:51:04.137850] finish handle ('127.0.0.1', 61695)
[2020-09-28 19:51:04.138167] start connect ('127.0.0.1', 61702)
127.0.0.1 wrote:
b'8'
[2020-09-28 19:51:09.087899] finish handle ('127.0.0.1', 61702)
[2020-09-28 19:51:09.088197] start connect ('127.0.0.1', 61703)
127.0.0.1 wrote:
b'9'
[2020-09-28 19:51:09.143399] finish handle ('127.0.0.1', 61703)
[2020-09-28 19:51:09.143657] start connect ('127.0.0.1', 61704)
127.0.0.1 wrote:
b'10'
[2020-09-28 19:51:14.094122] finish handle ('127.0.0.1', 61704)
[2020-09-28 19:51:14.094636] start connect ('127.0.0.1', 61705)
127.0.0.1 wrote:
b'11'
[2020-09-28 19:51:14.150364] finish handle ('127.0.0.1', 61705)
[2020-09-28 19:51:14.150668] start connect ('127.0.0.1', 61706)
127.0.0.1 wrote:
b'12'
[2020-09-28 19:51:19.097659] finish handle ('127.0.0.1', 61706)
[2020-09-28 19:51:19.097987] start connect ('127.0.0.1', 61707)
127.0.0.1 wrote:
b'13'
[2020-09-28 19:51:19.153941] finish handle ('127.0.0.1', 61707)
[2020-09-28 19:51:19.154163] start connect ('127.0.0.1', 61708)
127.0.0.1 wrote:
b'14'
[2020-09-28 19:51:24.104571] finish handle ('127.0.0.1', 61708)
[2020-09-28 19:51:24.104871] start connect ('127.0.0.1', 61709)
127.0.0.1 wrote:
b'15'
[2020-09-28 19:51:24.159250] finish handle ('127.0.0.1', 61709)

当第一个链接被处理时, 第二个连接已经进入了backlog队列, 因此, 第一个连接处理完毕时, 可以立刻处理第二个连接. 此后, 每两个连接的debug信息在几乎同一时间发生.

让我们修改multi_tcp_client中, sleep函数的位置.

1
2
3
4
5
6
7
8
9
10
# Connect to server and send data
sock.connect((HOST, PORT))
data = str(data)
sock.sendall(bytes(data, "utf-8"))
# Receive data from the server and shut down
received = str(sock.recv(1024), "utf-8")

# send for sleep
time.sleep(3)
sock.sendall(bytes(data, "utf-8")) # 在tcp_server中也需要添加对应的receive

可以看到, 我们将sleep函数移动到了通信阶段, 用来模拟耗时比较长的连接.

测试结果为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
[2020-09-28 20:05:47.595561] start connect ('127.0.0.1', 61967)
127.0.0.1 wrote:
b'0'
[2020-09-28 20:05:50.597057] finish handle ('127.0.0.1', 61967)
[2020-09-28 20:05:50.597355] start connect ('127.0.0.1', 61968)
127.0.0.1 wrote:
b'1'
[2020-09-28 20:05:53.599988] finish handle ('127.0.0.1', 61968)
[2020-09-28 20:05:53.600498] start connect ('127.0.0.1', 61977)
127.0.0.1 wrote:
b'8'
[2020-09-28 20:05:56.603293] finish handle ('127.0.0.1', 61977)
[2020-09-28 20:05:56.603585] start connect ('127.0.0.1', 61978)
127.0.0.1 wrote:
b'9'
[2020-09-28 20:05:59.604627] finish handle ('127.0.0.1', 61978)
[2020-09-28 20:05:59.604995] start connect ('127.0.0.1', 61979)
127.0.0.1 wrote:
b'10'
[2020-09-28 20:06:02.606392] finish handle ('127.0.0.1', 61979)
[2020-09-28 20:06:02.607370] start connect ('127.0.0.1', 61980)
127.0.0.1 wrote:
b'11'
[2020-09-28 20:06:05.614071] finish handle ('127.0.0.1', 61980)
[2020-09-28 20:06:05.614558] start connect ('127.0.0.1', 61981)
127.0.0.1 wrote:
b'12'
[2020-09-28 20:06:08.616184] finish handle ('127.0.0.1', 61981)
[2020-09-28 20:06:08.616566] start connect ('127.0.0.1', 61982)
127.0.0.1 wrote:
b'13'
[2020-09-28 20:06:11.619736] finish handle ('127.0.0.1', 61982)
[2020-09-28 20:06:11.620305] start connect ('127.0.0.1', 61983)
127.0.0.1 wrote:
b'14'
[2020-09-28 20:06:14.625111] finish handle ('127.0.0.1', 61983)
[2020-09-28 20:06:14.625440] start connect ('127.0.0.1', 61984)
127.0.0.1 wrote:
b'15'
[2020-09-28 20:06:17.630012] finish handle ('127.0.0.1', 61984)

可以发现产生了阻塞, 并且有丢包, 2-7的连接被丢掉了. 让我们修改listen参数回0, 再次测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
[2020-09-28 20:08:35.079838] start connect ('127.0.0.1', 62008)
127.0.0.1 wrote:
b'0'
[2020-09-28 20:08:38.084865] finish handle ('127.0.0.1', 62008)
[2020-09-28 20:08:38.085164] start connect ('127.0.0.1', 62009)
127.0.0.1 wrote:
b'1'
[2020-09-28 20:08:41.086948] finish handle ('127.0.0.1', 62009)
[2020-09-28 20:08:41.087304] start connect ('127.0.0.1', 62010)
127.0.0.1 wrote:
b'2'
[2020-09-28 20:08:44.088548] finish handle ('127.0.0.1', 62010)
[2020-09-28 20:08:44.088838] start connect ('127.0.0.1', 62011)
127.0.0.1 wrote:
b'3'
[2020-09-28 20:08:47.092957] finish handle ('127.0.0.1', 62011)
[2020-09-28 20:08:47.093275] start connect ('127.0.0.1', 62012)
127.0.0.1 wrote:
b'4'
[2020-09-28 20:08:50.095218] finish handle ('127.0.0.1', 62012)
[2020-09-28 20:08:50.095506] start connect ('127.0.0.1', 62013)
127.0.0.1 wrote:
b'6'
[2020-09-28 20:08:53.098302] finish handle ('127.0.0.1', 62013)
[2020-09-28 20:08:53.098758] start connect ('127.0.0.1', 62014)
127.0.0.1 wrote:
b'5'
[2020-09-28 20:08:56.104663] finish handle ('127.0.0.1', 62014)
[2020-09-28 20:08:56.104967] start connect ('127.0.0.1', 62015)
127.0.0.1 wrote:
b'7'
[2020-09-28 20:08:59.107536] finish handle ('127.0.0.1', 62015)
[2020-09-28 20:08:59.107826] start connect ('127.0.0.1', 62016)
127.0.0.1 wrote:
b'8'
[2020-09-28 20:09:02.113798] finish handle ('127.0.0.1', 62016)
[2020-09-28 20:09:02.114221] start connect ('127.0.0.1', 62017)
127.0.0.1 wrote:
b'9'
[2020-09-28 20:09:05.116121] finish handle ('127.0.0.1', 62017)
[2020-09-28 20:09:05.116354] start connect ('127.0.0.1', 62018)
127.0.0.1 wrote:
b'10'
[2020-09-28 20:09:08.121383] finish handle ('127.0.0.1', 62018)
[2020-09-28 20:09:08.121712] start connect ('127.0.0.1', 62021)
127.0.0.1 wrote:
b'11'
[2020-09-28 20:09:11.123565] finish handle ('127.0.0.1', 62021)
[2020-09-28 20:09:11.123847] start connect ('127.0.0.1', 62022)
127.0.0.1 wrote:
b'12'
[2020-09-28 20:09:14.125535] finish handle ('127.0.0.1', 62022)
[2020-09-28 20:09:14.125742] start connect ('127.0.0.1', 62025)
127.0.0.1 wrote:
b'13'
[2020-09-28 20:09:17.129745] finish handle ('127.0.0.1', 62025)
[2020-09-28 20:09:17.130031] start connect ('127.0.0.1', 62026)
127.0.0.1 wrote:
b'14'
[2020-09-28 20:09:20.132656] finish handle ('127.0.0.1', 62026)
[2020-09-28 20:09:20.132866] start connect ('127.0.0.1', 62027)
127.0.0.1 wrote:
b'15'
[2020-09-28 20:09:23.135194] finish handle ('127.0.0.1', 62027)

延迟依旧, 但是TimeoutError导致的连接中断消失了.

多连接解决方案

针对多连接的解决方案有很多, 在此次试验中优先测试multiprocessing, select, libuv.

multiprocessing server

针对原tcp_server中, server_forever函数进行修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def serve_forever(self):
while True:
try:
conn, addr = self.socket.accept()
print(f"[{datetime.datetime.today()}] start connect {addr}")
except KeyboardInterrupt as ex:
break
except Exception as ex:
break
if conn:
process = multiprocessing.Process(target=handle, args=(conn, addr))
# self.handle(conn, addr)
process.daemon = True
process.start()

再次测试 得到结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
[2020-09-28 20:32:22.081443] start connect ('127.0.0.1', 62655)
[2020-09-28 20:32:22.106736] start connect ('127.0.0.1', 62656)
[2020-09-28 20:32:22.117108] start connect ('127.0.0.1', 62657)
[2020-09-28 20:32:22.184763] start connect ('127.0.0.1', 62658)
[2020-09-28 20:32:22.191656] start connect ('127.0.0.1', 62659)
[2020-09-28 20:32:22.195872] start connect ('127.0.0.1', 62660)
[2020-09-28 20:32:22.288573] start connect ('127.0.0.1', 62661)
[2020-09-28 20:32:22.340250] start connect ('127.0.0.1', 62662)
('127.0.0.1', 62655) wrote:
b'0'
('127.0.0.1', 62659) wrote:
b'4'
('127.0.0.1', 62656) wrote:
b'1'
('127.0.0.1', 62657) wrote:
b'2'
('127.0.0.1', 62658) wrote:
b'3'
('127.0.0.1', 62660) wrote:
b'5'
('127.0.0.1', 62662) wrote:
b'7'
('127.0.0.1', 62661) wrote:
b'6'
[2020-09-28 20:32:26.034654] finish handle ('127.0.0.1', 62655)
[2020-09-28 20:32:26.037662] start connect ('127.0.0.1', 62663)
[2020-09-28 20:32:26.041779] finish handle ('127.0.0.1', 62659)
[2020-09-28 20:32:26.044135] start connect ('127.0.0.1', 62664)
[2020-09-28 20:32:26.047419] finish handle ('127.0.0.1', 62656)
[2020-09-28 20:32:26.048139] finish handle ('127.0.0.1', 62657)
[2020-09-28 20:32:26.061695] finish handle ('127.0.0.1', 62658)
[2020-09-28 20:32:26.063596] start connect ('127.0.0.1', 62665)
[2020-09-28 20:32:26.061698] finish handle ('127.0.0.1', 62660)
[2020-09-28 20:32:26.071198] start connect ('127.0.0.1', 62666)
[2020-09-28 20:32:26.073034] finish handle ('127.0.0.1', 62662)
[2020-09-28 20:32:26.074966] finish handle ('127.0.0.1', 62661)
[2020-09-28 20:32:26.080513] start connect ('127.0.0.1', 62667)
[2020-09-28 20:32:26.089662] start connect ('127.0.0.1', 62668)
[2020-09-28 20:32:26.107653] start connect ('127.0.0.1', 62669)
[2020-09-28 20:32:26.120258] start connect ('127.0.0.1', 62670)
('127.0.0.1', 62663) wrote:
b'8'
('127.0.0.1', 62669) wrote:
b'15'
('127.0.0.1', 62670) wrote:
b'14'
('127.0.0.1', 62666) wrote:
b'11'
('127.0.0.1', 62668) wrote:
b'13'
('127.0.0.1', 62665) wrote:
b'10'
('127.0.0.1', 62667) wrote:
b'12'
('127.0.0.1', 62664) wrote:
b'9'
[2020-09-28 20:32:29.896884] finish handle ('127.0.0.1', 62663)
[2020-09-28 20:32:29.900672] finish handle ('127.0.0.1', 62669)
[2020-09-28 20:32:29.909882] finish handle ('127.0.0.1', 62670)
[2020-09-28 20:32:29.917748] finish handle ('127.0.0.1', 62666)
[2020-09-28 20:32:29.925119] finish handle ('127.0.0.1', 62665)
[2020-09-28 20:32:29.928934] finish handle ('127.0.0.1', 62668)
[2020-09-28 20:32:29.934114] finish handle ('127.0.0.1', 62667)
[2020-09-28 20:32:29.935469] finish handle ('127.0.0.1', 62664)

可见已经可以实现无延迟的处理request.

select模式

完全重写了serve_forever函数, 因为select的编程模式中, 对同一socket的读写操作不再是同步, 阻塞的, 因此, 原handle函数不再使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
def serve_forever(self):
while True:
read_sockets, write_sockets, error_sockets = select.select(self.connection_list, self.write_list, self.connection_list)

for sock in read_sockets:
if sock == self.socket:
sockfd, addr = self.socket.accept()
sockfd.setblocking(0)
self.connection_list.append(sockfd)
self.message_queues[sockfd] = Queue()
print(f"[{datetime.datetime.now()}] Client {addr} connected")

# Some incoming message from a client
else:
# Data recieved from client, process it
try:
# In Windows, sometimes when a TCP program closes abruptly,
# a "Connection reset by peer" exception will be thrown
data = sock.recv(1024)
if data == '' and sock in self.write_list:
self.write_list.remove(sock)
if sock not in self.write_list:
print(f"[{datetime.datetime.now()}] get data: {data}")
self.message_queues[sock].put(data)
self.write_list.append(sock)

# client disconnected, so remove from socket list
except Exception as ex:
# print(ex)
print(f"[{datetime.datetime.now()}] Client {addr} is offline")
sock.close()
self.connection_list.remove(sock)
del self.message_queues[sock]

for sock in write_sockets:
sname = id(sock)
data = self.message_queues[sock].get_nowait()
sock.sendall(data)
self.write_list.remove(sock)

测试结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
[2020-09-28 21:37:23.874508] Client ('127.0.0.1', 63481) connected
[2020-09-28 21:37:23.874573] get data: b'0'
[2020-09-28 21:37:23.874960] Client ('127.0.0.1', 63482) connected
[2020-09-28 21:37:23.875002] get data: b'1'
[2020-09-28 21:37:23.883249] Client ('127.0.0.1', 63483) connected
[2020-09-28 21:37:23.883356] get data: b'2'
[2020-09-28 21:37:23.895548] Client ('127.0.0.1', 63484) connected
[2020-09-28 21:37:23.895659] get data: b'3'
[2020-09-28 21:37:23.896493] Client ('127.0.0.1', 63485) connected
[2020-09-28 21:37:23.896615] Client ('127.0.0.1', 63486) connected
[2020-09-28 21:37:23.896641] get data: b'4'
[2020-09-28 21:37:23.896706] get data: b'5'
[2020-09-28 21:37:23.899129] Client ('127.0.0.1', 63487) connected
[2020-09-28 21:37:23.899175] get data: b'6'
[2020-09-28 21:37:23.901726] Client ('127.0.0.1', 63488) connected
[2020-09-28 21:37:23.901775] get data: b'7'
[2020-09-28 21:37:26.879538] get data: b'fin'
[2020-09-28 21:37:26.879723] get data: b'fin'
[2020-09-28 21:37:26.880561] Client ('127.0.0.1', 63488) is offline
[2020-09-28 21:37:26.880714] Client ('127.0.0.1', 63488) is offline
[2020-09-28 21:37:26.882456] Client ('127.0.0.1', 63490) connected
[2020-09-28 21:37:26.882997] Client ('127.0.0.1', 63489) connected
[2020-09-28 21:37:26.883083] get data: b'8'
[2020-09-28 21:37:26.883237] get data: b'9'
[2020-09-28 21:37:26.888187] get data: b'fin'
[2020-09-28 21:37:26.888785] Client ('127.0.0.1', 63489) is offline
[2020-09-28 21:37:26.889816] Client ('127.0.0.1', 63491) connected
[2020-09-28 21:37:26.889922] get data: b'10'
[2020-09-28 21:37:26.898865] get data: b'fin'
[2020-09-28 21:37:26.899299] get data: b'fin'
[2020-09-28 21:37:26.899793] get data: b'fin'
[2020-09-28 21:37:26.901391] Client ('127.0.0.1', 63491) is offline
[2020-09-28 21:37:26.901686] Client ('127.0.0.1', 63491) is offline
[2020-09-28 21:37:26.902033] Client ('127.0.0.1', 63491) is offline
[2020-09-28 21:37:26.904855] get data: b'fin'
[2020-09-28 21:37:26.905892] get data: b'fin'
[2020-09-28 21:37:26.906721] Client ('127.0.0.1', 63492) connected
[2020-09-28 21:37:26.907406] Client ('127.0.0.1', 63493) connected
[2020-09-28 21:37:26.907519] Client ('127.0.0.1', 63493) is offline
[2020-09-28 21:37:26.907654] Client ('127.0.0.1', 63493) is offline
[2020-09-28 21:37:26.907854] get data: b'13'
[2020-09-28 21:37:26.908168] Client ('127.0.0.1', 63494) connected
[2020-09-28 21:37:26.908221] get data: b'12'
[2020-09-28 21:37:26.908484] Client ('127.0.0.1', 63496) connected
[2020-09-28 21:37:26.908548] get data: b'11'
[2020-09-28 21:37:26.908755] Client ('127.0.0.1', 63495) connected
[2020-09-28 21:37:26.908794] get data: b'15'
[2020-09-28 21:37:26.909165] get data: b'14'
[2020-09-28 21:37:29.887799] get data: b'fin'
[2020-09-28 21:37:29.888304] get data: b'fin'
[2020-09-28 21:37:29.890121] Client ('127.0.0.1', 63495) is offline
[2020-09-28 21:37:29.890436] Client ('127.0.0.1', 63495) is offline
[2020-09-28 21:37:29.893419] get data: b'fin'
[2020-09-28 21:37:29.894439] Client ('127.0.0.1', 63495) is offline
[2020-09-28 21:37:29.912341] get data: b'fin'
[2020-09-28 21:37:29.912730] get data: b'fin'
[2020-09-28 21:37:29.913233] get data: b'fin'
[2020-09-28 21:37:29.913477] get data: b'fin'
[2020-09-28 21:37:29.914070] get data: b'fin'
[2020-09-28 21:37:29.915454] Client ('127.0.0.1', 63495) is offline
[2020-09-28 21:37:29.915544] Client ('127.0.0.1', 63495) is offline
[2020-09-28 21:37:29.915603] Client ('127.0.0.1', 63495) is offline
[2020-09-28 21:37:29.915654] Client ('127.0.0.1', 63495) is offline
[2020-09-28 21:37:29.915702] Client ('127.0.0.1', 63495) is offline

同样达到了非阻塞的效果.

我会尽量在这里实时更新当前各个课程需要完成的作业内容以及参考答案.

本学期考试/期末大作业时间汇总

  • 中特 13周 周一 电子版报告

  • 电子信息 13周 周四 闭卷考试

  • 中特 14周 周一 开卷考试

  • 信息安全 14周 周一 开卷考试 8道简答题

  • 计算机网络 14周 周四 闭卷考试

  • 大数据分析 14周 大作业

  • 高级数据库技术 14周 论文简述

  • 学术论文写作 14周 论文

  • 工程伦理 论文 14周

  • 高等工程数学 15周 周三 闭卷考试 三小时

  • 智能计算系统 15周 大作业

  • 实证金融 没选 不知道

  • 资产定价与风险分析 大作业 已完成

高级计算机网络

已经完成

实验一: TCP 和 UDP 的通信实验

Deadline: 2020/10/22
Content:

用你熟悉的语言对 TCP 和 UDP 协议各实现一对服务端和客户端.

代码参考: tcp udp 通信实验

实验二: 抓包实验

Deadline: 2020/10/22
Content:

自己实现一个类似wireshark的软件并成功抓包.

编写程序,监控本地网络,捕获一段时间内以本机为源地址或目的地址的IP数据包,统计IP数据包的信息
程序统计信息包括源地址、目的地址、协议类型以及相应的IP数据包的数量

代码参考: 自己实现wireshark实验

实验三: 网络实验

Deadline: 2020/11/18
Content:

交换机实验, 实验讲义中的第四五六章内容

完成实验报告

实验四: 网络实验

Deadline: 2020/11/19
Content:

路由器实验

完成实验报告

实验五: 综合组网实验

Deadline: 2020/11/26
Content:

实验三.doc + 实验四(物联网)

------

学术论文写作/工程伦理

阅读论文, 完成理解

Deadline: 2020/12/11

内容不限, 字数别太少即可, 任意一篇格式完善的论文. 查重

对工程伦理进行探讨

Deadline: 2020/12/11

分小组做presentation, 约15-20分钟.

每人提交一份信息相关的工程伦理问题分析的作业, 字数不限, 这个可以比学术论文短一点(三五百有点少, 你懂我的意思吧.jpg), 查重.


高级软件工程

实验报告: 基于中台的微服务开发模式研究

Deadline: 2020/11/26
Content:

3K字

已经完成

完成某个系统的 UML 设计

Deadline: 2020/10/15 (不确切)
Content:

需要包含以下内容:

  1. 用例图 (usecase diagram)
  2. 类图 (class diagram)
  3. 时序图 (sequence diagram)
  4. 活动图 (activity diagram)
  5. 部署图 (deploy diagram)

请使用超星系统里课程资料中的<财经大学实验报告>模板撰写报告

因为内容太过庞杂, 我就不提供参考内容了.

用 Java 完成以下设计模式的代码, 运行, 并完成实验报告

1
2
3
1. 单例模式
2. 观察者模式
3. 组合模式

在超星上提交

示例工程: 设计模式Java项目

---

大数据分析

已经完成

使用nn对Boston housing数据集进行分类

Deadline: 2020-11-3

数据文件: boston housing

注意, 不允许修改源数据集!

Code Link: boston_housing

svm 的手动推导或代码实现

Deadline: 2020-10-20

Content:
手动推导 svm 公式, 以手写材料直接交给老师.
或者, 编写代码完成一次 svm 的分类实践, 数据集为 ,要求为使用 70%数据作为训练集, 30%数据作为测试集, 训练一个 SVM 的二分模型(注意, 原数据集中为 7 分类问题), 并附上运行成果. 提交方式为超星网上提交.

Code Link: svm简易分类器

---

资产定价与风险管理

已经完成

Information about assignment #2

  1. Case: Acpana Business Systems Inc.: Effect of Currency Exposure on Revenue
  2. Due time: 12:00 pm, November 22, 2020.
  3. Your case report MUST be typed, and should not exceed 8 pages (double spaced). Hand-written reports are not accepted.
  4. Send your work to me at anyunbi5@gmail.com.
  5. Feel free to make assumptions; justify your assumptions.
  6. You report should include the following analysis:
  • Frame the issue. Why is Brenzel Concerned by the current exchange rate fluctuation?
  • Which vehicles does Acpana have at its disposal for hedging? Assume Acpana will need to transfer $200,000 US dollars to Canadian dollars on a regular basis, calculate the impact of these different hedging strategies against a naked position at:
    a. 1 Cdn$ =1 US$;
    b. 1 Cdn$ =0.90 US$;
    c. 1 Cdn$ =1.10 US$.
  • Should Schenkel recommend Acpana hedge its position in Canadian dollars? Why or why not? If you think he should recommend a hedge, which vehicle should he recommend?
  • Does the Economist Business Unit’s forecast that the Canadian dollar will finish the year at parity to the US dollar affect your decision to hedge?
  • Summarize your conclusions.

Acapana.pdf

文献阅读以及读后感

Deadline: 12:00 pm, October 16, 2020.

Content:

阅读材料: International portfolio selection with exchange rate risk: A behavioural portfolio theory perspective

  1. Study and provide an analysis of the following research article on international portfolio investments:
    International portfolio selection with exchange rate risk: A behavioural portfolio theory perspective
  2. Your analysis must include the following components: a) A summary of the paper’s research issues, methodology, and findings; b) your critiques of the paper; and c) your own thoughts on international portfolio investments and risk hedging from the perspective of Chinese investors.
  3. Due time: 12:00 pm, October 16, 2020.
  4. Your analysis MUST be typed; hand-written work is not accepted. It should be generally 6 to 8 pages long, double spaced. Email your work to me at anyunbi5@gmail.com.
  5. It is important that the submitted work is your own and adheres to all your Academic Rights and Responsibilities.

翻译链接: 具有汇率风险的国际投资组合选择:行为投资组合理论的观点


高等工程数学

已经完成

课后习题

Deadline: October 30, 2020.

Content:
P338: 1, 4, 5, 11, 12, 13, 14, 15

P366: 3, 4, 6, 7, 9, 12, 16, 17, 19

P389: 2, 3, 4, 5, 6, 7

答案别问, 不会, 真不会 QAQ

考点:

矩阵:

  1. 范式的定义, 性质(正定型, 其次性, 三角不等式, 相容性), 矩阵的谱半径(计算), 条件数
  2. 三角分解(计算), $LDL^T$分解(条件, 计算), 对称三角分解
  3. QR分解

数理统计:

  1. 什么是统计量, 顺序统计量
  2. 常见分布(x^2, t, F), 会查分位数, 构造分位点
  3. 参数估计: 矩估计, 似然估计(重点), 参数估计的性质(选择填空)
  4. 区间估计, 单总体(均值, 方差), 两个总体(均值的差, 方差之比)
  5. 假设检验, 但总体, 双总体
  6. 线性回归, 最小二乘法, 似然估计, 参数检验(类似假设检验, 原假设, 备择假设), 模型检验(方差分析表)
  7. 广义线性回归: logistics 回归模型以及参数估计的方法
  8. 混合高斯模型: 模型的形式, 如何估计参数, 似然函数要求会写
  9. 范数的应用, 一范式, 二范式加到回归模型中的样子

svm_covtype.py:

建议拿到jupyter里跑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
"""
dataset from https://archive.ics.uci.edu/ml/datasets/Covertype
target: create a svm two-class classifier

@author John Lyu
@date 10/6/2020
"""
import pandas as pd
import numpy as np
import re
import pickle
import os

from IPython.display import display

from sklearn.model_selection import train_test_split

# create colnames
column_descriptions = """Elevation
Aspect
Slope
Horizontal_Distance_To_Hydrology
Vertical_Distance_To_Hydrology
Horizontal_Distance_To_Roadways
Hillshade_9am
Hillshade_Noon
Hillshade_3pm
Horizontal_Distance_To_Fire_Points
Wilderness_Area (4 binary columns)
Soil_Type (40 binary columns)
Cover_Type (7 types) """.splitlines()

col_names = []
one_hot_pattern = re.compile(r"(.+?)\((\d+).+columns\)")
for cd in column_descriptions:
m = one_hot_pattern.search(cd)
if m:
for i in range(int(m.group(2))):
col_names.append(f"{m.group(1).strip()}_{i}")
else:
col_names.append(cd.strip())
display(col_names)

raw_df = pd.read_csv("./covtype.data", names=col_names).fillna(0)

display(raw_df.head())

def get_float_columns(df, limit=30):
'''find cols contains continuous data'''
ret = []
for col in df.columns:
if df[col].nunique() > limit:
ret.append(col)
return ret

# 连续量标准化
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()

norm_df = raw_df.copy()
fcols = get_float_columns(norm_df)
norm_df[fcols] = ss.fit_transform(norm_df[fcols])

display(norm_df.head())

# 查看样本分布
norm_df[norm_df.columns[-1]].value_counts()

from sklearn import preprocessing
from sklearn.svm import SVC

X, y = raw_df.iloc[:, :-1].values, raw_df.iloc[:, -1].values # 切割最后一列为target
change_result = lambda x: 1 if x == 2 else 0 # 将target分为二分问题
y = np.vectorize(change_result)(y)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0) #全部数据的30%作为测试集

display(pd.value_counts(y_train))

svm_model = SVC(class_weight='balanced',kernel='linear') # 'rbf' 太慢
svm_model.fit(X_train, y_train) # 非常耗时!建议睡前跑

# 学习完了赶紧把模型存起来
with open('./model.pickle', 'wb') as f:
pickle.dump(svm_model, f)


# 读取svm model
with open('./model.pickle', 'rb') as f:
svm_model = pickle.load(f)

# 验证训练集的效果
y_t = svm_model.predict(X_train)
acc = np.sum(y_t == y_train)/ y_train.size
print(f"train acc is: {acc}")

# 验证测试集
y_pred = svm_model.predict(X_test)
# display(y_pred)
acc = np.sum(y_test == y_pred)/ y_test.size
print(f"test acc is: {acc}")

# 查看支撑向量
display(svm_model.n_support_)
display(svm_model.support_vectors_)