轻松快捷开发 MetaTrader 程序的函数库(第九部分):与 MQL4 的兼容性 - 准备数据

1 八月 2019, 12:58
Artyom Trishkin
0
613

内容

在本系列文章的前几部分中,我们为 MetaTrader 5 和 MetaTrader 4 跨平台函数库准备了以下工具:

  • 用于创建用户用例功能,支持从程序快速访问对冲和净持结算账户当中任何订单和持仓的任何数据,
  • 用于跟踪订单和持仓发生的事件 — 下单、移除和激活挂单,以及开/平仓和修改持仓和订单。

现在是时候实现函数库与 MQL4 的兼容性了,因为我们要开发交易类,并且函数库应该在 MQL5 和 MQL4 中都能正常工作。

在本文中,我们将开始改进函数库,以便实现其跨平台性。

MQL4 对比 MQL5

将整个函数库文件夹复制到相应的 MetaTrader 4 目录 \MQL4\Include\DoEasy。 我们将从包含 MQL5 EA 的相应文件夹中得到测试 EA,并以 *.mq4 作为扩展名保存到 \MQL4\Experts\TestDoEasy EA 目录(与文章编号对应的文件夹中,在此情况是 Part09)。

在编辑器的导航栏中找到函数库目录 \MQL4\Include\DoEasy,右键单击它并选择“编译”。


这会编译所有函数库文件,导致超过两千个编译错误:


如果我们分析得到的错误,我们会看到它们绝大多数与 MQL5 常量和枚举相关,而 MQL4 对此一无所知。 这意味着我们需要令 MQL4 知晓函数库中用到的常量。 当然还存在不同性质的错误,例如缺少某些函数,这意味着我们将利用 MQL4 函数实现其操作逻辑。

此外,MQL4 和 MQL5 的订单系统差异很大。 我们不得不为 MQL4 实现一个单独的事件处理程序,与 MQL5 中的实现不同,因为 MQL4 中的历史订单列表提供的订单数据少得多(并且没有成交数据),这意味着我们无法直接从终端的列表里获取订单和成交数据。 在此,我们必须在逻辑上比较在场和历史订单列表中发生的事件,并依据比较结果定义发生的事件。

改进函数库

DoEasy 函数库的根文件夹中,创建新的 ToMQL4.mqh 包含文件。 在此我们将论述 MQL4 的所有必要常量和枚举。 在 Defines.mqh 列表的最开头,包含 用于 MQL4 编译的 Defines.mqh 文件

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#ifdef __MQL4__      
#include "ToMQL4.mqh"
#endif               
//+------------------------------------------------------------------+

之后,整个 MQL4 函数库就能够在编译期间查看 ToMQL4.mqh 文件中写入的内容。

我们转到编辑器工具箱的错误选项卡,按下 NumPad Home 或简单地向上滚动到错误列表的最开头。 双击最先的第一个错误:


编辑器将我们移动到 Defines.mqh 文件中的出错字符串

