如何在 MQL 4 中开发可靠安全的交易机器人

Shashev Sergei | 26 二月, 2016

简介

在创建任何重要的程序解决方案的过程中,开发人员都会面临这一事实:他的程序可能包含所有可能及不可能的错误。这些错误将给开发阶段带来很多麻烦,从而导致解决方案的不稳定性,如果是交易机器人,可能会显示你的保证金为负值。让我们来分析一下最常见的错误、造成这些错误的原因以及检测和用程序处理这些错误的方法。在开发和使用客户终端 MetaTrader 4 的 Expert Advisor 的过程中,可能会出现以下错误:

  1. 语法错误 – 程序员可在编译阶段发现并轻松修复此类错误;
  2. 逻辑错误 – 此类错误无法通过编译器检测出来。示例包括:变量名称混乱、函数调用错误、各种类型的数据操作错误等等;
  3. 算法错误 – 当出现括号放置不正确、分支语句混乱等情况时会出现此类错误;
  4. 严重错误 – 此类错误是不大可能的错误,你应花些精力注意避免此类错误。尽管如此,当你使用 dll 时,往往会出现此类错误。
  5. 交易错误 – 当你处理订单时可能会出现此类错误。此类错误是交易机器人的脆弱点。
首先,我们建议你研究有关执行错误的文档。一旦执行了这个程序,你可以在稍后节约很多时间。这里将介绍由交易操作引发的错误。

语法错误

此类错误是由运算符、变量和不同函数调用的类型错误而引发的。在编译期间,将检查程序的代码,同时将在 MetaEditor 的“工具”窗口中显示所有语法错误。事实上,程序员将检测出几乎所有错误并可修复这些错误。

例外情况是括号混乱,当在编译阶段检测出左/右括号放置错误,但以错误的方式显示此错误的位置。然后,你需要再次核对代码以目测找到此错误,但不幸的是,可能会失败。第二种方式是使用注释连续关闭代码块。在这种情况下,如果错误在注释新的块之后消失了,很明显,它被放置在此注释的块中了。这种方式可大幅缩小搜索范围,有助于快速找到括号的错误位置。


逻辑错误、算法错误和严重错误

此类最常见的错误是变量名称和类型混乱以及 Expert Advisor 的分支中的算法错误。例如,让我们来分析一下以下代码:

bool Some = false;
 
void check()
  {
    // Much code
    Some = true;
  }
// Very much code
int start()
  {
    bool Some = false;
    //
    if(Some)   
      {
        //sending the order
      }
   return(0);
  }

我们现在可以看到什么?逻辑变量“Some”不小心被设置得过低,此变量在整个程序中很常见,并且是一个用于建仓的重要指标。它会导致建单错误,从而导致发生损失。你可以为变量设置很多名称!但是由于某种原因,这些名称在大型程序中可能会不小心重复,这会导致上述问题。

当变量混淆不清或某种类型的表达式被分派给另一种类型的表达式时,会出现此类错误。例如,在下行中:

int profit = NormalizeDouble(SomeValue*point*2 / 3, digit);
我们正在试图将“double”类型的值表达式分派给“int”类型的变量,这会导致零值。而且我们正在计算获利位!此类错误会导致交易错误。


Expert Advisor 的分支中的算法错误意味着,未按照算法放置括号,或者“if”运算符以及“else”运算符的范围出错。结果我们会令 Expert Advisor 无法按照技术要求工作。

一些错误可能会非常不易察觉,因此你可能要花几个小时的时间来思考代码以找出这些错误。遗憾的是,与 C++ 系列语言的环境不同,无法跟踪 MetaEditor 中的变量值。因此,唯一的方法是通过 Print() 函数输出消息来跟踪错误。

函数 GetLastError() 将返回错误的代码。在检查程序的每个可能容易出错的位置之后,建议检查最后一个值。使用错误的代码,你可以在文档中轻松地找到其描述,对于某些错误,你甚至可以找到处理方法。

应该说,上述错误很可能在使用模拟帐户之前的测试阶段就会被检测出来,因此不大可能会因为这些错误而导致损失。

