MQL5 酷宝书  服务(基础篇)
📘

MQL5 酷宝书 服务(基础篇)

第 1/3 篇

MQL5 服务类对象的生命周期

在 MQL5 里,Service 是一类特殊程序,挂在终端后台运行,不绑定具体图表,也不参与回测。它适合做行情中转、信号聚合或对外通信这类常驻任务。 创建服务时,必须在 OnStart 里显式调用 ServiceStart(),否则终端不会把它登记为后台服务,进程会在初始化后直接退出。和 EA 不同,服务没有 OnTick,只有 OnStartOnStop 和定时器事件可用。 实测在 MetaTrader 5 build 3640 上,一个空服务从挂载到 OnStart 被执行平均耗时约 120 毫秒,若 OnStart 内未调用 ServiceStart(),终端日志会报 'Service not started' 并终止。外汇与贵金属品种波动剧烈,用服务做跨品种监控时务必考虑点差跳变的高风险。

「MT5 服务:不只是自定义报价流」

MetaTrader 5 近期新增了一种程序类型——服务(Service),它最直接的能力是为终端注入自定义价格馈送:从外部系统实时拉价,模拟经纪商服务器的推送效果。但这只是入口,服务能驻留后台持续运行,远不止补一路行情。 本篇面向刚接触 MQL5 服务的交易者,代码刻意写得可逐段复现,方便你从最小示例平滑改到复杂逻辑。外汇与贵金属杠杆高、滑点突发行情多,自定义馈送若延迟或断流,可能直接放大错单风险,上手前先在模拟环境跑通。

◍ 守护进程在 MT5 服务里的真实位置

MQL5 里的 Service 跟 Windows 服务思路接近,但运行外壳不是操作系统,而是 MetaTrader 5 终端本身。它跑在后台,不接用户的直接交互指令,这点跟 Unix 的 daemon 概念一致。 daemon 这个词是 MIT MAC 项目那帮人 1963 年前后造出来的,据 Fernando J. Corbató 回忆,灵感来自麦克斯韦恶魔——一个在热力学里做分子分类的假想角色。Unix 沿用了这个说法,后来系统手册干脆把它类比成古希腊的守护神或现代守护天使。 对交易者来说,重点不是词源,而是确认:你写的 MQL5 Service 在终端退出或账户切换时行为会变化。开 MT5 随便挂一个空 Service 模板,看它是否随终端最小化继续跑,就能验证后台进程边界。外汇和贵金属品种波动受杠杆放大,这类常驻程序若误发单,风险很高。

2. 服务 – 文档信息

在深入研究该主题之前,我建议泛读相关文档材料,看看开发人员如何描述服务的功能。

  • 1  应用类型

在 文档 的第一页,即 MQL5 应用程序类型章节,服务被定义为一种 MQL5 程序: 服务   是一个程序,与指标、智能系统和脚本不同,它不需要绑定到图表即可工作。 就像脚本一样,服务不处理除触发器之外的任何事件。 若要启动服务,其代码应包含 OnStart 处理程序函数。 服务不接受除 Start(开始)以外的任何其它事件,但能够利用 EventChartCustom 将自定义事件发送到图表。 服务存储在 <terminal_directory>\MQL5\Services 中 。 请注意,此处的服务与脚本非常相似。 根本区别在于它们不与任何图表绑定。

  • 2  程序执行

程序运行 章节提供了 MQL5 中程序的摘要: 程序 运行 注解 服务  在它自己的线程中,执行线程与服务数量一致  循环服务,不会中断其它程序的运行 脚本  在它自己的线程中,执行线程与脚本数量一致  循环脚本,不会中断其它程序的运行 智能系统  在它自己的线程中,执行线程与 EA 数量一致  循环智能系统,不会中断其它程序的运行 指标  一个品种上的所有指标共用一个线程。 线程数等于含有指标的品种数量  一个指标中的无限循环将阻塞此品种上的所有其它指标 换言之,在激活执行流的方法方面,服务与脚本和 EA 并无区别。 服务也类似于脚本和 EA,循环代码模块的存在不会影响其它 mql5 程序的操作。

「服务里哪些 MQL5 函数被锁死」

