myTestFreqAI/docs/lookahead_bias_fix.md
zhangkun9038@dingtalk.com fa813242c7 lookahead fix
2026-01-01 23:09:11 +08:00

5.7 KiB
Raw Blame History

看前偏差Lookahead Bias修复方案

变更日期2025-12-30
目标:减少回测结果的乐观偏差,提升回测与实盘一致性


问题描述

传统的 Freqtrade 策略使用全局指标计算方法(如 ta.rsi(), ta.ema() 等),这些方法在回测时会:

  1. 一次性计算所有历史数据的指标,导致每根 K 线都"看到了"完整的历史数据
  2. 实盘中逐行追加的 K 线只能基于过去的数据计算
  3. 造成回测信号过于乐观,实盘表现不如回测

示例

# 传统方法(存在看前偏差)
dataframe['rsi'] = ta.rsi(dataframe['close'], length=14)

在这种方式下,第 10 根 K 线的 RSI 值已经看到了第 11-N 根 K 线的数据,但实盘中第 10 根 K 线只能基于前 10 根计算。


解决方案

方案选择:使用原生 Pandas 方法

用滚动窗口rolling和指数加权移动平均EWM替代全局计算方法。

1. BB Bands布林带

改造前

bb_3m = ta.bbands(dataframe['close'], length=20, std=2.0)
dataframe['bb_lower_3m'] = bb_3m['BBL_20_2.0']
dataframe['bb_upper_3m'] = bb_3m['BBU_20_2.0']

改造后

bb_ma = dataframe['close'].rolling(window=20).mean()
bb_std = dataframe['close'].rolling(window=20).std()
dataframe['bb_lower_3m'] = bb_ma - (2.0 * bb_std)
dataframe['bb_upper_3m'] = bb_ma + (2.0 * bb_std)

原理rolling() 在每行计算时,只用了该行前面的 N 个数据,模拟实盘的逐行追加。


2. RSI相对强弱指数

改造前

dataframe['rsi_3m'] = ta.rsi(dataframe['close'], length=14)

改造后

delta = dataframe['close'].diff()
gain = delta.where(delta > 0, 0).rolling(window=14).mean()
loss = -delta.where(delta < 0, 0).rolling(window=14).mean()
rs = gain / loss
dataframe['rsi_3m'] = 100 - (100 / (1 + rs))

原理:改用 rolling 计算涨跌幅的移动平均,而非全局计算。


3. EMA指数移动平均

改造前

dataframe['ema_50_3m'] = ta.ema(dataframe['close'], length=50)

改造后

dataframe['ema_50_3m'] = dataframe['close'].ewm(span=50, adjust=False).mean()

原理

  • ewm() = Exponentially Weighted Moving Average指数加权移动平均
  • adjust=False:模拟实时计算,每行只用过去的数据
  • adjust=True 会向前看,因此必须设为 False

应用范围

本次变更覆盖了所有主要指标的计算:

时间框架 改造指标 方法
3m BB Bands, RSI, EMA(50/200) rolling / ewm
15m RSI, EMA(50/200) rolling / ewm
1h BB Bands, RSI, EMA(50/200) rolling / ewm

未改造指标(保持原样):

  • StochRSI复杂度高保留 ta.stochrsi()
  • MACD计算逻辑复杂保留 ta.macd()
  • ATR技术细节复杂保留 ta.atr()

代码变更详情

文件修改

文件/freqtrade/templates/freqaiprimer.py

方法populate_indicators()(第 289-400 行)

变更统计

  • 新增行数49 行
  • 删除行数27 行
  • 净增加22 行

关键改造

  1. 3m RSI:从 ta.rsi() 改为 rolling gain/loss 计算
  2. 3m EMA:从 ta.ema() 改为 .ewm(span=..., adjust=False)
  3. 3m BB Bands:从 ta.bbands() 改为 rolling mean/std
  4. 15m 同步改造RSI 和 EMA 使用相同方法
  5. 1h 同步改造BB Bands、RSI、EMA 都改造

预期效果

回测结果变化

  1. 入场信号减少:因为指标更保守

    • 预期:交易次数下降 10-30%
  2. 胜率可能上升:因为减少了虚假信号

    • 预期:胜率提升 5-15%
  3. 回测利润下降:不再享受"看前偏差"红利

    • 预期:利润下降 20-50%(取决于信号质量)

实盘一致性提升

  • Live vs Backtest 差异缩小:相对改善 30-50%
  • 交易稳定性增强:减少因策略问题导致的亏损

验证方法

运行回测对比:

# 变更前的快照(如有备份)
# vs 变更后的回测结果

比对指标:
1. 总交易数
2. 胜率 (Win Rate)
3. 利润 (Profit %)
4. Sharpe Ratio
5. 最大回撤 (Max Drawdown)

注意事项

⚠️ 性能影响

  • rolling 计算会增加轻微的 CPU 负载
  • 对于常规回测(< 10 年数据),影响可忽略
  • 对于长期回测(> 20 年),建议监控速度

⚠️ 与 FreqAI 的兼容性

  • 本次改造不影响 FreqAI 模型训练
  • FreqAI 仍然使用改造后的指标作为输入特征
  • 预期:因为指标更准确,模型表现可能更好

⚠️ 手动修改的指标

如果策略中有自定义指标,也应该遵循相同的改造原则:

  • rolling() 代替全局窗口函数
  • .ewm(adjust=False) 代替全局 EMA

相关参考


回滚方案

如果变更后回测结果不理想,可以快速回滚:

  1. 恢复 populate_indicators() 方法为原版本
  2. 重新运行回测对比
  3. 保留本文档作为参考

回滚命令(如有 Git 备份):

git diff freqtrade/templates/freqaiprimer.py  # 查看变更
git checkout HEAD -- freqtrade/templates/freqaiprimer.py  # 回滚

后续优化

  • 监控 Live 模式下的性能表现
  • 根据实盘反馈,调整 rolling 窗口大小
  • 考虑对 StochRSI 和 MACD 也进行类似改造
  • 评估是否需要调整其他指标参数(如 BB std 倍数)