严重错误的一个主要特征是,当出现严重错误时,将立即停止执行程序。尽管如此,此类错误的代码在预定义变量“last_error”中保持不变。这样,我们可以通过调用函数 GetLastError() 了解此类错误的代码。


交易错误

此类错误往往会导致 Expert Advisor 在模拟帐户甚至真实帐户上出现损失和不可操作性。当你处理发送和修改订单时,换言之,当与交易服务器互动时,将发生此类错误。
类似于以下处理的简单处理:

ticket = OrderSend(Symbol(), OP_SELL, LotsOptimized(), Bid, 3,
         Bid + StopLoss*Point, Bid - TakeProfit*Point, 0, MAGICMA, 
         0, Red);
if(ticket > 0) 
  {
    err = GetLastError();
    Print("While opening the order the error #  occured", err);
  }

没用。我们确保,没有向服务器发送订单,也不了解错误的代码。那会怎样?我们错过了进入市场的重要入口,当然,如果我们有一个可盈利的 Expert Advisor。

具有无限循环的变量:

while (true)
  {
    ticket = OrderSend(Symbol(), OP_SELL, Lots, Bid, slippage,
             Bid + StopLoss*Point, Bid - TakeProfit*Point, 0, 
             MAGICMA, 0, Red);
    if(ticket > 0) 
      {
        err = GetLastError();
        Print("While opening the order the error #  occured", err);
        break;      
      }
    Sleep(1000);
    RefleshRates();
  }

有点帮助。订单将更可能到达服务器。但是,我们可能会面临一些问题:

  1. 经纪人不喜欢频繁请求;
  2. 错误可能致命,在这种情况下,请求将无法到达服务器;
  3. Expert Advisor 长时间未作出响应;
  4. 服务器可能根本未收到交易请求,可能是周末、假期、正在进行维护工作等。

几乎每个错误都具有独特性,需要单独进行处理。因此,让我们来讨论具有 Switch 运算符的变量并或多或少地独立建立每个错误。标准错误 #146 -“Trade flow is busy”,使用在 TradeContext.mqh 库中实现的信号量进行处理。你可以在本文中找到此库及其详细描述。

//The library for differentiation of work with the trading flow
//written by komposter
#include <TradeContext.mqh>
 
//parameters for the signals
extern double MACDOpenLevel=3;
extern double MACDCloseLevel=2;
extern double MATrendPeriod=26;
 
// maximum acceptable slippage
int       slippage = 3;
//total number of transactions
int deals = 0;
//time for the pause after transaction
int TimeForSleep = 10;
//period of request
int time_for_action = 1;
//number of tries of opening/closing the position
int count = 5;
//indicator of operability of the EA
bool Trade = true;
//indicator of availability of funds for opening the position
bool NoOpen = false;
//+------------------------------------------------------------------+
//| Do not ask the server for quotes on weekends                     |
//+------------------------------------------------------------------+
bool ServerWork()
  {     
   if(DayOfWeek() == 0 || DayOfWeek() == 6)
       return(false);
   return(true);      
  }
//+------------------------------------------------------------------+
//| Generation of magik                                              |
//+------------------------------------------------------------------+ 
int GenericMagik()
  {
   return(deals);
  }
//+------------------------------------------------------------------+   
//| Closing of transactions                                          |
//+------------------------------------------------------------------+
bool CloseOrder(int magik)
  {
   int ticket,i;
   double Price_close;
   int err;
   int N;
//Function tries to shut the server at count attempts, if it fails,
//it gives an error message to the logfile
   while(N < count)
     {          
       for(i = OrdersTotal() - 1; i >= 0; i--) 
         {
           if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
               if(OrderSymbol() == Symbol()) 
                   if(OrderMagicNumber() == magik)
                     {
                       if(OrderType() == OP_BUY)        
                           Price_close = NormalizeDouble(Bid, Digits);
                       if(OrderType() == OP_SELL)        
                           Price_close = NormalizeDouble(Ask, Digits);
                       if(OrderClose(OrderTicket(), OrderLots(),
                          Price_close,slippage))
                         { 
                           //reduce the number of transactions for the EA
                           deals--;
                           //the piece of the margin became available - you can open again
                           NoOpen = false;                           
                           return(true);
                         }
                         //we have reached this place, it means that the order has not been sent
                       N++;
                       //processing of possible errors
                       err = ErrorBlock();
                       //if the error is seriuos
                       if(err > 1)
                         {
                           Print("Manual closing of the order #  needed",
                                 OrderTicket());
                           return(false);
                         }
                     }                                          
         }
        // taking a pause of 5 seconds and trying to close the transaction again
       Sleep(5000);
       RefreshRates();
     }
    //if we have reached this place, the transaction was not closed at count attempts 
   Print("Manual closing of the order #  needed",OrderTicket());
   return(false);
  }
