下达挂单

订单类型中,我们从理论上探讨了平台支持的所有挂单下达选项。从实用的角度来看,订单是使用 OrderSend/OrderSendAsync 函数创建的,根据特殊的规则预先填充了请求结构体 MqlTradeRequest。具体而言,action 字段必须包含 TRADE_ACTION_PENDING 值(在 ENUM_TRADE_REQUEST_ACTIONS 枚举中)。考虑到这一点,以下字段为必填:

  • action
  • symbol
  • volume
  • price
  • type(默认值 0 对应于 ORDER_TYPE_BUY)
  • type_filling(默认值 0 对应于 ORDER_FILLING_FOK)
  • type_time(默认值 0 对应于 ORDER_TIME_GTC)
  • expiration(默认值为 0,不适用于 ORDER_TIME_GTC)

如果零默认值适用于该任务,则可以跳过最后四个字段中的某些字段。

只有订单类型为 ORDER_TYPE_BUY_STOP_LIMIT 和 ORDER_TYPE_SELL_STOP_LIMIT 时,才必须填写 stoplimit 字段。

以下字段可选填:

  • sl
  • tp
  • magic
  • comment

sltp 中的零值表示没有保护水平。

我们在 MqlTradeSync.mqh 文件中将检查值和将字段填充方法添加到我们的结构体中。所有类型的订单的形成原理都是相同的,所以我们考虑一些下达限制买卖订单的特殊情况。其余类型的仅在字段类型的值方面有区别。具有全套必需字段以及保护水平的公共方法是根据类型命名的:buyLimitsellLimit

   ulong buyLimit(const string nameconst double lotconst double p,
      const double stop = 0const double take = 0,
      ENUM_ORDER_TYPE_TIME duration = ORDER_TIME_GTCdatetime until = 0)
   {
      type = ORDER_TYPE_BUY_LIMIT;
      return _pending(namelotpstoptakedurationuntil);
   }
   
   ulong sellLimit(const string nameconst double lotconst double p,
      const double stop = 0const double take = 0,
      ENUM_ORDER_TYPE_TIME duration = ORDER_TIME_GTCdatetime until = 0)
   {
      type = ORDER_TYPE_SELL_LIMIT;
      return _pending(namelotpstoptakedurationuntil);
   }

由于该结构体包含 symbol 字段,而该字段在构造函数中的初始化是可选的,所以有一些类似的无 name 参数的方法:它们通过传递 symbol 作为第一个参数来调用上述方法。因此,要以最少的工作量创建订单,请编写以下代码:

MqlTradeRequestSync request// by default uses the current chart symbol
request.buyLimit(volumeprice);

用于检查传递的值、规范化、并将其保存在结构体字段中以及创建挂单代码的常规部分已经被移到了辅助方法 _pending 中。如果成功,则返回订单号,如果失败,则返回 0。

   ulong _pending(const string nameconst double lotconst double p,
      const double stop = 0const double take = 0,
      ENUM_ORDER_TYPE_TIME duration = ORDER_TIME_GTCdatetime until = 0,
      const double origin = 0)
   {
      action = TRADE_ACTION_PENDING;
      if(!setSymbol(name)) return 0;
      if(!setVolumePrices(lotpstoptakeorigin)) return 0;
      if(!setExpiration(durationuntil)) return 0;
      if((SymbolInfoInteger(nameSYMBOL_ORDER_MODE) & (1 << (type / 2))) == 0)
      {
         Print(StringFormat("pending orders %s not allowed for %s",
            EnumToString(type), name));
         return 0;
      }
      ZeroMemory(result);
      if(OrderSend(thisresult)) return result.order;
      return 0;
   }

我们已经知道如何填充 action 字段以及如何从以前的交易操作中调用 setSymbolsetVolumePrices 的方法。

多字符串 if 运算符可确保正在准备的运算出现在允许交易品种运算中(在 SYMBOL_ORDER_MODE 特性中指定)。整数型除法 type 除以二并将结果值移位 1,用于在允许的订单类型掩码中设置正确的位。这是由于 ENUM_ORDER_TYPE 枚举中的常量和 SYMBOL_ORDER_MODE 特性的组合。例如,ORDER_TYPE_BUY_STOP 和 ORDER_TYPE_SELL_STOP 的值分别为 4 和 5,除以 2 均为 2(去掉小数)。运算 1 << 2 的结果 4 等于 SYMBOL_ORDER_STOP。

