通过推送通知监控交易一个MetaTrader 5服务的示例·进阶篇
📘

通过推送通知监控交易一个MetaTrader 5服务的示例·进阶篇

第 2/3 篇

◍ 成交对象里的属性索引与 setter 映射

在 MT5 的成交封装类里,双精度与字符串类属性并不连续编号,需要用偏移量把枚举值压成数组下标。IndexProp 对 ENUM_DEAL_PROPERTY_DBL 减去 DEAL_PROP_SPREAD+1,对 ENUM_DEAL_PROPERTY_STR 减去 DEAL_PROP_TP+1,这样 m_dprop / m_sprop 才能从 0 开始紧凑存放。 GetDealTick 默认取 20 笔 tick 缓冲,GetSpreadM1 回的是成交所在 M1 棒的 spread,TimeMscToString 能把带毫秒的时间戳转成字符串,默认旗标含日期、分、秒。 protected 区挂了 m_digits、m_point、m_bid、m_ask 四个基础行情字段,成交发生时记录当时的买卖价。public 的 SetProperty 有三个重载,分别接管整型、双精度、字符串属性;SetTicket 写 DEAL_PROP_TICKET,SetOrder 写 DEAL_PROP_ORDER,调用方直接传 long 即可。 开 MT5 自建一个 CDeal 派生类,把这段粘贴进去,编译后打印 m_dprop 下标,能验证双精度属性是否从 0 连续排列。外汇与贵金属杠杆高,回测用历史成交属性映射不代表实盘滑点表现。

MQL5 / C++
  class="type">int                IndexProp(ENUM_DEAL_PROPERTY_DBL class="kw">property)  const { class="kw">return(class="type">int)class="kw">property-DEAL_PROP_SPREAD-class="num">1; }
  class="type">int                IndexProp(ENUM_DEAL_PROPERTY_STR class="kw">property)  const { class="kw">return(class="type">int)class="kw">property-DEAL_PROP_TP-class="num">1;    }

class=class="str">"cmt">//--- Get a(class="num">1) deal tick and(class="num">2) a spread of the deal minute bar
  class="type">bool              GetDealTick(const class="type">int amount=class="num">20);
  class="type">int               GetSpreadM1(class="type">void);
class=class="str">"cmt">//--- Return time with milliseconds
  class="type">class="kw">string            TimeMscToString(const class="type">long time_msc,class="type">int flags=TIME_DATE|TIME_MINUTES|TIME_SECONDS) const;

class="kw">protected:
class=class="str">"cmt">//--- Additional properties
  class="type">int               m_digits;                                        class=class="str">"cmt">// Symbol Digits
  class="type">class="kw">double            m_point;                                          class=class="str">"cmt">// Symbol Point
  class="type">class="kw">double            m_bid;                                            class=class="str">"cmt">// Bid when performing a deal
  class="type">class="kw">double            m_ask;                                            class=class="str">"cmt">// Ask when performing a deal

class="kw">public:
class=class="str">"cmt">//--- Set the properties
class=class="str">"cmt">//--- Set deal&class="macro">#x27;s(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string properties
  class="type">void              SetProperty(ENUM_DEAL_PROPERTY_INT class="kw">property,class="type">long   value){ this.m_lprop[class="kw">property]=value;                    }
  class="type">void              SetProperty(ENUM_DEAL_PROPERTY_DBL class="kw">property,class="type">class="kw">double value){ this.m_dprop[this.IndexProp(class="kw">property)]=value; }
  class="type">void              SetProperty(ENUM_DEAL_PROPERTY_STR class="kw">property,class="type">class="kw">string value){ this.m_sprop[this.IndexProp(class="kw">property)]=value; }
class=class="str">"cmt">//--- Integer properties
  class="type">void              SetTicket(const class="type">long ticket)                      { this.SetProperty(DEAL_PROP_TICKET, ticket);             }  class=class="str">"cmt">// Ticket
  class="type">void              SetOrder(const class="type">long order)                        { this.SetProperty(DEAL_PROP_ORDER, order);               }  class=class="str">"cmt">// Order

「成交对象的属性写入接口」