//+------------------------------------------------------------------+
//| List of possible trading events on the account                   |
//+------------------------------------------------------------------+
enum ENUM_TRADE_EVENT
  {
   TRADE_EVENT_NO_EVENT = 0,                                // No trading event
   TRADE_EVENT_PENDING_ORDER_PLASED,                        // Pending order placed
   TRADE_EVENT_PENDING_ORDER_REMOVED,                       // Pending order removed
//--- enumeration members matching the ENUM_DEAL_TYPE enumeration members
//--- (constant order below should not be changed, no constants should be added/deleted)
   TRADE_EVENT_ACCOUNT_CREDIT = DEAL_TYPE_CREDIT,           // Charging credit (3)
   TRADE_EVENT_ACCOUNT_CHARGE,                              // Additional charges

自然而然地,MQL4 对成交及其类型一无所知。 这应该加以修复。 只需打开 MQL5 参考,并在成交属性里使用 DEAL_TYPE_CREDIT 查询搜索数据:

ID

说明

类型

DEAL_TICKET

成交票据。 为每笔成交分配的独有编号

long

DEAL_ORDER

成交 订单号

long

DEAL_TIME

成交时间

datetime

DEAL_TIME_MSC

执行成交的时刻,自 1970 年 1 月 1 日起以毫秒为单位的时间

long

DEAL_TYPE

成交类型

ENUM_DEAL_TYPE

DEAL_ENTRY

交易方向 - 入场,离场或逆转

ENUM_DEAL_ENTRY

DEAL_MAGIC

成交的魔幻数字 (参阅 ORDER_MAGIC)

long

DEAL_REASON

成交执行的原因或来源

ENUM_DEAL_REASON

DEAL_POSITION_ID

成交开立,修改或平仓的仓位 ID。 每笔仓位都有一个唯一的 ID,分配给在持仓生存周期内该品种上执行的所有成交。

long


在表中,我们最感兴趣的是 ENUM_DEAL_TYPE。 点击链接并获取所有成交类型的列表:

ID

说明

DEAL_TYPE_BUY

买入

DEAL_TYPE_SELL

卖出

DEAL_TYPE_BALANCE

余额

DEAL_TYPE_CREDIT

信贷

DEAL_TYPE_CHARGE

额外收费

DEAL_TYPE_CORRECTION

调整

DEAL_TYPE_BONUS

奖金

DEAL_TYPE_COMMISSION

额外佣金

DEAL_TYPE_COMMISSION_DAILY

日支佣金

DEAL_TYPE_COMMISSION_MONTHLY

月支佣金

DEAL_TYPE_COMMISSION_AGENT_DAILY

日支代理佣金

DEAL_TYPE_COMMISSION_AGENT_MONTHLY

月支代理佣金

DEAL_TYPE_INTEREST

利率

DEAL_TYPE_BUY_CANCELED

取消的买入成交。 可能存在先前执行的买入成交被取消的情况。 在此情况下,之前执行的成交类型 (DEAL_TYPE_BUY) 被修改为 DEAL_TYPE_BUY_CANCELED,且其盈利/亏损清零。 先前获得的利润/亏损由单独的余额操作收取/提取

DEAL_TYPE_SELL_CANCELED

取消的卖出成交。 可能存在先前执行的卖出成交被取消的情况。 在此情况下,之前执行的成交类型 (DEAL_TYPE_SELL) 被修改为 DEAL_TYPE_SELL_CANCELED,且其盈利/亏损清零。 先前获得的利润/亏损由单独的余额操作收取/提取

DEAL_DIVIDEND

股息操作

DEAL_DIVIDEND_FRANKED

分红(非应税)股息操作

DEAL_TAX

税费


将 ENUM_DEAL_TYPE 枚举中的成交类型添加到 ToMQL4.mqh 文件中:

//+------------------------------------------------------------------+
//|                                                       ToMQL4.mqh |
//|              Copyright 2017, Artem A. Trishkin, Skype artmedia70 |
//|                         https://www.mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2017, Artem A. Trishkin, Skype artmedia70"
#property link      "https://www.mql5.com/en/users/artmedia70"
#property strict
#ifdef __MQL4__
//+------------------------------------------------------------------+
//| MQL5 deal types                                                  |
//+------------------------------------------------------------------+
enum ENUM_DEAL_TYPE
  {
   DEAL_TYPE_BUY,
   DEAL_TYPE_SELL,
   DEAL_TYPE_BALANCE,
   DEAL_TYPE_CREDIT,
   DEAL_TYPE_CHARGE,
   DEAL_TYPE_CORRECTION,
   DEAL_TYPE_BONUS,
   DEAL_TYPE_COMMISSION,
   DEAL_TYPE_COMMISSION_DAILY,
   DEAL_TYPE_COMMISSION_MONTHLY,
   DEAL_TYPE_COMMISSION_AGENT_DAILY,
   DEAL_TYPE_COMMISSION_AGENT_MONTHLY,
   DEAL_TYPE_INTEREST,
   DEAL_TYPE_BUY_CANCELED,
   DEAL_TYPE_SELL_CANCELED,
   DEAL_DIVIDEND,
   DEAL_DIVIDEND_FRANKED,
   DEAL_TAX
  };
//+------------------------------------------------------------------+
#endif 

保存文件并再次编译所有函数库文件。 现在的错误较少了:


再次移至出错列表的开头,然后单击第一个错误。 现在它是 ENUM_POSITION_TYPE,所以我们添加:

//+------------------------------------------------------------------+
//|                                                       ToMQL4.mqh |
//|              Copyright 2017, Artem A. Trishkin, Skype artmedia70 |
//|                         https://www.mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2017, Artem A. Trishkin, Skype artmedia70"
#property link      "https://www.mql5.com/en/users/artmedia70"
#property strict
#ifdef __MQL4__
//+------------------------------------------------------------------+
//| MQL5 deal type                                                   |
//+------------------------------------------------------------------+
enum ENUM_DEAL_TYPE
  {
   DEAL_TYPE_BUY,
   DEAL_TYPE_SELL,
   DEAL_TYPE_BALANCE,
   DEAL_TYPE_CREDIT,
   DEAL_TYPE_CHARGE,
   DEAL_TYPE_CORRECTION,
   DEAL_TYPE_BONUS,
   DEAL_TYPE_COMMISSION,
   DEAL_TYPE_COMMISSION_DAILY,
   DEAL_TYPE_COMMISSION_MONTHLY,
   DEAL_TYPE_COMMISSION_AGENT_DAILY,
   DEAL_TYPE_COMMISSION_AGENT_MONTHLY,
   DEAL_TYPE_INTEREST,
   DEAL_TYPE_BUY_CANCELED,
   DEAL_TYPE_SELL_CANCELED,
   DEAL_DIVIDEND,
   DEAL_DIVIDEND_FRANKED,
   DEAL_TAX
  };
//+------------------------------------------------------------------+
//| Open position direction                                          |
//+------------------------------------------------------------------+
enum ENUM_POSITION_TYPE
  {
   POSITION_TYPE_BUY,
   POSITION_TYPE_SELL
  };
//+------------------------------------------------------------------+
#endif 

编译后,我们得到的错误更少了。 移至列表中的第一个错误,定义原因并添加以下枚举

//+------------------------------------------------------------------+
//|                                                       ToMQL4.mqh |
//|              Copyright 2017, Artem A. Trishkin, Skype artmedia70 |
//|                         https://www.mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2017, Artem A. Trishkin, Skype artmedia70"
#property link      "https://www.mql5.com/en/users/artmedia70"
#property strict
#ifdef __MQL4__
//+------------------------------------------------------------------+
//| MQL5 deal types                                                  |
//+------------------------------------------------------------------+
enum ENUM_DEAL_TYPE
  {
   DEAL_TYPE_BUY,
   DEAL_TYPE_SELL,
   DEAL_TYPE_BALANCE,
   DEAL_TYPE_CREDIT,
   DEAL_TYPE_CHARGE,
   DEAL_TYPE_CORRECTION,
   DEAL_TYPE_BONUS,
   DEAL_TYPE_COMMISSION,
   DEAL_TYPE_COMMISSION_DAILY,
   DEAL_TYPE_COMMISSION_MONTHLY,
   DEAL_TYPE_COMMISSION_AGENT_DAILY,
   DEAL_TYPE_COMMISSION_AGENT_MONTHLY,
   DEAL_TYPE_INTEREST,
   DEAL_TYPE_BUY_CANCELED,
   DEAL_TYPE_SELL_CANCELED,
   DEAL_DIVIDEND,
   DEAL_DIVIDEND_FRANKED,
   DEAL_TAX
  };
//+------------------------------------------------------------------+
//| Open position direction                                          |
//+------------------------------------------------------------------+
enum ENUM_POSITION_TYPE
  {
   POSITION_TYPE_BUY,
   POSITION_TYPE_SELL
  };
//+------------------------------------------------------------------+
//| Order status                                                     |
//+------------------------------------------------------------------+
enum ENUM_ORDER_STATE
  {
   ORDER_STATE_STARTED,
   ORDER_STATE_PLACED,
   ORDER_STATE_CANCELED,
   ORDER_STATE_PARTIAL,
   ORDER_STATE_FILLED,
   ORDER_STATE_REJECTED,
   ORDER_STATE_EXPIRED,
   ORDER_STATE_REQUEST_ADD,
   ORDER_STATE_REQUEST_MODIFY,
   ORDER_STATE_REQUEST_CANCEL
  };
//+------------------------------------------------------------------+
#endif 

在下一次编译期间,我们得到了错误的订单类型 ORDER_TYPE_BUY_STOP_LIMIT
MQL4 已经具有 ENUM_ORDER_TYPE枚举。 我们不能向其添加新的常量。 因此,将它们添加为宏替换。

在 MQL5 中,ENUM_ORDER_TYPE 枚举中的 ORDER_TYPE_BUY_STOP_LIMIT 常量设置为 6,而在 MQL4 中,这个订单类型已存在。 此为余额操作(如 MQL5 中的 ORDER_TYPE_SELL_STOP_LIMIT)设置为 7,而在 MQL4 中,此订单类型是信贷操作。

因此,在 MQL5 中为它们设置超过 ORDER_TYPE_CLOSE_BY 平单常量的值:ORDER_TYPE_CLOSE_BY+1ORDER_TYPE_CLOSE_BY+2

//+------------------------------------------------------------------+
//|                                                       ToMQL4.mqh |
//|              Copyright 2017, Artem A. Trishkin, Skype artmedia70 |
//|                         https://www.mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2017, Artem A. Trishkin, Skype artmedia70"
#property link      "https://www.mql5.com/en/users/artmedia70"
#property strict
#ifdef __MQL4__
//+------------------------------------------------------------------+
//| MQL5 deal types                                                  |
//+------------------------------------------------------------------+
enum ENUM_DEAL_TYPE
  {
   DEAL_TYPE_BUY,
   DEAL_TYPE_SELL,
   DEAL_TYPE_BALANCE,
   DEAL_TYPE_CREDIT,
   DEAL_TYPE_CHARGE,
   DEAL_TYPE_CORRECTION,
   DEAL_TYPE_BONUS,
   DEAL_TYPE_COMMISSION,
   DEAL_TYPE_COMMISSION_DAILY,
   DEAL_TYPE_COMMISSION_MONTHLY,
   DEAL_TYPE_COMMISSION_AGENT_DAILY,
   DEAL_TYPE_COMMISSION_AGENT_MONTHLY,
   DEAL_TYPE_INTEREST,
   DEAL_TYPE_BUY_CANCELED,
   DEAL_TYPE_SELL_CANCELED,
   DEAL_DIVIDEND,
   DEAL_DIVIDEND_FRANKED,
   DEAL_TAX
  };
//+------------------------------------------------------------------+
//| Open position direction                                          |
//+------------------------------------------------------------------+
enum ENUM_POSITION_TYPE
  {
   POSITION_TYPE_BUY,
   POSITION_TYPE_SELL
  };
//+------------------------------------------------------------------+
//| Order status                                                     |
//+------------------------------------------------------------------+
enum ENUM_ORDER_STATE
  {
   ORDER_STATE_STARTED,
   ORDER_STATE_PLACED,
   ORDER_STATE_CANCELED,
   ORDER_STATE_PARTIAL,
   ORDER_STATE_FILLED,
   ORDER_STATE_REJECTED,
   ORDER_STATE_EXPIRED,
   ORDER_STATE_REQUEST_ADD,
   ORDER_STATE_REQUEST_MODIFY,
   ORDER_STATE_REQUEST_CANCEL
  };
//+------------------------------------------------------------------+
//| Order types                                                      |
//+------------------------------------------------------------------+
#define ORDER_TYPE_CLOSE_BY         (8) 
#define ORDER_TYPE_BUY_STOP_LIMIT   (9) 
#define ORDER_TYPE_SELL_STOP_LIMIT  (10)
//+------------------------------------------------------------------+
#endif 

编译整个函数库。 在实现 StopLimit 订单类型的宏替换后,出错指示函数返回正确的订单下单价格,即 ENUM_ORDER_TYPE 枚举没有值 9 和 10,因为我们在 switch 运算符后面使用 ENUM_ORDER_TYPE 枚举类型,即订单类型的值

//+------------------------------------------------------------------+
//| Return the correct order placement price                         |
//| relative to StopLevel                                            |
//+------------------------------------------------------------------+
double CorrectPricePending(const string symbol_name,const ENUM_ORDER_TYPE order_type,const double price_set,const double price=0,const int spread_multiplier=2)
  {
   double pt=SymbolInfoDouble(symbol_name,SYMBOL_POINT),pp=0;
   int lv=StopLevel(symbol_name,spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name,SYMBOL_DIGITS);
   switch(order_type)
     {
      case ORDER_TYPE_BUY_LIMIT        :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmin(pp-lv*pt,price_set),dg);
      case ORDER_TYPE_BUY_STOP         :  
      case ORDER_TYPE_BUY_STOP_LIMIT   :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmax(pp+lv*pt,price_set),dg);
      case ORDER_TYPE_SELL_LIMIT       :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmax(pp+lv*pt,price_set),dg);
      case ORDER_TYPE_SELL_STOP        :  
      case ORDER_TYPE_SELL_STOP_LIMIT  :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmin(pp-lv*pt,price_set),dg);
      default                          :  Print(DFUN,TextByLanguage("Неправильный тип ордера: ","Invalid order type: "),EnumToString(order_type)); return 0;
     }
  }