//+-----------------------------------------------------------------------------+
//|Transaction for act 1-buy, 2-sell, the second parameter - the number of lots |
//+-----------------------------------------------------------------------------+ 
int Deal(int act, double Lot)
  {
   int N = 0;
   int ticket;
   int err;
   double Price_open;
   double Lots;
   int cmd;
   int magik;
   magik = GenericMagik();
   Lots = NormalizeDouble(Lot,1);
   if(act == 1)
     {
       Price_open = NormalizeDouble(Ask, Digits);
       cmd = OP_BUY;
     }
   if(act == 2)
     {
       Price_open = NormalizeDouble(Bid, Digits);
       cmd = OP_SELL;
     }
   //checking the margin for opening the position
   AccountFreeMarginCheck(Symbol(), cmd,Lots);
   err = GetLastError();
   if(err>0)
     {
       Print("No money for new position");
       NoOpen = true;
       return(0);
     }      
//Sending the order                  
   ticket = OrderSend(Symbol(), cmd, Lots, Price_open, slippage, 
                      0, 0, 0, magik);
   if(ticket > 0)
     {
       deals++;
       return(ticket);
     }
//If the order has not been sent, we will try to open it 5 times again      
   else
     {
       while(N < count)
         {
           N++;
           err = ErrorBlock();
           if(err == 1)
             {
               Sleep(5000);
               RefreshRates();
               if(act == 1)
                   Price_open = NormalizeDouble(Ask, Digits);
               if(act == 2)
                   Price_open = NormalizeDouble(Bid, Digits);              
               ticket = OrderSend(Symbol(), cmd, Lots, Price_open,
                                  slippage, 0, 0, 0, magik);
               if(ticket > 0)
                 {
                   deals++;
                   return(ticket);
                 }
             }
           // we have got a serious error  
           if(err > 1)            
               return(0);               
         }                                                       
     }    
   return(0);
  }
//+------------------------------------------------------------------+
//| 0-no error, 1-need to wait and refresh,                          |
//| 2-transaction rejected, 3-fatal error                            |
//+------------------------------------------------------------------+
//Block of the error control 
int ErrorBlock()
  {
   int err = GetLastError();
   switch(err)
     {
       case 0: return(0);
       case 2:
         {
           Print("System failure. Reboot the computer/check the server");
           Trade = false;
           return(3);  
         }
       case 3:
         {
           Print("Error of the logic of the EA");
           Trade = false;
           return(3);   
         }
       case 4:
         {
           Print("Trading server is busy. Wait for 2 minutes.");
           Sleep(120000);
           return(2);   
         }
       case 6:
         { 
           bool connect = false;
           int iteration = 0;
           Print("Disconnect ");
           while((!connect) || (iteration > 60))
             {
               Sleep(10000);
               Print("Connection not restored", iteration*10,
                     "  seconds passed");
               connect = IsConnected();
               if(connect)
                 {
                   Print("Connection restored");
                   return(2);
                 }
               iteration++;
             }
           Trade = false; 
           Print("Connection problems");
           return(3);
         }
       case 8:
         {
           Print("Frequent requests");
           Trade = false; 
           return(3);
         }
       case 64:
         {
           Print("Account is blocked!");
           Trade = false; 
           return(3);            
         }
       case 65:
         {
           Print("Wrong account number???");
           Trade = false; 
           return(3);            
         }
       case 128:
         {
           Print("Waiting of transaction timed out");
           return(2);
         }
       case 129:
         {
           Print("Wrong price");
           return(1);            
         }
       case 130:
         {
           Print("Wrong stop");
           return(1);
         }
       case 131:
         {
           Print("Wrong calculation of trade volume");
           Trade = false;            
           return(3);
         }
       case 132:
         {
           Print("Market closed");
           Trade = false; 
           return(2);
         }
       case 134:
         {
           Print("Lack of margin for performing operation");
           Trade = false;             
           return(2);
         }
       case 135:
         {
           Print("Prices changed");
           return (1);
         }
       case 136:
         {
           Print("No price!");
           return(2);
         }
       case 138:
         {
           Print("Requote again!");
           return(1);
         }
       case 139:
         {
           Print("The order is in process. Program glitch");
           return(2);
         }
       case 141:
         {
           Print("Too many requests");
           Trade = false; 
           return(2);            
         }
       case 148:
         {
           Print("Transaction volume too large");
           Trade = false; 
           return(2);            
         }                                          
     }
   return (0);
  }
