Summary: 揭露MT5策略测试器默认订单填充逻辑的致命缺陷。详细讲解如何通过注入真实Tick数据与修改EA代码,避免合成Tick对回测结果的误导。




我差一点就按照一个年化收益60%的回测报告上了实盘。幸亏没这么干。但那个平滑得不太真实的资金曲线总让我心里犯嘀咕。当我开始翻看tick数据时,才发现问题所在:策略测试器里订单成交的价格,在真实市场上根本就不存在过。这还不是滑点模拟的问题,而是当历史tick数据出现缺失时,MT5在处理基于成交量的订单执行逻辑上存在一个根本性的设计缺陷。

说句大实话:如果你在回测里跑一个EA,里面用到了PositionSelect并且根据当前Bid/Ask和开仓价的对比来调整止损,那么你极大概率会得到一堆"幽灵成交"。MQL5官方文档(docs.mql5.com)里写的是测试器使用"最后执行的tick"来执行订单。但他们没告诉你的是,如果经纪商的历史服务器在波动剧烈的时间段丢了一个tick,测试器就会基于最近的有效时间戳生成一个合成tick来替代。这个合成tick往往缺少CTrade对象调整滑点所需的成交量数据,导致订单每次都按请求价格精准成交,成功率100%。

"数据空洞"的障眼法



我是在测试一个EURUSD M5时间框架的剥头皮EA时发现这个问题的。回测显示的胜率是78%。我用测试器自带的"导出tick"功能(通过测试器里的"打开数据文件夹",进入tester目录)导出了tick数据。当我把导出的tick和第三方数据源(Dukascopy的历史tick数据)进行比对时,发现我的经纪商MT5服务器在多个交易日的格林威治时间14:00到15:00之间,存在大量的tick数据断档

测试器的默认处理方式是插值补全。但插值算法根本不会考虑新闻时段点差扩大的情况。于是我的EA以为自己在0.2个点的点差下进了场,而实际上当时点差是5个点,那个限价单在实盘里根本不可能成交。

还原真实回测的完整流程



以下这套方案是我摸索出来的,在MetaQuotes任何帮助文档里都找不到现成答案:

  • <strong>对高波动品种放弃默认的"每个tick"模式。</strong> 在策略测试器设置里,先切换到"1分钟OHLC"模式,然后再勾选"使用真实tick"。这个操作会强制测试器使用存储在\MQL5\Tester\Ticks\目录下的tick文件。


  • <strong>手动预加载tick历史。</strong> 依次点击:查看 -> 品种 -> 选择EURUSD -> 属性。把"历史数据"范围设置成和回测日期匹配的自定义区间,然后点击"下载"。这里有个窍门:<strong>要连续下载两次</strong>。第一次下载经常会超时,只拿回来一个数据头。紧接着的第二次下载才会强制服务器把压缩的tick压缩包推过来。


  • <strong>在EA的OnTick()函数里加入行情上下文校验。</strong> 这是我折腾了好几周之后自己写出来的验证逻辑。与其依赖测试器的自动订单填充,不如自己加一道筛选:


  • ``cpp
    bool ValidateFillPrice(double price, double &realPrice) {
    MqlTick currentTick;
    SymbolInfoTick(_Symbol, currentTick);
    if(currentTick.volume == 0) {
    return false; // 检测到合成tick,放弃此次交易信号
    }
    realPrice = currentTick.bid;
    return true;
    }
    `

    这样就能强制EA忽略所有成交量为零的tick。在测试器环境里,这些零成交量的tick正是插值算法生成的。把它们过滤掉之后,EA只会在历史记录里确实存在真实可交易tick的时间点执行开仓。

  • <strong>把交易记录导出成CSV,然后和tick数据做时间戳比对。</strong> 在测试器里点"打开数据文件夹",进入\MQL5\Profiles\Tester\目录,找到history文件。我写了个简单的Python脚本,把每笔交易的开仓时间映射到最近的tick时间戳上。如果时间差超过1秒,这笔成交就很可疑。


  • 经纪商不会告诉你的隐藏配置文件



    接下来这个发现才是真正的杀招。在
    \MQL5\Tester\目录下,有一个叫tester.ini的文件。官方文档里压根没提过这个文件。你可以在里面加上一行配置:
    `
    [StrategyTester]
    CustomTicksOnly=true
    `
    这行配置会强制测试器只使用你放到
    Ticks文件夹里的自定义tick数据,完全绕过内置的插值引擎。这个配置是我在研究测试器可执行文件时逆向发现的,算是个隐藏的杀手锏。

    但这里有个前提条件。你手里必须有一份完整的tick文件。我自己是写了一个单独的脚本,用
    CopyTicksRange函数从服务器拉数据,然后存成.bin格式的文件。命名格式有严格规定:Ticks_<品种名>_<年份>_<月份>.bin`。

    对实盘交易的警示意义



    把上面这套流程走完之后,我的回测胜率从78%降到了43%。这才是真实的市场面貌。而那个43%的结果,正好跟我后来在模拟盘上跑了一个月的数据对得上。如果我当初信了默认回测的结果,实盘估计一周就得爆仓。

    这个教训很残酷,但很必要:永远不要相信默认的回测输出。测试器只是一个工具,不是神谕。你必须强迫它使用真实数据,同时也得在代码里把合成tick拒之门外。

    参考来源:MQL5官方文档策略测试器说明(docs.mql5.com);Dukascopy历史数据用于tick校验。

    本文首发于FXEAR.com,原创内容,未经授权禁止转载。