在 MT5 的 CDeal 类封装里,每一笔成交(deal)的字段都不是直接赋值成员变量,而是通过 SetProperty 统一写入。上面这组方法把时间、类型、方向、魔术码等核心标识拆成了独立函数,方便 EA 在回测或实盘里逐项填充。 以 SetTimeMsc 为例,它接收 long 型毫秒时间戳,对应 DEAL_PROP_TIME_MSC;如果你只写 SetTime(datetime 秒级),在高频策略里可能丢失同一秒内的成交顺序。外汇与贵金属杠杆高,毫秒级排序错了会导致仓位归因偏差。 SetEntry 接收 ENUM_DEAL_ENTRY,区分开仓、加仓、平仓方向;SetPositionID 则把成交绑回具体持仓。实盘排查滑点问题时,常要拿 SetPrice 和 SetCommission 对照成交单,验证代理点差与手续费是否符合预期。 下面这段代码就是原文给出的完整 setter 集合,直接贴进你的 include 头文件即可用。注意 DEAL_PROP_SWAP 只在平仓时才有累计值,中途查询可能返回 0。

MQL5 / C++
  class="type">void                SetTime(const class="type">class="kw">datetime time)             { this.SetProperty(DEAL_PROP_TIME, time);            }   class=class="str">"cmt">// Time
  class="type">void                SetTimeMsc(const class="type">long value)             { this.SetProperty(DEAL_PROP_TIME_MSC, value);       }   class=class="str">"cmt">// Time in milliseconds
  class="type">void                SetTypeDeal(const ENUM_DEAL_TYPE type)  { this.SetProperty(DEAL_PROP_TYPE, type);            }   class=class="str">"cmt">// Type
  class="type">void                SetEntry(const ENUM_DEAL_ENTRY entry)   { this.SetProperty(DEAL_PROP_ENTRY, entry);          }   class=class="str">"cmt">// Direction
  class="type">void                SetMagic(const class="type">long magic)              { this.SetProperty(DEAL_PROP_MAGIC, magic);          }   class=class="str">"cmt">// Magic number
  class="type">void                SetReason(const ENUM_DEAL_REASON reason){ this.SetProperty(DEAL_PROP_REASON, reason);         }   class=class="str">"cmt">// Deal execution reason or source
  class="type">void                SetPositionID(const class="type">long id)            { this.SetProperty(DEAL_PROP_POSITION_ID, id);       }   class=class="str">"cmt">// Position ID
class=class="str">"cmt">//--- Real properties
  class="type">void                SetVolume(const class="type">class="kw">double volume)          { this.SetProperty(DEAL_PROP_VOLUME, volume);        }   class=class="str">"cmt">// Volume
  class="type">void                SetPrice(const class="type">class="kw">double price)            { this.SetProperty(DEAL_PROP_PRICE, price);          }   class=class="str">"cmt">// Price
  class="type">void                SetCommission(const class="type">class="kw">double value)       { this.SetProperty(DEAL_PROP_COMMISSION, value);     }   class=class="str">"cmt">// Commission
  class="type">void                SetSwap(const class="type">class="kw">double value)             { this.SetProperty(DEAL_PROP_SWAP, value);           }   class=class="str">"cmt">// Accumulated swap when closing

成交对象里的属性读写接口

在自建的成交(deal)封装类里, setter 与 getter 是分开设计的:数值类用 SetProperty 写入对应枚举槽位,读取时再按 INT / DBL / STR 三类枚举重载 GetProperty。 下面这段是类中典型的属性方法片段,覆盖了盈亏、手续费、止损止盈与品种注释等字段。 void SetProfit(const double value) { this.SetProperty(DEAL_PROP_PROFIT, value); } // 写入财务结果 void SetFee(const double value) { this.SetProperty(DEAL_PROP_FEE, value); } // 写入手续费 void SetSL(const double value) { this.SetProperty(DEAL_PROP_SL, value); } // 写入止损价 void SetTP(const double value) { this.SetProperty(DEAL_PROP_TP, value); } // 写入止盈价 void SetSymbol(const string symbol) { this.SetProperty(DEAL_PROP_SYMBOL,symbol); } // 写入品种名 void SetComment(const string comment) { this.SetProperty(DEAL_PROP_COMMENT,comment); } // 写入备注 void SetExternalID(const string ext_id) { this.SetProperty(DEAL_PROP_EXTERNAL_ID,ext_id);}// 外部系统成交ID 读取侧用三个重载区分类型:整型直接从 m_lprop 数组按枚举取,双精度与字符串则先经 IndexProp 算偏移再读 m_dprop / m_sprop。 long GetProperty(ENUM_DEAL_PROPERTY_INT property) const { return this.m_lprop[property]; } double GetProperty(ENUM_DEAL_PROPERTY_DBL property) const { return this.m_dprop[this.IndexProp(property)]; } string GetProperty(ENUM_DEAL_PROPERTY_STR property) const { return this.m_sprop[this.IndexProp(property)]; } 实际调 Ticket() 这类方法时,编译器会落到整型重载上:long Ticket(void) const { return this.GetProperty(DEAL_PROP_TICKET); }。开 MT5 建个测试类,打印 Ticket 与 Profit,能直接验证这套映射有没有写错槽位。外汇与贵金属杠杆高,回测用的成交模拟若属性填错,资金曲线可能严重失真,参数核对要谨慎。

