资产组合(均值-方差分析)Python绘图

liftword2个月前 (02-24)技术文章21

今天,我们介绍如何用Python绘制资产组合理论(又称“均值-方差分析”)中的资产组合散点图,它的最外侧、上半部的点所连成的线被称为效前沿,efficient frontier。当资产数为2时,各个点可以用一条曲线连起来,但资产数为3及3个以上时,所有点就组成一个面。

全部程序位于最下,请首先确保安装好如下库:pandas、numpy、matplotlib、datetime和yfinance。安装好后,我们就分5步,介绍如何获取股票数据、计算每只股票的收益率的均值和它们的方差-协方差矩阵(variance-covariance matrix,VCM)、如何生成组合、计算组合的均值和方差,最后,画出散点图。

第一步:获取股票数据。

使用Yahoo Finance(yfinance)库,导入需要分析的几只股票,这里先要查到它们的股票代码(Ticker symbol),美国股票是以英文缩写表示的,比如MSFT就是Microsoft(微软公司)的股票代码,BAC代表Bank of America(美国银行)。这里,选择频率为月度,即yf.download中的interval参数的值为'1mo',默认是日度。

下载后,数据包含了开盘价、收盘价、调整后的收盘价乃至交易量等变量。通常,我们选择调整后的收盘价(它调整了配股、分红等影响股价的因素,此处不细说)作为股票价格。然后,使用Pandas库中的pct_change函数计算收益率。

第二步:计算每只股票收益率的均值和它们的方差-协方差矩阵。

Pandas库中的函数mean、cov直接就能计算,mean函数默认就是对每一列求均值,不像Numpy等库中的均值函数那样还要写明对行还是对列求。但是,由于我们接下来要用到矩阵计算,因此,我们需要把Pandas计算出来的值通过to_numpy函数转换为向量或矩阵。

第三步:生成权重向量(矩阵)。

这是难点,为什么?首先在理论上,所谓的一个资产组合,其本质上就是一个权重向量,这是容易被遗忘或忽视的。另外,在实际操作上,我们要生成N个组合,N可以很大,比如10^4个,这不可能通过手动一个个地生成,还有,要随机地生成,因此,我们需要用到随机数生成函数。举例:先设N=10,我们现在考虑3只股票,因此我们要生成10×3的随机数矩阵,可是我们的目的是生成权重向量(矩阵),而这些随机数每一行加起来不等于1, 因此我们还要把每一行的随机数相加,用这个和去除每一个随机数,这就实现了新的随机数的每一行加和为1,符合权重向量的定义。

但是,这里面有一个很难处理的问题,就是权重为负的情况。我们知道,资产组合中,某些股票的权重是可以为负的。那么,我们只要把随机数的生成区间设置成由负到正不就可以了吗?这么做当然能生成随机数,但是,如果这样做,像我们上一段说的“加和后然后做除法”,其中有几行的加和,就可能因为正负相加的原因,出现很接近0的数,如此,分母很接近0,就会导致权重出现极端值,几千几万都可能,从而进一步导致下一步中组合的均值、方差出现极端值,最终导致画图失败。为此,解决的方法是,要么不考虑负权重,要么在这一步允许负权重,但是在下一步,删去极端值。

第四步:计算组合的期望收益率和波动率。

这是计算的难点。首先,我们必须知道,就一个组合而言,其期望收益率和方差(波动率的平方)的计算公式分别为:



这里分别为第二步计算的股票收益率均值向量和方差-协方差矩阵。但是,这里编程的难点是,我们不止有一个权重向量,我们生成的是N个权重向量,也就是一个权重矩阵,比如记作。因此,需要动脑几分钟,想想怎样相乘才能一次性地得到N个组合的均值、N个组合的方差。如果实在想不出来,就用循环,根据上面两个式子计算每一个组合的期望收益率和方差,这样循环N次。

这里,我们采用点乘与矩阵乘相配合的方法。用权重矩阵点乘股票均值向量,再按列加和,就得到了N个组合的均值。计算组合方差时,先用矩阵乘法(符号为@),把“N*股票数”(比如股票数为3)维度的权重矩阵与“股票数*股票数”维度的方差-协方差矩阵相乘,得到“N*股票数”的一个矩阵,再用它点乘,再按列加和,就得到了N个组合的方差。最后,别忘开根号,因为我们用的是波动率。

第五步:绘图。

使用matplotlib.pyplot,绘制散点图。参数中,可以选择点的透明度、大小和colormap的样式。最后,用plt.show()呈现图像,括号中可根据自己电脑的情况,看看是否要加block=True,有时不加可能图像无法呈现。两种随机数生成方法绘制的散点图如下:

以上就是绘制资产组合图像的简要介绍,有些细节,由于时间和精力限制问题,难以一一说明,程序可直接复制运行,除了有些地方可能需要根据自己的Python版本和环境进行微调外,大概率一次就能成功。

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from datetime import datetime
import yfinance as yf

# Step 1. Download montly level stock data
start_date = datetime(2018, 1, 1)
end_date = datetime(2021, 1, 1)
tickers = ['AAPL', 'TSLA', 'MSFT', 'BAC', 'JPM']
data = yf.download(tickers, start=start_date, end=end_date, interval='1mo')
# use adjusted close prices
dataclose = data['Adj Close']  # data.columns --> MultiIndex, e.g., ([('Adj Close', 'AAPL'), ('Adj Close', 'MSFT'), ...]
# calculate returns
stockret = dataclose.pct_change()

# Step 2. Cacluate average returns and the variance-covariance matrix
retmean = stockret.mean().to_numpy()
retmean.shape = (1, len(tickers)) 
vcm = stockret.cov().to_numpy()

# Step 3. Generate random variables and weights
N = 20000  # 1000 above is fine, bigger than 10^5 makes plotting slow
rvmethod = 0  # Set it to be 0 or other values
if rvmethod == 0:
    rv = np.random.uniform(0.1, 5, size=[N, len(tickers)])
else:
    rv = np.random.normal(0, 5, size=[N, len(tickers)])  # This method allows for negative weights but need to exclude extreme values later

rvsum = np.sum(rv, axis=1)
rvsumrep = np.matlib.repmat(rvsum, len(tickers), 1).T  # replicate the column vector to facilitate division in the next line
weightmat = rv/rvsumrep  # important step: generate weight matrix
print(np.sum(weightmat, axis=1))  # check whether weights in each row sum up to one

# Step 4. Cacluate mean return and volatility of porfolios
portret = np.sum(weightmat*retmean, axis=1)  # 点乘时,dim(weightmat)=N*股票数, dim(retmean)=1*股票数,Python能自动理解是weightmat的每一行与retmean点乘。
portret.shape = (N, 1)
portvol = np.sqrt(np.sum((weightmat@vcm) * weightmat, axis=1))  # @是矩阵乘,*是点乘,即相同位置的元素相乘,这是计算的难点,实在不行就一个个地循环。
portvol.shape = (N, 1)

# this step is mainly for dropping extreme values
tab0 = pd.DataFrame(np.hstack((portvol, portret)), columns=['portvol', 'portret'])
if rvmethod == 0:
    tab = tab0
else:
    tab = tab0[tab0['portvol'] <= 0.15]

# Step 5. Draw scatter plot
f1 = plt.figure(1)
plt.scatter(tab['portvol'], tab['portret'], alpha=0.5, s=100, c=np.linspace(1, 10, len(tab)), cmap='viridis')
plt.xlabel("Portfolio Volatility", fontsize=14)
plt.ylabel("Portfolio Expected Return", fontsize=14)
plt.grid(linestyle=':')
plt.show(block=True)

相关文章

Python矩阵操作详解

Python 处理矩阵的方式有很多,其中最常用的库是 NumPy,它提供了高效的矩阵运算和操作能力。此外,pandas、SciPy 和 TensorFlow 等库也支持矩阵运算。下面介绍一下矩阵操作的...

巧用python的矩阵运算来进行电路的计算和分析

多元一次方程组早在初中时,我们就学习过多元一次方程组的求程。所谓多元一次组指的是含有有个未知数,并且含有未知数的项的次数都是1方程组。一个简单的例子:学校的篮球数比排球数的2倍多3个,足球数与排球数的...

仅用 10 行 Python 代码,搞定 10 种数学运算!

【CSDN 编者按】在如今这个 AI 飞速发展的时代,Python 以其简洁、易读的语法和强大的表现力,深受程序员和科学家的喜爱。本文将展示在仅仅 10 行代码内,Python 如何实现复杂的数学运算...

如何在 Python 中制作矩阵:详细指南

什么是矩阵?在深入研究之前,让我们了解一下矩阵是什么:它是一个按行和列排列的数字的矩形数组。在 Python 中,我们通常使用嵌套列表来表示矩阵,其中每个内部列表代表一行。使用列表创建基本矩阵让我们从...

学习Numpy,看这篇文章就够啦

导读:在数据分析当中,Python用到最多的第三方库就是Numpy。本文内容是「大数据DT」内容合伙人王皓阅读学习《Python 3智能数据分析快速入门》过后的思考和补充,结合这本书一起学习,效果更佳...

进入Python的世界27-NumPy 库的介绍及学习方法

NumPy是多个高级库的基础,掌握深度学习之前,必须先学习这个库,让咱们一起开始学习了解一下这个库,形成自己的学习与知识体系。一 NumPy 主要框架内容1. ndarray对象ndarray(N-d...