Regime Detection: 识别你的因子何时有效
市场在不同的 regime 之间切换——趋势、反转、高波动、低波动。Regime detection 识别当前哪个 regime 活跃,以便你相应调整因子敞口。
一个在趋势明显的牛市表现出色的动量因子,在震荡、反转的市场中可能亏钱。一个在经济扩张期蓬勃发展的价值因子,可能在衰退期被重创。Regime detection 是识别当前市场处于哪个 regime——并相应调整因子敞口的做法。
什么是 Market Regime?
市场 regime 是以特定组合为特征的市场环境:
- 趋势强度(动量 vs. 均值回归)
- 波动率水平(高 vs. 低)
- 相关性结构(多元化有效 vs. 相关性飙升)
- 流动性条件
Regime 不是永久性的。它们毫无预兆地切换。低波动、趋势的 regime 可以在几天内翻转到高波动、均值回归的 regime(就像 2020 年 3 月那样)。
检测 Regime 的方法
方法 1:隐马尔可夫模型(HMM)
HMM 假设市场在有限数量的未观察状态(“regime”)之间切换。你观察收益;模型推断你处于哪个 regime。
import numpy as np
from hmmlearn import hmm
import pandas as pd
def detect_regimes_hmm(returns, n_states=3):
"""
使用高斯 HMM 检测市场 regime。
Parameters
----------
returns : Series 或 DataFrame
资产收益
n_states : int
要检测的 regime 数量
Returns
-------
regimes : Series
推断的 regime 序列
model : HMMModel
拟合的 HMM 模型
"""
X = returns.values.reshape(-1, 1)
model = hmm.GaussianHMM(
n_components=n_states,
covariance_type="full",
n_iter=1000
).fit(X)
regimes = model.predict(X)
regimes_series = pd.Series(regimes, index=returns.index)
return regimes_series, model
def interpret_regimes(regimes_series, returns):
"""
解释检测到的 regime 的特征。
"""
results = {}
for regime in regimes_series.unique():
mask = regimes_series == regime
regime_returns = returns[mask]
results[regime] = {
'mean_return': regime_returns.mean(),
'volatility': regime_returns.std(),
'n_obs': mask.sum(),
'pct_of_data': mask.mean()
}
return pd.DataFrame(results).T
方法 2:波动率 Regime
相对于历史平均值按波动率水平分类 regime:
def detect_vol_regime(returns, lookback=252, threshold=1.5):
"""
相对于历史将 regime 分类为低/高波动。
"""
rolling_vol = returns.rolling(lookback).std()
avg_vol = rolling_vol.mean()
current_vol = rolling_vol.iloc[-1]
if current_vol > threshold * avg_vol:
return 'high_vol'
else:
return 'low_vol'
def compute_rolling_volatility_regimes(returns, window=60, threshold=1.5):
"""
计算滚动波动率 regime。
"""
rolling_vol = returns.rolling(window).std()
avg_vol = rolling_vol.expanding().mean()
regimes = (rolling_vol > threshold * avg_vol).astype(int)
return regimes
方法 3:趋势强度 Regime
测量绝对收益动量来分类趋势 vs. 均值回归 regime:
def detect_trend_regime(prices, mom_lookback=60, threshold=0.02):
"""
检测市场是趋势还是均值回归。
"""
mom = (prices / prices.shift(mom_lookback) - 1).iloc[-1]
if mom > threshold:
return 'trending'
elif mom < -threshold:
return 'reversing'
else:
return 'choppy'
def dual_momentum_regimes(prices, short_lookback=20, long_lookback=60):
"""
使用短期和长期动量检测 regime。
短期和长期都正向 = 趋势确认
短期和长期都负向 = 逆转确认
短期和长期矛盾 = 震荡
"""
short_mom = (prices / prices.shift(short_lookback) - 1).iloc[-1]
long_mom = (prices / prices.shift(long_lookback) - 1).iloc[-1]
if short_mom > 0 and long_mom > 0:
return 'strong_trend'
elif short_mom < 0 and long_mom < 0:
return 'mean_reversion'
elif short_mom > 0 and long_mom < 0:
return 'short_term_long_reversal'
elif short_mom < 0 and long_mom > 0:
return 'short_term_trend'
else:
return 'choppy'
使用 Regime 信息
一旦检测到 regime,你可以调整因子敞口:
| Regime | 动量权重 | 均值回归权重 | 低波动权重 | 风险平价 |
|---|---|---|---|---|
| 趋势 | 高 | 低 | 低 | 减配 |
| 均值回归 | 低 | 高 | 中 | 超配 |
| 高波动 | 减配 | 减配 | 高 | 等权 |
| 低波动 | 中 | 中 | 中 | 等权 |
def regime_adjusted_weights(base_weights, current_regime, regime_adjustments):
"""
根据当前 regime 调整因子权重。
"""
adjustments = regime_adjustments.get(current_regime, {})
adjusted = base_weights.copy()
for factor, adjustment in adjustments.items():
if factor in adjusted:
adjusted[factor] *= adjustment
# 重新归一化
total = sum(adjusted.values())
if total > 0:
adjusted = {k: v / total for k, v in adjusted.items()}
return adjusted
REGIME_ADJUSTMENTS = {
'trending': {'momentum': 1.5, 'mean_reversion': 0.5, 'low_vol': 0.7},
'mean_reversion': {'momentum': 0.5, 'mean_reversion': 1.5, 'low_vol': 1.2},
'high_vol': {'momentum': 0.7, 'mean_reversion': 0.7, 'low_vol': 1.5},
'low_vol': {'momentum': 1.0, 'mean_reversion': 1.0, 'low_vol': 1.0},
'choppy': {'momentum': 0.8, 'mean_reversion': 0.8, 'low_vol': 1.0}
}
HMM Regime 与因子 IC
分析每个 regime 下因子的 IC:
def compute_ic_by_regime(factor_values, returns, regimes):
"""
计算每个 regime 下的 IC。
"""
results = {}
for regime in regimes.unique():
mask = regimes == regime
fv = factor_values[mask].dropna()
ret = returns[mask].dropna()
common = fv.index.intersection(ret.index)
if len(common) > 30:
ic = np.corrcoef(fv[common].values, ret[common].values)[0, 1]
results[f'regime_{regime}'] = ic
else:
results[f'regime_{regime}'] = np.nan
return pd.Series(results)
常见错误
Regime 估计有噪声。 HMM regime 标签可能频繁切换,即使底层市场没有变化。使用平滑或要求最小 regime 持续时间再切换敞口。
Regime 切换的交易成本。 如果你每周根据噪声 regime 估计切换因子, transaction costs 会摧毁收益。只有当 regime 信号强烈且持久时才切换。
只用一个指标。 单一波动率或动量指标可能对某些市场无效。组合多个指标(波动率 + 趋势 + 相关性)给出更稳健的 regime 估计。
Regime 预测是困难的。 检测当前 regime 相对简单。预测 regime 切换时间要困难得多。大多数 regime 切换策略由于过早或过晚切换而失败。
在知识体系中的位置
Regime detection 与因子衰减和因子组合密切相关。衰减在市场结构变化期间加速。因子组合应根据当前 regime 加权。横截面稳定性测试在不同 regime 下因子是否一致有效。
参考文献
- Ang, Asset Management, 第 10 章. Oxford University Press, 2014. ISBN: 978-0-19-995932-3.
- López de Prado, Advances in Financial Machine Learning, 第 10 章. Wiley, 2018. ISBN: 978-1-119-48208-6.
- Hamilton, Time Series Analysis. Princeton University Press, 1994. ISBN: 978-0-691-04289-3.