目录
1. 学习目标
- 使用 Python 实现不同的投资配比
- 使用 Python 实现均值-方差模型
2. 操作讲解
通过上一个任务,你对马科维茨的均值-方差投资组合模型已经有所了解了。那么在 Python 中该如何实现呢?整个过程有些复杂,和之前使用Excel的实现也有较大差异。因此我们会将整段代码拆解一下,给你逐个讲解。大体上,这段代码将分为以下几个主要的步骤:
- 收集两支股票(阿里巴巴和新东方教育)的信息,主要是从 2020 年初至今,每日的收盘价格
- 计算两支股票的每日对数收益率和每日协方差
- 设置不同的股票配置比例,例如股票 A 从 0% 到 100%,每次递增 5%,股票 B 从 100% 到 0%,每次递减 5%。并将所有这些不同的配置比例记录在一个列表中,作为最终结果的对照。
- 对于每种配置比例,计算综合性的投资对数收益率和基于标准差的不确定性风险。并将它们记录下来
- 通过图表,可视化综合收益和综合风险之间的关系,找到最佳平衡点。
对于之前已经讲述过的代码,这里不再重复,我们只挑出有改变或者新增的部分重点讲解。
首先,我们要收集阿里巴巴和新东方教育这两支股票的信息。接下来,我们需要尝试不同的股票占比。为此,我们尝试从 0% 的阿里巴巴,100% 的新东方教育,到5% 的阿里巴巴,95% 的新东方教育,等等一直到 100% 的阿里巴巴,0% 的新东方教育,一共 20 种可能。代码层面会使用 Python 的循环语句来实现。
# 尝试20种不同的投资配比,例如(0.0, 1.0), (0.05, 0.95), ..., (0.95, 0.05), (1.0, 0.0)
interval = 0.05
rounds = 20
# 定义持股比例的列表,用于存放20种不同投资比例
ratio = []
# 定义综合收益的列表,用于存放20种不同投资比例下的综合收益
combined_returns = []
# 定义综合风险的列表,用于存放20种不同投资比例下的综合风险(标准差)
combined_risks = []
for i in range(0, rounds + 1):
weight1 = i * interval
weight2 = 1.0 - i * interval
# 输出当前的股票占比
print(weight1, weight2)
其中,for i in range(0, rounds + 1) 是一种新的循环方式,它将产生一系列的 i,从 0,1,2,…,20。
注意,索引是从 0 开始,而且最后不会到 21,而是到 20 结束。这样第一支股票的占比就是 i * interval,而第二支股票的占比就是 1.0 - i * interval。第一次循环的时候,i=0,这两个比例就分别是 0%和100%。以此类推,具体数据如下表:
当然,你可以设置更小的变化幅度 interval,以及对应的组合次数 rounds。
在循环的过程中,对于每种投资比例,我们都要计算综合收益和综合风险,然后使用列表的数据结构记录下来。
具体代码如下:
# 将持股比例加入列表
ratio.append([weight1, weight2])
weights = np.array([weight1, weight2])
# 按照持股比例,计算预期的年均综合收益
combined_return = np.sum(weights * returns.mean()) * 250
# 将这个综合收益加入列表
combined_returns.append(combined_return)
# 按照持股比例,计算年均的综合标准差,作为风险
combined_risk = np.sqrt(np.dot(weights.T, np.dot(returns.cov() * 250, weights)))
# 将这个综合收益加入列表
combined_risks.append(combined_risk)
这里使用了年均的收益率和风险计算。需要注意的是,相比于综合收益率的计算,综合风险的计算稍微复杂一点,我们使用了矩阵的点乘和矩阵的转置。其中,weights.T 表示矩阵(这里是向量)的转置,np.dot(returns.cov() * 250, weights) 将协方差矩阵和权重向量进行点乘,之间让 weights.T 向量点乘np.dot(returns.cov() * 250, weights) 的结果。最终,使用 np.sqrt() 函数开根号,这一步实现的就是如下公式。
格式上不如 Excel 那么直观,需要你仔细对照。为了便于理解,我们也可以使用如下循环进行输出。
for i in range(0, len(ratio)):
print('阿里和新东方的股票配比为', ratio[i], '的时候,综合收益为', combined_returns[i], ',综合风险为', combined_risks[i])
请注意,由于Python浮点数精度的原因,可能会出现0.15000…或者0.64999…的情况,实际上分别对应的就是0.15和0.65。
你还可以使用Python进行可视化,使用下述代码将散点图输出。
combination = pd.DataFrame({'Return': combined_returns, 'Risk': combined_risks})
combination.plot(x='Risk', y='Return', kind='scatter', figsize=(10, 6))
plt.xlabel('Expected Risk')
plt.ylabel('Expected Return')
plt.show()
从结果可以看到,在持股 55% 的阿里巴巴、45% 的新东方教育的时候,我们达到了一个均衡点,也就是 58.68% 的综合收益和 42.41% 的综合风险。当然,如果你尝试的股票时间周期和这里不同,那么结果也会有所不同。
完整的代码我列在这里,便于你阅读和实践。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# 使用多个股票代码,构建新的dataframe
BABA = pd.read_csv('./BABA.csv', sep='\t', index_col=0)
EDU = pd.read_csv('./EDU.csv', sep='\t', index_col=0)
stock_data = pd.DataFrame()
stock_data['BABA'] = BABA['Adj Close']
stock_data['EDU'] = EDU['Adj Close']
# 获取两支股票的每日对数收益率
returns = np.log(stock_data / stock_data.shift(1))
print(returns)
# 输出两支股票的协方差矩阵
print(returns.cov())
# 尝试20种不同的投资配比,例如(0.0, 1.0), (0.05, 0.95), ..., (0.95, 0.05), (1.0, 0.0)
interval = 0.05
rounds = 20
# 定义持股比例的列表,用于存放20种不同投资比例
ratio = []
# 定义综合收益的列表,用于存放20种不同投资比例下的综合收益
combined_returns = []
# 定义综合风险的列表,用于存放20种不同投资比例下的综合风险(标准差)
combined_risks = []
for i in range(0, rounds + 1):
weight1 = i * interval
weight2 = 1.0 - i * interval
# 输出当前的股票占比
print(weight1, weight2)
# 将持股比例加入列表
ratio.append([weight1, weight2])
weights = np.array([weight1, weight2])
# 按照持股比例,计算预期的年均综合收益
combined_return = np.sum(weights * returns.mean()) * 250
# 将这个综合收益加入列表
combined_returns.append(combined_return)
# 按照持股比例,计算年均的综合标准差,作为风险
combined_risk = np.sqrt(np.dot(weights.T, np.dot(returns.cov() * 250, weights)))
# 将这个综合收益加入列表
combined_risks.append(combined_risk)
print('阿里和新东方的股票配比', ratio)
print('综合收益率', combined_returns)
print('综合风险', combined_risks)
for i in range(0, len(ratio)):
print('阿里和新东方的股票配比为', ratio[i], '的时候,综合收益为', combined_returns[i], ',综合风险为', combined_risks[i])
combination = pd.DataFrame({'Return': combined_returns, 'Risk': combined_risks})
combination.plot(x='Risk', y='Return', kind='scatter', figsize=(10, 6))
plt.xlabel('Expected Risk')
plt.ylabel('Expected Return')
plt.show()
好了,内容我讲完了,轮到你来动手练习了,请参照上述 Python 代码的实现,完成下面两个小任务。
- 请将上述代码中的对数收益率,替换为简单收益率,看看均值-方差模型的结果有何不同;
- 完成上一步之后,请使用均值-方差模型,分析腾讯和新东方教育这两支股票的投资组合。
在完成练习的同时,请你注意把关键过程和最终结果截图,放到一个文件夹中打包,并用“任务5”来命名,我们在项目的「课后作业」部分为你设置了统一提交的入口。
3、作业结果
1.、作业1
# -*- coding: utf-8 -*-
# @Software: PyCharm
# @File : Task5-1.py
# @Author : Benjamin
# @Time : 2021/9/13 11:45
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# 使用多个股票代码,构建新的dataframe
BABA = pd.read_csv('./BABA.csv', sep='\t', index_col=0)
EDU = pd.read_csv('./EDU.csv', sep='\t', index_col=0)
stock_data = pd.DataFrame()
stock_data['BABA'] = BABA['Adj Close']
stock_data['EDU'] = EDU['Adj Close']
# 获取两支股票的每日对数收益率
#returns = np.log(stock_data / stock_data.shift(1))
#print(returns)
# 获取全部股票的每日简单收益率
returns = stock_data / stock_data.shift(1) - 1
print(returns)
# 输出两支股票的协方差矩阵
print(returns.cov())
# 尝试20种不同的投资配比,例如(0.0, 1.0), (0.05, 0.95), ..., (0.95, 0.05), (1.0, 0.0)
interval = 0.05
rounds = 20
# 定义持股比例的列表,用于存放20种不同投资比例
ratio = []
# 定义综合收益的列表,用于存放20种不同投资比例下的综合收益
combined_returns = []
# 定义综合风险的列表,用于存放20种不同投资比例下的综合风险(标准差)
combined_risks = []
for i in range(0, rounds + 1):
weight1 = i * interval
weight2 = 1.0 - i * interval
# 输出当前的股票占比
print(weight1, weight2)
# 将持股比例加入列表
ratio.append([weight1, weight2])
weights = np.array([weight1, weight2])
# 按照持股比例,计算预期的年均综合收益
combined_return = np.sum(weights * returns.mean()) * 250
# 将这个综合收益加入列表
combined_returns.append(combined_return)
# 按照持股比例,计算年均的综合标准差,作为风险
combined_risk = np.sqrt(np.dot(weights.T, np.dot(returns.cov() * 250, weights)))
# 将这个综合收益加入列表
combined_risks.append(combined_risk)
print('阿里和新东方的股票配比', ratio)
print('综合收益率', combined_returns)
print('综合风险', combined_risks)
for i in range(0, len(ratio)):
print('阿里和新东方的股票配比为', ratio[i], '的时候,综合收益为', combined_returns[i], ',综合风险为', combined_risks[i])
combination = pd.DataFrame({'Return': combined_returns, 'Risk': combined_risks})
combination.plot(x='Risk', y='Return', kind='scatter', figsize=(10, 6))
plt.xlabel('Expected Risk')
plt.ylabel('Expected Return')
plt.show()
2、作业2
# -*- coding: utf-8 -*-
# @Software: PyCharm
# @File : Task5-2.py
# @Author : Benjamin
# @Time : 2021/9/13 11:45
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# 使用多个股票代码,构建新的dataframe
TC = pd.read_csv('./TC.csv', sep='\t', index_col=0)
EDU = pd.read_csv('./EDU.csv', sep='\t', index_col=0)
stock_data = pd.DataFrame()
stock_data['TC'] = TC['Adj Close']
stock_data['EDU'] = EDU['Adj Close']
# 获取两支股票的每日对数收益率
#returns = np.log(stock_data / stock_data.shift(1))
#print(returns)
# 获取全部股票的每日简单收益率
returns = stock_data / stock_data.shift(1) - 1
print(returns)
# 输出两支股票的协方差矩阵
print(returns.cov())
# 尝试20种不同的投资配比,例如(0.0, 1.0), (0.05, 0.95), ..., (0.95, 0.05), (1.0, 0.0)
interval = 0.05
rounds = 20
# 定义持股比例的列表,用于存放20种不同投资比例
ratio = []
# 定义综合收益的列表,用于存放20种不同投资比例下的综合收益
combined_returns = []
# 定义综合风险的列表,用于存放20种不同投资比例下的综合风险(标准差)
combined_risks = []
for i in range(0, rounds + 1):
weight1 = i * interval
weight2 = 1.0 - i * interval
# 输出当前的股票占比
print(weight1, weight2)
# 将持股比例加入列表
ratio.append([weight1, weight2])
weights = np.array([weight1, weight2])
# 按照持股比例,计算预期的年均综合收益
combined_return = np.sum(weights * returns.mean()) * 250
# 将这个综合收益加入列表
combined_returns.append(combined_return)
# 按照持股比例,计算年均的综合标准差,作为风险
combined_risk = np.sqrt(np.dot(weights.T, np.dot(returns.cov() * 250, weights)))
# 将这个综合收益加入列表
combined_risks.append(combined_risk)
print('阿里和新东方的股票配比', ratio)
print('综合收益率', combined_returns)
print('综合风险', combined_risks)
for i in range(0, len(ratio)):
print('阿里和新东方的股票配比为', ratio[i], '的时候,综合收益为', combined_returns[i], ',综合风险为', combined_risks[i])
combination = pd.DataFrame({'Return': combined_returns, 'Risk': combined_risks})
combination.plot(x='Risk', y='Return', kind='scatter', figsize=(10, 6))
plt.xlabel('Expected Risk')
plt.ylabel('Expected Return')
plt.show()
如果觉得文章写不错,那就点个赞,点个收藏吧。
可关注微信公众号,后期有推文