1. 这节究竟解决什么实际问题?¶
5.9 市场状态识别与动态配置(Regime Switching) 的具体任务是:识别市场状态并评估状态模型的样本外稳定性。 本节将明确输入数据、计算过程、输出及其在后续研究中的用途。
2. 前置知识¶
理解收益率、协方差、波动率和矩阵的基本含义。 对公式的理解应包括变量定义、单位、时间索引和适用条件。
3. 数据说明¶
固定多资产价格样本用于演示;不把少量资产、单一时期的最优权重当作配置建议。 运行前先检查 shape、日期范围、列名与缺失值。数据看起来能画图,不代表它适合回答研究问题。
4. 学习目标与完成标准¶
完成本节后,你应能用自己的话解释 5.9 市场状态识别与动态配置(Regime Switching) 的输入、计算步骤、输出和局限;尤其能说明:识别市场状态并评估状态模型的样本外稳定性。 你还应能独立改动一个参数并解释结果为什么变化,指出至少一个会让结论失真的假设。
5. 核心概念与公式¶
组合配置同时考虑单项资产风险和资产之间的协方差,目标是控制整体风险暴露。
组合收益是权重加权和;风险由协方差矩阵决定,权重之和、杠杆和做空限制必须写清楚。 看到公式时依次确认:每个符号的单位是什么?它属于哪个时点?分母会不会为零?它是描述性统计量、预测量,还是交易规则?
6. 从零实现¶
原始代码按照“准备输入 → 计算中间量 → 得到结果 → 解释输出”的顺序组织。每个单元应检查变量类型、索引、维度和代表性数值。中间变量用于核对公式实现和定位计算错误。
7. 成熟库实现¶
优化器可以求满足约束的数值解;它不会告诉你输入的预期收益是否可信。 使用库函数前先阅读参数含义、默认值和返回对象;库输出一个数字,并不说明这个数字适合你的交易假设。
8. 两种实现的差异¶
从零实现便于核对公式、调试和识别隐含假设;成熟库适合在计算逻辑已验证后复用。两种实现依赖相同的数据口径与时点假设,使用库函数不会改变输入数据的质量。
9. 预期结果与检查范围¶
你应看到维度正确的 Series/DataFrame、日期单调递增的索引,以及能支持“识别市场状态并评估状态模型的样本外稳定性。”这一任务的表格或图形。金融日收益率均值通常接近 0;若出现极大的连续收益、空表、全 NaN 或索引错位,应先排查数据和对齐逻辑,而不是立即相信结果。
10. 常见错误与诊断¶
协方差矩阵不可逆、权重未归一化、用样本内最优权重宣称可投资、忽略再平衡成本。
诊断顺序:先打印类型与 shape,再看 head()/tail(),接着检查日期和 NaN,最后才比较数值。这样能避免在错误的对象上反复改参数。
11. 研究偏差检查¶
均值估计噪声极大;需要样本外比较、参数敏感性和等权等简单基线。 结果至少应与一个简单基线比较,并明确哪些部分只是教学演示。
12. 三层练习与答案¶
- 基础练习:验证一个中间变量、补全一段小计算,并用一句话解释输出。
- 研究练习:改变窗口、样本区间、约束或标的,记录哪些结论仍成立。
- 开放项目:把本节方法嵌入模块项目,报告基线、失败结果和限制。
题目在 ../exercises/05_portfolio/09_regime_switching_exercises.ipynb;完整推导、可运行参考实现与结果检查在 ../solutions/05_portfolio/09_regime_switching_solutions.ipynb。请先独立作答,再打开答案。
13. 延伸阅读¶
延伸材料:Portfolio Selection(Markowitz, 1952)。阅读记录应包括研究假设、数据频率、样本范围和结论适用条件。
14. 下一节¶
完成本模块后,进入下一模块的概览页,并把本节的检查清单带到后续研究中。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
np.random.seed(42)
print('Libraries loaded')
Libraries loaded
import matplotlib.pyplot as plt
# 1. 设置系统自带的中文字体(这里使用黑体 SimHei)
plt.rcParams['font.sans-serif'] = ['SimHei'] # 如果你想用微软雅黑,可以改成 ['Microsoft YaHei']
# 2. 解决更换字体后,负号(-)显示为方块的问题
plt.rcParams['axes.unicode_minus'] = False
1. 为什么要识别市场状态?¶
许多量化策略在牛市和熊市中完全不同:
- 动量策略在牛市高效,熊市崩溃时动量策略大幅亏损
- 均值回归在震荡市有效,趋势市中持续亏损
- 待检验方案:依据估计的市场状态调整策略或资产权重
# 模拟一段带有状态切换的市场数据
np.random.seed(42)
n = 252 * 5
# 真实状态序列:0=熊市, 1=震荡, 2=牛市
true_states = np.zeros(n, dtype=int)
true_states[0:400] = 0 # 熊市
true_states[400:700] = 1 # 震荡
true_states[700:900] = 2 # 牛市
true_states[900:1000] = 0 # 熊市
true_states[1000:] = 2 # 牛市
state_params = {0:(-0.0008, 0.022), 1:(0.0002, 0.012), 2:(0.0010, 0.008)}
market_returns = np.array([np.random.normal(*state_params[s]) for s in true_states])
cum_market = (1 + pd.Series(market_returns)).cumprod()
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(13, 7), sharex=True)
ax1.plot(cum_market, color='steelblue', lw=1.2)
for start, end, lbl, color in [(0,400,'熊市','red'),(400,700,'震荡','yellow'),(700,900,'牛市','green'),(900,1000,'熊市','red'),(1000,n,'牛市','green')]:
ax1.axvspan(start, end, alpha=0.2, color=color)
ax1.set_title('模拟市场价格(不同颜色代表不同状态)'); ax1.set_ylabel('累积净值')
volatility = pd.Series(market_returns).rolling(20).std() * np.sqrt(252)
volatility.plot(ax=ax2, color='darkorange', lw=1.2)
ax2.set_title('20日滚动年化波动率'); ax2.set_ylabel('年化波动率')
plt.tight_layout(); plt.show()
vol_20d = pd.Series(market_returns).rolling(20).std() * np.sqrt(252)
identified_state = pd.Series('unknown', index=range(n))
identified_state[vol_20d > 0.20] = 'risk_off' # 高波动 → 防御
identified_state[vol_20d < 0.12] = 'risk_on' # 低波动 → 进攻
# 动态配置策略
# risk_on: 80% 股票 + 20% 债券
# risk_off: 20% 股票 + 80% 债券
bond_returns = np.random.normal(0.0003, 0.003, n)
stock_returns = market_returns
weights_stock = pd.Series(0.5, index=range(n)) # 默认 50/50
weights_stock[identified_state == 'risk_on'] = 0.8
weights_stock[identified_state == 'risk_off'] = 0.2
ret_dynamic = weights_stock * stock_returns + (1-weights_stock) * bond_returns
ret_passive = 0.6 * stock_returns + 0.4 * bond_returns # 60/40 被动
cum_dyn = (1 + pd.Series(ret_dynamic)).cumprod()
cum_pass = (1 + pd.Series(ret_passive)).cumprod()
plt.figure(figsize=(12, 5))
plt.plot(cum_dyn, 'blue', lw=2, label='波动率状态识别动态配置')
plt.plot(cum_pass, 'gray', lw=2, linestyle='--', label='60/40 被动配置')
plt.title('市场状态识别动态配置 vs 60/40 被动')
plt.ylabel('累积净值'); plt.legend(); plt.grid(alpha=0.3); plt.show()
def sr(r): return pd.Series(r).mean()/pd.Series(r).std()*np.sqrt(252)
print(f'动态配置 Sharpe: {sr(ret_dynamic):.3f}')
动态配置 Sharpe: 1.881
练习¶
- 用指数移动平均(EWM)替代简单移动平均计算波动率,对比状态识别的稳定性。
- 引入宏观信号(如收益率曲线斜率代理变量),结合波动率构建双信号状态识别器。
- 研究 hmmlearn 库的 GaussianHMM,用真实 SPY 数据拟合 2-状态 HMM,标注历史牛熊区间。
下一节 → ../04_backtesting/10_performance_attribution.ipynb