开发回放系统 市场模拟(第 08 部分):锁定指标(基础篇)
「把指标钉死在重播K线上」
在 MT5 市场重播(Market Replay)里,最容易被忽略的一步是指标绑定。很多人以为切到历史帧后指标会自动跟着走,实际默认情况下新建指标会挂在实时图表上,回放窗口里根本看不到线。
解决方法是用 ChartIndicatorAdd 把指标句柄显式挂到重播图表 ID 上。下面这段在回放启动时锁定一个 MA 到当前图表,避免你反复手动拖拽。
外汇与贵金属重播属于高风险模拟,参数不对可能让你误判真实滑点,仅作盘感训练。
class="type">int replay_chart = ChartID(); class=class="str">"cmt">// 取当前重播图表ID class="type">int ma_handle = iMA(_Symbol, PERIOD_CURRENT, class="num">14, class="num">0, MODE_SMA, PRICE_CLOSE); class=class="str">"cmt">// 建14期SMA句柄 ChartIndicatorAdd(replay_chart, class="num">0, ma_handle); class=class="str">"cmt">// 把MA钉到重播图副窗0
◍ 把指标锁死在单一图表和品种上
上一轮回放系统改进里留下的那个错误,在配套视频里还能直接看到——指标仍可能被拖到别的图表,或在同一会话里重复加载。本文要做的修复,表面只是加几道限制,实际要踩准几个顺序步骤才能不引入新 bug。 核心目标是让指标只认当前图表和当前品种:用户手动拖到其他符号、或点开第二次,都应被引擎拒绝。外汇和贵金属这类高杠杆品种,误挂指标到错误周期可能放大滑点风险,所以这种硬绑定在实盘前值得先在 MT5 里验证。 实现思路不复杂,但必须分步走:先拦截 OnChartEvent 里的拖放,再在 OnInit 里比对 Symbol() 与运行时记录,最后用全局标记阻止二次实例化。接下来几节会逐行给代码。
把控制指标焊死在回放品种上
做市场回放的第一道关卡,是让控制指标只活在指定的回放品种图表里。逻辑不复杂:初始化时比对当前 _Symbol 和预设的 def_SymbolReplay,对不上就直接 ChartIndicatorDelete 把自己删掉并返回 INIT_FAILED,图表不会被误关。 MT5 里手动删指标会触发 DeInit,reason 标成 REASON_REMOVE;若是图表被关,则是 REASON_CHARTCLOSE。代码在 OnDeinit 里又做了一次 _Symbol 判断,非回放品种直接 break,避免误杀正在看的其它图表。 这套检查只保证「指标不串品种」,但同一回放图表仍可能被挂多个实例,或者不同图表各挂一个。外汇与贵金属波动剧烈、杠杆风险高,多实例并发可能让全局变量互相踩踏,下一步得补去重逻辑。 下面这段是锁定品种的最小可用骨架,注意 include 的 C_Controls.mqh 需放在 Market Replay 目录下,def_ 开头的宏要在头文件里先定义好。
class="macro">#class="kw">property copyright "Daniel Jose" class="macro">#class="kw">property indicator_chart_window class="macro">#class="kw">property indicator_plots class="num">0 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() { u_Interprocess Info; IndicatorSetString(INDICATOR_SHORTNAME, def_ShortName); if (_Symbol != def_SymbolReplay) { ChartIndicatorDelete(ChartID(), class="num">0, def_ShortName); class="kw">return INIT_FAILED; } if (GlobalVariableCheck(def_GlobalVariableReplay)) Info.Value = GlobalVariableGet(def_GlobalVariableReplay); else Info.Value = class="num">0; Control.Init(Info.s_Infos.isPlay); class="kw">return INIT_SUCCEEDED; } class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnCalculate(class="kw">const class="type">int rates_total, class="kw">const class="type">int prev_calculated, class="kw">const class="type">int begin, class="kw">const class="type">class="kw">double &price[]) { class="kw">return rates_total; } 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="type">void OnDeinit(class="kw">const class="type">int reason) { class="kw">switch (reason) { case REASON_REMOVE: case REASON_CHARTCLOSE: if (_Symbol != def_SymbolReplay) break; GlobalVariableDel(def_GlobalVariableReplay); ChartClose(ChartID()); break; } } class=class="str">"cmt">//+------------------------------------------------------------------+
「用布尔逻辑焊死单图表指标锁」
在同一图表反复挂控制指标会破坏回放服务的隔离性。作者放弃 DLL 外挂方案,纯用 MQL5 在 build 3280 下做了一套基于布尔逻辑的锁定机制,把控制指标强制限定在回放服务生成的那个图表上。 核心思路是盯住 ChartID 返回的 long 类型(占 8 字节 / 64 位)里用不到的符号位。用 (sizeof(long)*8 - 1) 算出 63,再左移得到 0x8000000000000000,把最高有效位当锁定位。全局终端变量里存图表 ID,指标初始化时比对这个值,不一样就自删。 解锁走 XOR 清最高位;锁定走 OR 把最高位写 1,并把指标名改掉,使第二个控制指标无法在同图加载。作者实测:仅靠代码逻辑仍拦不住模板自动挂指标,翻模板文件才发现某行需改,改后系统才真正锁死。 外汇 / 贵金属回放品种高风险,这套锁只解决图表污染,不预示任何价格方向。想验证就去 MT5 建服务 + 控制指标,故意拖两个进同图看是否被拒。