开发回放系统(第 36 部分):进行调整(二)(基础篇)
「给回放系统加一个时间轴拖拽入口」
在 MT5 里做历史回放,默认只能靠滑条或快捷键逐帧推进,调参时很费手。原文给出的思路是在主图表外挂一个独立子窗口,用鼠标拖拽直接定位到指定时间戳,省去反复敲键盘。
实现上,核心是在自定义指标里用 ChartNavigate 配合 ChartSetInteger 捕获子窗口的鼠标事件,再把像素坐标换算成具体时间。下面这段是原文里最小可跑的骨架,只做拖拽定位,不含绘制逻辑。
外汇与贵金属品种波动受消息面影响大,回放结论只代表历史切片,实盘仍属高风险,参数验证请在策略测试器里先跑一遍。
class="type">int OnInit() { ChartSetInteger(class="num">0,CHART_EVENT_MOUSE_WHEEL,true); ChartSetInteger(class="num">0,CHART_EVENT_MOUSE_MOVE,true); class="kw">return(INIT_SUCCEEDED); } 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) { if(id==CHARTEVENT_MOUSE_MOVE) { class="type">int x=(class="type">int)lparam; class="type">class="kw">datetime t=TimeCurrent()-(ChartGetInteger(class="num">0,CHART_WIDTH_IN_BARS)-x)*PeriodSeconds(); ChartNavigate(class="num">0,CHART_CURRENT_POS,t); } }
◍ 回放系统还留着一个坑
上一篇文章里我们做好了能轻松管理市场回放服务的 EA,体验比之前顺了,但回放/模拟相关的底层设计并没有全部重做。 眼下还剩一个小问题没解决:现有回放机制在切换品种或重启服务时,状态对齐偶尔会失准,导致模拟报价和真实节奏错位。 这一篇就专门补这个洞。它理论上不复杂,真要做对却很费劲,我们会借机把 MQL5 里几个平时容易忽略的用法啃透。 外汇和贵金属本身高波动、高杠杆,回放错位若带进实盘验证,可能直接误导你的价格行为判断,所以这套修正值得认真跟一遍。
回放系统怎么把账户类型透给EA
在 MT5 里跑回放/模拟系统时,EA 靠 AccountInfoInteger 拿到的账户类型并不可靠。只要你连的是 NETTING 服务器,哪怕回放资产本身是 HEDGING,函数返回的仍是 NETTING——这会让后续下单逻辑直接错位。 MQL5 的布尔类型和 C/C++ 不同:每个 bool 占 8 个 bit,而不是 1 个 bit。InterProcess.mqh 里的 st_0 结构原本预期 3 字节,加了 4 个布尔变量后实际吃掉 6 字节,离全局终端变量 8 字节传输上限已经很近。 解决思路是让用户在自己写的品种配置文件里声明账户类型,回放服务读完后通过全局终端变量把信息塞给 EA。C_ConfigService 里加一个私有变量记住用户选项,C_Replay 提供读取方法,控制指标加载后再写全局变量,EA 侧就能拿到正确类型。 配置文件里你可以直接写 NETTING 或 HEDGING(EXCHANGE 按 NETTING 处理),这样同一套回放框架就能覆盖不同市场。外汇和贵金属波动剧烈、杠杆风险高,账户类型判错可能让对冲或净额逻辑完全反向,动手前先在模拟盘验证。
C_Manager(C_Terminal *arg1, C_Study *arg2, class="type">class="kw">color cPrice, class="type">class="kw">color cStop, class="type">class="kw">color cTake, class="kw">const class="type">class="kw">ulong magic, class="kw">const class="type">class="kw">double FinanceStop, class="kw">const class="type">class="kw">double FinanceTake, class="type">uint Leverage, class="type">bool IsDayTrade) :C_ControlOfTime(arg1, magic) { class="type">class="kw">string szInfo = "HEDGING"; Terminal = arg1; Study = arg2; if (CheckPointer(Terminal) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid); if (CheckPointer(Study) == POINTER_INVALID) SetUserError(C_Terminal::ERR_PointerInvalid); if (_LastError != ERR_SUCCESS) class="kw">return; m_Infos.FinanceStop = FinanceStop; m_Infos.FinanceTake = FinanceTake; m_Infos.Leverage = Leverage; m_Infos.IsDayTrade = IsDayTrade;
「账户模式判定与回放全局变量定义」
EA 初始化阶段先关掉对冲标记,并把价格、止损、止盈对象指针暂存到结构体里,bCreate 置 false 表示暂不绘制图形对象。随后用 AccountInfoInteger(ACCOUNT_MARGIN_MODE) 读取账户保证金模式,switch 分支里只有 RETAIL_HEDGING 会把 m_Infos.AccountHedging 改成 true,其余 NETTING 与 EXCHANGE 仅记录字符串到 szInfo 并打印「Detected Account ...」。 这套回放机制依赖终端全局变量做进程间通信,预编译宏把符号名固定为 "RePlay",并派生出 _Infos、_ID、_Time 三个全局变量后缀,滑杆最大位置 def_MaxPosSlider 设为 400,短名拼成 "Market_RePlay"。 联合体 u_Interprocess 是关键:u_Value 用 double/ulong 共用内存存全局变量值或图形 ID,s_Infos 结构体里 isPlay、isWait 两个 bool 控制播放/暂停与等待状态,iPosShift 为 0~400 的 ushort 位移量,外加独立 datetime ServerTime 记录服务器时间。外汇与贵金属杠杆高、模式差异会直接影响下单逻辑,实盘前务必在 MT5 策略测试器里打印 AccountHedging 确认判定符合你的账户类型。
m_Infos.AccountHedging = class="kw">false; m_Objects.corPrice = cPrice; m_Objects.corStop = cStop; m_Objects.corTake = cTake; m_Objects.bCreate = class="kw">false; class="kw">switch ((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)) { case ACCOUNT_MARGIN_MODE_RETAIL_HEDGING: m_Infos.AccountHedging = true; class="kw">break; case ACCOUNT_MARGIN_MODE_RETAIL_NETTING: szInfo = "NETTING"; class="kw">break; case ACCOUNT_MARGIN_MODE_EXCHANGE : szInfo = "EXCHANGE"; class="kw">break; } Print("Detected Account ", szInfo); } class="macro">#class="kw">property copyright "Daniel Jose" class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#define def_SymbolReplay "RePlay" class="macro">#define def_GlobalVariableReplay def_SymbolReplay + "_Infos" class="macro">#define def_GlobalVariableIdGraphics def_SymbolReplay + "_ID" class="macro">#define def_GlobalVariableServerTime def_SymbolReplay + "_Time" class="macro">#define def_MaxPosSlider class="num">400 class="macro">#define def_ShortName "Market_" + def_SymbolReplay class=class="str">"cmt">//+------------------------------------------------------------------+ union u_Interprocess { union u_0 { class="type">class="kw">double df_Value; class=class="str">"cmt">// Value of the terminal global variable... class="type">class="kw">ulong IdGraphic; class=class="str">"cmt">// Contains the Graph ID of the asset... }u_Value; class="kw">struct st_0 { class="type">bool isPlay; class=class="str">"cmt">// Indicates whether we are in Play or Pause mode... class="type">bool isWait; class=class="str">"cmt">// Tells the user to wait... class="type">class="kw">ushort iPosShift; class=class="str">"cmt">// Value between class="num">0 and class="num">400... }s_Infos; class="type">class="kw">datetime ServerTime; };