开发回放系统(第 48 部分):了解服务的概念·进阶篇
用服务锁住图表上的指标
用预设模板统一 MT5 图表风格,最怕手滑删了某个指标或改了参数。与其事后一个个恢复,不如写个后台服务,让特定指标在终端里删不掉、关不掉。
下面这段服务代码跑起来后,会每秒扫 4 次(循环间隔 250 毫秒)所有已打开图表,发现缺了 9 周期 EMA 就自动补回去。对外汇、贵金属这类高波动品种,图表上控盘均线突然消失可能让你误判节奏,这种自动兜底能降低人为操作风险。
代码里第 13 行用 while (!_StopFlag) 做主循环,保证服务收到停止信号才退。第 15 行 ChartFirst() 拿终端里第一个窗口 ID,存进 id;之后第 16 行内层循环靠 ChartNext(id) 顺着窗口链往下走,直到返回 -1 才停。
第 18 行判断当前图表第一个指标名是否等于 "MA(9)",不等就在第 20 行建 iMA 句柄(周期 9、EMA、收盘价),第 21 行 ChartIndicatorAdd 贴回图表,第 22 行 IndicatorRelease 释放句柄。哪怕你当场删了线,服务下一轮扫描就会把它加回来。
别把正态当圣经
这套逻辑只保证「指标在」,不保证「参数对你当前品种合适」。9 周期 EMA 是示例值,黄金 5 分钟和欧美日线该用哪条均线,得自己回测定。
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; Print("Waiting for the chart of the first asset to be opened..."); while (!_StopFlag) { while (((id = ChartFirst()) < class="num">0) && (!_StopFlag)); while ((id > class="num">0) && (!_StopFlag)) { if (ChartIndicatorName(id, class="num">0, class="num">0) != "MA(class="num">9)") { handle = iMA(szSymbol = ChartSymbol(id), ChartPeriod(id), class="num">9, class="num">0, MODE_EMA, PRICE_CLOSE); ChartIndicatorAdd(id, class="num">0, handle); IndicatorRelease(handle); } id = ChartNext(id); } } Print("Service removed"); }
「用 Sleep 控制服务脚本的轮询节奏」
上面这段代码是一个 MQL5 服务脚本的收尾片段,核心在循环里用 Sleep(250) 让线程暂停 250 毫秒再进入下一轮。对于常驻型 EA 或指标服务,不加 Sleep 会让 CPU 在空转循环里吃满,MT5 终端容易卡顿甚至被券商断连。 把 250 这个值调小到 50,轮询更密、对 tick 响应更快,但日志和对象访问频率会翻倍;调大到 1000 则更省资源,适合只做分钟级检查的场景。外汇与贵金属杠杆高,脚本卡死可能漏掉止损触发,实盘前务必在策略测试器里跑一遍观察资源占用。 Print("Service finish..."); 在循环退出后输出,说明服务收到了停止信号并正常收尾,和终端里手动移除脚本的日志能对上。
class="num">26. Sleep(class="num">250); class="num">27. } class="num">28. Print("Service finish..."); class="num">29. } class="num">30. class=class="str">"cmt">//+------------------------------------------------------------------+
◍ 用服务锁死图表配置
MT5 服务(service)能把图表状态变成终端服务配置的一部分,而不是依赖模板。好处很直接:你给 WDO(巴西 B3 的迷你美元期货)挂好随机振荡指标后,哪怕手动删掉,服务也会立刻把它加回来,配置不会被意外改掉。 代码里第 19、20 行先抓图表的交易品种名和周期,塞进 szSymbol 与 tf 两个变量。第 21 行做双重判断:品种名前三个字母是不是 WDO,以及图表里有没有叫 "Stoch(8,3,3)" 的指标子窗口。两个条件同时满足才往下走。 用 StringSubstr 只比前 3 个字母,是因为期货每个月换合约(WDOZ24、WDOF25 之类),通用前缀能覆盖全部到期版本,不用月月改代码。指标名必须写短名称(short name),拿不准就先手动挂指标,用个小脚本把 ChartWindowFind 能识别的字符串打印出来对照。 第 23 行用 iStochastic(szSymbol, tf, 8, 3, 3, MODE_SMA, STO_CLOSECLOSE) 建句柄,第 24 行 ChartIndicatorAdd 挂到 ChartGetInteger(id, CHART_WINDOWS_TOTAL) 返回的新子窗口——也就是 MT5 自动开一个干净窗口放指标,不会塞进你已有的别的子窗口造成错位。第 25 行 IndicatorRelease 释放句柄,之后服务只观察不再干预。 原文第 27 行附近按索引查指标在多个指标同图时容易误判,实际改成按名称查更稳:无论指标在第几个窗口,都只保证服务放的那一个存在。外汇和贵金属市场杠杆高、滑点大,这类自动重挂逻辑先在模拟盘验证再上实盘。
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=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart() { class="type">long id; class="type">class="kw">string szSymbol; class="type">int handle; ENUM_TIMEFRAMES tf; Print("Waiting for the chart of the first asset to be opened..."); while (!_StopFlag) { while (((id = ChartFirst()) < class="num">0) && (!_StopFlag)); while ((id > class="num">0) && (!_StopFlag)) { szSymbol = ChartSymbol(id); tf = ChartPeriod(id); if ((StringSubstr(szSymbol, class="num">0, class="num">3) == "WDO") && (ChartWindowFind(id, "Stoch(class="num">8,class="num">3,class="num">3)") < class="num">0)) { handle = iStochastic(szSymbol, tf, class="num">8, class="num">3, class="num">3, MODE_SMA, STO_CLOSECLOSE); ChartIndicatorAdd(id, (class="type">int)ChartGetInteger(id, CHART_WINDOWS_TOTAL), handle); IndicatorRelease(handle); }
遍历图表自动补齐 9 周期均线
这段逻辑跑在一个常驻服务脚本里,目标是把当前终端所有图表都挂上一条 9 周期 EMA(收盘价)。外层用 ChartNext 不断拿下一个图表 ID,直到返回 0 结束遍历。 判断某图表是否已有该指标,有两种写法:其一是取第 0 号指标名比对是否为 "MA(9)";其二是直接用 ChartIndicatorGet 按名称查句柄,若返回 INVALID_HANDLE 就说明没挂。后者更稳,不会因为指标排序变化而漏判。 确认缺失后,iMA 以 szSymbol、tf、9、0、MODE_EMA、PRICE_CLOSE 创建句柄,ChartIndicatorAdd 把它加到图表主窗口,随后 IndicatorRelease 释放本地引用计数——句柄仍由图表持有,不会失效。每次循环尾部的 Sleep(250) 让出 250 毫秒 CPU,避免扫太多图表时卡死终端。 外汇与贵金属品种波动快,这类自动挂指标脚本仅作辅助观察,不代表任何方向判断;实盘前务必在策略测试器或模拟盘验证遍历逻辑不会重复添加。
if(ChartIndicatorName(id, class="num">0, class="num">0) != "MA(class="num">9)") { handle = iMA(szSymbol, tf, class="num">9, class="num">0, MODE_EMA, PRICE_CLOSE); ChartIndicatorAdd(id, class="num">0, handle); IndicatorRelease(handle); } id = ChartNext(id); } Sleep(class="num">250); } Print("Service finish..."); } class=class="str">"cmt">//+------------------------------------------------------------------+ if(ChartIndicatorGet(id, class="num">0, "MA(class="num">9)") == INVALID_HANDLE)
「一点提醒」
MT5 的回放与自定义符号能力很少被用透,有读者在讨论区提到用真实账户拉取 GBPUSD 脱欧前后各一天的历史,以重播模式灌进 repGBPUSD 这类自定义品种,再挂 RSI(14)、MA(50) 做事件重演,这类玩法值得自己搭一遍。 学习别停在看文章,独立把数据库、套接字、重播这几块拼起来才有进展;碰到瓶颈就找更熟的人碰,而不是重复造轮子。 外汇和贵金属波动剧烈、杠杆风险高,任何回放验证都只是训练手段,别当成实盘依据。