量化教程 2:Numpy 基础

目前股市的量化交易已经成为了一个热门领域,很多计算机人员都想利用自己的编程技术去量化交易,也有很多的金融人员想要学习编程技术。所以计划写一个量化系列。

本次 Chat,主要学习如何利用 Numpy 进行编程,对还不会 Numpy 的程序员或者金融从业者一个基础讲解。如果你也想从事量化交易,那么本场 Chat 将会为你介绍 Numpy 这个强有力的武器。

本场 Chat 你将学到如下内容:

  1. 基本的 Numpy 数组;
  2. Numpy 中函数的使用;
  3. 利用 Numpy 进行异常值的处理;
  4. 利用 Numpy 进行投资组合计算;

介绍

Numpy 是 Python 中令人难以置信的流行科学计算库。它支持数值运算,多维数组,线性代数,Nan 处理等等。我们在这里简要介绍一下让读者熟悉一些广泛的功能和应用。

在开始学习之前,让我们先导入一些我们需要用到的 Python 模块吧。

import numpy as npimport matplotlib.pyplot as plt

基本的 Numpy 数组

我们可以在金融中使用 Numpy 的最基本的方法是计算投资组合的平均收益。假设我们有一个包含多只股票历史回报的列表。

stock_list = [3.5, 5, 2, 8, 4.2]

我们可以通过调用 Numpy 中的函数来创建一个数组:

returns = np.array(stock_list)print(returns, type(returns))# 输出结果为:[ 3.5  5.   2.   8.   4.2] 

你会注意到我们的数据类型是 ndarray ,而不仅仅是数组。这是因为 Numpy 数组可以创建多个维度的数组。如果我们传递 np.array() 列表中的列表,也就是说它将创建二维数组。如果我们传递一个列表中的列表中的列表,那么也就是说它将创建一个三维数组,等等可以以此类推。

A = np.array([[1, 2], [3, 4]])print(A, type(A))

我们可以通过查看其维度成员变量来访问数组的维度。

print(A.shape)# 输出结果为:(2,2)

Numpy 数组中的索引切片和 Python 中的列表的索引切片都是一样的。元素索引的开始位置都是 0 ,结束位置是 n - 1 ,其中 n 是数组的长度。

print(returns[0], returns[len(returns) - 1])# 输出结果为:3.5 4.2

我们可以使用冒号来分割数组,就像在列表中操作的一样。

print(returns[1:3])# 输出结果为:[5. 2.]

像列表中操作的一样,数组的一个片段姜葱指定的第一个元素开始,选择数组中的一组元素,并指向最后一个元素。

在多维数组的情况下,切片和索引的操作和一维数组是相同的。我们可以像这样访问二维数组的第一列:

print(A[:, 0])# 输出结果为:[1 3]

我们可以按照这样来访问二维数组的第一行:

print(A[0, :])# 输出结果为:[1 2]

请注意,Numpy 数组的每一个切片都会返回另一个 Numpy 数组。

print(type(A[0,:]))# 输出结果为:

仅将一个索引传递给二维数组京导致返回具有给定索引的行,这为我们提供了访问数组每个行的另一种方式。

print(A[0])# 输出结果为:[1 2]

访问单个元素的索引数组只会返回该位置的元素。

print(A[1, 1])# 输出结果为:4

数组函数

内置在 Numpy 中的函数可以很容易的在数组中调用。大多数函数应用于数组中的各个元素(比如,矩阵乘法)。例如,如果我们在一个数组上调用 log() ,则将对数组中的每一个元素都取对数。

print(np.log(returns))# 输出结果为:[ 1.25276297  1.60943791  0.69314718  2.07944154  1.43508453]

有些函数会返回一个值。这是因为他们将数组视为一个集合(类似于列表),然后在整个集合上面进行操作,而不是在单个元素上面进行操作。例如,mean() 函数将按照你的希望,来计算整个数组中的平均值。

print(np.mean(returns))# 输出结果为:4.54

或者 max() 函数,这个函数将返回数组中的最大元素。

print(np.max(returns))# 输出结果为:8.0