挂单的一个特殊功能是处理到期日期。setExpiration 方法用于处理到期日期。在此方法中,应确保允许为该交易品种指定的到期模式 ENUM_ORDER_TYPE_TIME (duration) 并且正确填写 until 中的日期和时间。

   bool setExpiration(ENUM_ORDER_TYPE_TIME duration = ORDER_TIME_GTCdatetime until = 0)
   {
      const int modes = (int)SymbolInfoInteger(symbolSYMBOL_EXPIRATION_MODE);
      if(((1 << duration) & modes) != 0)
      {
         type_time = duration;
         if((duration == ORDER_TIME_SPECIFIED || duration == ORDER_TIME_SPECIFIED_DAY)
            && until == 0)
         {
            Print(StringFormat("datetime is 0, "
               "but it's required for order expiration mode %s",
               EnumToString(duration)));
            return false;
         }
         if(until > 0 && until <= TimeTradeServer())
         {
            Print(StringFormat("expiration datetime %s is in past, server time is %s",
               TimeToString(until), TimeToString(TimeTradeServer())));
            return false;
         }
         expiration = until;
      }
      else
      {
         Print(StringFormat("order expiration mode %s is not allowed for %s",
            EnumToString(duration), symbol));
         return false;
      }
      return true;
   }

允许模式的位掩码在 SYMBOL_EXPIRATION_MODE 特性中提供。掩码中的位和常量 ENUM_ORDER_TYPE_TIME 的组合是这样的,我们只需计算表达式 1 << duration,并将其叠加在掩码上:非零值表示模式存在。

对于 ORDER_TIME_SPECIFIED 和 ORDER_TIME_SPECIFIED_DAY 模式,具有特定 datetime 值的 expiration 字段不能为空。此外,指定的日期和时间不能是过去的时间。

由于前面介绍的 _pending 方法最终使用 OrderSend 向服务器发送请求,所以我们的程序必须确保实际创建了带有接收订单号的订单(这对于可以输出到外部交易系统的限价订单尤其重要)。因此,在用于“阻止”结果控制的 completed 方法中,我们将为 TRADE_ACTION_PENDING 操作添加一个分支。

   bool completed()
   {
      // old processing code
      // TRADE_ACTION_DEAL
      // TRADE_ACTION_SLTP
      // TRADE_ACTION_CLOSE_BY
      ...
      else if(action == TRADE_ACTION_PENDING)
      {
         return result.placed(timeout);
      }
      ...
      return false;
   }

MqlTradeResultSync 结构体中,我们添加了 placed 方法。

   bool placed(const ulong msc = 1000)
   {
      if(retcode != TRADE_RETCODE_DONE
         && retcode != TRADE_RETCODE_DONE_PARTIAL)
      {
         return false;
      }
      
      if(!wait(orderExistmsc))
      {
         Print("Waiting for order: #" + (string)order);
         return false;
      }
      return true;
   }

其主要任务是使用 orderExist 函数中的 wait 等待订单的出现:其已经被用于 开仓验证的第一阶段。

为了测试新功能,我们实现 EA 交易 PendingOrderSend.mq5。其支持使用输入变量选择挂单类型及其所有特性,之后执行确认请求。

enum ENUM_ORDER_TYPE_PENDING
{                                                        // UI interface strings
   PENDING_BUY_STOP = ORDER_TYPE_BUY_STOP,               // ORDER_TYPE_BUY_STOP
   PENDING_SELL_STOP = ORDER_TYPE_SELL_STOP,             // ORDER_TYPE_SELL_STOP
   PENDING_BUY_LIMIT = ORDER_TYPE_BUY_LIMIT,             // ORDER_TYPE_BUY_LIMIT
   PENDING_SELL_LIMIT = ORDER_TYPE_SELL_LIMIT,           // ORDER_TYPE_SELL_LIMIT
   PENDING_BUY_STOP_LIMIT = ORDER_TYPE_BUY_STOP_LIMIT,   // ORDER_TYPE_BUY_STOP_LIMIT
   PENDING_SELL_STOP_LIMIT = ORDER_TYPE_SELL_STOP_LIMIT// ORDER_TYPE_SELL_STOP_LIMIT
};
 
input string Symbol;             // Symbol (empty = current _Symbol)
input double Volume;             // Volume (0 = minimal lot)
input ENUM_ORDER_TYPE_PENDING Type = PENDING_BUY_STOP;
input int Distance2SLTP = 0;     // Distance to SL/TP in points (0 = no)
input ENUM_ORDER_TYPE_TIME Expiration = ORDER_TIME_GTC;
input datetime Until = 0;
input ulong Magic = 1234567890;
input string Comment;

