← 文档库
📄 文档learn/量化工程优化/回测体系优化-滚动窗口与抗过拟合方案.md
🌱 创建于2026-06-27 01:04
✏️ 修改于2026-06-27 01:04
👁️ 浏览量—
🔗 短链接/~learn/s/a9783c30
🔐 可见性

回测体系优化 — 滚动窗口与抗过拟合方案

> 背景:当前回测评估函数使用指数衰减时间加权(decay=0.92),在全量历史上优化参数。此方法在 regime switch(牛熊切换)时会系统性失效——如果近年是熊市,筛选出的策略会偏向保守,导致牛市踏空。

---

一、问题根因:评估函数与 regime 的耦合

当前评估函数

Strategy.calculate_exponential_decay_annual_returns(decay=0.92) 按自然年划分数据,最近年份权重为 1,每早一年权重乘以 0.92。

权重分布(decay=0.92):
  今年(2026): 1.000
  去年(2025): 0.920
  前年(2024): 0.846
  3年前(2023): 0.779
  5年前(2021): 0.659
  10年前(2016): 0.434

失效场景:如果 2023-2025 是熊市,这三年合计权重占主导,筛选出的策略会高度适应熊市特征(低仓位频率、高避险偏好)。2026 年一旦牛市来临,这些特征全部变为劣势。

核心矛盾:指数衰减的假设是"近期规律更可能延续",但在 regime switch 时,近期规律恰恰是最不可靠的。

---

二、优化方案总览

方案优先级:2 > 1 > 3 > 4 > 5
原则:先止血(加 floor + benchmark),再治本(walk-forward)
# 方案 难度 效果 心理收益 状态
---------:---::---::---::---:
1 回测评价分段化(按 regime 分报告) 低 中 理解策略在什么环境下表现好 待实现
2 加入 Buy & Hold Benchmark 低 高 知道择时的边际价值 待实现
3 仓位区间可见性 + 子策略特征 低 高 消除踏空焦虑 ✅ 已实现
4 Walk-Forward 滚动窗口 中 高 验证策略在不同区段的稳定性 待实现
5 Regime Detection 元策略 高 高 牛市用牛市策略、熊市用熊市策略 远期

---

三、方案 1:回测评价分段化(按市场状态分报告)

做法

在全量回测报告之外,额外按市场状态切片输出子报告:

═══════════════════════════════════════
  策略: RSIAndMAStrategy 分段回测报告
═══════════════════════════════════════

  📈 牛市区间 (如: 2019-01 ~ 2021-02)
    年化收益: 15.2%  |  最大回撤: -12.3%
    胜率: 62%  |  交易次数: 45

  📉 熊市区间 (如: 2021-03 ~ 2024-01)
    年化收益: -3.1%  |  最大回撤: -8.5%
    胜率: 48%  |  交易次数: 52

  ↔️  震荡区间 (如: 2024-02 ~ 2026-06)
    年化收益: 5.7%  |  最大回撤: -6.2%
    胜率: 55%  |  交易次数: 38

实现要点

代码改动量

约 30 行(在 evaluate_strategy_metrics 中增加分段输出)。

---

四、方案 2:加入 Buy & Hold Benchmark(推荐优先级最高)

问题

当前策略池没有"满仓不动"作为参照系。你无法回答:择时策略比 buy & hold 到底好在哪里?好多少?

做法

在策略池中注册一个 BuyAndHoldStrategy:

class BuyAndHoldStrategy(Strategy):
    """基准策略:始终满仓,不做任何择时"""
    def generate_signals(self):
        self.df['position'] = 1.0
        self.df['signal'] = 0

推送报告中增加一行对比:

─────────────────────────────────────────────
  📊 策略组合 vs 基准
─────────────────────────────────────────────
  择时加权仓位: 0.40%    基准(buy&hold): 100.00%
  择时年化收益: 3.2%     基准年化收益: 12.5%
  择时最大回撤: -4.1%    基准最大回撤: -18.3%
  择时夏普比率: 0.85     基准夏普比率: 0.62
─────────────────────────────────────────────

心理价值

当你的择时仓位=0.4% 而大盘涨了 5% 时,你会看到:

这个对比让你清晰地看到:你付出的"踏空代价"换来了"低回撤"。这不是系统错了,这是你做的 trade-off。

---

五、方案 3:仓位区间可见性 — 解决踏空焦虑的核心 ✅ 已实现

问题

你的择时策略会把仓位压到 ≈0%。当市场上涨时,你完全没参与,且看不到策略还要多久才会切换回来,产生焦虑。

为何不用 Position Floor

之前考虑过 Core-Satellite(设仓位下限,永远不空仓),但这在超级大熊市中会一直亏损,违背了趋势跟踪的本质。正确的方式不是强行改变仓位,而是让你看到转换周期。

已实现的方案:仓位区间转换周期统计

推送中现在会自动显示每个品种的:

📊 仓位区间分布与转换周期

  历史仓位分布:
    极低仓位(<20%):  49.4% ██████████░░░░░░░░░░  平均39.5天  最长46天
    高仓位(>80%):    39.4% ███████░░░░░░░░░░░░░  平均31.5天  最长32天

  低→高仓位转换周期:
    极低(<20%) → 高(>80%): 平均 40.5 天, 最长 42 天

  🔍 当前状态: 极低仓位(<20%), 已连续 12 天
  ⏳ 历史平均还需 28 天恢复至高仓位(最长 42 天)

以及每个子策略的:

📋 子策略交易特征:
  RSIAndMAStrategy-open-8-63 [趋势策略]: 平均持仓23天, 资占45.2%, 年交易8次, 胜率62.3%
  MomentumBounceStrategy [反转策略]: 平均持仓5天, 资占12.1%, 年交易42次, 胜率55.1%

心理价值

---

六、方案 4:Walk-Forward 滚动窗口(治本方案)

原理

不在全量历史上优化参数,而是用滚动窗口模拟"随时间推移不断重新优化"的过程:

  训练窗口[2016-2019] → 验证窗口[2020]
  训练窗口[2017-2020] → 验证窗口[2021]
  训练窗口[2018-2021] → 验证窗口[2022]
  训练窗口[2019-2022] → 验证窗口[2023]
  训练窗口[2020-2023] → 验证窗口[2024]
  训练窗口[2021-2024] → 验证窗口[2025]
                ↓
        汇总所有验证窗口的表现 → 这才是策略的"真实预期表现"

为什么 Walk-Forward 能解决 regime switch 问题

实现框架

class WalkForwardOptimizer:
    def __init__(self, strategy_class, param_grid, train_years=4, step_years=1):
        self.train_years = train_years  # 训练窗口长度(年)
        self.step_years = step_years    # 步长(年)

    def run(self, df):
        all_results = []
        start_year = df.index.year.min()
        end_year = df.index.year.max()

        for test_start in range(start_year + self.train_years, end_year + 1, self.step_years):
            train_end = test_start - 1
            train_start = train_end - self.train_years + 1

            # 训练窗口
            df_train = df[(df.index.year >= train_start) & (df.index.year <= train_end)]
            # 验证窗口
            df_test = df[df.index.year == test_start]

            # 在训练窗口上做网格搜索 → 找最优参数
            best_params = self.grid_search(df_train)
            # 用最优参数在验证窗口上跑一次
            test_result = self.evaluate(df_test, best_params)

            all_results.append({
                'train_window': f'{train_start}-{train_end}',
                'test_year': test_start,
                'best_params': best_params,
                'test_return': test_result,
            })

        return all_results

关键指标

Walk-Forward 跑完后,看两个东西:

---

七、方案 5:Regime Detection 元策略(远期方案)

原理

                 ┌─→ 如果判断为牛市 → 使用牛市策略池(高仓位、低避险)
市场状态检测 ────┤
                 └─→ 如果判断为熊市 → 使用熊市策略池(低仓位、高避险)

简单的市场状态判断

def detect_regime(df):
    """用最简单的均线关系判断牛熊"""
    ma_60 = df['close'].rolling(60).mean().iloc[-1]
    ma_200 = df['close'].rolling(200).mean().iloc[-1]
    current_price = df['close'].iloc[-1]

    if current_price > ma_60 > ma_200:
        return 'bull'      # 牛市:价格在均线之上,短期均线在长期之上
    elif current_price < ma_60 < ma_200:
        return 'bear'      # 熊市
    else:
        return 'range'     # 震荡

对应仓位策略

市场状态 择时仓位上限 Core仓位 说明
---------:----------::------:------
牛市 100% 50% 充分参与
震荡 70% 40% 保留部分现金
熊市 40% 30% 防御为主

---

八、实施路线

第一优先级(已实现):
  ✅ 方案3: 仓位区间可见性 + 子策略交易特征摘要

第二优先级(本周或下周,小改代码):
  □ 方案2: 推送中增加 buy & hold benchmark 对比行
  □ 方案1: 评估报告中按牛市/熊市/震荡分段输出
  □ 降低 eval 的 decay 系数: 0.92 → 0.98(让历史年份权重更均匀)

第三优先级(下周+,中改代码):
  □ 方案4: 实现 Walk-Forward 滚动窗口优化器
  □ 对比 Walk-Forward 结果与全量回测结果的差距

远期探索:
  □ 方案5: Regime Detection 元策略

---

九、与现有系统的关系

现有系统组件 本方案的影响
------------------------
StrategyOptimizer.py 新增 WalkForwardOptimizer 类
Strategy.evaluate_strategy_metrics() 增加分段输出
show_marker.py / allocation_utils.py 增加 CORE_POSITION 参数和 benchmark 对比
消息推送.txt 输出格式增加 benchmark 对比行

---

TODO

▶ 📜 变更历史(1 次提交)

4616ca62026-06-27 01:04docs: 同步 learn/ 文档 & 工作区改动
🍃quant 知识库
by 伊甸园和枫

🔊 播放设置

勾选音源后调节配比,总和自动归一化
已选 0 个 · 合计:0%
合成中...
00:00/00:00
1.0x

分享图片

生成中...