风险模型与组合优化基础 (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): 往往解释了市场最大部分的集体涨跌(“市场风险”)。
- 其他主成分: 往往对应隐性的行业轮动或宏观冲击。
1. 这节究竟解决什么实际问题?¶
风险模型与组合优化基础 (Risk Models & Optimization) 的具体任务是:用协方差和风险预算建立组合风险模型,明确模型遗漏的风险。 本节将明确输入数据、计算过程、输出及其在后续研究中的用途。
2. 前置知识¶
理解收益率、协方差、波动率和矩阵的基本含义。 对公式的理解应包括变量定义、单位、时间索引和适用条件。
3. 数据说明¶
固定多资产价格样本用于演示;不把少量资产、单一时期的最优权重当作配置建议。 运行前先检查 shape、日期范围、列名与缺失值。数据看起来能画图,不代表它适合回答研究问题。
4. 学习目标与完成标准¶
完成本节后,你应能用自己的话解释 风险模型与组合优化基础 (Risk Models & Optimization) 的输入、计算步骤、输出和局限;尤其能说明:用协方差和风险预算建立组合风险模型,明确模型遗漏的风险。 你还应能独立改动一个参数并解释结果为什么变化,指出至少一个会让结论失真的假设。
5. 核心概念与公式¶
组合配置同时考虑单项资产风险和资产之间的协方差,目标是控制整体风险暴露。
组合收益是权重加权和;风险由协方差矩阵决定,权重之和、杠杆和做空限制必须写清楚。 看到公式时依次确认:每个符号的单位是什么?它属于哪个时点?分母会不会为零?它是描述性统计量、预测量,还是交易规则?
6. 从零实现¶
原始代码按照“准备输入 → 计算中间量 → 得到结果 → 解释输出”的顺序组织。每个单元应检查变量类型、索引、维度和代表性数值。中间变量用于核对公式实现和定位计算错误。
7. 成熟库实现¶
优化器可以求满足约束的数值解;它不会告诉你输入的预期收益是否可信。 使用库函数前先阅读参数含义、默认值和返回对象;库输出一个数字,并不说明这个数字适合你的交易假设。
8. 两种实现的差异¶
从零实现便于核对公式、调试和识别隐含假设;成熟库适合在计算逻辑已验证后复用。两种实现依赖相同的数据口径与时点假设,使用库函数不会改变输入数据的质量。
9. 预期结果与检查范围¶
你应看到维度正确的 Series/DataFrame、日期单调递增的索引,以及能支持“用协方差和风险预算建立组合风险模型,明确模型遗漏的风险。”这一任务的表格或图形。金融日收益率均值通常接近 0;若出现极大的连续收益、空表、全 NaN 或索引错位,应先排查数据和对齐逻辑,而不是立即相信结果。
10. 常见错误与诊断¶
协方差矩阵不可逆、权重未归一化、用样本内最优权重宣称可投资、忽略再平衡成本。
诊断顺序:先打印类型与 shape,再看 head()/tail(),接着检查日期和 NaN,最后才比较数值。这样能避免在错误的对象上反复改参数。
11. 研究偏差检查¶
均值估计噪声极大;需要样本外比较、参数敏感性和等权等简单基线。 结果至少应与一个简单基线比较,并明确哪些部分只是教学演示。
12. 三层练习与答案¶
- 基础练习:验证一个中间变量、补全一段小计算,并用一句话解释输出。
- 研究练习:改变窗口、样本区间、约束或标的,记录哪些结论仍成立。
- 开放项目:把本节方法嵌入模块项目,报告基线、失败结果和限制。
题目在 ../exercises/05_portfolio/05_risk_models_exercises.ipynb;完整推导、可运行参考实现与结果检查在 ../solutions/05_portfolio/05_risk_models_solutions.ipynb。请先独立作答,再打开答案。
13. 延伸阅读¶
延伸材料:Portfolio Selection(Markowitz, 1952)。阅读记录应包括研究假设、数据频率、样本范围和结论适用条件。
14. 下一节¶
下一节是 06_var_cvar。本节的概念会成为理解它的输入,而不是孤立的名词。
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)**组合示例。对第一主成分的估计敞口为 0,不代表组合在实际市场中完全不受系统性风险影响;估计误差、因子变化、交易成本和其他风险敞口仍会造成损益。样本中的 0.0195 也不能解释为稳定的未来收益。
本章结语¶
风险模型(Risk Model)防守,Alpha 模型(Alpha Model)进攻,优化器(Optimizer)将两者融合。
- 在
04_factor_analysis.ipynb中,我们学到了如何挖掘一把锐利的矛(寻找高 IC 的 Alpha 特征)。 - 在本章中,我们学会了用 PCA 打造一面盾牌(提取所有隐藏的市场风险并剥离它们)。
Alpha 的解释需要控制已知风险因子,并通过样本外检验评估稳定性。未解释收益可能来自遗漏风险因子、模型误设或估计误差,不能直接认定为异常收益。
后续机器学习模块将比较数据驱动模型与传统因子方法,并重点检查数据泄漏(Data Leakage)和样本外稳定性。
练习¶
- 增加第二个风险中性约束(例如,同时对 PC1 和 PC2 的敞口为 0),重新运行优化器。
- 将 PCA 提取的主成分个数从 10 改为 3 和 20,对比各自解释的方差比例。
- 用真实的股票收益率数据构建协方差矩阵,与模拟数据的结果进行对比。
下一节 → ../06_ml_trading/01_feature_selection.ipynb