MQL5编程踩坑实录

 

今天说说OrderSend(m_request,m_result)的事。这不是MQL4,但事却多。

当你使用它来处理挂单时,正常的示例代码如下:

#define EXPERT_MAGIC 123456                             // EA交易的幻数
input ENUM_ORDER_TYPE orderType=ORDER_TYPE_BUY_LIMIT;   // 订单类型
//+------------------------------------------------------------------+
//| 下挂单                                                            |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- 声明并初始化交易请求和交易请求结果
   MqlTradeRequest request={};
   MqlTradeResult  result={};
//--- 下挂单的参数
   request.action   =TRADE_ACTION_PENDING;                             // 交易操作类型
   request.symbol   =Symbol();                                         // 交易品种
   request.volume   =0.1;                                              //0.1手交易量
   request.deviation=2;                                                // 允许价格偏差
   request.magic    =EXPERT_MAGIC;                                     // 订单幻数
   int offset = 50;                                                    // 以点数从当前价抵消下单
   double price;                                                       // 订单触动价
   double point=SymbolInfoDouble(_Symbol,SYMBOL_POINT);                // value of point
   int digits=SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);                // 小数位数 (精确度)
   //--- 检查操作类型
   if(orderType==ORDER_TYPE_BUY_LIMIT)
     {
      request.type     =ORDER_TYPE_BUY_LIMIT;                          // 订单类型
      price=SymbolInfoDouble(Symbol(),SYMBOL_ASK)-offset*point;        // 持仓价格 
      request.price    =NormalizeDouble(price,digits);                 //正常开盘价 
     }
   else if(orderType==ORDER_TYPE_SELL_LIMIT)
     {
      request.type     =ORDER_TYPE_SELL_LIMIT;                          // order type
      price=SymbolInfoDouble(Symbol(),SYMBOL_ASK)+offset*point;         // 持仓价 
      request.price    =NormalizeDouble(price,digits);                  // 正常开盘价 
     }
   else if(orderType==ORDER_TYPE_BUY_STOP)
     {
      request.type =ORDER_TYPE_BUY_STOP;                                // 订单类型
      price        =SymbolInfoDouble(Symbol(),SYMBOL_ASK)+offset*point; // 开盘价 
      request.price=NormalizeDouble(price,digits);                      // 正常开盘价 
     }
   else if(orderType==ORDER_TYPE_SELL_STOP)
     {
      request.type     =ORDER_TYPE_SELL_STOP;                           // 订单类型
      price=SymbolInfoDouble(Symbol(),SYMBOL_ASK)-offset*point;         // 开盘价 
      request.price    =NormalizeDouble(price,digits);                  // 正常开盘价 
     }
   else Alert("This example is only for placing pending orders");   // 如果没有挂单被选择
//--- 发送请求
   if(!OrderSend(request,result))
      PrintFormat("OrderSend error %d",GetLastError());                 // 如果不能发送请求,输出错误代码
//--- 操作信息
   PrintFormat("retcode=%u  deal=%I64u  order=%I64u",result.retcode,result.deal,result.order);
  }
//+------------------------------------------------------------------+

示例中,挂单间隔是50点。在正常或较慢的行情中,完全正常运行。但当行情波动较快时,会频繁出现#4756 的失败提示,同时retcode=10015,从帮助文档里得知,这是价格无效。

问题来了,明明设置了50点的间隔距离,也标准化了小数位数,为什么会无效呢?其实还是50点小了,执行OrderSend()时,当服务器收到你的发送请求时,行情当时的价格已经跑了一段,这个时候的价格已经不适合接收到的订单类型的订单了。

以BUY_STOP为例,假设XAUUSD现价5000.00,你以5000.50报价,可服务器接收到你的请求时,行情价格已经是5000.60了,自然,你5000.50的突破多单便成了无效价格。

解决办法:酌情放弃小间隔50点,对于活跃的品种,须适当放大些点值;若不愿放大,只想在50点上挂,就得坦然看待 专家/日志中的4756失败提示。

上述代码中还存在一个BUG。按前述举例,价格上到了5000.60,实际当价格行情为5000.40时,STOP_BUY也许就会4756了。为什么?不同的品种都有2个可能“冻结”的阙值常量如下:

SYMBOL_TRADE_STOPS_LEVEL

止蚀盘当前收盘价格的最小空间



SYMBOL_TRADE_FREEZE_LEVEL

凝结交易操作的距离

 
 


当你的价格位于它以行情实时价为基础的此常量点值的范围时,你的报价也将是4756无效的。所以,上述参考文档中的示例代码可以简单认为属于“伪代码”……哈哈,严格说,是不严谨的。

基于此,我们在计算price时,还得核对一下price不应处于其“冻结”范围内。如下:

……
        int stoplevel = (int)SymbolInfoInteger(Symbol(), SYMBOL_TRADE_STOPS_LEVEL);
         int freezelevel = (int)SymbolInfoInteger(Symbol(), SYMBOL_TRADE_FREEZE_LEVEL);
        int step=10;
        
//--- 检查操作类型
        ......//limit 单类似如下处理
   ...... if(orderType==ORDER_TYPE_BUY_STOP)
     {
      request.type =ORDER_TYPE_BUY_STOP;                                // 订单类型
     double ask=SymbolInfoDouble(Symbol(),SYMBOL_ASK);   //取得当前价

         if( price>ask+(stoplevel+step)*mpoint && price>ask+(freezelevel+step)*mpoint) //判断报价不在其“冻结”范围内,同时预留10个点空间
                price        =ask+offset*point; // 开盘价 
        else {
                Print("当前价格过于接近现价.");
                return false;
        }
      request.price=NormalizeDouble(price,digits);                      // 正常开盘价 
     }
   else ....//sell stop 单类似
//--- 发送请求
   if(!OrderSend(request,result))
      PrintFormat("OrderSend error %d",GetLastError());                 // 如果不能发送请求,输出错误代码
//--- 操作信息
   PrintFormat("retcode=%u  deal=%I64u  order=%I64u",result.retcode,result.deal,result.order);
  }

以上,是正常发送挂单请求的处理。发送时增加检查是否处于“冻结”内,这仅仅是多一道处理环节,以尽量少收到4756通知,不代表就完全避免接“电话”,

因为,MT5的异步机制,它不以你发送时的校对为准,而是以它收到你的请求时,它取当时的实时价(如ask)来判断你的请求是否有效的。

因此我们能做的尽量做到即可,至于会不会收到4756“电话”通知,就只有祈祷了……