如果你想要更多的了解 Numpy 中的内置函数,那么你可以阅读文档。

数组返回

现在让我们用标量值来修改我们的返回数组。如果我们为数组添加标量值,它将被添加到数组中的每一个元素。如果我们将一个数组乘以一个标量值,它将于数组中的每一个元素都相乘。如果我们同事做乘法和加法,那么两者都会发生。

returns*2 + 5# 输出结果为:array([ 12. ,  15. ,   9. ,  21. ,  13.4])

Numpy 还具有专门为数组操作而设计的功能,比如我们看看如何计算一组数据的均值和标准差。

print("Mean: ", np.mean(returns), "Std Dev: ", np.std(returns))# 输出结果为:Mean:  4.54 Std Dev:  1.99158228552

接下来,让我们 使用 Numpy 的函数模拟股票的全部情况。首先,我们需要创建数组来保存我们将用于构建投资组合的资产和回报。这是因为数组的大小是在创建的时候被确定的,如果我们不再重新创建新的数组,那么该数组的维度就不会被改变。

N = 10assets = np.zeros((N, 100))returns = np.zeros((N, 100))

此函数 zeroes() 创建了一个 Numpy 数组,其中的元素都是用零来进行填充的。我们可以按照我们自己喜欢的方式来创建单维数组或者多维数组。我们在此创建的是一个多维数组,它的维度是 (N, 100) ,也就是说数据将返回一个包含 N 行和 100 列的二维数组。我们最终得到的结果就是一个 Numpy 数组。

现在我们将来模拟一个基础股票的资产。我们希望所有的股票都是彼此相关的,所以我们将使用这个初始值来生成其他股票。

R_1 = np.random.normal(1.01, 0.03, 100)returns[0] = R_1assets[0] = np.cumprod(R_1)

Numpy 中的随机模块是非常有用的,它包含许多不同概率分布的抽样方法,其中一些在我们后续的随机变量中介绍。在这种情况下,我们从均值 1.01 和标准差 0.03 的正态分布中抽取随机样本,生成 N=100 条数据。我们将这些数据视为我们资产的每日回报,并将这些样本的累积作为当前价格。

我们生成的每只股票,都是一维的向量组。而回报值和资产变量则包含一个二维数组。基于以上讨论,我们将回报和资产的第一行分别设置为第一个向量和基于这些回报的累积资产价格。

# Generate assets that are correlated with R_1for i in range(1, N):    R_i = R_1 + np.random.normal(0.001, 0.02, 100)    returns[i] = R_i # Set each row of returns equal to the new R_i array    assets[i] = np.cumprod(R_i)mean_returns = [(np.mean(R) - 1)*100 for R in returns]return_volatilities = [np.std(R) for R in returns]

在这里,我们通过添加随机噪声来生成我们想要的剩下的 N-1 个股票。这也就确保了我们其他的股票价格将于我们构建的基础股票价格相关联了,因为它们具有一些共享的基础信息。

让我们绘制每种股票的平均收益情况:

plt.bar(np.arange(len(mean_returns)), mean_returns)plt.xlabel('Stock')plt.ylabel('Returns')plt.title('Returns for {0} Random Assets'.format(N));plt.show()

量化教程 2:Numpy 基础_第1张图片

计算预期回报

现在,我们已经有了一大堆的股票。这是一个非常好的消息!现在让我们把这些股票放到一个投资组合中,并且计算它们的预期收益和风险。

首先,我们为我们投资组合中的每个股票资产生成 N 个随机权重。

weights = np.random.uniform(0, 1, N)weights = weights/np.sum(weights)

我们必须重新调整我们的权重,以使得它们全部加起来为 1。我们通过将权重矢量除以权重总和来实现这一点。这一步确保了我们将使用投资组合 100% 的现金。

为了计算投资组合的平均收益率,我们必须按照指定的权重来衡量每项资产的收益。我们可以抽取每个数组的每个元素并让它们进行单独相乘,但是我们使用 Numpy 的线性代数方法会更加快。我们需要的函数是 dot() 。这将为我们计算两个阵列之间的点积。所以如果 v = [1, 2, 3]w = [4, 5, 6] ,那么我们就可以得到如下点积:

