Ускорить эксперта в 12 раз? Легко!

 
По обсуждению в ветке http://www.metatrader4.com/ru/forum/7969/ для оптимизации был предложен код, сгенерированный программой Gordago. Так как я сам лично просил предоставить код для алгоритмической оптимизации, то я сам и разберу его.

Вот отформатированный оригинальный код (файл TestOriginal.mq4):
//+------------------------------------------------------------------+
//|                           Copyright 2005, Gordago Software Corp. |
//|                                                      version 2.0 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2005, Gordago Software Corp."
 
#define MIN_STOPLOSS_POINT   10
#define MIN_TAKEPROFIT_POINT 10
#define MAGIC                180865
 
extern string sNameExpert="Generate from Gordago";
extern int    nAccount =0;
extern double dBuyStopLossPoint=20;
extern double dSellStopLossPoint=20;
extern double dBuyTakeProfitPoint=50;
extern double dSellTakeProfitPoint=50;
extern double dBuyTrailingStopPoint=15;
extern double dSellTrailingStopPoint=15;
extern double dLots=0.5;
extern int    nSlippage=4;
extern bool   lFlagUseHourTrade=False;
extern int    nFromHourTrade=0;
extern int    nToHourTrade=23;
extern bool   lFlagUseSound=True;
extern string sSoundFileName="alert.wav";
extern color  colorOpenBuy=Blue;
extern color  colorCloseBuy=Aqua;
extern color  colorOpenSell=Red;
extern color  colorCloseSell=Aqua;
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void deinit() 
  {
   Comment("");
  }
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
int start()
  {
//----
   if(lFlagUseHourTrade)
     {
      if(!(Hour()>=nFromHourTrade && Hour()<=nToHourTrade)) 
        {
         Comment("Time for trade has not come else!");
         return(0);
        }
     }
//----
   if(Bars < 100)
     {
      Print("bars less than 100");
      return(0);
     }
//----
   if(nAccount > 0 && nAccount!=AccountNumber())
     {
      Comment("Trade on account :"+AccountNumber()+" FORBIDDEN!");
      return(0);
     }
//----
   if((dBuyStopLossPoint > 0 && dBuyStopLossPoint < MIN_STOPLOSS_POINT) ||
      (dSellStopLossPoint > 0 && dSellStopLossPoint < MIN_STOPLOSS_POINT))
     {
      Print("StopLoss less than " + MIN_STOPLOSS_POINT);
      return(0);
     }
   if((dBuyTakeProfitPoint > 0 && dBuyTakeProfitPoint < MIN_TAKEPROFIT_POINT) ||
      (dSellTakeProfitPoint > 0 && dSellTakeProfitPoint < MIN_TAKEPROFIT_POINT))
     {
      Print("TakeProfit less than " + MIN_TAKEPROFIT_POINT);
      return(0);
     }
//----
   double diMA0=iMA(NULL,1,6,0,MODE_EMA,PRICE_CLOSE,0);
   double diClose1=iClose(NULL,1,0);
   double diMA2=iMA(NULL,5,13,0,MODE_EMA,PRICE_CLOSE,0);
   double diClose3=iClose(NULL,5,0);
   double diMA4=iMA(NULL,15,21,0,MODE_EMA,PRICE_CLOSE,0);
   double diClose5=iClose(NULL,15,0);
   double diMA6=iMA(NULL,30,32,0,MODE_EMA,PRICE_CLOSE,0);
   double diClose7=iClose(NULL,30,0);
   double diMA8=iMA(NULL,1,6,0,MODE_EMA,PRICE_CLOSE,0);
   double diClose9=iClose(NULL,1,0);
   double diMA10=iMA(NULL,5,13,0,MODE_EMA,PRICE_CLOSE,0);
   double diClose11=iClose(NULL,5,0);
   double diMA12=iMA(NULL,15,21,0,MODE_EMA,PRICE_CLOSE,0);
   double diClose13=iClose(NULL,15,0);
   double diMA14=iMA(NULL,30,32,0,MODE_EMA,PRICE_CLOSE,0);
   double diClose15=iClose(NULL,30,0);
//----
   if(AccountFreeMargin() < (1000*dLots))
     {
      Print("We have no money. Free Margin = " + AccountFreeMargin());
      return(0);
     }
//----
   bool lFlagBuyOpen=false, lFlagSellOpen=false, lFlagBuyClose=false, lFlagSellClose=false;
 
   lFlagBuyOpen=(diMA0<diClose1 && diMA2<diClose3 && diMA4<diClose5 && diMA6<diClose7);
   lFlagSellOpen=(diMA8>diClose9 && diMA10>diClose11 && diMA12>diClose13 && diMA14>diClose15);
   lFlagBuyClose=False;
   lFlagSellClose=False;
//----
   if(!ExistPositions())
     {
      if(lFlagBuyOpen)  { OpenBuy();  return(0); }
      if(lFlagSellOpen) { OpenSell(); return(0); }
     }
//----
   if(ExistPositions())
     {
      if(OrderType()==OP_BUY)
        {
         if(lFlagBuyClose)
           {
            bool flagCloseBuy=OrderClose(OrderTicket(), OrderLots(), Bid, nSlippage, colorCloseBuy);
            if(flagCloseBuy && lFlagUseSound) PlaySound(sSoundFileName);
            return(0);
           }
        }
      if(OrderType()==OP_SELL)
        {
         if(lFlagSellClose)
           {
            bool flagCloseSell=OrderClose(OrderTicket(), OrderLots(), Ask, nSlippage, colorCloseSell);
            if(flagCloseSell && lFlagUseSound) PlaySound(sSoundFileName);
            return(0);
           }
        }
     }
//----
   if(dBuyTrailingStopPoint > 0 || dSellTrailingStopPoint > 0)
     {
      for(int i=0; i<OrdersTotal(); i++) 
        {
         if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) 
           {
            bool lMagic=true;
            if (MAGIC > 0 && OrderMagicNumber()!=MAGIC) lMagic=false;
 
            if(OrderSymbol()==Symbol() && lMagic) 
              {
               if(OrderType()==OP_BUY && dBuyTrailingStopPoint > 0) 
                 {
                  if(Bid-OrderOpenPrice() > dBuyTrailingStopPoint*Point) 
                    {
                     if(OrderStopLoss()<Bid-dBuyTrailingStopPoint*Point) 
                       ModifyStopLoss(Bid-dBuyTrailingStopPoint*Point);
                    }
                 }
               if(OrderType()==OP_SELL) 
                 {
                  if(OrderOpenPrice()-Ask>dSellTrailingStopPoint*Point) 
                    {
                     if(OrderStopLoss()>Ask+dSellTrailingStopPoint*Point || OrderStopLoss()==0)
                       ModifyStopLoss(Ask+dSellTrailingStopPoint*Point);
                    }
                 }
              }
           }
        }
     }
