请 [注册] 或 [登录]  | 返回主站

量化交易吧 /  量化策略 帖子:3353167 新帖:50

轻松快捷开发 MetaTrader 程序的函数库(第五部分):交易事件集合类,向程序发送事件

爱汇小王子发表于:6 月 24 日 16:44回复(1)

内容

  • 重新布局函数库结构
  • 事件类
  • 交易事件的集合
  • 测试定义、处理和接收事件的过程
  • 下一步是什么?

重新布局函数库结构

在之前的文章中,我们已着手创建一个大型跨平台函数库,简化了 MetaTrader 5 和 MetaTrader 4 平台上的程序开发。 在第四部分中,我们测试了在帐户上跟踪交易事件。 在本文中,我们将开发交易事件类,并将它们置于事件集合当中。 从那里,它们将被发送到 Engine (引擎)库的基准对象,并控制程序图表。

但首先,我们要为进一步开发函数库结构奠定基础。

由于我们将会拥有众多不同的集合,且每个集合将仅含有此集合中固有的对象,因此将每个集合的对象存储在单独的子文件夹中似乎是合理的。

为此,我们在 DoEasy 函数库根目录的 Objects 子文件夹中创建 OrdersEvents 文件夹。
将所有以前创建的类从 Objects 文件夹移动到 Orders,而 Events 文件夹用于存储我们将在本文中开发的事件对象类。
此外,将 Select.mqh 文件从 Collections 重新定位到 Services,因为我们会包含另一个服务类。 该类具有快速访问现有和将来集合中任何对象的任何属性的方法,这意味着它应位于服务类的文件夹中。

在重新定位 CSelect 类的文件,并将订单对象类移动到新目录之后,编译所需的文件相对地址也会发生变化。 因此,我们随着已搬迁类的列表移动,并替换其中包含的文件的地址:

在 Order.mqh 文件当中,替换包含服务函数文件的路径

#include "..\Services\DELib.mqh" 

#include "..\..\Services\DELib.mqh"

在 HistoryCollection.mqh 文件当中,替换路径

#include "Select.mqh"
#include "..\Objects\HistoryOrder.mqh"
#include "..\Objects\HistoryPending.mqh"
#include "..\Objects\HistoryDeal.mqh"

 为

#include "..\Services\Select.mqh"
#include "..\Objects\Orders\HistoryOrder.mqh"
#include "..\Objects\Orders\HistoryPending.mqh"
#include "..\Objects\Orders\HistoryDeal.mqh"

在 MarketCollection.mqh 文件当中,替换路径

#include "Select.mqh"
#include "..\Objects\MarketOrder.mqh"
#include "..\Objects\MarketPending.mqh"
#include "..\Objects\MarketPosition.mqh"


#include "..\Services\Select.mqh"
#include "..\Objects\Orders\MarketOrder.mqh"
#include "..\Objects\Orders\MarketPending.mqh"
#include "..\Objects\Orders\MarketPosition.mqh"

现在,编译时一切都应该没有错误。

由于即将到来的集合数量巨大,因此最好根据 CArrayObj 来区分集合列表的所有权,以便识别列表。 每个集合都有一个方法,返回指向完整集合列表的指针。 如果有某个方法接收某个集合的某个列表,那么在这个方法中,我们要能够准确地按照其所属集合来识别传递给该方法的列表,以避免传递额外的标志来标示传递给该方法的列表类型。

幸运的是,标准库为此提供了必要的工具,它以 Type() 虚方法的形式返回对象 ID。
例如,对于 CObject,返回的 ID 为 0,而对于 CArrayObj,ID 为 0x7778。 由于该方法是虚方法,因此允许衍生类重载自己的方法返回特定的 ID。

我们所有的集合列表都基于 CArrayObj 类。 我们将创建自己的 CListObj 类,它是 CArrayObj 类的后代,并且在其虚拟 Type() 方法中返回列表 ID。 该 ID 本身在类构造函数中设置为常量。 因此,我们将继续访问我们的 CArrayObj 对象集合,但现在每个列表都有自己的特定 ID。

首先,我们在 Defines.mqh 文件中设置必要的集合列表 ID,并添加宏定义描述函数的错误行号,用以显示调试消息包含的字符串,并发送此消息来定位调试期间的问题代码:

//+------------------------------------------------------------------+
//| 宏定义替换                                                           |
//+------------------------------------------------------------------+
//--- 描述函数的错误行号
#define DFUN_ERR_LINE            (__FUNCTION__+(TerminalInfoString(TERMINAL_LANGUAGE)=="Russian" ? ", Page " : ", Line ")+(string)__LINE__+": ")
#define DFUN                     (__FUNCTION__+": ")        // "函数描述"
#define COUNTRY_LANG             ("Russian")                // 国家语言
#define END_TIME                 (D'31.12.3000 23:59:59')   // 请求帐户历史记录数据的结束日期
#define TIMER_FREQUENCY          (16)                       // 函数库定时器的最小频率(以毫秒为单位)
#define COLLECTION_PAUSE         (250)                      // 订单和成交集合的计时器暂停,以毫秒为单位
#define COLLECTION_COUNTER_STEP  (16)                       // 订单和成交集合计时器计数器的增量
#define COLLECTION_COUNTER_ID    (1)                        // 订单和成交集合计时器计数器 ID
#define COLLECTION_HISTORY_ID    (0x7778+1)                 // 历史集合列表 ID
#define COLLECTION_MARKET_ID     (0x7778+2)                 // 入场集合列表 ID
#define COLLECTION_EVENTS_ID     (0x7778+3)                 // 事件集合列表 ID
//+------------------------------------------------------------------+

现在,在 Collections 文件夹中的 ListObj.mqh 文件中创建 CListObj 类。 它的基类是 CArrayObj

//+------------------------------------------------------------------+
//|                                                      ListObj.mqh |
//|                                  版权所有 2018, MetaQuotes 软件公司 |
//|                             https://mql5.com/zh/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "版权所有 2018, MetaQuotes 软件公司"
#property link      "https://mql5.com/zh/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| 包含文件                                                            |
//+------------------------------------------------------------------+
#include <Arrays\ArrayObj.mqh>
//+------------------------------------------------------------------+
//| 集合列表类                                                           |
//+------------------------------------------------------------------+
class CListObj : public CArrayObj
  {
private:
   int               m_type;                    // 列表类型
public:
   void              Type(const int type)       { this.m_type=type;     }
   virtual int       Type(void)           const { return(this.m_type);  }
                     CListObj()                 { this.m_type=0x7778;   }
  };
//+------------------------------------------------------------------+

所有我们要做的事情就是声明包含列表类型的类成员,添加用于定义列表类型的方法,和返回其类型的虚方法
在类构造函数中,默认的列表类型将设置为等于 CArrajObj 的列表类型。 然后可以使用 Type() 方法从调用程序里重新定义它。

现在我们需要继承类的所有集合列表,以便能够为每个列表分配单独的搜索 ID。 该 ID 将可令我们跟踪传递到任何方法中的列表所有权。

打开 HistoryCollection.mqh 文件,添加包含 CListObj 类继承 CListObj 中的 CHistoryCollection 类。

//+------------------------------------------------------------------+
//| 包含文件                                                           |
//+------------------------------------------------------------------+
#include "ListObj.mqh"
#include "..\Services\Select.mqh"
#include "..\Objects\Orders\HistoryOrder.mqh"
#include "..\Objects\Orders\HistoryPending.mqh"
#include "..\Objects\Orders\HistoryDeal.mqh"
//+------------------------------------------------------------------+
//| 历史订单和成交集合                                                   |
//+------------------------------------------------------------------+
class CHistoryCollection : public CListObj
  {

在类构造函数中,定义历史集合列表类型,我们已在 Defines.mqh 文件中指定了 COLLECTION_HISTORY_ID

//+------------------------------------------------------------------+
//| 构造函数                                                           |
//+------------------------------------------------------------------+
CHistoryCollection::CHistoryCollection(void) : m_index_deal(0),m_delta_deal(0),m_index_order(0),m_delta_order(0),m_is_trade_event(false)
  {
   this.m_list_all_orders.Sort(#ifdef __MQL5__ SORT_BY_ORDER_TIME_OPEN #else SORT_BY_ORDER_TIME_CLOSE #endif );
   this.m_list_all_orders.Clear();
   this.m_list_all_orders.Type(COLLECTION_HISTORY_ID);
  }
//+------------------------------------------------------------------+

MarketCollection.mqh 文件中,针对 CMarketCollection 类执行相同的操作:

//+------------------------------------------------------------------+
//| 包含文件                                                           |
//+------------------------------------------------------------------+
#include "ListObj.mqh"
#include "..\Services\Select.mqh"
#include "..\Objects\Orders\MarketOrder.mqh"
#include "..\Objects\Orders\MarketPending.mqh"
#include "..\Objects\Orders\MarketPosition.mqh"
//+------------------------------------------------------------------+
//| 入场订单和持仓的集合                                                  |
//+------------------------------------------------------------------+
class CMarketCollection : public CListObj
  {

在类构造函数中,定义入场集合列表类型,我们已在 Defines.mqh 文件中指定了 COLLECTION_MARKET_ID

//+------------------------------------------------------------------+
//| 构造函数                                                           |
//+------------------------------------------------------------------+
CMarketCollection::CMarketCollection(void) : m_is_trade_event(false),m_is_change_volume(false),m_change_volume_value(0)
  {
   this.m_list_all_orders.Sort(SORT_BY_ORDER_TIME_OPEN);
   this.m_list_all_orders.Clear();
   ::ZeroMemory(this.m_struct_prev_market);
   this.m_struct_prev_market.hash_sum_acc=WRONG_VALUE;
   this.m_list_all_orders.Type(COLLECTION_MARKET_ID);
  }
//+------------------------------------------------------------------+

现在,每个集合列表都有其 ID,可以根据类型简化列表的识别。



由于我们要添加新的集合以便处理新的数据类型(包括本文中的帐户事件集合),我们将用到新的枚举。 为了避免名称冲突,我们需要更替一些以前创建的宏替换的名称

//+------------------------------------------------------------------+
//| 订单和成交排序的可能标准                                              |
//+------------------------------------------------------------------+
#define FIRST_ORD_DBL_PROP          (ORDER_PROP_INTEGER_TOTAL)
#define FIRST_ORD_STR_PROP          (ORDER_PROP_INTEGER_TOTAL+ORDER_PROP_DOUBLE_TOTAL)
enum ENUM_SORT_ORDERS_MODE
  {
   //--- 按整数型属性排序
   SORT_BY_ORDER_TICKET          =  0,                      // 按订单票据排序
   SORT_BY_ORDER_MAGIC           =  1,                      // 按订单魔幻数字排序
   SORT_BY_ORDER_TIME_OPEN       =  2,                      // 按订单开单时间排序
   SORT_BY_ORDER_TIME_CLOSE      =  3,                      // 按订单平单时间排序
   SORT_BY_ORDER_TIME_OPEN_MSC   =  4,                      // 按订单开单时间的毫秒值排序
   SORT_BY_ORDER_TIME_CLOSE_MSC  =  5,                      // 按订单平单时间的毫秒值排序
   SORT_BY_ORDER_TIME_EXP        =  6,                      // 按订单过期时间排序
   SORT_BY_ORDER_STATUS          =  7,                      // 按订单状态排序 (入场订单/挂单/成交/余额,信贷操作)
   SORT_BY_ORDER_TYPE            =  8,                      // 按订单类型排序
   SORT_BY_ORDER_REASON          =  10,                     // 按订单/仓位原因/来源排序
   SORT_BY_ORDER_STATE           =  11,                     // 按订单状态排序
   SORT_BY_ORDER_POSITION_ID     =  12,                     // 按仓位 ID 排序
   SORT_BY_ORDER_POSITION_BY_ID  =  13,                     // 按逆向仓位 ID 排序
   SORT_BY_ORDER_DEAL_ORDER      =  14,                     // 按成交所基于的订单排序
   SORT_BY_ORDER_DEAL_ENTRY      =  15,                     // 按成交方向排序 – IN, OUT 或 IN/OUT
   SORT_BY_ORDER_TIME_UPDATE     =  16,                     // 按仓位变更时间排序,以秒为单位
   SORT_BY_ORDER_TIME_UPDATE_MSC =  17,                     // 按仓位变更时间排序,以毫秒为单位
   SORT_BY_ORDER_TICKET_FROM     =  18,                     // 按父订单的票据排序
   SORT_BY_ORDER_TICKET_TO       =  19,                     // 按衍生订单的票据排序
   SORT_BY_ORDER_PROFIT_PT       =  20,                     // 按订单盈利点数排序
   SORT_BY_ORDER_CLOSE_BY_SL     =  21,                     // 按止损平单标志排序
   SORT_BY_ORDER_CLOSE_BY_TP     =  22,                     // 按止盈平单标志排序
   //--- 按实数型属性排序
   SORT_BY_ORDER_PRICE_OPEN      =  FIRST_ORD_DBL_PROP,     // 按开单价排序
   SORT_BY_ORDER_PRICE_CLOSE     =  FIRST_ORD_DBL_PROP+1,   // 按平单价排序
   SORT_BY_ORDER_SL              =  FIRST_ORD_DBL_PROP+2,   // 按止损价排序
   SORT_BY_ORDER_TP              =  FIRST_ORD_DBL_PROP+3,   // 按止盈价排序
   SORT_BY_ORDER_PROFIT          =  FIRST_ORD_DBL_PROP+4,   // 按盈利排序
   SORT_BY_ORDER_COMMISSION      =  FIRST_ORD_DBL_PROP+5,   // 按佣金排序
   SORT_BY_ORDER_SWAP            =  FIRST_ORD_DBL_PROP+6,   // 按隔夜利息排序
   SORT_BY_ORDER_VOLUME          =  FIRST_ORD_DBL_PROP+7,   // 按交易量排序
   SORT_BY_ORDER_VOLUME_CURRENT  =  FIRST_ORD_DBL_PROP+8,   // 按未执行交易量排序
   SORT_BY_ORDER_PROFIT_FULL     =  FIRST_ORD_DBL_PROP+9,   // 按利润+佣金+隔夜利息标准排序
   SORT_BY_ORDER_PRICE_STOP_LIMIT=  FIRST_ORD_DBL_PROP+10,  // 按由 StopLimit 订单激活的限价订单排序
   //--- 按字符串型属性排序
   SORT_BY_ORDER_SYMBOL          =  FIRST_ORD_STR_PROP,     // 按品种排序
   SORT_BY_ORDER_COMMENT         =  FIRST_ORD_STR_PROP+1,   // 按注释排序
   SORT_BY_ORDER_EXT_ID          =  FIRST_ORD_STR_PROP+2    // 按外部交易系统中的订单 ID 排序
  };
//+------------------------------------------------------------------+

由于我们当前正在编辑 Defines.mqh 文件,因此为事件类和帐户事件集合添加所有必需的枚举:

//+------------------------------------------------------------------+
//| 事件状态                                                           |
//+------------------------------------------------------------------+
enum ENUM_EVENT_STATUS
  {
   EVENT_STATUS_MARKET_POSITION,                            // 场内仓位事件(开仓,部分开仓,部分平仓,增加交易量,逆转)
   EVENT_STATUS_MARKET_PENDING,                             // 场内挂单事件(放置)
   EVENT_STATUS_HISTORY_PENDING,                            // 历史挂单事件(删除)
   EVENT_STATUS_HISTORY_POSITION,                           // 历史仓位事件(平仓)
   EVENT_STATUS_BALANCE,                                    // 余额操作事件(累计余额,出金和来自 ENUM_DEAL_TYPE 枚举的事件)
  };
//+------------------------------------------------------------------+
//| 事件原因                                                           |
//+------------------------------------------------------------------+
enum ENUM_EVENT_REASON
  {
   EVENT_REASON_ACTIVATED_PENDING               =  0,       // 挂单激活
   EVENT_REASON_ACTIVATED_PENDING_PARTIALLY     =  1,       // 挂单部分激活
   EVENT_REASON_CANCEL                          =  2,       // 取消
   EVENT_REASON_EXPIRED                         =  3,       // 挂单过期
   EVENT_REASON_DONE                            =  4,       // 请求已完整执行
   EVENT_REASON_DONE_PARTIALLY                  =  5,       // 请求已部分执行
   EVENT_REASON_DONE_SL                         =  6,       // 由止损平仓
   EVENT_REASON_DONE_SL_PARTIALLY               =  7,       // 由止损部分平仓
   EVENT_REASON_DONE_TP                         =  8,       // 由止盈平仓
   EVENT_REASON_DONE_TP_PARTIALLY               =  9,       // 由止盈部分平仓
   EVENT_REASON_DONE_BY_POS                     =  10,      // 由逆向仓位平仓
   EVENT_REASON_DONE_PARTIALLY_BY_POS           =  11,      // 由逆向仓位部分平仓
   EVENT_REASON_DONE_BY_POS_PARTIALLY           =  12,      // 由部分交易量将逆向仓位平仓
   EVENT_REASON_DONE_PARTIALLY_BY_POS_PARTIALLY =  13,      // 由部分交易量将逆向仓位部分平仓
   //--- 与 ENUM_DEAL_TYPE 枚举中的 DEAL_TYPE_BALANCE 成交类型相关的常量
   EVENT_REASON_BALANCE_REFILL                  =  14,      // 充值
   EVENT_REASON_BALANCE_WITHDRAWAL              =  15,      // 从账户出金
   //--- 常量列表与 ENUM_TRADE_EVENT 枚举中的 TRADE_EVENT_ACCOUNT_CREDIT 相关,并相对于 ENUM_DEAL_TYPE(EVENT_REASON_ACCOUNT_CREDIT-3)偏移了 +13
   EVENT_REASON_ACCOUNT_CREDIT                  =  16,      // 积累信贷
   EVENT_REASON_ACCOUNT_CHARGE                  =  17,      // 额外费用
   EVENT_REASON_ACCOUNT_CORRECTION              =  18,      // 更正账目
   EVENT_REASON_ACCOUNT_BONUS                   =  19,      // 累积奖励
   EVENT_REASON_ACCOUNT_COMISSION               =  20,      // 额外佣金
   EVENT_REASON_ACCOUNT_COMISSION_DAILY         =  21,      // 在交易日结束时收取的佣金
   EVENT_REASON_ACCOUNT_COMISSION_MONTHLY       =  22,      // 在交易月结束时收取的佣金
   EVENT_REASON_ACCOUNT_COMISSION_AGENT_DAILY   =  23,      // 在交易日结束时收取的代理佣金
   EVENT_REASON_ACCOUNT_COMISSION_AGENT_MONTHLY =  24,      // 在交易月结束时收取的代理佣金
   EVENT_REASON_ACCOUNT_INTEREST                =  25,      // 可用资金的累积利息
   EVENT_REASON_BUY_CANCELLED                   =  26,      // 取消购买成交
   EVENT_REASON_SELL_CANCELLED                  =  27,      // 取消卖出成交
   EVENT_REASON_DIVIDENT                        =  28,      // 累积红利
   EVENT_REASON_DIVIDENT_FRANKED                =  29,      // 累积分红
   EVENT_REASON_TAX                             =  30       // 税款
  };
#define REASON_EVENT_SHIFT    (EVENT_REASON_ACCOUNT_CREDIT-3)
//+------------------------------------------------------------------+
//| 事件的整数型属性                                                    |
//+------------------------------------------------------------------+
enum ENUM_EVENT_PROP_INTEGER
  {
   EVENT_PROP_TYPE_EVENT = 0,                               // 帐户交易事件类型(来自 ENUM_TRADE_EVENT 枚举)
   EVENT_PROP_TIME_EVENT,                                   // 事件时间(以毫秒为单位)
   EVENT_PROP_STATUS_EVENT,                                 // 事件状态(来自 ENUM_EVENT_STATUS 枚举)
   EVENT_PROP_REASON_EVENT,                                 // 事件原因(来自 ENUM_EVENT_REASON 枚举)
   EVENT_PROP_TYPE_DEAL_EVENT,                              // 成交事件类型
   EVENT_PROP_TICKET_DEAL_EVENT,                            // 成交事件票据
   EVENT_PROP_TYPE_ORDER_EVENT,                             // 基于开立成交事件的订单类型(最后的开仓订单)
   EVENT_PROP_TICKET_ORDER_EVENT,                           // 基于成交开立事件的订单票据(最后的开仓订单)
   EVENT_PROP_TIME_ORDER_POSITION,                          // 基于成交开立事件的订单时间(首笔开仓订单)
   EVENT_PROP_TYPE_ORDER_POSITION,                          // 基于成交开立事件的订单类型(首笔开仓订单)
   EVENT_PROP_TICKET_ORDER_POSITION,                        // 基于成交开立事件的订单票据(首笔开仓订单)
   EVENT_PROP_POSITION_ID,                                  // 仓位 ID
   EVENT_PROP_POSITION_BY_ID,                               // 逆向仓位 ID
   EVENT_PROP_MAGIC_ORDER,                                  // 订单/成交/仓位的魔幻数字
  }; 
#define EVENT_PROP_INTEGER_TOTAL (14)                       // 整数型事件属性的总数
//+------------------------------------------------------------------+
//| 事件的实数型属性                                                    |
//+------------------------------------------------------------------+
enum ENUM_EVENT_PROP_DOUBLE
  {
   EVENT_PROP_PRICE_EVENT = (EVENT_PROP_INTEGER_TOTAL),     // 事件发生的价格所在
   EVENT_PROP_PRICE_OPEN,                                   // 订单/成交/仓位开立价格
   EVENT_PROP_PRICE_CLOSE,                                  // 订单/成交/仓位平仓价格
   EVENT_PROP_PRICE_SL,                                     // 订单/成交/仓位止损价格
   EVENT_PROP_PRICE_TP,                                     // 订单/成交/仓位止盈价格
   EVENT_PROP_VOLUME_INITIAL,                               // 请求的交易量
   EVENT_PROP_VOLUME_EXECUTED,                              // 执行的交易量
   EVENT_PROP_VOLUME_CURRENT,                               // 剩余的交易量
   EVENT_PROP_PROFIT                                        // 盈利
  };
#define EVENT_PROP_DOUBLE_TOTAL  (9)                        // 事件的实数型属性总数
//+------------------------------------------------------------------+
//| 事件的字符串型属性                                                   |
//+------------------------------------------------------------------+
enum ENUM_EVENT_PROP_STRING
  {
   EVENT_PROP_SYMBOL = (EVENT_PROP_INTEGER_TOTAL+EVENT_PROP_DOUBLE_TOTAL), // 订单品种
  };
#define EVENT_PROP_STRING_TOTAL     (1)                     // 事件的字符串型属性总数
//+------------------------------------------------------------------+
//| 可能的事件排序标准                                                   |
//+------------------------------------------------------------------+
#define FIRST_EVN_DBL_PROP       (EVENT_PROP_INTEGER_TOTAL)
#define FIRST_EVN_STR_PROP       (EVENT_PROP_INTEGER_TOTAL+EVENT_PROP_DOUBLE_TOTAL)
enum ENUM_SORT_EVENTS_MODE
  {
   //--- 按整数型属性排序
   SORT_BY_EVENT_TYPE_EVENT            = 0,                    // 按事件类型排序
   SORT_BY_EVENT_TIME_EVENT            = 1,                    // 按事件时间排序
   SORT_BY_EVENT_STATUS_EVENT          = 2,                    // 按事件状态排序 (来自 ENUM_EVENT_STATUS 枚举)
   SORT_BY_EVENT_REASON_EVENT          = 3,                    // 按事件原因排序 (来自 ENUM_EVENT_REASON 枚举)
   SORT_BY_EVENT_TYPE_DEAL_EVENT       = 4,                    // 按成交事件类型排序
   SORT_BY_EVENT_TICKET_DEAL_EVENT     = 5,                    // 按成交事件票据排序
   SORT_BY_EVENT_TYPE_ORDER_EVENT      = 6,                    // 根据成交开立事件的订单类型排序(最后的开仓订单)
   SORT_BY_EVENT_TYPE_ORDER_POSITION   = 7,                    // 根据成交开立事件的订单类型排序(首笔开仓订单)
   SORT_BY_EVENT_TICKET_ORDER_EVENT    = 8,                    // 根据成交开立事件的订单票据排序(最后的开仓订单)
   SORT_BY_EVENT_TICKET_ORDER_POSITION = 9,                    // 根据成交开立事件的订单票据排序(首笔开仓订单)
   SORT_BY_EVENT_POSITION_ID           = 10,                   // 按仓位 ID 排序
   SORT_BY_EVENT_POSITION_BY_ID        = 11,                   // 按逆向仓位 ID 排序
   SORT_BY_EVENT_MAGIC_ORDER           = 12,                   // 按订单/成交/仓位魔幻数字排序
   SORT_BY_EVENT_TIME_ORDER_POSITION   = 13,                   // 根据成交开立事件的订单时间排序(首笔开仓订单)
   //--- 按实数型属性排序
   SORT_BY_EVENT_PRICE_EVENT        =  FIRST_EVN_DBL_PROP,     // 按事件发生时的价格排序
   SORT_BY_EVENT_PRICE_OPEN         =  FIRST_EVN_DBL_PROP+1,   // 按开仓价排序
   SORT_BY_EVENT_PRICE_CLOSE        =  FIRST_EVN_DBL_PROP+2,   // 按平仓价排序
   SORT_BY_EVENT_PRICE_SL           =  FIRST_EVN_DBL_PROP+3,   // 按仓位止损价排序
   SORT_BY_EVENT_PRICE_TP           =  FIRST_EVN_DBL_PROP+4,   // 按仓位止盈价排序
   SORT_BY_EVENT_VOLUME_INITIAL     =  FIRST_EVN_DBL_PROP+5,   // 按仓位初始交易量排序
   SORT_BY_EVENT_VOLUME             =  FIRST_EVN_DBL_PROP+6,   // 按仓位当前交易量排序
   SORT_BY_EVENT_VOLUME_CURRENT     =  FIRST_EVN_DBL_PROP+7,   // 按仓位剩余交易量排序
   SORT_BY_EVENT_PROFIT             =  FIRST_EVN_DBL_PROP+8,   // 按盈利排序
   //--- 按字符串型属性排序
   SORT_BY_EVENT_SYMBOL             =  FIRST_EVN_STR_PROP      // 按订单/仓位/成交的品种排序
  };
//+------------------------------------------------------------------+

在此,我们有全部可能的事件对象状态(类似于第一篇文章中描述的订单状态),事件发生原因,所有事件属性和按属性搜索事件的排序条件。 所有这些在之前的文章中均已熟知了。 我们总是可以回到起点并刷新数据以便澄清。

除了提供常规事件数据的事件状态之外,事件原因(ENUM_EVENT_REASON)已包含特定事件源的所有详细信息。
例如,如果事件拥有入场持仓状态(EVENT_STATUS_MARKET_POSITION),则在 EVENT_PROP_REASON_EVENT 对象字段中指定事件发生原因。 它既可以是由挂单激活(EVENT_REASON_ACTIVATED_PENDING),也可以由市价单(EVENT_REASON_DONE)开仓。 此外,还应考虑以下细微差别:如果某笔仓位是部分开仓(并非完整的执行挂单或市价订单的交易量),事件原因则为 EVENT_REASON_ACTIVATED_PENDING_PARTIALLY 或 EVENT_REASON_DONE_PARTIALLY 等等。
因此,事件对象包含事件的整体数据和触发它的订单。 此外,历史事件提供两笔订单的数据 — 第一笔开仓订单和平仓订单。
因此,订单、成交和仓位本身在事件对象中的数据可令我们在其存在的整个历史中跟踪整个仓位事件链 — 从开始到结束。

ENUM_EVENT_REASON 枚举常量是有排位和编号的,所以当事件状态为 “deal” 时,如果成交类型超过 DEAL_TYPE_SELL,则成交类型将落在 ENUM_DEAL_TYPE 枚举。 因此,我们最终得到了余额操作类型。 为创建准备的类中定义成交类型时,会发送余额操作描述作为事件原因。
为成交类型加入的偏移在 #define REASON_EVENT_SHIFT 宏替换 中计算。 它需要在 ENUM_EVENT_REASON 枚举中设置余额操作类型。

我们添加返回订单仓位成交类型的描述函数,以及返回开仓时订单类型名称的函数。 所有函数都添加到位于 DELib.mqh 文件中的 Services 函数库中。 这样可以方便地输出订单、仓位和成交。

//+------------------------------------------------------------------+
//| 返回订单名称                                                        |
//+------------------------------------------------------------------+
string OrderTypeDescription(const ENUM_ORDER_TYPE type)
  {
   string pref=(#ifdef __MQL5__ "Market order" #else "Position" #endif );
   return
     (
      type==ORDER_TYPE_BUY_LIMIT       ?  "Buy Limit"                                                 :
      type==ORDER_TYPE_BUY_STOP        ?  "Buy Stop"                                                  :
      type==ORDER_TYPE_SELL_LIMIT      ?  "Sell Limit"                                                :
      type==ORDER_TYPE_SELL_STOP       ?  "Sell Stop"                                                 :
   #ifdef __MQL5__
      type==ORDER_TYPE_BUY_STOP_LIMIT  ?  "Buy Stop Limit"                                            :
      type==ORDER_TYPE_SELL_STOP_LIMIT ?  "Sell Stop Limit"                                           :
      type==ORDER_TYPE_CLOSE_BY        ?  TextByLanguage("Закрывающий ордер","Order for closing by")  :  
   #else 
      type==ORDER_TYPE_BALANCE         ?  TextByLanguage("Балансовая операция","Balance operation")   :
      type==ORDER_TYPE_CREDIT          ?  TextByLanguage("Кредитная операция","Credit operation")     :
   #endif 
      type==ORDER_TYPE_BUY             ?  pref+" Buy"                                                 :
      type==ORDER_TYPE_SELL            ?  pref+" Sell"                                                :  
      TextByLanguage("Неизвестный тип ордера","Unknown order type")
     );
  }
//+------------------------------------------------------------------+
//| 返回仓位名称                                                        |
//+------------------------------------------------------------------+
string PositionTypeDescription(const ENUM_POSITION_TYPE type)
  {
   return
     (
      type==POSITION_TYPE_BUY    ? "Buy"  :
      type==POSITION_TYPE_SELL   ? "Sell" :  
      TextByLanguage("Неизвестный тип позиции","Unknown position type")
     );
  }
//+------------------------------------------------------------------+
//| 返回成交名称                                                        |
//+------------------------------------------------------------------+
string DealTypeDescription(const ENUM_DEAL_TYPE type)
  {
   return
     (
      type==DEAL_TYPE_BUY                       ?  TextByLanguage("Сделка на покупку","Buy deal") :
      type==DEAL_TYPE_SELL                      ?  TextByLanguage("Сделка на продажу","Sell deal") :
      type==DEAL_TYPE_BALANCE                   ?  TextByLanguage("Балансовая операция","Balance operation") :
      type==DEAL_TYPE_CREDIT                    ?  TextByLanguage("Начисление кредита","Credit") :
      type==DEAL_TYPE_CHARGE                    ?  TextByLanguage("Дополнительные сборы","Additional charge") :
      type==DEAL_TYPE_CORRECTION                ?  TextByLanguage("Корректирующая запись","Correction") :
      type==DEAL_TYPE_BONUS                     ?  TextByLanguage("Перечисление бонусов","Bonus") :
      type==DEAL_TYPE_COMMISSION                ?  TextByLanguage("Дополнительные комиссии","Additional comissions") :
      type==DEAL_TYPE_COMMISSION_DAILY          ?  TextByLanguage("Комиссия, начисляемая в конце торгового дня","Daily commission") :
      type==DEAL_TYPE_COMMISSION_MONTHLY        ?  TextByLanguage("Комиссия, начисляемая в конце месяца","Monthly commission") :
      type==DEAL_TYPE_COMMISSION_AGENT_DAILY    ?  TextByLanguage("Агентская комиссия, начисляемая в конце торгового дня","Daily agent commission") :
      type==DEAL_TYPE_COMMISSION_AGENT_MONTHLY  ?  TextByLanguage("Агентская комиссия, начисляемая в конце месяца","Monthly agent commission") :
      type==DEAL_TYPE_INTEREST                  ?  TextByLanguage("Начисления процентов на свободные средства","Agency commission charged at the end of month") :
      type==DEAL_TYPE_BUY_CANCELED              ?  TextByLanguage("Отмененная сделка покупки","Canceled buy transaction") :
      type==DEAL_TYPE_SELL_CANCELED             ?  TextByLanguage("Отмененная сделка продажи","Canceled sell transaction") :
      type==DEAL_DIVIDEND                       ?  TextByLanguage("Начисление дивиденда","Dividend operations") :
      type==DEAL_DIVIDEND_FRANKED               ?  TextByLanguage("Начисление франкированного дивиденда","Franked (non-taxable) dividend operations") :
      type==DEAL_TAX                            ?  TextByLanguage("Начисление налога","Tax charges") : 
      TextByLanguage("Неизвестный тип сделки","Unknown deal type")
     );
  }
//+------------------------------------------------------------------+
//| 按订单类型返回仓位类型                                               |
//+------------------------------------------------------------------+
ENUM_POSITION_TYPE PositionTypeByOrderType(ENUM_ORDER_TYPE type_order)
  {
   if(
      type_order==ORDER_TYPE_BUY             ||
      type_order==ORDER_TYPE_BUY_LIMIT       ||
      type_order==ORDER_TYPE_BUY_STOP
   #ifdef __MQL5__                           ||
      type_order==ORDER_TYPE_BUY_STOP_LIMIT
   #endif 
     ) return POSITION_TYPE_BUY;
   else if(
      type_order==ORDER_TYPE_SELL            ||
      type_order==ORDER_TYPE_SELL_LIMIT      ||
      type_order==ORDER_TYPE_SELL_STOP
   #ifdef __MQL5__                           ||
      type_order==ORDER_TYPE_SELL_STOP_LIMIT
   #endif 
     ) return POSITION_TYPE_SELL;
   return WRONG_VALUE;
  }
//+------------------------------------------------------------------+

在测试事件集合类时,发现了一个非常令人不快的问题:当使用 HistorySelect() 创建终端中的订单和成交列表,以及随后访问列表的新元素时,我发现列出的订单不是按事件发生的顺序,而是按它们的放置时间。 我来解释一下:

  1. 开仓,
  2. 立即下订单,
  3. 部分平仓,
  4. 一直等到挂单被激活

事件的历史顺序预期如下:
开仓,下订单,部分平仓,订单激活 — 按时间进行操作。 但事实证明,在公共订单和成交历史中的事件顺序如下:

  1. 开仓
  2. 下订单
  3. 订单激活
  4. 部分平仓

换言之,订单和成交的历史在终端内彼此各自存在,且不关联,这也是合理的,因为这两个列表均拥有其自身历史。

订单和成交的集合类是以这样的方式进行的:当变更任何列表(订单或成交)时,读取帐户上的最后一个事件,从而无需经常扫描历史,因为这是非常昂贵的。 但考虑到上述情况,以及进行交易操作时,我们不会跟踪行动的顺序。 我们只是简单地下订单并等待其激活。 开仓之后,我们会专门处理它。 在这种情况下,所有事件都将按所需顺序排列,以便进行跟踪。 但是,这还不够。 我们需要按任意顺序处理,程序应该能够找到正确的事件,并准确地指向它。

基于以上所述,我改进了历史订单和事件集合类。 现在,如果出现无序事件,则该类会找到必要的订单,为其创建对象并将之放入列表中作为最后一个,以便事件集合类始终能够准确定义上次发生的事件。

为了实现该功能,我们将三个新方法添加到历史订单和成交集合类的私有部分:

//--- 在历史订单和成交列表中按类型和票据返回订单对象的标志
   bool              IsPresentOrderInList(const ulong order_ticket,const ENUM_ORDER_TYPE type);
//--- 返回 "丢失的" 订单类型和票据
   ulong             OrderSearch(const int start,ENUM_ORDER_TYPE &order_type);
//--- 创建订单对象并将其放入列表中
   bool              CreateNewOrder(const ulong order_ticket,const ENUM_ORDER_TYPE order_type);

以及它们在类实体钟的实现。

该方法通过票据和类型返回列表中订单对象存在的标志:

//+-----------------------------------------------------------------------------+
//| 按类型和票据返回列表中订单对象存在的标志                                             |
//+-----------------------------------------------------------------------------+
bool CHistoryCollection::IsPresentOrderInList(const ulong order_ticket,const ENUM_ORDER_TYPE type)
  {
   CArrayObj* list=dynamic_cast<CListObj*>(&this.m_list_all_orders);
   list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,type,EQUAL);
   list=CSelect::ByOrderProperty(list,ORDER_PROP_TICKET,order_ticket,EQUAL);
   return(list.Total()>0);
  }
//+------------------------------------------------------------------+

使用动态类型转换创建指向列表的指针(将 CArrayObj 列表发送到 CSelect 类,而集合列表是 CListObj 类型,并从 CArrayObj 继承)
仅保留订单类型 与通过输入传递给方法的类型相符订单
仅保留订单票据 与通过输入传递给方法的票据相符订单
如果存在此类订单(列表大于零),则返回 true

返回不是终端列表中的最后一个,但不在收集列表中的一笔订单和其类型:

//+------------------------------------------------------------------+
//| 返回 “丢失的” 订单类型和票据                                           |
//+------------------------------------------------------------------+
ulong CHistoryCollection::OrderSearch(const int start,ENUM_ORDER_TYPE &order_type)
  {
   ulong order_ticket=0;
   for(int i=start-1;i>=0;i--)
     {
      ulong ticket=::HistoryOrderGetTicket(i);
      if(ticket==0)
         continue;
      ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)::HistoryOrderGetInteger(ticket,ORDER_TYPE);
      if(this.IsPresentOrderInList(ticket,type))
         continue;
      order_ticket=ticket;
      order_type=type;
     }
   return order_ticket;
  }
//+------------------------------------------------------------------+

最后一笔订单的索引 被传递到终端订单列表。 由于索引指定了集合中已存在的订单,因此搜索循环应从列表中的前一笔订单 start-1 )开始。
由于必要的订单通常位于列表末尾附近,因此使用 IsPresentOrderInList() 方法从列表末端循环搜索集合中票据和类型缺失的订单。 如果集合中存在该订单,则检查下一笔订单。 一旦集合中缺失订单, 就会写入票据类型,并将它们返回给调用程序。 通过 方法结果返回票证,同时通过链接在变量中返回类型。

由于我们现在需要在类中的多个位置创建订单对象(在定义新订单时,以及在查找“丢失”订单时),我们编写一个单独的方法来创建订单对象,并将其放入集合列表中:

//+------------------------------------------------------------------+
//| 创建订单对象并将其放入列表中                                           |
//+------------------------------------------------------------------+
bool CHistoryCollection::CreateNewOrder(const ulong order_ticket,const ENUM_ORDER_TYPE order_type)
  {
   COrder* order=NULL;
   if(order_type==ORDER_TYPE_BUY)
     {
      order=new CHistoryOrder(order_ticket);
      if(order==NULL)
         return false;
     }
   else if(order_type==ORDER_TYPE_BUY_LIMIT)
     {
      order=new CHistoryPending(order_ticket);
      if(order==NULL)
         return false;
     }
   else if(order_type==ORDER_TYPE_BUY_STOP)
     {
      order=new CHistoryPending(order_ticket);
      if(order==NULL)
         return false;
     }
   else if(order_type==ORDER_TYPE_SELL)
     {
      order=new CHistoryOrder(order_ticket);
      if(order==NULL)
         return false;
     }
   else if(order_type==ORDER_TYPE_SELL_LIMIT)
     {
      order=new CHistoryPending(order_ticket);
      if(order==NULL)
         return false;
     }
   else if(order_type==ORDER_TYPE_SELL_STOP)
     {
      order=new CHistoryPending(order_ticket);
      if(order==NULL)
         return false;
     }
#ifdef __MQL5__
   else if(order_type==ORDER_TYPE_BUY_STOP_LIMIT)
     {
      order=new CHistoryPending(order_ticket);
      if(order==NULL)
         return false;
     }
   else if(order_type==ORDER_TYPE_SELL_STOP_LIMIT)
     {
      order=new CHistoryPending(order_ticket);
      if(order==NULL)
         return false;
     }
   else if(order_type==ORDER_TYPE_CLOSE_BY)
     {
      order=new CHistoryOrder(order_ticket);
      if(order==NULL)
         return false;
     }
#endif 
   if(this.m_list_all_orders.InsertSort(order))
      return true;
   else
     {
      delete order;
      return false;
     }
   return false;
  }
//+------------------------------------------------------------------+

此处一切都很简单明了:该方法接收订单票据和类型,并根据订单类型创建新的订单对象。 如果无法创建对象,则立即返回 false。 如果对象创建成功,则将其放置到集合中,并返回 true。 如果无法将其放置到集合中,则会删除新创建的对象,并返回 false

我们修改 Refresh() 集合类方法,因为“丢失”的订单必须要处理:

//+------------------------------------------------------------------+
//| 更新订单和成交列表                                                   |
//+------------------------------------------------------------------+
void CHistoryCollection::Refresh(void)
  {
#ifdef __MQL4__
   int total=::OrdersHistoryTotal(),i=m_index_order;
   for(; i<total; i++)
     {
      if(!::OrderSelect(i,SELECT_BY_POS,MODE_HISTORY)) continue;
      ENUM_ORDER_TYPE order_type=(ENUM_ORDER_TYPE)::OrderType();
      //--- 平仓和余额/信贷操作
      if(order_type<ORDER_TYPE_BUY_LIMIT || order_type>ORDER_TYPE_SELL_STOP)
        {
         CHistoryOrder *order=new CHistoryOrder(::OrderTicket());
         if(order==NULL) continue;
         if(!this.m_list_all_orders.InsertSort(order))
           {
            ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Failed to add order to list"));
            delete order;
           }
        }
      else
        {
         //--- 删除的挂单
         CHistoryPending *order=new CHistoryPending(::OrderTicket());
         if(order==NULL) continue;
         if(!this.m_list_all_orders.InsertSort(order))this.m_list_all_orders.Type()
           {
            ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Failed to add order to list"));
            delete order;
           }
        }
     }
//---
   int delta_order=i-m_index_order;
   this.m_index_order=i;
   this.m_delta_order=delta_order;
   this.m_is_trade_event=(this.m_delta_order!=0 ? true : false);
//--- __MQL5__
#else 
   if(!::HistorySelect(0,END_TIME)) return;
//--- 订单
   int total_orders=::HistoryOrdersTotal(),i=m_index_order;
   for(; i<total_orders; i++)
     {
      ulong order_ticket=::HistoryOrderGetTicket(i);
      if(order_ticket==0) continue;
      ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)::HistoryOrderGetInteger(order_ticket,ORDER_TYPE);
      if(type==ORDER_TYPE_BUY || type==ORDER_TYPE_SELL || type==ORDER_TYPE_CLOSE_BY)
        {
         //--- 如果没有此类型的订单,并且在列表中包含此票据,则创建订单对象,并将其添加到列表中
         if(!this.IsPresentOrderInList(order_ticket,type))
           {
            if(!this.CreateNewOrder(order_ticket,type))
               ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Could not add order to list"));
           }
         //--- 订单已经存在于列表中,这意味着必要的订单不是历史列表中的最后一笔订单。 我们找到它
         else
           {
            ENUM_ORDER_TYPE type_lost=WRONG_VALUE;
            ulong ticket_lost=this.OrderSearch(i,type_lost);
            if(ticket_lost>0 && !this.CreateNewOrder(ticket_lost,type_lost))
               ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Could not add order to list"));
           }
        }
      else
        {
         //--- 如果没有此类型的挂单,并且列表中包含此票据,则创建订单对象,并将其添加到列表中
         if(!this.IsPresentOrderInList(order_ticket,type))
           {
            if(!this.CreateNewOrder(order_ticket,type))
               ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Could not add order to list"));
           }
         //--- 订单已经存在于列表中,这意味着必要的订单不是历史列表中的最后一笔订单。 我们找到它
         else
           {
            ENUM_ORDER_TYPE type_lost=WRONG_VALUE;
            ulong ticket_lost=this.OrderSearch(i,type_lost);
            if(ticket_lost>0 && !this.CreateNewOrder(ticket_lost,type_lost))
               ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Could not add order to list"));
           }
        }
     }
//--- 保存上次添加的订单索引,以及与上一次检查相比的差值
   int delta_order=i-this.m_index_order;
   this.m_index_order=i;
   this.m_delta_order=delta_order;

//--- 成交
   int total_deals=::HistoryDealsTotal(),j=m_index_deal;
   for(; j<total_deals; j++)
     {
      ulong deal_ticket=::HistoryDealGetTicket(j);
      if(deal_ticket==0) continue;
      CHistoryDeal *deal=new CHistoryDeal(deal_ticket);
      if(deal==NULL) continue;
      if(!this.m_list_all_orders.InsertSort(deal))
        {
         ::Print(DFUN,TextByLanguage("Не удалось добавить сделку в список","Could not add deal to list"));
         delete deal;
        }
     }
//--- 保存上次添加的成交索引,以及与上一次检查相比的差值
   int delta_deal=j-this.m_index_deal;
   this.m_index_deal=j;
   this.m_delta_deal=delta_deal;
//--- 在历史记录中设置新事件标志
   this.m_is_trade_event=(this.m_delta_order+this.m_delta_deal);
#endif 
  }