$v \cdot w = 1 \times 4+2 \times 5 + 3 \times 6$

对于一维向量,点积将逐点乘以每个元素,并且将最后的各个元素进行相加。在我们的例子中,我们有一个权重向量 $\omega = \left [ \omega_{1}, \omega_{2}, \omega_{3}, \cdots , \omega_{N} \right ]$ 和一个回报向量 $\mu = \left [ \mu_{1}, \mu_{2}, \mu_{3}, \cdots , \mu_{N} \right ]$ 。如果我们将这两个向量进行点积,那么我们将会得到如下公式:

$\omega \cdot \mu = \omega_{1} \mu_{1} + \omega_{2} \mu_{2} + \cdots +\omega_{N} \mu_{N} = \mu_{P}$

这产生了所有股票收益按照其各自权重缩放进行计算最总的资产收益。这就是投资组合的总体预期回报。

p_returns = np.dot(weights, mean_returns)print("Expected return of the portfolio: ", p_returns)# 输出结果为:Expected return of the portfolio:  0.938249456527

计算均值回报是非常直观的一种计算方式,我们不需要太多的线性代数只是。然而,如果我们去计算我们投资组合的方差,那么可能就需要更多的线性代数背景了。

谨防 NaN 值

在大多数情况下,所有这些数据都可以毫无问题的计算工作。但是,在我们处理真实数据的时候,我们的数据中可能会存在 NaN 数据。这是 Numpy 中的一种表达方法,表示这个数据缺失或者不存在。但是在计算中,这些 NaN 值会导致数学计算中的很多错误,因此了解数组中是否包含 NaN 值并指导如何删除它们是非常重要的一个准备工作。

v = np.array([1, 2, np.nan, 4, 5])print(v)# 输出结果为:[  1.   2.  nan   4.   5.]

让我们看看当我们想要尝试采用这个数组去计算平均值会发生些什么?

print(np.mean(v))# 输出结果为:nan

先让,NaN 值对我们的计算会有很大的影响。幸运的是,我们可以通过 isnan() 函数来检查,我们的 Numpy 数组中是否存在 NaN 值。

np.isnan(v)# 输出结果为:array([False, False,  True, False, False], dtype=bool)

我们在数组上面调用 isnan() 函数,如果数组中的某个元素是 nan ,那么该位置的返回值就是 True ,如果不是 nan ,那么函数就会在该位置返回 False 。现在,我们可以很容的知道一个 Numpy 数组是否包含 nan 值了。但是,下一步我们应该如何去删除这些 nan 值呢?别急,Numpy 也为我们准备好了秘密武器。在 Numpy 中我们可以通过布尔值进行索引(True 或者 False)。如果我们使用布尔数组来索引数组,我们将删除在该条件下索引为 False 的所有值。那么,我们也就找到了如何去删除 nan 值的方法。我们可以使用 isnan() 函数来创建一个布尔数组,将一个 True 值赋予所有不是 nan 的值,并且将一个 False 赋给 nans 值,然后我们就可以用这个布尔值数据来索引原来的数组。

ix = ~np.isnan(v) # the ~ indicates a logical not, inverting the boolsprint(v[ix]) # We can also just write v = v[~np.isnan(v)]# 输出结果为;[ 1.  2.  4.  5.]
print(np.mean(v[ix]))# 输出结果为:3.0

我们剔除 nan 值分为了两个步骤进行,但是在 Numpy 数组中也存在一些快捷方式来处理此类问题,比如 nanmean() 函数。

print(np.nanmean(v))# 输出结果为:3.0

nanmean() 函数简单的计算数组的平均值,就好像根本不存在 nan 值一样!还有很多类似的功能函数,如果你想要了解更多,请查看这个文档。这些值的不确定性对线性代数的处理是一个非常麻烦的事,所以我们先处理它们对我们后续的数据处理事非常有帮助的。

总结

线性代数在金融数据处理中普遍存在。例如,根据现代投资组合理论计算最有权重就是使用线性代数技术来完成的。Numpy 中的数组和函数允许我们以直观的方式处理这些计算。有关线性代数的快速介绍,我们会在下面部分进行介绍学习。