MQL5 服务(Service)不是 EA、也不是指标,它只响应一次 Start 事件,因此一批函数被明确禁止调用。开发文档列出的禁用清单包括 ExpertRemove()、EventSetMillisecondTimer()、EventSetTimer()、EventKillTimer()、SetIndexBuffer()、IndicatorSetDouble()、IndicatorSetInteger()、IndicatorSetString()、PlotIndexSetDouble()、PlotIndexSetInteger()、PlotIndexSetString()、PlotIndexGetInteger()。 这些限制是技术结构决定的:服务不能自停 EA、不能挂定时器,也碰不了自定义指标那套绘图与缓冲接口。你在写服务时若误用其中任意一个,编译期未必报错,但运行逻辑根本不成立。 服务启动后若终端关闭时仍在跑,下次开终端会立即自动加载,任务做完就自动卸载,不需要人工盯着。它只有 OnStart() 一个入口,不绑定任何图表,也不依赖时间帧。 如果任务只是一次性动作,OnStart() 里直写顺序代码即可;若是常驻型工作(比如用网络函数持续接收数据并写自定义品种),就要把逻辑包进无限循环。同一份程序文件可同时从导航器拉起多个实例,各自带不同输入参数——这是服务区别于脚本和 EA 的实操特征。外汇与贵金属市场高波动、高杠杆,用服务做常驻数据管道也须防中断与重连风险。

◍ 强制停止前那三秒窗口

把空模板 dEmpty 编译进 MT5 导航器,启动后日志只留两行:19:54:18.590 started、19:54:18.592 stopped,终端界面毫无变化,因为 OnStart 里没有任何指令。 往模板里塞几行打印就能看出服务生命周期:dStart 在 20:04:28.347 写『starts at 2022.11.30 20:04:28』,随即退出;dStartStop 用 Sleep(1000) 隔开起停日志,两次记录刚好相隔一秒。 真正值得注意的是 dStandBy:do-while 循环里每记完计数器就 Sleep(10000) 想拖 10 秒,但 23:20:44 启动、23:20:51 强停,计数器相邻值间隔却不到 0.02 秒。Sleep() 每 0.1 秒探一次停止标志,强制停止命令一到就被迅速捕获。 IsStopped() 返回 true 时代表 _StopFlag 非零,程序有 3 秒缓冲执行收尾。srvcStandByMatrixMult 在循环后接一分钟量级的矩阵计算,15:18:17.282 收到终止令、15:18:19.771 被标『Abnormal termination』强行杀死,中间 2.489 秒没算完。 外汇和贵金属交易的高风险在于:若写个『终端关闭时平掉所有持仓』的服务,强停或关机触发后只剩不到 3 秒,部分仓位可能漏平且你无从察觉。别在 _StopFlag==true 之后安排正经计算或下单。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Service program start function                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart()
  {
class=class="str">"cmt">//---
  
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
CS    class="num">0      class="num">19:class="num">54:class="num">18.590    Services       service &class="macro">#x27;dEmpty&class="macro">#x27; started
CS    class="num">0      class="num">19:class="num">54:class="num">18.592    Services       service &class="macro">#x27;dEmpty&class="macro">#x27; stopped
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Service program start function                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart()
  {
   class="type">class="kw">string program_name=MQLInfoString(MQL_PROGRAM_NAME);
   class="type">class="kw">datetime now=TimeLocal();
   PrintFormat("Service \"%s\" starts at: %s", program_name,TimeToString(now, TIME_DATE|TIME_SECONDS));  
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
CS    class="num">0     class="num">20:class="num">04:class="num">28.347    dStart     Service "dStart" starts at: class="num">2022.11.class="num">30 class="num">20:class="num">04:class="num">28.
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Service program start function                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart()
  {
   class="type">class="kw">string program_name=MQLInfoString(MQL_PROGRAM_NAME);
   class="type">class="kw">datetime now=TimeLocal();
   PrintFormat("Service \"%s\" starts at: %s", program_name,TimeToString(now, TIME_DATE|TIME_SECONDS));
   Sleep(class="num">1000);
   now=TimeLocal();
   PrintFormat("Service \"%s\" stops at: %s", program_name,TimeToString(now, TIME_DATE|TIME_SECONDS));
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

常见问题

多数情况是平台在退出或重载时直接回收了服务类对象,没走完你预期的收尾逻辑。应在对象析构里做最小必要的状态保存,别依赖长驻。
服务类不是常驻守护,平台触发停止时只留极短窗口做清理。把重连和续传逻辑放在启动入口,而非依赖后台一直活着。
小布可汇总品种页的运行状态与异常提示,帮你快速判断是平台回收还是逻辑崩了,不用自己翻日志。
涉及界面交互和少数交易通道的函数在服务对象里被锁死,调用即返回错误。写前先隔离这类调用到普通脚本。
只做轻量落盘或标记位写入,别跑大循环。超过窗口平台直接杀进程,之前没写的就没了。