每次启动或更改参数时,EA 交易都会创建一个新订单。自动 订单删除 尚未提供。我们将在后面讨论这种操作类型。在这方面,不要忘记手动删除订单。

与前面的一些示例一样,一次性订单下达是基于计时器执行的(因此,你应首先确保市场是开放的)。

void OnTimer()
{
   // execute once and wait for the user to change the settings
   EventKillTimer();
   
   const string symbol = StringLen(Symbol) == 0 ? _Symbol : Symbol;
   if(PlaceOrder((ENUM_ORDER_TYPE)TypesymbolVolume,
      Distance2SLTPExpirationUntilMagicComment))
   {
      Alert("Pending order placed - remove it manually, please");
   }
}

PlaceOrder 函数接受所有设置作为参数,发送一个请求,并返回一个成功指示符(非零订单号)。所有受支持类型的订单都提供了与当前价格的预先填充距离,该距离作为每日报价范围的一部分进行计算。

ulong PlaceOrder(const ENUM_ORDER_TYPE type,
   const string symbolconst double lot,
   const int sltpENUM_ORDER_TYPE_TIME expirationdatetime until,
   const ulong magic = 0const string comment = NULL)
{
   static double coefficients[] = // indexed by order type
   {
      0  ,   // ORDER_TYPE_BUY - not used
      0  ,   // ORDER_TYPE_SELL - not used
     -0.5,   // ORDER_TYPE_BUY_LIMIT - slightly below the price
     +0.5,   // ORDER_TYPE_SELL_LIMIT - slightly above the price
     +1.0,   // ORDER_TYPE_BUY_STOP - far above the price
     -1.0,   // ORDER_TYPE_SELL_STOP - far below the price
     +0.7,   // ORDER_TYPE_BUY_STOP_LIMIT - average above the price 
     -0.7,   // ORDER_TYPE_SELL_STOP_LIMIT - average below the price
      0  ,   // ORDER_TYPE_CLOSE_BY - not used
   };
   ...

例如,ORDER_TYPE_BUY_LIMIT 的系数为 -0.5,表示订单将低于当前价格一半的日线范围下达(在该范围内反弹),ORDER_TYPE_BUY_STOP 的系数为 +1.0,表示订单将位于该范围的上边界(突破)。

日线范围本身的计算方式如下。

   const double range = iHigh(symbolPERIOD_D11) - iLow(symbolPERIOD_D11);
   Print("Autodetected daily range: ", (float)range);
   ...

我们找到了下面需要的交易量和点值。

   const double volume = lot == 0 ? SymbolInfoDouble(symbolSYMBOL_VOLUME_MIN) : lot;
   const double point = SymbolInfoDouble(symbolSYMBOL_POINT);

根据总范围中给定的系数,在 price 变量中计算下达订单的价格水平。

   const double price = TU::GetCurrentPrice(typesymbol) + range * coefficients[type];

只能为 *_STOP_LIMIT 订单填写 stoplimit 字段。其值存储在 origin 变量中。

   const bool stopLimit =
      type == ORDER_TYPE_BUY_STOP_LIMIT ||
      type == ORDER_TYPE_SELL_STOP_LIMIT;
   const double origin = stopLimit ? TU::GetCurrentPrice(typesymbol) : 0;

当这两种类型的订单被触发时,将以当前价格下达一个新的挂单。事实上,在这种情况下,价格从当前值移动到订单被激活的 price 水平,因此“以前的当前”价格成为限价订单指示的正确反弹水平。我们将在下面说明这种情况。

保护水平是使用 TU::TradeDirection 对象确定的。对于限制止损订单,我们从 origin 开始计算。

   TU::TradeDirection dir(type);
   const double stop = sltp == 0 ? 0 :
      dir.negative(stopLimit ? origin : pricesltp * point);
   const double take = sltp == 0 ? 0 :
      dir.positive(stopLimit ? origin : pricesltp * point);

接下来,说明其结构体并填写可选字段。

   MqlTradeRequestSync request(symbol);
   
   request.magic = magic;
   request.comment = comment;
   // request.type_filling = SYMBOL_FILLING_FOK;

此处,你可以选择填充模式。默认情况下,MqlTradeRequestSync 会自动选择第一个允许的模式 ENUM_ORDER_TYPE_FILLING

根据用户选择的订单类型,我们可调用某一种交易方法。

   ResetLastError();
   // fill in and check the required fields, send the request
   ulong order = 0;
   switch(type)
   {
   case ORDER_TYPE_BUY_STOP:
      order = request.buyStop(volumepricestoptakeexpirationuntil);
      break;
   case ORDER_TYPE_SELL_STOP:
      order = request.sellStop(volumepricestoptakeexpirationuntil);
      break;
   case ORDER_TYPE_BUY_LIMIT:
      order = request.buyLimit(volumepricestoptakeexpirationuntil);
      break;
   case ORDER_TYPE_SELL_LIMIT:
      order = request.sellLimit(volumepricestoptakeexpirationuntil);
      break;
   case ORDER_TYPE_BUY_STOP_LIMIT:
      order = request.buyStopLimit(volumepriceoriginstoptakeexpirationuntil);
      break;
   case ORDER_TYPE_SELL_STOP_LIMIT:
      order = request.sellStopLimit(volumepriceoriginstoptakeexpirationuntil);
      break;
   }
   ...

如果收到订单号,我们应等待其出现在终端的交易环境中。

   if(order != 0)
   {
      Print("OK order sent: #="order);
      if(request.completed()) // expect result (order confirmation)
      {
         Print("OK order placed");
      }
   }
   Print(TU::StringOf(request));
   Print(TU::StringOf(request.result));
   return order;
}

