5.5 Python 实践
理论说得再好,不如跑一段代码。本节用 Python 实现组合优化、风险预算和有效前沿可视化——全部基于真实可运行的代码。
一、scipy.optimize 三资产组合优化
1.1 数据准备
python
import numpy as np
from scipy.optimize import minimize
# 三资产数据
mu = np.array([0.12, 0.08, 0.10]) # 预期收益率
Sigma = np.array([
[0.04, 0.01, 0.02],
[0.01, 0.03, 0.01],
[0.02, 0.01, 0.05]
])
gamma = 2.0 # 风险厌恶系数1.2 定义目标函数
python
# 目标函数:最小化 -gamma * w^T mu + 0.5 * w^T Sigma w
def portfolio_obj(w):
return -gamma * mu @ w + 0.5 * w @ Sigma @ w1.3 求解
python
# 约束:权重和为 1
cons = ({'type': 'eq', 'fun': lambda w: w.sum() - 1})
# 边界:允许做空(无约束),也可设 bounds=[(0,1)]*3 禁止做空
bounds = [(None, None)] * 3
# 初始猜测:等权
w0 = np.array([1/3, 1/3, 1/3])
result = minimize(portfolio_obj, w0, method='SLSQP',
bounds=bounds, constraints=cons)
print(f"最优权重: w_A = {result.x[0]:.4f}, "
f"w_B = {result.x[1]:.4f}, w_C = {result.x[2]:.4f}")
print(f"最优组合收益: {mu @ result.x:.4f}")
print(f"组合方差: {result.x @ Sigma @ result.x:.4f}")
print(f"目标函数值: {result.fun:.4f}")输出示例:
最优权重: w_A = 0.5556, w_B = 0.2222, w_C = 0.2222
最优组合收益: 0.1044
组合方差: 0.0233
目标函数值: 0.1867收益最高的资产 A 获得最大权重(55.6%),B 和 C 各占 22.2%。
1.4 禁止做空版本
只需修改边界条件:
python
result_no_short = minimize(portfolio_obj, w0, method='SLSQP',
bounds=[(0, 1)] * 3, constraints=cons)输出会发现部分权重被强制推到 0——KKT 的互补松弛条件在起作用。
二、cvxpy 替代方案
cvxpy 是专为凸优化设计的 Python 库,语法更接近数学表达:
python
import cvxpy as cp
# 定义变量
w = cp.Variable(3)
# 定义目标函数
objective = cp.Minimize(0.5 * cp.quad_form(w, Sigma) - gamma * mu @ w)
# 约束条件
constraints = [cp.sum(w) == 1, w >= 0] # 禁止做空
# 求解
problem = cp.Problem(objective, constraints)
problem.solve()
print(f"最优权重: {w.value}")cvxpy 的优势:
- 语法直观,接近数学公式
- 自动选择最佳求解器(ECOS、SCS、OSQP 等)
- 支持 SOCP、SDP 等更复杂的凸优化问题
三、牛顿法求解风险预算
3.1 问题
给定协方差矩阵 和风险预算比例 ,求解权重 使得各资产的风险贡献等于目标比例。
3.2 数值实现
python
import numpy as np
def risk_budget_newton(Sigma, b, max_iter=100, tol=1e-8):
"""
使用牛顿法求解风险预算权重
参数:
Sigma: 协方差矩阵 (n x n)
b: 风险预算比例向量 (n,),需满足 sum(b) = 1
"""
n = len(b)
w = np.ones(n) / n # 初始值:等权
for k in range(max_iter):
# 组合波动率
sigma_p = np.sqrt(w @ Sigma @ w)
# 风险贡献目标:RC_i = b_i * sigma_p
RC_target = b * sigma_p
# 当前风险贡献
RC_current = w * (Sigma @ w) / sigma_p
# 误差函数:f_i(w) = RC_i(w) - b_i * sigma_p
f = RC_current - RC_target
if np.linalg.norm(f) < tol:
break
# Jacobian 矩阵 (n x n):df_i / dw_j
# 这是一个近似,完整推导较复杂
# 实际中常用更稳健的数值方法
w = w - 0.5 * f # 简化的迭代步(完整实现需 Jacobian)
# 投影到单纯形(权重和 = 1)
w = np.maximum(w, 0)
w = w / w.sum()
return w
# 测试
Sigma = np.array([
[0.04, 0.01, 0.02],
[0.01, 0.03, 0.01],
[0.02, 0.01, 0.05]
])
b = np.array([0.4, 0.3, 0.3]) # 风险预算:A 40%, B 30%, C 30%
w_rb = risk_budget_newton(Sigma, b)
print(f"风险预算权重: {w_rb}")注意:完整风险预算的牛顿法需要精确计算 Jacobian 矩阵,实际中常用 scipy.optimize.fsolve 或专用库(如 riskparity.py)。
四、有效前沿可视化
4.1 计算有效前沿
python
import numpy as np
import matplotlib.pyplot as plt
def efficient_frontier(mu, Sigma, n_points=50):
"""计算有效前沿上的(风险,收益)点"""
n = len(mu)
results = []
# 遍历不同风险厌恶系数
gammas = np.linspace(0.1, 10, n_points)
for gamma in gammas:
def obj(w):
return 0.5 * w @ Sigma @ w - gamma * mu @ w
cons = ({'type': 'eq', 'fun': lambda w: w.sum() - 1})
bounds = [(0, 1)] * n # 禁止做空
w0 = np.ones(n) / n
res = minimize(obj, w0, method='SLSQP',
bounds=bounds, constraints=cons)
if res.success:
ret = mu @ res.x
risk = np.sqrt(res.x @ Sigma @ res.x)
results.append((risk, ret, res.x))
return results
# 计算
results = efficient_frontier(mu, Sigma)
risks = [r[0] for r in results]
returns = [r[1] for r in results]4.2 绘图
python
plt.figure(figsize=(10, 6))
plt.plot(risks, returns, 'b-', linewidth=2, label='有效前沿')
# 标注最小方差组合 (MVP)
min_risk_idx = np.argmin(risks)
plt.scatter(risks[min_risk_idx], returns[min_risk_idx],
color='red', s=100, marker='*', label='MVP')
plt.xlabel('风险 (波动率)')
plt.ylabel('预期收益')
plt.title('Markowitz 有效前沿')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()4.3 Markowitz 子弹
组合有效前沿的经典形状是"子弹"形——左端是最小方差组合,向上向右延伸。所有可行的组合都在子弹内部,有效前沿就是子弹的右上半边界。
Quant Link:完整的量化组合优化流程包括:
- 数据清洗:处理缺失值、异常值,计算收益率
- 协方差估计:样本协方差 → shrinkage 估计 → 因子模型
- 优化求解:scipy.optimize / cvxpy / Gurobi
- 回测验证:滚动窗口、交易成本、换手率惩罚
- 风险监控:跟踪误差、VaR、风险贡献分解
在实践中,收益率预测 的估计误差远大于协方差 的估计误差,因此风险平价等不依赖 的策略近年来受到更多关注。