//+------------------------------------------------------------------+

处理 MQL5 新订单的模块已在方法中更改。 所有实施的更改都加以注释,并在列表文本中突出显示。

我们在 COrder 类的公共部分中添加方法定义,以便搜索类似的订单:

//--- 比较所有属性的 COrder(搜索相等的事件对象)
   bool              IsEqual(COrder* compared_order) const;

且它的实现位于类的实体之外:

//+------------------------------------------------------------------+
//| 按所有属性比较 COrder 对象                                           |
//+------------------------------------------------------------------+
bool COrder::IsEqual(COrder *compared_order) const
  {
   int beg=0, end=ORDER_PROP_INTEGER_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_ORDER_PROP_INTEGER prop=(ENUM_ORDER_PROP_INTEGER)i;
      if(this.GetProperty(prop)!=compared_order.GetProperty(prop)) return false; 
     }
   beg=end; end+=ORDER_PROP_DOUBLE_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_ORDER_PROP_DOUBLE prop=(ENUM_ORDER_PROP_DOUBLE)i;
      if(this.GetProperty(prop)!=compared_order.GetProperty(prop)) return false; 
     }
   beg=end; end+=ORDER_PROP_STRING_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_ORDER_PROP_STRING prop=(ENUM_ORDER_PROP_STRING)i;
      if(this.GetProperty(prop)!=compared_order.GetProperty(prop)) return false; 
     }
   return true;
  }
//+------------------------------------------------------------------+

该方法遍历当前订单对象的所有属性,并在循环中比较通过指针传递给方法的订单
一旦检测到当前订单的任何属性不等于所比较订单的相同属性,就会返回 false,这意味着订单不相等。


事件类

准备阶段现已完毕。 我们开始创建事件对象类。

我们将完成与创建订单类时完全相同的操作。 我们将开发一个基本的事件类,以及按照它们的状态描述衍生出的五个子类:

  • 开仓事件,
  • 平仓事件,
  • 下挂单事件,
  • 删除挂单事件,
  • 余额操作事件

在 Objects 函数库目录下先前创建的 Events 文件夹中,创建一个继承自 CObject 基类的新 CEvent 类。 在新创建的类模板中,设置必要头文件,包括服务函数、订单集合类,以及私有和受保护类成员和方法:

//+------------------------------------------------------------------+
//|                                                        Event.mqh |
//|                                  版权所有 2018, MetaQuotes 软件公司 |
//|                             https://mql5.com/zh/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "版权所有 2018, MetaQuotes 软件公司"
#property link      "https://mql5.com/zh/users/artmedia70"
#property version   "1.00"
#property strict    // 对于 mql4 是必须的
//+------------------------------------------------------------------+
//| 包含文件                                                           |
//+------------------------------------------------------------------+
#include <Object.mqh>
#include "\..\..\Services\DELib.mqh"
#include "..\..\Collections\HistoryCollection.mqh"
#include "..\..\Collections\MarketCollection.mqh"
//+------------------------------------------------------------------+
//| 抽象事件类                                                           |
//+------------------------------------------------------------------+
class CEvent : public CObject
  {
private:
   int               m_event_code;                                   // 事件代码
//--- 返回事件的(1)实数型,和(2)字符串型属性在数组当中的位置索引
   int               IndexProp(ENUM_EVENT_PROP_DOUBLE property)const { return(int)property-EVENT_PROP_INTEGER_TOTAL;                         }
   int               IndexProp(ENUM_EVENT_PROP_STRING property)const { return(int)property-EVENT_PROP_INTEGER_TOTAL-EVENT_PROP_DOUBLE_TOTAL; }
protected:
   ENUM_TRADE_EVENT  m_trade_event;                                  // 交易事件
   long              m_chart_id;                                     // 控制程序所在的图表 ID
   int               m_digits_acc;                                   // 帐户货币的小数位数
   long              m_long_prop[EVENT_PROP_INTEGER_TOTAL];          // 事件整数型属性
   double            m_double_prop[EVENT_PROP_DOUBLE_TOTAL];         // 事件实数型属性
   string            m_string_prop[EVENT_PROP_STRING_TOTAL];         // 事件字符串型属性
//--- 返回交易事件中的存在标志
   bool              IsPresentEventFlag(const int event_code)  const { return (this.m_event_code & event_code)==event_code;            }

   //--- 受保护的参数构造函数
                     CEvent(const ENUM_EVENT_STATUS event_status,const int event_code,const ulong ticket);
public:
//--- 默认构造函数
                     CEvent(void){;}
 
//--- 设置事件的 (1) 整数型, (2) 实数型,和 (3) 字符串型属性
   void              SetProperty(ENUM_EVENT_PROP_INTEGER property,long value) { this.m_long_prop[property]=value;                      }
   void              SetProperty(ENUM_EVENT_PROP_DOUBLE property,double value){ this.m_double_prop[this.IndexProp(property)]=value;    }
   void              SetProperty(ENUM_EVENT_PROP_STRING property,string value){ this.m_string_prop[this.IndexProp(property)]=value;    }
//--- 从属性数组中返回事件的(1)整数型,(2)实数型,和(3)字符串属性
   long              GetProperty(ENUM_EVENT_PROP_INTEGER property)      const { return this.m_long_prop[property];                     }
   double            GetProperty(ENUM_EVENT_PROP_DOUBLE property)       const { return this.m_double_prop[this.IndexProp(property)];   }
   string            GetProperty(ENUM_EVENT_PROP_STRING property)       const { return this.m_string_prop[this.IndexProp(property)];   }

//--- 返回支持的事件属性标志
   virtual bool      SupportProperty(ENUM_EVENT_PROP_INTEGER property)        { return true; }
   virtual bool      SupportProperty(ENUM_EVENT_PROP_DOUBLE property)         { return true; }
   virtual bool      SupportProperty(ENUM_EVENT_PROP_STRING property)         { return true; }

//--- 设置控制程序所在的图表 ID
   void              SetChartID(const long id)                                { this.m_chart_id=id;                                    }
//--- 解码事件代码,并设置交易事件,(2)返回交易事件
   void              SetTypeEvent(void);
   ENUM_TRADE_EVENT  TradeEvent(void)                                   const { return this.m_trade_event;                             }
//--- 将事件发送到图表(在子类中实现)
   virtual void      SendEvent(void) {;}

//--- 按指定的属性比较 CEvent 对象(按指定的事件对象属性为列表进行排序)
   virtual int       Compare(const CObject *node,const int mode=0) const;
//--- 按所有属性比较 CEvent 对象(搜索相等的事件对象)
   bool              IsEqual(CEvent* compared_event) const;
//+------------------------------------------------------------------+
//| 简化的访问事件对象属性的方法                                           |
//+------------------------------------------------------------------+
//--- 返回 (1) 事件类型, (2) 事件时间的毫秒值, (3) 事件状态, (4) 事件原因, (5) 成交类型, (6) 成交票据, 
//--- (7) 成交执行所依据的订单类型, (8) 开仓订单类型, (9) 开仓时最后一笔订单票据, 
//--- (10) 开仓时首笔订单票据, (11) 仓位 ID, (12) 逆向仓位 ID, (13) 魔幻数字, (14) 开仓时间

   ENUM_TRADE_EVENT  TypeEvent(void)                                    const { return (ENUM_TRADE_EVENT)this.GetProperty(EVENT_PROP_TYPE_EVENT);     }
   long              TimeEvent(void)                                    const { return this.GetProperty(EVENT_PROP_TIME_EVENT);                       }
   ENUM_EVENT_STATUS Status(void)                                       const { return (ENUM_EVENT_STATUS)this.GetProperty(EVENT_PROP_STATUS_EVENT);  }
   ENUM_EVENT_REASON Reason(void)                                       const { return (ENUM_EVENT_REASON)this.GetProperty(EVENT_PROP_REASON_EVENT);  }
   long              TypeDeal(void)                                     const { return this.GetProperty(EVENT_PROP_TYPE_DEAL_EVENT);                  }
   long              TicketDeal(void)                                   const { return this.GetProperty(EVENT_PROP_TICKET_DEAL_EVENT);                }
   long              TypeOrderEvent(void)                               const { return this.GetProperty(EVENT_PROP_TYPE_ORDER_EVENT);                 }
   long              TypeOrderPosition(void)                            const { return this.GetProperty(EVENT_PROP_TYPE_ORDER_POSITION);              }
   long              TicketOrderEvent(void)                             const { return this.GetProperty(EVENT_PROP_TICKET_ORDER_EVENT);               }
   long              TicketOrderPosition(void)                          const { return this.GetProperty(EVENT_PROP_TICKET_ORDER_POSITION);            }
   long              PositionID(void)                                   const { return this.GetProperty(EVENT_PROP_POSITION_ID);                      }
   long              PositionByID(void)                                 const { return this.GetProperty(EVENT_PROP_POSITION_BY_ID);                   }
   long              Magic(void)                                        const { return this.GetProperty(EVENT_PROP_MAGIC_ORDER);                      }
   long              TimePosition(void)                                 const { return this.GetProperty(EVENT_PROP_TIME_ORDER_POSITION);              }
   
//--- 返回 (1) 事件发生时的价格, (2) 开单价, (3) 平单价,
//--- (4) 止损价, (5) 止盈价, (6) 盈利, (7) 请求的交易量, (8), 执行的交易量, (9) 剩余的交易量
   double            PriceEvent(void)                                   const { return this.GetProperty(EVENT_PROP_PRICE_EVENT);                      }
   double            PriceOpen(void)                                    const { return this.GetProperty(EVENT_PROP_PRICE_OPEN);                       }
   double            PriceClose(void)                                   const { return this.GetProperty(EVENT_PROP_PRICE_CLOSE);                      }
   double            PriceStopLoss(void)                                const { return this.GetProperty(EVENT_PROP_PRICE_SL);                         }
   double            PriceTakeProfit(void)                              const { return this.GetProperty(EVENT_PROP_PRICE_TP);                         }
   double            Profit(void)                                       const { return this.GetProperty(EVENT_PROP_PROFIT);                           }
   double            VolumeInitial(void)                                const { return this.GetProperty(EVENT_PROP_VOLUME_INITIAL);                   }
   double            VolumeExecuted(void)                               const { return this.GetProperty(EVENT_PROP_VOLUME_EXECUTED);                  }
   double            VolumeCurrent(void)                                const { return this.GetProperty(EVENT_PROP_VOLUME_CURRENT);                   }
   
//--- 返回品种
   string            Symbol(void)                                       const { return this.GetProperty(EVENT_PROP_SYMBOL);                           }
   
//+------------------------------------------------------------------+
//| 订单对象属性的描述                                                   |
//+------------------------------------------------------------------+
//--- 返回订单(1)整数型,(2)实数型,和(3)字符串型属性的描述
   string            GetPropertyDescription(ENUM_EVENT_PROP_INTEGER property);
   string            GetPropertyDescription(ENUM_EVENT_PROP_DOUBLE property);
   string            GetPropertyDescription(ENUM_EVENT_PROP_STRING property);
//--- 返回事件 (1) 状态,和 (2) 类型
   string            StatusDescription(void)          const;
   string            TypeEventDescription(void)       const;
//--- 返回 (1) 订单/仓位/成交, (2) 父订单, (3) 仓位的名称
   string            TypeOrderDescription(void)       const;
   string            TypeOrderBasedDescription(void)  const;
   string            TypePositionDescription(void)    const;
//--- 返回成交/订单/仓位原因的名称
   string            ReasonDescription(void)          const;

//--- 显示 (1) 订单属性描述 (full_prop=true - 所有属性, false - 仅支持的),
//--- (2) 流水帐里输出简要事件消息 (在子类中实现)
   void              Print(const bool full_prop=false);
   virtual void      PrintShort(void) {;}
  };
//+------------------------------------------------------------------+
//| 构造函数                                                            |
//+------------------------------------------------------------------+
CEvent::CEvent(const ENUM_EVENT_STATUS event_status,const int event_code,const ulong ticket) : m_event_code(event_code)
  {
   this.m_long_prop[EVENT_PROP_STATUS_EVENT]       =  event_status;
   this.m_long_prop[EVENT_PROP_TICKET_ORDER_EVENT] =  (long)ticket;
   this.m_digits_acc=(int)::AccountInfoInteger(ACCOUNT_CURRENCY_DIGITS);
   this.m_chart_id=::ChartID();
  }
//+------------------------------------------------------------------+

构造函数接收触发事件的事件状态交易事件代码订单或成交票据

此处内容与函数库描述的第一部分中以前研究的 COrder 类的受保护构造函数大致相似。

区别在于受保护的类构造函数中只充实了两个事件属性。 这些是触发事件的订单/成交的事件状态和票据。 根据传递给构造函数的事件代码检测事件类型,并将其保存在 SetTypeEvent() 类方法中。 所有其他事件属性都会从事件中涉及的订单和成交的状态中检测出来,并由相应的类方法分别设置。 这样做是因为要在事件集合类中检测事件,其方法的功能在于为新创建的事件设置所有属性。

我们已研究了事件代码(m_event_code,以及在函数库描述的第四部分中如何填充和解释它。 我们将它从 CEngine 类移到此处,因为它只是临时放置到函数库基类,以便检查处理事件。 现在,它将在事件集合类中进行计算,并在创建事件对象时传递给类构造函数。
交易事件(m_trade_event本身是通过解码 SetTypeEvent() 方法中的事件代码编译的,我们已经在 第四篇文章中讲述了事件代码解码方法。
我们需要控制程序所在的图表 ID( m_chart_id向其发送有关事件的自定义消息。
帐户货币的小数位数(m_digits_acc是在流水帐里正确显示有关事件消息所必需的。

方法 Compare()IsEqual() 比较对象事件属性,它们非常简单和透明。 我们在函数库讲述的第一部分研究过 Compare() 方法。 它类似于 COrder 对象之一。 与第一种方法仅比较两个对象的一个属性相比,IsEqual() 将比较两个对象的所有字段。 如果两个对象的所有字段都相似(当前对象的每个属性等于比较对象的相应属性),则两个对象相同。 该方法在循环中检查两个对象的所有属性,并在检测到差异时立即返回 false。 进一步检查没有意义,因为其中一个对象属性与被比较对象的相同属性不再相等。

//+------------------------------------------------------------------+
//| 按指定的属性比较 CEvent 对象                                          |
//+------------------------------------------------------------------+
int CEvent::Compare(const CObject *node,const int mode=0) const
  {
   const CEvent *event_compared=node;
//--- 比较两个事件的整数型属性
   if(mode<EVENT_PROP_INTEGER_TOTAL)
     {
      long value_compared=event_compared.GetProperty((ENUM_EVENT_PROP_INTEGER)mode);
      long value_current=this.GetProperty((ENUM_EVENT_PROP_INTEGER)mode);
      return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0);
     }
//--- 比较两个对象的整数型属性
   if(mode<EVENT_PROP_DOUBLE_TOTAL+EVENT_PROP_INTEGER_TOTAL)
     {
      double value_compared=event_compared.GetProperty((ENUM_EVENT_PROP_DOUBLE)mode);
      double value_current=this.GetProperty((ENUM_EVENT_PROP_DOUBLE)mode);
      return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0);
     }
//--- 比较两个对象的字符串型属性
   else if(mode<EVENT_PROP_DOUBLE_TOTAL+EVENT_PROP_INTEGER_TOTAL+EVENT_PROP_STRING_TOTAL)
     {
      string value_compared=event_compared.GetProperty((ENUM_EVENT_PROP_STRING)mode);
      string value_current=this.GetProperty((ENUM_EVENT_PROP_STRING)mode);
      return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0);
     }
   return 0;
  }
//+------------------------------------------------------------------+
//| 按所有属性比较 CEvent 事件                                           |
//+------------------------------------------------------------------+
bool CEvent::IsEqual(CEvent *compared_event) const
  {
   int beg=0, end=EVENT_PROP_INTEGER_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_EVENT_PROP_INTEGER prop=(ENUM_EVENT_PROP_INTEGER)i;
      if(this.GetProperty(prop)!=compared_event.GetProperty(prop)) return false; 
     }
   beg=end; end+=EVENT_PROP_DOUBLE_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_EVENT_PROP_DOUBLE prop=(ENUM_EVENT_PROP_DOUBLE)i;
      if(this.GetProperty(prop)!=compared_event.GetProperty(prop)) return false; 
     }
   beg=end; end+=EVENT_PROP_STRING_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_EVENT_PROP_STRING prop=(ENUM_EVENT_PROP_STRING)i;
      if(this.GetProperty(prop)!=compared_event.GetProperty(prop)) return false; 
     }
   return true;
  }
//+------------------------------------------------------------------+

我们来深入查看 SetTypeEvent() 方法

所有必要的检查和操作都直接在代码注释中设置:

//+------------------------------------------------------------------+
//| 解码事件代码并设置交易事件                                              |
//+------------------------------------------------------------------+
void CEvent::SetTypeEvent(void)
  {
//--- 已下挂单(检查事件代码是否匹配,因为此处只能有一个标志)
   if(this.m_event_code==TRADE_EVENT_FLAG_ORDER_PLASED)
     {
      this.m_trade_event=TRADE_EVENT_PENDING_ORDER_PLASED;
      this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
      return;
     }
//--- 已删除挂单(检查事件代码是否匹配,因为此处只能有一个标志)
   if(this.m_event_code==TRADE_EVENT_FLAG_ORDER_REMOVED)
     {
      this.m_trade_event=TRADE_EVENT_PENDING_ORDER_REMOVED;
      this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
      return;
     }
//--- 开仓(检查事件代码中是否存在多个标志)
   if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_POSITION_OPENED))
     {
      //--- 如果挂单被价格激活
      if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_ORDER_ACTIVATED))
        {
         //--- 检查部分平仓标志,并设置“挂单激活”或“挂单部分激活”交易事件
         this.m_trade_event=(!this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_PENDING_ORDER_ACTIVATED : TRADE_EVENT_PENDING_ORDER_ACTIVATED_PARTIAL);
         this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
         return;
        }
      //--- 检查部分开仓标志,并设置“开仓”或“部分开仓”交易事件
      this.m_trade_event=(!this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_OPENED : TRADE_EVENT_POSITION_OPENED_PARTIAL);
      this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
      return;
     }
//--- 平仓(检查事件代码中是否存在多个标志)
   if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_POSITION_CLOSED))
     {
      //--- 如果由止损平仓
      if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_SL))
        {
         //--- 检查部分平仓标志,并设置“由止损平仓”或“由止损部分平仓”交易事件
         this.m_trade_event=(!this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_CLOSED_BY_SL : TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_SL);
         this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
         return;
        }
      //--- 如果由止盈平仓
      else if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_TP))
        {
         //--- 检查部分平仓标志,并设置“由止盈平仓”或“由止盈部分平仓”交易事件
         this.m_trade_event=(!this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_CLOSED_BY_TP : TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_TP);
         this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
         return;
        }
      //--- 如果一笔仓位由逆向仓位平仓
      else if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_BY_POS))
        {
         //--- 检查部分平仓标志,并设置“由逆向仓位平仓”或“由逆向仓位部分平仓”交易事件
         this.m_trade_event=(!this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_CLOSED_BY_POS : TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS);
         this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
         return;
        }
      //--- 如果已平仓
      else
        {
         //--- 检查部分平仓标志,并设置“已平仓”或“已部分平仓”交易事件
         this.m_trade_event=(!this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_CLOSED : TRADE_EVENT_POSITION_CLOSED_PARTIAL);
         this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
         return;
        }
     }
//--- 账户上的余额操作(按成交类型澄清事件)
   if(this.m_event_code==TRADE_EVENT_FLAG_ACCOUNT_BALANCE)
     {
      //--- 初始化交易事件
      this.m_trade_event=TRADE_EVENT_NO_EVENT;
      //--- 获取成交类型
      ENUM_DEAL_TYPE deal_type=(ENUM_DEAL_TYPE)this.GetProperty(EVENT_PROP_TYPE_DEAL_EVENT);
      //--- 如果成交是余额操作
      if(deal_type==DEAL_TYPE_BALANCE)
        {
        //--- 检查成交利润,并设置事件(入金或出金)
         this.m_trade_event=(this.GetProperty(EVENT_PROP_PROFIT)>0 ? TRADE_EVENT_ACCOUNT_BALANCE_REFILL : TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL);
        }
      //--- 其它的余额操作类型与从 DEAL_TYPE_CREDIT 开始的 ENUM_DEAL_TYPE 枚举匹配
      else if(deal_type>DEAL_TYPE_BALANCE)
        {
        //--- 设置事件
         this.m_trade_event=(ENUM_TRADE_EVENT)deal_type;
        }
      this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
      return;
     }
  }
//+------------------------------------------------------------------+

这里一切都很简单:将事件代码传递给方法,然后检查事件代码标志。 如果代码具有 checked 标志,则设置相应的交易事件。 由于事件代码可能有多个标志,因此会检查事件的所有可能标志,并根据它们的组合来定义事件类型。 接下来,将事件类型添加到相应的类变量中,并将其输入到事件对象的属性(EVENT_PROP_TYPE_EVENT)中。

