【TBQuant3-新手指南】量化回测的两大陷阱:“信号闪烁”与“偷价”

量化回测的两大陷阱:“信号闪烁”与“偷价”

在量化交易的世界里,回测是策略从构想走向实战的必经之门。一道完美的回测资金曲线,是每个交易者梦寐以求的圣杯。然而,这条曲线往往隐藏着致命的幻觉——许多在回测中表现优异的策略,一旦投入实盘,便会失效甚至亏损。究其根源,“信号闪烁”与“未来函数偷价”是制造这些幻觉的两大元凶。只有彻底理解并根除它们,才能锻造出经得起市场考验的可靠策略。

第一部分:信号闪烁

什么是信号闪烁?

信号闪烁,指的是在单根K线的形成过程中,交易信号在某个瞬间出现,但在该K线最终走完并收盘时,这个信号又消失了的现象。

信号闪烁是如何产生的?

其核心原因在于:策略逻辑错误地依赖于K线盘中不稳定的、瞬时的中间价格数据,如最新价(close),而不是一个稳定的图表价格价格。

典型场景还原:

  • 策略逻辑: “当价格close突破上一根K线的最高价时,立即开仓买入。”
//错误的写法
Events
    OnBar(ArrayRef<Integer> indexs)
    {
        Numeric a = Highest(high[1], 5);
        if(close > a)
        {
            buy(1,close);
        }
    }
  • 回测中的过程:当前K线正在运行中,价格一路上冲,瞬间突破了上一根K线的最高点。图表检测到这一突破,立刻记录一个“开多”信号,并以理想化的最新价标记成交。然而,市场并未继续上涨,价格随后回落。当这根K线最终收盘时,其收盘价已经低于了上一根K线的最高点。
  • 在回测中: 系统忽略了一次失败的开仓和平仓(由于信号消失,策略多下一单,并且丧失了平仓的方法),这次交易可能是小幅盈利或亏损,但重要的是,它掩盖了一个事实,一个开仓的事实。在实盘中: 更可能的情况是,下单后,价格已经回落,导致持仓变成无法处理的情况。
  • 图示1:错误情况的展示

如何根治信号闪烁?

黄金法则:永远基于已经确定的历史数据做决策。

  1. 使用“收盘价模型”: 这是最根本的解决方法。将策略的所有逻辑判断延迟到K线结束时进行。例如,将策略改为:“如果本根K线收盘后,其收盘价突破了上一根K线的最高价,那么在下一根K线开盘时买入。” 这样,所有的信号都建立在已经固定的数据之上,不会再发生变化。
  2. 使用正确的价格: 例如上个例子中,在编写代码时,确保条件判断引用的是已经固定或相对固定的值,比如HIGH,LOW。这个例子纠正了上面错误例子的问题。
//正确
Events
    OnBar(ArrayRef<Integer> indexs)
    {
        Numeric a = Highest(high[1], 5);
        if(high > a)
        {
            buy(1,max(open,a));
        }
    }

3. 审阅交易记录: 在回测报告中,仔细检查每一笔交易的开仓信号。问自己一个问题:“在这根K线彻底走完后,这个开仓信号是否依然成立?” 如果答案是否定的,那么你的策略就存在信号闪烁。

4. 进行逐笔数据回测: 在有条件的情况下,盘中或利用回放观察策略变化,有闪烁肉眼可以看到。

第二部分:偷价和未来函数

如果说信号闪烁是让你“抓不到”信号,那么偷价则是让你在回测中“作弊”,它制造出的完美盈利假象,危害性更大。

什么是偷价和未来函数?

  • 未来函数: 指策略在回测的某个时间点上,使用了在该时点还不可能知道的未来信息。
  • 偷价: 指在回测中,系统使用了一个在信号触发时点不可能成交的、过于理想化的价格来进行盈亏计算。

两者的共同本质: 都是在回测中开辟了一条“隧道”,让策略能够“先知先觉”,而在实盘中完全无法复现回测的辉煌战绩。

未来函数与偷价的典型场景

场景一:最低价偷价买入

  • 策略逻辑: “当价格突破20日高点时,以当日最低价开仓买入。”
  • 问题剖析: 突破行为发生在价格已经上涨到一定程度之后。此时,当天的“最低价”很可能成为过去,你绝无可能回到过去,以那个最低价格成交。这是一种典型的“偷价”行为,它极大地低估了实际的成交价,使得回测中的入场价显得过于完美。
