MT4搭建开发一个跨平台网格 EA

发布日期:2019-07-06 15:35:19


简介

经常访问本网站的用户都很清楚, MQL5 是开发自定义 EA 交易的最佳选择,不幸的是, 并不是所有的经纪商都允许创建 MetaTrader 5 的账户。. 就栓您现在使用的经纪商允许这一点,您也许也会在将来换到只提供 MetaTrader 4 的经纪商那里,您将如何处理在这种情况下开发的所有 MQL5 EA 交易呢?您是否要花费大量时间对它们进行返工以适应MQL4?也许,开发一个既能在MetaTrader 5中工作又能在MetaTrader 4中工作的EA更为合理?

在本文中,我们将尝试开发这样的EA,并检查基于订单网格的交易系统是否可用。

关于条件编译的几句话

条件编译将允许我们开发一个同时在 MetaTrader 4 和 MetaTrader 5 中工作的EA。应用的语法如下:

   #ifdef __MQL5__       // MQL5 代码    #else 
      // MQL4 代码    #endif  

条件编译允许我们指定只有在MQL5 EA中完成编译时才应编译某个块。在MQL4和其他语言版本中编译时,只需丢弃此代码块,而是使用#else运算符后的代码块(如果已设置)。

这样,如果在 MQL4 和 MQL5 中实现了不同的功能,那么我们将以两种方式实现它,而条件编译允许选择特定语言所必需的选项。

在其他情况下,我们将使用在MQL4和MQL5中都有效的语法。

网格交易系统

在开始EA开发之前,让我们描述一下网格交易策略的基础知识。

网格是指将多个限价订单置于当前价格之上,同时将相同数量的限价订单置于当前价格之下的 EA 交易。

限价订单是通过一定的步骤而不是单一的价格来设定的。换言之,第一个限价订单被设定在当前价格之上的某个距离,第二个限价设置在第一个限价之上的相同距离处,以此类推。订单数量和应用的步骤各不相同,

一个方向的订单高于当前价格,而另一个方向的订单低于当前价格。应该考虑的是:

  • 在一个趋势中,买入订单应该高于当前价格,而卖出订单应该低于当前价格;
  • 在盘整期间,卖出订单应高于当前价格,而买入订单应低于当前价格。

您可以应用止损水平,也可以不使用它们。

如果你不使用止损和获利,所有未平仓合约,无论是盈利的还是亏损的,都会存在,直到整体利润达到一定水平。之后,所有未结头寸以及不受价格影响的限价订单都将关闭,并设置新的网格。

下面的屏幕截图显示了一个开启的网格:

因此,在理论上,网格交易系统可以让你在任何市场中获利,而不需要等待任何独特的进入点,也不需要使用任何指标。

如果使用止损和获利,那么利润是由于一个头寸的亏损被价格朝一个方向移动时的整体利润所覆盖而获得的。

没有止损水平,利润是由于在正确的方向上打开更多的订单而获得的。即使最初价格触及一个方向的仓位,然后转向,正确方向的新仓位将弥补先前开启的仓位的损失,因为最终会有更多的仓位。

我们的网格 EA 的工作原理

我们已经描述了上面最简单网格的工作原理,您可以为更改打开订单的方向、添加以相同价格打开多个订单的能力、添加指标等网格提供自己的选项。

在本文中,我们将尝试实现最简单的网格化版本,而不会造成停止损失,因为它基于的思想非常诱人。

事实上,认为价格迟早会在向一个方向移动时达到利润,即使最初的开仓方向是错误的,这似乎是合理的。假设在一开始,价格就经历了调整,触发了两个订单,此后,价格开始向相反(主要趋势)的方向移动。在这种情况下,早晚两个以上的订单将朝着正确的方向打开,我们的初始损失将在一段时间后变成利润。

似乎交易系统造成损失的唯一情况是,当价格首先触及一个订单,然后返回并触及另一个订单,然后再次改变方向并触及另一个订单,并反复改变其方向,触及越来越远的订单。但这种价格行为在实际情况下是可能的吗?

EA 模板

我们将从模板开始开发EA,这将使我们能够立即看到将涉及哪些标准MQL函数。