//+------------------------------------------------------------------+
//| Return the correct order placement price                         |
//| relative to StopLevel                                            |
//+------------------------------------------------------------------+
double CorrectPricePending(const string symbol_name,const ENUM_ORDER_TYPE order_type,const int distance_set,const double price=0,const int spread_multiplier=2)
  {
   double pt=SymbolInfoDouble(symbol_name,SYMBOL_POINT),pp=0;
   int lv=StopLevel(symbol_name,spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name,SYMBOL_DIGITS);
   switch(order_type)
     {
      case ORDER_TYPE_BUY_LIMIT        :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmin(pp-lv*pt,pp-distance_set*pt),dg);
      case ORDER_TYPE_BUY_STOP         :  
      case ORDER_TYPE_BUY_STOP_LIMIT   :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmax(pp+lv*pt,pp+distance_set*pt),dg);
      case ORDER_TYPE_SELL_LIMIT       :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmax(pp+lv*pt,pp+distance_set*pt),dg);
      case ORDER_TYPE_SELL_STOP        :  
      case ORDER_TYPE_SELL_STOP_LIMIT  :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmin(pp-lv*pt,pp-distance_set*pt),dg);
      default                          :  Print(DFUN,TextByLanguage("Неправильный тип ордера: ","Invalid order type: "),EnumToString(order_type)); return 0;
     }
  }
//+------------------------------------------------------------------+

解决方案很简单 — switch 中的 order_type 转换为整数类型

//+------------------------------------------------------------------+
//| Return the correct order placement price                         |
//| relative to StopLevel                                            |
//+------------------------------------------------------------------+
double CorrectPricePending(const string symbol_name,const ENUM_ORDER_TYPE order_type,const double price_set,const double price=0,const int spread_multiplier=2)
  {
   double pt=SymbolInfoDouble(symbol_name,SYMBOL_POINT),pp=0;
   int lv=StopLevel(symbol_name,spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name,SYMBOL_DIGITS);
   switch((int)order_type)
     {
      case ORDER_TYPE_BUY_LIMIT        :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmin(pp-lv*pt,price_set),dg);
      case ORDER_TYPE_BUY_STOP         :  
      case ORDER_TYPE_BUY_STOP_LIMIT   :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmax(pp+lv*pt,price_set),dg);
      case ORDER_TYPE_SELL_LIMIT       :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmax(pp+lv*pt,price_set),dg);
      case ORDER_TYPE_SELL_STOP        :  
      case ORDER_TYPE_SELL_STOP_LIMIT  :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmin(pp-lv*pt,price_set),dg);
      default                          :  Print(DFUN,TextByLanguage("Неправильный тип ордера: ","Invalid order type: "),EnumToString(order_type)); return 0;
     }
  }