简单介绍线性代数

让我们从一些线性代数的基本概念开始吧。线性代数归结为标量和矩阵值的多重性组成。标量值是我们乘以数组的一个实数。当我们使用标量进行缩放数组或者矩阵时,其实我们只是将一个实数乘以数据或者矩阵中的每一个值。

矩阵其实可以看做是一些数的集合,它存在 m*n 个网络格子,其中 m 表示网格的行数,n 表示网格的列数。mn 的值并不一定是要相同的。如果我们存在 m = n ,那么我们称这个矩阵为方形矩阵。矩阵还有一个特别有意思的地方是我们可以取 m = 1 或者 n = 1 ,在这种情况下,我们把这个特殊的矩阵称之为向量。虽然在 Numpy 中有一个矩阵对象,但我们一般情况下都是通过 Numpy 数组分来完成我们所有的工作,因为有时候我们的数据维度可能是大于 2 的,那么这时候矩阵的表达不如 Numpy 数组来的好。在本节中,我们的目的是用于教学矩阵和数组,所以我们会交替来使用矩阵和数组。

我们可以将矩阵方差表示为如下:

$y = A\cdot x$

其中,A 是一个 m*n 的矩阵,y 是一个 m*1 的向量,x 是一个 n*1 的向量。在等式的右边,我们将一个矩阵乘以一个向量。这里我需要做一个简单的解释,不是任何的矩阵都可以跟任何的向量进行相乘的,只有当矩阵的第二维度和向量的第一维度相同时,才可以进行乘法操作。

矩阵乘法

使用矩阵乘法时,矩阵乘法的顺序非常重要。将左侧的矩阵乘以另一个矩阵可能会更好,而不是用右边的矩阵乘以左边的矩阵。

A = np.array([        [1, 2, 3, 12, 6],        [4, 5, 6, 15, 20],        [7, 8, 9, 10, 10]            ])B = np.array([        [4, 4, 2],        [2, 3, 1],        [6, 5, 8],        [9, 9, 9]    ])

请注意我们上面定义的矩阵 A 和 B,它们拥有不同的维度。A 的维度是 3*5,B 的维度是 4*3。正如我们前面所提到的,两个矩阵是否可以进行相乘是根据它们的维度来决定的。具体的说,就是两个矩阵相乘,左边矩阵的第二个维度数值必须等于右边矩阵的第一个维度数值。在非正式的数学表达式中,我们可以这样来理解,假设我们存在一个矩阵 m*n 维度,存在另一个矩阵 p*q 。如果我们用第一个矩阵乘以第二个矩阵,那么我们将得到如下的方程式:

$\left ( m\times n \right )\cdot \left ( p \times q \right ) = \left ( m\times q \right )$

因此,所以最终得到的矩阵具有与第一个矩阵相同的行数,与第二个矩阵相同的列数。矩阵乘法的维控制对程序的编写是非常重要的。为了证明这一点,让我们使用 dot() 函数来乘以下面的矩阵:

print(np.dot(A, B))# 输出结果为:---------------------------------------------------------------------------ValueError                                Traceback (most recent call last) in ()----> 1 print np.dot(A, B)ValueError: shapes (3,5) and (4,3) not aligned: 5 (dim 1) != 4 (dim 0)

这个结果符合我们刚刚所讨论的矩阵乘法规则。A 矩阵的第二维度跟 B 矩阵的第一维度不相同,所以导致了矩阵无法很好的进行相乘,从而报错。

print(np.dot(B,A))# 输出结果为:[[ 34  44  54 128 124] [ 21  27  33  79  82] [ 82 101 120 227 216] [108 135 162 333 324]]

投资组合差异

让我们回到之前的投资组合例子。我们计算了投资组合的预期回报,但是我们如何计算方差呢?我们首先尝试将投资组合评估为每个单项资产的总和,并按照他的权重进行缩放。

$VAR[P] = VAR[\omega_{1} S_{1} + \omega_{2} S_{2} + \omega_{3} S_{3} + \cdots + \omega_{N} S_{N} ]$