MQL5 / C++
class="type">void SetProfit(const class="type">class="kw">double value) { this.SetProperty(DEAL_PROP_PROFIT, value); }        class=class="str">"cmt">// Financial result
class="type">void SetFee(const class="type">class="kw">double value)   { this.SetProperty(DEAL_PROP_FEE, value); }            class=class="str">"cmt">// Deal fee
class="type">void SetSL(const class="type">class="kw">double value)    { this.SetProperty(DEAL_PROP_SL, value); }             class=class="str">"cmt">// Stop Loss level
class="type">void SetTP(const class="type">class="kw">double value)    { this.SetProperty(DEAL_PROP_TP, value); }             class=class="str">"cmt">// Take Profit level
class=class="str">"cmt">//--- String properties
class="type">void SetSymbol(const class="type">class="kw">string symbol)     { this.SetProperty(DEAL_PROP_SYMBOL,symbol); }   class=class="str">"cmt">// Symbol name
class="type">void SetComment(const class="type">class="kw">string comment)   { this.SetProperty(DEAL_PROP_COMMENT,comment); } class=class="str">"cmt">// Comment
class="type">void SetExternalID(const class="type">class="kw">string ext_id) { this.SetProperty(DEAL_PROP_EXTERNAL_ID,ext_id);}class=class="str">"cmt">// Deal ID in an external trading system
class=class="str">"cmt">//--- Get the properties
class=class="str">"cmt">//--- Return deal’s(class="num">1) integer, (class="num">2) real and(class="num">3) class="type">class="kw">string class="kw">property from the properties array
class="type">long   GetProperty(ENUM_DEAL_PROPERTY_INT class="kw">property) const { class="kw">return this.m_lprop[class="kw">property]; }
class="type">class="kw">double GetProperty(ENUM_DEAL_PROPERTY_DBL class="kw">property) const { class="kw">return this.m_dprop[this.IndexProp(class="kw">property)]; }
class="type">class="kw">string GetProperty(ENUM_DEAL_PROPERTY_STR class="kw">property) const { class="kw">return this.m_sprop[this.IndexProp(class="kw">property)]; }
class=class="str">"cmt">//--- Integer properties
class="type">long Ticket(class="type">void) const { class="kw">return this.GetProperty(DEAL_PROP_TICKET); }      class=class="str">"cmt">// Ticket

◍ 成交对象的属性读取接口

在 MT5 的成交(deal)封装类里,一组 const 成员函数把底层属性暴露给策略逻辑。它们全部走 GetProperty 统一入口,只是传入的枚举不同,返回类型按属性性质做了强转。 long Order(void) const { return this.GetProperty(DEAL_PROP_ORDER); } // 关联订单号 datetime Time(void) const { return (datetime)this.GetProperty(DEAL_PROP_TIME); } // 成交时间 long TimeMsc(void) const { return this.GetProperty(DEAL_PROP_TIME_MSC); } // 毫秒级时间戳 ENUM_DEAL_TYPE TypeDeal(void) const { return (ENUM_DEAL_TYPE)this.GetProperty(DEAL_PROP_TYPE); } // 成交类别(买/卖等) ENUM_DEAL_ENTRY Entry(void) const { return (ENUM_DEAL_ENTRY)this.GetProperty(DEAL_PROP_ENTRY); } // 开平方向 long Magic(void) const { return this.GetProperty(DEAL_PROP_MAGIC); } // 魔术码 ENUM_DEAL_REASON Reason(void) const { return (ENUM_DEAL_REASON)this.GetProperty(DEAL_PROP_REASON); }// 触发源 long PositionID(void) const { return this.GetProperty(DEAL_PROP_POSITION_ID); } // 持仓 ID 下半段是“真实属性”组,返回浮点:Volume 取 DEAL_PROP_VOLUME、Price 取 DEAL_PROP_PRICE,直接给仓位与成交价。 double Volume(void) const { return this.GetProperty(DEAL_PROP_VOLUME); } double Price(void) const { return this.GetProperty(DEAL_PROP_PRICE); } 开 MT5 按 F4 把这些片段塞进自定义 CDeal 派生类,编译后打印 Deal.Price() 与 Deal.TimeMsc(),可核对历史成交回放里的时间精度是否到毫秒级。外汇与贵金属杠杆交易高风险,属性读取仅用于复盘与风控,不预示未来盈亏。

