MQL5 酷宝书 服务(基础篇)
MQL5 服务类对象的生命周期
在 MQL5 里,Service 是一类特殊程序,挂在终端后台运行,不绑定具体图表,也不参与回测。它适合做行情中转、信号聚合或对外通信这类常驻任务。
创建服务时,必须在 OnStart 里显式调用 ServiceStart(),否则终端不会把它登记为后台服务,进程会在初始化后直接退出。和 EA 不同,服务没有 OnTick,只有 OnStart、OnStop 和定时器事件可用。
实测在 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 之后安排正经计算或下单。
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">//+------------------------------------------------------------------+