//+------------------------------------------------------------------+
//| Return the correct order placement price                         |
//| relative to StopLevel                                            |
//+------------------------------------------------------------------+
double CorrectPricePending(const string symbol_name,const ENUM_ORDER_TYPE order_type,const int distance_set,const double price=0,const int spread_multiplier=2)
  {
   double pt=SymbolInfoDouble(symbol_name,SYMBOL_POINT),pp=0;
   int lv=StopLevel(symbol_name,spread_multiplier), dg=(int)SymbolInfoInteger(symbol_name,SYMBOL_DIGITS);
   switch((int)order_type)
     {
      case ORDER_TYPE_BUY_LIMIT        :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmin(pp-lv*pt,pp-distance_set*pt),dg);
      case ORDER_TYPE_BUY_STOP         :  
      case ORDER_TYPE_BUY_STOP_LIMIT   :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_ASK) : price); return NormalizeDouble(fmax(pp+lv*pt,pp+distance_set*pt),dg);
      case ORDER_TYPE_SELL_LIMIT       :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmax(pp+lv*pt,pp+distance_set*pt),dg);
      case ORDER_TYPE_SELL_STOP        :  
      case ORDER_TYPE_SELL_STOP_LIMIT  :  pp=(price==0 ? SymbolInfoDouble(symbol_name,SYMBOL_BID) : price); return NormalizeDouble(fmin(pp-lv*pt,pp-distance_set*pt),dg);
      default                          :  Print(DFUN,TextByLanguage("Неправильный тип ордера: ","Invalid order type: "),EnumToString(order_type)); return 0;
     }
  }
//+------------------------------------------------------------------+

我们进行编译。 现在 Order.mqh 文件中存在错误 — MQL4 不知晓以下值 ORDER_FILLING_RETURN, ORDER_TIME_GTC, ORDER_REASON_SL, ORDER_REASON_TPORDER_REASON_EXPERT 常量。

//+------------------------------------------------------------------+
//| Return execution type by residue                                 |
//+------------------------------------------------------------------+
long COrder::OrderTypeFilling(void) const
  {
#ifdef __MQL4__
   return (long)ORDER_FILLING_RETURN;
#else 
   long res=0;
   switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_MARKET_ORDER      :
      case ORDER_STATUS_MARKET_PENDING    : res=::OrderGetInteger(ORDER_TYPE_FILLING);                break;
      case ORDER_STATUS_HISTORY_PENDING   :
      case ORDER_STATUS_HISTORY_ORDER     : res=::HistoryOrderGetInteger(m_ticket,ORDER_TYPE_FILLING);break;
      default                             : res=0;                                                    break;
     }
   return res;
#endif 
  }
//+------------------------------------------------------------------+
//| Return order lifetime                                            |
//+------------------------------------------------------------------+
long COrder::OrderTypeTime(void) const
  {
#ifdef __MQL4__
   return (long)ORDER_TIME_GTC;
#else 
   long res=0;
   switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_MARKET_ORDER      :
      case ORDER_STATUS_MARKET_PENDING    : res=::OrderGetInteger(ORDER_TYPE_TIME);                break;
      case ORDER_STATUS_HISTORY_PENDING   :
      case ORDER_STATUS_HISTORY_ORDER     : res=::HistoryOrderGetInteger(m_ticket,ORDER_TYPE_TIME);break;
      default                             : res=0;                                                 break;
     }
   return res;
#endif 
  }
//+------------------------------------------------------------------+
//| Order reason or source                                           |
//+------------------------------------------------------------------+
long COrder::OrderReason(void) const
  {
#ifdef __MQL4__
   return
     (
      this.OrderCloseByStopLoss()   ?  ORDER_REASON_SL      :
      this.OrderCloseByTakeProfit() ?  ORDER_REASON_TP      :  
      this.OrderMagicNumber()!=0    ?  ORDER_REASON_EXPERT  : WRONG_VALUE
     );
#else 
   long res=WRONG_VALUE;
   switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_MARKET_POSITION   : res=::PositionGetInteger(POSITION_REASON);          break;
      case ORDER_STATUS_MARKET_ORDER      :
      case ORDER_STATUS_MARKET_PENDING    : res=::OrderGetInteger(ORDER_REASON);                break;
      case ORDER_STATUS_HISTORY_PENDING   :
      case ORDER_STATUS_HISTORY_ORDER     : res=::HistoryOrderGetInteger(m_ticket,ORDER_REASON);break;
      case ORDER_STATUS_DEAL              : res=::HistoryDealGetInteger(m_ticket,DEAL_REASON);  break;
      default                             : res=WRONG_VALUE;                                    break;
     }
   return res;
#endif 
  }
//+------------------------------------------------------------------+

我们在 ToMQL4.mqh 文件的末尾添加宏替换(为节省空间,我不会在此给出完整的清单):

//+------------------------------------------------------------------+
//| Order types, execution policy, lifetime, reasons                 |
//+------------------------------------------------------------------+
#define ORDER_TYPE_CLOSE_BY         (8)
#define ORDER_TYPE_BUY_STOP_LIMIT   (9)
#define ORDER_TYPE_SELL_STOP_LIMIT  (10)
#define ORDER_FILLING_RETURN        (2)
#define ORDER_TIME_GTC              (0)
#define ORDER_REASON_EXPERT         (3)
#define ORDER_REASON_SL             (4)
#define ORDER_REASON_TP             (5)
//+------------------------------------------------------------------+
#endif 

另一次编译导致我们在 CHistoryCollection::OrderSearch() 方法的 HistoryCollection.mqh 文件中缺少 MQL5 函数 HistoryOrderGetTicket()。 代码分析建议在此处应用条件编译指令。 我们为方法加以补充:

//+------------------------------------------------------------------+
//| Return the "lost" order's type and ticket                        |
//+------------------------------------------------------------------+
ulong CHistoryCollection::OrderSearch(const int start,ENUM_ORDER_TYPE &order_type)
  {
   ulong order_ticket=0;
#ifdef __MQL5__
   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;
     }