其中,$S_{0},\dots , S_{N}$ 是我们上面提到的各个单只股票资产。如果我们所有的股票资产都是相互独立的,那么我们可以简单的将其评估为:

$VAR[P] = VAR[\omega_{1} S_{1}] + VAR[\omega_{2} S_{2}] + VAR[\omega_{3} S_{3}] + \cdots + VAR[\omega_{N} S_{N}] = \omega_{1}^{2} \sigma_{1}^{2}+ \omega_{2}^{2} \sigma_{2}^{2}+ \cdots +\omega_{N}^{2} \sigma_{N}^{2}$

但是,我们在最开始生成股票资产的时候,我们是基于某个基础资产来进行生成的,也就是说它们在某种程度上与我们的基础资产是相关联的,也就是说各个资产之间有一种相互关联性。因此,我们必须通过包含每个资产的单独配对协方差来计算投资组合的方差。我们的投资组合反差公式为:

$VAR[P] = \sigma_{2}^{2} =\sum_{i}\omega_{i}^{2} \sigma_{i}^{2} + \sum_{i\not=j}\omega_{i}\omega_{j} \sigma_{i}\sigma_{j}\rho_{i,j} , \space\space\space\space\space \space i,j\in \left \{ 1,2,\cdots ,N \right \}$

其中,$\rho_{i,j}$ 是 $S_{i}$ 和 $S_{j}$ 的相关系数,$\rho_{i,j} = \frac{COV[S_{i}S_{j}]}{\sigma_{i}\sigma_{j}}$ 。这看起来非常复杂,但我们可以使用 Numpy 数组轻松的来实现这个过程。首先,我们计算与我们股票中所有个股相关的协方差矩阵。

cov_mat = np.cov(returns)print(cov_mat)# 输出结果为:[[ 0.00086058  0.00089203  0.00090705  0.00097867  0.00079707  0.00085327   0.00076728  0.00093279  0.00085655  0.00076459] [ 0.00089203  0.00133583  0.00097512  0.00099617  0.00082914  0.00089555   0.00087399  0.00092142  0.00080857  0.00083485] [ 0.00090705  0.00097512  0.00134549  0.00106267  0.00082659  0.00087943   0.00081558  0.00101182  0.0008822   0.00079261] [ 0.00097867  0.00099617  0.00106267  0.00151787  0.00094079  0.0010231   0.00086453  0.00106496  0.00100924  0.00090792] [ 0.00079707  0.00082914  0.00082659  0.00094079  0.00109599  0.00077462   0.00071631  0.00089835  0.00081464  0.00073657] [ 0.00085327  0.00089555  0.00087943  0.0010231   0.00077462  0.00124298   0.00072712  0.00096373  0.00078529  0.00079774] [ 0.00076728  0.00087399  0.00081558  0.00086453  0.00071631  0.00072712   0.00107769  0.0007624   0.00076273  0.00066191] [ 0.00093279  0.00092142  0.00101182  0.00106496  0.00089835  0.00096373   0.0007624   0.00141976  0.00086849  0.00085365] [ 0.00085655  0.00080857  0.0008822   0.00100924  0.00081464  0.00078529   0.00076273  0.00086849  0.00135418  0.00070977] [ 0.00076459  0.00083485  0.00079261  0.00090792  0.00073657  0.00079774   0.00066191  0.00085365  0.00070977  0.00106124]]

这个输出数组的格式不是很友好,但是协方差矩阵是一个非常重要的概念。协方差矩阵的具体形式可以看下面的形式:

$ \begin{bmatrix} VAR[S_{1}] & COV[S_{1}, S_{2}] & \dots & COV[S_{1}, S_{N}] \\ COV[S_{2}, S_{1}] & VAR[S_{2}] & \dots & COV[S_{2}, S_{N}] \\ \vdots & \vdots & \ddots & \vdots \\ COV[S_{N}, S_{1}] & COV[S_{N}, S_{2}] & \dots & VAR[S_{N}]\end{bmatrix}$

