复习完矩阵微分和求导的知识[1],我们继续第二讲。如何给定投资组合的目标,在约束条件下,求出最佳的配置?
主要参考了MIT的均值方差模型[2]。
符号说明
- 一共有m个风险资产,
- m个资产的历史均值收益率向量为
- 收益率期望 ,收益率的协方差矩阵为
- 我们想要利用这m个资产,配置“理想”的权重,得到投资组合;其中,权重向量为 ,且 ,即为权重和等于1。
- 投资组合的情况:投资组合的收益率 ,注意,R_w是一个标量;投资组合收益率的方差 ,这也是一个标量。
- 投资组合的期望收益率:
- 实际操作中,我们会把期望收益率=历史收益率的均值。
组合1:投资组合最小波动率
- 给定投资组合的预期收益率为 ,如何选择权重向量,使得投资组合的波动率最小?
- 数学表达式
首先,这里较为困难的就是标量L对向量w的求导,用到第一讲的内容。
另外对 的求导很简单,就是标量的导数:
三个偏导数都是等于0;
链接:https://pan.baidu.com/s/1GUVyCMXBzFjIcuKeY5c18w
提取码:le24
如果你用自己的数据,数据在excel中得按这种格式,避免格式的麻烦(毕竟主要目标是计算权重,而不是清洗数据)。
图1:月度收益率,每一列是一个股票的收益率时间序列
在使用程序计算的时候,需要小心时间频率。我们的目标都是对投资组合的年化数据进行处理。例如,样本数据是资产收益率的月度序列,在计算样本平均收益率的时候,记得将每个均值*12,化成年化收益率。
问题来了:收益率协方差矩阵要不要*12进行年化?首先,假设不乘以12,协方差矩阵(1,1)位置是资产1的收益率的方差,该方差是月度数据计算出来的,因此是月度方差;那么应该*12,使其等于年化的方差才行。因此协方差矩阵也要乘以12,进行年化。
import pandas as pd
import numpy as np
def get_ret_and_cov(df, frequency):
"""输入:股票时间序列
输出:股票年画平均收益,股票收益的年化标准差,股票收益率的年化协方差矩阵。
frequency控制了数据频率:如果是日度数据,frequency=252;
如果是周度数据,frequency=52;如果是月度数据,frequency=12。本例中是月度数据
"""
ret_ave = pd.DataFrame(df.mean()).reset_index()
ret_ave.columns = ['Stkcd', 'ret_ave']
# 年化收益率
ret_ave['ret_ave'] = ret_ave['ret_ave'] * frequency
ret_std = pd.DataFrame(df.std()).reset_index()
ret_std.columns = ['Stkcd', 'ret_std']
ret_std['ret_std'] = ret_std['ret_std'] * np.sqrt(frequency)
# 年化协方差矩阵
ret_cov = df.cov() * frequency
return ret_ave, ret_std, ret_cov
def mini_vol(number, ret_ave_vector, ret_cov_matrix, target_ret):
"""number是股票个数;ret_ave_vector是股票的收益率向量,注意是列向量;
ret_cov_matrix是股票收益率的协方差矩阵;
target_ret是目标收益率
"""
# 协方差矩阵的逆矩阵
ret_cov_matrix_I = ret_cov_matrix.I
# 全1向量e
e = np.matrix([1]*number).reshape((-1,1))
# 4*4的系数矩阵
a = float(np.dot(np.dot(ret_ave_vector.T, ret_cov_matrix_I), ret_ave_vector))
b = float(np.dot(np.dot(e.T, ret_cov_matrix_I), ret_ave_vector))
c = float(np.dot(np.dot(e.T, ret_cov_matrix_I), e))
# 得到系数矩阵
coeff_matrix = np.matrix([a,b,b,c]).reshape((2,2))
coeff_matrix_I = coeff_matrix.I
# 解方程
y1 = np.matrix([target_ret, 1]).reshape((-1,1))
lambda_result = np.dot(coeff_matrix_I, y1)
lambda1 = float(lambda_result[0])
lambda2 = float(lambda_result[1])
# 得到权重
weights = lambda1 * np.dot(ret_cov_matrix_I, ret_ave_vector) + lambda2 * np.dot(
ret_cov_matrix_I, e)
return weights
def port_info(weights, ret_ave_vector, ret_cov_matrix):
"""计算投资组合的年化收益率,以及年化波动率"""
port_return = float(np.dot(weights.T, ret_ave_vector))
port_var = float(np.dot(np.dot(weights.T, ret_cov_matrix),weights))
port_std = np.sqrt(port_var)
return port_return, port_std
monthly_data = pd.read_csv(r'...\return.csv', engine='python')
ret_ave, ret_std, ret_cov = get_ret_and_cov(m_data, 12)
# 生成收益率的列向量+协方差矩阵
ret_ave1 = np.matrix(ret_ave['ret_ave']).reshape((-1,1))
ret_cov1 = np.matrix(ret_cov)
# 计算权重:假设我的目标收益率是0
weights = mini_vol(4, ret_ave1, ret_cov1, 0)
# 计算投资组合的信息
port_return, port_std = port_info(weights, ret_ave1, ret_cov1)组合2:投资组合最大收益率
- 给定投资组合的预期波动率为 ,如何选择权重向量,使得投资组合的收益率最大?
- 数学表达式
首先对w求偏导:
另外对 的求导很简单,就是标量的导数:
三个偏导数都是等于0;
在运行程序前,注意点:
先找出所有资产中,最低的一个波动率( )。这是能够达到的极限,也就是只有这个资产权重为1,其他全是0。如果设置波动率比它还小,那么是无法达到的。这个前提条件,也是上面方程组有解的前提条件。
程序:
def get_smallest_vol(ret_cov_matrix):
"""找出所有资产中最小的波动率;注意是返回σ,要开根号"""
diags = np.diag(ret_cov_matrix)
return np.sqrt(np.min(diags))
def max_ret(number, ret_ave_vector, ret_cov_matrix, target_vol):
"""
number是股票个数;ret_ave_vector是股票的收益率向量,注意是列向量;
ret_cov_matrix是股票收益率的协方差矩阵;
target_vol是目标波动率,注意是σ。
"""
ret_cov_matrix_I = ret_cov_matrix.I
e = np.matrix([1]*number).reshape((-1,1))
a = float(np.dot(np.dot(ret_ave_vector.T, ret_cov_matrix_I), ret_ave_vector))
b = float(np.dot(np.dot(ret_ave_vector.T, ret_cov_matrix_I), e))
c = float(np.dot(np.dot(e.T, ret_cov_matrix_I), e))
A = (target_vol * c) ** 2 - c
B = 2 * b * (1 - c * target_vol ** 2)
C = (target_vol * b) ** 2 - a
delta = B ** 2 - 4 * A * C
lambda21 = 0.5 / A * (- B + np.sqrt(delta))
lambda22 = 0.5 / A * (- B - np.sqrt(delta))
lambda11 = (b - c * lambda21) / 2
lambda12 = (b - c * lambda22) / 2
weights1 = np.dot(ret_cov_matrix_I, (ret_ave_vector - lambda21 * e))/(2*lambda11)
weights2 = np.dot(ret_cov_matrix_I, (ret_ave_vector - lambda22 * e))/(2*lambda12)
return weights1, weights2其他程序和例1相同。
# 首先判断所能够达到的最小波动率σ,下面穿进去的target_vol绝对不能够比它小:
print(get_smallest_vol(ret_cov1))
# 计算权重:假设我的目标波动率是40%
weights1, weights2 = max_ret(4, ret_ave1, ret_cov1, 0.4)
# 计算投资组合的信息
port_return1, port_std1 = port_info(weights1, ret_ave1, ret_cov1)
port_return2, port_std2 = port_info(weights2, ret_ave1, ret_cov1)有效前沿
事实上,利用例子1,我们能够把有效前沿找出来。
import matplotlib.pyplot as plt
port_ret = []
port_std = []
# 收益率从0到1,步长为0.00001,一共10万个收益率样本
ranges = np.arange(0,1, 0.00001)
for i in ranges:
weight = mini_vol(4, ret_ave1, ret_cov1, i)
port_ret1, port_std1 = port_info(weight, ret_ave1, ret_cov1)
port_ret.append(port_ret1)
port_std.append(port_std1)
port_std = np.array(port_std)
port_ret = np.array(port_ret)
plt.figure(figsize = (16,9))
plt.scatter(port_std,port_ret,c = port_ret/port_std, marker = 'o')
plt.grid(True)
plt.xlabel('excepted volatility')
plt.ylabel('expected return')
plt.colorbar(label = 'Sharpe ratio')
图2:有效前沿
怎么样?感觉美不美?是不是比大批量采样更加简洁?
结语
这次主要把矩阵微分和求导复习了一下。这里特别感谢雷敬华老师,把我们的计量经济学教得很扎实;感谢魏丽老师,教给我们很棒的规划求解知识。回想当时,自己还不会任何编程知识,一直都是纸上谈兵;现在才发现,老师们给我们这么多宝贵的知识。编程只是工具,而有趣的思想才是首要目标。如果硬核模拟撒点求有效前沿,程序较为复杂;但是通过规划求解,很简单就把权重求了出来。
欢迎正在阅读这篇文章的你,来人大读书。它真的是个好学校。
参考
- ^https://zhuanlan.zhihu.com/p/137761052
- ^https://ocw.mit.edu/courses/mathematics/18-s096-topics-in-mathematics-with-applications-in-finance-fall-2013/lecture-notes/MIT18_S096F13_lecnote14.pdf
|
|