#else 
   for(int i=start-1;i>=0;i--)
     {
      if(!::OrderSelect(i,SELECT_BY_POS,MODE_HISTORY))
         continue;
      ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)::OrderType();
      ulong ticket=::OrderTicket();
      if(ticket==0 || type<ORDER_TYPE_BUY_LIMIT || type>ORDER_TYPE_SELL_STOP)
         continue;
      if(this.IsPresentOrderInList(ticket,type))
         continue;
      order_ticket=ticket;
      order_type=type;
     }
#endif    
   return order_ticket;
  }
//+------------------------------------------------------------------+

所有用于 MQL5 的内容都由支路 #ifdef __MQL5 __ 指令构成。 在 #else 指令之后,添加 MQL4 代码直至 #endif

下一个错误位于 CEvent 类构造函数中。 使用相同的条件编译指令补充代码:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CEvent::CEvent(const ENUM_EVENT_STATUS event_status,const int event_code,const ulong ticket) : m_event_code(event_code),m_digits(0)
  {
   this.m_long_prop[EVENT_PROP_STATUS_EVENT]       =  event_status;
   this.m_long_prop[EVENT_PROP_TICKET_ORDER_EVENT] =  (long)ticket;
   this.m_is_hedge=#ifdef __MQL4__ true #else bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) #endif;
   this.m_digits_acc=#ifdef __MQL4__ 2 #else (int)::AccountInfoInteger(ACCOUNT_CURRENCY_DIGITS) #endif;
   this.m_chart_id=::ChartID();
  }
//+------------------------------------------------------------------+

当检查“对冲”类型帐户时,我们面临缺少常量错误,因此只需立即返回 true,因为所有帐户在 MetaTrader 4 中都是对冲类型。
此外,当收到账户货币的小数位数时,返回 2,因为 MQL4 无法获得此值。

下一次编译将引导我们进入 CEventsCollection::NewDealEventHedge() 方法 — 接收 MetaTrader 5 对冲账户的事件。 它应对 MQL4 中缺少的成交。 通过在条件编译支路中包含所有方法代码来暂时禁用该方法:

在方法的开头插入指令

//+------------------------------------------------------------------+
//| Create a hedging account event                                   |
//+------------------------------------------------------------------+
void CEventsCollection::NewDealEventHedge(COrder* deal,CArrayObj* list_history,CArrayObj* list_market)
  {
#ifdef __MQL5__
   double ask=::SymbolInfoDouble(deal.Symbol(),SYMBOL_ASK);
   double bid=::SymbolInfoDouble(deal.Symbol(),SYMBOL_BID);
   //--- Market entry

以及在方法的最后

#endif 
  }
//+------------------------------------------------------------------+

接下来,我们最后遇到 CEventsCollection::NewDealEventNetto() 方法中的错误 — 为净持结算帐户创建一个事件。 解决方案与前一种情况相同 — 使用条件编译指令支路构建整个 NewDealEventNetto() 方法代码。

编译并面对 CEventsCollection::GetListAllDealsInByPosID() 方法中的 DEAL_ENTRY_IN 未知常量错误。 将必要的枚举添加到 ToMQL4.mqh 文件中(我们可以再次使用条件编译来禁用代码,但稍后我们可能需要此枚举):

//+------------------------------------------------------------------+
//| MQL5 deal types                                                  |
//+------------------------------------------------------------------+
enum ENUM_DEAL_TYPE
  {
   DEAL_TYPE_BUY,
   DEAL_TYPE_SELL,
   DEAL_TYPE_BALANCE,
   DEAL_TYPE_CREDIT,
   DEAL_TYPE_CHARGE,
   DEAL_TYPE_CORRECTION,
   DEAL_TYPE_BONUS,
   DEAL_TYPE_COMMISSION,
   DEAL_TYPE_COMMISSION_DAILY,
   DEAL_TYPE_COMMISSION_MONTHLY,
   DEAL_TYPE_COMMISSION_AGENT_DAILY,
   DEAL_TYPE_COMMISSION_AGENT_MONTHLY,
   DEAL_TYPE_INTEREST,
   DEAL_TYPE_BUY_CANCELED,
   DEAL_TYPE_SELL_CANCELED,
   DEAL_DIVIDEND,
   DEAL_DIVIDEND_FRANKED,
   DEAL_TAX
  };
//+------------------------------------------------------------------+
//| Position change method                                           |
//+------------------------------------------------------------------+
enum ENUM_DEAL_ENTRY
  {
   DEAL_ENTRY_IN,
   DEAL_ENTRY_OUT,
   DEAL_ENTRY_INOUT,
   DEAL_ENTRY_OUT_BY
  };
//+------------------------------------------------------------------+
//| Open position direction                                          |
//+------------------------------------------------------------------+
enum ENUM_POSITION_TYPE
  {
   POSITION_TYPE_BUY,
   POSITION_TYPE_SELL
  };
//+------------------------------------------------------------------+

接下来,我们最后遇到了已经熟悉的错误,即检查“对冲”类型帐户,但现在它位于事件集合类构造函数中。 我们来修复它:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CEventsCollection::CEventsCollection(void) : m_trade_event(TRADE_EVENT_NO_EVENT),m_trade_event_code(TRADE_EVENT_FLAG_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=#ifdef __MQL4__ true #else bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) #endif;
   this.m_chart_id=::ChartID();
   ::ZeroMemory(this.m_tick);
  }
//+------------------------------------------------------------------+

接下来,对 CEngine 类构造函数执行相同的更正

//+------------------------------------------------------------------+
//| CEngine constructor                                              |
//+------------------------------------------------------------------+
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=#ifdef __MQL4__ true #else bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) #endif;
  }
//+------------------------------------------------------------------+

一切都准备好了。 现在编译整个函数库都没有出错。 但这只是第一阶段。 现在我们需要启动它。 由于我们使用条件编译禁用了一些方法,因此我们需要开发它们,以便能够在 MetaTrader 4 中工作。

在 MQL5 中,余额操作是成交。 它们可以在历史订单和成交列表中找到。 在 MQL4 中,余额操作是 ORDER_TYPE_BALANCE(6) 和 ORDER_TYPE_CREDIT(7) 类型的订单。 因此,我为 MQL4 创建了一个单独的余额操作对象类,存储在历史订单和仓位列表中。

在 \MQL4\Include\DoEasy\Objects\Orders 下的 HistoryBalance.mqh 文件中创建新的 CHistoryBalance 类。 COrder 应作为一个基类:

