通过推送通知监控交易一个MetaTrader 5服务的示例(基础篇)
📘

通过推送通知监控交易一个MetaTrader 5服务的示例(基础篇)

第 1/3 篇

「用推送通知盯住 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 数据,就退回分钟柱的平均点差。对象最终进历史仓位类的成交列表,外部拿指针就能处理数据。 下面这段是类骨架与属性枚举的起始代码,注意高亮处就是派生关系和头文件包含:

MQL5 / C++
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,对照下面枚举就能看清一笔成交在内存里被切成了几段。外汇与贵金属杠杆高,用这类底层结构做成交回溯时,点差与库存费字段务必纳入成本核算,否则胜率测算可能偏乐观。

MQL5 / C++
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

常见问题

在程序里挂接成交事件,触发时用 SendNotification 把品种、手数、盈亏发到手机。复制标准库的通知封装块即可,无需自己写通信层。
按对象层级拆成六块:账户、订单池、成交池、持仓、历史、通知。每块独立成类,主程序只做调度,改一处不影响其他。
小布已内置 AIGC 成交诊断,打开对应品种页就能看到每日成交汇总与异常提醒,不必手动搭服务程序。
服务程序常驻不占用图表,能跨品种收集数据,适合做后台报告与推送;EA 绑定图表且易被手动关闭。
至少存成交时间、品种、方向、手数、开平价、手续费、盈亏。用枚举映射属性,取数时按名读取不易写错。