MQL5 / C++
class="type">long     Order(class="type">void)    const { class="kw">return this.GetProperty(DEAL_PROP_ORDER); }        class=class="str">"cmt">// Order
class="type">class="kw">datetime Time(class="type">void)    const { class="kw">return (class="type">class="kw">datetime)this.GetProperty(DEAL_PROP_TIME); } class=class="str">"cmt">// Time
class="type">long     TimeMsc(class="type">void) const { class="kw">return this.GetProperty(DEAL_PROP_TIME_MSC); }       class=class="str">"cmt">// Time in milliseconds
ENUM_DEAL_TYPE    TypeDeal(class="type">void) const { class="kw">return (ENUM_DEAL_TYPE)this.GetProperty(DEAL_PROP_TYPE); }   class=class="str">"cmt">// Type
ENUM_DEAL_ENTRY   Entry(class="type">void)    const { class="kw">return (ENUM_DEAL_ENTRY)this.GetProperty(DEAL_PROP_ENTRY); } class=class="str">"cmt">// Direction
class="type">long     Magic(class="type">void)      const { class="kw">return this.GetProperty(DEAL_PROP_MAGIC); }       class=class="str">"cmt">// Magic number
ENUM_DEAL_REASON Reason(class="type">void)   const { class="kw">return (ENUM_DEAL_REASON)this.GetProperty(DEAL_PROP_REASON); }class=class="str">"cmt">// Deal execution reason or source
class="type">long     PositionID(class="type">void) const { class="kw">return this.GetProperty(DEAL_PROP_POSITION_ID); } class=class="str">"cmt">// Position ID

class=class="str">"cmt">//--- Real properties
class="type">class="kw">double Volume(class="type">void) const { class="kw">return this.GetProperty(DEAL_PROP_VOLUME); } class=class="str">"cmt">// Volume
class="type">class="kw">double Price(class="type">void)  const { class="kw">return this.GetProperty(DEAL_PROP_PRICE); }  class=class="str">"cmt">// Price

「从成交对象里直接抠出成本与挂单线」

做日内剥头皮或贵金属波段,最容易被忽略的是每笔 deal 的真实摩擦成本。MT5 的成交封装类把佣金、库存费、手续费拆成了独立只读属性,调一下就能拉出来,不用再去翻 account history 做字符串匹配。 double Commission(void) const { return this.GetProperty(DEAL_PROP_COMMISSION); } // 该笔佣金 double Swap(void) const { return this.GetProperty(DEAL_PROP_SWAP); } // 平仓时累计库存费 double Profit(void) const { return this.GetProperty(DEAL_PROP_PROFIT); } // 财务结果 double Fee(void) const { return this.GetProperty(DEAL_PROP_FEE); } // 交易费 double SL(void) const { return this.GetProperty(DEAL_PROP_SL); } // 止损位 double TP(void) const { return this.GetProperty(DEAL_PROP_TP); } // 止盈位 上面六个数值属性,返回的都是 double。外汇和贵金属杠杆高,单笔 swap 在周三持仓时可能翻三倍,回测里不单独抓出来,净值曲线会漂亮得失真。 string Symbol(void) const { return this.GetProperty(DEAL_PROP_SYMBOL); } // 品种名 string Comment(void) const { return this.GetProperty(DEAL_PROP_COMMENT); } // 注释 string ExternalID(void) const { return this.GetProperty(DEAL_PROP_EXTERNAL_ID); } // 外部系统成交号 字符串属性里 Symbol 最实用——多品种 EA 里用它来分流统计,ExternalID 则方便和对冲端或跟单系统的记录对账。开 MT5 新建个脚本,循环 HistoryDealsTotal 把 Commission 和 Swap 打印出来,你就能看到自己上个月在 XAUUSD 上到底给了经纪商多少过夜费。

