指标重画揭秘:历史里完美的信号为何实盘消失
重画(Repaint)如何骗过你的回测,以及如何在实盘前识破它
一、什么是「重绘」
重绘(repaint)指指标在历史 K 线上反复改写自己已经「画过」的数值:你回头看历史,信号清清楚楚;但当时实时触发时,那个箭头可能根本不存在,或位置完全不同。重绘让历史回测「看起来很美」,实盘却失效,是新手被「圣杯指标」骗的最常见原因,也是很多「完美信号」指标的真实面目。
二、重绘的两类
第一类「即时重画」:当前未收盘 K 线上的信号,在价格跳动时不断变化,收盘后才固定。这类在实时预警场景可接受,但绝不能用于回测入场,因为回测按收盘价成交时它早已变过不止一次。
第二类「历史重画」:指标修改已经收盘的、更早 K 线上的数值。这是最危险的,因为它让整个历史图表「被美化」,任何基于它的策略回测都是欺诈性的——EA 在历史里看到的是未来才确定的信号,等于用后见之明炒过去的钱。
三、为什么 EA 最怕重绘
EA 在回测中按历史逐根 K 线调用 OnCalculate。若指标回写历史,EA「看到」的是未来才确定的信号,于是回测盈利爆炸、实盘亏钱——典型的前视偏差(look-ahead bias)。你看回测曲线觉得稳赚,实盘一跑就崩,根源往往就在这里。
更隐蔽的是:重绘指标会让 EA 的「平仓信号」也来自未来,于是回测里每次都能卖在最高点、买在最低点。这种完美无法在实盘复制,因为它依赖的信息当时根本不存在。
四、判断重绘的 3 个信号
- 把图表往左翻,对比「当时实时截图」与「现在看到的历史」,箭头位置是否变了
- 指标在 OnCalculate 里用到未收盘 K 线的 Close,且把结果写回更早的 buffer 索引
- 用 iCustom 取历史值时,结果与图表实时显示不一致(说明它依赖后续 tick)
五、非重绘写法要点
核心纪律:只在本根 K 线收盘后,才把信号写入对应 buffer;严禁回写 index < i 的历史单元。借助 prev_calculated 跳过已计算部分提升效率,并在最后一根 K 线上只做「预测显示」、不计为历史信号。这样无论何时回看,信号位置都固定不变。
另一个要点是不要在 OnCalculate 之外(如定时器、事件)去改写历史 buffer。所有落信号的动作都收敛到 OnCalculate 的已收盘区间,才保证可复现。
int OnCalculate(const int rates_total, const int prev_calculated,
const int begin, const double& price[])
{
int start = (prev_calculated > 0) ? prev_calculated - 1 : begin;
for(int i = start; i < rates_total; i++)
{
if(i < rates_total - 1) // 仅已收盘 K 线落信号
ExtSignalBuffer[i] = (price[i] > price[i-1]) ? 1 : 0;
else
ExtSignalBuffer[i] = EMPTY_VALUE; // 末根只显示,不作历史信号
}
return rates_total;
}
六、实战:用 iCustom + 缓冲验证
在 EA 中通过 iCustom 取得指标句柄,用 CopyBuffer 取已计算缓冲。验证方法:在策略测试器用「每 tick」模式跑一遍,再把可视化结果与历史图表逐根比对;若信号位置一致且无漂移,才算可信。任何不一致都说明该指标在回测中「作弊」。
另一个做法是把指标信号导出成 CSV,与 EA 内部记录的交易理由逐笔核对。只有两层完全一致,你才能相信这套信号能在实盘复现。
七、哪些指标容易重绘
依赖 ZigZag(之字转向)类的指标天然对末端敏感;用当前 K 线极值做判断的支撑阻力指标、以及「在最后一根 K 线实时重算全部历史」的自定义脚本,都是重绘高发区。使用时务必确认其只在收盘后定稿,且从不回写历史。
八、重绘对参数优化的隐性破坏
遗传优化会在参数空间里搜索「最优」。若指标重绘,测试器每次评估都用未来信息,于是优化器找到的「最佳参数」只是最会作弊的参数——它在历史里看起来完美,实盘一跑就崩。这也是为什么重绘指标配合优化器,会产出最诱人也最危险的结果,必须从源头禁用。
九、用合成历史检验
一个朴素的检验:把历史价格序列整体平移一根 K 线(模拟「当时看不到未来」),再跑指标,看信号是否还和原图一致。若不一致,说明它依赖了未来信息。这种合成检验能快速暴露绝大多重绘隐患,成本极低。