//错误案例
Events
    OnBar(ArrayRef<Integer> indexs)
    {
        buy(1,low);
        sell(1,high);
    }


场景二:信号价格加入有利偏移


//错误
Events
    OnBar(ArrayRef<Integer> indexs)
    {
        Numeric a = Highest(high[1], 5);
        if(high > a)
        {
            buy(1,max(open,a)-10);
        }
    }

在买入开仓上,减去一个额外的点差。相当于每次偷了一个固定金额。不要小看偷得少(比如1跳),积累下来曲线就会更好看。

如何杜绝未来函数与偷价?

核心原则:在回测的任何一个时间点,策略只能使用该时点及之前的历史数据。

  1. 采用合理的成交价模型:例如使用 Open(下根K线开盘价)作为成交价。
  2. 警惕特殊函数: 某些用于识别峰谷的函数(如 zigzag 等),如果使用不当,可能会在算法中引入未来信息,需要仔细审查其机制。
  3. 注意不要错标价格,用错了价格,有时候偷价可能是无心之举。


结论:从“回测幻境”走向“实盘赢家”

“信号闪烁”与“未来函数偷价”是量化交易者在策略开发初期必须跨过的两道鸿沟。它们一个让策略信号变得虚无缥缈,无法捕捉;另一个则让策略绩效变得虚假繁荣,不堪一击。

要打造一个坚实可靠的策略,必须秉持着比法庭证据更严谨的态度对待回测:

  • 根治闪烁: 坚持使用收盘价或确定的历史数据进行逻辑判断。
  • 杜绝偷价: 确保每一个决策和成交价都立足于当下,不占未来的丝毫便宜。

最终,一个策略的价值不在于它在历史数据上能画出多美的曲线,而在于其逻辑的严谨性与在实盘中的复现能力。记住,在量化交易中,可靠性永远优先于回测收益率。 只有经得起“真实性”拷问的策略,才真正具备在残酷市场中生存并盈利的资格。

【TBQuant3-新手指南】如何查看回测报告?
【新手福利】1分钟上手量化交易!零基础量化从实操入手,终结散户困境!(内附新手指南)
【TBQuant3-新手指南】为什么策略交易盈亏和账户盈亏看上去不同
如何避免偷价
【TBQuant3-新手指南】如何在K线图启动量化交易
【TBQuant3-新手指南】 委托偏移是什么?
【TBQuant3-新手指南】定时登录的设置
偷价和信号闪烁是啥意思?和滑点有啥不一样
咨询偷价问题
新手对信号闪烁的几个疑惑

王老师,我看你的文章,感到更困惑了, 我看了这个视频:

https://video.tbquant.net/video?id=video412

https://video.tbquant.net/video?id=video444


介绍了为了解决信号闪烁问题尽量用固定价格, 还说用Open作为成交价是偷价行为, 那您这个文章总结如下:

核心原则:在回测的任何一个时间点,策略只能使用该时点及之前的历史数据。

  1. 采用合理的成交价模型:例如使用 Open(下根K线开盘价)作为成交价。
  2. 警惕特殊函数: 某些用于识别峰谷的函数(如 zigzag 等),如果使用不当,可能会在算法中引入未来信息,需要仔细审查其机制。
  3. 注意不要错标价格,用错了价格,有时候偷价可能是无心之举。

这个应该和视频介绍的恰恰相反, 您推荐用Open作为成交价。我不知道您和视频说的到底哪个是对的? 另外自己尝试了一下, 用tb内置的策略, 里面大多都是Open作为成交价, 有些夏普可以达到3以上, 但是如果我改为close作为成交价, 夏普直接都是-10, 误差太大了。

所以您能否给讲解一下下列两种情况:

1. 如果我在OnbarClose中做最后一次计算和发单, 也不会有闪烁问题, 那应该用Open发单还是close发单?

2. 如果我在Onbar中计算指标和发单, 不考虑信号闪烁问题,那应该是用Open还是close发单?

3. 我用Open发单,是否tb默认就是在下一个bar的开盘价成交? 但是这样回测和实盘能否一致? 因为我发现我在OnbarClose中用Open发单(夏普是3)和Close发单(夏普是-10),误差也是很大的。

以上问题让我很困惑 , 能否给指导一下,我如何实现回测与实盘尽量一致,到底用哪个价格发单更合理。


注意在什么情况下用的open, 你用open必然也是在开盘的时刻下单

你在非K线开盘的位置下单用open,那也是错的

王老师👍

学习了