//+------------------------------------------------------------------+
//| generation of signals for opening/closing position on Macd       |
//+------------------------------------------------------------------+
int GetAction(int &action, double &lot, int &magik)
   {
   double MacdCurrent, MacdPrevious, SignalCurrent;
   double SignalPrevious, MaCurrent, MaPrevious;
   int cnt,total;
   
   MacdCurrent=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_MAIN,0);
   MacdPrevious=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_MAIN,1);
   SignalCurrent=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_SIGNAL,0);
   SignalPrevious=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_SIGNAL,1);
   MaCurrent=iMA(NULL,0,MATrendPeriod,0,MODE_EMA,PRICE_CLOSE,0);
   MaPrevious=iMA(NULL,0,MATrendPeriod,0,MODE_EMA,PRICE_CLOSE,1);
   
  if(MacdCurrent<0 && MacdCurrent>SignalCurrent && MacdPrevious<SignalPrevious &&
         MathAbs(MacdCurrent)>(MACDOpenLevel*Point) && MaCurrent>MaPrevious)
      {
         action=1;
         lot=1;
         return (0);
      }
  if(MacdCurrent>0 && MacdCurrent<SignalCurrent && MacdPrevious>SignalPrevious && 
         MacdCurrent>(MACDOpenLevel*Point) && MaCurrent<MaPrevious)
      {
         action=2;
         lot=1;
         return (0);               
      }
   total=OrdersTotal();
   for(cnt=0;cnt<total;cnt++)
     {
      OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES);
      if(OrderType()<=OP_SELL &&   // check for opened position 
         OrderSymbol()==Symbol())  // check for symbol
        {
         if(OrderType()==OP_BUY)   // long position is opened
           {
            // should it be closed?
            if(MacdCurrent>0 && MacdCurrent<SignalCurrent && MacdPrevious>SignalPrevious &&
               MacdCurrent>(MACDCloseLevel*Point))
                {
                 action=3;
                 magik=OrderMagicNumber();
                 return(0); // exit
                }
           }
         else // go to short position
           {
            // should it be closed?
            if(MacdCurrent<0 && MacdCurrent>SignalCurrent &&
               MacdPrevious<SignalPrevious && MathAbs(MacdCurrent)>(MACDCloseLevel*Point))
              {
               action=3;
               magik=OrderMagicNumber();
               return(0); 
              }
           }
        }
     }
   }
//+------------------------------------------------------------------+
//| The EA initialization function                                   |
//+------------------------------------------------------------------+
int init()
  { 
    if(!IsTradeAllowed())
      {
        Print("Trade not allowed!");
        return(0);     
      }
  }
//+------------------------------------------------------------------+
//| The EA deinitialization function                                 |
//+------------------------------------------------------------------+
int deinit()
  {
//Closing all orders
   for(int k = OrdersTotal() - 1; k >= 0 ; k--)
       if(OrderSymbol() == Symbol()) 
         {
           if(OrderType() == OP_BUY)
              OrderClose(OrderTicket(), OrderLots(), 
                         NormalizeDouble(Bid,Digits), 10);               
           if(OrderType() == OP_SELL)
               OrderClose(OrderTicket(), OrderLots(),
                          NormalizeDouble(Ask, Digits),10);                 
         }
  } 
