通过推送通知监控交易一个MetaTrader 5服务的示例(基础篇)
「用推送通知盯住 MT5 交易动作」
MT5 自带推送通知(Push Notifications),能把 EA 或脚本里的事件直接发到手机 MetaTrader 终端,不必一直守在电脑前。 接入点在「工具 → 选项 → 通知」里填 MetaQuotes ID,终端设置里可查到这串数字;EA 侧用 SendNotification() 即可触发。 实测一次推送从 MT5 发到手机端延迟通常在 1–3 秒,但网络抖动时可能到 10 秒以上,贵金属与外汇跳空时段丢推概率会升高,属高风险环境下的辅助手段,不能当唯一风控。 想验证:开 MT5 建个脚本调 SendNotification("test"),手机端收到即通;收不到先查 ID 是否填错、通知开关是否开。
◍ 把 MT5 标准库拆成可复用的六块
做自动化策略前,先把 MT5 标准库的 CTrade 相关封装摸清,能省掉大量重复造轮子的时间。实际工程里它主要拆成六个相互独立的模块:交易类、历史仓位类、按属性搜排的类、历史仓位集合、账户类、账户集合,以及专门出交易报告与推送通知的服务程序。 交易类直接包裹 OrderSend / PositionOpen 等底层调用,历史仓位类负责把已平单按 ticket 和时间拉回来;中间那层「按交易和仓位属性搜索排序的类」是多数回测过滤器的核心,比如按 magic number 或符号筛出某段时间的单。 账户类与账户集合分别对应单账户快照与多账户聚合,服务程序则把前五项的结果格式化后发到终端或外部。开 MT5 按 F4 进 Include/Trade 目录,对照这六块看头文件,比直接读文档快得多。
为什么用服务程序做自定义报告
评估一段时期的交易结果,是调整策略前的硬动作。MT5 自带的报告(Alt+E 或“查看→报告”)能直观给出盈亏与稳定性数据,但一旦标准字段不够用,就只能自己写程序补。 MQL5 的 Service 与普通 EA、指标、脚本不同:它不挂图表,靠 OnStart 启动后在独立进程里跑无限循环,不影响同终端其他程序。我们的需求——跟踪账户变动、捕捉交易日起点、生成并推送报告——正好契合这种常驻后台、低耦合的形态。 服务默认只响应 Start 事件,但能用 EventChartCustom 向图表发自定义事件;它住在 <terminal_directory>\MQL5\Services。推送通知有单位时间频次上限,首次启动或改设置时还要通过消息窗口确认推送权限可用,因此代码里必须加发送延迟与用户交互等待,否则手机端会漏报。 把这些约束拼起来:独立进程 + 无限循环 + 限频推送 + 权限自检,就是这套自研报告方案的基础骨架。
「从账户到成交:六层对象怎么摆」
这套统计服务不是单文件脚本,而是按「服务应用—账户集合—账户对象—历史头寸集合—头寸对象—成交订单对象」六层往下拆。服务应用跑在终端后台,能拿到连续运行期内所有连过终端的活跃账户数据;它把各账户已平仓头寸并成一个总表,再按交易时段出统计,推送到手机并写进 Experts 日志。账户集合只管存「连过哪些账户」以及这些账户的已平仓头寸清单,真正算数的是上层服务应用来挑。 账户对象类存单个账户的全部已平仓头寸,历史头寸集合类管「头寸对象」的列表与筛选,头寸对象类存已平仓头寸属性并带比较方法,成交订单对象类则落到最细粒度——单笔成交的属性与比较。MQL5 里每笔成交都带 PositionID,靠这个字段就能把散在历史里的成交「拼」回原来那个已平仓头寸,思路和之前那篇「图表直看成交」一致,但底层类结构换了套更省事的做法。 新做法不写抽象类、不保护构造函数,每个对象把自己的整数、实数、字符串属性塞进三个数组,用统一 get/set 读写;所有对象再挂进标准库的 CObject 动态数组(列表)。这样等于在 MQL5 里手搓了一个轻量数据库:能按任意属性过滤、排序,成本极低。 注意一个事实差异——头寸只在「开仓成交 In deal」到「平仓成交 Out/OutBuy deal」之间作为市场对象存在;成交永远是历史对象。终端历史里根本没有「头寸」这一行,只有成交记录。所以重建已平仓头寸必须遍历历史成交、按 PositionID 归并,这一步后面才实现,当前先落成交订单和头寸两个类。外汇与贵金属品种波动大、滑点频繁,用历史成交拼头寸时务必核对 PositionID 去重,否则统计会虚高。
◍ 在 MT5 里搭一个成交记录类
打开 MT5 终端数据目录,进到 \MQL5\Services\ 下新建 AccountReporter 文件夹,里面建 Deal.mqh。这个类叫 CDeal,从标准库 CObject 公有派生,文件头部直接 #include <Object.mqh> 把基类拉进来。 类里先定义三组枚举:整数、实数、字符串类型的成交属性,对应私有/保护/公有区声明成员变量与方法。构造函数默认成交已选中,直接读属性写进数组,同时存下品种 Digits 和 Point,后面算点差就靠它们。 比较方法重写了父类的虚函数:按指定属性比对两个对象,当前大于传入返回 1,小于返回 -1,相等返回 0。文本类方法(交易类型、头寸变更、原因、描述、带毫秒时间、日志打印)现在用不上几种,但留着方便以后扩展。 接收 tick 时取买卖价,点差 = (卖价 - 买价) / Point;若取不到 tick 数据,就退回分钟柱的平均点差。对象最终进历史仓位类的成交列表,外部拿指针就能处理数据。 下面这段是类骨架与属性枚举的起始代码,注意高亮处就是派生关系和头文件包含:
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Deal.mqh | class=class="str">"cmt">//| Copyright class="num">2024, MetaQuotes Ltd. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2024, MetaQuotes Ltd." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#include <Object.mqh> class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Deal class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CDeal : class="kw">public CObject { } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Deal.mqh | class=class="str">"cmt">//| Copyright class="num">2024, MetaQuotes Ltd. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Copyright class="num">2024, MetaQuotes Ltd." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#include <Object.mqh> class=class="str">"cmt">//--- Enumeration of integer deal properties enum ENUM_DEAL_PROPERTY_INT { DEAL_PROP_TICKET = class="num">0, class=class="str">"cmt">// Deal ticket DEAL_PROP_ORDER, class=class="str">"cmt">// Deal order number DEAL_PROP_TIME, class=class="str">"cmt">// Deal execution time
成交属性枚举与CDeal类骨架
MT5 的标准库把每笔成交(deal)拆成三类属性:整型、双精度、字符串,分别用 ENUM_DEAL_PROPERTY_INT、ENUM_DEAL_PROPERTY_DBL、ENUM_DEAL_PROPERTY_STR 三个枚举管理。整型块从 DEAL_PROP_TIME_MSC 到 DEAL_PROP_SPREAD,覆盖成交毫秒时间、类型、方向、魔术码、来源、持仓 ID 与当时点差;双精度块紧随其后,从 DEAL_PROP_VOLUME 起编,记录成交量、价格、佣金、库存费、盈亏、手续费以及 SL/TP 水平;字符串块再接 DEAL_PROP_SYMBOL,带上注释与外部系统 ID。 CDeal 类直接继承 CObject,私有成员用三个定长数组承接上述分类:m_lprop 长度 DEAL_PROP_SPREAD+1 存整型,m_dprop 长度 DEAL_PROP_TP-DEAL_PROP_SPREAD 存双精度,m_sprop 长度 DEAL_PROP_EXTERNAL_ID-DEAL_PROP_TP 存字符串,另外挂一个 MqlTick 结构 m_tick 保留当时行情快照。 开 MT5 切到 MetaEditor,在 Include/Trade/Deal.mqh 里搜 CDeal,对照下面枚举就能看清一笔成交在内存里被切成了几段。外汇与贵金属杠杆高,用这类底层结构做成交回溯时,点差与库存费字段务必纳入成本核算,否则胜率测算可能偏乐观。
enum ENUM_DEAL_PROPERTY_INT { DEAL_PROP_TIME_MSC, class=class="str">"cmt">// Deal execution time in milliseconds DEAL_PROP_TYPE, class=class="str">"cmt">// Deal type DEAL_PROP_ENTRY, class=class="str">"cmt">// Deal direction DEAL_PROP_MAGIC, class=class="str">"cmt">// Deal magic number DEAL_PROP_REASON, class=class="str">"cmt">// Deal execution reason or source DEAL_PROP_POSITION_ID, class=class="str">"cmt">// Position ID DEAL_PROP_SPREAD, class=class="str">"cmt">// Spread when performing a deal }; class=class="str">"cmt">//--- Enumeration of real deal properties enum ENUM_DEAL_PROPERTY_DBL { DEAL_PROP_VOLUME = DEAL_PROP_SPREAD+class="num">1, class=class="str">"cmt">// Deal volume DEAL_PROP_PRICE, class=class="str">"cmt">// Deal price DEAL_PROP_COMMISSION, class=class="str">"cmt">// Commission DEAL_PROP_SWAP, class=class="str">"cmt">// Accumulated swap when closing DEAL_PROP_PROFIT, class=class="str">"cmt">// Deal financial result DEAL_PROP_FEE, class=class="str">"cmt">// Deal fee DEAL_PROP_SL, class=class="str">"cmt">// Stop Loss level DEAL_PROP_TP, class=class="str">"cmt">// Take Profit level }; class=class="str">"cmt">//--- Enumeration of class="type">class="kw">string deal properties enum ENUM_DEAL_PROPERTY_STR { DEAL_PROP_SYMBOL = DEAL_PROP_TP+class="num">1, class=class="str">"cmt">// Symbol the deal is executed for DEAL_PROP_COMMENT, class=class="str">"cmt">// Deal comment DEAL_PROP_EXTERNAL_ID, class=class="str">"cmt">// Deal ID in an external trading system }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Deal class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CDeal : class="kw">public CObject { class="kw">private: class="type">MqlTick m_tick; class=class="str">"cmt">// Deal tick structure class="type">long m_lprop[DEAL_PROP_SPREAD+class="num">1]; class=class="str">"cmt">// Array for storing integer properties class="type">class="kw">double m_dprop[DEAL_PROP_TP-DEAL_PROP_SPREAD]; class=class="str">"cmt">// Array for storing real properties class="type">class="kw">string m_sprop[DEAL_PROP_EXTERNAL_ID-DEAL_PROP_TP]; class=class="str">"cmt">// Array for storing class="type">class="kw">string properties class=class="str">"cmt">//--- Return the index of the array the deal&class="macro">#x27;s(class="num">1) class="type">class="kw">double and(class="num">2) class="type">class="kw">string properties are located at