diff --git a/docs/experiments/007_momentum_window_optimization.md b/docs/experiments/007_momentum_window_optimization.md new file mode 100644 index 0000000..e9a4294 --- /dev/null +++ b/docs/experiments/007_momentum_window_optimization.md @@ -0,0 +1,470 @@ +# 实验 007:动量因子回看窗口优化研究 + +**实验日期**:2026-06-12 +**实验目标**:研究动量因子回看窗口(n_days)的选择方法,评估多周期融合对策略表现的影响 +**实验结论**:多周期融合不适用于本策略,维持 25 天单窗口 + +--- + +## 一、问题背景 + +### 1.1 当前配置 + +策略使用 `slope_r2` 因子,全局统一 25 天回看窗口: + +```yaml +factor: + n_days: 25 + type: slope_r2 +``` + +### 1.2 发现的问题 + +从回测数据的因子分布分析发现: + +| 标的 | IQR(波动代理) | 中位数 | 正得分% | 特征 | +|------|----------------|--------|---------|------| +| 创业板指 | 28.23 | 0.00 | 51.0% | 高波动,趋势间歇性强 | +| 恒生科技 | 20.71 | -0.01 | 44.0% | 高波动,趋势弱 | +| 纳指100 | 20.10 | 4.31 | 67.4% | 高波动,趋势强 | +| 短债 | 0.38 | 0.68 | 97.9% | 低波动,几乎无趋势 | +| 黄金 | 13.39 | 0.77 | 60.7% | 中等波动,趋势持续 | + +**核心问题**:不同资产的趋势周期差异很大,统一 25 天窗口是否合理? + +--- + +## 二、学界与业界调研 + +### 2.1 经典文献的标准做法 + +#### 横截面动量(Cross-Sectional Momentum) + +**Jegadeesh & Titman (1993)** 开创性研究: +- **标准窗口**:12-1 月(过去12个月收益,跳过最近1个月) +- **金融语义**:跳过最近1个月是因为存在短期反转效应(1周-1个月),而中期(3-12个月)存在动量效应 +- **原理**:信息扩散慢 → 价格对新信息反应不足 → 形成中期趋势 + +**Asness, Moskowitz & Pedersen (2013)** "Value and Momentum Everywhere": +- 股票:12-1 月 +- 债券:12-1 月或 6-1 月 +- 商品:12 月(不跳过,因为商品市场短期反转弱) +- 货币:12 月 +- **核心观点**:不同资产类别的最优窗口不同,但 12 月是一个稳健的起点 + +#### 时间序列动量(Time-Series Momentum / TSMOM) + +**Moskowitz, Ooi & Pedersen (2012)**: +- **标准窗口**:12 月(用于期货) +- **金融语义**:TSMOM 关注资产自身的绝对收益,不与其他资产比较 +- **关键发现**:1-12 月窗口都有效,但 12 月最稳健 +- **波动率调整**:用实现波动率标准化头寸规模,使得不同资产可比 + +### 2.2 窗口选择的金融语义 + +#### 不同窗口对应的市场微观结构 + +| 窗口长度 | 捕捉的效应 | 金融解释 | 风险 | +|---------|-----------|---------|------| +| 1周-1月 | 短期反转 | 流动性冲击、过度反应修正 | 高换手、交易成本 | +| 1-3月 | 早期动量 | 信息扩散初期、盈余公告后漂移 | 容易被打断 | +| 3-12月 | 经典动量 | 信息扩散慢、机构调仓周期 | 最稳健 | +| 12-24月 | 长期动量 | 经济周期、企业基本面变化 | 均值回归开始显现 | +| >24月 | 长期反转 | 估值回归、经济周期反转 | 动量效应消失 | + +#### 不同资产类别的特征周期 + +| 资产类别 | 特征周期 | 推荐窗口 | 理由 | +|---------|---------|---------|------| +| **股票指数** | 季度财报+机构调仓 | 6-12月 | 信息扩散慢,机构季度调仓 | +| **债券** | 央行政策周期 | 3-6月 | 利率变化快,久期短 | +| **商品** | 供需周期+季节性 | 6-12月 | 供需调整慢,但有季节性 | +| **货币** | 利差+央行政策 | 3-6月 | 政策变化快 | + +### 2.3 避免过拟合的原则 + +#### 先验选择 vs 数据挖掘 + +**过拟合的做法**: +```python +# 在历史数据上测试 5, 10, 15, 20, 25, 30... 天,选最好的 +for window in [5, 10, 15, 20, 25, 30, 60, 120]: + backtest(window) +best_window = argmax(results) # 过拟合! +``` + +**有金融语义的做法**: +```python +# 基于资产类别选择窗口 +window_map = { + 'equity_index': 252, # 12月(252交易日) + 'bond': 126, # 6月 + 'commodity': 252, # 12月 + 'currency': 126, # 6月 +} +``` + +#### 稳健性检验原则 + +**学界推荐的做法**: +1. **选择有理论支撑的窗口**:12月、6月、3月是标准选择 +2. **测试邻域稳健性**:如果 12 月好,11 月和 13 月也应该不差 +3. **多窗口平均**:用 3-12 月的多个窗口取平均,降低单窗口风险 +4. **样本外验证**:在不同时间段、不同市场验证 + +**AQR 的实践建议**: +- 不要优化到极端值(如 17 天、23 天) +- 选择"足够好"的标准窗口(如 252 天而非 247 天) +- 关注经济解释而非统计显著性 + +### 2.4 多窗口融合方法 + +#### 1. 等权平均(Simple Ensemble) + +```python +windows = [63, 126, 252] # 3月、6月、12月 +momentum = mean([return(p, w) for w in windows]) +``` + +**优点**: +- 降低单窗口风险 +- 捕捉不同周期的趋势 +- 无需优化参数 + +**缺点**: +- 等权可能不合理 +- 可能引入噪音窗口 + +#### 2. 波动率加权(Volatility-Weighted) + +```python +# 波动率低的窗口权重更高(更稳定) +weights = 1 / vol(window_i) +momentum = weighted_mean(momentum_i, weights) +``` + +**金融语义**:低波动窗口的信号更可靠 + +#### 3. 自适应窗口(Adaptive Window) + +**基于波动率的自适应**: +```python +# 高波动时缩短窗口(快速反应),低波动时延长(过滤噪音) +if realized_vol > threshold: + window = 63 # 3月 +else: + window = 252 # 12月 +``` + +**基于机制转换(Regime Switching)**: +```python +# 用 HMM 识别市场状态 +regime = detect_regime(market_data) +if regime == 'trending': + window = 252 # 趋势市用长窗口 +elif regime == 'mean_reverting': + window = 21 # 震荡市用短窗口或反转 +``` + +### 2.5 多周期融合为什么有效? + +#### 1. 信息扩散有多个时间尺度 + +- **短期(1-4周)**:流动性冲击、技术性买卖、短期情绪(噪音) +- **中期(1-6月)**:盈余公告、行业数据、政策变化(信息扩散) +- **长期(6-12月)**:经济周期转换、产业趋势、估值重定价 + +单一窗口只能捕捉一个尺度的信息。多窗口融合等于同时监听多个信息频段。 + +#### 2. 市场参与者的决策周期不同 + +| 参与者 | 决策周期 | 影响的价格趋势 | +|--------|---------|--------------| +| 高频/量化 | 天-周 | 短期噪音 | +| 共同基金 | 月-季 | 中期动量 | +| 养老金/保险 | 季-年 | 长期趋势 | +| 主权基金 | 年+ | 结构性变化 | + +当多个周期的信号一致时,意味着不同时间维度的市场参与者方向一致——这是最强的趋势确认。 + +#### 3. 统计角度:偏差-方差权衡 + +- **短窗口**:低偏差(快速捕捉趋势变化),高方差(容易被噪音干扰) +- **长窗口**:高偏差(趋势转折时反应慢),低方差(噪音被平滑掉) + +多窗口融合相当于对偏差-方差做了平均,短窗口提供灵敏度,长窗口提供稳定性。 + +#### 4. 信号处理的类比 + +把价格序列想象成一个信号,不同窗口就是不同频率的带通滤波器: + +``` +价格信号 = 高频噪音 + 中期趋势 + 长期趋势 + 周期性波动 + +25天窗口 → 带通滤波器:主要透过高频成分 +126天窗口 → 带通滤波器:主要透过中期成分 +252天窗口 → 低通滤波器:只保留长期趋势 +``` + +多个滤波器融合 = 宽频带接收,信息更完整。 + +### 2.6 多周期融合对 slope_r2 偏好的影响 + +slope_r2 真正偏好的是**趋势性波动高的资产**(高波动+有方向),而不是单纯的高波动。 + +多周期融合的预期影响: + +| 资产类型 | 单窗口(25天) | 多周期融合 | 变化方向 | +|---------|-------------|-----------|---------| +| 高波动+持续趋势(纳指) | 高分 | 高分 | 不变 | +| 高波动+间歇趋势(创业板) | 不稳定高分 | 中等分 | **下降** | +| 低波动+持续趋势(黄金) | 中等分 | 中等偏高分 | **上升** | +| 低波动+无趋势(短债) | 低分 | 低分 | 不变 | +| 高波动+无趋势(恒生科技) | 低分 | 低分 | 不变 | + +**核心预期**:融合会让 slope_r2 的偏好从"高波动+趋势性"转向"持续性趋势"——不管波动高低,只要趋势持续就得分高。 + +--- + +## 三、实验设计 + +### 3.1 实验一:IDM 信息离散动量融合 + +#### 方法 + +**IDM(Information Dispersal Momentum)**:正收益天数占比,衡量上涨的持续性 + +```python +def info_dispersal_momentum(prices: np.ndarray) -> float: + returns = np.diff(prices) + positive_days = np.sum(returns > 0) + return positive_days / len(returns) +``` + +**方式一:乘法融合** +```python +def slope_r2_idm_score(prices: np.ndarray) -> float: + sr2 = slope_r2_score(prices) + idm = info_dispersal_momentum(prices) + return sr2 * idm # IDM 作为折扣系数 +``` + +**方式三:阈值过滤** +```python +def slope_r2_idm_filter_score(prices: np.ndarray, threshold: float = 0.5) -> float: + idm = info_dispersal_momentum(prices) + if idm < threshold: + return 0.0 # 上涨天数不足阈值则清零 + return slope_r2_score(prices) +``` + +#### 实验配置 + +- **方式一**:`type: slope_r2_idm` +- **方式三**:`type: slope_r2_idm_filter`,测试阈值 0.4/0.5/0.6 + +### 3.2 实验二:多周期融合 + +#### 方法 + +```python +def slope_r2_ensemble_score(prices: np.ndarray, windows: list = None) -> float: + if windows is None: + windows = [63, 126, 252] # 3月、6月、12月 + + scores = [] + for w in windows: + if len(prices) >= w: + window_prices = prices[-w:] + score = slope_r2_score(window_prices) + scores.append(score) + + return sum(scores) / len(scores) if scores else 0.0 +``` + +#### 实验配置 + +- **配置**:`type: slope_r2_ensemble` +- **窗口**:63/126/252 天(3月/6月/12月) +- **数据预加载**:504 天(2倍最大窗口) + +--- + +## 四、实验结果 + +### 4.1 实验一:IDM 融合结果 + +#### 方式一:乘法融合 + +| 指标 | slope_r2 (baseline) | slope_r2_idm | 变化 | +|------|---------------------|--------------|------| +| 总收益 | 288.30% | 296.55% | +8.25% | +| 年化收益 | 24.61% | 25.03% | +0.42% | +| 最大回撤 | -16.27% | -16.19% | 略改善 | +| Sharpe | 1.17 | 1.20 | +0.03 | +| Calmar | 1.51 | 1.55 | +0.04 | +| 胜率 | 53.74% | 54.48% | +0.74% | +| 调仓次数 | 363 | 374 | +11 | + +**结论**:方式一(乘法融合)全面小幅优于 baseline。 + +#### 方式三:阈值过滤 + +| 阈值 | 总收益 | 年化 | 最大回撤 | Sharpe | Calmar | 胜率 | +|------|--------|------|----------|--------|--------|------| +| 0.4 | 297.88% | 25.10% | -16.27% | 1.19 | 1.54 | 53.87% | +| 0.5 | 205.38% | 19.85% | -17.90% | 1.00 | 1.11 | 53.35% | +| 0.6 | 69.75% | 8.96% | -24.77% | 0.58 | 0.36 | 56.87% | + +**结论**:方式三(过滤器)阈值敏感,0.5 和 0.6 明显变差,容易过拟合。 + +### 4.2 实验二:多周期融合结果 + +#### 绩效对比 + +| 指标 | slope_r2 (25天) | slope_r2_ensemble (63/126/252) | 变化 | +|------|----------------|-------------------------------|------| +| 总收益 | 288.30% | 182.82% | **-105.48%** | +| 年化收益 | 24.61% | 18.36% | **-6.25%** | +| 最大回撤 | -16.27% | -21.61% | **恶化 5.34%** | +| Sharpe | 1.17 | 0.96 | **-0.21** | +| Calmar | 1.51 | 0.85 | **-0.66** | +| 胜率 | 53.74% | 55.47% | +1.73% | +| 调仓次数 | 363 | 167 | **-196** | + +#### 持仓频率变化 + +| 标的 | baseline 占比 | ensemble 占比 | 变化 | 资产类型 | +|------|--------------|--------------|------|---------| +| 纳指100 | 44.7% | 53.3% | **+8.6%** | 高波动+持续趋势 | +| 黄金 | 21.0% | 35.8% | **+14.8%** | 低波动+持续趋势 | +| 创业板指 | 29.9% | 40.2% | **+10.3%** | 高波动+间歇趋势 | +| 日经225 | 31.0% | 37.7% | +6.7% | 中波动+持续趋势 | +| 德国DAX | 27.8% | 33.1% | +5.3% | 中波动+持续趋势 | +| **短债指数** | **32.0%** | **16.6%** | **-15.4%** | 防御填充 | +| 红利低波 | 24.3% | 11.9% | **-12.4%** | 低波动+持续趋势 | +| 有色金属 | 18.1% | 12.8% | -5.3% | 高波动+周期趋势 | + +#### 与预期对比 + +| 预期 | 实际 | 符合? | +|------|------|--------| +| 纳指100 和黄金占比上升 | 纳指+8.6%,黄金+14.8% | ✓ 符合 | +| 红利低波占比上升 | 红利低波-12.4% | ✗ 不符合 | +| 创业板指占比下降 | 创业板指+10.3% | ✗ 不符合 | +| 整体表现改善 | 收益降6%,回撤增5% | ✗ 不符合 | + +--- + +## 五、结论与分析 + +### 5.1 IDM 融合结论 + +**推荐方案**:方式一(乘法融合) + +**理由**: +1. 全面小幅优于 baseline,无需调参 +2. IDM 作为折扣系数,逻辑简洁 +3. 过滤方式(方式三)阈值敏感,容易过拟合 + +**保留代码**: +- `slope_r2_idm_score` 函数已实现 +- `FactorType.SLOPE_R2_IDM` 枚举已添加 +- 可通过配置 `type: slope_r2_idm` 启用 + +### 5.2 多周期融合结论 + +**结论**:不适用于本策略 + +**原因分析**: + +1. **长窗口反应太慢**:252 天窗口在趋势转折时严重滞后。2022 年全球熊市、2024 年风格切换时,ensemble 无法及时退出 + +2. **调仓次数骤降**:从 363 次降到 167 次,说明信号太稳定了,错过了很多轮动机会。这个策略的核心价值就是**轮动**,过于稳定的信号反而不利 + +3. **短债填充减少**:从 32% 降到 16.6%,说明 ensemble 在弱势市场中也倾向于持有风险资产(因为长窗口记忆了之前的上涨趋势),导致回撤增大 + +4. **创业板指上升的原因**:2024-2025 年创业板有持续上涨趋势,ensemble 的长窗口恰好捕捉到了这个趋势,但这不是"持续性偏好",而是"恰好匹配" + +**核心问题**: + +这个策略的 alpha 来源是**中短期轮动**(25 天窗口),不是长期趋势跟踪。ensemble 把因子变成了半趋势跟踪因子,与策略的核心逻辑冲突。 + +### 5.3 最终决策 + +**维持现有配置**: +```yaml +factor: + n_days: 25 + type: slope_r2 +``` + +**理由**: +1. 25 天窗口与策略的轮动逻辑匹配 +2. 多周期融合与策略核心逻辑冲突 +3. IDM 融合虽然有效,但提升有限,暂不启用 + +--- + +## 六、参考资料 + +### 学术文献 + +1. **Jegadeesh, N., & Titman, S. (1993)**. Returns to Buying Winners and Selling Losers: Implications for Stock Market Efficiency. *Journal of Finance*, 48(1), 65-91. + +2. **Asness, C. S., Moskowitz, T. J., & Pedersen, L. H. (2013)**. Value and Momentum Everywhere. *Journal of Finance*, 68(3), 929-985. + +3. **Moskowitz, T. J., Ooi, Y. H., & Pedersen, L. H. (2012)**. Time Series Momentum. *Journal of Financial Economics*, 104(2), 228-250. + +### 业界实践 + +- [Momentum Factor Investing: 30 years of Out of Sample Data](https://alphaarchitect.com/momentum-factor-investing-30-years-of-out-of-sample-data/) +- [Systematic Trend-Following with Adaptive Portfolio Construction](https://arxiv.org/html/2602.11708v1) +- [Value and Momentum Everywhere - AQR Capital Management](https://www.aqr.com/Insights/Research/Journal-Article/Value-and-Momentum-Everywhere) + +--- + +## 七、附录:代码实现 + +### 7.1 IDM 融合因子 + +```python +def info_dispersal_momentum(prices: np.ndarray) -> float: + """Information Dispersal Momentum (IDM): 正收益天数占比""" + if len(prices) < 2: + return 0.0 + returns = np.diff(prices) + positive_days = np.sum(returns > 0) + return positive_days / len(returns) + + +def slope_r2_idm_score(prices: np.ndarray) -> float: + """Slope * R² * IDM: 趋势强度 × 拟合质量 × 上涨持续性""" + sr2 = slope_r2_score(prices) + idm = info_dispersal_momentum(prices) + return sr2 * idm +``` + +### 7.2 多周期融合因子 + +```python +def slope_r2_ensemble_score(prices: np.ndarray, windows: list = None) -> float: + """多窗口 slope_r2 等权融合""" + if windows is None: + windows = [63, 126, 252] # 3月、6月、12月 + + scores = [] + for w in windows: + if len(prices) >= w: + window_prices = prices[-w:] + score = slope_r2_score(window_prices) + scores.append(score) + + return sum(scores) / len(scores) if scores else 0.0 +``` + +--- + +**文档版本**:v1.0 +**最后更新**:2026-06-12 +**实验状态**:已完成