#property copyright "Klymenko Roman (needtome@icloud.com)" #property link      "https://www.mql5.com/en/users/needtome" #property version   "1.00" #property strict //+------------------------------------------------------------------+ //| EA 交易初始化函数                                                                                 | //+------------------------------------------------------------------+ int OnInit()
  { //---    //---    return(INIT_SUCCEEDED);
  } //+------------------------------------------------------------------+ //| EA 交易去初始化函数                                                                                | //+------------------------------------------------------------------+ void OnDeinit(const int reason)
  {
  } //+------------------------------------------------------------------+ //| EA交易分时函数                                                                     | //+------------------------------------------------------------------+ void OnTick()
  {
  } void OnChartEvent(const int id,         // 事件 ID                      const long& lparam,   // 长整型的事件参数                    const double& dparam, // 双精度浮点型的事件参数                    const string& sparam) // 字符串类型的事件参数     {
   }

它与使用 MQL5 向导创建 EA 时生成的标准模板的唯一区别是 #property strict 这一行,我们添加它以便 EA 也在 MQL4 中工作。

我们需要 OnChartEvent() 函数来响应单击按钮,接下来,我们将实现Close All(全部关闭)按钮,以便在达到所需利润或只想停止 EA 时手动关闭所有交易品种仓位和订单。

开启仓位函数

可能任何EA最重要的功能都是下订单的能力,第一个问题就在这里等着我们。在 MQL5 和 MQL4 中,订单的设置方式非常不同。为了以某种方式统一这个功能,我们必须开发一个自定义函数来下订单。

每个订单都有自己的类型:买入订单、卖出订单、限价买入或卖出订单。在下订单时设置此类型的变量在MQL5和MQL4中也不同。

在 MQL4 中,订单类型由 int 类型变量指定,而在 MQL5 中,则使用ENUM_ORDER_TYPE枚举,在 MQL4 中没有这样的枚举。因此,为了组合这两种方法,我们应该创建一个自定义枚举来设置订单类型。因此,我们将来要创建的函数将不依赖于MQL版本:

enum TypeOfPos{
   MY_BUY,
   MY_SELL,
   MY_BUYSTOP,
   MY_BUYLIMIT,
   MY_SELLSTOP,
   MY_SELLLIMIT,
}; 

现在我们可以创建一个自定义函数来下订单了,让我们称它为 pdxSendOrder()。我们将传递下订单所需的所有信息:订单类型、未平仓价格、止损(未设定为0)、获利(未设定为0)、成交量、未平仓订单(如果在MQL5中应修改未平仓)、注释和交易品种(如果您需要为不是当前打开的交易品种开启订单):