//+------------------------------------------------------------------+
//|                                               HistoryBalance.mqh |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "Order.mqh"
//+------------------------------------------------------------------+
//| Historical balance operation                                     |
//+------------------------------------------------------------------+
class CHistoryBalance : public COrder
  {
public:
   //--- Constructor
                     CHistoryBalance(const ulong ticket) : COrder(ORDER_STATUS_BALANCE,ticket) {}
   //--- Supported deal properties (1) real, (2) integer
   virtual bool      SupportProperty(ENUM_ORDER_PROP_INTEGER property);
   virtual bool      SupportProperty(ENUM_ORDER_PROP_DOUBLE property);
   virtual bool      SupportProperty(ENUM_ORDER_PROP_STRING property);
  };
//+------------------------------------------------------------------+
//| Return 'true' if an order supports a passed                      |
//| integer property, otherwise return 'false'                       |
//+------------------------------------------------------------------+
bool CHistoryBalance::SupportProperty(ENUM_ORDER_PROP_INTEGER property)
  {
   if(property==ORDER_PROP_TICKET      ||
      property==ORDER_PROP_TIME_OPEN   || 
      property==ORDER_PROP_STATUS      ||
      property==ORDER_PROP_TYPE        ||
      property==ORDER_PROP_REASON
     ) return true;
   return false;
  }
//+------------------------------------------------------------------+
//| Return 'true' if an order supports a passed                      |
//| real property, otherwise return 'false'                          |
//+------------------------------------------------------------------+
bool CHistoryBalance::SupportProperty(ENUM_ORDER_PROP_DOUBLE property)
  {
   return(property==ORDER_PROP_PROFIT ? true : false);
  }
//+------------------------------------------------------------------+
//| Return 'true' if an order supports a passed                      |
//| string property, otherwise return 'false'                        |
//+------------------------------------------------------------------+
bool CHistoryBalance::SupportProperty(ENUM_ORDER_PROP_STRING property)
  {
   if(property==ORDER_PROP_SYMBOL || property==ORDER_PROP_EXT_ID)
      return false;
   return true;
  }
//+------------------------------------------------------------------+

该类对我们来说没有任何新内容。 我们已经查看了函数库论述的第二部分中的所有历史订单类。

我们已有两种类型的余额操作 — 余额和信贷操作。 相应地,它们的类型对应 6 和 7 的数值。 我们将针对两种类型使用单个余额操作类,并在 “reason” 订单属性中阐明某种类型。

在 ToMQL4.mqh 文件中添加两个缺失的订单“原因”:

//+------------------------------------------------------------------+
//| Order types, execution policy, lifetime, reasons                 |
//+------------------------------------------------------------------+
#define ORDER_TYPE_CLOSE_BY         (8)
#define ORDER_TYPE_BUY_STOP_LIMIT   (9)
#define ORDER_TYPE_SELL_STOP_LIMIT  (10)
#define ORDER_FILLING_RETURN        (2)
#define ORDER_TIME_GTC              (0)
#define ORDER_REASON_EXPERT         (3)
#define ORDER_REASON_SL             (4)
#define ORDER_REASON_TP             (5)
#define ORDER_REASON_BALANCE        (6)
#define ORDER_REASON_CREDIT         (7)
//+------------------------------------------------------------------+

由于我们已有一个从抽象订单类派生的新类,我们需要在 COrder 中添加缺失的功能。

在 COrder::OrderPositionID() 方法中,针对 MQL4 替换返回魔幻数字

//+------------------------------------------------------------------+
//| Return position ID                                               |
//+------------------------------------------------------------------+
long COrder::OrderPositionID(void) const
  {
#ifdef __MQL4__
   return ::OrderMagicNumber();
#else

返回票据(稍后将针对 MQL4 仓位实现一种 PositionID):

//+------------------------------------------------------------------+
//| Return position ID                                               |
//+------------------------------------------------------------------+
long COrder::OrderPositionID(void) const
  {
#ifdef __MQL4__
   return ::OrderTicket();
#else

在 MQL4 中返回订单状态的方法始终返回来自 ENUM_ORDER_STATE 枚举的 ORDER_STATE_FILLED,但对于删除挂单则不然。 实现订单状态检查,如果这是删除挂单,则返回 ORDER_STATE_CANCELED

//+------------------------------------------------------------------+
//| Return the order status                                          |
//+------------------------------------------------------------------+
long COrder::OrderState(void) const
  {
#ifdef __MQL4__
   return(this.Status()==ORDER_STATUS_HISTORY_ORDER ? ORDER_STATE_FILLED : ORDER_STATE_CANCELED);
#else
   long res=0;
   switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS))
     {
      case ORDER_STATUS_HISTORY_PENDING   :
      case ORDER_STATUS_HISTORY_ORDER     : res=::HistoryOrderGetInteger(m_ticket,ORDER_STATE); break;
      case ORDER_STATUS_MARKET_ORDER      :
      case ORDER_STATUS_MARKET_PENDING    : res=::OrderGetInteger(ORDER_STATE);                 break;
      case ORDER_STATUS_MARKET_POSITION   : 
      case ORDER_STATUS_DEAL              : 
      default                             : res=0;                                              break;
     }
   return res;
#endif
  }
//+------------------------------------------------------------------+

针对 MQL4 返回的订单原因,将两个新添加的“原因”添加到方法中:

//+------------------------------------------------------------------+
//| Order reason or source                                           |
//+------------------------------------------------------------------+
long COrder::OrderReason(void) const
  {
#ifdef __MQL4__
   return
     (
      this.TypeOrder()==ORDER_TYPE_BALANCE   ?  ORDER_REASON_BALANCE :
      this.TypeOrder()==ORDER_TYPE_CREDIT    ?  ORDER_REASON_CREDIT  :
      this.OrderCloseByStopLoss()            ?  ORDER_REASON_SL      :
      this.OrderCloseByTakeProfit()          ?  ORDER_REASON_TP      :  
      this.OrderMagicNumber()!=0             ?  ORDER_REASON_EXPERT  : WRONG_VALUE
     );
#else 

在我们的例子中,返回 MQL4 未执行交易量的方法总是返回一个订单手数,这对于仓位是不正确的。 对于删除挂单,我们将返回订单手数,而对于仓位,我们将返回

//+------------------------------------------------------------------+
//| Return unexecuted volume                                         |
//+------------------------------------------------------------------+
double COrder::OrderVolumeCurrent(void) const
  {
#ifdef __MQL4__
   return(this.Status()==ORDER_STATUS_HISTORY_PENDING ? ::OrderLots() : 0);
#else 

在返回订单原因描述的方法中添加两个新“原因”的描述。 对于余额信贷操作,检查利润。 如果超过零,则为入金,否则为出金:

//+------------------------------------------------------------------+
//| Reason description                                               |
//+------------------------------------------------------------------+
string COrder::GetReasonDescription(const long reason) const
  {
#ifdef __MQL4__
   return
     (
      this.IsCloseByStopLoss()            ?  TextByLanguage("Срабатывание StopLoss","Due to StopLoss")                  :
      this.IsCloseByTakeProfit()          ?  TextByLanguage("Срабатывание TakeProfit","Due to TakeProfit")              :
      this.Reason()==ORDER_REASON_EXPERT  ?  TextByLanguage("Выставлен из mql4-программы","Placed from mql4 program")   :
      this.Comment()=="cancelled"         ?  TextByLanguage("Отменён","Cancelled")                                      :
      this.Reason()==ORDER_REASON_BALANCE ?  (
                                              this.Profit()>0 ? TextByLanguage("Пополнение баланса","Deposit of funds on the account balance") :
                                              TextByLanguage("Снятие средств с баланса","Withdrawal from the balance")
                                             )                                                                          :
      this.Reason()==ORDER_REASON_CREDIT  ?  (
                                              this.Profit()>0 ? TextByLanguage("Начисление кредитных средств","Received credit funds") :
                                              TextByLanguage("Изъятие кредитных средств","Withdrawal of credit")
                                             )                                                                          :
                                             
      TextByLanguage("Свойство не поддерживается в MQL4","Property not supported in MQL4")
     );
#else 

此外,还进行了一些小的修订。 它们太微不足道了,不必在此详述。 它们主要与 MQL5/MQL4 日志中显示的文本相关。 所有编辑都可以在文章附带的函数库文件中找到。

现在我们来改进 HistoryCollection.mqh 文件中的历史集合类。
首先,包含新的类文件

//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "ListObj.mqh"
#include "..\Services\Select.mqh"
#include "..\Objects\Orders\HistoryOrder.mqh"
#include "..\Objects\Orders\HistoryPending.mqh"
#include "..\Objects\Orders\HistoryDeal.mqh"
#ifdef __MQL4__
#include "..\Objects\Orders\HistoryBalance.mqh"
#endif 
//+------------------------------------------------------------------+

由于我们只需要为函数库的 MQL4 版本提供 CHistoryBalance 类,因此包含此类的文件包含在 MQL4 的条件编译指令中。

现在我们有了一个新的余额操作类。 为了开发并将其放入集合中,我们需要添加检查订单类型的余额和信用操作类型,并将它们添加到集合中的 Refresh() 方法中。 MQL4 的 CHistoryCollection 类:

//+------------------------------------------------------------------+
//| Update the list of orders and deals                              |
//+------------------------------------------------------------------+
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();
      //--- Closed positions
      if(order_type<ORDER_TYPE_BUY_LIMIT)
        {
         CHistoryOrder *order=new CHistoryOrder(::OrderTicket());
         if(order==NULL) continue;
         if(!this.m_list_all_orders.InsertSort(order))
           {
            ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Could not add order to the list"));
            delete order;
           }
        }
      //--- Balance/credit operations
      else if(order_type>ORDER_TYPE_SELL_STOP)
        {
         CHistoryBalance *order=new CHistoryBalance(::OrderTicket());
         if(order==NULL) continue;
         if(!this.m_list_all_orders.InsertSort(order))
           {
            ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Could not add order to the list"));
            delete order;
           }
        }
      else
        {
         //--- Removed pending orders
         CHistoryPending *order=new CHistoryPending(::OrderTicket());
         if(order==NULL) continue;
         if(!this.m_list_all_orders.InsertSort(order))
           {
            ::Print(DFUN,TextByLanguage("Не удалось добавить ордер в список","Could not add order to the 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 
我们在历史订单类中进行一些修正:
//+------------------------------------------------------------------+
//| Return 'true' if an order supports the passed                    |
//| real property, otherwise, return 'false'                         |
//+------------------------------------------------------------------+
bool CHistoryOrder::SupportProperty(ENUM_ORDER_PROP_DOUBLE property)
  {
   if(
   #ifdef __MQL5__
      property==ORDER_PROP_PROFIT                  || 
      property==ORDER_PROP_PROFIT_FULL             || 
      property==ORDER_PROP_SWAP                    || 
      property==ORDER_PROP_COMMISSION              ||
      property==ORDER_PROP_PRICE_CLOSE             ||
      (
       property==ORDER_PROP_PRICE_STOP_LIMIT       && 
       (
        this.TypeOrder()<ORDER_TYPE_BUY_STOP_LIMIT || 
        this.TypeOrder()>ORDER_TYPE_SELL_STOP_LIMIT  
       )
      )
   #else
      property==ORDER_PROP_PRICE_STOP_LIMIT        && 
      this.Status()==ORDER_STATUS_HISTORY_ORDER
   #endif 
     ) return false;

   return true;
  }
//+------------------------------------------------------------------+

以前,在 MQL5 中的 StopLimit 挂单价格未传递到日志。 所以,我实现了一个检查:如果已检查的属性是 StopLimit 订单价格且如果订单类型不是 StopLimit 订单, 则属性未用到。 否则,这是一个 StopLimit 挂单,该属性是必需的。
在 MQL4 中,
对于仓位,未使用 StopLimit 订单价格。

这样 MQL4 兼容性改进的第一阶段就完毕了。

测试

为了测试目的,从 \MQL5\Experts\TestDoEasy\Part03 得到 TestDoEasyPart03_1.mq5 EA,将其保存在 MQL4 EA 文件夹 \MQL4\Experts\TestDoEasy\Part09 中,并命名为 TestDoEasyPart09.mq4

所编译的 EA 没有更改,但是如果我们查看代码,事实上它使用 MQL4 中缺失的成交列表

//--- enums
enum ENUM_TYPE_ORDERS
  {
   TYPE_ORDER_MARKET,   // Market orders
   TYPE_ORDER_PENDING,  // Pending orders
   TYPE_ORDER_DEAL      // Deals
  };
//--- input parameters

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- update history
   history.Refresh();
//--- get the collection list within the date range
   CArrayObj* list=history.GetListByTime(InpTimeBegin,InpTimeEnd,SELECT_BY_TIME_CLOSE);
   if(list==NULL)
     {
      Print("Could not get collection list");
      return INIT_FAILED;
     }
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      //--- get the order from the list
      COrder* order=list.At(i);
      if(order==NULL) continue;
      //--- if this is a deal
      if(order.Status()==ORDER_STATUS_DEAL && InpOrderType==TYPE_ORDER_DEAL)
         order.Print();
      //--- if this is a historical market order
      if(order.Status()==ORDER_STATUS_HISTORY_ORDER && InpOrderType==TYPE_ORDER_MARKET)
         order.Print();
      //--- if this is a removed pending order
      if(order.Status()==ORDER_STATUS_HISTORY_PENDING && InpOrderType==TYPE_ORDER_PENDING)
         order.Print();
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

简单地将成交替换为余额操作。 在这种情况下,我们直接在 EA 中使用条件编译,这对于最终产品是不正确的,其中应向用户隐藏按语言版本划分的所有操作。 但在这种情况下,我们只是测试函数库的改进结果,所以这不算什么大问题。

我们添加对 EA 代码的微小更改,用 MQL4 余额操作替换 MQL5 的成交

//+------------------------------------------------------------------+
//|                                           TestDoEasyPart03_1.mq4 |
//|                        Copyright 2018, MetaQuotes Software Corp. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//--- includes
#include <DoEasy\Collections\HistoryCollection.mqh>
//--- enums
enum ENUM_TYPE_ORDERS
  {
   TYPE_ORDER_MARKET,   // Market orders
   TYPE_ORDER_PENDING,  // Pending orders
#ifdef __MQL5__
   TYPE_ORDER_DEAL      // Deals
#else 
   TYPE_ORDER_BALANCE   // Balance/Credit
#endif 
  };
//--- input parameters
input ENUM_TYPE_ORDERS  InpOrderType   =  TYPE_ORDER_MARKET;   // Show type:
input datetime          InpTimeBegin   =  0;                   // Start date of required range
input datetime          InpTimeEnd     =  END_TIME;            // End date of required range
//--- global variables
CHistoryCollection history;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- update history
   history.Refresh();
//--- get the collection list within the date range
   CArrayObj* list=history.GetListByTime(InpTimeBegin,InpTimeEnd,SELECT_BY_TIME_CLOSE);
   if(list==NULL)
     {
      Print("Could not get collection list");
      return INIT_FAILED;
     }
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      //--- get the order from the list
      COrder* order=list.At(i);
      if(order==NULL) continue;
   //--- if this is a deal
   #ifdef __MQL5__
      if(order.Status()==ORDER_STATUS_DEAL && InpOrderType==TYPE_ORDER_DEAL)
         order.Print();
   #else 
   //--- if this is a balance/credit operation
      if(order.Status()==ORDER_STATUS_BALANCE && InpOrderType==TYPE_ORDER_BALANCE)
         order.Print();
   #endif 
   //--- if this is a historical market order
      if(order.Status()==ORDER_STATUS_HISTORY_ORDER && InpOrderType==TYPE_ORDER_MARKET)
         order.Print();
   //--- if this is a removed pending order
      if(order.Status()==ORDER_STATUS_HISTORY_PENDING && InpOrderType==TYPE_ORDER_PENDING)
         order.Print();
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+

在终端中编译并启动 EA(来自第三篇文章的测试 EA 仅在 OnInit() 处理程序中工作,因此它将在启动后或更改列表后显示一次在设置中所需的历史集合列表)。

在启动 EA 之前,在终端的“帐户历史记录”选项卡的关联菜单中选择“所有历史记录”选项,因为在 MetaTrader 4 中,应用程序可用的历史记录数量取决于选项卡中选择的历史记录大小。

在设置中选择余额/信贷,并在日志中显示第一笔余额:


现在我们需要检查平仓的搜索和显示是否正确。 由于我最近才开设了 MetaTrader 4 账户,因此其中没有交易。 我开了卖单,设置了止损和止盈,然后离开去煮咖啡。 当我回来后,持仓被止损平仓,行情开始向空头方向移动。 对的,一切总是那样! :)

但现在有了一个平仓可进行测试。
在设置中选择“在场订单”:


现在我们检查已删除的挂单列表。 我设置了几对订单,然后将它们删除。
在设置中选择“挂单”:


还会显示已删除的挂单列表。

下一步是什么?

在下一篇文章中,我们将实现在 MQL4 中处理持仓和激活挂单的能力。

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

返回内容

系列中的前几篇文章:

第一部分 概念,数据管理。
第二部分 历史订单和成交集合。
第三部分 在场订单和持仓集合,安排搜索。
第四部分 交易事件, 概念。
第五部分 交易事件的集合和类。 将事件发送到程序。
第六部分 净持帐户事件。
第七部分 StopLimit 挂单激活事件,为订单和持仓修改事件准备功能。
第八部分 订单和持仓修改事件。


本文译自 MetaQuotes Software Corp. 撰写的俄文原文
原文地址: https://www.mql5.com/ru/articles/6651

附加的文件 |
MQL5.zip (89.98 KB)
MQL4.zip (89.98 KB)
轻松快捷开发 MetaTrader 程序的函数库(第八部分):订单和持仓修改事件 轻松快捷开发 MetaTrader 程序的函数库(第八部分):订单和持仓修改事件

在之前的文章中,我们已着手创建一个大型跨平台函数库,简化 MetaTrader 5 和 MetaTrader 4 平台程序的开发。 在第七部分中,我们加入了 StopLimit 订单激活跟踪,以及准备了其它涉及订单和持仓事件的跟踪功能。 在本文中,我们将开发用于跟踪订单和持仓修改事件的类。

开发跨平台网格EA(第二部分):在趋势方向上的基于范围的网格 开发跨平台网格EA(第二部分):在趋势方向上的基于范围的网格

在本文中,我们将开发一个网格EA,用于在一个范围内的趋势方向上进行交易。这样,此EA主要适用于外汇和大宗商品市场,根据测试,我们的网格EA 自2018年以来显示盈利,不幸的是,这在2014-2018年期间并非如此。

开发一个跨平台的EA交易来根据风险设置止损和获利 开发一个跨平台的EA交易来根据风险设置止损和获利

在本文中,我们将创建一个EA交易,用于基于风险值自动进行入场手数的计算。此外,EA交易将能够以止损的比率来自动设置获利,也就是说,它可以根据任何选择的比率计算获利,例如3比1、4比1或任何其他选择的值。

攫取盈利至最后的点位 攫取盈利至最后的点位

本文尝试阐述在算法交易领域中将理论与实践相结合。 有关创建交易系统的大多数讨论都与依据历史柱线和其上应用的各种指标有关联。 这是覆盖率最高的领域,因此我们不会再过多涉及。 柱线体现出非常人工的实体; 因此,我们将使用更接近原始数据的东西,即价格的即时报价。