如何判断一个策略只是过拟合?

发布于

**“回测收益高”** 是最容易骗人的指标。一个策略回测年化 50%,最大回撤 10%,Sharpe 2+,看起来非常漂亮。但如果把它拿到真实市场里跑,可能很快就发现完全不是那么回事。想分享一下我目前判断策略是否过拟合的一些方法。

## 1. 先把训练集和测试集分开

比如我有 2018 ~ 2025 年的历史数据,不能直接拿全部数据调参数,然后说这个策略在 2018 ~ 2025 年表现很好。因为策略参数本身就是根据这些数据调出来的。我一般会先分成:

- 2018 - 2022:训练集
- 2023 - 2024:验证集
- 2025:测试集

训练集用来开发策略和调参数。验证集用来检查策略是不是还能工作。测试集最好完全不碰,最后才拿出来做一次真正的 out-of-sample test 。如果一个策略在训练集表现特别好,但到了验证集和测试集突然失效,我一般就会开始怀疑过拟合。

## 2. 参数稍微变化,策略就完全失效?

比如我做一个均线策略:

- fast_ma = 20
- slow_ma = 60

回测结果很好。然后我把参数稍微改一下:

- fast_ma = 19, slow_ma = 60
- fast_ma = 21, slow_ma = 60
- fast_ma = 20, slow_ma = 59
- fast_ma = 20, slow_ma = 61

如果结果都差不多,我会觉得这个策略可能找到了某种比较稳定的市场规律。但如果:

- 20 / 60 → 年化 45%
- 19 / 60 → 年化 5%
- 21 / 60 → 亏损
- 20 / 59 → 亏损
- 20 / 61 → 亏损

这种情况我就会比较警惕。因为这很可能不是策略本身有效,而是参数刚好“撞上”了历史数据中的某个特殊模式。所以我现在倾向看参数附近的 performance surface 。一个真正有一定 robustness 的策略,通常不会只有一个孤立的最优点,而应该是一片相对稳定的区域。

## 3. 换一个市场还能不能跑?

比如我做的是一个美股策略。如果策略只在 SPY 上表现很好,我会尝试 SPY 、QQQ 、IWM 、DIA ,甚至换成其他市场。当然,不是说一个策略必须在所有市场都赚钱。如果策略背后的逻辑是合理的,那么通常应该能够解释,为什么它在这个市场有效。如果只是“我在 SPY 上测试了 1000 组参数,最后发现这一组最好”,那我会觉得这个结果的可信度比较低。

## 4. 用 Monte Carlo 看看是不是太依赖某几笔交易

有些策略回测收益非常高,但仔细看发现,总收益的 70% 来自 3 笔交易。这种策略我也会比较担心。我一般会尝试对交易顺序、收益序列做 Monte Carlo simulation 。例如原来的交易结果是:

- +5%
- -1%
- +2%
- -1%
- +20%
- -2%
- +3%

我可以随机打乱交易顺序,重复跑很多次。然后观察:

- Median CAGR
- 5% worst CAGR
- 95% best CAGR
- Max Drawdown distribution
- Probability of ruin

如果随机后的结果和原始结果差别非常大,说明策略可能比较依赖特定的交易顺序。这不一定意味着策略过拟合,但至少说明风险可能比回测结果看起来更高。

## 5. 不要只看收益率

除了看年化收益率、夏普比率、最大回撤,我还会看:

- 交易次数
- 胜率
- 盈利因子
- 单笔平均收益
- 平均持仓时间
- 换手率
- 年度收益表现
- 不同市场环境下的表现
- 不同波动率环境下的表现

尤其是交易次数。如果一个策略回测了 10 年,最后只有 20 笔交易,然后 CAGR 50%。我会觉得这个结果的统计意义可能不太够,因为样本太少了。反之,如果一个策略 10 年做了几万笔交易,虽然每笔平均收益只有 0.05%,但经过手续费和滑点之后还能赚钱,我反而会觉得这种结果可能更值得研究。

## 6. 实盘数据和回测数据一定要尽量一致

很多策略回测使用的是日 K ,但真正交易的时候却需要 Tick 级数据和订单簿。这时候很容易出现一个问题:回测里看起来你是在某个价格成交的,但真实市场里根本没有办法以那个价格成交。尤其是

- 高频策略
- 短周期策略
- 突破策略
- 新闻事件策略
- 盘口策略

这些策略对数据质量和时间精度都比较敏感。我自己在测试实时策略的时候,会用上实时行情,主要是想把策略回测和实时行情环境尽量连接起来:

```python
import json
import websocket

API_KEY = "YOUR_API_KEY"

def on_message(ws, message):
data = json.loads(message)
print(data)

def on_open(ws):
print("WebSocket connected")

# 根据 API 文档发送订阅请求
subscribe_message = {
"action": "subscribe",
"symbols": ["AAPL"]
}

ws.send(json.dumps(subscribe_message))

ws = websocket.WebSocketApp(
f"wss://quote.alltick.co/quote-stock-b-ws-api?token={API_KEY}",
on_open=on_open,
on_message=on_message
)

ws.run_forever()
```

实战过程中要确保 **[回测数据 → 策略信号 → 实时行情 → 模拟成交 → 实际交易]** 这几个环节的数据定义尽量一致。否则可能出现一种情况:回测策略赚钱的原因,是因为你在历史数据里拥有“未来信息”。

## 7. Walk-forward Test

这是我目前比较喜欢的一种回测方法。
假设我有 2018 ~ 2025 年数据。可以这样滚动测试:

Train: 2018-2020
Test : 2021

Train: 2019-2021
Test : 2022

Train: 2020-2022
Test : 2023

Train: 2021-2023
Test : 2024

Train: 2022-2024
Test : 2025

每次只使用过去的数据训练和优化参数,然后拿未来的一段数据测试。这样可以模拟真实交易过程中:我只能看到过去的数据,然后决定未来怎么交易。如果一个策略在多个 walk-forward window 里都表现不错,我会认为它的 robustness 比单次回测更高。

## 8. 我自己现在判断过拟合,大概会看这几个信号

如果一个策略出现下面这些情况,我会提高警惕:

1. 参数稍微变化,收益就断崖式下降
2. 训练集很好,测试集明显失效
3. 换一个相似市场就完全不能用
4. 收益高度依赖少数几笔交易
5. 回测交易次数太少
6. 加入手续费和滑点后直接亏损
7. 使用更高频的数据后表现明显恶化
8. 策略逻辑很难解释为什么有效
9. 参数是经过大量试错后“挑”出来的
10. 不同时间窗口测试结果差异非常大

尤其是第 9 个,我觉得很容易被忽略。如果我尝试了 500 个策略,最后只挑一个表现最好的出来,然后说:我的策略年化 100%。这个结果本身其实没有太大意义,因为我实际上是在做一种隐性的 multiple testing 。如果测试的策略足够多,总会有一些策略“碰巧”在历史数据里表现特别好。

---

原文链接:[点击查看](https://www.v2ex.com/t/1230983)

评论

暂无评论。

0.087927s