用于读取活动订单特性的函数

对于活动订单和历史订单,可用于获取所有订单特性值的函数集并不相同。本节介绍读取活动订单特性的函数。有关访问历史订单特性的函数,请参见 相关章节

可以使用 OrderGetInteger 函数读取整数型特性,该函数有两种形式:第一种直接返回特性值,第二种返回成功 (true) 或错误 (false) 的逻辑交易品种,第二种通过引用传递的参数用特性值填充。

long OrderGetInteger(ENUM_ORDER_PROPERTY_INTEGER property)

bool OrderGetInteger(ENUM_ORDER_PROPERTY_INTEGER property, long &value)

这两个函数都允许你获取请求的整数兼容类型的订单特性(datetimelong/ulong 或 listing)。虽然原型提到了long,但从技术角度来看,该值存储为一个 8 字节的单元,可以将其强制转换为兼容的类型,而无需对内部表示进行任何转换,特别是转换为 ulong,用于所有订单号。

一对类似的函数用于实数型 double 的特性。

double OrderGetDouble(ENUM_ORDER_PROPERTY_DOUBLE property)

bool OrderGetDouble(ENUM_ORDER_PROPERTY_DOUBLE property, double &value)

最后,字符串型特性可通过一对 OrderGetString 函数获得。

string OrderGetString(ENUM_ORDER_PROPERTY_STRING property)

bool OrderGetString(ENUM_ORDER_PROPERTY_STRING property, string &value)

所有函数的第一个参数都是我们感兴趣的特性标识符。这必须是下列其中一个枚举元素:ENUM_ORDER_PROPERTY_INTEGER、ENUM_ORDER_PROPERTY_DOUBLE 或 ENUM_ORDER_PROPERTY_STRING(在 上一节中讨论过)。

请注意,在调用任何上述函数之前,应首先使用 OrderSelectOrderGetTicket 选择一个订单。

为了读取特定订单的所有特性,我们将开发 OrderMonitor 类(OrderMonitor.mqh),其工作原理与之前考虑的交易品种 (SymbolMonitor.mqh) 和交易账户 (AccountMonitor.mqh) 监视器相同。

本书中讨论的这些类和其他 monitor 类提供了一种统一的方法,通过重载版本的虚拟 get 方法来分析特性。

回顾一下,假设交易和仓位根据三种主要类型的值具有相同的特性分组,并且我们还需要为其实现监视器。在这方面,将通用算法将其分离到一个基础抽象类 MonitorInterface (TradeBaseMonitor.mqh) 中是有意义的。这是一个具有三个参数的模板类,用于为整数型 (I)、实数型 (D) 和字符串型 (S) 特性组指定特定枚举的类型。

#include <MQL5Book/EnumToArray.mqh>
   
template<typename I,typename D,typename S>
class MonitorInterface
{
protected:
   bool ready;
public:
   MonitorInterface(): ready(false) { }
   
   bool isReady() const
   {
      return ready;
   }
   ...

由于在交易环境中寻找订单(交易或仓位)可能会因各种原因而失败,该类有一个保留变量 ready,其中派生类必须写入成功初始化的标志,即选择一个对象来读取其特性。

几个纯虚方法声明对相应类型特性的访问。