我们使用默认设置运行 EURUSD 图表上的 EA 交易,并另外选择到 1000 点保护水平的距离。我们将在日志中看到以下条目(假设默认设置与你账户中的 EURUSD 权限相匹配)。

Autodetected daily range: 0.01413
OK order sent: #=1282106395
OK order placed
TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_BUY_STOP, V=0.01, ORDER_FILLING_FOK, »
  » @ 1.11248, SL=1.10248, TP=1.12248, ORDER_TIME_GTC, M=1234567890
DONE, #=1282106395, V=0.01, Request executed, Req=91
Alert: Pending order placed - remove it manually, please

以下是其在图表上的显示效果:

挂单 ORDER_TYPE_BUY_STOP

挂单 ORDER_TYPE_BUY_STOP

我们手动删除该订单,并将订单类型更改为 ORDER_TYPE_BUY_STOP_LIMIT。结果情况会更加复杂。

挂单 ORDER_TYPE_BUY_STOP_LIMIT

挂单 ORDER_TYPE_BUY_STOP_LIMIT

上面一对点划线所在的价格为订单触发价格,因此 ORDER_TYPE_BUY_LIMIT 订单将在当前价格水平下达,Stop LossTake Profit 值用红线标记。期货 ORDER_TYPE_BUY_LIMIT 订单的 Take Profit 水平实际上与新创建的初步订单 ORDER_TYPE_BUY_STOP_LIMIT 的激活水平一致。

作为自学的另一个示例,本书包含了一个 EA 交易 AllPendingsOrderSend.mq5,EA 交易一次设置 6 个挂单:每种类型 1 个。

所有类型的挂单

所有类型的挂单

如果使用默认设置运行该 EA 交易,你可能会得到如下日志条目:

Autodetected daily range: 0.01413
OK order placed: #=1282032135
TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_BUY_LIMIT, V=0.01, ORDER_FILLING_FOK, »
  » @ 1.08824, ORDER_TIME_GTC, M=1234567890
DONE, #=1282032135, V=0.01, Request executed, Req=73
OK order placed: #=1282032136
TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_SELL_LIMIT, V=0.01, ORDER_FILLING_FOK, »
  » @ 1.10238, ORDER_TIME_GTC, M=1234567890
DONE, #=1282032136, V=0.01, Request executed, Req=74
OK order placed: #=1282032138
TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_BUY_STOP, V=0.01, ORDER_FILLING_FOK, »
  » @ 1.10944, ORDER_TIME_GTC, M=1234567890
DONE, #=1282032138, V=0.01, Request executed, Req=75
OK order placed: #=1282032141
TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_SELL_STOP, V=0.01, ORDER_FILLING_FOK, »
  » @ 1.08118, ORDER_TIME_GTC, M=1234567890
DONE, #=1282032141, V=0.01, Request executed, Req=76
OK order placed: #=1282032142
TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_BUY_STOP_LIMIT, V=0.01, ORDER_FILLING_FOK, »
  » @ 1.10520, X=1.09531, ORDER_TIME_GTC, M=1234567890
DONE, #=1282032142, V=0.01, Request executed, Req=77
OK order placed: #=1282032144
TRADE_ACTION_PENDING, EURUSD, ORDER_TYPE_SELL_STOP_LIMIT, V=0.01, ORDER_FILLING_FOK, »
  » @ 1.08542, X=1.09531, ORDER_TIME_GTC, M=1234567890
DONE, #=1282032144, V=0.01, Request executed, Req=78
Alert: 6 pending orders placed - remove them manually, please