风险模型与组合优化基础 (Risk Models & Optimization)¶
如果我们把所有 Alpha 得分最高的股票都买入,我们的组合就一定安全吗? 答案是否定的。如果我们的 Alpha 因子碰巧倾向于给能源股高分,我们不知不觉中就押注了“原油行业风险暴露”。如果油价崩盘,组合照样暴跌。
这就是建立风险模型(Risk Models)的意义:隔离风险,提纯 Alpha。
1. 系统性风险 vs 特质风险¶
股票 $i$ 的日收益率 $R_i$ 可以被分解: $$ R_i = \sum_{k=1}^{K} \beta_{i,k} \cdot f_k + \alpha_i + \epsilon_i $$
- $\beta_{i,k}$: 股票 $i$ 对风险因子 $k$ 的暴露程度(Exposure)。
- $f_k$: 第 $k$ 个共同风险因子(如大盘、科技行业、大市值风格等)的收益率。
- $\alpha_i$ (Alpha): 股票独有的正收益。我们真正想要的源泉。
风险模型的任务就是准确估计由那 $K$ 个风险因子组成的协方差 $\Sigma$。
2. 主成分分析 (PCA) 与隐式因子¶
我们除了预定义行业/风格(Barra 方法),还可以使用纯数学的主成分分析 (PCA)从所有股票的历史变动中挖掘隐式风险。
- 第1主成分 (PC1): 往往解释了市场最大部分的集体涨跌(“市场风险”)。
- 其他主成分: 往往对应隐性的行业轮动或宏观冲击。
import pandas as pd
import numpy as np
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
# 模拟 100 只股票在 252 个交易日的标准化收益率数据
np.random.seed(42)
market_factor = np.random.normal(0, 1, 252)
sector_factor = np.random.normal(0, 0.5, 252)
returns = []
for i in range(100):
# 每只股票在市场、行业上有不同的 beta,另外加特质噪音
beta_m = np.random.uniform(0.5, 1.5)
beta_s = np.random.uniform(-1, 1)
noise = np.random.normal(0, 0.5, 252)
stock_return = beta_m * market_factor + beta_s * sector_factor + noise
returns.append(stock_return)
returns_df = pd.DataFrame(returns).T
# 提取主成分因子构建风险模型
pca = PCA(n_components=10)
pca.fit(returns_df)
plt.figure(figsize=(8,4))
plt.bar(range(1, 11), pca.explained_variance_ratio_)
plt.title('PCA Explained Variance by Component')
plt.xlabel('Principal Component')
plt.ylabel('Variance Explained')
plt.show()
print(f"前 5 个主成分解释了 {sum(pca.explained_variance_ratio_[:5]):.2%} 的全市场波动")
前 5 个主成分解释了 82.34% 的全市场波动
import matplotlib.pyplot as plt
# 1. 设置系统自带的中文字体(这里使用黑体 SimHei)
plt.rcParams['font.sans-serif'] = ['SimHei'] # 如果你想用微软雅黑,可以改成 ['Microsoft YaHei']
# 2. 解决更换字体后,负号(-)显示为方块的问题
plt.rcParams['axes.unicode_minus'] = False
3. 剥离风险,执行组合优化¶
有了风险模型,当我们将 Alpha 转化为持仓权重 $w$ 时,我们添加风险中性(Risk Neutral)约束: 使得 $\sum w_i * \beta_{i,k} \approx 0$ 对于每一个重要的风险因子 $k$ 都成立。
这样,我们的组合虽然持有能源股,却免疫了整体能源行业暴跌或大盘暴跌带来的冲击。留下的就是穿越牛熊的纯净 Alpha 收益。
4. 实战:带风险中性约束的投资组合优化¶
虽然我们在这个基础教程中无法引入庞大的凸优化求解器(如 CVXPY 或 Mosek),但我们可以通过直观的矩阵运算逻辑来理解“剥离风险”的核心动作。
假设我们有 5 只股票,我们通过机器学习或因子分析算出了它们的 Alpha 预期(得分)。同时,通过上面的 PCA,我们得到了这 5 只股票在“第一主成分(市场风险因子 PC1)”上的暴露度(Beta)。
我们的目标是:做多做空这 5 只股票,最大化 Alpha 收益,同时让组合在 PC1 上的总暴露度严格为 0。
import scipy.optimize as sco
# 假设 5 只股票的 Alpha 预期分 (得分越高越应该买)
expected_alphas = np.array([0.05, 0.02, -0.01, -0.04, 0.03])
# 假设 5 只股票对市场风险因子 (PC1) 的暴露度 (Beta)
pca_betas = np.array([1.2, 0.8, 1.0, 1.5, 0.5])
# 组合构建问题:求解权重向量 w
# 目标:最小化 negative_alpha_sum (即最大化 Alpha)
def objective_function(weights):
return -np.dot(weights, expected_alphas)
# 约束条件 1: 多空总资本限制 (假设杠杆率为 1,即多空绝对头寸加起来等于本金 100%)
def constraint_capital(weights):
return 1.0 - np.sum(np.abs(weights))
# 约束条件 2: 市场风险中性 (核心!组合对 PC1 的总暴露度为 0)
def constraint_risk_neutral(weights):
return 0.0 - np.dot(weights, pca_betas)
constraints = (
{'type': 'eq', 'fun': constraint_capital},
{'type': 'eq', 'fun': constraint_risk_neutral}
)
# 可以限制每只股票的最大持仓比例防止过渡集中 (例如不超过20%)
bounds = tuple((-0.20, 0.20) for _ in range(5))
# 初始平均权重
init_weights = np.array([0.2, 0.2, -0.2, -0.2, 0])
# 运行 SLSQP 优化器
optimal_result = sco.minimize(objective_function, init_weights, method='SLSQP', bounds=bounds, constraints=constraints)
optimized_weights = optimal_result.x
print("--- 风险中性组合优化结果 ---")
for i, w in enumerate(optimized_weights):
print(f"股票 {i+1} 权重: {w:8.4f} (Alpha: {expected_alphas[i]:.2f}, Beta: {pca_betas[i]:.1f})")
print("\n--- 约束检验 ---")
print(f"预测组合总 Alpha: {np.dot(optimized_weights, expected_alphas):.4f}")
print(f"组合总市场敞口 (Total Beta to PC1): {np.dot(optimized_weights, pca_betas):.4f} (应当接近 0)")
print(f"多头仓位总和: {np.sum(optimized_weights[optimized_weights>0]):.4f}")
print(f"空头仓位总和: {np.sum(optimized_weights[optimized_weights<0]):.4f}")
--- 风险中性组合优化结果 --- 股票 1 权重: 0.2000 (Alpha: 0.05, Beta: 1.2) 股票 2 权重: 0.2000 (Alpha: 0.02, Beta: 0.8) 股票 3 权重: -0.2000 (Alpha: -0.01, Beta: 1.0) 股票 4 权重: -0.2000 (Alpha: -0.04, Beta: 1.5) 股票 5 权重: 0.2000 (Alpha: 0.03, Beta: 0.5) --- 约束检验 --- 预测组合总 Alpha: 0.0300 组合总市场敞口 (Total Beta to PC1): -0.0000 (应当接近 0) 多头仓位总和: 0.6000 空头仓位总和: -0.4000
这就是典型的市场中性 (Market Neutral) 策略模型。哪怕明天大盘暴跌 5%,由于你对 PC1 的敞口是 0(做空的利润完美抵消做多的亏损),你的组合也不会受大盘影响。只有那 0.0195 的纯净 Alpha 在为你稳定的赚钱。
本章结语¶
风险模型(Risk Model)防守,Alpha 模型(Alpha Model)进攻,优化器(Optimizer)将两者融合。
- 在
04_factor_analysis.ipynb中,我们学到了如何挖掘一把锐利的矛(寻找高 IC 的 Alpha 特征)。 - 在本章中,我们学会了用 PCA 打造一面盾牌(提取所有隐藏的市场风险并剥离它们)。
只有经过了统计学检验的因子和严格去掉了所有风险暴露的头寸,才能被称为真正的 Alpha。 否则那都只是承担未知风险带来的补偿(Smart Beta)。
在接下来的最后两个阶段 Phase 5,我们将进入现代金融的最前沿:如何用纯正的机器学习算法代替传统的因子公式,以及如何避免被算法的反噬(Data Leakage)。
🎯 练习¶
- 增加第二个风险中性约束(例如,同时对 PC1 和 PC2 的敞口为 0),重新运行优化器。
- 将 PCA 提取的主成分个数从 10 改为 3 和 20,对比各自解释的方差比例。
- 用真实的股票收益率数据构建协方差矩阵,与模拟数据的结果进行对比。
下一节 → ../06_ml_trading/01_feature_selection.ipynb