   virtual long get(const I propertyconst = 0;
   virtual double get(const D propertyconst = 0;
   virtual string get(const S propertyconst = 0;
   virtual long get(const int propertyconst longconst = 0;
   virtual double get(const int propertyconst doubleconst = 0;
   virtual string get(const int propertyconst stringconst = 0;
   ...

在前三种方法中,特性类型由模板参数之一指定。在另外三种方法中,类型由方法本身的第二个参数指定:这是必需的,因为最新方法不接受特定枚举的常量,而只接受一个整数作为第一个参数。一方面,这便于标识符的连续编号(三种类型的枚举常量不相交)。另一方面,我们需要另一个来源来确定值类型,因为函数/方法返回的类型不参与选择适当 重载的过程。

这种方法允许你基于调用代码中可用的各种输入来获取特性。接下来,我们将基于 OrderMonitor(以及期货的 DealMonitorPositionMonitor)创建类,以根据一组任意条件选择对象,所有这些方法都将是必需的。

通常,程序需要获得任何特性的字符串表示,例如,用于日志记录。在新的监视器中,这是通过 stringify 方法实现的。显然,它们可通过上面提到的 get 方法调用来获取所请求特性的值。

   virtual string stringify(const long vconst I propertyconst = 0;
   
   virtual string stringify(const I propertyconst
   {
      return stringify(get(property), property);
   }
   
   virtual string stringify(const D propertyconst string format = NULLconst
   {
      if(format == NULLreturn (string)get(property);
      return StringFormat(formatget(property));
   }
   
   virtual string stringify(const S propertyconst
   {
      return get(property);
   }
   ...

唯一没有实现的方法是 long类型的 stringify 的第一个版本。这是因为,正如我们在上一节中看到的,整数型特性组实际上包含不同的应用程序类型,包括日期和时间、枚举和整数。因此,只有派生类可以提供其到可理解字符串的转换。这种情况对于所有交易实体都是常见的,不仅是订单,还有交易和仓位,我们将在后面考虑其特性。

当整数型特性包含枚举元素(例如 ENUM_ORDER_TYPE、ORDER_TYPE_FILLING 等)时,应使用 EnumToString 函数将其转换为字符串。这个任务由一个辅助方法 enumstr 来完成。很快我们将看到该方法在特定监视器类中的广泛使用,在几个段落之后从 OrderMonitor 开始。

   template<typename E>
   static string enumstr(const long v)
   {
      return EnumToString((E)v);
   }

为了记录特定类型的所有特性,我们创建了 list2log 方法,该方法在循环中使用 stringify

   template<typename E>
   void list2log() const
   {
      E e = (E)0// suppress warning 'possible use of uninitialized variable'
      int array[];
      const int n = EnumToArray(earray0USHORT_MAX);
      Print(typename(E), " Count="n);
      for(int i = 0i < n; ++i)
      {
         e = (E)array[i];
         PrintFormat("% 3d %s=%s"iEnumToString(e), stringify(e));
      }
   }

最后,为了更容易地记录所有三个组的特性,有一个print 方法,它为每组特性调用 list2log 三次。

   virtual void print() const
   {
      if(!readyreturn;
      
      Print(typename(this));
      list2log<I>();
      list2log<D>();
      list2log<S>();
   }

利用一个基础模板类 MonitorInterface,我们可以说明 OrderMonitorInterface,其中为上一节中的订单指定了某些枚举类型,并为订单的整数型特性提供了 stringify 的实现。

class OrderMonitorInterface:
   public MonitorInterface<ENUM_ORDER_PROPERTY_INTEGER,
   ENUM_ORDER_PROPERTY_DOUBLE,ENUM_ORDER_PROPERTY_STRING>
{
public:
   // description of properties according to subtypes
   virtual string stringify(const long v,
      const ENUM_ORDER_PROPERTY_INTEGER propertyconst override
   {
      switch(property)
      {
         case ORDER_TYPE:
            return enumstr<ENUM_ORDER_TYPE>(v);
         case ORDER_STATE:
            return enumstr<ENUM_ORDER_STATE>(v);
         case ORDER_TYPE_FILLING:
            return enumstr<ENUM_ORDER_TYPE_FILLING>(v);
         case ORDER_TYPE_TIME:
            return enumstr<ENUM_ORDER_TYPE_TIME>(v);
         case ORDER_REASON:
            return enumstr<ENUM_ORDER_REASON>(v);
         
         case ORDER_TIME_SETUP:
         case ORDER_TIME_EXPIRATION:
         case ORDER_TIME_DONE:
            return TimeToString(vTIME_DATE TIME_SECONDS);
         
         case ORDER_TIME_SETUP_MSC:
         case ORDER_TIME_DONE_MSC:
            return STR_TIME_MSC(v);
      }
      
      return (string)v;
   }
};

以毫秒为单位显示时间的 STR_TIME_MSC 宏定义如下:

#define STR_TIME_MSC(T) (TimeToString((T) / 1000TIME_DATE | TIME_SECONDS) \
    + StringFormat("'%03d", (T) % 1000))

现在我们准备介绍用于读取任何订单特性的最终类:OrderMonitor,派生自 OrderMonitorInterface。订单号被传递给构造函数,并在交易环境中使用 OrderSelect 进行选择。

class OrderMonitorpublic OrderMonitorInterface
{
public:
   const ulong ticket;
   OrderMonitor(const long t): ticket(t)
   {
      if(!OrderSelect(ticket))
      {
         PrintFormat("Error: OrderSelect(%lld) failed: %s",
            ticketE2S(_LastError));
      }
      else
      {
         ready = true;
      }
   }
   ...

监视器的主要工作部分包括读取特性的虚函数的重新定义。此处我们看到了 OrderGetIntegerOrderGetDoubleOrderGetString 函数的调用。

   virtual long get(const ENUM_ORDER_PROPERTY_INTEGER propertyconst override
   {
      return OrderGetInteger(property);
   }
   
   virtual double get(const ENUM_ORDER_PROPERTY_DOUBLE propertyconst override
   {
      return OrderGetDouble(property);
   }
   
   virtual string get(const ENUM_ORDER_PROPERTY_STRING propertyconst override
   {
      return OrderGetString(property);
   }
   
   virtual long get(const int propertyconst longconst override
   {
      return OrderGetInteger((ENUM_ORDER_PROPERTY_INTEGER)property);
   }
   
   virtual double get(const int propertyconst doubleconst override
   {
      return OrderGetDouble((ENUM_ORDER_PROPERTY_DOUBLE)property);
   }
   
   virtual string get(const int propertyconst string)  const override
   {
      return OrderGetString((ENUM_ORDER_PROPERTY_STRING)property);
   }
};

该代码片段以简短的形式呈现:用于处理历史中订单的运算符已删除。当我们在接下来的章节中探讨这个方面时,我们将看到 OrderMonitor 的完整代码。

值得注意的是,监视器对象不存储其特性的副本。因此,对 get 方法的访问必须在创建对象以及相应的调用 OrderSelect 之后立即执行。要在以后读取这些特性,需要在 MQL 程序的内部缓存中再次分配该订单,例如,通过调用方法 refresh

   void refresh()
   {
      ready = OrderSelect(ticket);
   }

我们通过将 OrderMonitor 添加到 EA 交易 MarketOrderSend.mq5 来测试其运行情况。一个名为 MarketOrderSendMonitor.mq5 的新版本通过指令 #include 连接文件 OrderMonitor.mqh,并在 OnTimer 函数体(在订单上成功确认开仓的块)中创建一个监视器对象并调用其 print 方法。

#include <MQL5Book/OrderMonitor.mqh>
...
void OnTimer()
{
   ...
   const ulong order = (wantToBuy ?
      request.buy(volumePrice) :
      request.sell(volumePrice));
   if(order != 0)
   {
      Print("OK Order: #="order);
      if(request.completed())
      {
         Print("OK Position: P="request.result.position);
         
         OrderMonitor m(order);
         m.print();
         ...
      }
   }
}

在日志中,我们可看到包含订单所有特性的新行。

OK Order: #=1287846602
Waiting for position for deal D=1270417032
OK Position: P=1287846602
MonitorInterface<ENUM_ORDER_PROPERTY_INTEGER, »
   » ENUM_ORDER_PROPERTY_DOUBLE,ENUM_ORDER_PROPERTY_STRING>
ENUM_ORDER_PROPERTY_INTEGER Count=14
  0 ORDER_TIME_SETUP=2022.03.21 13:28:59
  1 ORDER_TIME_EXPIRATION=1970.01.01 00:00:00
  2 ORDER_TIME_DONE=2022.03.21 13:28:59
  3 ORDER_TYPE=ORDER_TYPE_BUY
  4 ORDER_TYPE_FILLING=ORDER_FILLING_FOK
  5 ORDER_TYPE_TIME=ORDER_TIME_GTC
  6 ORDER_STATE=ORDER_STATE_FILLED
  7 ORDER_MAGIC=1234567890
  8 ORDER_POSITION_ID=1287846602
  9 ORDER_TIME_SETUP_MSC=2022.03.21 13:28:59'572
 10 ORDER_TIME_DONE_MSC=2022.03.21 13:28:59'572
 11 ORDER_POSITION_BY_ID=0
 12 ORDER_TICKET=1287846602
 13 ORDER_REASON=ORDER_REASON_EXPERT
ENUM_ORDER_PROPERTY_DOUBLE Count=7
  0 ORDER_VOLUME_INITIAL=0.01
  1 ORDER_VOLUME_CURRENT=0.0
  2 ORDER_PRICE_OPEN=1.10275
  3 ORDER_PRICE_CURRENT=1.10275
  4 ORDER_PRICE_STOPLIMIT=0.0
  5 ORDER_SL=0.0
  6 ORDER_TP=0.0
ENUM_ORDER_PROPERTY_STRING Count=3
  0 ORDER_SYMBOL=EURUSD
  1 ORDER_COMMENT=
  2 ORDER_EXTERNAL_ID=
TRADE_ACTION_DEAL, EURUSD, ORDER_TYPE_BUY, V=0.01, ORDER_FILLING_FOK, »
   » @ 1.10275, P=1287846602, M=1234567890
DONE, D=1270417032, #=1287846602, V=0.01, @ 1.10275, Bid=1.10275, Ask=1.10275, »
   » Request executed, Req=3

第四行开始于 print 方法的输出,其中包括监视器对象 MonitorInterface 的全名以及参数类型(在本例中是三重 ENUM_ORDER_PROPERTY ),然后是特定订单的所有特性。

但是,特性打印并不是监视器所能提供的最有趣操作。根据条件(任意特性值)选择订单的任务在 EA 交易中更受欢迎。使用监视器作为辅助工具,我们可创建一个筛选订单的机制,类似于我们对交易品种 SymbolFilter.mqh 所创建的机制。