开发回放系统(第 48 部分):了解服务的概念(基础篇)
「正文」
开发回放系统(第 48 部分):了解服务的概念 MetaTrader 5 — 示例 | 17 十二月 2024, 08:58 553 1 Daniel Jose
◍ 脚本转服务才能脱钩图表监控
上一阶段我们已经让 Chart Trade 指标跑起来了,但回放系统的核心卡点一直没解决:用服务在图表上挂控制指标始终失败,而脚本却能做到。MQL5 里任何脚本本质上都能改成服务,区别最明显的有两条。 其一,脚本永远绑定在某张图表上,直到图表被关掉才结束;MT5 切换周期时会发重绘命令,先清掉窗口内的图形对象再重建,脚本因为不支持某些事件,不会跟着重新载入。也就是说,想靠脚本直接盯盘,它只会死绑在原图表,换周期或换标的就失联。 改成服务后,它脱离具体图表,能在后台持续观察指定资产图表。多数情况下只要改几处代码细节就能完成转换。外汇与贵金属市场高波动、高杠杆,这类后台监控逻辑若用于实盘辅助,需先在小布盯盘环境里用模拟账户验证稳定性。 这一步不是炫技,而是后续回放开发的必经路:我们必须有个不依附图表却能看穿图表动作的东西,否则更细的项目都无从谈起。
从零跑通一个指标挂载脚本
先别急着上复杂架构,用一段极简脚本把 MT5 里「取图表上下文 → 建指标句柄 → 挂到图表」的链路跑通,后面所有服务化改造都基于这个骨架。 下面这段脚本编译后拖到任意图表,会在上面加载一条基于收盘价的 9 周期指数移动平均线(EMA)。它没有任何错误检查,因为脚本执行极快、主要用来验证接口行为,异常会直接进 MT5 日志。 代码里第 13–15 行拿图表 ID、品种名、周期,严格说只有 ChartID 是必须的,后两个是为后面调试打印和复用上下文留的。第 16 行 iMA 返回句柄,第 17 行用 ChartIndicatorAdd 把句柄绑到主窗口,第 19 行把指标名、品种、句柄值等一股脑 Print 出来,相当于最原始的运行时自检。 把第 16 行换成 iCustom 调用你自己的 «Replay\Chart Trade.ex5»,脚本就能挂上自定义指标。单独跑一次看似无用,但当你把它从「一次性脚本」改成常驻服务时,这套取上下文再挂指标的写法就是入口。外汇与贵金属杠杆高,任何自动挂载逻辑先在模拟盘验证再谈实盘。
class="macro">#class="kw">property copyright "Daniel Jose" class="macro">#class="kw">property version "class="num">1.00" class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart() { class="type">long id; class=class="str">"cmt">// 图表标识符变量 class="type">class="kw">string szSymbol; class=class="str">"cmt">// 图表所属品种名称 class="type">int handle; class=class="str">"cmt">// 指标句柄 class="type">bool bRet; class=class="str">"cmt">// 添加指标是否成功 ENUM_TIMEFRAMES tf; class=class="str">"cmt">// 图表周期枚举 id = ChartID(); class=class="str">"cmt">// 取当前图表ID szSymbol = ChartSymbol(id); class=class="str">"cmt">// 用ID取图表品种 tf = ChartPeriod(id); class=class="str">"cmt">// 用ID取图表周期 handle = iMA(szSymbol, tf, class="num">9, class="num">0, MODE_EMA, PRICE_CLOSE); class=class="str">"cmt">// 建9期收盘价EMA句柄 bRet = ChartIndicatorAdd(id, class="num">0, handle); class=class="str">"cmt">// 把句柄指标加到主窗口 Print(ChartIndicatorName(id, class="num">0, class="num">0), " ", szSymbol, " ", handle, " ", bRet, " >> ",id); class=class="str">"cmt">// 打印指标名/品种/句柄/结果/图表ID Print("*******"); } class=class="str">"cmt">//+------------------------------------------------------------------+ handle = iCustom(szSymbol, tf, "Replay\Chart Trade.ex5", id);
「把脚本改成服务给首张图表挂均线」
服务与脚本在 MQL5 里本质差异只是一行属性声明,但运行机制完全不同:服务不绑定任何图表,而脚本默认依附于运行它的那张图。想让一段逻辑在「任意品种的第一张图被打开时」自动生效,就得走服务这条路。
上面这段代码把前文脚本重写成服务。第 02 行 #property service 是身份标识;第 15 行用 while ((id = ChartFirst()) < 0); 空转等待,直到终端里出现第一张图表——因为刚启动且关掉所有图时,ChartFirst 返回 -1。
验证步骤很直接:在 MetaEditor 编译后,MT5 里关掉全部图表再运行服务,日志会先打印第 14 行等待信息;随后你开任意品种图,服务立刻通过 ChartIndicatorAdd 把 9 周期指数均线(iMA 参数 9, 0, MODE_EMA, PRICE_CLOSE)挂上去,并打印第 21 行反馈然后退出。图表上确实会出现那条均线。
外汇与贵金属市场波动剧烈、杠杆风险高,这种自动挂指标手段只解决「部署时机」问题,不构成任何方向判断。实盘前请在模拟环境反复跑这套逻辑。
class="macro">#class="kw">property service class="macro">#class="kw">property copyright "Daniel Jose" class="macro">#class="kw">property version "class="num">1.00" class="type">void OnStart() { class="type">long id; class="type">class="kw">string szSymbol; class="type">int handle; class="type">bool bRet; ENUM_TIMEFRAMES tf; Print("Waiting for the chart of the first asset to be opened..."); while ((id = ChartFirst()) < class="num">0); szSymbol = ChartSymbol(id); tf = ChartPeriod(id); handle = iMA(szSymbol, tf, class="num">9, class="num">0, MODE_EMA, PRICE_CLOSE); bRet = ChartIndicatorAdd(id, class="num">0, handle); Print(ChartIndicatorName(id, class="num">0, class="num">0), " ", szSymbol, " ", handle, " ", bRet, " >> ",id); Print("*******"); }