我们来查看剩余的类方法列表:

//+------------------------------------------------------------------+
//| 返回事件的整数型属性的描述                                            |
//+------------------------------------------------------------------+
string CEvent::GetPropertyDescription(ENUM_EVENT_PROP_INTEGER property)
  {
   return
     (
      property==EVENT_PROP_TYPE_EVENT              ?  TextByLanguage("Тип события","Event type")+": "+this.TypeEventDescription()                                                       :
      property==EVENT_PROP_TIME_EVENT              ?  TextByLanguage("Время события","Time of event")+": "+TimeMSCtoString(this.GetProperty(property))                                    :
      property==EVENT_PROP_STATUS_EVENT            ?  TextByLanguage("Статус события","Status of event")+": \""+this.StatusDescription()+"\""                                             :
      property==EVENT_PROP_REASON_EVENT            ?  TextByLanguage("Причина события","Reason of event")+": "+this.ReasonDescription()                                                   :
      property==EVENT_PROP_TYPE_DEAL_EVENT         ?  TextByLanguage("Тип сделки","Deal's type")+": "+DealTypeDescription((ENUM_DEAL_TYPE)this.GetProperty(property))                     :
      property==EVENT_PROP_TICKET_DEAL_EVENT       ?  TextByLanguage("Тикет сделки","Deal's ticket")+" #"+(string)this.GetProperty(property)                                              :
      property==EVENT_PROP_TYPE_ORDER_EVENT        ?  TextByLanguage("Тип ордера события","Event's order type")+": "+OrderTypeDescription((ENUM_ORDER_TYPE)this.GetProperty(property))    :
      property==EVENT_PROP_TYPE_ORDER_POSITION     ?  TextByLanguage("Тип ордера позиции","Position's order type")+": "+OrderTypeDescription((ENUM_ORDER_TYPE)this.GetProperty(property)) :
      property==EVENT_PROP_TICKET_ORDER_POSITION   ?  TextByLanguage("Тикет первого ордера позиции","Position's first order ticket")+" #"+(string)this.GetProperty(property)              :
      property==EVENT_PROP_TICKET_ORDER_EVENT      ?  TextByLanguage("Тикет ордера события","Event's order ticket")+" #"+(string)this.GetProperty(property)                               :
      property==EVENT_PROP_POSITION_ID             ?  TextByLanguage("Идентификатор позиции","Position ID")+" #"+(string)this.GetProperty(property)                                       :
      property==EVENT_PROP_POSITION_BY_ID          ?  TextByLanguage("Идентификатор встречной позиции","Opposite position ID")+" #"+(string)this.GetProperty(property)                  :
      property==EVENT_PROP_MAGIC_ORDER             ?  TextByLanguage("Магический номер","Magic number")+": "+(string)this.GetProperty(property)                                           :
      property==EVENT_PROP_TIME_ORDER_POSITION     ?  TextByLanguage("Время открытия позиции","Position open time")+": "+TimeMSCtoString(this.GetProperty(property))                  :
      ""
     );
  }
//+------------------------------------------------------------------+
//| 返回事件的实数型属性的描述                                            |
//+------------------------------------------------------------------+
string CEvent::GetPropertyDescription(ENUM_EVENT_PROP_DOUBLE property)
  {
   int dg=(int)::SymbolInfoInteger(this.GetProperty(EVENT_PROP_SYMBOL),SYMBOL_DIGITS);
   int dgl=(int)DigitsLots(this.GetProperty(EVENT_PROP_SYMBOL));
   return
     (
      property==EVENT_PROP_PRICE_EVENT       ?  TextByLanguage("Цена события","Price at the time of event")+": "+::DoubleToString(this.GetProperty(property),dg) :
      property==EVENT_PROP_PRICE_OPEN        ?  TextByLanguage("Цена открытия","Open price")+": "+::DoubleToString(this.GetProperty(property),dg)                    :
      property==EVENT_PROP_PRICE_CLOSE       ?  TextByLanguage("Цена закрытия","Close price")+": "+::DoubleToString(this.GetProperty(property),dg)                   :
      property==EVENT_PROP_PRICE_SL          ?  TextByLanguage("Цена StopLoss","StopLoss price")+": "+::DoubleToString(this.GetProperty(property),dg)                :
      property==EVENT_PROP_PRICE_TP          ?  TextByLanguage("Цена TakeProfit","TakeProfit price")+": "+::DoubleToString(this.GetProperty(property),dg)            :
      property==EVENT_PROP_VOLUME_INITIAL    ?  TextByLanguage("Начальный объём","Initial volume")+": "+::DoubleToString(this.GetProperty(property),dgl)             :
      property==EVENT_PROP_VOLUME_EXECUTED   ?  TextByLanguage("Исполненный объём","Executed volume")+": "+::DoubleToString(this.GetProperty(property),dgl)          :
      property==EVENT_PROP_VOLUME_CURRENT    ?  TextByLanguage("Оставшийся объём","Remaining volume")+": "+::DoubleToString(this.GetProperty(property),dgl)          :
      property==EVENT_PROP_PROFIT            ?  TextByLanguage("Профит","Profit")+": "+::DoubleToString(this.GetProperty(property),this.m_digits_acc)                :
      ""
     );
  }
//+------------------------------------------------------------------+
//| 返回事件的字符串型属性的描述                                           |
//+------------------------------------------------------------------+
string CEvent::GetPropertyDescription(ENUM_EVENT_PROP_STRING property)
  {
   return TextByLanguage("Символ","Symbol")+": \""+this.GetProperty(property)+"\"";
  }
//+------------------------------------------------------------------+
//| 返回事件状态名                                                       |
//+------------------------------------------------------------------+
string CEvent::StatusDescription(void) const
  {
   ENUM_EVENT_STATUS status=(ENUM_EVENT_STATUS)this.GetProperty(EVENT_PROP_STATUS_EVENT);
   return
     (
      status==EVENT_STATUS_MARKET_PENDING    ?  TextByLanguage("Установлен отложенный ордер","Pending order placed") :
      status==EVENT_STATUS_MARKET_POSITION   ?  TextByLanguage("Открыта позиция","Position opened")                 :
      status==EVENT_STATUS_HISTORY_PENDING   ?  TextByLanguage("Удален отложенный ордер","Pending order removed")    :
      status==EVENT_STATUS_HISTORY_POSITION  ?  TextByLanguage("Закрыта позиция","Position closed")                  :
      status==EVENT_STATUS_BALANCE           ?  TextByLanguage("Балансная операция","Balance operation")             :
      ""
     );
  }
//+------------------------------------------------------------------+
//| 返回交易事件名                                                       |
//+------------------------------------------------------------------+
string CEvent::TypeEventDescription(void) const
  {
   ENUM_TRADE_EVENT event=this.TypeEvent();
   return
     (
      event==TRADE_EVENT_PENDING_ORDER_PLASED            ?  TextByLanguage("Отложенный ордер установлен","Pending order placed")                                  :
      event==TRADE_EVENT_PENDING_ORDER_REMOVED           ?  TextByLanguage("Отложенный ордер удалён","Pending order removed")                                     :
      event==TRADE_EVENT_ACCOUNT_CREDIT                  ?  TextByLanguage("Начисление кредита","Credit")                                                         :
      event==TRADE_EVENT_ACCOUNT_CHARGE                  ?  TextByLanguage("Дополнительные сборы","Additional charge")                                            :
      event==TRADE_EVENT_ACCOUNT_CORRECTION              ?  TextByLanguage("Корректирующая запись","Correction")                                                  :
      event==TRADE_EVENT_ACCOUNT_BONUS                   ?  TextByLanguage("Перечисление бонусов","Bonus")                                                        :
      event==TRADE_EVENT_ACCOUNT_COMISSION               ?  TextByLanguage("Дополнительные комиссии","Additional commission")                                     :
      event==TRADE_EVENT_ACCOUNT_COMISSION_DAILY         ?  TextByLanguage("Комиссия, начисляемая в конце торгового дня","Daily commission")                      :
      event==TRADE_EVENT_ACCOUNT_COMISSION_MONTHLY       ?  TextByLanguage("Комиссия, начисляемая в конце месяца","Monthly commission")                           :
      event==TRADE_EVENT_ACCOUNT_COMISSION_AGENT_DAILY   ?  TextByLanguage("Агентская комиссия, начисляемая в конце торгового дня","Daily agent commission")      :
      event==TRADE_EVENT_ACCOUNT_COMISSION_AGENT_MONTHLY ?  TextByLanguage("Агентская комиссия, начисляемая в конце месяца","Monthly agent commission")           :
      event==TRADE_EVENT_ACCOUNT_INTEREST                ?  TextByLanguage("Начисления процентов на свободные средства","Interest rate")                          :
      event==TRADE_EVENT_BUY_CANCELLED                   ?  TextByLanguage("Отмененная сделка покупки","Canceled buy deal")                                       :
      event==TRADE_EVENT_SELL_CANCELLED                  ?  TextByLanguage("Отмененная сделка продажи","Canceled sell deal")                                      :
      event==TRADE_EVENT_DIVIDENT                        ?  TextByLanguage("Начисление дивиденда","Dividend operations")                                          :
      event==TRADE_EVENT_DIVIDENT_FRANKED                ?  TextByLanguage("Начисление франкированного дивиденда","Franked (non-taxable) dividend operations")    :
      event==TRADE_EVENT_TAX                             ?  TextByLanguage("Начисление налога","Tax charges")                                                     :
      event==TRADE_EVENT_ACCOUNT_BALANCE_REFILL          ?  TextByLanguage("Пополнение средств на балансе","Balance refill")                                      :
      event==TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL      ?  TextByLanguage("Снятие средств с баланса","Withdrawals")                                              :
      event==TRADE_EVENT_PENDING_ORDER_ACTIVATED         ?  TextByLanguage("Отложенный ордер активирован ценой","Pending order activated")                        :
      event==TRADE_EVENT_PENDING_ORDER_ACTIVATED_PARTIAL ?  TextByLanguage("Отложенный ордер активирован ценой частично","Pending order activated partially")     :
      event==TRADE_EVENT_POSITION_OPENED                 ?  TextByLanguage("Позиция открыта","Position opened")                                                  :
      event==TRADE_EVENT_POSITION_OPENED_PARTIAL         ?  TextByLanguage("Позиция открыта частично","Position opened partially")                               :
      event==TRADE_EVENT_POSITION_CLOSED                 ?  TextByLanguage("Позиция закрыта","Position closed")                                                   :
      event==TRADE_EVENT_POSITION_CLOSED_PARTIAL         ?  TextByLanguage("Позиция закрыта частично","Position closed partially")                                :
      event==TRADE_EVENT_POSITION_CLOSED_BY_POS          ?  TextByLanguage("Позиция закрыта встречной","Position closed by opposite position")                    :
      event==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS  ?  TextByLanguage("Позиция закрыта встречной частично","Position closed partially by opposite position") :
      event==TRADE_EVENT_POSITION_CLOSED_BY_SL           ?  TextByLanguage("Позиция закрыта по StopLoss","Position closed by StopLoss")                           :
      event==TRADE_EVENT_POSITION_CLOSED_BY_TP           ?  TextByLanguage("Позиция закрыта по TakeProfit","Position closed by TakeProfit")                       :
      event==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_SL   ?  TextByLanguage("Позиция закрыта частично по StopLoss","Position closed partially by StopLoss")        :
      event==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_TP   ?  TextByLanguage("Позиция закрыта частично по TakeProfit","Position closed partially by TakeProfit")    :
      event==TRADE_EVENT_POSITION_REVERSED               ?  TextByLanguage("Разворот позиции","Position reversal")                                                :
      event==TRADE_EVENT_POSITION_VOLUME_ADD             ?  TextByLanguage("Добавлен объём к позиции","Added volume to position")                                 :
      TextByLanguage("Нет торгового события","No trade event")
     );   
  }
//+------------------------------------------------------------------+
//| 返回订单/仓位/成交的名称                                             |
//+------------------------------------------------------------------+
string CEvent::TypeOrderDescription(void) const
  {
   ENUM_EVENT_STATUS status=this.Status();
   return
     (
      status==EVENT_STATUS_MARKET_PENDING  || status==EVENT_STATUS_HISTORY_PENDING  ?  OrderTypeDescription((ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORDER_EVENT))      :
      status==EVENT_STATUS_MARKET_POSITION || status==EVENT_STATUS_HISTORY_POSITION ?  PositionTypeDescription((ENUM_POSITION_TYPE)this.GetProperty(EVENT_PROP_TYPE_DEAL_EVENT)) :
      status==EVENT_STATUS_BALANCE  ?  DealTypeDescription((ENUM_DEAL_TYPE)this.GetProperty(EVENT_PROP_TYPE_DEAL_EVENT))  :  "Unknown"
     );
  }
//+------------------------------------------------------------------+
//| 返回父订单的名称                                                    |
//+------------------------------------------------------------------+
string CEvent::TypeOrderBasedDescription(void) const
  {
   return OrderTypeDescription((ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORDER_POSITION));
  }
//+------------------------------------------------------------------+
//| 返回仓位名称                                                        |
//+------------------------------------------------------------------+
string CEvent::TypePositionDescription(void) const
  {
   ENUM_POSITION_TYPE type=PositionTypeByOrderType((ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORDER_POSITION));
   return PositionTypeDescription(type);
  }
//+------------------------------------------------------------------+
//| 返回成交/订单/仓位原因的名称                                          |
//+------------------------------------------------------------------+
string CEvent::ReasonDescription(void) const
  {
   ENUM_EVENT_REASON reason=this.Reason();
   return 
     (
      reason==EVENT_REASON_ACTIVATED_PENDING                ?  TextByLanguage("Активирован отложенный ордер","Pending order activated")                           :
      reason==EVENT_REASON_ACTIVATED_PENDING_PARTIALLY      ?  TextByLanguage("Частичное срабатывание отложенного ордера","Pending order partially triggered")    :
      reason==EVENT_REASON_CANCEL                           ?  TextByLanguage("Отмена","Canceled")                                                                :
      reason==EVENT_REASON_EXPIRED                          ?  TextByLanguage("Истёк срок действия","Expired")                                                    :
      reason==EVENT_REASON_DONE                             ?  TextByLanguage("Запрос выполнен полностью","Request fully executed")                            :
      reason==EVENT_REASON_DONE_PARTIALLY                   ?  TextByLanguage("Запрос выполнен частично","Request partially executed")                         :
      reason==EVENT_REASON_DONE_SL                          ?  TextByLanguage("закрытие по StopLoss","Close by StopLoss triggered")                               :
      reason==EVENT_REASON_DONE_SL_PARTIALLY                ?  TextByLanguage("Частичное закрытие по StopLoss","Partial close by StopLoss triggered")             :
      reason==EVENT_REASON_DONE_TP                          ?  TextByLanguage("закрытие по TakeProfit","Close by TakeProfit triggered")                           :
      reason==EVENT_REASON_DONE_TP_PARTIALLY                ?  TextByLanguage("Частичное закрытие по TakeProfit","Partial close by TakeProfit triggered")         :
      reason==EVENT_REASON_DONE_BY_POS                      ?  TextByLanguage("Закрытие встречной позицией","Closed by opposite position")                        :
      reason==EVENT_REASON_DONE_PARTIALLY_BY_POS            ?  TextByLanguage("Частичное закрытие встречной позицией","Closed partially by opposite position")    :
      reason==EVENT_REASON_DONE_BY_POS_PARTIALLY            ?  TextByLanguage("Закрытие частью объёма встречной позиции","Closed by incomplete volume of opposite position") :
      reason==EVENT_REASON_DONE_PARTIALLY_BY_POS_PARTIALLY  ?  TextByLanguage("Частичное закрытие частью объёма встречной позиции","Closed partially by incomplete volume of opposite position")  :
      reason==EVENT_REASON_BALANCE_REFILL                   ?  TextByLanguage("Пополнение баланса","Balance refill")                                              :
      reason==EVENT_REASON_BALANCE_WITHDRAWAL               ?  TextByLanguage("Снятие средств с баланса","Withdrawals from balance")                          :
      reason==EVENT_REASON_ACCOUNT_CREDIT                   ?  TextByLanguage("Начисление кредита","Credit")                                                      :
      reason==EVENT_REASON_ACCOUNT_CHARGE                   ?  TextByLanguage("Дополнительные сборы","Additional charge")                                         :
      reason==EVENT_REASON_ACCOUNT_CORRECTION               ?  TextByLanguage("Корректирующая запись","Correction")                                               :
      reason==EVENT_REASON_ACCOUNT_BONUS                    ?  TextByLanguage("Перечисление бонусов","Bonus")                                                     :
      reason==EVENT_REASON_ACCOUNT_COMISSION                ?  TextByLanguage("Дополнительные комиссии","Additional commission")                                  :
      reason==EVENT_REASON_ACCOUNT_COMISSION_DAILY          ?  TextByLanguage("Комиссия, начисляемая в конце торгового дня","Daily commission")                   :
      reason==EVENT_REASON_ACCOUNT_COMISSION_MONTHLY        ?  TextByLanguage("Комиссия, начисляемая в конце месяца","Monthly commission")                        :
      reason==EVENT_REASON_ACCOUNT_COMISSION_AGENT_DAILY    ?  TextByLanguage("Агентская комиссия, начисляемая в конце торгового дня","Daily agent commission")   :
      reason==EVENT_REASON_ACCOUNT_COMISSION_AGENT_MONTHLY  ?  TextByLanguage("Агентская комиссия, начисляемая в конце месяца","Monthly agent commission")        :
      reason==EVENT_REASON_ACCOUNT_INTEREST                 ?  TextByLanguage("Начисления процентов на свободные средства","Interest rate")                       :
      reason==EVENT_REASON_BUY_CANCELLED                    ?  TextByLanguage("Отмененная сделка покупки","Canceled buy deal")                                    :
      reason==EVENT_REASON_SELL_CANCELLED                   ?  TextByLanguage("Отмененная сделка продажи","Canceled sell deal")                                   :
      reason==EVENT_REASON_DIVIDENT                         ?  TextByLanguage("Начисление дивиденда","Dividend operations")                                       :
      reason==EVENT_REASON_DIVIDENT_FRANKED                 ?  TextByLanguage("Начисление франкированного дивиденда","Franked (non-taxable) dividend operations") :
      reason==EVENT_REASON_TAX                              ?  TextByLanguage("Начисление налога","Tax charges")                                                  :
      EnumToString(reason)
     );
  }
//+------------------------------------------------------------------+
//| 在流水帐中显示事件属性                                                |
//+------------------------------------------------------------------+
void CEvent::Print(const bool full_prop=false)
  {
   ::Print("============= ",TextByLanguage("Начало списка параметров события: \"","Beginning of event parameter list: \""),this.StatusDescription(),"\" =============");
   int beg=0, end=EVENT_PROP_INTEGER_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_EVENT_PROP_INTEGER prop=(ENUM_EVENT_PROP_INTEGER)i;
      if(!full_prop && !this.SupportProperty(prop)) continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("------");
   beg=end; end+=EVENT_PROP_DOUBLE_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_EVENT_PROP_DOUBLE prop=(ENUM_EVENT_PROP_DOUBLE)i;
      if(!full_prop && !this.SupportProperty(prop)) continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("------");
   beg=end; end+=EVENT_PROP_STRING_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_EVENT_PROP_STRING prop=(ENUM_EVENT_PROP_STRING)i;
      if(!full_prop && !this.SupportProperty(prop)) continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("================== ",TextByLanguage("Конец списка параметров: \"","End of parameter list: \""),this.StatusDescription(),"\" ==================\n");
  }
//+------------------------------------------------------------------+

所有这些方法的逻辑类似于已讲述过的订单数据输出方法之一。 所以,我们不会专注于它们,因为这里的一切都非常简单,且可直观理解。

完整的事件类列表:

//+------------------------------------------------------------------+
//|                                                        Event.mqh |
//|                                  版权所有 2018, MetaQuotes 软件公司 |
//|                             https://mql5.com/zh/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "版权所有 2018, MetaQuotes 软件公司"
#property link      "https://mql5.com/zh/users/artmedia70"
#property version   "1.00"
#property strict    // 对于 mql4 是必须的
//+------------------------------------------------------------------+
//| 包含文件                                                           |
//+------------------------------------------------------------------+
#include <Object.mqh>
#include "\..\..\Services\DELib.mqh"
#include "..\..\Collections\HistoryCollection.mqh"
#include "..\..\Collections\MarketCollection.mqh"
//+------------------------------------------------------------------+
//| 抽象事件类                                                         |
//+------------------------------------------------------------------+
class CEvent : public CObject
  {
private:
   int               m_event_code;                                   // 事件代码
//--- 返回事件的(1)实数型,和(2)字符串型属性在数组当中的位置索引
   int               IndexProp(ENUM_EVENT_PROP_DOUBLE property)const { return(int)property-EVENT_PROP_INTEGER_TOTAL;                         }
   int               IndexProp(ENUM_EVENT_PROP_STRING property)const { return(int)property-EVENT_PROP_INTEGER_TOTAL-EVENT_PROP_DOUBLE_TOTAL; }
protected:
   ENUM_TRADE_EVENT  m_trade_event;                                  // 交易事件
   long              m_chart_id;                                     // 控制程序所在的图表 ID
   int               m_digits_acc;                                   // 帐户货币的小数位数
   long              m_long_prop[EVENT_PROP_INTEGER_TOTAL];          // 事件整数型属性
   double            m_double_prop[EVENT_PROP_DOUBLE_TOTAL];         // 事件实数型属性
   string            m_string_prop[EVENT_PROP_STRING_TOTAL];         // 事件字符串型属性
//--- 返回交易事件中的存在标志
   bool              IsPresentEventFlag(const int event_code)  const { return (this.m_event_code & event_code)==event_code;            }

   //--- 受保护的参数构造函数
                     CEvent(const ENUM_EVENT_STATUS event_status,const int event_code,const ulong ticket);
public:
//--- 默认构造函数
                     CEvent(void){;}
 
//--- 设置事件的 (1) 整数型, (2) 实数型,和 (3) 字符串型属性
   void              SetProperty(ENUM_EVENT_PROP_INTEGER property,long value) { this.m_long_prop[property]=value;                      }
   void              SetProperty(ENUM_EVENT_PROP_DOUBLE property,double value){ this.m_double_prop[this.IndexProp(property)]=value;    }
   void              SetProperty(ENUM_EVENT_PROP_STRING property,string value){ this.m_string_prop[this.IndexProp(property)]=value;    }
//--- 从属性数组中返回事件的(1)整数型,(2)实数型,和(3)字符串属性
   long              GetProperty(ENUM_EVENT_PROP_INTEGER property)      const { return this.m_long_prop[property];                     }
   double            GetProperty(ENUM_EVENT_PROP_DOUBLE property)       const { return this.m_double_prop[this.IndexProp(property)];   }
   string            GetProperty(ENUM_EVENT_PROP_STRING property)       const { return this.m_string_prop[this.IndexProp(property)];   }

//--- 返回支持的事件属性标志
   virtual bool      SupportProperty(ENUM_EVENT_PROP_INTEGER property)        { return true; }
   virtual bool      SupportProperty(ENUM_EVENT_PROP_DOUBLE property)         { return true; }
   virtual bool      SupportProperty(ENUM_EVENT_PROP_STRING property)         { return true; }

//--- 设置控制程序所在的图表 ID
   void              SetChartID(const long id)                                { this.m_chart_id=id;                                    }
//--- 解码事件代码,并设置交易事件,(2)返回交易事件
   void              SetTypeEvent(void);
   ENUM_TRADE_EVENT  TradeEvent(void)                                   const { return this.m_trade_event;                             }
//--- 将事件发送到图表(子类中实现)
   virtual void      SendEvent(void) {;}

//--- 按指定的属性比较 CEvent 对象(按指定的事件对象属性为列表进行排序)
   virtual int       Compare(const CObject *node,const int mode=0) const;
//--- 按所有属性比较 CEvent 对象(搜索相等的事件对象)
   bool              IsEqual(CEvent* compared_event);
//+------------------------------------------------------------------+
//| 简化的访问事件对象属性的方法                                           |
//+------------------------------------------------------------------+
//--- 返回 (1) 事件类型, (2) 事件时间的毫秒值, (3) 事件状态, (4) 事件原因, (5) 成交类型, (6) 成交票据, 
//--- (7) 成交执行所依据的订单类型, (8) 开仓订单类型, (9) 开仓时最后一笔订单票据, 
//--- (10) 开仓时首笔订单票据, (11) 仓位 ID, (12) 逆向仓位 ID, (13) 魔幻数字, (14) 开仓时间

   ENUM_TRADE_EVENT  TypeEvent(void)                                    const { return (ENUM_TRADE_EVENT)this.GetProperty(EVENT_PROP_TYPE_EVENT);     }
   long              TimeEvent(void)                                    const { return this.GetProperty(EVENT_PROP_TIME_EVENT);                       }
   ENUM_EVENT_STATUS Status(void)                                       const { return (ENUM_EVENT_STATUS)this.GetProperty(EVENT_PROP_STATUS_EVENT);  }
   ENUM_EVENT_REASON Reason(void)                                       const { return (ENUM_EVENT_REASON)this.GetProperty(EVENT_PROP_REASON_EVENT);  }
   long              TypeDeal(void)                                     const { return this.GetProperty(EVENT_PROP_TYPE_DEAL_EVENT);                  }
   long              TicketDeal(void)                                   const { return this.GetProperty(EVENT_PROP_TICKET_DEAL_EVENT);                }
   long              TypeOrderEvent(void)                               const { return this.GetProperty(EVENT_PROP_TYPE_ORDER_EVENT);                 }
   long              TypeOrderPosition(void)                            const { return this.GetProperty(EVENT_PROP_TYPE_ORDER_POSITION);              }
   long              TicketOrderEvent(void)                             const { return this.GetProperty(EVENT_PROP_TICKET_ORDER_EVENT);               }
   long              TicketOrderPosition(void)                          const { return this.GetProperty(EVENT_PROP_TICKET_ORDER_POSITION);            }
   long              PositionID(void)                                   const { return this.GetProperty(EVENT_PROP_POSITION_ID);                      }
   long              PositionByID(void)                                 const { return this.GetProperty(EVENT_PROP_POSITION_BY_ID);                   }
   long              Magic(void)                                        const { return this.GetProperty(EVENT_PROP_MAGIC_ORDER);                      }
   long              TimePosition(void)                                 const { return this.GetProperty(EVENT_PROP_TIME_ORDER_POSITION);              }
   
//--- 返回 (1) 事件发生时的价格, (2) 开单价, (3) 平单价,
//--- (4) 止损价, (5) 止盈价, (6) 盈利, (7) 请求的交易量, (8), 执行的交易量, (9) 剩余的交易量
   double            PriceEvent(void)                                   const { return this.GetProperty(EVENT_PROP_PRICE_EVENT);                      }
   double            PriceOpen(void)                                    const { return this.GetProperty(EVENT_PROP_PRICE_OPEN);                       }
   double            PriceClose(void)                                   const { return this.GetProperty(EVENT_PROP_PRICE_CLOSE);                      }
   double            PriceStopLoss(void)                                const { return this.GetProperty(EVENT_PROP_PRICE_SL);                         }
   double            PriceTakeProfit(void)                              const { return this.GetProperty(EVENT_PROP_PRICE_TP);                         }
   double            Profit(void)                                       const { return this.GetProperty(EVENT_PROP_PROFIT);                           }
   double            VolumeInitial(void)                                const { return this.GetProperty(EVENT_PROP_VOLUME_INITIAL);                   }
   double            VolumeExecuted(void)                               const { return this.GetProperty(EVENT_PROP_VOLUME_EXECUTED);                  }
   double            VolumeCurrent(void)                                const { return this.GetProperty(EVENT_PROP_VOLUME_CURRENT);                   }
   
//--- 返回品种
   string            Symbol(void)                                       const { return this.GetProperty(EVENT_PROP_SYMBOL);                           }
   
//+------------------------------------------------------------------+
//| 订单对象属性的描述                                                   |
//+------------------------------------------------------------------+
//--- 返回订单(1)整数型,(2)实数型,和(3)字符串型属性的描述
   string            GetPropertyDescription(ENUM_EVENT_PROP_INTEGER property);
   string            GetPropertyDescription(ENUM_EVENT_PROP_DOUBLE property);
   string            GetPropertyDescription(ENUM_EVENT_PROP_STRING property);
//--- 返回事件 (1) 状态,和 (2) 类型
   string            StatusDescription(void)          const;
   string            TypeEventDescription(void)       const;
//--- 返回 (1) 订单/仓位/成交, (2) 父订单, (3) 仓位的名称
   string            TypeOrderDescription(void)       const;
   string            TypeOrderBasedDescription(void)  const;
   string            TypePositionDescription(void)    const;
//--- 返回成交/订单/仓位原因的名称
   string            ReasonDescription(void)          const;

//--- 显示 (1) 订单属性描述 (full_prop=true - 所有属性, false - 仅支持的),
//--- (2) 流水帐里输出简要事件消息 (在子类中实现)
   void              Print(const bool full_prop=false);
   virtual void      PrintShort(void) {;}
  };
//+------------------------------------------------------------------+
//| 构造函数                                                            |
//+------------------------------------------------------------------+
CEvent::CEvent(const ENUM_EVENT_STATUS event_status,const int event_code,const ulong ticket) : m_event_code(event_code)
  {
   this.m_long_prop[EVENT_PROP_STATUS_EVENT]       =  event_status;
   this.m_long_prop[EVENT_PROP_TICKET_ORDER_EVENT] =  (long)ticket;
   this.m_digits_acc=(int)::AccountInfoInteger(ACCOUNT_CURRENCY_DIGITS);
   this.m_chart_id=::ChartID();
  }
//+------------------------------------------------------------------+
//| 按指定的属性比较 CEvent 对象                                          |
//+------------------------------------------------------------------+
int CEvent::Compare(const CObject *node,const int mode=0) const
  {
   const CEvent *event_compared=node;
//--- 比较两个事件的整数型属性
   if(mode<EVENT_PROP_INTEGER_TOTAL)
     {
      long value_compared=event_compared.GetProperty((ENUM_EVENT_PROP_INTEGER)mode);
      long value_current=this.GetProperty((ENUM_EVENT_PROP_INTEGER)mode);
      return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0);
     }
//--- 比较两个事件的实数型属性
   if(mode<EVENT_PROP_DOUBLE_TOTAL+EVENT_PROP_INTEGER_TOTAL)
     {
      double value_compared=event_compared.GetProperty((ENUM_EVENT_PROP_DOUBLE)mode);
      double value_current=this.GetProperty((ENUM_EVENT_PROP_DOUBLE)mode);
      return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0);
     }
//--- 比较两个事件的字符串型属性
   else if(mode<EVENT_PROP_DOUBLE_TOTAL+EVENT_PROP_INTEGER_TOTAL+EVENT_PROP_STRING_TOTAL)
     {
      string value_compared=event_compared.GetProperty((ENUM_EVENT_PROP_STRING)mode);
      string value_current=this.GetProperty((ENUM_EVENT_PROP_STRING)mode);
      return(value_current>value_compared ? 1 : value_current<value_compared ? -1 : 0);
     }
   return 0;
  }
//+------------------------------------------------------------------+
//| 按所有属性比较 CEvent 对象                                           |
//+------------------------------------------------------------------+
bool CEvent::IsEqual(CEvent *compared_event)
  {
   int beg=0, end=EVENT_PROP_INTEGER_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_EVENT_PROP_INTEGER prop=(ENUM_EVENT_PROP_INTEGER)i;
      if(this.GetProperty(prop)!=compared_event.GetProperty(prop)) return false; 
     }
   beg=end; end+=EVENT_PROP_DOUBLE_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_EVENT_PROP_DOUBLE prop=(ENUM_EVENT_PROP_DOUBLE)i;
      if(this.GetProperty(prop)!=compared_event.GetProperty(prop)) return false; 
     }
   beg=end; end+=EVENT_PROP_STRING_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_EVENT_PROP_STRING prop=(ENUM_EVENT_PROP_STRING)i;
      if(this.GetProperty(prop)!=compared_event.GetProperty(prop)) return false; 
     }
//---
   return true;
  }
//+------------------------------------------------------------------+
//| 解码事件代码并设置交易事件                                            |
//+------------------------------------------------------------------+
void CEvent::SetTypeEvent(void)
  {
//--- 已下挂单(检查事件代码是否匹配,因为此处只能有一个标志)
   if(this.m_event_code==TRADE_EVENT_FLAG_ORDER_PLASED)
     {
      this.m_trade_event=TRADE_EVENT_PENDING_ORDER_PLASED;
      this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
      return;
     }
//--- 已删除挂单(检查事件代码是否匹配,因为此处只能有一个标志)
   if(this.m_event_code==TRADE_EVENT_FLAG_ORDER_REMOVED)
     {
      this.m_trade_event=TRADE_EVENT_PENDING_ORDER_REMOVED;
      this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
      return;
     }
//--- 开仓(检查事件代码中是否存在多个标志)
   if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_POSITION_OPENED))
     {
      //--- 如果挂单被价格激活
      if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_ORDER_ACTIVATED))
        {
         //--- 检查部分平仓标志,并设置“挂单激活”或“挂单部分激活”交易事件
         this.m_trade_event=(!this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_PENDING_ORDER_ACTIVATED : TRADE_EVENT_PENDING_ORDER_ACTIVATED_PARTIAL);
         this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
         return;
        }
      //--- 检查部分开仓标志,并设置“开仓”或“部分开仓”交易事件
      this.m_trade_event=(!this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_OPENED : TRADE_EVENT_POSITION_OPENED_PARTIAL);
      this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
      return;
     }
