开发回放系统(第 48 部分):了解服务的概念(基础篇)
📘

开发回放系统(第 48 部分):了解服务的概念(基础篇)

第 1/2 篇

「正文」

开发回放系统(第 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»,脚本就能挂上自定义指标。单独跑一次看似无用,但当你把它从「一次性脚本」改成常驻服务时,这套取上下文再挂指标的写法就是入口。外汇与贵金属杠杆高,任何自动挂载逻辑先在模拟盘验证再谈实盘。

MQL5 / C++
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 行反馈然后退出。图表上确实会出现那条均线。 外汇与贵金属市场波动剧烈、杠杆风险高,这种自动挂指标手段只解决「部署时机」问题,不构成任何方向判断。实盘前请在模拟环境反复跑这套逻辑。

MQL5 / C++
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("*******");
}

常见问题

把脚本改写成服务(Service)形态,不绑定具体图表生命周期,就能在终端后台持续运行,关图不影响。
新建脚本工程,用 ChartFirst 拿首张图表句柄,再调 iMA 创建均线指标并贴到该图表,编译拖入即可看到。
可以,小布能按你设定的品种和周期自动巡检图表并提示指标状态,你把规则交给它,只管看结论。
容易漏掉服务入口函数 ServiceMain 的死循环或事件等待,导致服务启动后直接退出,挂不上均线。
贵金属波动大、杠杆高,服务后台跑若逻辑有误可能漏警或误触,建议先在模拟环境验证参数再上实盘。