// 发送订单的函数 bool pdxSendOrder(TypeOfPos mytype, double price, double sl, double tp, double volume, ulong position=0, string comment="", string sym=""){
   // 检查传入的数值    if( !StringLen(sym) ){
      sym=_Symbol;
   }
   int curDigits=(int) SymbolInfoInteger(sym, SYMBOL_DIGITS);
   if(sl>0){
      sl=NormalizeDouble(sl,curDigits);
   }
   if(tp>0){
      tp=NormalizeDouble(tp,curDigits);
   }
   if(price>0){
      price=NormalizeDouble(price,curDigits);
   }
   
   #ifdef __MQL5__       ENUM_TRADE_REQUEST_ACTIONS action=TRADE_ACTION_DEAL;
      ENUM_ORDER_TYPE type=ORDER_TYPE_BUY;
      switch(mytype){
         case MY_BUY:
            action=TRADE_ACTION_DEAL;
            type=ORDER_TYPE_BUY;
            break;
         case MY_BUYSTOP:
            action=TRADE_ACTION_PENDING;
            type=ORDER_TYPE_BUY_STOP;
            break;
         case MY_BUYLIMIT:
            action=TRADE_ACTION_PENDING;
            type=ORDER_TYPE_BUY_LIMIT;
            break;
         case MY_SELL:
            action=TRADE_ACTION_DEAL;
            type=ORDER_TYPE_SELL;
            break;
         case MY_SELLSTOP:
            action=TRADE_ACTION_PENDING;
            type=ORDER_TYPE_SELL_STOP;
            break;
         case MY_SELLLIMIT:
            action=TRADE_ACTION_PENDING;
            type=ORDER_TYPE_SELL_LIMIT;
            break;
      }
      
      MqlTradeRequest mrequest;
      MqlTradeResult mresult;
      ZeroMemory(mrequest);
      
      mrequest.action = action;
      mrequest.sl = sl;
      mrequest.tp = tp;
      mrequest.symbol = sym;
      if(position>0){
         mrequest.position = position;
      }
      if(StringLen(comment)){
         mrequest.comment=comment;
      }
      if(action!=TRADE_ACTION_SLTP){
         if(price>0){
            mrequest.price = price;
         }
         if(volume>0){
            mrequest.volume = volume;
         }
         mrequest.type = type;
         mrequest.magic = EA_Magic;
         switch(useORDER_FILLING_RETURN){
            case FOK:
               mrequest.type_filling = ORDER_FILLING_FOK;
               break;
            case RETURN:
               mrequest.type_filling = ORDER_FILLING_RETURN;
               break;
            case IOC:
               mrequest.type_filling = ORDER_FILLING_IOC;
               break;
         }
         mrequest.deviation=100;
      }
      if(OrderSend(mrequest,mresult)){
         if(mresult.retcode==10009 || mresult.retcode==10008){
            return true;
         }else{
            msgErr(GetLastError(), mresult.retcode);
         }
      }
   #else 
      int type=OP_BUY;
      switch(mytype){
         case MY_BUY:
            type=OP_BUY;
            break;
         case MY_BUYSTOP:
            type=OP_BUYSTOP;
            break;
         case MY_BUYLIMIT:
            type=OP_BUYLIMIT;
            break;
         case MY_SELL:
            type=OP_SELL;
            break;
         case MY_SELLSTOP:
            type=OP_SELLSTOP;
            break;
         case MY_SELLLIMIT:
            type=OP_SELLLIMIT;
            break;
      }
      if(OrderSend(sym, type, volume, price, 100, sl, tp, comment, EA_Magic, 0)<0){
         msgErr(GetLastError());
      }else{
         return true;
      }
   
   #endif 
   return false;
}

首先,检查传递给函数的数值并规范化价格。

输入参数. 之后,使用条件编译来定义当前的MQL版本,并根据其规则设置顺序。多出的 useORDER_FILLING_RETURN 输入参数是为 MQL5 使用的,在它的帮助下,我们根据代理支持的模式配置订单执行模式。因为 useORDER_FILLING_RETURN 输入参数只对 MQL5 EA 才是必须的,再次使用条件编译来添加它:

#ifdef __MQL5__    enum TypeOfFilling //执行模式      {
      FOK,//ORDER_FILLING_FOK       RETURN,// ORDER_FILLING_RETURN       IOC,//ORDER_FILLING_IOC      }; 
   input TypeOfFilling  useORDER_FILLING_RETURN=FOK; //执行模式 #endif  

此外,下订单时,还使用包含EA幻数的EA_Magic输入参数。

如果在EA设置中未设置此参数,则 MT4搭建EA平台 已启动的交易品种上的任何仓位都被视为属于EA,这样,EA就完全控制了它们。

如果设置了幻数,则EA只考虑在其工作中具有此幻数的仓位。

显示错误. 如果订单成功设置, 就返回 true。否则,会将适当的错误代码传递给 msgErr() 的函数,以便进一步分析并显示可理解的错误消息。这个函数显示包含详细错误描述的本地化消息,在这里提供完整的代码是没有意义的,所以我只展示其中的一部分:

void msgErr(int err, int retcode=0){
   string curErr="";
   switch(err){
      case 1:
         curErr=langs.err1;
         break; //      case N: //         curErr=langs.errN; //         break;       default:
         curErr=langs.err0+": "+(string) err;
   }
   if(retcode>0){
      curErr+=" ";
      switch(retcode){
         case 10004:
            curErr+=langs.retcode10004;
            break; //         case N: //            curErr+=langs.retcodeN; //            break;       }
   }
   
   Alert(curErr);
}

我们将在下一节详细讨论本地化。

  • QQ在线客服