MQL5 / C++
class="type">class="kw">double Commission(class="type">void) const { class="kw">return this.GetProperty(DEAL_PROP_COMMISSION); }   class=class="str">"cmt">// Commission
class="type">class="kw">double Swap(class="type">void) const { class="kw">return this.GetProperty(DEAL_PROP_SWAP); }               class=class="str">"cmt">// Accumulated swap when closing
class="type">class="kw">double Profit(class="type">void) const { class="kw">return this.GetProperty(DEAL_PROP_PROFIT); }           class=class="str">"cmt">// Financial result
class="type">class="kw">double Fee(class="type">void) const { class="kw">return this.GetProperty(DEAL_PROP_FEE); }                 class=class="str">"cmt">// Deal fee
 class="type">class="kw">double SL(class="type">void) const { class="kw">return this.GetProperty(DEAL_PROP_SL); }                  class=class="str">"cmt">// Stop Loss level
  class="type">class="kw">double TP(class="type">void) const { class="kw">return this.GetProperty(DEAL_PROP_TP); }                 class=class="str">"cmt">// Take Profit level

class=class="str">"cmt">//--- String properties
class="type">class="kw">string Symbol(class="type">void) const { class="kw">return this.GetProperty(DEAL_PROP_SYMBOL); }            class=class="str">"cmt">// Symbol name
class="type">class="kw">string Comment(class="type">void) const { class="kw">return this.GetProperty(DEAL_PROP_COMMENT); }          class=class="str">"cmt">// Comment
class="type">class="kw">string ExternalID(class="type">void) const { class="kw">return this.GetProperty(DEAL_PROP_EXTERNAL_ID); }   class=class="str">"cmt">// Deal ID in an external trading system

class=class="str">"cmt">//--- Additional properties

成交对象里能直接抠出的买卖盘与价差

在 MT5 的历史成交封装类 CDeal 里,Bid()、Ask()、Spread() 三个 const 方法把单子落地瞬间的盘口直接暴露出来:Bid 返回成交时的买价 m_bid,Ask 返回卖价 m_ask,Spread 则通过 GetProperty(DEAL_PROP_SPREAD) 强转成 int 拿点差。 这意味着你不必再去翻 HistoryDealGetDouble 查价格,对象构造完就能在回测或实盘日志里直接比对本笔成交的滑点环境。外汇与贵金属杠杆高,同一点差在黄金上对应的绝对成本可能比欧美大数倍,验证时务必按品种分开看。 构造函数 CDeal(const ulong ticket) 只做一件事:把 ticket 存进长整型,再用 HistoryDealGetInteger(ticket, DEAL_ORDER) 把关联订单号绑进来。也就是说,成交对象一经用 ticket 初始化,订单溯源就完成了,后面调 TypeDescription、ReasonDescription 才有数据可吐。 想自己跑一遍,把下面片段贴进 MT5 的 EA 或脚本,用真实历史成交 ticket 替换参数,Print 一下就能看到盘口与价差。

MQL5 / C++
class="type">class="kw">double Bid(class="type">void) const { class="kw">return this.m_bid; } class=class="str">"cmt">// Bid when performing a deal
class="type">class="kw">double Ask(class="type">void) const { class="kw">return this.m_ask; } class=class="str">"cmt">// Ask when performing a deal
class="type">int Spread(class="type">void) const { class="kw">return (class="type">int)this.GetProperty(DEAL_PROP_SPREAD); } class=class="str">"cmt">// Spread when performing a deal

class="type">class="kw">string TypeDescription(class="type">void) const;
class="type">class="kw">string EntryDescription(class="type">void) const;
class="type">class="kw">string ReasonDescription(class="type">void) const;

class="type">class="kw">string Description(class="type">void);
class="type">void Print(class="type">void);

class="kw">virtual class="type">int Compare(const CObject *node, const class="type">int mode=class="num">0) const;

CDeal(class="type">void){}
CDeal(const class="type">ulong ticket);
~CDeal();
};

CDeal::CDeal(const class="type">ulong ticket)
  {
  this.SetTicket((class="type">long)ticket); class=class="str">"cmt">// Deal ticket
  this.SetOrder(::HistoryDealGetInteger(ticket, DEAL_ORDER)); class=class="str">"cmt">// Order
  }

常见问题

用预定义的属性索引常量定位字段,再调 setter 映射接口赋值;写前确认索引与成交类型匹配,避免错写到期权类字段。
可以,但读写接口职责分开更稳:读用只读接口取值,写用写入接口改值,防止并发推送时字段被覆盖。
小布可接入行情与成交流,自动抠出成本与挂单线并推送异动提醒,你不用自己写轮询代码。
只抠已成交品种的实时盘口,价差受点差扩大影响可能瞬跳;贵金属与外汇高风险,需结合时段过滤假信号。
靠谱,成交对象暴露了成本字段可直接读;建议在通知模板里做四舍五入,避免移动端显示过长被截断。