Skip to content

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 @ w

1.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 问题

给定协方差矩阵 Σ\Sigma 和风险预算比例 bb,求解权重 w\mathbf{w} 使得各资产的风险贡献等于目标比例。

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:完整的量化组合优化流程包括:

  1. 数据清洗:处理缺失值、异常值,计算收益率
  2. 协方差估计:样本协方差 → shrinkage 估计 → 因子模型
  3. 优化求解:scipy.optimize / cvxpy / Gurobi
  4. 回测验证:滚动窗口、交易成本、换手率惩罚
  5. 风险监控:跟踪误差、VaR、风险贡献分解

在实践中,收益率预测 μ\boldsymbol{\mu} 的估计误差远大于协方差 Σ\Sigma 的估计误差,因此风险平价等不依赖 μ\boldsymbol{\mu} 的策略近年来受到更多关注。

Built with VitePress