//+------------------------------------------------------------------+
//| The EA start function                                            |
//+------------------------------------------------------------------+
int start()
  {
   int action =0;
   double lot = 1;
   int magik = 0;     
   while(Trade)
     {
       Sleep(time_for_action*1000);      
       RefreshRates();
       /*Logic of the EA where the we calculate the action, position limit and magik for closing the order
       action 1-buy, 2-sell, 3-close
       for example, take the EA on Macd*/
       GetAction(action,lot,magik);
       if(ServerWork())
         {
           if(((action == 1) || (action == 2)) && (!NoOpen))
             {                                        
               if(TradeIsBusy() < 0) 
                   return(-1); 
               Deal(action, lot);
               Sleep(TimeForSleep*1000);                                
               TradeIsNotBusy();
             }
           if(action == 3)
             {
               if(TradeIsBusy() < 0) 
                 { 
                   return(-1); 
                   if(!CloseOrder(magik))
                       Print("MANUAL CLOSE OF TRANSATION NEEDED");
                   Sleep(TimeForSleep*1000);   
                   TradeIsNotBusy();
                 } 
             }
         }
       else
         {
            Print("Weekends");
            if(TradeIsBusy() < 0) 
                  return(-1); 
            Sleep(1000*3600*48);
            TradeIsNotBusy();
         }
       action = 0;
       lot = 0;
       magik = 0;
     }
   Print("Critical error occured and the work of the EA terminated");  
   return(0);
  }
//+------------------------------------------------------------------+

此版交易机器人以无限循环形式运行。当创建了超短线多货币的Expert Advisor 后,将需要使用到它。EA 的操作算法如下:

  1. 从分析块 GetAction() 获取信号;
  2. 在 Deal() и CloseOrder() 中进行必要的交易;
  3. 如果没有重大失败,在短时暂停 time_for_action 之后回到第 1 点。

在从分析块获取信号(买入、卖出、关闭)之后, Expert Advisor 将限制交易流(阅读文章)并尝试进行交易,之后将暂停数秒,然后为其他智能 EA 释放交易流。Expert Advisor 尝试发送订单的次数不超过“count”次数。应该足以将订单传递到不稳定的市场,可在其中获取重新报价。如果在发送订单时发生了严重错误,Expert Advisor 将停止工作。如果发生任何问题,将在“Expert Advisor”文件夹中出现错误消息。如果错误不严重,Expert Advisor 将继续工作。

将按照以下方案在 ErrorBlock() 程序中处理错误:此程序获取错误的代码并提供处理错误的简短算法。对于大多数错误,仅仅是日志中出现一则消息。如果是严重错误,则交易指标“Trade”和 “NoOpen”将发生变化。如果是连接失败,这种情况的处理稍微复杂些。机器人将尝试按照预定义的周期序列连接服务器六十次。如果服务器仍未连接上,很可能是服务器有一些严重的问题,你应停止交易一段时间。根据错误对交易的影响,处理算法将返回不同的含义:
  • 0 - 没有错误;
  • 1 - 错误与市场的波动性相关联,你可以尝试再次发送订单;
  • 2 - 在发送此订单时发生严重错误,停止建仓一段时间;
  • 3 - EA 发生严重故障,连接失败 - 停止交易,直至澄清情况。

总结

当你没有特别重视算法编码时,可能会发生语法、算法和逻辑错误。可通过彻底检查和验证日志中的变量的值,修复这些错误。可在 Expert Advisor 的编译和测试阶段检测出这些错误。此类错误不会存在很长时间,通常在使用模拟帐户之前就会修复好这些错误。

在将订单发送给服务器时可能会发生交易错误。它们与实际交易相关,你可以在其中找到重新报价、滑点以及经销商应对超短线交易和设备故障。此类错误无法预测,但能够并且应当处理此类错误。你应根据 Expert Advisor 的逻辑、交易的频率和订单的修改每周分别处理一次此类错误。

在 Expert Advisor 操作期间发生的错误需要进行处理。这并非是一项微不足道的工作。它而取决于 EA 及其功能的复杂性。在本文中,你可以找到执行此任务的 Expert Advisor 示例模式。要创建更有保障和更安全的交易系统,需要花费很多的时间。但是,开发无故障的自动交易系统所花费的时间会收到多次保障保证金安全的回报,让你无后顾之忧。