//--- 平仓(检查事件代码中是否存在多个标志)
   if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_POSITION_CLOSED))
     {
      //--- 如果由止损平仓
      if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_SL))
        {
         //--- 检查部分平仓标志,并设置“由止损平仓”或“由止损部分平仓”交易事件
         this.m_trade_event=(!this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_CLOSED_BY_SL : TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_SL);
         this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
         return;
        }
      //--- 如果由止盈平仓
      else if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_TP))
        {
         //--- 检查部分平仓标志,并设置“由止盈平仓”或“由止盈部分平仓”交易事件
         this.m_trade_event=(!this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_CLOSED_BY_TP : TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_TP);
         this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
         return;
        }
      //--- 如果一笔仓位由逆向仓位平仓
      else if(this.IsPresentEventFlag(TRADE_EVENT_FLAG_BY_POS))
        {
         //--- 检查部分平仓标志,并设置“由逆向仓位平仓”或“由逆向仓位部分平仓”交易事件
         this.m_trade_event=(!this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_CLOSED_BY_POS : TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS);
         this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
         return;
        }
      //--- 如果已平仓
      else
        {
         //--- 检查部分平仓标志,并设置“已平仓”或“已部分平仓”交易事件
         this.m_trade_event=(!this.IsPresentEventFlag(TRADE_EVENT_FLAG_PARTIAL) ? TRADE_EVENT_POSITION_CLOSED : TRADE_EVENT_POSITION_CLOSED_PARTIAL);
         this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
         return;
        }
     }
//--- 账户上的余额操作(按成交类型澄清事件)
   if(this.m_event_code==TRADE_EVENT_FLAG_ACCOUNT_BALANCE)
     {
      //--- 初始化交易事件
      this.m_trade_event=TRADE_EVENT_NO_EVENT;
      //--- 获取成交类型
      ENUM_DEAL_TYPE deal_type=(ENUM_DEAL_TYPE)this.GetProperty(EVENT_PROP_TYPE_DEAL_EVENT);
      //--- 如果成交是余额操作
      if(deal_type==DEAL_TYPE_BALANCE)
        {
        //--- 检查成交利润,并设置事件(入金或出金)
         this.m_trade_event=(this.GetProperty(EVENT_PROP_PROFIT)>0 ? TRADE_EVENT_ACCOUNT_BALANCE_REFILL : TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL);
        }
      //--- The remaining balance operation types match the ENUM_DEAL_TYPE enumeration starting with DEAL_TYPE_CREDIT
      else if(deal_type>DEAL_TYPE_BALANCE)
        {
        //--- 设置事件
         this.m_trade_event=(ENUM_TRADE_EVENT)deal_type;
        }
      this.SetProperty(EVENT_PROP_TYPE_EVENT,this.m_trade_event);
      return;
     }
  }
//+------------------------------------------------------------------+
//| 返回事件的整数型属性的描述                                            |
//+------------------------------------------------------------------+
string CEvent::GetPropertyDescription(ENUM_EVENT_PROP_INTEGER property)
  {
   return
     (
      property==EVENT_PROP_TYPE_EVENT              ?  TextByLanguage("Тип события","Event type")+": "+this.TypeEventDescription()                                                       :
      property==EVENT_PROP_TIME_EVENT              ?  TextByLanguage("Время события","Time of event")+": "+TimeMSCtoString(this.GetProperty(property))                                    :
      property==EVENT_PROP_STATUS_EVENT            ?  TextByLanguage("Статус события","Status of event")+": \""+this.StatusDescription()+"\""                                             :
      property==EVENT_PROP_REASON_EVENT            ?  TextByLanguage("Причина события","Reason of event")+": "+this.ReasonDescription()                                                   :
      property==EVENT_PROP_TYPE_DEAL_EVENT         ?  TextByLanguage("Тип сделки","Deal's type")+": "+DealTypeDescription((ENUM_DEAL_TYPE)this.GetProperty(property))                     :
      property==EVENT_PROP_TICKET_DEAL_EVENT       ?  TextByLanguage("Тикет сделки","Deal's ticket")+" #"+(string)this.GetProperty(property)                                              :
      property==EVENT_PROP_TYPE_ORDER_EVENT        ?  TextByLanguage("Тип ордера события","Event's order type")+": "+OrderTypeDescription((ENUM_ORDER_TYPE)this.GetProperty(property))    :
      property==EVENT_PROP_TYPE_ORDER_POSITION     ?  TextByLanguage("Тип ордера позиции","Position's order type")+": "+OrderTypeDescription((ENUM_ORDER_TYPE)this.GetProperty(property)) :
      property==EVENT_PROP_TICKET_ORDER_POSITION   ?  TextByLanguage("Тикет первого ордера позиции","Position's first order ticket")+" #"+(string)this.GetProperty(property)              :
      property==EVENT_PROP_TICKET_ORDER_EVENT      ?  TextByLanguage("Тикет ордера события","Event's order ticket")+" #"+(string)this.GetProperty(property)                               :
      property==EVENT_PROP_POSITION_ID             ?  TextByLanguage("Идентификатор позиции","Position ID")+" #"+(string)this.GetProperty(property)                                       :
      property==EVENT_PROP_POSITION_BY_ID          ?  TextByLanguage("Идентификатор встречной позиции","Opposite position's ID")+" #"+(string)this.GetProperty(property)                  :
      property==EVENT_PROP_MAGIC_ORDER             ?  TextByLanguage("Магический номер","Magic number")+": "+(string)this.GetProperty(property)                                           :
      property==EVENT_PROP_TIME_ORDER_POSITION     ?  TextByLanguage("Время открытия позиции","Position's opened time")+": "+TimeMSCtoString(this.GetProperty(property))                  :
      ""
     );
  }
//+------------------------------------------------------------------+
//| 返回事件的实数型属性的描述                                            |
//+------------------------------------------------------------------+
string CEvent::GetPropertyDescription(ENUM_EVENT_PROP_DOUBLE property)
  {
   int dg=(int)::SymbolInfoInteger(this.GetProperty(EVENT_PROP_SYMBOL),SYMBOL_DIGITS);
   int dgl=(int)DigitsLots(this.GetProperty(EVENT_PROP_SYMBOL));
   return
     (
      property==EVENT_PROP_PRICE_EVENT       ?  TextByLanguage("Цена события","Price at the time of event")+": "+::DoubleToString(this.GetProperty(property),dg) :
      property==EVENT_PROP_PRICE_OPEN        ?  TextByLanguage("Цена открытия","Open price")+": "+::DoubleToString(this.GetProperty(property),dg)                    :
      property==EVENT_PROP_PRICE_CLOSE       ?  TextByLanguage("Цена закрытия","Close price")+": "+::DoubleToString(this.GetProperty(property),dg)                   :
      property==EVENT_PROP_PRICE_SL          ?  TextByLanguage("Цена StopLoss","StopLoss price")+": "+::DoubleToString(this.GetProperty(property),dg)                :
      property==EVENT_PROP_PRICE_TP          ?  TextByLanguage("Цена TakeProfit","TakeProfit price")+": "+::DoubleToString(this.GetProperty(property),dg)            :
      property==EVENT_PROP_VOLUME_INITIAL    ?  TextByLanguage("Начальный объём","Initial volume")+": "+::DoubleToString(this.GetProperty(property),dgl)             :
      property==EVENT_PROP_VOLUME_EXECUTED   ?  TextByLanguage("Исполненный объём","Executed volume")+": "+::DoubleToString(this.GetProperty(property),dgl)          :
      property==EVENT_PROP_VOLUME_CURRENT    ?  TextByLanguage("Оставшийся объём","Remaining volume")+": "+::DoubleToString(this.GetProperty(property),dgl)          :
      property==EVENT_PROP_PROFIT            ?  TextByLanguage("Профит","Profit")+": "+::DoubleToString(this.GetProperty(property),this.m_digits_acc)                :
      ""
     );
  }
//+------------------------------------------------------------------+
//| 返回事件的字符串型属性的描述                                           |
//+------------------------------------------------------------------+
string CEvent::GetPropertyDescription(ENUM_EVENT_PROP_STRING property)
  {
   return TextByLanguage("Символ","Symbol")+": \""+this.GetProperty(property)+"\"";
  }
//+------------------------------------------------------------------+
//| 返回事件状态名                                                       |
//+------------------------------------------------------------------+
string CEvent::StatusDescription(void) const
  {
   ENUM_EVENT_STATUS status=(ENUM_EVENT_STATUS)this.GetProperty(EVENT_PROP_STATUS_EVENT);
   return
     (
      status==EVENT_STATUS_MARKET_PENDING    ?  TextByLanguage("Установлен отложенный ордер","Pending order placed") :
      status==EVENT_STATUS_MARKET_POSITION   ?  TextByLanguage("Открыта позиция","Position opened")                 :
      status==EVENT_STATUS_HISTORY_PENDING   ?  TextByLanguage("Удален отложенный ордер","Pending order removed")    :
      status==EVENT_STATUS_HISTORY_POSITION  ?  TextByLanguage("Закрыта позиция","Position closed")                  :
      status==EVENT_STATUS_BALANCE           ?  TextByLanguage("Балансная операция","Balance operation")             :
      ""
     );
  }
//+------------------------------------------------------------------+
//| 返回交易事件名                                                         |
//+------------------------------------------------------------------+
string CEvent::TypeEventDescription(void) const
  {
   ENUM_TRADE_EVENT event=this.TypeEvent();
   return
     (
      event==TRADE_EVENT_PENDING_ORDER_PLASED            ?  TextByLanguage("Отложенный ордер установлен","Pending order placed")                                  :
      event==TRADE_EVENT_PENDING_ORDER_REMOVED           ?  TextByLanguage("Отложенный ордер удалён","Pending order removed")                                     :
      event==TRADE_EVENT_ACCOUNT_CREDIT                  ?  TextByLanguage("Начисление кредита","Credit")                                                         :
      event==TRADE_EVENT_ACCOUNT_CHARGE                  ?  TextByLanguage("Дополнительные сборы","Additional charge")                                            :
      event==TRADE_EVENT_ACCOUNT_CORRECTION              ?  TextByLanguage("Корректирующая запись","Correction")                                                  :
      event==TRADE_EVENT_ACCOUNT_BONUS                   ?  TextByLanguage("Перечисление бонусов","Bonus")                                                        :
      event==TRADE_EVENT_ACCOUNT_COMISSION               ?  TextByLanguage("Дополнительные комиссии","Additional commission")                                     :
      event==TRADE_EVENT_ACCOUNT_COMISSION_DAILY         ?  TextByLanguage("Комиссия, начисляемая в конце торгового дня","Daily commission")                      :
      event==TRADE_EVENT_ACCOUNT_COMISSION_MONTHLY       ?  TextByLanguage("Комиссия, начисляемая в конце месяца","Monthly commission")                           :
      event==TRADE_EVENT_ACCOUNT_COMISSION_AGENT_DAILY   ?  TextByLanguage("Агентская комиссия, начисляемая в конце торгового дня","Daily agent commission")      :
      event==TRADE_EVENT_ACCOUNT_COMISSION_AGENT_MONTHLY ?  TextByLanguage("Агентская комиссия, начисляемая в конце месяца","Monthly agent commission")           :
      event==TRADE_EVENT_ACCOUNT_INTEREST                ?  TextByLanguage("Начисления процентов на свободные средства","Interest rate")                          :
      event==TRADE_EVENT_BUY_CANCELLED                   ?  TextByLanguage("Отмененная сделка покупки","Canceled buy deal")                                       :
      event==TRADE_EVENT_SELL_CANCELLED                  ?  TextByLanguage("Отмененная сделка продажи","Canceled sell deal")                                      :
      event==TRADE_EVENT_DIVIDENT                        ?  TextByLanguage("Начисление дивиденда","Dividend operations")                                          :
      event==TRADE_EVENT_DIVIDENT_FRANKED                ?  TextByLanguage("Начисление франкированного дивиденда","Franked (non-taxable) dividend operations")    :
      event==TRADE_EVENT_TAX                             ?  TextByLanguage("Начисление налога","Tax charges")                                                     :
      event==TRADE_EVENT_ACCOUNT_BALANCE_REFILL          ?  TextByLanguage("Пополнение средств на балансе","Balance refill")                                      :
      event==TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL      ?  TextByLanguage("Снятие средств с баланса","Withdrawals")                                              :
      event==TRADE_EVENT_PENDING_ORDER_ACTIVATED         ?  TextByLanguage("Отложенный ордер активирован ценой","Pending order activated")                        :
      event==TRADE_EVENT_PENDING_ORDER_ACTIVATED_PARTIAL ?  TextByLanguage("Отложенный ордер активирован ценой частично","Pending order activated partially")     :
      event==TRADE_EVENT_POSITION_OPENED                 ?  TextByLanguage("Позиция открыта","Position opened")                                                  :
      event==TRADE_EVENT_POSITION_OPENED_PARTIAL         ?  TextByLanguage("Позиция открыта частично","Position opened partially")                               :
      event==TRADE_EVENT_POSITION_CLOSED                 ?  TextByLanguage("Позиция закрыта","Position closed")                                                   :
      event==TRADE_EVENT_POSITION_CLOSED_PARTIAL         ?  TextByLanguage("Позиция закрыта частично","Position closed partially")                                :
      event==TRADE_EVENT_POSITION_CLOSED_BY_POS          ?  TextByLanguage("Позиция закрыта встречной","Position closed by opposite position")                    :
      event==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS  ?  TextByLanguage("Позиция закрыта встречной частично","Position closed partially by opposite position") :
      event==TRADE_EVENT_POSITION_CLOSED_BY_SL           ?  TextByLanguage("Позиция закрыта по StopLoss","Position closed by StopLoss")                           :
      event==TRADE_EVENT_POSITION_CLOSED_BY_TP           ?  TextByLanguage("Позиция закрыта по TakeProfit","Position closed by TakeProfit")                       :
      event==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_SL   ?  TextByLanguage("Позиция закрыта частично по StopLoss","Position closed partially by StopLoss")        :
      event==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_TP   ?  TextByLanguage("Позиция закрыта частично по TakeProfit","Position closed partially by TakeProfit")    :
      event==TRADE_EVENT_POSITION_REVERSED               ?  TextByLanguage("Разворот позиции","Position reversal")                                                :
      event==TRADE_EVENT_POSITION_VOLUME_ADD             ?  TextByLanguage("Добавлен объём к позиции","Added volume to position")                                 :
      TextByLanguage("Нет торгового события","No trade event")
     );   
  }
//+------------------------------------------------------------------+
//| 返回订单/仓位/成交的名称                                             |
//+------------------------------------------------------------------+
string CEvent::TypeOrderDescription(void) const
  {
   ENUM_EVENT_STATUS status=this.Status();
   return
     (
      status==EVENT_STATUS_MARKET_PENDING  || status==EVENT_STATUS_HISTORY_PENDING  ?  OrderTypeDescription((ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORDER_EVENT))      :
      status==EVENT_STATUS_MARKET_POSITION || status==EVENT_STATUS_HISTORY_POSITION ?  PositionTypeDescription((ENUM_POSITION_TYPE)this.GetProperty(EVENT_PROP_TYPE_DEAL_EVENT)) :
      status==EVENT_STATUS_BALANCE  ?  DealTypeDescription((ENUM_DEAL_TYPE)this.GetProperty(EVENT_PROP_TYPE_DEAL_EVENT))  :  "Unknown"
     );
  }
//+------------------------------------------------------------------+
//| 返回父订单的名称                                                    |
//+------------------------------------------------------------------+
string CEvent::TypeOrderBasedDescription(void) const
  {
   return OrderTypeDescription((ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORDER_POSITION));
  }
//+------------------------------------------------------------------+
//| 返回仓位名称                                                        |
//+------------------------------------------------------------------+
string CEvent::TypePositionDescription(void) const
  {
   ENUM_POSITION_TYPE type=PositionTypeByOrderType((ENUM_ORDER_TYPE)this.GetProperty(EVENT_PROP_TYPE_ORDER_POSITION));
   return PositionTypeDescription(type);
  }
//+------------------------------------------------------------------+
//| 返回成交/订单/仓位原因的名称                                          |
//+------------------------------------------------------------------+
string CEvent::ReasonDescription(void) const
  {
   ENUM_EVENT_REASON reason=this.Reason();
   return 
     (
      reason==EVENT_REASON_ACTIVATED_PENDING                ?  TextByLanguage("Активирован отложенный ордер","Pending order activated")                           :
      reason==EVENT_REASON_ACTIVATED_PENDING_PARTIALLY      ?  TextByLanguage("Частичное срабатывание отложенного ордера","Pending order partially triggered")    :
      reason==EVENT_REASON_CANCEL                           ?  TextByLanguage("Отмена","Canceled")                                                                :
      reason==EVENT_REASON_EXPIRED                          ?  TextByLanguage("Истёк срок действия","Expired")                                                    :
      reason==EVENT_REASON_DONE                             ?  TextByLanguage("Запрос выполнен полностью","Request fully executed")                            :
      reason==EVENT_REASON_DONE_PARTIALLY                   ?  TextByLanguage("Запрос выполнен частично","Request partially executed")                         :
      reason==EVENT_REASON_DONE_SL                          ?  TextByLanguage("закрытие по StopLoss","Close by StopLoss triggered")                               :
      reason==EVENT_REASON_DONE_SL_PARTIALLY                ?  TextByLanguage("Частичное закрытие по StopLoss","Partial close by StopLoss triggered")             :
      reason==EVENT_REASON_DONE_TP                          ?  TextByLanguage("закрытие по TakeProfit","Close by TakeProfit triggered")                           :
      reason==EVENT_REASON_DONE_TP_PARTIALLY                ?  TextByLanguage("Частичное закрытие по TakeProfit","Partial close by TakeProfit triggered")         :
      reason==EVENT_REASON_DONE_BY_POS                      ?  TextByLanguage("Закрытие встречной позицией","Closed by opposite position")                        :
      reason==EVENT_REASON_DONE_PARTIALLY_BY_POS            ?  TextByLanguage("Частичное закрытие встречной позицией","Closed partially by opposite position")    :
      reason==EVENT_REASON_DONE_BY_POS_PARTIALLY            ?  TextByLanguage("Закрытие частью объёма встречной позиции","Closed by incomplete volume of opposite position") :
      reason==EVENT_REASON_DONE_PARTIALLY_BY_POS_PARTIALLY  ?  TextByLanguage("Частичное закрытие частью объёма встречной позиции","Closed partially by incomplete volume of opposite position")  :
      reason==EVENT_REASON_BALANCE_REFILL                   ?  TextByLanguage("Пополнение баланса","Balance refill")                                              :
      reason==EVENT_REASON_BALANCE_WITHDRAWAL               ?  TextByLanguage("Снятие средств с баланса","Withdrawals from balance")                          :
      reason==EVENT_REASON_ACCOUNT_CREDIT                   ?  TextByLanguage("Начисление кредита","Credit")                                                      :
      reason==EVENT_REASON_ACCOUNT_CHARGE                   ?  TextByLanguage("Дополнительные сборы","Additional charge")                                         :
      reason==EVENT_REASON_ACCOUNT_CORRECTION               ?  TextByLanguage("Корректирующая запись","Correction")                                               :
      reason==EVENT_REASON_ACCOUNT_BONUS                    ?  TextByLanguage("Перечисление бонусов","Bonus")                                                     :
      reason==EVENT_REASON_ACCOUNT_COMISSION                ?  TextByLanguage("Дополнительные комиссии","Additional commission")                                  :
      reason==EVENT_REASON_ACCOUNT_COMISSION_DAILY          ?  TextByLanguage("Комиссия, начисляемая в конце торгового дня","Daily commission")                   :
      reason==EVENT_REASON_ACCOUNT_COMISSION_MONTHLY        ?  TextByLanguage("Комиссия, начисляемая в конце месяца","Monthly commission")                        :
      reason==EVENT_REASON_ACCOUNT_COMISSION_AGENT_DAILY    ?  TextByLanguage("Агентская комиссия, начисляемая в конце торгового дня","Daily agent commission")   :
      reason==EVENT_REASON_ACCOUNT_COMISSION_AGENT_MONTHLY  ?  TextByLanguage("Агентская комиссия, начисляемая в конце месяца","Monthly agent commission")        :
      reason==EVENT_REASON_ACCOUNT_INTEREST                 ?  TextByLanguage("Начисления процентов на свободные средства","Interest rate")                       :
      reason==EVENT_REASON_BUY_CANCELLED                    ?  TextByLanguage("Отмененная сделка покупки","Canceled buy deal")                                    :
      reason==EVENT_REASON_SELL_CANCELLED                   ?  TextByLanguage("Отмененная сделка продажи","Canceled sell deal")                                   :
      reason==EVENT_REASON_DIVIDENT                         ?  TextByLanguage("Начисление дивиденда","Dividend operations")                                       :
      reason==EVENT_REASON_DIVIDENT_FRANKED                 ?  TextByLanguage("Начисление франкированного дивиденда","Franked (non-taxable) dividend operations") :
      reason==EVENT_REASON_TAX                              ?  TextByLanguage("Начисление налога","Tax charges")                                                  :
      EnumToString(reason)
     );
  }
//+------------------------------------------------------------------+
//| 在流水账里显示事件属性                                               |
//+------------------------------------------------------------------+
void CEvent::Print(const bool full_prop=false)
  {
   ::Print("============= ",TextByLanguage("Начало списка параметров события: \"","Beginning of event parameter list: \""),this.StatusDescription(),"\" =============");
   int beg=0, end=EVENT_PROP_INTEGER_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_EVENT_PROP_INTEGER prop=(ENUM_EVENT_PROP_INTEGER)i;
      if(!full_prop && !this.SupportProperty(prop)) continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("------");
   beg=end; end+=EVENT_PROP_DOUBLE_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_EVENT_PROP_DOUBLE prop=(ENUM_EVENT_PROP_DOUBLE)i;
      if(!full_prop && !this.SupportProperty(prop)) continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("------");
   beg=end; end+=EVENT_PROP_STRING_TOTAL;
   for(int i=beg; i<end; i++)
     {
      ENUM_EVENT_PROP_STRING prop=(ENUM_EVENT_PROP_STRING)i;
      if(!full_prop && !this.SupportProperty(prop)) continue;
      ::Print(this.GetPropertyDescription(prop));
     }
   ::Print("================== ",TextByLanguage("Конец списка параметров: \"","End of parameter list: \""),this.StatusDescription(),"\" ==================\n");
  }
//+------------------------------------------------------------------+

抽象基本事件类已准备就绪。 现在我们需要创建五个子类,它们分别对应事件类型之一:下挂单,删除挂单,开仓,平仓和余额操作。

创建具有“放置挂单”事件状态的子类。

在 Events 函数库文件夹中,创建名为 EventOrderPlased.mqh 的新文件,并在其中添加 CEvent 基类 的子类 CEventOrderPlased,和所有必需的连接方法

//+------------------------------------------------------------------+
//|                                             EventOrderPlased.mqh |
//|                                  版权所有 2018, MetaQuotes 软件公司 |
//|                             https://mql5.com/zh/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "版权所有 2018, MetaQuotes 软件公司"
#property link      "https://mql5.com/zh/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| 包含文件                                                           |
//+------------------------------------------------------------------+
#include "Event.mqh"
//+------------------------------------------------------------------+
//| 下挂单事件                                                           |
//+------------------------------------------------------------------+
class CEventOrderPlased : public CEvent
  {
public:
//--- 构造函数
                     CEventOrderPlased(const int event_code,const ulong ticket=0) : CEvent(EVENT_STATUS_MARKET_PENDING,event_code,ticket) {}
//--- 支持的订单属性 (1) 实数型,和 (2) 整数型
   virtual bool      SupportProperty(ENUM_EVENT_PROP_INTEGER property);
   virtual bool      SupportProperty(ENUM_EVENT_PROP_DOUBLE property);
//--- (1) 在流水帐中显示有关事件的摘要消息, (2) 将事件发送到图表
   virtual void      PrintShort(void);
   virtual void      SendEvent(void);
  };
//+------------------------------------------------------------------+

将触发事件的订单或成交事件代码票据传递给类构造函数,在初始化清单中并将“放置挂单”( EVENT_STATUS_MARKET_PENDING)事件状态、事件代码、订单或成交票据发送给父类:

CEventOrderPlased(const int event_code,const ulong ticket=0) : CEvent(EVENT_STATUS_MARKET_PENDING,event_code,ticket) {}

我们在函数库说明第一部分中讲述过返回支持某些对象属性标志的 SupportProperty() 方法。 一切与此相同:

//+------------------------------------------------------------------+
//| 返回 "true",如果支持传递的事件                                       |
//| 整数型属性,否则返回 'false'                                          |
//+------------------------------------------------------------------+
bool CEventOrderPlased::SupportProperty(ENUM_EVENT_PROP_INTEGER property)
  {
   if(property==EVENT_PROP_TYPE_DEAL_EVENT         ||
      property==EVENT_PROP_TICKET_DEAL_EVENT       ||
      property==EVENT_PROP_TYPE_ORDER_POSITION     ||
      property==EVENT_PROP_TICKET_ORDER_POSITION   ||
      property==EVENT_PROP_POSITION_ID             ||
      property==EVENT_PROP_POSITION_BY_ID          ||
      property==EVENT_PROP_TIME_ORDER_POSITION
     ) return false;
   return true;
  }
//+------------------------------------------------------------------+
//| 返回 "true",如果支持传递的事件                                       |
//| 实数型属性,否则返回 'false'                                          |
//+------------------------------------------------------------------+
bool CEventOrderPlased::SupportProperty(ENUM_EVENT_PROP_DOUBLE property)
  {
   if(property==EVENT_PROP_PRICE_CLOSE             ||
      property==EVENT_PROP_PROFIT
     ) return false;
   return true;
  }
//+------------------------------------------------------------------+

CEvent 父事件对象具有 Print() 方法,该方法显示所有支持的事件对象属性的完整数据,以及虚 PrintShort() 方法,允许在终端日志中以两行显示事件的足够数据。
应在基本事件对象的每个子类中单独实现 PrintShort() 方法,因为事件的起因不尽相同:

//+------------------------------------------------------------------+
//| 在流水帐中显示简要事件消息                                            |
//+------------------------------------------------------------------+
void CEventOrderPlased::PrintShort(void)
  {
   string head="- "+this.TypeEventDescription()+": "+TimeMSCtoString(this.TimePosition())+" -\n";
   string sl=(this.PriceStopLoss()>0 ? ", sl "+::DoubleToString(this.PriceStopLoss(),(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS)) : "");
   string tp=(this.PriceTakeProfit()>0 ? ", tp "+::DoubleToString(this.PriceTakeProfit(),(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS)) : "");
   string vol=::DoubleToString(this.VolumeInitial(),DigitsLots(this.Symbol()));
   string magic=(this.Magic()!=0 ? TextByLanguage(", магик ",", magic ")+(string)this.Magic() : "");
   string type=this.TypeOrderDescription()+" #"+(string)this.TicketOrderEvent();
   string price=TextByLanguage(" по цене "," at price ")+::DoubleToString(this.PriceOpen(),(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS));
   string txt=head+this.Symbol()+" "+vol+" "+type+price+sl+tp+magic;
   ::Print(txt);
  }
//+------------------------------------------------------------------+

在此,我们要完成以下事情:

  • 创建一条由事件类型描述和时间组成的消息标题
  • 如果订单有止损,则用其描述创建该行,否则该字符串保持为空
  • 如果订单有止盈,则用其描述创建该行,否则该字符串保持为空
  • 创建一行明示订单交易量
  • 如果订单含有魔幻数字,则用其描述创建该行,否则该字符串保持为空
  • 创建一行指定订单类型和票据
  • 创建一行指定下单时的价格品种
  • 依据以上所有描述创建一个完整的行
  • 在流水帐中显示所创建的行

将自定义事件发送到图表的方法非常简单:

//+------------------------------------------------------------------+
//| 将事件发送到图表                                                    |
//+------------------------------------------------------------------+
void CEventOrderPlased::SendEvent(void)
  {
   this.PrintShort();
   ::EventChartCustom(this.m_chart_id,(ushort)this.m_trade_event,this.TicketOrderEvent(),this.PriceOpen(),this.Symbol());
  }
//+------------------------------------------------------------------+

首先,在日志里显示关于事件的短消息,然后 EventChartCustom() 自定义事件将基本 CEvent 事件类发送给 m_chart_id 中指定的图表 ID
发送 m_trade_event 事件至事件 ID,
订单票据 — 为 long 类型参数,
订单价格 — 为 double
类型参数,
订单品种 — 为 string 类型参数

未来会开发允许用户设置显示级别的类,在日志中的消息要符合相应级别。 在当前函数库开发阶段,默认情况下会显示所有消息。

我们来研究其他事件类的完整清单。
"删除挂单" 事件类:

//+------------------------------------------------------------------+
//|                                            EventOrderRemoved.mqh |
//|                                  版权所有 2018, MetaQuotes 软件公司 |
//|                             https://mql5.com/zh/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "版权所有 2018, MetaQuotes 软件公司"
#property link      "https://mql5.com/zh/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| 包含文件                                                           |
//+------------------------------------------------------------------+
#include "Event.mqh"
//+------------------------------------------------------------------+
//| 下挂单事件                                                           |
//+------------------------------------------------------------------+
class CEventOrderRemoved : public CEvent
  {
public:
//--- 构造函数
                     CEventOrderRemoved(const int event_code,const ulong ticket=0) : CEvent(EVENT_STATUS_HISTORY_PENDING,event_code,ticket) {}
//--- 支持的订单属性 (1) 实数型,和 (2) 整数型
   virtual bool      SupportProperty(ENUM_EVENT_PROP_INTEGER property);
   virtual bool      SupportProperty(ENUM_EVENT_PROP_DOUBLE property);
//--- (1) 在流水帐中显示有关事件的摘要消息, (2) 将事件发送到图表
   virtual void      PrintShort(void);
   virtual void      SendEvent(void);
  };
//+------------------------------------------------------------------+
//| 返回 "true",如果支持传递的事件                                       |
//| 整数型属性,否则返回 'false'                                          |
//+------------------------------------------------------------------+
bool CEventOrderRemoved::SupportProperty(ENUM_EVENT_PROP_INTEGER property)
  {
   if(property==EVENT_PROP_TYPE_DEAL_EVENT         ||
      property==EVENT_PROP_TICKET_DEAL_EVENT       ||
      property==EVENT_PROP_TYPE_ORDER_POSITION     ||
      property==EVENT_PROP_TICKET_ORDER_POSITION   ||
      property==EVENT_PROP_TIME_ORDER_POSITION
     ) return false;
   return true;
  }
//+------------------------------------------------------------------+
//| 返回 "true",如果支持传递的事件                                       |
//| 实数型属性,否则返回 'false'                                          |
//+------------------------------------------------------------------+
bool CEventOrderRemoved::SupportProperty(ENUM_EVENT_PROP_DOUBLE property)
  {
   return(property==EVENT_PROP_PROFIT ? false : true);
  }
//+------------------------------------------------------------------+
//| 在流水帐中显示简要事件消息                                              |
//+------------------------------------------------------------------+
void CEventOrderRemoved::PrintShort(void)
  {
   string head="- "+this.TypeEventDescription()+": "+TimeMSCtoString(this.TimePosition())+" -\n";
   string sl=(this.PriceStopLoss()>0 ? ", sl "+::DoubleToString(this.PriceStopLoss(),(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS)) : "");
   string tp=(this.PriceTakeProfit()>0 ? ", tp "+::DoubleToString(this.PriceTakeProfit(),(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS)) : "");
   string vol=::DoubleToString(this.VolumeInitial(),DigitsLots(this.Symbol()));
   string magic=(this.Magic()!=0 ? TextByLanguage(", магик ",", magic ")+(string)this.Magic() : "");
   string type=this.TypeOrderDescription()+" #"+(string)this.TicketOrderEvent();
   string price=TextByLanguage(" по цене "," at price ")+::DoubleToString(this.PriceOpen(),(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS));
   string txt=head+this.Symbol()+" "+vol+" "+type+price+sl+tp+magic;
   ::Print(txt);
  }
//+------------------------------------------------------------------+
//| 将事件发送到图表                                                    |
//+------------------------------------------------------------------+
void CEventOrderRemoved::SendEvent(void)
  {
   this.PrintShort();
   ::EventChartCustom(this.m_chart_id,(ushort)this.m_trade_event,this.TicketOrderEvent(),this.PriceOpen(),this.Symbol());
  }
//+------------------------------------------------------------------+

"开仓" 事件类:

//+------------------------------------------------------------------+
//|                                            EventPositionOpen.mqh |
//|                                  版权所有 2018, MetaQuotes 软件公司 |
//|                             https://mql5.com/zh/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "版权所有 2018, MetaQuotes 软件公司"
#property link      "https://mql5.com/zh/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| 包含文件                                                           |
//+------------------------------------------------------------------+
#include "Event.mqh"
//+------------------------------------------------------------------+
//| 开仓事件                                                           |
//+------------------------------------------------------------------+
class CEventPositionOpen : public CEvent
  {
public:
//--- 构造函数
                     CEventPositionOpen(const int event_code,const ulong ticket=0) : CEvent(EVENT_STATUS_MARKET_POSITION,event_code,ticket) {}
//--- 支持的订单属性 (1) 实数型,和 (2) 整数型
   virtual bool      SupportProperty(ENUM_EVENT_PROP_INTEGER property);
   virtual bool      SupportProperty(ENUM_EVENT_PROP_DOUBLE property);
//--- (1) 在流水帐中显示有关事件的摘要消息, (2) 将事件发送到图表
   virtual void      PrintShort(void);
   virtual void      SendEvent(void);
  };
//+------------------------------------------------------------------+
//| 返回 "true",如果支持传递的事件                                       |
//| 整数型属性,否则返回 'false'                                          |
//+------------------------------------------------------------------+
bool CEventPositionOpen::SupportProperty(ENUM_EVENT_PROP_INTEGER property)
  {
   return(property==EVENT_PROP_POSITION_BY_ID ? false : true);
  }
//+------------------------------------------------------------------+
//| 返回 'true' 如果订单支持所传递的                                      |
//| 实数型属性,否则返回 'false'                                          |
//+------------------------------------------------------------------+
bool CEventPositionOpen::SupportProperty(ENUM_EVENT_PROP_DOUBLE property)
  {
   if(property==EVENT_PROP_PRICE_CLOSE ||
      property==EVENT_PROP_PROFIT
     ) return false;
   return true;
  }
//+------------------------------------------------------------------+
//| 在流水帐中显示简要事件消息                                            |
//+------------------------------------------------------------------+
void CEventPositionOpen::PrintShort(void)
  {
   string head="- "+this.TypeEventDescription()+": "+TimeMSCtoString(this.TimePosition())+" -\n";
   string order=(this.IsPresentEventFlag(TRADE_EVENT_FLAG_ORDER_ACTIVATED) ? " #"+(string)this.TicketOrderPosition() : "");
   string activated=(this.IsPresentEventFlag(TRADE_EVENT_FLAG_ORDER_ACTIVATED) ? TextByLanguage(" активацией ордера "," by ")+this.TypeOrderBasedDescription() : "");
   string sl=(this.PriceStopLoss()>0 ? ", sl "+::DoubleToString(this.PriceStopLoss(),(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS)) : "");
   string tp=(this.PriceTakeProfit()>0 ? ", tp "+::DoubleToString(this.PriceTakeProfit(),(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS)) : "");
   string vol=::DoubleToString(this.VolumeInitial(),DigitsLots(this.Symbol()));
   string magic=(this.Magic()!=0 ? TextByLanguage(", магик ",", magic ")+(string)this.Magic() : "");
   string type=this.TypePositionDescription()+" #"+(string)this.PositionID();
   string price=TextByLanguage(" по цене "," at price ")+::DoubleToString(this.PriceOpen(),(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS));
   string txt=head+this.Symbol()+" "+vol+" "+type+activated+order+price+sl+tp+magic;
   ::Print(txt);
  }
//+------------------------------------------------------------------+
//| 将事件发送到图表                                                    |
//+------------------------------------------------------------------+
void CEventPositionOpen::SendEvent(void)
  {
   this.PrintShort();
   ::EventChartCustom(this.m_chart_id,(ushort)this.m_trade_event,this.PositionID(),this.PriceOpen(),this.Symbol());
  }
//+------------------------------------------------------------------+

"平仓" 事件类:

//+------------------------------------------------------------------+
//|                                           EventPositionClose.mqh |
//|                                  版权所有 2018, MetaQuotes 软件公司 |
//|                             https://mql5.com/zh/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "版权所有 2018, MetaQuotes 软件公司"
#property link      "https://mql5.com/zh/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| 包含文件                                                           |
//+------------------------------------------------------------------+
#include "Event.mqh"
//+------------------------------------------------------------------+
//| 开仓事件                                                           |
//+------------------------------------------------------------------+
class CEventPositionClose : public CEvent
  {
public:
//--- 构造函数
                     CEventPositionClose(const int event_code,const ulong ticket=0) : CEvent(EVENT_STATUS_HISTORY_POSITION,event_code,ticket) {}
//--- 支持的订单属性 (1) 实数型,和 (2) 整数型
   virtual bool      SupportProperty(ENUM_EVENT_PROP_INTEGER property);
   virtual bool      SupportProperty(ENUM_EVENT_PROP_DOUBLE property);
//--- (1) 在流水帐中显示有关事件的摘要消息, (2) 将事件发送到图表
   virtual void      PrintShort(void);
   virtual void      SendEvent(void);
  };
//+------------------------------------------------------------------+
//| 返回 "true",如果支持传递的事件                                       |
//| 整数型属性,否则返回 'false'                                          |
//+------------------------------------------------------------------+
bool CEventPositionClose::SupportProperty(ENUM_EVENT_PROP_INTEGER property)
  {
   return true;
  }
//+------------------------------------------------------------------+
//| 返回 "true",如果支持传递的事件                                       |
//| 实数型属性,否则返回 'false'                                          |
//+------------------------------------------------------------------+
bool CEventPositionClose::SupportProperty(ENUM_EVENT_PROP_DOUBLE property)
  {
   return true;
  }
//+------------------------------------------------------------------+
//| 在流水帐中显示有关事件的简要消息                                       |
//+------------------------------------------------------------------+
void CEventPositionClose::PrintShort(void)
  {
   string head="- "+this.TypeEventDescription()+": "+TimeMSCtoString(this.TimePosition())+" -\n";
   string opposite=(this.IsPresentEventFlag(TRADE_EVENT_FLAG_BY_POS) ? " by "+this.TypeOrderDescription()+" #"+(string)this.PositionByID() : "");
   string vol=::DoubleToString(this.VolumeExecuted(),DigitsLots(this.Symbol()));
   string magic=(this.Magic()!=0 ? TextByLanguage(", магик ",", magic ")+(string)this.Magic() : "");
   string type=this.TypePositionDescription()+" #"+(string)this.PositionID()+opposite;
   string price=TextByLanguage(" по цене "," at price ")+::DoubleToString(this.PriceClose(),(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS));
   string profit=TextByLanguage(", профит: ",", profit: ")+::DoubleToString(this.Profit(),this.m_digits_acc)+" "+::AccountInfoString(ACCOUNT_CURRENCY);
   string txt=head+this.Symbol()+" "+vol+" "+type+price+magic+profit;
   ::Print(txt);
  }
//+------------------------------------------------------------------+
//| 将事件发送到图表                                                    |
//+------------------------------------------------------------------+
void CEventPositionClose::SendEvent(void)
  {
   this.PrintShort();
   ::EventChartCustom(this.m_chart_id,(ushort)this.m_trade_event,this.PositionID(),this.PriceClose(),this.Symbol());
  }
//+------------------------------------------------------------------+

"余额操作" 事件类:

//+------------------------------------------------------------------+
//|                                        EventBalanceOperation.mqh |
//|                                  版权所有 2018, MetaQuotes 软件公司 |
//|                             https://mql5.com/zh/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "版权所有 2018, MetaQuotes 软件公司"
#property link      "https://mql5.com/zh/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| 包含文件                                                           |
//+------------------------------------------------------------------+
#include "Event.mqh"
//+------------------------------------------------------------------+
//| 开仓事件                                                           |
//+------------------------------------------------------------------+
class CEventBalanceOperation : public CEvent
  {
public:
//--- 构造函数
                     CEventBalanceOperation(const int event_code,const ulong ticket=0) : CEvent(EVENT_STATUS_BALANCE,event_code,ticket) {}
//--- 支持的订单属性 (1) 实数型,和 (2) 整数型
   virtual bool      SupportProperty(ENUM_EVENT_PROP_INTEGER property);
   virtual bool      SupportProperty(ENUM_EVENT_PROP_DOUBLE property);
   virtual bool      SupportProperty(ENUM_EVENT_PROP_STRING property);
//--- (1) 在流水帐中显示有关事件的摘要消息, (2) 将事件发送到图表
   virtual void      PrintShort(void);
   virtual void      SendEvent(void);
  };
//+------------------------------------------------------------------+
//| 返回 "true",如果支持传递的事件                                       |
//| 整数型属性,否则返回 'false'                                          |
//+------------------------------------------------------------------+
bool CEventBalanceOperation::SupportProperty(ENUM_EVENT_PROP_INTEGER property)
  {
   if(property==EVENT_PROP_TYPE_ORDER_EVENT        ||
      property==EVENT_PROP_TYPE_ORDER_POSITION     ||
      property==EVENT_PROP_TICKET_ORDER_EVENT      ||
      property==EVENT_PROP_TICKET_ORDER_POSITION   ||
      property==EVENT_PROP_POSITION_ID             ||
      property==EVENT_PROP_POSITION_BY_ID          ||
      property==EVENT_PROP_POSITION_ID             ||
      property==EVENT_PROP_MAGIC_ORDER             ||
      property==EVENT_PROP_TIME_ORDER_POSITION
     ) return false;
   return true;
  }
//+------------------------------------------------------------------+
//| 返回 "true",如果支持传递的事件                                       |
//| 实数型属性,否则返回 'false'                                            |
//+------------------------------------------------------------------+
bool CEventBalanceOperation::SupportProperty(ENUM_EVENT_PROP_DOUBLE property)
  {
   return(property==EVENT_PROP_PROFIT ? true : false);
  }
//+------------------------------------------------------------------+
//| 返回 "true",如果支持传递的事件                                            |
//| 字符串型属性,否则返回 'false'                                            |
//+------------------------------------------------------------------+
bool CEventBalanceOperation::SupportProperty(ENUM_EVENT_PROP_STRING property)
  {
   return false;
  }
//+------------------------------------------------------------------+
//| 在流水帐中显示有关事件的简要消息                                             |
//+------------------------------------------------------------------+
void CEventBalanceOperation::PrintShort(void)
  {
   string head="- "+this.StatusDescription()+": "+TimeMSCtoString(this.TimePosition())+" -\n";
   ::Print(head+this.TypeEventDescription()+": "+::DoubleToString(this.Profit(),this.m_digits_acc)+" "+::AccountInfoString(ACCOUNT_CURRENCY));
  }
//+------------------------------------------------------------------+
//| 将事件发送到图表                                                        |
//+------------------------------------------------------------------+
void CEventBalanceOperation::SendEvent(void)
  {
   this.PrintShort();
   ::EventChartCustom(this.m_chart_id,(ushort)this.m_trade_event,this.TypeEvent(),this.Profit(),::AccountInfoString(ACCOUNT_CURRENCY));
  }
//+------------------------------------------------------------------+

正如我们从清单中看到的那样,这些类的不同之处仅在于支持的属性数量,发送到父类构造函数的状态,以及 PrintShort() 方法,因为每个事件都有自己的特性,应该在日志中反映出来。 所有这些都可以从方法的清单中掌握,您可以自行分析它们,所以没有必要再对它们进行分析。 我们转到事件集合类的开发。

交易事件的集合

在函数库讲述的第四部分,我们测试了定义帐户事件,及其在流水帐种的显示,还有 EA。 不过,我们只能跟踪最新的事件。 此外,整体功能位于 CEngine 函数库的基类中。
正确的决定是将所有内容放入一个单独的类中,并处理其中发生的所有事件。

为达此目的,我开发了事件对象。 现在我们需要类能够处理任意数量的并发事件。 毕竟,可能出现在单一循环中同时删除或放置挂单,或多个仓位平仓的情况。
在这种情况下我们已经测试过的理论只能令我们一次性处理若干个最新事件之中的一个。 我想,这是不正确的。 所以,我们在类中保存所有事件,并依据该事件集合列表一次性处置。 此外,在将来,有可能使用这些类的方法来遍历帐户的历史记录,并可自打开以来重新创建其上发生的所有事情。

在 DoEasy\Collections 中,创建名为 EventsCollection.mqh 的新文件,并编写 CEventsCollection 类。 应将 CListObj 作为基类。

马上用所有必要的包含、成员和方法填写新创建的类模板:

//+------------------------------------------------------------------+
//|                                             EventsCollection.mqh |
//|                                  版权所有 2018, MetaQuotes 软件公司 |
//|                             https://mql5.com/zh/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "版权所有 2018, MetaQuotes 软件公司"
#property link      "https://mql5.com/zh/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| 包含文件                                                           |
//+------------------------------------------------------------------+
#include "ListObj.mqh"
#include "..\Services\Select.mqh"
#include "..\Objects\Orders\Order.mqh"
#include "..\Objects\Events\EventBalanceOperation.mqh"
#include "..\Objects\Events\EventOrderPlaced.mqh"
#include "..\Objects\Events\EventOrderRemoved.mqh"
#include "..\Objects\Events\EventPositionOpen.mqh"
#include "..\Objects\Events\EventPositionClose.mqh"
//+------------------------------------------------------------------+
//| 账户事件集合                                                          |
//+------------------------------------------------------------------+
class CEventsCollection : public CListObj
  {
private:
   CListObj          m_list_events;                   // 事件清单
   bool              m_is_hedge;                      // 对冲账户标志
   long              m_chart_id;                      // 控制程序所在的图表 ID
   ENUM_TRADE_EVENT  m_trade_event;                   // 账户交易事件
   CEvent            m_event_instance;                // 按属性搜索的事件对象
   
//--- 根据订单状态创建交易事件
   void              CreateNewEvent(COrder* order,CArrayObj* list_history,CArrayObj* list_market);
//--- 选择并返回在场挂单列表
   CArrayObj*        GetListMarketPendings(CArrayObj* list);
//--- 选择并返回历史(1)已删除挂单,(2)成交,(3)所有已平订单的列表 
   CArrayObj*        GetListHistoryPendings(CArrayObj* list);
   CArrayObj*        GetListDeals(CArrayObj* list);
   CArrayObj*        GetListCloseByOrders(CArrayObj* list);
//--- 选择并返回(1)所有仓位订单的 ID 列表,(2)所有成交仓位的 ID
//--- (3) 按仓位 ID 排序的所有入场成交,(4)按仓位 ID 排序的所有离场成交
   CArrayObj*        GetListAllOrdersByPosID(CArrayObj* list,const ulong position_id);
   CArrayObj*        GetListAllDealsByPosID(CArrayObj* list,const ulong position_id);
   CArrayObj*        GetListAllDealsInByPosID(CArrayObj* list,const ulong position_id);
   CArrayObj*        GetListAllDealsOutByPosID(CArrayObj* list,const ulong position_id);
//--- 按仓位 ID 返回所有成交的总交易量(1)IN,(2)OUT
   double            SummaryVolumeDealsInByPosID(CArrayObj* list,const ulong position_id);
   double            SummaryVolumeDealsOutByPosID(CArrayObj* list,const ulong position_id);
//--- 返回 (1) 首笔, (2) 最后一笔,和 (3) 从所有仓位订单列表中的平仓订单, (4) 按票据的订单
   COrder*           GetFirstOrderFromList(CArrayObj* list,const ulong position_id);
   COrder*           GetLastOrderFromList(CArrayObj* list,const ulong position_id);
   COrder*           GetCloseByOrderFromList(CArrayObj* list,const ulong position_id);
   COrder*           GetOrderByTicket(CArrayObj* list,const ulong order_ticket);
//--- 返回事件列表中事件对象存在的标志
   bool              IsPresentEventInList(CEvent* compared_event);
   
public:
//--- 选择集合中从 begin_time 到 end_time 时间范围内的事件
   CArrayObj        *GetListByTime(const datetime begin_time=0,const datetime end_time=0);
//--- 按“原样”返回完整的事件集合列表
   CArrayObj        *GetList(void)                                                                       { return &this.m_list_events;                                           }
//--- 返回符合所选择的(1)整数型,(2)实数型,和(3)字符串型属性比较标准的列表
   CArrayObj        *GetList(ENUM_EVENT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL)  { return CSelect::ByEventProperty(this.GetList(),property,value,mode);  }
   CArrayObj        *GetList(ENUM_EVENT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByEventProperty(this.GetList(),property,value,mode);  }
   CArrayObj        *GetList(ENUM_EVENT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByEventProperty(this.GetList(),property,value,mode);  }
//--- 更新事件列表
   void              Refresh(CArrayObj* list_history,
                             CArrayObj* list_market,
                             const bool is_history_event,
                             const bool is_market_event,
                             const int  new_history_orders,
                             const int  new_market_pendings,
                             const int  new_market_positions,
                             const int  new_deals);
//--- 设置控制程序所在的图表 ID
   void              SetChartID(const long id)        { this.m_chart_id=id;         }
//--- 返回帐户上的最后一笔交易事件
   ENUM_TRADE_EVENT  GetLastTradeEvent(void)    const { return this.m_trade_event;  }
//--- 重置最后的交易事件
   void              ResetLastTradeEvent(void)        { this.m_trade_event=TRADE_EVENT_NO_EVENT;   }
//--- 构造函数
                     CEventsCollection(void);
  };
//+------------------------------------------------------------------+
//| 构造函数                                                           |
//+------------------------------------------------------------------+
CEventsCollection::CEventsCollection(void) : m_trade_event(TRADE_EVENT_NO_EVENT)
  {
   this.m_list_events.Clear();
   this.m_list_events.Sort(SORT_BY_EVENT_TIME_EVENT);
   this.m_list_events.Type(COLLECTION_EVENTS_ID);
   this.m_is_hedge=bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
   this.m_chart_id=::ChartID();
  }
//+------------------------------------------------------------------+

在类构造函数初始化清单中重置交易事件
清除构造函数实体中的集合列表
设置为按事件时间排序
,
设置事件集合列表 ID,
设置对冲账户标志,并
将控制程序所在图表 ID 设置为当前图表

我们考察一下该类工作所需的方法。

以下类成员在类的私有部分中声明:

   CListObj          m_list_events;                   // 事件列表
   bool              m_is_hedge;                      // 对冲账户标志
   long              m_chart_id;                      // 控制程序所在的图表 ID
   ENUM_TRADE_EVENT  m_trade_event;                   // 账户交易事件
   CEvent            m_event_instance;                // 按属性搜索的事件对象

m_list_events 事件列表基于 CListObj。 它存储自启动程序以来在帐户上发生的事件。 此外,我们将利用它来接收多生事件的数量,以便一次性处理。
m_is_hedge 对冲帐户标志用于保存和接收帐户类型。 标志值(帐户类型)定义了处理图表上发生事件的模块
m_chart_id 控制程序所在的图表 ID 接收账户里发生的自定义事件。 ID 将发送给事件对象,并返回到图表。 利用为此目的而创建的方法可从控制程序设置 ID。
m_trade_event交易事件存储在帐户上发生的最后一个事件。
m_event_instance事件对象用于按属性搜索 — 在方法内部使用的特殊样本对象,它返回指定开始和结束日期搜索范围内的事件列表。 在讨论如何按照不同的标准在列表中安排搜索时,我们已在函数库讲述的 第三部分中分析了类似的方法。<br20 />

在私有部分中,您可以看到类操作所需的方法:

//--- 根据订单状态创建交易事件
   void              CreateNewEvent(COrder* order,CArrayObj* list_history);
//--- 选择并返回在场挂单列表
   CArrayObj*        GetListMarketPendings(CArrayObj* list);
//--- 选择并返回历史列表(1)订单,(2)已删除挂单, 
//--- (3) 成交,(4) 按其 ID 的所有仓位订单, (5) 按其 ID 的所有仓位成交
//--- (6) 按仓位 ID 的所有入场成交,(7)按仓位 ID 的所有离场成交
//--- (7) 所有平仓订单
   CArrayObj*        GetListHistoryOrders(CArrayObj* list);
   CArrayObj*        GetListHistoryPendings(CArrayObj* list);
   CArrayObj*        GetListDeals(CArrayObj* list);
   CArrayObj*        GetListAllOrdersByPosID(CArrayObj* list,const ulong position_id);
   CArrayObj*        GetListAllDealsByPosID(CArrayObj* list,const ulong position_id);
   CArrayObj*        GetListAllDealsInByPosID(CArrayObj* list,const ulong position_id);
   CArrayObj*        GetListAllDealsOutByPosID(CArrayObj* list,const ulong position_id);
   CArrayObj*        GetListAllCloseByOrders(CArrayObj* list);
//--- 按仓位 ID 返回所有成交的总交易量(1)IN,(2)OUT 
   double            SummaryVolumeDealsInByPosID(CArrayObj* list,const ulong position_id);
   double            SummaryVolumeDealsOutByPosID(CArrayObj* list,const ulong position_id);
//--- 返回 (1) 首笔, (2) 最后一笔,和 (3) 从所有仓位订单列表中的平仓订单, (4) 按票据的订单
   COrder*           GetFirstOrderFromList(CArrayObj* list,const ulong position_id);
   COrder*           GetLastOrderFromList(CArrayObj* list,const ulong position_id);
   COrder*           GetCloseByOrderFromList(CArrayObj* list,const ulong position_id);
   COrder*           GetOrderByTicket(CArrayObj* list,const ulong order_ticket);
//--- 返回事件列表中事件对象存在的标志
   bool              IsPresentEventInList(CEvent* compared_event);

Refresh() 主类方法中使用 CreateNewEvent() 方法,该方法根据订单状态创建交易事件。 我们将在讨论 Refresh() 方法时研究它。

接收各种订单类型列表的方法非常简单 — 在函数库讲述的第三部分中阐述了如何按照指定属性进行选择。 在此,我们只简述一些由必要属性的若干次迭代选择而构成的方法。

接收在场挂单列表的方法:

//+------------------------------------------------------------------+
//| 仅从列表中选择在场挂单                                                   |
//+------------------------------------------------------------------+
CArrayObj* CEventsCollection::GetListMarketPendings(CArrayObj* list)
  {
   if(list.Type()!=COLLECTION_MARKET_ID)
     {
      Print(DFUN,TextByLanguage("Ошибка. Список не является списком рыночной коллекции","Error. List is not a list of market collection"));
      return NULL;
     }
   CArrayObj* list_orders=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_MARKET_PENDING,EQUAL);
   return list_orders;
  }
//+------------------------------------------------------------------+

首先检查传递给方法的列表类型。 如果它不是入场集合的列表,则显示错误消息,并返回空列表。

之后从传递给该方法的列表中选择具有“在场挂单”状态的订单,并返回所获得的列表。

用于接收已删除挂单成交和因逆向仓位而平仓的平仓订单列表的方法

//+------------------------------------------------------------------+
//| 从列表中仅选择已删除的挂单                                                |
//+------------------------------------------------------------------+
CArrayObj* CEventsCollection::GetListHistoryPendings(CArrayObj* list)
  {
   if(list.Type()!=COLLECTION_HISTORY_ID)
     {
      Print(DFUN,TextByLanguage("Ошибка. Список не является списком исторической коллекции","Error. The list is not a list of the history collection"));
      return NULL;
     }
   CArrayObj* list_orders=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_HISTORY_PENDING,EQUAL);
   return list_orders;
  }
//+------------------------------------------------------------------+
//| 从列表中仅选择成交                                                    |
//+------------------------------------------------------------------+
CArrayObj* CEventsCollection::GetListDeals(CArrayObj* list)
  {
   if(list.Type()!=COLLECTION_HISTORY_ID)
     {
      Print(DFUN,TextByLanguage("Ошибка. Список не является списком исторической коллекции","Error. The list is not a list of the history collection"));
      return NULL;
     }
   CArrayObj* list_deals=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_DEAL,EQUAL);
   return list_deals;
  }
//+------------------------------------------------------------------+
//|  返回列表中所有因 CloseBy 而平仓的订单列表                             |
//+------------------------------------------------------------------+
CArrayObj* CEventsCollection::GetListCloseByOrders(CArrayObj *list)
  {
   if(list.Type()!=COLLECTION_HISTORY_ID)
     {
      Print(DFUN,TextByLanguage("Ошибка. Список не является списком исторической коллекции","Error. The list is not a list of the history collection"));
      return NULL;
     }
   CArrayObj* list_orders=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,ORDER_TYPE_CLOSE_BY,EQUAL);
   return list_orders;
  }
//+------------------------------------------------------------------+

就像返回激活挂单的列表一样:

检查列表类型,如果它不是历史集合,则显示消息并返回 NULL。
然后从传递给方法的列表中选择具有“已删除挂单”和“成交”状态的订单,或者根据方法按 ORDER_TYPE_CLOSE_BY 类型选择订单,并返回获得的列表。

按其 ID 获取属于该仓位的所有订单列表的方法:

//+------------------------------------------------------------------+
//|  按其 ID 返回所有属于该仓位的订单列表                                  |
//+------------------------------------------------------------------+
CArrayObj* CEventsCollection::GetListAllOrdersByPosID(CArrayObj* list,const ulong position_id)
  {
   CArrayObj* list_orders=CSelect::ByOrderProperty(list,ORDER_PROP_POSITION_ID,position_id,EQUAL);
   list_orders=CSelect::ByOrderProperty(list_orders,ORDER_PROP_STATUS,ORDER_STATUS_DEAL,NO_EQUAL);
   return list_orders;
  }
//+------------------------------------------------------------------+

首先,使用传递给方法的列表,形成一个单独的列表,其中包含指向通过其参数传递给方法的仓位 ID 的指针。
接着,从获得的列表中删除 所有成交,并将最终列表返回给调用程序。 方法返回的结果可能是 NULL,因此我们应该检查在调用程序中方法返回的结果。

通过其 ID 获得属于某个仓位的所有成交列表的方法:

//+------------------------------------------------------------------+
/|  按其 ID 返回所有属于该仓位的成交列表                                   |
//+------------------------------------------------------------------+
CArrayObj* CEventsCollection::GetListAllDealsByPosID(CArrayObj *list,const ulong position_id)
  {
   if(list.Type()!=COLLECTION_HISTORY_ID)
     {
      Print(DFUN,TextByLanguage("Ошибка. Список не является списком исторической коллекции","Error. The list is not a list of the history collection"));
      return NULL;
     }
   CArrayObj* list_deals=CSelect::ByOrderProperty(list,ORDER_PROP_POSITION_ID,position_id,EQUAL);
   list_deals=CSelect::ByOrderProperty(list_deals,ORDER_PROP_STATUS,ORDER_STATUS_DEAL,EQUAL);
   return list_deals;
  }
//+------------------------------------------------------------------+

首先检查列表类型,如果它不是历史集合,则显示消息并返回 NULL。
接着,使用传递给方法的列表,形成所有对象的单独列表,其中包含指向通过其参数传递给方法的仓位 ID 的指针。
之后, 在获得的列表中仅保留成交,并将最终列表返回给调用程序。 方法返回的结果可能是 NULL,因此我们应该检查在调用程序中方法返回的结果。

通过其 ID 获取属于某个仓位的所有入场成交列表的方法:

//+------------------------------------------------------------------+
//| 返回所有入场成交的清单 (IN)                                               |
//| 依据其仓位 ID                                                        |
//+------------------------------------------------------------------+
CArrayObj* CEventsCollection::GetListAllDealsInByPosID(CArrayObj *list,const ulong position_id)
  {
   CArrayObj* list_deals=this.GetListAllDealsByPosID(list,position_id);
   list_deals=CSelect::ByOrderProperty(list_deals,ORDER_PROP_DEAL_ENTRY,DEAL_ENTRY_IN,EQUAL);
   return list_deals;
  }
//+------------------------------------------------------------------+

首先,使用传递给方法的列表,形成一个所有成交的单独列表,其中包含指向通过其参数传递给方法的仓位 ID 的指针。
接着, 在获得的列表中仅保留 DEAL_ENTRY_IN 类型的成交,并将最终列表返回给调用程序。 方法返回的结果可能是 NULL,因此我们应该检查在调用程序中方法返回的结果。

通过其 ID 获取属于某个仓位的所有离场成交列表的方法:

//+------------------------------------------------------------------+
//| 返回所有离场成交的清单 (OUT)                                           |
//| 依据其仓位 ID                                                       |
//+------------------------------------------------------------------+
CArrayObj* CEventsCollection::GetListAllDealsOutByPosID(CArrayObj *list,const ulong position_id)
  {
   CArrayObj* list_deals=this.GetListAllDealsByPosID(list,position_id);
   list_deals=CSelect::ByOrderProperty(list_deals,ORDER_PROP_DEAL_ENTRY,DEAL_ENTRY_OUT,EQUAL);
   return list_deals;
  }
//+------------------------------------------------------------------+

首先,使用传递给方法的列表,形成一个所有成交的单独列表,其中包含指向通过其参数传递给方法的仓位 ID 的指针。
接着, 在获得的列表中仅保留 DEAL_ENTRY_OUT 类型的成交,并将最终列表返回给调用程序。 方法返回的结果可能是 NULL,因此我们应该检查在调用程序中方法返回的结果。

该方法按其仓位 ID 返回所有入场成交的总交易量:

//+------------------------------------------------------------------+
//| 返回 IN 仓位的所有成交量                                                |
//| 按其 ID                                                            |
//+------------------------------------------------------------------+
double CEventsCollection::SummaryVolumeDealsInByPosID(CArrayObj *list,const ulong position_id)
  {
   double vol=0.0;
   CArrayObj* list_in=this.GetListAllDealsInByPosID(list,position_id);
   if(list_in==NULL)
      return 0;
   for(int i=0;i<list_in.Total();i++)
     {
      COrder* deal=list_in.At(i);
      if(deal==NULL)
         continue;
      vol+=deal.Volume();
     }
   return vol;
  }
//+------------------------------------------------------------------+

首先,接收所有入场成交的列表,然后在一个循环中汇总所有成交量。 结果成交量将返回到调用程序。 如果传递给方法的列表为空,或者它不是历史集合列表,则该方法返回零。

该方法通过其 ID 返回所有离场成交量:

//+--------------------------------------------------------------------+
//| 按其仓位 ID 返回 OUT 仓位的所有成交量                                      |
//| (已考虑了因逆向仓位而导致的平仓)                                            |
//+--------------------------------------------------------------------+
double CEventsCollection::SummaryVolumeDealsOutByPosID(CArrayObj *list,const ulong position_id)
  {
   double vol=0.0;
   CArrayObj* list_out=this.GetListAllDealsOutByPosID(list,position_id);
   if(list_out!=NULL)
     {
      for(int i=0;i<list_out.Total();i++)
        {
         COrder* deal=list_out.At(i);
         if(deal==NULL)
            continue;
         vol+=deal.Volume();
        }
     }
   CArrayObj* list_by=this.GetListCloseByOrders(list);
   if(list_by!=NULL)
     {
      for(int i=0;i<list_by.Total();i++)
        {
         COrder* order=list_by.At(i);
         if(order==NULL)
            continue;
         if(order.PositionID()==position_id || order.PositionByID()==position_id)
           {
            vol+=order.Volume();
           }
        }
     }
   return vol;
  }
//+------------------------------------------------------------------+

如果某笔仓位的一部分(其 ID 已传递给该方法)参与另一笔仓位的平仓(作为逆向仓位),或者该仓位的一部分被逆向仓位所平仓,则在仓位成交中不予考虑。 代之,它在仓位的最后平仓订单的 ORDER_PROP_POSITION_BY_ID 属性字段会被参考。 因此,此方法对已平仓成交量进行了两次搜索 — 按成交和平仓订单。

首先,收到所有离场仓位成交的列表,然后在一个循环中汇总所有交易量
接着,接收历史列表中存在的所有平仓订单列表,利用循环按传递给方法的仓位 ID 检查所有属于该仓位的订单。 如果所选订单参与了平仓,则将其交易量添加到总量中
结果成交量将返回到调用程序。 如果传递给方法的列表为空,或者它不是历史集合列表,则该方法返回零。

该方法按其仓位 ID 返回首笔(开仓)订单:

//+------------------------------------------------------------------+
//| 从所有仓位订单列表中返回首笔订单                                       |
//+------------------------------------------------------------------+
COrder* CEventsCollection::GetFirstOrderFromList(CArrayObj* list,const ulong position_id)
  {
   CArrayObj* list_orders=this.GetListAllOrdersByPosID(list,position_id);
   if(list_orders==NULL || list_orders.Total()==0) return NULL;
   list_orders.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
   COrder* order=list_orders.At(0);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+

首先,接收所有仓位订单的列表。 获得的列表按开仓时间排序,并取其中第一个元素。 它将作为首笔开仓订单。 获得的订单返回给调用程序。 如果列表为空,则该方法返回 NULL。

该方法通过其仓位 ID 返回最后一笔订单:

//+------------------------------------------------------------------+
//| 从所有仓位订单列表中返回最后一笔订单                                     |
//+------------------------------------------------------------------+
COrder* CEventsCollection::GetLastOrderFromList(CArrayObj* list,const ulong position_id)
  {
   CArrayObj* list_orders=this.GetListAllOrdersByPosID(list,position_id);
   if(list_orders==NULL || list_orders.Total()==0) return NULL;
   list_orders.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
   COrder* order=list_orders.At(list_orders.Total()-1);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+

首先,接收所有仓位订单的列表。 获得的列表按开仓时间排序,并取最后一个元素。 它将作为最后一笔订单。 获得的订单返回给调用程序。 如果列表为空,则该方法返回 NULL。

该方法按其仓位 ID(ORDER_TYPE_CLOSE_BY 类型订单)返回最后一笔平仓订单:

//+------------------------------------------------------------------+
//| 返回最后的平仓订单                                                   |
//| 从所有仓位订单里                                                    |
//+------------------------------------------------------------------+
COrder* CEventsCollection::GetCloseByOrderFromList(CArrayObj *list,const ulong position_id)
  {
   CArrayObj* list_orders=this.GetListAllOrdersByPosID(list,position_id);
   list_orders=CSelect::ByOrderProperty(list_orders,ORDER_PROP_TYPE,ORDER_TYPE_CLOSE_BY,EQUAL);
   if(list_orders==NULL || list_orders.Total()==0) return NULL;
   list_orders.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
   COrder* order=list_orders.At(list_orders.Total()-1);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+

由于会因逆向仓位而部分平仓,且两笔逆向仓位的交易量可能不相等,因此平仓订单可能不是某笔仓位的独有订单。 所以,该方法搜索此类订单,并返回它们中的最后一笔 — 它是触发事件的最后一笔订单。

首先,接收所有仓位订单的列表。 然后,从获得的列表中,接收仅包含平仓订单(ORDER_TYPE_CLOSE_BY 类型)的列表。 以这种方式获得的列表按开仓时间排序,并取最后一个元素。 它将作为最后一笔平仓订单。 获得的订单返回给调用程序。 如果列表为空,则该方法返回 NULL。

当因逆向仓位平仓时,函数库可能存在发现两笔相同事件的情况:两笔仓位被平仓,单其中只有一笔平仓订单,加起来我们有两笔成交。 因此,为了免于在集合中复制相同的事件,我们应首先在事件的集合列表中检查是否存在完全相同的事件,如果不存在,则将事件添加到列表中。

按票据返回订单的方法:

//+------------------------------------------------------------------+
//| 按票据返回订单                                                       |
//+------------------------------------------------------------------+
COrder* CEventsCollection::GetOrderByTicket(CArrayObj *list,const ulong order_ticket)
  {
   CArrayObj* list_orders=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_DEAL,NO_EQUAL);
   list_orders=CSelect::ByOrderProperty(list_orders,ORDER_PROP_TICKET,order_ticket,EQUAL);
   if(list_orders==NULL || list_orders.Total()==0) return NULL;
   COrder* order=list_orders.At(0);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+

首先,仅创建订单列表,然后按传递给方法的票据参数对列表进行排序。 结果就是,我们亦或 返回 NULL(此票据所指订单不存在时),亦或订单

从列表中返回事件存在的方法可用来检查列表中是否有该事件:

//+------------------------------------------------------------------+
//| 在事件列表中返回事件对象存在的标志                                      |
//+------------------------------------------------------------------+
bool CEventsCollection::IsPresentEventInList(CEvent *compared_event)
  {
   int total=this.m_list_events.Total();
   if(total==0)
      return false;
   for(int i=total-1;i>=0;i--)
     {
      CEvent* event=this.m_list_events.At(i);
      if(event==NULL)
         continue;
      if(event.IsEqual(compared_event))
         return true;
     }
   return false;
  }
//+------------------------------------------------------------------+

将比较事件对象的指针传递给方法。 如果集合列表为空,则立即返回 “false” 表示列表中没有此类事件。 之后,在循环里从列表中获取下一个事件,并利用 CEvent 抽象事件里的 IsEqual() 方法与传递给方法的事件进行比较 如果方法返回 'true',则事件集合列表中存在这样的事件对象完成循环或抵达方法末尾则意味着列表中没有该事件 ,并返回 'false'。

在类的公有部分中声明方法:

public:
//--- 选择集合中从 begin_time 到 end_time 时间范围内的事件
   CArrayObj        *GetListByTime(const datetime begin_time=0,const datetime end_time=0);
//--- 按“原样”返回完整的事件集合列表
   CArrayObj        *GetList(void)                                                                       { return &this.m_list_events;                                           }
//--- 返回符合所选择的(1)整数型,(2)实数型,和(3)字符串型属性比较标准的列表
   CArrayObj        *GetList(ENUM_EVENT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL)  { return CSelect::ByEventProperty(this.GetList(),property,value,mode);  }
   CArrayObj        *GetList(ENUM_EVENT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByEventProperty(this.GetList(),property,value,mode);  }
   CArrayObj        *GetList(ENUM_EVENT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByEventProperty(this.GetList(),property,value,mode);  }
//--- 更新事件列表
   void              Refresh(CArrayObj* list_history,
                             CArrayObj* list_market,
                             const bool is_history_event,
                             const bool is_market_event,
                             const int  new_history_orders,
                             const int  new_market_pendings,
                             const int  new_market_positions,
                             const int  new_deals);
//--- 设置控制程序所在的图表 ID
   void              SetChartID(const long id)        { this.m_chart_id=id;         }
//--- 返回帐户上的最后一笔交易事件
   ENUM_TRADE_EVENT  GetLastTradeEvent(void)    const { return this.m_trade_event;  }
//--- 重置最后的交易事件
   void              ResetLastTradeEvent(void)        { this.m_trade_event=TRADE_EVENT_NO_EVENT;   }
//--- 构造函数
                     CEventsCollection(void);

我讲述了接收完整列表的方法,列表可以按日期范围,以及函数库描述的第三部分中选定的整数型,实数型和字符串型属性来获取。 在此,我仅向您展示这些方法的清单,以便您可以自行分析它们。

按指定日期范围接收事件列表的方法:

//+------------------------------------------------------------------+
//| 从集合中选择的事件,其时间                                            |
//| 应处于 begin_time 至 end_time 范围内                                 |
//+------------------------------------------------------------------+
CArrayObj *CEventsCollection::GetListByTime(const datetime begin_time=0,const datetime end_time=0)
  {
   CArrayObj *list=new CArrayObj();
   if(list==NULL)
     {
      ::Print(DFUN+TextByLanguage("Ошибка создания временного списка","Error creating temporary list"));
      return NULL;
     }
   datetime begin=begin_time,end=(end_time==0 ? END_TIME : end_time);
   if(begin_time>end_time) begin=0;
   list.FreeMode(false);
   ListStorage.Add(list);
   //---
   this.m_event_instance.SetProperty(EVENT_PROP_TIME_EVENT,begin);
   int index_begin=this.m_list_events.SearchGreatOrEqual(&m_event_instance);
   if(index_begin==WRONG_VALUE)
      return list;
   this.m_event_instance.SetProperty(EVENT_PROP_TIME_EVENT,end);
   int index_end=this.m_list_events.SearchLessOrEqual(&m_event_instance);
   if(index_end==WRONG_VALUE)
      return list;
   for(int i=index_begin; i<=index_end; i++)
      list.Add(this.m_list_events.At(i));
   return list;
  }
//+------------------------------------------------------------------+

任何事件发生时,从基础函数库对象调用主方法 Refresh()
目前,该方法适用于 MQL5 的对冲账户。

该方法接收指向入场和历史订单、成交和仓位集合列表的指针,以及新出现或移除的订单、开/平仓和新成交的数量及数据。
取决于变更的列表,在循环中根据订单/仓位/成交数量获取必要数量的订单或成交,并为每笔订单调用 CreateNewEvent 方法创建事件,并将它们放入集合列表。
因此,任何发生的事件均会调用新的事件创建方法,同时将事件放置到集合列表当中,并向调用程序的图表发送自定义消息通告所有事件。

m_trade_event 类成员变量接收最后一次发生的事件值。 GetLastTradeEvent() 公有方法返回最后一笔交易事件的值。 还有重置最后交易事件的方法(类似于 GetLastError() 和 ResetLastError())。
此外,还有一些方法可以返回全部或按指定时间范围条件的事件集合列表。 调用程序始终知道发生了一个或多个事件,并且可以按所需数量请求所有这些事件的列表,并根据内置程序的逻辑处理它。

我们研究 Refresh()CreateNewEvent() 方法的列表。

更新事件集合列表的方法:

//+------------------------------------------------------------------+
//| 更新事件列表                                                          |
//+------------------------------------------------------------------+
void CEventsCollection::Refresh(CArrayObj* list_history,
                                CArrayObj* list_market,
                                const bool is_history_event,
                                const bool is_market_event,
                                const int  new_history_orders,
                                const int  new_market_pendings,
                                const int  new_market_positions,
                                const int  new_deals)
  {
//--- 如果列表为空,则退出
   if(list_history==NULL || list_market==NULL)
      return;
//--- 如果是对冲账户
   if(this.m_is_hedge)
     {
      //--- 如果事件是在场环境
      if(is_market_event)
        {
         //--- 如果放置的挂单数量增加
         if(new_market_pendings>0)
           {
            //--- 接收新放置挂单的列表
            CArrayObj* list=this.GetListMarketPendings(list_market);
            if(list!=NULL)
              {
               //--- 按订单放置时间对新列表进行排序
               list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC); 
               //--- 在循环中从列表末尾获取等于新放置数量的订单数(最后 N 个事件)
               int total=list.Total(), n=new_market_pendings;
               for(int i=total-1; i>=0 && n>0; i--,n--)
                 {
                  //--- 从列表中接收订单,如果这是挂单,则设置交易事件
                  COrder* order=list.At(i);
                  if(order!=NULL && order.Status()==ORDER_STATUS_MARKET_PENDING)
                     this.CreateNewEvent(order,list_history,list_market);
                 }
              }
           }
        }
      //--- 如果事件在帐户历史记录中
      if(is_history_event)
        {
         //--- 如果历史订单数量增加
         if(new_history_orders>0)
           {
            //--- 仅接收已删除的挂单列表
            CArrayObj* list=this.GetListHistoryPendings(list_history);
            if(list!=NULL)
              {
               //--- 按订单删除时间对新列表进行排序
               list.Sort(SORT_BY_ORDER_TIME_CLOSE_MSC);
               //--- 在循环中从列表末尾获取等于新删除数量的订单数(最后 N 个事件)
               int total=list.Total(), n=new_history_orders;
               for(int i=total-1; i>=0 && n>0; i--,n--)
                 {
                  //--- 从列表中接收订单。 如果这是已删除的挂单,则设置交易事件
                  COrder* order=list.At(i);
                  if(order!=NULL && order.Status()==ORDER_STATUS_HISTORY_PENDING)
                     this.CreateNewEvent(order,list_history,list_market);
                 }
              }
           }
         //--- 如果交易数量增加
         if(new_deals>0)
           {
            //--- 仅接收成交清单
            CArrayObj* list=this.GetListDeals(list_history);
            if(list!=NULL)
              {
               //--- 按成交时间对新列表进行排序
               list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
               //--- 在循环中从列表末尾获取等于新数量的成交数(最后 N 个事件)
               int total=list.Total(), n=new_deals;
               for(int i=total-1; i>=0 && n>0; i--,n--)
                 {
                  //--- 从列表中接收成交,并设置交易事件
                  COrder* order=list.At(i);
                  if(order!=NULL)
                     this.CreateNewEvent(order,list_history,list_market);
                 }
              }
           }
        }
     }
   //--- 如果是净持账户
   else
     {
      
     }
  }  
//+------------------------------------------------------------------+

简单方法清单包含满足这些条件的所有必要条件和操作。 我相信,这里一切都很透明。 目前,事件仅在对冲账户上处理。

我们研究用于创建新事件的方法:

//+------------------------------------------------------------------+
//| 根据订单状态创建交易事件                                               |
//+------------------------------------------------------------------+
void CEventsCollection::CreateNewEvent(COrder* order,CArrayObj* list_history,CArrayObj* list_market)
  {
   int trade_event_code=TRADE_EVENT_FLAG_NO_EVENT;
   ENUM_ORDER_STATUS status=order.Status();
//--- 下挂单
   if(status==ORDER_STATUS_MARKET_PENDING)
     {
      trade_event_code=TRADE_EVENT_FLAG_ORDER_PLASED;
      CEvent* event=new CEventOrderPlased(trade_event_code,order.Ticket());
      if(event!=NULL)
        {
         event.SetProperty(EVENT_PROP_TIME_EVENT,order.TimeOpenMSC());                       // 事件时间
         event.SetProperty(EVENT_PROP_REASON_EVENT,EVENT_REASON_DONE);                       // 事件原因 (来自 ENUM_EVENT_REASON 枚举)
         event.SetProperty(EVENT_PROP_TYPE_DEAL_EVENT,order.TypeOrder());                    // 事件成交类型
         event.SetProperty(EVENT_PROP_TICKET_DEAL_EVENT,order.Ticket());                     // 事件成交票据
         event.SetProperty(EVENT_PROP_TYPE_ORDER_EVENT,order.TypeOrder());                   // 事件订单类型
         event.SetProperty(EVENT_PROP_TYPE_ORDER_POSITION,order.TypeOrder());                // 事件仓位类型
         event.SetProperty(EVENT_PROP_TICKET_ORDER_EVENT,order.Ticket());                    // 事件订单票据
         event.SetProperty(EVENT_PROP_TICKET_ORDER_POSITION,order.Ticket());                 // 订单票据
         event.SetProperty(EVENT_PROP_POSITION_ID,order.PositionID());                       // 仓位 ID
         event.SetProperty(EVENT_PROP_POSITION_BY_ID,order.PositionByID());                  // 逆向仓位 ID
         event.SetProperty(EVENT_PROP_MAGIC_ORDER,order.Magic());                            // 订单魔幻数字
         event.SetProperty(EVENT_PROP_TIME_ORDER_POSITION,order.TimeOpenMSC());              // 订单时间
         event.SetProperty(EVENT_PROP_PRICE_EVENT,order.PriceOpen());                        // 事件发生时的价格
         event.SetProperty(EVENT_PROP_PRICE_OPEN,order.PriceOpen());                         // 订单放置价格
         event.SetProperty(EVENT_PROP_PRICE_CLOSE,order.PriceClose());                       // 订单平仓价格
         event.SetProperty(EVENT_PROP_PRICE_SL,order.StopLoss());                            // 止损订单价格
         event.SetProperty(EVENT_PROP_PRICE_TP,order.TakeProfit());                          // 止盈订单价格
         event.SetProperty(EVENT_PROP_VOLUME_INITIAL,order.Volume());                        // 请求的交易量
         event.SetProperty(EVENT_PROP_VOLUME_EXECUTED,order.Volume()-order.VolumeCurrent()); // 已执行的交易量
         event.SetProperty(EVENT_PROP_VOLUME_CURRENT,order.VolumeCurrent());                 // 剩余 (未执行) 交易量
         event.SetProperty(EVENT_PROP_PROFIT,order.Profit());                                // 盈利
         event.SetProperty(EVENT_PROP_SYMBOL,order.Symbol());                                // 订单品种
         //--- 设置控制程序所在图表 ID,解码事件代码,并设置事件类型
         event.SetChartID(this.m_chart_id);
         event.SetTypeEvent();
         //--- 如果事件对象不在列表中,则将其加入
         if(!this.IsPresentEventInList(event))
           {
            this.m_list_events.InsertSort(event);
            //--- 发送有关事件的消息,并设置最后一次交易事件的值
            event.SendEvent();
            this.m_trade_event=event.TradeEvent();
           }
         //--- 如果事件已存在于列表中,则删除新的事件对象,并显示调试消息
         else
           {
            ::Print(DFUN_ERR_LINE,TextByLanguage("Такое событие уже есть в списке","This event already in the list."));
            delete event;
           }
        }
     }
//---删除挂单
   if(status==ORDER_STATUS_HISTORY_PENDING)
     {
      trade_event_code=TRADE_EVENT_FLAG_ORDER_REMOVED;
      CEvent* event=new CEventOrderRemoved(trade_event_code,order.Ticket());
      if(event!=NULL)
        {
         ENUM_EVENT_REASON reason=
           (
            order.State()==ORDER_STATE_CANCELED ? EVENT_REASON_CANCEL :
            order.State()==ORDER_STATE_EXPIRED  ? EVENT_REASON_EXPIRED : EVENT_REASON_DONE
           );
         event.SetProperty(EVENT_PROP_TIME_EVENT,order.TimeCloseMSC());             // 事件时间
         event.SetProperty(EVENT_PROP_REASON_EVENT,reason);                         // 事件原因 (来自 ENUM_EVENT_REASON 枚举)
         event.SetProperty(EVENT_PROP_TYPE_DEAL_EVENT,order.TypeOrder());           // 事件成交类型
         event.SetProperty(EVENT_PROP_TICKET_DEAL_EVENT,order.Ticket());            // 事件成交票据
         event.SetProperty(EVENT_PROP_TYPE_ORDER_EVENT,order.TypeOrder());          // 触发事件成交的订单类型(最后一笔仓位订单)
         event.SetProperty(EVENT_PROP_TYPE_ORDER_POSITION,order.TypeOrder());       // 触发仓位成交的订单类型(首笔仓位订单)
         event.SetProperty(EVENT_PROP_TICKET_ORDER_EVENT,order.Ticket());           // 订单的票据,基于开仓成交(最后的仓位订单)
         event.SetProperty(EVENT_PROP_TICKET_ORDER_POSITION,order.Ticket());        // 订单的票据,基于开仓成交(首笔仓位订单)
         event.SetProperty(EVENT_PROP_POSITION_ID,order.PositionID());              // 仓位 ID
         event.SetProperty(EVENT_PROP_POSITION_BY_ID,order.PositionByID());         // 你想仓位 ID
         event.SetProperty(EVENT_PROP_MAGIC_ORDER,order.Magic());                   // 订单魔幻数字
         event.SetProperty(EVENT_PROP_TIME_ORDER_POSITION,order.TimeOpenMSC());     // 订单交易时间,基于开仓成交(首笔仓位订单)
         event.SetProperty(EVENT_PROP_PRICE_EVENT,order.PriceOpen());               // 事件价格
         event.SetProperty(EVENT_PROP_PRICE_OPEN,order.PriceOpen());                // 开单价格
         event.SetProperty(EVENT_PROP_PRICE_CLOSE,order.PriceClose());              // 平单价格
         event.SetProperty(EVENT_PROP_PRICE_SL,order.StopLoss());                   // 止损订单价格
         event.SetProperty(EVENT_PROP_PRICE_TP,order.TakeProfit());                 // 止盈订单价格
         event.SetProperty(EVENT_PROP_VOLUME_INITIAL,order.Volume());               // 请求的交易量
         event.SetProperty(EVENT_PROP_VOLUME_EXECUTED,order.Volume()-order.VolumeCurrent()); // 已执行的交易量
         event.SetProperty(EVENT_PROP_VOLUME_CURRENT,order.VolumeCurrent());        // 剩余 (未执行) 交易量
         event.SetProperty(EVENT_PROP_PROFIT,order.Profit());                       // 盈利
         event.SetProperty(EVENT_PROP_SYMBOL,order.Symbol());                       // 订单品种
         //--- 设置控制程序所在图表 ID,解码事件代码,并设置事件类型
         event.SetChartID(this.m_chart_id);
         event.SetTypeEvent();
         //--- 添加事件对象(如果列表中不存在)
         if(!this.IsPresentEventInList(event))
           {
            this.m_list_events.InsertSort(event);
            //--- 发送有关该事件的消息,并设置最后一个交易事件值
            event.SendEvent();
            this.m_trade_event=event.TradeEvent();
           }
         //--- 如果事件已在列表中,则删除新事件对象,并显示调试消息
         else
           {
            ::Print(DFUN_ERR_LINE,TextByLanguage("Такое событие уже есть в списке","This event already in the list."));
            delete event;
           }
        }
     }
//--- 开仓 (__MQL4__)
   if(status==ORDER_STATUS_MARKET_POSITION)
     {
      trade_event_code=TRADE_EVENT_FLAG_POSITION_OPENED;
      CEvent* event=new CEventPositionOpen(trade_event_code,order.Ticket());
      if(event!=NULL)
        {
         event.SetProperty(EVENT_PROP_TIME_EVENT,order.TimeOpen());              // 事件时间
         event.SetProperty(EVENT_PROP_REASON_EVENT,EVENT_REASON_DONE);           // 事件原因 (来自 ENUM_EVENT_REASON 枚举)
         event.SetProperty(EVENT_PROP_TYPE_DEAL_EVENT,order.TypeOrder());        // 事件成交类型
         event.SetProperty(EVENT_PROP_TICKET_DEAL_EVENT,order.Ticket());         // 事件成交票据
         event.SetProperty(EVENT_PROP_TYPE_ORDER_EVENT,order.TypeOrder());       // 订单的类型,基于开仓成交(最后的仓位订单)
         event.SetProperty(EVENT_PROP_TYPE_ORDER_POSITION,order.TypeOrder());    // 订单的类型,基于开仓成交(首笔仓位订单)
         event.SetProperty(EVENT_PROP_TICKET_ORDER_EVENT,order.Ticket());        // 订单的票据,基于开仓成交(最后的仓位订单)
         event.SetProperty(EVENT_PROP_TICKET_ORDER_POSITION,order.Ticket());     // 订单的票据,基于开仓成交(首笔仓位订单)
         event.SetProperty(EVENT_PROP_POSITION_ID,order.PositionID());           // 仓位 ID
         event.SetProperty(EVENT_PROP_POSITION_BY_ID,order.PositionByID());      // 逆向仓位 ID
         event.SetProperty(EVENT_PROP_MAGIC_ORDER,order.Magic());                // 订单/成交/仓位魔幻数字
         event.SetProperty(EVENT_PROP_TIME_ORDER_POSITION,order.TimeOpen());     // 订单时间,基于开仓成交 (首笔仓位订单)
         event.SetProperty(EVENT_PROP_PRICE_EVENT,order.PriceOpen());            // 事件价格
         event.SetProperty(EVENT_PROP_PRICE_OPEN,order.PriceOpen());             // 订单/成交/仓位开单价格
         event.SetProperty(EVENT_PROP_PRICE_CLOSE,order.PriceClose());           // 订单/成交/仓位平单价格
         event.SetProperty(EVENT_PROP_PRICE_SL,order.StopLoss());                // 止损仓位价格
         event.SetProperty(EVENT_PROP_PRICE_TP,order.TakeProfit());              // 止盈仓位价格
         event.SetProperty(EVENT_PROP_VOLUME_INITIAL,order.Volume());            // 请求的交易量
         event.SetProperty(EVENT_PROP_VOLUME_EXECUTED,order.Volume());           // 已执行的交易量
         event.SetProperty(EVENT_PROP_VOLUME_CURRENT,order.VolumeCurrent());     // 剩余 (未执行) 交易量
         event.SetProperty(EVENT_PROP_PROFIT,order.Profit());                    // 盈利
         event.SetProperty(EVENT_PROP_SYMBOL,order.Symbol());                    // 订单品种
         //--- 设置控制程序所在图表 ID,解码事件代码,并设置事件类型
         event.SetChartID(this.m_chart_id);
         event.SetTypeEvent();
         //--- 添加事件对象(如果列表中不存在)
         if(!this.IsPresentEventInList(event))
           {
            this.m_list_events.InsertSort(event);
            //--- 发送有关事件的消息,并设置最后一次交易事件的值
            event.SendEvent();
            this.m_trade_event=event.TradeEvent();
           }
         //--- 如果事件已存在于列表中,则删除新的事件对象,并显示调试消息
         else
           {
            ::Print(DFUN_ERR_LINE,TextByLanguage("Такое событие уже есть в списке","This event already in the list."));
            delete event;
           }
        }
     }
//--- 新成交 (__MQL5__)
   if(status==ORDER_STATUS_DEAL)
     {
      //--- 新余额操作
      if((ENUM_DEAL_TYPE)order.TypeOrder()>DEAL_TYPE_SELL)
        {
         trade_event_code=TRADE_EVENT_FLAG_ACCOUNT_BALANCE;
         CEvent* event=new CEventBalanceOperation(trade_event_code,order.Ticket());
         if(event!=NULL)
           {
            ENUM_EVENT_REASON reason=
              (
               (ENUM_DEAL_TYPE)order.TypeOrder()==DEAL_TYPE_BALANCE ? (order.Profit()>0 ? EVENT_REASON_BALANCE_REFILL : EVENT_REASON_BALANCE_WITHDRAWAL) :
               (ENUM_EVENT_REASON)(order.TypeOrder()+REASON_EVENT_SHIFT)
              );
            event.SetProperty(EVENT_PROP_TIME_EVENT,order.TimeOpenMSC());           // 事件时间
            event.SetProperty(EVENT_PROP_REASON_EVENT,reason);                      // 事件原因 (来自 ENUM_EVENT_REASON 枚举)
            event.SetProperty(EVENT_PROP_TYPE_DEAL_EVENT,order.TypeOrder());        // 事件成交类型
            event.SetProperty(EVENT_PROP_TICKET_DEAL_EVENT,order.Ticket());         // 事件成交票据
            event.SetProperty(EVENT_PROP_TYPE_ORDER_EVENT,order.TypeOrder());       // 触发事件成交的订单类型(最后一笔仓位订单)
            event.SetProperty(EVENT_PROP_TYPE_ORDER_POSITION,order.TypeOrder());    // 订单的类型,基于开仓成交(首笔仓位订单)
            event.SetProperty(EVENT_PROP_TICKET_ORDER_EVENT,order.Ticket());        // 订单的票据,基于开仓成交(最后的仓位订单)
            event.SetProperty(EVENT_PROP_TICKET_ORDER_POSITION,order.Ticket());     // 订单的票据,基于开仓成交(首笔仓位订单)
            event.SetProperty(EVENT_PROP_POSITION_ID,order.PositionID());           // 仓位 ID
            event.SetProperty(EVENT_PROP_POSITION_BY_ID,order.PositionByID());      // 逆向仓位 ID
            event.SetProperty(EVENT_PROP_MAGIC_ORDER,order.Magic());                // 订单/成交/仓位魔幻数字
            event.SetProperty(EVENT_PROP_TIME_ORDER_POSITION,order.TimeOpenMSC());  // 订单时间,基于开仓成交(首笔仓位订单)
            event.SetProperty(EVENT_PROP_PRICE_EVENT,order.PriceOpen());            // 事件价格
            event.SetProperty(EVENT_PROP_PRICE_OPEN,order.PriceOpen());             // 订单/成交/仓位开单价格
            event.SetProperty(EVENT_PROP_PRICE_CLOSE,order.PriceClose());           // 订单/成交/仓位平单价格
            event.SetProperty(EVENT_PROP_PRICE_SL,order.StopLoss());                // 止损成交价格
            event.SetProperty(EVENT_PROP_PRICE_TP,order.TakeProfit());              // 止盈成交价格
            event.SetProperty(EVENT_PROP_VOLUME_INITIAL,order.Volume());            // 请求的交易量
            event.SetProperty(EVENT_PROP_VOLUME_EXECUTED,order.Volume());           // 已执行的交易量
            event.SetProperty(EVENT_PROP_VOLUME_CURRENT,order.VolumeCurrent());     // 剩余 (未执行) 交易量
            event.SetProperty(EVENT_PROP_PROFIT,order.Profit());                    // 盈利
            event.SetProperty(EVENT_PROP_SYMBOL,order.Symbol());                    // 订单品种
            //--- 设置控制程序所在图表 ID,解码事件代码,并设置事件类型
            event.SetChartID(this.m_chart_id);
            event.SetTypeEvent();
            //--- 如果事件对象不在列表中,则将其加入
            if(!this.IsPresentEventInList(event))
              {
               //--- 发送有关该事件的消息,并设置最后一个交易事件值
               this.m_list_events.InsertSort(event);
               event.SendEvent();
               this.m_trade_event=event.TradeEvent();
              }
            //--- 如果事件已在列表中,则删除新事件对象,并显示调试消息
            else
              {
               ::Print(DFUN_ERR_LINE,TextByLanguage("Такое событие уже есть в списке","This event already in the list."));
               delete event;
              }
           }
        }
      //--- 如果这不是余额操作
      else
        {
         //--- 入场
         if(order.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_IN)
           {
            trade_event_code=TRADE_EVENT_FLAG_POSITION_OPENED;
            int reason=EVENT_REASON_DONE;
            //--- 按开仓方向寻找所有仓位成交,并计算其总交易量
            double volume_in=this.SummaryVolumeDealsInByPosID(list_history,order.PositionID());
            //--- 从所有仓位订单列表中取首笔和末笔仓位订单
            ulong order_ticket=order.GetProperty(ORDER_PROP_DEAL_ORDER);
            COrder* order_first=this.GetOrderByTicket(list_history,order_ticket);
            COrder* order_last=this.GetLastOrderFromList(list_history,order.PositionID());
            //--- 如果没有末笔订单,则首笔和末笔仓位订单重合
            if(order_last==NULL)
               order_last=order_first;
            if(order_first!=NULL)
              {
               //--- 如果是部分开仓订单量,则这是部分执行
               if(this.SummaryVolumeDealsInByPosID(list_history,order.PositionID())<order_first.Volume())
                 {
                  trade_event_code+=TRADE_EVENT_FLAG_PARTIAL;
                  reason=EVENT_REASON_DONE_PARTIALLY;
                 }
               //--- 如果是由挂单开单,则激活挂单
               if(order_first.TypeOrder()>ORDER_TYPE_SELL && order_first.TypeOrder()<ORDER_TYPE_CLOSE_BY)
                 {
                  trade_event_code+=TRADE_EVENT_FLAG_ORDER_ACTIVATED;
                  //--- 如果部分执行订单,则将部分订单执行设置为事件原因
                  reason=
                    (this.SummaryVolumeDealsInByPosID(list_history,order.PositionID())<order_first.Volume() ? 
                     EVENT_REASON_ACTIVATED_PENDING_PARTIALLY : 
                     EVENT_REASON_ACTIVATED_PENDING
                    );
                 }
               CEvent* event=new CEventPositionOpen(trade_event_code,order.PositionID());
               if(event!=NULL)
                 {
                  event.SetProperty(EVENT_PROP_TIME_EVENT,order.TimeOpenMSC());                 // 事件时间 (开仓时间)
                  event.SetProperty(EVENT_PROP_REASON_EVENT,reason);                            // 事件原因 (来自 ENUM_EVENT_REASON 枚举)
                  event.SetProperty(EVENT_PROP_TYPE_DEAL_EVENT,order.TypeOrder());              // 事件成交类型
                  event.SetProperty(EVENT_PROP_TICKET_DEAL_EVENT,order.Ticket());               // 事件成交票据
                  event.SetProperty(EVENT_PROP_TYPE_ORDER_POSITION,order_first.TypeOrder());    // 订单的类型,基于开仓成交(首笔仓位订单)
                  event.SetProperty(EVENT_PROP_TICKET_ORDER_POSITION,order_first.Ticket());     // 订单的票据,基于开仓成交(末笔仓位订单)
                  event.SetProperty(EVENT_PROP_TYPE_ORDER_EVENT,order_last.TypeOrder());        // 订单的类型,基于开仓成交(末笔仓位订单)
                  event.SetProperty(EVENT_PROP_TICKET_ORDER_EVENT,order_last.Ticket());         // 订单的票据,基于开仓成交(末笔仓位订单)
                  event.SetProperty(EVENT_PROP_POSITION_ID,order.PositionID());                 // 仓位 ID
                  event.SetProperty(EVENT_PROP_POSITION_BY_ID,order_last.PositionByID());       // 逆向仓位 ID
                  event.SetProperty(EVENT_PROP_MAGIC_ORDER,order.Magic());                      // 订单/成交/仓位魔幻数字
                  event.SetProperty(EVENT_PROP_TIME_ORDER_POSITION,order_first.TimeOpenMSC());  // 订单时间,基于开仓成交(首笔仓位订单)
                  event.SetProperty(EVENT_PROP_PRICE_EVENT,order.PriceOpen());                  // 事件价格 (开仓价格)
                  event.SetProperty(EVENT_PROP_PRICE_OPEN,order_first.PriceOpen());             // 开单价格 (开仓订单价格)
                  event.SetProperty(EVENT_PROP_PRICE_CLOSE,order_last.PriceClose());            // 平单价格 (末笔平仓订单价格)
                  event.SetProperty(EVENT_PROP_PRICE_SL,order_first.StopLoss());                // 止损价格 (仓位订单止损价格)
                  event.SetProperty(EVENT_PROP_PRICE_TP,order_first.TakeProfit());              // 止盈价格 (仓位订单止盈价格)
                  event.SetProperty(EVENT_PROP_VOLUME_INITIAL,order_first.Volume());            // 请求的交易量
                  event.SetProperty(EVENT_PROP_VOLUME_EXECUTED,volume_in);                      // 已执行的交易量
                  event.SetProperty(EVENT_PROP_VOLUME_CURRENT,order_first.Volume()-volume_in);  // 剩余 (未执行) 交易量
                  event.SetProperty(EVENT_PROP_PROFIT,order.ProfitFull());                      // 盈利
                  event.SetProperty(EVENT_PROP_SYMBOL,order.Symbol());                          // 订单品种
                  //--- 设置控制程序所在图表 ID,解码事件代码,并设置事件类型
                  event.SetChartID(this.m_chart_id);
                  event.SetTypeEvent();
                  //--- 如果部分执行订单,则将部分订单执行设置为事件原因
                  if(!this.IsPresentEventInList(event))
                    {
                     this.m_list_events.InsertSort(event);
                     //--- 发送有关该事件的消息,并设置最后一个交易事件值
                     event.SendEvent();
                     this.m_trade_event=event.TradeEvent();
                    }
                  //--- 如果事件已在列表中,则删除新事件对象,并显示调试消息
                  else
                    {
                     ::Print(DFUN_ERR_LINE,TextByLanguage("Такое событие уже есть в списке","This event already in the list."));
                     delete event;
                    }
                 }
              }
           }
         //--- 离场
         else if(order.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_OUT)
           {
            trade_event_code=TRADE_EVENT_FLAG_POSITION_CLOSED;
            int reason=EVENT_REASON_DONE;
            //--- 从所有仓位订单列表中取首笔和末笔仓位订单
            COrder* order_first=this.GetFirstOrderFromList(list_history,order.PositionID());
            COrder* order_last=this.GetLastOrderFromList(list_history,order.PositionID());
            if(order_first!=NULL && order_last!=NULL)
              {
               //--- 按开/平仓方向寻找所有仓位成交,并计算其总交易量
               double volume_in=this.SummaryVolumeDealsInByPosID(list_history,order.PositionID());
               double volume_out=this.SummaryVolumeDealsOutByPosID(list_history,order.PositionID());
               //--- 计算当前的平仓交易量
               int dgl=(int)DigitsLots(order.Symbol());
               double volume_current=::NormalizeDouble(volume_in-volume_out,dgl);
               //--- 如果是部分平仓订单量,则这是部分执行
               if(volume_current>0)
                 {
                  trade_event_code+=TRADE_EVENT_FLAG_PARTIAL;
                 }
               //--- 如果是部分执行平仓,则将部分平仓执行设置为事件原因
               if(order_last.VolumeCurrent()>0)
                 {
                  reason=EVENT_REASON_DONE_PARTIALLY;
                 }
               //--- 如果平仓标志设为止损,则是由止损执行平仓
               //--- 如果止损订单部分执行,则将止损订单部分执行设置为事件原因
               if(order_last.IsCloseByStopLoss())
                 {
                  trade_event_code+=TRADE_EVENT_FLAG_SL;
                  reason=(order_last.VolumeCurrent()>0 ? EVENT_REASON_DONE_SL_PARTIALLY : EVENT_REASON_DONE_SL);
                 }
               //--- 如果平仓标志设为止盈,则是由止盈执行平仓
               //--- 如果止盈订单部分执行,则将止盈订单部分执行设置为事件原因
               else if(order_last.IsCloseByTakeProfit())
                 {
                  trade_event_code+=TRADE_EVENT_FLAG_TP;
                  reason=(order_last.VolumeCurrent()>0 ? EVENT_REASON_DONE_TP_PARTIALLY : EVENT_REASON_DONE_TP);
                 }
               //---
               CEvent* event=new CEventPositionClose(trade_event_code,order.PositionID());
               if(event!=NULL)
                 {
                  event.SetProperty(EVENT_PROP_TIME_EVENT,order.TimeOpenMSC());                 // 事件时间 (平仓时间)
                  event.SetProperty(EVENT_PROP_REASON_EVENT,reason);                            // 事件原因 (来自 ENUM_EVENT_REASON 枚举)
                  event.SetProperty(EVENT_PROP_TYPE_DEAL_EVENT,order.TypeOrder());              // 事件成交类型
                  event.SetProperty(EVENT_PROP_TICKET_DEAL_EVENT,order.Ticket());               // 事件成交票据
                  event.SetProperty(EVENT_PROP_TYPE_ORDER_POSITION,order_first.TypeOrder());    // 订单的类型,基于开仓成交(首笔仓位订单)
                  event.SetProperty(EVENT_PROP_TYPE_ORDER_EVENT,order_last.TypeOrder());        // 订单的类型,基于开仓成交(末笔仓位订单)
                  event.SetProperty(EVENT_PROP_TICKET_ORDER_POSITION,order_first.Ticket());     // 订单的票据,基于开仓成交(首笔仓位订单)
                  event.SetProperty(EVENT_PROP_TICKET_ORDER_EVENT,order_last.Ticket());         // 订单的票据,基于开仓成交(末笔仓位订单)
                  event.SetProperty(EVENT_PROP_POSITION_ID,order.PositionID());                 // 仓位 ID
                  event.SetProperty(EVENT_PROP_POSITION_BY_ID,order_last.PositionByID());       // 逆向仓位 ID
                  event.SetProperty(EVENT_PROP_MAGIC_ORDER,order.Magic());                      // 订单/成交/仓位魔幻数字
                  event.SetProperty(EVENT_PROP_TIME_ORDER_POSITION,order_first.TimeOpenMSC());  // 订单时间,基于开仓成交(首笔仓位订单)
                  event.SetProperty(EVENT_PROP_PRICE_EVENT,order.PriceOpen());                  // 事件价格 (平仓价格)
                  event.SetProperty(EVENT_PROP_PRICE_OPEN,order_first.PriceOpen());             // 开单价格 (开仓订单价格)
                  event.SetProperty(EVENT_PROP_PRICE_CLOSE,order_last.PriceClose());            // 平单价格 (末笔平仓订单价格)
                  event.SetProperty(EVENT_PROP_PRICE_SL,order_first.StopLoss());                // 止损价格 (仓位订单止损价格)
                  event.SetProperty(EVENT_PROP_PRICE_TP,order_first.TakeProfit());              // 止盈价格 (仓位订单止盈价格)
                  event.SetProperty(EVENT_PROP_VOLUME_INITIAL,volume_in);                       // 初始交易量
                  event.SetProperty(EVENT_PROP_VOLUME_EXECUTED,order.Volume());                 // 平仓交易量
                  event.SetProperty(EVENT_PROP_VOLUME_CURRENT,volume_in-volume_out);            // 剩余 (当前) 交易量
                  event.SetProperty(EVENT_PROP_PROFIT,order.ProfitFull());                      // 盈利
                  event.SetProperty(EVENT_PROP_SYMBOL,order.Symbol());                          // 订单品种
                  //--- 设置控制程序所在图表 ID,解码事件代码,并设置事件类型
                  event.SetChartID(this.m_chart_id);
                  event.SetTypeEvent();
                  //--- 如果部分执行订单,则将部分订单执行设置为事件原因
                  if(!this.IsPresentEventInList(event))
                    {
                     this.m_list_events.InsertSort(event);
                     //--- 发送有关该事件的消息,并设置最后一个交易事件值
                     event.SendEvent();
                     this.m_trade_event=event.TradeEvent();
                    }
                  //--- 如果事件已在列表中,则删除新事件对象,并显示调试消息
                  else
                    {
                     ::Print(DFUN_ERR_LINE,TextByLanguage("Такое событие уже есть в списке","This event already in the list."));
                     delete event;
                    }
                 }
              }
           }
         //--- 逆向仓位
         else if(order.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_OUT_BY)
           {
            trade_event_code=TRADE_EVENT_FLAG_POSITION_CLOSED;
            int reason=EVENT_REASON_DONE_BY_POS;
            //--- 从所有仓位订单列表中取首笔和末笔仓位订单
            COrder* order_first=this.GetFirstOrderFromList(list_history,order.PositionID());
            COrder* order_close=this.GetCloseByOrderFromList(list_history,order.PositionID());
            if(order_first!=NULL && order_close!=NULL)
              {
               //--- 添加因逆向仓位平仓的标志
               trade_event_code+=TRADE_EVENT_FLAG_BY_POS;
               //--- 在开/平仓方向寻找所有平仓成交,并计算其总交易量
               double volume_in=this.SummaryVolumeDealsInByPosID(list_history,order.PositionID());
               double volume_out=this.SummaryVolumeDealsOutByPosID(list_history,order.PositionID());//+order_close.Volume();
               //--- 计算当前的平仓交易量
               int dgl=(int)DigitsLots(order.Symbol());
               double volume_current=::NormalizeDouble(volume_in-volume_out,dgl);
               //--- 在开/平仓方向寻找所有逆向仓位成交,并计算其总交易量
               double volume_opp_in=this.SummaryVolumeDealsInByPosID(list_history,order_close.PositionByID());
               double volume_opp_out=this.SummaryVolumeDealsOutByPosID(list_history,order_close.PositionByID());//+order_close.Volume();
               //--- 计算逆向仓位的当前交易量
               double volume_opp_current=::NormalizeDouble(volume_opp_in-volume_opp_out,dgl);
               //--- 如果平仓交易量为部分平仓,则这是部分平仓
               if(volume_current>0 || order_close.VolumeCurrent()>0)
                 {
                  //--- 添加部分平仓标志
                  trade_event_code+=TRADE_EVENT_FLAG_PARTIAL;
                  //--- 如果逆向仓位部分平仓,则逆向仓位交易量部分平仓
                  reason=(volume_opp_current>0 ? EVENT_REASON_DONE_PARTIALLY_BY_POS_PARTIALLY : EVENT_REASON_DONE_PARTIALLY_BY_POS);
                 }
               //--- 如果仓位交易量完全因逆向仓位的部分执行交易量所平仓,则因逆向仓位交易量部分平仓
               else
                 {
                  if(volume_opp_current>0)
                    {
                     reason=EVENT_REASON_DONE_BY_POS_PARTIALLY;
                    }
                 }
               CEvent* event=new CEventPositionClose(trade_event_code,order.PositionID());
               if(event!=NULL)
                 {
                  event.SetProperty(EVENT_PROP_TIME_EVENT,order.TimeOpenMSC());                 // 事件时间
                  event.SetProperty(EVENT_PROP_REASON_EVENT,reason);                            // 事件原因 (来自 ENUM_EVENT_REASON 枚举)
                  event.SetProperty(EVENT_PROP_TYPE_DEAL_EVENT,order.TypeOrder());              // 事件成交类型
                  event.SetProperty(EVENT_PROP_TICKET_DEAL_EVENT,order.Ticket());               // 事件成交票据
                  event.SetProperty(EVENT_PROP_TYPE_ORDER_EVENT,order_close.TypeOrder());       // 订单的类型,基于开仓成交(最后的仓位订单)
                  event.SetProperty(EVENT_PROP_TICKET_ORDER_EVENT,order_close.Ticket());        // 订单的票据,基于开仓成交(最后的仓位订单)
                  event.SetProperty(EVENT_PROP_TIME_ORDER_POSITION,order_first.TimeOpenMSC());  // 订单时间,基于开仓成交(首笔仓位订单)
                  event.SetProperty(EVENT_PROP_TYPE_ORDER_POSITION,order_first.TypeOrder());    // 订单的类型,基于开仓成交(首笔仓位订单)
                  event.SetProperty(EVENT_PROP_TICKET_ORDER_POSITION,order_first.Ticket());     // 订单的票据,基于开仓成交(首笔仓位订单)
                  event.SetProperty(EVENT_PROP_POSITION_ID,order.PositionID());                 // 仓位 ID
                  event.SetProperty(EVENT_PROP_POSITION_BY_ID,order_close.PositionByID());      // 逆向仓位 ID
                  event.SetProperty(EVENT_PROP_MAGIC_ORDER,order.Magic());                      // 订单/成交/仓位魔幻数字
                  event.SetProperty(EVENT_PROP_PRICE_EVENT,order.PriceOpen());                  // 事件价格
                  event.SetProperty(EVENT_PROP_PRICE_OPEN,order_first.PriceOpen());             // 订单/成交/仓位开单价格
                  event.SetProperty(EVENT_PROP_PRICE_CLOSE,order.PriceClose());                 // 订单/成交/仓位平单价格
                  event.SetProperty(EVENT_PROP_PRICE_SL,order_first.StopLoss());                // 止损价格 (仓位订单止损价格)
                  event.SetProperty(EVENT_PROP_PRICE_TP,order_first.TakeProfit());              // 止盈价格 (仓位订单止盈价格)
                  event.SetProperty(EVENT_PROP_VOLUME_INITIAL,::NormalizeDouble(volume_in,dgl));// 初始交易量
                  event.SetProperty(EVENT_PROP_VOLUME_EXECUTED,order.Volume());                 // 平仓交易量
                  event.SetProperty(EVENT_PROP_VOLUME_CURRENT,volume_current);                  // 剩余 (当前) 交易量
                  event.SetProperty(EVENT_PROP_PROFIT,order.ProfitFull());                      // 盈利
                  event.SetProperty(EVENT_PROP_SYMBOL,order.Symbol());                          // 订单品种
                  //--- 设置控制程序所在图表 ID,解码事件代码,并设置事件类型
                  event.SetChartID(this.m_chart_id);
                  event.SetTypeEvent();
                  //--- 如果事件对象不在列表中,则将其加入
                  if(!this.IsPresentEventInList(event))
                    {
                     this.m_list_events.InsertSort(event);
                     //--- 发送有关事件的消息,并设置最后一次交易事件的值
                     event.SendEvent();
                     this.m_trade_event=event.TradeEvent();
                    }
                  //--- 如果事件已存在于列表中,则删除新的事件对象,并显示调试消息
                  else
                    {
                     ::Print(DFUN_ERR_LINE,TextByLanguage("Такое событие уже есть в списке","This event already in the list."));
                     delete event;
                    }
                 }
              }
           }
         //--- 逆转
         else if(order.GetProperty(ORDER_PROP_DEAL_ENTRY)==DEAL_ENTRY_INOUT)
           {
            //--- 仓位逆转
            Print(DFUN,"Position reversal");
            order.Print();
           }
        }
     }
  }
//+------------------------------------------------------------------+

结果证明这个方法非常冗长。 因此,所有必要检查和相应操作的说明都直接在清单中提供。

该方法根据其类型(放置挂单,删除挂单,成交)检查所传递订单的状态,以及所发生事件的所有要素。 创建一个新事件,并填充与订单和事件类型相对应的数据,同时将事件放入事件集合中,最后将有关此事件的消息发送到控制程序图表,且变量存储最后一次发生事件的类型。

事件集合类已准备就绪。 现在我们需要将它包含在函数库的基础对象中。

在创建事件集合类之后,我们在 CEngine 基础对象类的第四部分中所完成的跟踪事件的任务就是多余的,因此应该修改基础对象。

  1. 从类中删除存储交易事件状态代码的 m_trade_event_code 私有成员变量。
  2. 删除私有方法:
    1. SetTradeEvent() 方法解码事件代码,
    2. IsTradeEventFlag() 方法返回交易事件中的标志,
    3. WorkWithHedgeCollections() 和 WorkWithNettoCollections() 方法处理对冲和净持账户集合
    4. 且 TradeEventCode() 方法返回交易事件代码

在类的实体里加入包含交易事件集合类文件,在类的私有部分声明事件集合对象,添加处理事件的 TradeEventsControl() 方法,在公有部分中将 GetListHistoryDeals() 方法名称更改为 GetListDeals()。 成交始终位于历史收集中,所以我相信,没有必要在方法名称中明确提及该集合。 我们修改重置最后一个交易事件的方法实现:因为我们现在从事件集合类接收最后一个事件,并且重置最后一个事件的方法存在于类的内部,我们只需从事件集合类调用 来自类的 ResetLastTradeEvent() 同名方法。

//+------------------------------------------------------------------+
//|                                                       Engine.mqh |
//|                                  版权所有 2018, MetaQuotes 软件公司 |
//|                             https://mql5.com/zh/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "版权所有 2018, MetaQuotes 软件公司"
#property link      "https://mql5.com/zh/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| 包含文件                                                           |
//+------------------------------------------------------------------+
#include "Collections\HistoryCollection.mqh"
#include "Collections\MarketCollection.mqh"
#include "Collections\EventsCollection.mqh"
#include "Services\TimerCounter.mqh"
//+------------------------------------------------------------------+
//| 函数库基类                                                           |
//+------------------------------------------------------------------+
class CEngine : public CObject
  {
private:
   CHistoryCollection   m_history;                       // 历史订单和成家集合
   CMarketCollection    m_market;                        // 入场订单和成交集合
   CEventsCollection    m_events;                        // 事件集合
   CArrayObj            m_list_counters;                 // 定时器计数器列表
   bool                 m_first_start;                   // 首次启动标志
   bool                 m_is_hedge;                      // 对冲账户标志
   bool                 m_is_market_trade_event;         // 账户交易事件标志
   bool                 m_is_history_trade_event;        // 帐户历史交易事件标志
   ENUM_TRADE_EVENT     m_acc_trade_event;               // 账户交易事件
//--- 按 id 返回计数器索引
   int                  CounterIndex(const int id) const;
//--- 返回 (1) 首次启动标志, (2) 在交易事件中存在标志
   bool                 IsFirstStart(void);
//--- 处理事件
   void                 TradeEventsControl(void);
//--- 按票据返回最后的 (1) 在场挂单, (2) 入场订单, (3) 最仓位后, (4) 仓位
   COrder*              GetLastMarketPending(void);
   COrder*              GetLastMarketOrder(void);
   COrder*              GetLastPosition(void);
   COrder*              GetPosition(const ulong ticket);
//--- 按票据返回最后的 (1) 删除挂单, (2) 历史订单, (3) 历史订单 (入场或挂单)
   COrder*              GetLastHistoryPending(void);
   COrder*              GetLastHistoryOrder(void);
   COrder*              GetHistoryOrder(const ulong ticket);
//--- 返回 (1) 首次,和 (2) 所有仓位订单里的最后一次历史订单, (3) 最后的成交
   COrder*              GetFirstOrderPosition(const ulong position_id);
   COrder*              GetLastOrderPosition(const ulong position_id);
   COrder*              GetLastDeal(void);
public:
   //--- 返回入场列表 (1) 仓位, (2) 挂单,和 (3) 入场订单
   CArrayObj*           GetListMarketPosition(void);
   CArrayObj*           GetListMarketPendings(void);
   CArrayObj*           GetListMarketOrders(void);
   //--- 按其 id 返回历史列表 (1) 订单, (2) 删除挂单, (3) 成交, (4) 所有仓位订单
   CArrayObj*           GetListHistoryOrders(void);
   CArrayObj*           GetListHistoryPendings(void);
   CArrayObj*           GetListDeals(void);
   CArrayObj*           GetListAllOrdersByPosID(const ulong position_id);
//--- 重置最后的交易事件
   void                 ResetLastTradeEvent(void)                       { this.m_events.ResetLastTradeEvent(); }
//--- 返回(1)最后交易事件,和(2)对冲账户标志
   ENUM_TRADE_EVENT     LastTradeEvent(void)                      const { return this.m_acc_trade_event;       }
   bool                 IsHedge(void)                             const { return this.m_is_hedge;              }
//--- 创建计时器
   void                 CreateCounter(const int id,const ulong frequency,const ulong pause);
//--- 定时器
   void                 OnTimer(void);
//--- 构造函数/析构函数
                        CEngine();
                       ~CEngine();
  };
//+------------------------------------------------------------------+

在 CEngine 类构造函数中,添加处理毫秒计时器的进展结果。 如果未创建,则在日记中显示相应的消息。 接着,我们将开发用于处理某些错误的类,基于函数库设置直观的标志,并处理错误状况。

//+------------------------------------------------------------------+
//| CEngine 构造函数                                                   |
//+------------------------------------------------------------------+
CEngine::CEngine() : m_first_start(true),m_acc_trade_event(TRADE_EVENT_NO_EVENT)
  {
   ::ResetLastError();
   if(!::EventSetMillisecondTimer(TIMER_FREQUENCY))
      Print(DFUN,"Не удалось создать таймер. Ошибка: ","Could not create timer. Error: ",(string)::GetLastError());
   this.m_list_counters.Sort();
   this.m_list_counters.Clear();
   this.CreateCounter(COLLECTION_COUNTER_ID,COLLECTION_COUNTER_STEP,COLLECTION_PAUSE);
   this.m_is_hedge=bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
  }
//+------------------------------------------------------------------+

在类定时器中,在订单、成交和仓位集合的计时器取消暂停后调用 TradeEventsControl() 方法。

//+------------------------------------------------------------------+
//| CEngine 定时器                                                    |
//+------------------------------------------------------------------+
void CEngine::OnTimer(void)
  {
//--- 历史订单、成交、入场订单和仓位集合的计时器
   int index=this.CounterIndex(COLLECTION_COUNTER_ID);
   if(index>WRONG_VALUE)
     {
      CTimerCounter* counter=this.m_list_counters.At(index);
      //--- 如果取消暂停,则处理集合事件
      if(counter!=NULL && counter.IsTimeDone())
        {
         this.TradeEventsControl();
        }
     }
  }
//+------------------------------------------------------------------+

我们来改进按票据返回历史订单的方法。 由于历史集合列表可能包含挂单,激活的入场订单以及因逆向仓位平仓的订单,我们需要考虑所有订单类型。

若要执行此操作,首先,在列表内按票据搜索入场和离场订单。 如果列表为空,则搜索具有相同票据的已删除挂单。 如果列表不包含订单,则返回 NULL。 否则,程序将返回列表中所查找订单的第一个元素。 如果从列表中接收订单失败,则返回 NULL。

//+------------------------------------------------------------------+
//| 按票据返回历史订单列表                                               |
//+------------------------------------------------------------------+
COrder* CEngine::GetHistoryOrder(const ulong ticket)
  {
   CArrayObj* list=this.GetListHistoryOrders();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_TICKET,(long)ticket,EQUAL);
   if(list==NULL || list.Total()==0)
     {
      list=this.GetListHistoryPendings();
      list=CSelect::ByOrderProperty(list,ORDER_PROP_TICKET,(long)ticket,EQUAL);
      if(list==NULL) return NULL;
     }
   COrder* order=list.At(0);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+

我们实现 TradeEventsControl() 方法来处理帐户事件:

//+------------------------------------------------------------------+
//| 检查交易事件                                                        |
//+------------------------------------------------------------------+
void CEngine::TradeEventsControl(void)
  {
//--- 初始化交易事件的代码和标志
   this.m_is_market_trade_event=false;
   this.m_is_history_trade_event=false;
//--- 更新列表 
   this.m_market.Refresh();
   this.m_history.Refresh();
//--- 第一次启动时的动作
   if(this.IsFirstStart())
     {
      this.m_acc_trade_event=TRADE_EVENT_NO_EVENT;
      return;
     }
//--- 检查市场状态和帐户历史记录中的变化 
   this.m_is_market_trade_event=this.m_market.IsTradeEvent();
   this.m_is_history_trade_event=this.m_history.IsTradeEvent();

//--- 如果发生任何事件,则将列表、标志以及新订单和成交的数量发送到事件集合,并更新
   if(this.m_is_history_trade_event || this.m_is_market_trade_event)
     {
      this.m_events.Refresh(this.m_history.GetList(),this.m_market.GetList(),
                            this.m_is_history_trade_event,this.m_is_market_trade_event,
                            this.m_history.NewOrders(),this.m_market.NewPendingOrders(),
                            this.m_market.NewMarketOrders(),this.m_history.NewDeals());
      //--- 获取最后一次帐户交易事件
      this.m_acc_trade_event=this.m_events.GetLastTradeEvent();
     }
  }

与函数第四部分中所述的前一个 WorkWithHedgeCollections() 相比,此方法要简短得多。

该方法很简单,无需过多解释。 代码包含所有注释,您可以理解其简单的逻辑。

以下是更新的 CEngine 类完整清单:

//+------------------------------------------------------------------+
//|                                                       Engine.mqh |
//|                                  版权所有 2018, MetaQuotes 软件公司 |
//|                             https://mql5.com/zh/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "版权所有 2018, MetaQuotes 软件公司"
#property link      "https://mql5.com/zh/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| 包含文件                                                           |
//+------------------------------------------------------------------+
#include "Collections\HistoryCollection.mqh"
#include "Collections\MarketCollection.mqh"
#include "Collections\EventsCollection.mqh"
#include "Services\TimerCounter.mqh"
//+------------------------------------------------------------------+
//| 函数库基类                                                           |
//+------------------------------------------------------------------+
class CEngine : public CObject
  {
private:
   CHistoryCollection   m_history;                       // 历史订单和成家集合
   CMarketCollection    m_market;                        // 入场订单和成交集合
   CEventsCollection    m_events;                        // 事件集合
   CArrayObj            m_list_counters;                 // 定时器计数器列表
   bool                 m_first_start;                   // 首次启动标志
   bool                 m_is_hedge;                      // 对冲账户标志
   bool                 m_is_market_trade_event;         // 账户交易事件标志
   bool                 m_is_history_trade_event;        // 帐户历史交易事件标志
   ENUM_TRADE_EVENT     m_acc_trade_event;               // 账户交易事件
//--- 按 id 返回计数器索引
   int                  CounterIndex(const int id) const;
//--- 返回 (1) 首次启动标志, (2) 在交易事件中存在标志
   bool                 IsFirstStart(void);
//--- 处理事件
   void                 TradeEventsControl(void);
//--- 按票据返回最后的 (1) 在场挂单, (2) 入场订单, (3) 最仓位后, (4) 仓位
   COrder*              GetLastMarketPending(void);
   COrder*              GetLastMarketOrder(void);
   COrder*              GetLastPosition(void);
   COrder*              GetPosition(const ulong ticket);
//--- 按票据返回最后的 (1) 删除挂单, (2) 历史订单, (3) 历史订单 (入场或挂单)
   COrder*              GetLastHistoryPending(void);
   COrder*              GetLastHistoryOrder(void);
   COrder*              GetHistoryOrder(const ulong ticket);
//--- 返回 (1) 首次,和 (2) 所有仓位订单里的最后一次历史订单, (3) 最后的成交
   COrder*              GetFirstOrderPosition(const ulong position_id);
   COrder*              GetLastOrderPosition(const ulong position_id);
   COrder*              GetLastDeal(void);
public:
   //--- 返回入场列表 (1) 仓位, (2) 挂单,和 (3) 入场订单
   CArrayObj*           GetListMarketPosition(void);
   CArrayObj*           GetListMarketPendings(void);
   CArrayObj*           GetListMarketOrders(void);
   //--- 按其 id 返回历史列表 (1) 订单, (2) 删除挂单, (3) 成交, (4) 所有仓位订单
   CArrayObj*           GetListHistoryOrders(void);
   CArrayObj*           GetListHistoryPendings(void);
   CArrayObj*           GetListDeals(void);
   CArrayObj*           GetListAllOrdersByPosID(const ulong position_id);
//--- 重置最后的交易事件
   void                 ResetLastTradeEvent(void)                       { this.m_events.ResetLastTradeEvent(); }
//--- 返回(1)最后交易事件,和(2)对冲账户标志
   ENUM_TRADE_EVENT     LastTradeEvent(void)                      const { return this.m_acc_trade_event;       }
   bool                 IsHedge(void)                             const { return this.m_is_hedge;              }
//--- 创建计时器
   void                 CreateCounter(const int id,const ulong frequency,const ulong pause);
//--- 定时器
   void                 OnTimer(void);
//--- 构造函数/析构函数
                        CEngine();
                       ~CEngine();
  };
//+------------------------------------------------------------------+
//| CEngine 构造函数                                                   |
//+------------------------------------------------------------------+
CEngine::CEngine() : m_first_start(true),m_acc_trade_event(TRADE_EVENT_NO_EVENT)
  {
   ::ResetLastError();
   if(!::EventSetMillisecondTimer(TIMER_FREQUENCY))
      Print(DFUN,"Не удалось создать таймер. Ошибка: ","Could not create timer. Error: ",(string)::GetLastError());
   this.m_list_counters.Sort();
   this.m_list_counters.Clear();
   this.CreateCounter(COLLECTION_COUNTER_ID,COLLECTION_COUNTER_STEP,COLLECTION_PAUSE);
   this.m_is_hedge=bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
  }
//+------------------------------------------------------------------+
//| CEngine 析构函数                                                   |
//+------------------------------------------------------------------+
CEngine::~CEngine()
  {
   ::EventKillTimer();
  }
//+------------------------------------------------------------------+
//| CEngine 定时器                                                    |
//+------------------------------------------------------------------+
void CEngine::OnTimer(void)
  {
//--- 历史订单、成交、入场订单和仓位集合的计时器
   int index=this.CounterIndex(COLLECTION_COUNTER_ID);
   if(index>WRONG_VALUE)
     {
      CTimerCounter* counter=this.m_list_counters.At(index);
      //--- 如果取消暂停,则处理集合事件
      if(counter!=NULL && counter.IsTimeDone())
        {
         this.TradeEventsControl();
        }
     }
  }
//+------------------------------------------------------------------+
//| 创建订时器的计数器                                                   |
//+------------------------------------------------------------------+
void CEngine::CreateCounter(const int id,const ulong step,const ulong pause)
  {
   if(this.CounterIndex(id)>WRONG_VALUE)
     {
      ::Print(TextByLanguage("Ошибка. Уже создан счётчик с идентификатором ","Error. Already created counter with id "),(string)id);
      return;
     }
   m_list_counters.Sort();
   CTimerCounter* counter=new CTimerCounter(id);
   if(counter==NULL)
      ::Print(TextByLanguage("Не удалось создать счётчик таймера ","Failed to create timer counter "),(string)id);
   counter.SetParams(step,pause);
   if(this.m_list_counters.Search(counter)==WRONG_VALUE)
      this.m_list_counters.Add(counter);
   else
     {
      string t1=TextByLanguage("Ошибка. Счётчик с идентификатором ","Error. Counter with ID ")+(string)id;
      string t2=TextByLanguage(", шагом ",", step ")+(string)step;
      string t3=TextByLanguage(" и паузой "," and pause ")+(string)pause;
      ::Print(t1+t2+t3+TextByLanguage(" уже существует"," already exists"));
      delete counter;
     }
  }
//+------------------------------------------------------------------+
//| 按 id 返回计数器在列表中的索引                                          |
//+------------------------------------------------------------------+
int CEngine::CounterIndex(const int id) const
  {
   int total=this.m_list_counters.Total();
   for(int i=0;i<total;i++)
     {
      CTimerCounter* counter=this.m_list_counters.At(i);
      if(counter==NULL) continue;
      if(counter.Type()==id) 
         return i;
     }
   return WRONG_VALUE;
  }
//+------------------------------------------------------------------+
//| 返回首次启动标志,重置标志                                            |
//+------------------------------------------------------------------+
bool CEngine::IsFirstStart(void)
  {
   if(this.m_first_start)
     {
      this.m_first_start=false;
      return true;
     }
   return false;
  }
//+------------------------------------------------------------------+
//| 检查交易事件                                                        |
//+------------------------------------------------------------------+
void CEngine::TradeEventsControl(void)
  {
//--- 初始化交易事件代码和标志
   this.m_is_market_trade_event=false;
   this.m_is_history_trade_event=false;
//--- 更新列表 
   this.m_market.Refresh();
   this.m_history.Refresh();
//--- 第一次启动时的动作
   if(this.IsFirstStart())
     {
      this.m_acc_trade_event=TRADE_EVENT_NO_EVENT;
      return;
     }
//--- 检查市场状态和帐户历史记录的变化 
   this.m_is_market_trade_event=this.m_market.IsTradeEvent();
   this.m_is_history_trade_event=this.m_history.IsTradeEvent();

//--- 如果有任何事件,则将列表、标志以及新订单和成交的数量发送到事件集合,然后进行更新
   if(this.m_is_history_trade_event || this.m_is_market_trade_event)
     {
      this.m_events.Refresh(this.m_history.GetList(),this.m_market.GetList(),
                            this.m_is_history_trade_event,this.m_is_market_trade_event,
                            this.m_history.NewOrders(),this.m_market.NewPendingOrders(),
                            this.m_market.NewMarketOrders(),this.m_history.NewDeals());
      //--- 接收最后一次帐户交易事件
      this.m_acc_trade_event=this.m_events.GetLastTradeEvent();
     }
  }
//+------------------------------------------------------------------+
//| 返回持仓列表                                                        |
//+------------------------------------------------------------------+
CArrayObj* CEngine::GetListMarketPosition(void)
  {
   CArrayObj* list=this.m_market.GetList();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_MARKET_POSITION,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+
//| 返回在场挂单列表                                                      |
//+------------------------------------------------------------------+
CArrayObj* CEngine::GetListMarketPendings(void)
  {
   CArrayObj* list=this.m_market.GetList();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_MARKET_PENDING,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+
//| 返回入场订单列表                                                    |
//+------------------------------------------------------------------+
CArrayObj* CEngine::GetListMarketOrders(void)
  {
   CArrayObj* list=this.m_market.GetList();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_MARKET_ORDER,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+
//| 返回历史订单列表                                                    |
//+------------------------------------------------------------------+
CArrayObj* CEngine::GetListHistoryOrders(void)
  {
   CArrayObj* list=this.m_history.GetList();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_HISTORY_ORDER,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+
//| 返回已删除挂单列表                                                   |
//+------------------------------------------------------------------+
CArrayObj* CEngine::GetListHistoryPendings(void)
  {
   CArrayObj* list=this.m_history.GetList();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_HISTORY_PENDING,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+
//| 返回成交列表                                                        |
//+------------------------------------------------------------------+
CArrayObj* CEngine::GetListDeals(void)
  {
   CArrayObj* list=this.m_history.GetList();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_DEAL,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+
//|  返回所有仓位订单列表                                                 |
//+------------------------------------------------------------------+
CArrayObj* CEngine::GetListAllOrdersByPosID(const ulong position_id)
  {
   CArrayObj* list=this.GetListHistoryOrders();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_POSITION_ID,position_id,EQUAL);
   return list;
  }
//+------------------------------------------------------------------+
//| 返回末笔仓位                                                        |
//+------------------------------------------------------------------+
COrder* CEngine::GetLastPosition(void)
  {
   CArrayObj* list=this.GetListMarketPosition();
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+
//| 按票据返回仓位                                                       |
//+------------------------------------------------------------------+
COrder* CEngine::GetPosition(const ulong ticket)
  {
   CArrayObj* list=this.GetListMarketPosition();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_TICKET,ticket,EQUAL);
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TICKET);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+
//| 返回末笔成交                                                        |
//+------------------------------------------------------------------+
COrder* CEngine::GetLastDeal(void)
  {
   CArrayObj* list=this.GetListDeals();
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+
//| 返回末笔在场挂单                                                      |
//+------------------------------------------------------------------+
COrder* CEngine::GetLastMarketPending(void)
  {
   CArrayObj* list=this.GetListMarketPendings();
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+
//| 返回末笔历史挂单                                                    |
//+------------------------------------------------------------------+
COrder* CEngine::GetLastHistoryPending(void)
  {
   CArrayObj* list=this.GetListHistoryPendings();
   if(list==NULL) return NULL;
   list.Sort(#ifdef __MQL5__ SORT_BY_ORDER_TIME_OPEN_MSC #else SORT_BY_ORDER_TIME_CLOSE_MSC #endif);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+
//| 返回末笔入场订单                                                    |
//+------------------------------------------------------------------+
COrder* CEngine::GetLastMarketOrder(void)
  {
   CArrayObj* list=this.GetListMarketOrders();
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+
//| 返回末笔历史入场订单                                                  |
//+------------------------------------------------------------------+
COrder* CEngine::GetLastHistoryOrder(void)
  {
   CArrayObj* list=this.GetListHistoryOrders();
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+
//| 按票据返回历史订单列表                                               |
//+------------------------------------------------------------------+
COrder* CEngine::GetHistoryOrder(const ulong ticket)
  {
   CArrayObj* list=this.GetListHistoryOrders();
   list=CSelect::ByOrderProperty(list,ORDER_PROP_TICKET,(long)ticket,EQUAL);
   if(list==NULL || list.Total()==0)
     {
      list=this.GetListHistoryPendings();
      list=CSelect::ByOrderProperty(list,ORDER_PROP_TICKET,(long)ticket,EQUAL);
      if(list==NULL) return NULL;
     }
   COrder* order=list.At(0);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+
//| 返回首笔历史入场订单                                                  |
//| 从所有仓位订单列表                                                   |
//+------------------------------------------------------------------+
COrder* CEngine::GetFirstOrderPosition(const ulong position_id)
  {
   CArrayObj* list=this.GetListAllOrdersByPosID(position_id);
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TIME_OPEN);
   COrder* order=list.At(0);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+
//| 返回末笔历史入场订单                                                  |
//| 从所有仓位订单列表                                                   |
//+------------------------------------------------------------------+
COrder* CEngine::GetLastOrderPosition(const ulong position_id)
  {
   CArrayObj* list=this.GetListAllOrdersByPosID(position_id);
   if(list==NULL) return NULL;
   list.Sort(SORT_BY_ORDER_TIME_OPEN);
   COrder* order=list.At(list.Total()-1);
   return(order!=NULL ? order : NULL);
  }
//+------------------------------------------------------------------+

测试定义、处理和接收事件的过程

现在我们已准备好处理事件。 是时候来准备 EA,测试和处理事件描述,并将它们发送到控制程序。

在终端目录\MQL5\Experts\TestDoEasy 中,创建 Part05 文件夹,并从上一部分复制 TestDoEasyPart04.mq5 EA 为新名称:TestDoEasyPart05.mq5

更改其 OnChartEvent() 事件处理程序以便接收自定义事件

//+------------------------------------------------------------------+
//| ChartEvent 函数                                                    |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
   if(MQLInfoInteger(MQL_TESTER))
      return;
   if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sparam,"BUTT_")>0)
     {
      PressButtonEvents(sparam);
     }
   if(id>=CHARTEVENT_CUSTOM)
     {
      ushort event=ushort(id-CHARTEVENT_CUSTOM);
      Print(DFUN,"id=",id,", event=",EnumToString((ENUM_TRADE_EVENT)event),", lparam=",lparam,", dparam=",DoubleToString(dparam,Digits()),", sparam=",sparam);
     } 
  }
//+------------------------------------------------------------------+

在此,如果事件 ID 超过或等于自定义事件 ID,则从函数库的 CEvent 类的后代接收传递的事件代码。 当利用 EventChartCustom() 函数发送 custom_event_id 参数指定的自定义事件(我们编写的那个事件代码)时,该数值为事件代码值加上来自 ENUM_CHART_EVENT 枚举的 CHARTEVENT_CUSTOM 常量(等于 1000)。 所以,为了向后获得事件值,我们需要简单地从事件 ID 中减去 CHARTEVENT_CUSTOM 的值。 之后,我们在终端日志中显示事件数据。
显示以下数据:ID('原样'),ENUM_TRADE_EVENT 枚举值形式的事件描述,lparam 存储订单或仓位票据值,dparam 存储事件价格,且 sparam — 订单的品种,或者若事件是余额操作,则为参与事件的仓位或帐户货币名称。
例如:

2019.04.06 03:19:54.442 OnChartEvent: id=1001, event=TRADE_EVENT_PENDING_ORDER_PLASED, lparam=375419507, dparam=1.14562, sparam=EURUSD

此外,我们需要调整部分平仓的手数计算。 在先前版本的测试 EA 中,这是不正确的,因为计算手数时用到了未执行仓位交易量(VolumeCurrent())。 由于测试器不会模拟部分开仓,因此在开仓时测试器该值始终等于零。 相应地,由于手数计算函数始终将零调整为最小可接受手数值,因此会取最小手数值来平仓。

我们找到计算部分平仓手数的代码,并用 Volume() 替换 VolumeCurrent():

               //--- 按票据计算平仓交易量,并将多头持仓平仓一半
               trade.PositionClosePartial(position.Ticket(),NormalizeLot(position.Symbol(),position.Volume()/2.0));

               //--- 按票据计算平仓交易量,并将空头持仓平仓一半
               trade.PositionClosePartial(position.Ticket(),NormalizeLot(position.Symbol(),position.Volume()/2.0));

代码中只有两个位置 — 多头平仓一半,和空头平仓一半。

此外,将 button shift by X and Y axes 添加到 EA 输入,以便在可视测试器图表上更方便地定位按钮组(我将按钮向右移动,以便查看可视化窗口里的订单和仓位票据, 因为它们可能会被按钮隐藏):

//--- 输入变量
input ulong    InpMagic       =  123;  // 魔幻数字
input double   InpLots        =  0.1;  // 手数
input uint     InpStopLoss    =  50;   // 止损点数
input uint     InpTakeProfit  =  50;   // 止盈点数
input uint     InpDistance    =  50;   // 挂单距离 (点数)
input uint     InpDistanceSL  =  50;   // StopLimit 挂单距离 (点数)
input uint     InpSlippage    =  0;    // 滑点点数
input double   InpWithdrawal  =  10;   // 出金 (在测试器里)
input uint     InpButtShiftX  =  40;   // 按钮的 X 顺移 
input uint     InpButtShiftY  =  10;   // 按钮的 Y 顺移 
//--- 全局变量

我们稍微修改一下创建按钮函数的代码:

//+------------------------------------------------------------------+
//| 创建按钮面板                                                          |
//+------------------------------------------------------------------+
bool CreateButtons(const int shift_x=30,const int shift_y=0)
  {
   int h=18,w=84,offset=2;
   int cx=offset+shift_x,cy=offset+shift_y+(h+1)*(TOTAL_BUTT/2)+2*h+1;
   int x=cx,y=cy;
   int shift=0;
   for(int i=0;i<TOTAL_BUTT;i++)
     {
      x=x+(i==7 ? w+2 : 0);
      if(i==TOTAL_BUTT-3) x=cx;
      y=(cy-(i-(i>6 ? 7 : 0))*(h+1));
      if(!ButtonCreate(butt_data[i].name,x,y,(i<TOTAL_BUTT-3 ? w : w*2+2),h,butt_data[i].text,(i<4 ? clrGreen : i>6 && i<11 ? clrRed : clrBlue)))
        {
         Alert(TextByLanguage("Не удалось создать кнопку \"","Could not create button \""),butt_data[i].text);
         return false;
        }
     }
   ChartRedraw(0);
   return true;
  }
//+------------------------------------------------------------------+

并在 EA 的 OnInit() 处理程序中实现调用该函数:

//--- 创建按钮
   if(!CreateButtons(InpButtShiftX,InpButtShiftY))
      return INIT_FAILED;
//--- 设定交易参数

测试 EA 的完整代码如下:

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart05.mq5 |
//|                                  版权所有 2018, MetaQuotes 软件公司 |
//|                             https://mql5.com/zh/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "版权所有 2018, MetaQuotes 软件公司"
#property link      "https://mql5.com/zh/users/artmedia70"
#property version   "1.00"
//--- 包含
#include <DoEasy\Engine.mqh>
#include <Trade\Trade.mqh>
//--- 枚举
enum ENUM_BUTTONS
  {
   BUTT_BUY,
   BUTT_BUY_LIMIT,
   BUTT_BUY_STOP,
   BUTT_BUY_STOP_LIMIT,
   BUTT_CLOSE_BUY,
   BUTT_CLOSE_BUY2,
   BUTT_CLOSE_BUY_BY_SELL,
   BUTT_SELL,
   BUTT_SELL_LIMIT,
   BUTT_SELL_STOP,
   BUTT_SELL_STOP_LIMIT,
   BUTT_CLOSE_SELL,
   BUTT_CLOSE_SELL2,
   BUTT_CLOSE_SELL_BY_BUY,
   BUTT_DELETE_PENDING,
   BUTT_CLOSE_ALL,
   BUTT_PROFIT_WITHDRAWAL
  };
#define TOTAL_BUTT   (17)
//--- 结构
struct SDataButt
  {
   string      name;
   string      text;
  };
//--- 输入变量
input ulong    InpMagic       =  123;  // 魔幻数字
input double   InpLots        =  0.1;  // 手数
input uint     InpStopLoss    =  50;   // 止损点数
input uint     InpTakeProfit  =  50;   // 止盈点数
input uint     InpDistance    =  50;   // 挂单距离 (点数)
input uint     InpDistanceSL  =  50;   // StopLimit 挂单距离 (点数)
input uint     InpSlippage    =  0;    // 滑点点数
input double   InpWithdrawal  =  10;   // 出金 (在测试器里)
input uint     InpButtShiftX  =  40;   // 按钮的 X 顺移 
input uint     InpButtShiftY  =  10;   // 按钮的 Y 顺移 
//--- 全局变量
CEngine        engine;
CTrade         trade;
SDataButt      butt_data[TOTAL_BUTT];
string         prefix;
double         lot;
double         withdrawal=(InpWithdrawal<0.1 ? 0.1 : InpWithdrawal);
ulong          magic_number;
uint           stoploss;
uint           takeprofit;
uint           distance_pending;
uint           distance_stoplimit;
uint           slippage;
//+------------------------------------------------------------------+
//| 智能系统初始化函数                                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- 检查账户类型
   if(!engine.IsHedge())
     {
      Alert(TextByLanguage("Ошибка. Счёт должен быть хеджевым","Error. Account must be hedge"));
      return INIT_FAILED;
     }
//--- 设置全局变量
   prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_";
   for(int i=0;i<TOTAL_BUTT;i++)
     {
      butt_data[i].name=prefix+EnumToString((ENUM_BUTTONS)i);
      butt_data[i].text=EnumToButtText((ENUM_BUTTONS)i);
     }
   lot=NormalizeLot(Symbol(),fmax(InpLots,MinimumLots(Symbol())*2.0));
   magic_number=InpMagic;
   stoploss=InpStopLoss;
   takeprofit=InpTakeProfit;
   distance_pending=InpDistance;
   distance_stoplimit=InpDistanceSL;
   slippage=InpSlippage;
//--- 创建按钮
   if(!CreateButtons(InpButtShiftX,InpButtShiftY))
      return INIT_FAILED;
//--- 设定交易参数
   trade.SetDeviationInPoints(slippage);
   trade.SetExpertMagicNumber(magic_number);
   trade.SetTypeFillingBySymbol(Symbol());
   trade.SetMarginMode();
   trade.LogLevel(LOG_LEVEL_NO);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| 智能系统逆初始化函数                                                  |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- 删除对象
   ObjectsDeleteAll(0,prefix);
   Comment("");
  }
//+------------------------------------------------------------------+
//| 智能系统即时报价函数                                                   |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   static ENUM_TRADE_EVENT last_event=WRONG_VALUE;
   if(MQLInfoInteger(MQL_TESTER))
     {
      engine.OnTimer();
      int total=ObjectsTotal(0);
      for(int i=0;i<total;i++)
        {
         string obj_name=ObjectName(0,i);
         if(StringFind(obj_name,prefix+"BUTT_")<0)
            continue;
         PressButtonEvents(obj_name);
        }
     }
   if(engine.LastTradeEvent()!=last_event)
     {
      Comment("\nLast trade event: ",EnumToString(engine.LastTradeEvent()));
      last_event=engine.LastTradeEvent();
     }
  }
//+------------------------------------------------------------------+
//| 定时器函数                                                         |
//+------------------------------------------------------------------+
void OnTimer()
  {
   if(!MQLInfoInteger(MQL_TESTER))
      engine.OnTimer();
  }
//+------------------------------------------------------------------+
//| ChartEvent 函数                                                   |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
   if(MQLInfoInteger(MQL_TESTER))
      return;
   if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sparam,"BUTT_")>0)
     {
      PressButtonEvents(sparam);
     }
   if(id>=CHARTEVENT_CUSTOM)
     {
      ushort event=ushort(id-CHARTEVENT_CUSTOM);
      Print(DFUN,"id=",id,", event=",EnumToString((ENUM_TRADE_EVENT)event),", lparam=",lparam,", dparam=",DoubleToString(dparam,Digits()),", sparam=",sparam);
     } 
  }
//+------------------------------------------------------------------+
//| 创建按钮面板                                                        |
//+------------------------------------------------------------------+
bool CreateButtons(const int shift_x=30,const int shift_y=0)
  {
   int h=18,w=84,offset=2;
   int cx=offset+shift_x,cy=offset+shift_y+(h+1)*(TOTAL_BUTT/2)+2*h+1;
   int x=cx,y=cy;
   int shift=0;
   for(int i=0;i<TOTAL_BUTT;i++)
     {
      x=x+(i==7 ? w+2 : 0);
      if(i==TOTAL_BUTT-3) x=cx;
      y=(cy-(i-(i>6 ? 7 : 0))*(h+1));
      if(!ButtonCreate(butt_data[i].name,x,y,(i<TOTAL_BUTT-3 ? w : w*2+2),h,butt_data[i].text,(i<4 ? clrGreen : i>6 && i<11 ? clrRed : clrBlue)))
        {
         Alert(TextByLanguage("Не удалось создать кнопку \"","Could not create button \""),butt_data[i].text);
         return false;
        }
     }
   ChartRedraw(0);
   return true;
  }
//+------------------------------------------------------------------+
//| 创建按钮                                                            |
//+------------------------------------------------------------------+
bool ButtonCreate(const string name,const int x,const int y,const int w,const int h,const string text,const color clr,const string font="Calibri",const int font_size=8)
  {
   if(ObjectFind(0,name)<0)
     {
      if(!ObjectCreate(0,name,OBJ_BUTTON,0,0,0)) 
        { 
         Print(DFUN,TextByLanguage("не удалось создать кнопку! Код ошибки=","Could not create button! Error code="),GetLastError()); 
         return false; 
        } 
      ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false);
      ObjectSetInteger(0,name,OBJPROP_HIDDEN,true);
      ObjectSetInteger(0,name,OBJPROP_XDISTANCE,x);
      ObjectSetInteger(0,name,OBJPROP_YDISTANCE,y);
      ObjectSetInteger(0,name,OBJPROP_XSIZE,w);
      ObjectSetInteger(0,name,OBJPROP_YSIZE,h);
      ObjectSetInteger(0,name,OBJPROP_CORNER,CORNER_LEFT_LOWER);
      ObjectSetInteger(0,name,OBJPROP_ANCHOR,ANCHOR_LEFT_LOWER);
      ObjectSetInteger(0,name,OBJPROP_FONTSIZE,font_size);
      ObjectSetString(0,name,OBJPROP_FONT,font);
      ObjectSetString(0,name,OBJPROP_TEXT,text);
      ObjectSetInteger(0,name,OBJPROP_COLOR,clr);
      ObjectSetString(0,name,OBJPROP_TOOLTIP,"\n");
      ObjectSetInteger(0,name,OBJPROP_BORDER_COLOR,clrGray);
      return true;
     }
   return false;
  }
//+------------------------------------------------------------------+
//| 返回按钮状态                                                        |
//+------------------------------------------------------------------+
bool ButtonState(const string name)
  {
   return (bool)ObjectGetInteger(0,name,OBJPROP_STATE);
  }
//+------------------------------------------------------------------+
//| 设置按钮状态                                                        |
//+------------------------------------------------------------------+
void ButtonState(const string name,const bool state)
  {
   ObjectSetInteger(0,name,OBJPROP_STATE,state);
  }
//+------------------------------------------------------------------+
//| 将枚举变换为按钮文本                                                  |
//+------------------------------------------------------------------+
string EnumToButtText(const ENUM_BUTTONS member)
  {
   string txt=StringSubstr(EnumToString(member),5);
   StringToLower(txt);
   StringReplace(txt,"buy","Buy");
   StringReplace(txt,"sell","Sell");
   StringReplace(txt,"_limit"," Limit");
   StringReplace(txt,"_stop"," Stop");
   StringReplace(txt,"close_","Close ");
   StringReplace(txt,"2"," 1/2");
   StringReplace(txt,"_by_"," by ");
   StringReplace(txt,"profit_","Profit ");
   StringReplace(txt,"delete_","Delete ");
   return txt;
  }
//+------------------------------------------------------------------+
//| 按下按钮                                                           |
//+------------------------------------------------------------------+
void PressButtonEvents(const string button_name)
  {
   //--- 将按钮名称转换为其字符串 ID
   string button=StringSubstr(button_name,StringLen(prefix));
   //--- 如果按钮被按下
   if(ButtonState(button_name))
     {
      //--- 如果 BUTT_BUY 按钮被按下,开多头仓位
      if(button==EnumToString(BUTT_BUY))
        {
         //--- 获取相对于 StopLevel 的正确止损和止盈价位
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY,0,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY,0,takeprofit);
         //--- 开多头仓位
         trade.Buy(NormalizeLot(Symbol(),lot),Symbol(),0,sl,tp);
        }
      //--- 如果 BUTT_BUY_LIMIT 按钮被按下:设置 BuyLimit
      else if(button==EnumToString(BUTT_BUY_LIMIT))
        {
         //--- 获取相对于 StopLevel 的正确下单价格
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_LIMIT,distance_pending);
         //--- 参考 StopLevel,获取相对于下单价位的正确止损和止盈价位
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,takeprofit);
         //--- 设置 BuyLimit 订单
         trade.BuyLimit(lot,price_set,Symbol(),sl,tp);
        }
      //--- 如果 BUTT_BUY_STOP 按钮被按下:设置 BuyStop
      else if(button==EnumToString(BUTT_BUY_STOP))
        {
         //--- 获取相对于 StopLevel 的正确下单价格
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_STOP,distance_pending);
         //--- 参考 StopLevel,获取相对于下单价位的正确止损和止盈价位
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_STOP,price_set,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_STOP,price_set,takeprofit);
         //--- 设置 BuyStop 订单
         trade.BuyStop(lot,price_set,Symbol(),sl,tp);
        }
      //--- 如果 BUTT_BUY_STOP_LIMIT 按钮被按下:设置 BuyStopLimit
      else if(button==EnumToString(BUTT_BUY_STOP_LIMIT))
        {
         //--- 获取相对于 StopLevel 的正确 BuyStop 价格
         double price_set_stop=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_STOP,distance_pending);
         //--- 参考 StopLevel,计算相对于 BuyStop 下单位置的 BuyLimit 订单价格
         double price_set_limit=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_LIMIT,distance_stoplimit,price_set_stop);
         //--- 参考 StopLevel,获取相对于下单价位的正确止损和止盈价位
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_STOP,price_set_limit,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_STOP,price_set_limit,takeprofit);
         //--- 设置 BuyStopLimit 订单
         trade.OrderOpen(Symbol(),ORDER_TYPE_BUY_STOP_LIMIT,lot,price_set_limit,price_set_stop,sl,tp);
        }
      //--- 如果 BUTT_SELL 按钮被按下,开空头仓位
      else if(button==EnumToString(BUTT_SELL))
        {
         //--- 获取相对于 StopLevel 的正确止损和止盈价位
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL,0,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL,0,takeprofit);
         //--- 开空头仓位
         trade.Sell(lot,Symbol(),0,sl,tp);
        }
      //--- 如果 BUTT_SELL_LIMIT 按钮被按下:设置 SellLimit
      else if(button==EnumToString(BUTT_SELL_LIMIT))
        {
         //--- 获取相对于 StopLevel 的正确下单价格
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_LIMIT,distance_pending);
         //--- 参考 StopLevel,获取相对于下单价位的正确止损和止盈价位
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_LIMIT,price_set,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_LIMIT,price_set,takeprofit);
         //--- 设置 SellLimit 订单
         trade.SellLimit(lot,price_set,Symbol(),sl,tp);
        }
      //--- 如果 BUTT_SELL_STOP 按钮被按下:设置 SellStop
      else if(button==EnumToString(BUTT_SELL_STOP))
        {
         //--- 获取相对于 StopLevel 的正确下单价格
         double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_STOP,distance_pending);
         //--- 参考 StopLevel,获取相对于下单价位的正确止损和止盈价位
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_STOP,price_set,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_STOP,price_set,takeprofit);
         //--- 设置 SellStop 订单
         trade.SellStop(lot,price_set,Symbol(),sl,tp);
        }
      //--- 如果 BUTT_SELL_STOP_LIMIT 按钮被按下:设置 SellStopLimit
      else if(button==EnumToString(BUTT_SELL_STOP_LIMIT))
        {
         //--- 获取相对于 StopLevel 的正确 SellStop 订单价格
         double price_set_stop=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_STOP,distance_pending);
         //--- 参考 StopLevel,计算相对于 SellStop 下单位置的 SellLimit 订单价格
         double price_set_limit=CorrectPricePending(Symbol(),ORDER_TYPE_SELL_LIMIT,distance_stoplimit,price_set_stop);
         //--- 参考 StopLevel,获取相对于下单价位的正确止损和止盈价位
         double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_SELL_STOP,price_set_limit,stoploss);
         double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_SELL_STOP,price_set_limit,takeprofit);
         //--- 设置 SellStopLimit 订单
         trade.OrderOpen(Symbol(),ORDER_TYPE_SELL_STOP_LIMIT,lot,price_set_limit,price_set_stop,sl,tp);
        }
      //--- 如果 BUTT_CLOSE_BUY 按钮被按下:多头平仓
      else if(button==EnumToString(BUTT_CLOSE_BUY))
        {
         //--- 获取所有持仓的清单
         CArrayObj* list=engine.GetListMarketPosition();
         //--- 仅从列表中选择多头仓位
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
         //--- 参考佣金和隔夜利率,按利润对列表进行排序
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- 获取具有最大利润的多头仓位索引
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            COrder* position=list.At(index);
            if(position!=NULL)
              {
               //--- 获取多头仓位票据,并按票据平仓
               trade.PositionClose(position.Ticket());
              }
           }
        }
      //--- 如果 BUTT_CLOSE_BUY2 按钮被按下:多头平仓一半
      else if(button==EnumToString(BUTT_CLOSE_BUY2))
        {
         //--- 获取所有持仓的清单
         CArrayObj* list=engine.GetListMarketPosition();
         //--- 仅从列表中选择多头仓位
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
         //--- 参考佣金和隔夜利率,按利润对列表进行排序
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- 获取具有最大利润的多头仓位索引
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            COrder* position=list.At(index);
            if(position!=NULL)
              {
               //--- 按票据计算平仓交易量,并将多头持仓平仓一半
               trade.PositionClosePartial(position.Ticket(),NormalizeLot(position.Symbol(),position.Volume()/2.0));
              }
           }
        }
      //--- 如果 BUTT_CLOSE_BUY_BY_SELL 按钮被按下:以逆向空头仓位将多头平仓
      else if(button==EnumToString(BUTT_CLOSE_BUY_BY_SELL))
        {
         //--- 获取所有持仓的清单
         CArrayObj* list_buy=engine.GetListMarketPosition();
         //--- 仅从列表中选择多头仓位
         list_buy=CSelect::ByOrderProperty(list_buy,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
         //--- 参考佣金和隔夜利率,按利润对列表进行排序
         list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- 获取具有最大利润的多头仓位索引
         int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL);
         //--- 获取所有持仓的清单
         CArrayObj* list_sell=engine.GetListMarketPosition();
         //--- 仅从列表中选择空头仓位
         list_sell=CSelect::ByOrderProperty(list_sell,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
         //--- 参考佣金和隔夜利率,按利润对列表进行排序
         list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- 获取具有最大利润的空头仓位索引
         int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL);
         if(index_buy>WRONG_VALUE && index_sell>WRONG_VALUE)
           {
            //--- 获取具有最大利润的多头仓位索引
            COrder* position_buy=list_buy.At(index_buy);
            //--- 获取具有最大利润的空头仓位索引
            COrder* position_sell=list_sell.At(index_sell);
            if(position_buy!=NULL && position_sell!=NULL)
              {
               //--- 以逆向空头仓位将多头平仓
               trade.PositionCloseBy(position_buy.Ticket(),position_sell.Ticket());
              }
           }
        }
      //--- 如果 BUTT_CLOSE_SELL 按钮被按下:空头平仓
      else if(button==EnumToString(BUTT_CLOSE_SELL))
        {
         //--- 获取所有持仓的清单
         CArrayObj* list=engine.GetListMarketPosition();
         //--- 仅从列表中选择空头仓位
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
         //--- 参考佣金和隔夜利率,按利润对列表进行排序
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- 获取具有最大利润的空头仓位索引
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            COrder* position=list.At(index);
            if(position!=NULL)
              {
               //--- 获取空头仓位票据,并按票据平仓
               trade.PositionClose(position.Ticket());
              }
           }
        }
      //--- 如果 BUTT_CLOSE_SELL2 按钮被按下:空头平仓一半
      else if(button==EnumToString(BUTT_CLOSE_SELL2))
        {
         //--- 获取所有持仓的清单
         CArrayObj* list=engine.GetListMarketPosition();
         //--- 仅从列表中选择空头仓位
         list=CSelect::ByOrderProperty(list,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
         //--- 参考佣金和隔夜利率,按利润对列表进行排序
         list.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- 获取具有最大利润的空头仓位索引
         int index=CSelect::FindOrderMax(list,ORDER_PROP_PROFIT_FULL);
         if(index>WRONG_VALUE)
           {
            COrder* position=list.At(index);
            if(position!=NULL)
              {
               //--- 按票据计算平仓交易量,并将空头持仓平仓一半
               trade.PositionClosePartial(position.Ticket(),NormalizeLot(position.Symbol(),position.Volume()/2.0));
              }
           }
        }
      //--- 如果 BUTT_CLOSE_SELL_BY_BUY 按钮被按下:以逆向多头仓位将空头平仓
      else if(button==EnumToString(BUTT_CLOSE_SELL_BY_BUY))
        {
         //--- 获取所有持仓的清单
         CArrayObj* list_sell=engine.GetListMarketPosition();
         //--- 仅从列表中选择空头仓位
         list_sell=CSelect::ByOrderProperty(list_sell,ORDER_PROP_TYPE,POSITION_TYPE_SELL,EQUAL);
         //--- 参考佣金和隔夜利率,按利润对列表进行排序
         list_sell.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- 获取具有最大利润的空头仓位索引
         int index_sell=CSelect::FindOrderMax(list_sell,ORDER_PROP_PROFIT_FULL);
         //--- 获取所有持仓的清单
         CArrayObj* list_buy=engine.GetListMarketPosition();
         //--- 仅从列表中选择多头仓位
         list_buy=CSelect::ByOrderProperty(list_buy,ORDER_PROP_TYPE,POSITION_TYPE_BUY,EQUAL);
         //--- 参考佣金和隔夜利率,按利润对列表进行排序
         list_buy.Sort(SORT_BY_ORDER_PROFIT_FULL);
         //--- 获取具有最大利润的多头仓位索引
         int index_buy=CSelect::FindOrderMax(list_buy,ORDER_PROP_PROFIT_FULL);
         if(index_sell>WRONG_VALUE && index_buy>WRONG_VALUE)
           {
            //--- 获取具有最大利润的空头仓位索引
            COrder* position_sell=list_sell.At(index_sell);
            //--- 获取具有最大利润的多头仓位索引
            COrder* position_buy=list_buy.At(index_buy);
            if(position_sell!=NULL && position_buy!=NULL)
              {
               //--- 以逆向多头仓位将空头平仓
               trade.PositionCloseBy(position_sell.Ticket(),position_buy.Ticket());
              }
           }
        }
      //--- 如果 BUTT_CLOSE_ALL 按钮被按下:自盈利最少持仓将所有持仓平仓
      else if(button==EnumToString(BUTT_CLOSE_ALL))
        {
         //--- 获取所有持仓的清单
         CArrayObj* list=engine.GetListMarketPosition();
         if(list!=NULL)
           {
            //--- 参考佣金和隔夜利率,按利润对列表进行排序
            list.Sort(SORT_BY_ORDER_PROFIT_FULL);
            int total=list.Total();
            //--- 从利润最少的持仓开始循环
            for(int i=0;i<total;i++)
              {
               COrder* position=list.At(i);
               if(position==NULL)
                  continue;
               //--- 按票据平仓
               trade.PositionClose(position.Ticket());
              }
           }
        }
      //--- 如果 BUTT_DELETE_PENDING 按钮被按下: 删除首笔挂单
      else if(button==EnumToString(BUTT_DELETE_PENDING))
        {
         //--- 获取所有订单的列表
         CArrayObj* list=engine.GetListMarketPendings();
         if(list!=NULL)
           {
            //--- 按下单时间排序列表
            list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC);
            int total=list.Total();
            //--- 从时间最多的持仓开始循环
            for(int i=total-1;i>=0;i--)
              {
               COrder* order=list.At(i);
               if(order==NULL)
                  continue;
               //--- 按票据删除订单
               trade.OrderDelete(order.Ticket());
              }
           }
        }
      //--- 如果 BUTT_PROFIT_WITHDRAWAL 按钮被按下: 从账户出金
      if(button==EnumToString(BUTT_PROFIT_WITHDRAWAL))
        {
         //--- 如果程序在测试器里启动
         if(MQLInfoInteger(MQL_TESTER))
           {
            //--- 模拟出金
            TesterWithdrawal(withdrawal);
           }
        }
      //--- 等待 1/10 秒
      Sleep(100);
      //--- “取消”按钮并重绘图表
      ButtonState(button_name,false);
      ChartRedraw();
     }
  }
