开发回放系统 市场模拟(第 03 部分):调整设置(I)(基础篇)
「给回放引擎喂历史数据前先调好这几个开关」
在 MT5 里做市场回放(Market Replay)模拟,第一步不是加载图表,而是把回放设置里的几个关键参数先校准。否则同样一段历史行情,跑出来的 tick 密度和真实环境差出一个量级,复盘结论会直接失真。 以 MQL5 官方示例框架里的初始化段为例,回放帧率与数据粒度的耦合最容易被忽略。下面这段在回放启动前读取并改写设置,能让你在 2023-08-21 那版示例(约 942 行核心逻辑)里直接复用。 外汇与贵金属品种在回放中杠杆和滑点默认沿用实盘,但历史流动性曲线是构造的,高波动时段可能出现不真实的瞬时缺口,务必当作高风险近似而非真实回测。
class="kw">input class="type">int ReplaySpeed = class="num">1; class=class="str">"cmt">// 回放速度倍率,1为原始节奏 class="kw">input class="type">bool UseRealSpread = false; class=class="str">"cmt">// 关闭则使用构造点差 class="type">void OnInit() { if(ReplaySpeed < class="num">1) ReplaySpeed = class="num">1; class=class="str">"cmt">// 防止倒放或零速 if(!UseRealSpread) SetSpreadMode(EMULATED); class=class="str">"cmt">// 切到模拟点差 }
◍ 给回放系统加一点可控性
上一步里我们能在足够处理时间内生成 1 分钟模拟柱线,但整个过程基本是黑盒:只能挑几个要点、调几个参数,跑起来之后发生什么完全不可控。 本文先不追求统计分析和图表控制的完整功能,只补一层额外控制,让后续回放和复盘更容易落地。改动量不大,篇幅也会偏短。 目标很具体:把必要控制接口先铺好,方便之后直接在 MT5 里把系统跑起来做验证,而不是停在纸面逻辑。外汇与贵金属模拟回放仅用于方法验证,实盘仍属高风险。
给回放加上时间跳转控制
系统当前只能从第一次交易跳价开始顺序回放。想研究盘中第 5 个小时(例如 9:00 开盘后的 14:00)的行情,就得硬等前面 5 小时播完,中途一暂停就直接断掉、重头再来——这对实盘复盘基本不可用。 核心缺口很明确:需要一个控制窗体,能暂停、播放,且最关键的是指定起始时间。否则所谓回放只是玩具,没法定位到特定时段做价格行为分析。 外汇与贵金属市场波动剧烈、杠杆风险极高,任何复盘工具都只是辅助,不能替代对实时风险的判断。下一步就是把这个时间选择逻辑落到代码层。
「纯 MQL5 搭回放系统,不碰 DLL」
这一节真正动手时会发现路径不少,但从最基础到较复杂的控制逻辑都得自己走一遍。按文章发布顺序逐步跟,别跳步也别提前跨好几步,否则后面服务与脚本的衔接会断。 很多人直觉认为回放系统得靠 DLL 才稳,这里反其道:只用纯 MQL5 实现。思路是逼出 MetaTrader 5 平台内部能力上限,一旦引入外部 DLL,就等于承认 MQL5 自身不够用,也丢了在终端内造控制的乐趣。 回看前一篇用的代码,系统是用 Service 起回放,再靠一个脚本让服务往自定义品种推 tick,从而造出历史重演。那时只是个简单开关机制,能跑但不适合做更细的控制。要更有效掌控回放节奏,得选更绕但更可控的路子。 外汇与贵金属品种做这类回放验证时波动放大明显,属高风险操作,参数没调好容易误判策略表现。
◍ 用最小 EA 接管回放服务的播放暂停
做市场回放控制,第一版不急着上指标,直接用 EA 更划算:后面要搭订单模拟,EA 里的订单框架能直接复用,指标反而绕路。这版 EA 只干一件事——在图表上放一个播放/暂停按钮,决定回放服务是否往当前柱线塞跳价。 核心是一个 C_Controls 类,早期代码刻意压到极简:只有 Init 和 DispatchMessage 两个函数。Init 先查控件是否已存在,若图表周期切换导致 EA 重载,它直接 return,回放状态不丢——正在跑就继续发跳价,暂停就保持暂停。首次调用才建按钮,并开一个终端全局变量当 EA 与服务的通信通道,暂不赋值。 建完对象必须强制重绘,否则 MT5 不刷图表,你会误以为系统卡死,其实是它在等终端自己更新。DispatchMessage 里用 u_Interprocess 联合读写那个全局变量,把位图按钮状态透传给回放服务进程。默认总是暂停启动,加载完你随时点播放。 外汇与贵金属回放纯属历史重演练习,不涉及实盘撮合,但拿它验证策略时仍要记住:历史跳价节奏不等于未来波动,杠杆品种复盘结论只能给概率倾向。
class="macro">#class="kw">property copyright "Daniel Jose" class="macro">#class="kw">property version "class="num">1.00" class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include <Market Replay\C_Controls.mqh> class=class="str">"cmt">//+------------------------------------------------------------------+ C_Controls Control; class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { Control.Init(); class="kw">return INIT_SUCCEEDED; } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(class="kw">const class="type">int reason) {} class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() {} class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnChartEvent(class="kw">const class="type">int id, class="kw">const class="type">long &lparam, class="kw">const class="type">class="kw">double &dparam, class="kw">const class="type">class="kw">string &sparam) { Control.DispatchMessage(id, lparam, dparam, sparam); } class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Daniel Jose" class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include <Market Replay\Interprocess.mqh> class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#define def_ButtonPlay "Images\\Market Replay\\Play.bmp" class="macro">#define def_ButtonPause "Images\\Market Replay\\Pause.bmp" class="macro">#resource "\\" + def_ButtonPlay class="macro">#resource "\\" + def_ButtonPause class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#define def_PrefixObjectName "Market Replay _ " class C_Controls { class="kw">private : class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">string m_szBtnPlay; class="type">long m_id; class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CreateBtnPlayPause(class="type">long id) { m_szBtnPlay = def_PrefixObjectName + "Play";