因此,每个对角线上面的元素就是指该处的资产的方差,并且每个非对角线都包含由列和行索引组成的两个资产的协方差。最重要的是,一旦我们有了协方差矩阵,我们就可以做一些非常快的线性代数来计算整个投资组合的方差。我们可以用数组形式来表达投资组合的反差:

$\sigma_{p}^{2} = \omega C \omega^{T}$

其中,C 是所有资产的协方差矩阵, w 是包含每个资产权重的数组。上面列出的第二个 w 的上标表示转置。有关方差和投资组合回报的资料,请参阅维基百科有关现代投资组合理论的文章。

数组的转置是你将数组的行和列进行互相对调。这你可以想象你沿着对角线将矩阵进行了反转。我们可以来举个例子,我们将我们之前的数组 A 进行转置:

print(A)# 输出结果为:[[ 1  2  3 12  6] [ 4  5  6 15 20] [ 7  8  9 10 10]]

转置之后的矩阵看起来就好像是原始矩阵的镜像。

print(np.transpose(A))# 输出结果为:[[ 1  4  7] [ 2  5  8] [ 3  6  9] [12 15 10] [ 6 20 10]]

但是这里 w 是一个一维数组,也就是一个向量!你可能看不出转置之后有什么区别。所以我们使用 3*5 的矩阵 A 来进行转置就非常完美了,因为转置之后的矩阵输出的维度将是 5*3 。但其实向量的转置我们也是用的非常频繁的,可以说是非常重要的。典型的一维数组,我们可以任务它是 1*n 的水平向量。因此,采用这个转置的方法,本质上是把它改变为 n*1 的垂直向量。看似这个操作什么都没有改变,但是这个操作时非常有用的,因为经过这样操作之后,我们可以更加灵活地与更高维度数组之间进行任何的乘法操作。

长话短说,我们认为 w 的表达矩阵是 1*N ,因为我们有 N 个股票。这就使得 $w^{T}$ 的维度编程 N*1 。再次,我们的协方差矩阵就是 N*N 。因此,整体的乘法表达式就变成如下所示:

$Dimensions(\sigma _{p}^{2}) = Dimensions(\omega C \omega^{T}) = (1\times N)\cdot (N \times N) \cdot (N \times 1) = (1 \times 1)$

我们在协方差矩阵的左边乘以水平向量,在它右边乘以该向量的转置,这样计算出来的就是投资组合的方差。

所以知道这一点之后,让我们继续计算投资组合方差!我们可以通过使用 dot() 来进行矩阵乘法轻松计算这些数组的乘积,但这一我们必须做两次。

# Calculating the portfolio volatilityvar_p = np.dot(np.dot(weights, cov_mat), weights.T)vol_p = np.sqrt(var_p)print("Portfolio volatility: ", vol_p)# 输出结果为:Portfolio volatility:  0.0297400694577

为了确认这个计算是否正确,我们只需使用 Numpy 函数来评估投资组合的波动性。

# Confirming calculationvol_p_alt = np.sqrt(np.var(np.dot(weights, returns), ddof=1))print("Portfolio volatility: ", vol_p_alt)# 输出结果为:Portfolio volatility:  0.0297400694577

ddof 参数是一个简单的整数输入,它告诉函数我们需要考虑的自由度数。这是一个跟统计相关的概念,但是结果告诉我们我们的矩阵计算是正确的。

从以上的程序分析,你可能会觉得这很没有意义,因为所有的一切都是在我们自己构造的伪数据上面进行分析的。但是这有助于帮助你在理论和代码之间来回切换,更好的将理论用于实践。有效的掌握线性代数知识,可以帮助我们简化大量的数据处理过程。如果你想要进一步了解 Numpy ,那么你可以查看这个文档。


本文首发于GitChat,未经授权不得转载,转载需与GitChat联系。

阅读全文: http://gitbook.cn/gitchat/activity/5afe4176a0810c23901c49df

您还可以下载 CSDN 旗下精品原创内容社区 GitChat App ,阅读更多 GitChat 专享技术内容哦。

FtooAtPSkEJwnW-9xkCLqSTRpBKX

你可能感兴趣的:(量化教程 2:Numpy 基础)