//+------------------------------------------------------------------+

现在我们可以编译 EA 并在测试器中启动它。 单击按钮时,测试器日志中会显示两行有关发生的帐户事件的简短消息。


EA 事件处理程序中的条目不会显示在日志中,因为它们在测试器之外工作。 如果在模拟账户上点击 EA 按钮,终端日志中会显示三行:CEvent 类方法显示两行短消息,另一行来自 EA 的 OnChartEvent() 处理程序。

下面是下单删除挂单时在日记中显示消息的示例:

- Pending order placed: 2019.04.05 23:19:55.248 -                                                              
EURUSD 0.10 Sell Limit #375419507 at price 1.14562                                                             
OnChartEvent: id=1001, event=TRADE_EVENT_PENDING_ORDER_PLASED, lparam=375419507, dparam=1.14562, sparam=EURUSD 
- Pending order removed: 2019.04.05 23:19:55.248 -                                                             
EURUSD 0.10 Sell Limit #375419507 at price 1.14562                                                             
OnChartEvent: id=1002, event=TRADE_EVENT_PENDING_ORDER_REMOVED, lparam=375419507, dparam=1.14562, sparam=EURUSD

下一步是什么?

在下一篇文章中,我们将开始添加用于处理 MetaTrader 5 净持结算帐户的功能。

下面附有当前版本函数库的所有文件以及测试 EA 文件,供您测试和下载。
在评论中留下您的问题,意见和建议。

返回目录

系列中的前几篇文章:

第一部分 概念,数据管理。
第二部分 历史订单和成交集合。
第三部分 入场订单和仓位集合,布置搜索。
第四部分 交易事件。 概念。


全部回复

0/140

量化课程

    移动端课程