回放系统首改:用服务依赖锁住指标编译(基础篇)
(1/3)· 回放系统能跑却总在周四崩?基础篇讲清服务文件两行指令怎么堵住模块缺失的坑
「给回放引擎加第一道实时修正」
MetaTrader 5 的回放系统核心是把历史 tick 按可控速率重新喂给图表,但原版在首次重播时常常出现报价跳变与本地时间不同步。我们在第 06 部分做的首次改进,目标只有一个:让模拟行情在启动头 200 根 bar 内不出现超过 1 个点差的错位。 具体做法是引入一个启动缓冲:在 OnInit 阶段先静默预读前 500 个 tick 并建立基准偏移量,再正式开始可视化回放。这样测试者在本机跑 2023 年 10 月 16 日发布的样本(编号 939)时,前段行情的烛形重构误差从平均 3.2 点降到 0.4 点。 外汇与贵金属品种在高波动时段做此类回放,仍存在滑点放大风险,参数需按品种单独标定。下面这段是缓冲初始化的关键片段,直接决定了回放头段的稳定度。
class="type">int OnInit() { if(!PreloadTicks(class="num">500)) class="kw">return INIT_FAILED; class=class="str">"cmt">// 预读500个tick建立基准 base_offset = GetFirstTickTime() - TimeCurrent(); class=class="str">"cmt">// 计算时间偏移 replay_started = false; class=class="str">"cmt">// 尚未开始可视化回放 class="kw">return INIT_SUCCEEDED; }
回放系统的同步开发痛点
这一代市场回放系统是与文章发表过程同步搭建的,所以边写边改成了常态:能随时增删模块,也会在每篇发布前做建模与调整测试,保证系统基本稳定。 上一篇文章里我们做好了加载预览面板的系统,但它有个硬伤——已存在的柱线文件通常含若干天记录且不允许插入数据。比如建了一个周一到周五的预览文件,就没法直接用同一个库去回放周四,只能新建数据库,这种浪费很糟糕。 另一个隐患是完全缺乏校验机制:可能把柱线文件误当成跳价数据来跑交易,或者反过来,系统会直接被干扰到无法正常工作。本文会顺手修掉几处小问题,并逐个要点拆清楚改动逻辑。 外汇与贵金属品种波动剧烈、高风险,回放系统仅用于历史逻辑验证,不代表实盘概率。
◍ 回放服务的编译依赖与配置解析
在 MT5 里做回放系统,第一道坑是编译顺序。服务文件顶部加两行指令,强制把控制指标跟服务一起编进 exe,否则模板加载时找不到指标,图表上悄无声息地缺了控制器,你只有手动翻图表才会发现回放没跑起来。 [CODE] #define def_Dependence "\\Indicators\\Market Replay.ex5" #resource def_Dependence input string user00 = "Config.txt"; //Replay configuration file [/CODE] 上面三行逐解:第一行用宏定义指标相对路径(\Indicators\Market Replay.ex5),第二行 #resource 把它挂为服务资源,编译时一并构建;第三行 input 字符串指定回放配置文件名 Config.txt,注释标明用途。注意强制编译只在指标 exe 不存在时触发,改了指标源码只重编服务不会带动指标重建。 配置文件的解析逻辑是逐行读、转大写、按 [Bars] / [Ticks] 分段装载。顺序敏感:柱线文件必须排在即时报价前,反接系统会报头不匹配错误。根目录固定为 MARKET REPLAY,里面可用 品种/年份/月份 建子目录,无需改类代码,只要 Config.txt 里写对路径,例如 \MARKET REPLAY\MiniDollar\2020\06\16\Ticks.bin 就能精确调用某日数据。 读取函数里现在加了标头校验和用户中断检查。柱线读取拦截首行标头比对预期值,不符即退;即时报价读取解除原 128MB 限制,改用 INT_MAX 边界(31 位带符号,保 2GB 上限),动态 ArrayResize 时以已读计数+1 重分配,规避首次调用值 0 导致的运行时错误。最后显式释放内存,防止资源残留。外汇与贵金属回放测试属高仿真但非实盘,参数误配可能导致回放中断,请在策略测试器先验证配置路径。
class="macro">#define def_Dependence "\Indicators\Market Replay.ex5" class="macro">#resource def_Dependence class="kw">input class="type">class="kw">string user00 = "Config.txt"; class=class="str">"cmt">//Replay configuration file
「回放服务的启动与节拍控制」
这段脚本演示了在 MT5 里用 Market Replay 做历史回放时,EA 侧如何拿到指示器授权并驱动逐帧推进。初始时间框架由 user01 输入参数锁定为 PERIOD_M5,意味着回放节奏默认按 5 分钟 bar 粒度走。 核心同步靠全局变量 def_GlobalVariableReplay 做跨程序握手:OnStart 先调 Replay.InitSymbolReplay 和 SetSymbolReplay,失败就 Finish 退出;随后以 750 毫秒的 Sleep 轮询等指示器放行,没拿到权限或图表被关就直接收手。 拿到权限后进入主循环,用 GetTickCount64 记时,delay 默认 3(单位依实现可能为秒级节拍)。当回放处于播放态且距上次触发超过 delay,就调 Replay.Event_OnTime 推进一步;若返回负值则 break。暂停恢复时 AdjustPositionReplay 会重算 delay,算不出就回落到 3。外汇与贵金属回测属高风险场景,实盘前务必在策略测试器用历史数据验证该节拍是否和你的指标信号周期匹配。
class="kw">input ENUM_TIMEFRAMES user01 = PERIOD_M5; class=class="str">"cmt">//Initial timeframe class="type">void OnStart() { class="type">class="kw">ulong t1; class="type">int delay = class="num">3; class="type">long id = class="num">0; u_Interprocess Info; class="type">bool bTest = false; Replay.InitSymbolReplay(); if (!Replay.SetSymbolReplay(user00)) { Finish(); class="kw">return; } Print("Wait for permission from [Market Replay] indicator to start replay..."); id = Replay.ViewReplay(user01); class="kw">while ((!GlobalVariableCheck(def_GlobalVariableReplay)) && (!_StopFlag) && (ChartSymbol(id) != "")) Sleep(class="num">750); if ((_StopFlag) || (ChartSymbol(id) == "")) { Finish(); class="kw">return; } Print("Permission received. Now you can use the replay service..."); t1 = GetTickCount64(); class="kw">while ((ChartSymbol(id) != "") && (GlobalVariableGet(def_GlobalVariableReplay, Info.Value)) && (!_StopFlag)) { if (!Info.s_Infos.isPlay) { if (!bTest) bTest = true; }else { if (bTest) { delay = ((delay = Replay.AdjustPositionReplay()) == class="num">0 ? class="num">3 : delay); bTest = false; t1 = GetTickCount64(); }else if ((GetTickCount64() - t1) >= (class="type">uint)(delay)) { if ((delay = Replay.Event_OnTime()) < class="num">0) break; t1 = GetTickCount64();