//----
   return(0);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool ExistPositions() 
  {
//----
   for(int i=0; i<OrdersTotal(); i++) 
     {
      if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) 
        {
         bool lMagic=true;
         if(MAGIC > 0 && OrderMagicNumber()!=MAGIC) lMagic=false;
         if (OrderSymbol()==Symbol() && lMagic) return(true);
        }
     }
//----
   return(false);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void ModifyStopLoss(double ldStopLoss) 
  {
   bool lFlagModify=OrderModify(OrderTicket(),OrderOpenPrice(),ldStopLoss,OrderTakeProfit(),0,CLR_NONE);
   if(lFlagModify && lFlagUseSound) PlaySound(sSoundFileName);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OpenBuy() 
  {
   double dStopLoss=0, dTakeProfit=0;
//---- выставим стопы
   if(dBuyStopLossPoint>0)   dStopLoss=Bid-dBuyStopLossPoint*Point;
   if(dBuyTakeProfitPoint>0) dTakeProfit=Ask + dBuyTakeProfitPoint * Point;
//---- отошлем заявку
   int numorder=OrderSend(Symbol(), OP_BUY, dLots, Ask, nSlippage, dStopLoss, 
                          dTakeProfit, sNameExpert, MAGIC, 0, colorOpenBuy);
//---- отыграем звуковое подтверждение
   if(numorder > -1 && lFlagUseSound) PlaySound(sSoundFileName);
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void OpenSell() 
  {
   double dStopLoss=0, dTakeProfit=0;
//---- выставим стопы
   if(dSellStopLossPoint>0)   dStopLoss=Ask+dSellStopLossPoint*Point;
   if(dSellTakeProfitPoint>0) dTakeProfit=Bid-dSellTakeProfitPoint*Point;
//---- отошлем заявку
   int numorder=OrderSend(Symbol(),OP_SELL,dLots,Bid,nSlippage,dStopLoss,
                          dTakeProfit,sNameExpert,MAGIC,0,colorOpenSell);
//---- отыграем звуковое подтверждение
   if(numorder > -1 && lFlagUseSound) PlaySound(sSoundFileName);
  }
//+------------------------------------------------------------------+
Как видно по коду, эксперт использует обычные мувинги по четырем таймфреймам (M1, M5, M15, M30) для подтверждения точки входа. Я провел тест на EURUSD M1, использовав 201 билд и данные из History Center с 1999 года (2 838 299 баров М1). Эксперт настолько тормозит, что его практически невозможно использовать. Пришлось тестировать всего 2 дня с 2006.05.01 по 2006.05.03:



Тест прошел за 24 минуты 9 секунд на Athlon 64 X2 Dual Core 4800+, RAM 4Gb. Логи находятся в приложенном файле Original.txt :



К сожалению, этот код глубоко ошибочен и абсолютно неоптимизированный.

Рассмотрим его в детялях:
  • Используется двойной набор мувингов с одинаковыми параметрами - это огромная расточительность по ресурсам!
  • Мало того, эти 4 индикатора бесполезно перерасчитываются на каждом тике, а не только в момент принятия решения об открытии позиции. Даже если первый индикатор не дает сигнала на открытие, то остальные будут все равно прерасчитаны - это самая главная потеря ресурсов.
  • Проверки входных параметров в функции start, а не в init, что приводит к бесполезным многократным проверкам на каждом тике
  • Явная ошибка в коде из-за выставленных lFlagBuyClose=False и lFlagSellClose=False, что приводит к невозможности закрытия позиций по логике эксперта. Позиции закрываются только по Stop Loss или Take Profit. Целый кусок кода по закрытию позиций работает вхолостую!
  • Лишние вычисления из-за отсутствия return в блоке оценки открытия позиций
  • Проверка на наличие маржи производится на каждом тике, а не в момент открытия позиций. Для оценки маржи используется не штатная функция AccountFreeMarginCheck, а некорректное условие AccountFreeMargin() < (1000*dLots)
  • Не нормализуются Stop Loss и Take Profit

В файле TestOptimized.mq4 находится оптимизированный эксперт, который проходит тест за 2 минуты 4 секунды, что в 12 раз быстрее оригинального. Логи в файле optimized.txt

Эту версию эксперта можно еще более ускорить. Если кто хочет, то опубликуйте свои варианты, пожалуйста.
Файлы:
 

Очень сильно тормозят конструкции сравнения с логическим оператором "И"

Например если поменять конструкцию типа
if( оператор1>0 && оператор2<0 ) {.......}

На конструкцию типа
if( оператор1>0 ) {
if( оператор2<0 ) {....... }}

Скорость вырастет в 2-3 раза (визуально видно).

 
Это значит, в MQL4 идёт полная проверка всего выражения, т.е. даже при ложном значении (оператор1>0) проверка пойдёт дальше и на (оператор2<0).
Можно для ускорения и вложенными if-ами проверять.
Причина обращения: