第 6 章 · 05 递归偏移检测 recursive-analysis


文档摘要

第 6 章 · 05 递归偏移检测 recursive-analysis 本节摘要:本节讲清回测诊断里比未来函数更隐蔽的一类问题——递归指标偏移。所谓「递归」,是指当前值依赖前一值(如 EMA: )。这类指标在「数据起点不同」时会收敛到略不同的值,导致回测(数据长)和 Dry-Run/实盘(交易所只给最近 N 根)算出来的指标值有偏差,进而让信号不一致。freqtrade 的 命令专门检测这个:它用不同长度的 startup 数据算指标,对比最后一根 K 线上的值差异。本节先讲递归偏移的数学直觉,再讲为什么默认 startup 用奇数、命令的工作流程,然后逐指标解读输出表(方差百分比),最后给出「判断阈值」和 shift 修正的实务建议——目标不是零方差,而是「方差小到不影响买卖决策」。

第 6 章 · 05 递归偏移检测 recursive-analysis

本节摘要:本节讲清回测诊断里比未来函数更隐蔽的一类问题——递归指标偏移。所谓「递归」,是指当前值依赖前一值(如 EMA:EMA_t = α·price + (1-α)·EMA_{t-1})。这类指标在「数据起点不同」时会收敛到略不同的值,导致回测(数据长)和 Dry-Run/实盘(交易所只给最近 N 根)算出来的指标值有偏差,进而让信号不一致。freqtrade 的 recursive-analysis 命令专门检测这个:它用不同长度的 startup 数据算指标,对比最后一根 K 线上的值差异。本节先讲递归偏移的数学直觉,再讲为什么默认 startup 用奇数、命令的工作流程,然后逐指标解读输出表(方差百分比),最后给出「判断阈值」和 shift 修正的实务建议——目标不是零方差,而是「方差小到不影响买卖决策」。

内容来源:原项目文档 docs/recursive-analysis.md,汉化并套用体系化模板。

学习目标

阅读完本节,你应当能够:

  1. 解释递归指标(EMA、RSI 等)为何在不同数据长度下产生偏移。
  2. 说清回测 vs 实盘的数据长度差异如何放大这个偏移。
  3. 理解为什么默认 startup_candle_count 是奇数
  4. recursive-analysis读懂输出表(各方差百分比)。
  5. 给出合理的判断阈值和 shift 修正建议。

一、递归偏移:数学直觉

递归公式定义「当前项依赖前一项」。一个最简单的例子叫 steps:第一行 = 0,之后每行 = 前一行 + 1。

  • 用最新 1000 根 K 线算:最后一行的 steps = 999。
  • 用最新 500 根 K 线算:最后一行的 steps = 499。

同一个时间点,数据长度不同,算出来的值就不同。 这就是递归偏移。

真实指标里,EMA 是典型递归指标:

EMA_t = α · close_t + (1 - α) · EMA_{t-1} 其中 α = 2 / (period + 1)

EMA 的初值需要某根历史 K 线的 close 来「种子化」。数据起点不同,种子不同,经过有限次迭代后收敛到的值也会有微小差异。RSI、MACD(含 EMA)等也都隐含递归。

二、为什么回测和实盘会有差异

关键在于 freqtrade 在不同模式下拿到的数据长度不同:

模式 数据长度
回测 你下载的全部历史(可能几千根)
Dry-Run / 实盘 交易所 API 每次给的有限根数(如 Binance 单次 1000 根,最多连 5 次 = 5000 根)

所以回测时 EMA 用了(比如)3000 根数据收敛,实盘只用最近 4999 根——两者算出的 EMA 值在最后一根 K 线上会有微小差异。如果你的策略恰好在这种边界上触发信号(比如 close > EMA),回测说「买」,实盘可能说「不买」——这就是递归偏移造成的回测/实盘不一致。

⚠️ 隐蔽性:这个差异通常很小(百分之零点几),但足以让临界信号翻转,让回测结果无法在实盘复现。它不像未来函数那样「印钞式失真」,而是「悄无声息地让信号对不上」。

三、为什么默认 startup 用奇数

这是 freqtrade 的一个细节:默认的 startup_candle_count 推荐用奇数(如 999 而非 1000)。原因是交易所 API 的分页机制。

以 Binance 为例,单次 API 调用最多返回 1000 根 K 线:

  • 你要 999 根 startup → 引擎请求 1000 根,最后 1 根是「当前 K 线」,前 999 根是 startup。正好一次调用,干净。
  • 你要 1000 根 startup → 引擎请求 1001 根,API 分两次返回(1000 + 1),引擎以为需要 1001 根,于是下次下载 2000 根(1 当前 + 1999 startup)。多了一次调用,浪费。

而且 Binance 限制连续 5 次批量调用,所以最多能拿 5000 根,即 startup 上限约 4999(奇数)。

💡 规则:startup_candle_count 用奇数(如 199、499、999、1999、4999),让 API 调用恰好对齐分页,避免浪费。

四、recursive-analysis 工作流程

命令的逻辑很直接——改变 startup 长度,看指标值变不变:

不做回测,只算指标。关注的焦点是「最后一根 K 线上,各指标值随 startup 变化的方差」。

命令与推荐设置

freqtrade recursive-analysis \ --strategy MyStrategy \ -p BTC/USDT \ --timerange 20260101-20260801

推荐实践:

建议 原因
-p 指定一个高价位、中等波动币(如 BTC、ETH) 避免价格太低导致浮点精度问题扭曲结果
--timerange 至少 5000 根 K 线(5m 周期约 18 天) 让基准计算本身的递归问题小到可忽略
--cache 强制 none 避免加载旧缓存

不指定 -p 时,默认用白名单第一个币。

五、读懂输出表

典型输出(有递归问题的策略):

| indicators | 20 | 40 | 80 | 100 | 150 | 300 | 999 | |--------------+---------+---------+--------+--------+---------+---------+--------| | rsi_30 | nan% | -6.025% | 0.612% | 0.828% | -0.140% | 0.000% | 0.000% | | rsi_14 | 24.141% | -0.876% | 0.070% | 0.007% | -0.000% | -0.000% | - |
  • 列头:不同的 startup_candle_count(20、40、80、100、150、300、999)。
  • 单元格:该指标在最后一根 K 线上、用该 startup 算出的值,与基准的百分比差异
  • nan%:数据不够,算不出。比如 rsi_30 在只有 21 根(1 当前 + 20 startup)时无法计算(需要至少 30 根)。
  • -:零方差(完美收敛)。

解读规则

  • 方差随 startup 增大而趋近 0:正常,递归指标会收敛。
  • 某个 startup 之后方差已很小(如 rsi_14 在 startup=100 之后都在 0.01% 以内):说明 startup 取到这个值就够用了。
  • 方差很大且收敛慢(如 rsi_30 在 startup=40 时还有 -6%):说明这个指标对 startup 敏感,需要更大的 startup_candle_count。

💡 目标不是零方差:追求绝对零方差往往需要不切实际的超长 startup(可能超出交易所 API 上限)。目标是「方差小到不影响买卖决策」。比如指标值方差 < 0.1%,通常就不会让 close > EMA 这类条件翻转。

六、Caveats(命令的局限)

  1. 只看最后一根 K 线:报告的是末行指标值的方差,不直接告诉你「这个方差是否真的让信号翻转」。需要你自己结合策略条件判断。
  2. 只算 populate_indicators@informative:如果你把指标计算写在 populate_entry_trend / populate_exit_trend 里,它不会被检测。指标应该放在 populate_indicators(这也是最佳实践)。
  3. 附带简单 lookahead 检查:命令顺带对指标值做一个简单的未来函数检查;但完整的 lookahead 检测还是要用第 04 节的 lookahead-analysis

七、实务建议:判断阈值与 shift 修正

判断阈值

经验上:

方差量级 含义 行动
< 0.1% 可忽略 安全
0.1% ~ 1% 边界,需评估 看策略条件是否在临界区
> 1% 显著偏移 提高 startup 或换指标

提高 startup_candle_count

最直接的修正:把策略的 startup_candle_count 设到「方差已收敛到可忽略」的那个值。比如 rsi_30 在 startup=150 后方差 < 0.15%,那就设 startup_candle_count = 199(奇数,留余量)。

注意别超过交易所上限(Binance 约 4999)。

shift 修正(慎用)

某些场景下,作者会用 dataframe['ema'].shift(1) 把指标往后挪一根,确保「当前 K 线只用截止昨日的数据」。这能消除「当前根 EMA 还没最终确定」的问题,但会改变信号时机(信号延后一根 K 线)。这是设计选择,不是万能修正,需要回测验证延后的代价。

选对指标

如果某个指标对 startup 极度敏感、又非要很长的 startup 才收敛,考虑换一个等价的非递归版本,或缩短周期。例如用 WMA(权重线性)替代 EMA,在某些场景下递归敏感性更低。

八、与 lookahead 的关系

检测 关注 严重性
lookahead-analysis 偷看未来(作弊) 致命,策略无效
recursive-analysis 数据长度导致的值偏移 隐蔽,信号对不上

两者都要跑。lookahead 是「定性」(有没有作弊),recursive 是「定量」(偏多少)。一个策略上线前,应该两个都通过。

本节要点回顾

  1. 递归偏移:递归指标(EMA、RSI、MACD)当前值依赖前一值,数据起点不同→收敛值不同→信号可能翻转。
  2. 回测 vs 实盘:回测用全部历史(长),实盘用交易所给的有限根(短),两者算出的递归指标值有微小差异。
  3. 奇数 startup:默认 startup_candle_count 用奇数(999 等),对齐交易所 API 分页,避免浪费调用。
  4. recursive-analysis:改变 startup 长度重算指标,对比末行值与基准的百分比差异;不做回测,只算指标。
  5. 推荐设置:-p 选 BTC/ETH 等高价币、--timerange 至少 5000 根、--cache none
  6. 输出表:列头是不同 startup,单元格是方差%;nan% = 算不出,- = 零方差。
  7. 阈值:< 0.1% 可忽略,> 1% 需修;提高 startup_candle_count 是首选修正,shift 修正慎用;指标计算必须放 populate_indicators 才会被检测。
  8. 与 lookahead 关系:lookahead 是定性(作弊),recursive 是定量(偏移);两者都要过。

下一节(第 6 章末节),我们看 Edge 边缘分析模块——以及它在最新版 freqtrade 中已被移除的重要事实。


发布者: 作者: 灏天文库 转发
评论区 (0)
U