Какие проверки должен пройти торговый робот перед публикацией в Маркете

MetaQuotes | 19 июля, 2016

Для чего нужна проверка перед публикацией в Маркете

Все продукты Маркета перед публикацией проходят обязательную предварительную проверку, так как небольшая ошибка в логике советника или индикатора может привести к убыткам на торговом счете. Именно поэтому нами разработана серия базовых проверок, призванных обеспечить необходимый уровень качества продуктов Маркета.

Если в процессе проверки вашего продукта модераторами Маркета будут выявлены ошибки, то вам нужно будет обязательно их исправить. В этой статье мы расскажем о наиболее частых ошибках, которые допускают разработчики в своих торговых роботах и технических индикаторах. Рекомендуем также почитать следующие статьи:


Как быстро выловить и исправить ошибки в торговом роботе

Встроенный в платформу тестер стратегий позволяет не только проверять на истории торговые системы, но также и выявлять логические и алгоритмические ошибки, допущенные при написании торгового робота. Во время тестирования все сообщения о торговых операциях и выявленных ошибках выводятся в Журнал тестера. Эти сообщения для анализа удобно изучать в специальном Просмотрщике логов, который вызывается командой контекстного меню.


Откройте после тестирования советника Просмотрщик и включите режим "Только ошибки", как показано на рисунке. Если в вашем торговом роботе есть ошибки, вы сразу же их увидите. Если же ошибки не были обнаружены с первого раза, проведите серию тестирований со сменой инструментов/таймфреймов/входных параметров и разными значениями начального депозита. 99% ошибок выявляются этими простыми приемами, и о них мы расскажем вам в этой статье.

Для детального изучения выявленной ошибки используйте в MetaEditor Отладку на истории — так в режиме визуального тестирования вы сможете наблюдать не только графики цен и значения используемых индикаторов, но и проследить на каждом тике значения каждой переменной программы. Это позволит вам отладить вашу торговую стратегию без необходимости тратить недели в режиме реального времени.


Нехватка средств для проведения торговой операции

Перед каждой отправкой торгового приказа необходимо проверять достаточность средств на вашем счете. Нехватка средств для обеспечения будущей открытой позиции или ордера считается грубой ошибкой.

Имейте в виду, что даже для выставления отложенного ордера может потребоваться залоговое обеспечение — маржа.


Рекомендуем тестировать своего торгового робота при заведомо малом размере начального депозита, например, 1 USD или 1 Euro.

Если проверка показала, что средств на совершение торговой операции не хватает, необходимо вместо вызова функции OrderSend() выдать в журнал сообщение об ошибке. Примеры проверки:

MQL5

bool CheckMoneyForTrade(string symb,double lots,ENUM_ORDER_TYPE type)
  {
//--- получим цену открытия
   MqlTick mqltick;
   SymbolInfoTick(symb,mqltick);
   double price=mqltick.ask;
   if(type==ORDER_TYPE_SELL)
      price=mqltick.bid;
//--- значения необходимой и свободной маржи
   double margin,free_margin=AccountInfoDouble(ACCOUNT_MARGIN_FREE);
   //--- вызовем функцию проверки
   if(!OrderCalcMargin(type,symb,lots,price,margin))
     {
      //--- что-то пошло не так, сообщим и вернем false
      Print("Error in ",__FUNCTION__," code=",GetLastError());
      return(false);
     }
   //--- если не хватает средств на проведение операции
   if(margin>free_margin)
     {
      //--- сообщим об ошибке и вернем false
      Print("Not enough money for ",EnumToString(type)," ",lots," ",symb," Error code=",GetLastError());
      return(false);
     }
//--- проверка прошла успешно
   return(true);
  }

MQL4

bool CheckMoneyForTrade(string symb, double lots,int type)
  {
   double free_margin=AccountFreeMarginCheck(symb,type,lots);
   //-- если денег не хватает
   if(free_margin<0)
     {
      string oper=(type==OP_BUY)? "Buy":"Sell";
      Print("Not enough money for ", oper," ",lots, " ", symb, " Error code=",GetLastError());
      return(false);
     }
   //-- проверка прошла успешно
   return(true);
  }

Неправильные объемы в торговых операциях

Перед отправкой торговых приказов необходимо также проверять корректность объемов, указываемых в ордере. Количество лотов, которое собирается указать в ордере советник, нужно проверять перед тем как вызвать функцию OrderSend(). Для финансовых инструментов в Спецификации указываются минимальный и максимальный разрешенные объемы для торговли, а также градация объема. Получить эти значения в MQL5 можно из перечисления ENUM_SYMBOL_INFO_DOUBLE с помощью функции SymbolInfoDouble()

SYMBOL_VOLUME_MIN

Минимальный объем для заключения сделки

SYMBOL_VOLUME_MAX

Максимальный объем для заключения сделки

SYMBOL_VOLUME_STEP

Минимальный шаг изменения объема для заключения сделки

Пример функции для проверки корректности объема

//+------------------------------------------------------------------+
//|  Проверяет объем ордера на корректность                          |
//+------------------------------------------------------------------+
bool CheckVolumeValue(double volume,string &description)
  {
//--- минимально допустимый объем для торговых операций
   double min_volume=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN);
   if(volume<min_volume)
     {
      description=StringFormat("Объем меньше минимально допустимого SYMBOL_VOLUME_MIN=%.2f",min_volume);
      return(false);
     }

//--- максимально допустимый объем для торговых операций
   double max_volume=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MAX);
   if(volume>max_volume)
     {
      description=StringFormat("Объем больше максимально допустимого SYMBOL_VOLUME_MAX=%.2f",max_volume);
      return(false);
     }

//--- получим минимальную градацию объема
   double volume_step=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_STEP);

   int ratio=(int)MathRound(volume/volume_step);
   if(MathAbs(ratio*volume_step-volume)>0.0000001)
     {
      description=StringFormat("Объем не является кратным минимальной градации SYMBOL_VOLUME_STEP=%.2f, ближайший корректный объем %.2f",
                               volume_step,ratio*volume_step);
      return(false);
     }
   description="Корректное значение объема";
   return(true);
  }


Ограничение на количество отложенных ордеров

Бывает также ограничение на количество действующих отложенных ордеров, которые одновременно могут быть размещены на данном счете. Пример функции IsNewOrderAllowed(), которая проверяет, можно ли выставить еще один отложенный ордер. 

//+------------------------------------------------------------------+
//| проверяет - можно ли выставить еще один ордер                    |
//+------------------------------------------------------------------+
bool IsNewOrderAllowed()
  {
//--- получим количество разрешенных на счете отложенных ордеров
   int max_allowed_orders=(int)AccountInfoInteger(ACCOUNT_LIMIT_ORDERS);

//--- если ограничения нет - вернем true, можно отослать ордер
   if(max_allowed_orders==0) return(true);

//--- если дошли до этого места, значит ограничение есть, узнаем, сколько уже ордеров действует
   int orders=OrdersTotal();

//--- вернем результат сравнения
   return(orders<max_allowed_orders);
  }
Функция простая: в переменную max_allowed_orders получаем разрешенное количество отложенных ордеров, и если это значение не равно нулю — сравниваем с текущим количеством ордеров. Правда, этой функцией не учитывается еще одно возможное ограничение — максимально допустимый совокупный объем открытой позиции и отложенных ордеров по конкретному символу.


Ограничение на количество лотов по одному символу

Для того чтобы получить размер объема открытой позиции по заданному символу, необходимо предварительно выбрать позицию с помощью функции PositionSelect(). И только после этого запросить объем выбранной позиции с помощью функции PositionGetDouble(), которая возвращает различные свойства выбранной позиции, имеющие тип double.  Для получения объема позиции по данному символу напишем функцию PositionVolume().

//+------------------------------------------------------------------+
//| Возвращает размер позиции по указанному символу                  |
//+------------------------------------------------------------------+
double PositionVolume(string symbol)
  {
//--- попробуем выбрать позицию по символу
   bool selected=PositionSelect(symbol);
//--- позиция существует
   if(selected)
      //--- вернем объем позиции
      return(PositionGetDouble(POSITION_VOLUME));
   else
     {
      //--- сообщим о неудачной попытке выбрать позицию
      Print(__FUNCTION__," Не удалось выполнить PositionSelect() для символа ",
            symbol," Ошибка ",GetLastError());
      return(-1);
     }
  }

Для счетов с поддержкой хеджинга нужно пройтись по всем позициям на данном инструменте.

Прежде чем сделать торговый запрос на выставление отложенного ордера по символу, необходимо проверить ограничение на совокупный объем для открытый позиций и отложенных ордеров по одному символу — SYMBOL_VOLUME_LIMIT. Если ограничения нет, то объем для отложенного ордера не может превышать установленного максимального объема, который можно получить с помощью функции SymbolInfoDouble().

double max_volume=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_LIMIT);
if(max_volume==0) volume=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MAX);

Но такой подход не учитывает объем в текущих отложенных ордерах по указанному символу. Приведем пример функции, которая вычисляет это значение:

//+------------------------------------------------------------------+
//|  возвращает объем действующих отложенных ордеров по символу      |
//+------------------------------------------------------------------+
double   PendingsVolume(string symbol)
  {
   double volume_on_symbol=0;
   ulong ticket;
//---  получим количество всех действующих ордеров на всех символах
   int all_orders=OrdersTotal();

//--- пройдем в цикле по всем ордерам
   for(int i=0;i<all_orders;i++)
     {
      //--- получим тикет ордера по его позиции в списке
      if(ticket=OrderGetTicket(i))
        {
         //--- если в ордере указан наш символ, прибавим объем этого ордера
         if(symbol==OrderGetString(ORDER_SYMBOL))
            volume_on_symbol+=OrderGetDouble(ORDER_VOLUME_INITIAL);
        }
     }
//--- вернем общий объем текуших отложенных ордеров для указанного символа
   return(volume_on_symbol);
  }

С учетом объема открытой позиции и объема в отложенных ордерах, окончательная проверка будет выглядеть так:

//+------------------------------------------------------------------+
//|  Возвращает максимально допустимый объем для ордера по символу   |
//+------------------------------------------------------------------+
double NewOrderAllowedVolume(string symbol)
  {
   double allowed_volume=0;
//--- получим ограничение на максимальный объем в ордере
   double symbol_max_volume=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MAX);
//--- получим ограничение по символу на объем
   double max_volume=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_LIMIT);

//--- получим объем открытой позиции по символу
   double opened_volume=PositionVolume(symbol);
   if(opened_volume>=0)
     {
      //--- если мы уже исчерпали объем
      if(max_volume-opened_volume<=0)
         return(0);

      //--- объем открытой позиции не превышает max_volume
      double orders_volume_on_symbol=PendingsVolume(symbol);
      allowed_volume=max_volume-opened_volume-orders_volume_on_symbol;
      if(allowed_volume>symbol_max_volume) allowed_volume=symbol_max_volume;
     }
   return(allowed_volume);
  }


Установка уровней TakeProfit и StopLoss в пределах минимального уровня SYMBOL_TRADE_STOPS_LEVEL

Многие советники торгуют с использованием ордеров TakeProfit и StopLoss, уровни которых вычисляются динамически в момент совершения покупки или продажи. Ордер TakeProfit служит для закрытия позиции при движении цены в благоприятном направлении, в то время как ордер StopLoss используется для ограничения убытков при движении цены в неблагоприятном направлении.

Поэтому уровни TakeProfit и StopLoss необходимо сравнивать с текущей ценой, по которой можно совершить операцию противоположного направления:

Покупка происходит по цене Ask
Продажа происходит по цене Bid
TakeProfit >= Bid
StopLoss <= Bid
TakeProfit <= Ask
StopLoss >= Ask


Для финансовых инструментов в настройках символа может быть задан параметр SYMBOL_TRADE_STOPS_LEVEL. Он указывает в пунктах минимальный отступ уровней StopLoss и TakeProfit  от текущей цены закрытия открываемой позиции. Если значение этого свойства равно нулю, значит, минимальный отступ для SL/TP ордеров при покупки и продаже не задан.

В общем случае, проверка уровней TakeProfit и StopLoss с учетом минимальной дистанции SYMBOL_TRADE_STOPS_LEVEL выглядит так:

Покупка происходит по цене Ask
Продажа происходит по цене Bid
TakeProfit - Bid >= SYMBOL_TRADE_STOPS_LEVEL
Bid - StopLoss >= SYMBOL_TRADE_STOPS_LEVEL
Ask - TakeProfit >= SYMBOL_TRADE_STOPS_LEVEL
StopLoss - Ask >= SYMBOL_TRADE_STOPS_LEVEL

Поэтому можно создать функцию проверки CheckStopLoss_Takeprofit(), которая требует, чтобы расстояние от TakeProfit и StopLoss до цены закрытия было не менее  SYMBOL_TRADE_STOPS_LEVEL пунктов:

bool CheckStopLoss_Takeprofit(ENUM_ORDER_TYPE type,double SL,double TP)
  {
//--- получим уровень SYMBOL_TRADE_STOPS_LEVEL
   int stops_level=(int)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL);
   if(stops_level!=0)
     {
      PrintFormat("SYMBOL_TRADE_STOPS_LEVEL=%d: StopLoss и TakeProfit должны быть"+
                  " не ближе %d пунктов от цены закрытия",stops_level,stops_level);
     }
//---
   bool SL_check=false,TP_check=false;
//--- проверяем только два типа ордеров
   switch(type)
     {
      //--- операция покупка
      case  ORDER_TYPE_BUY:
        {
         //--- проверим StopLoss
         SL_check=(Bid-SL>stops_level*_Point);
         if(!SL_check)
            PrintFormat("For order %s StopLoss=%.5f must be less than %.5f"+
                        " (Bid=%.5f - SYMBOL_TRADE_STOPS_LEVEL=%d пунктов)",
                        EnumToString(type),SL,Bid-stops_level*_Point,Bid,stops_level);
         //--- проверим TakeProfit
         TP_check=(TP-Bid>stops_level*_Point);
         if(!TP_check)
            PrintFormat("For order %s TakeProfit=%.5f must be greater than %.5f"+
                        " (Bid=%.5f + SYMBOL_TRADE_STOPS_LEVEL=%d пунктов)",
                        EnumToString(type),TP,Bid+stops_level*_Point,Bid,stops_level);
         //--- вернем результат проверки
         return(SL_check&&TP_check);
        }
      //--- операция продажа
      case  ORDER_TYPE_SELL:
        {
         //--- проверим StopLoss
         SL_check=(SL-Ask>stops_level*_Point);
         if(!SL_check)
            PrintFormat("For order %s StopLoss=%.5f must be greater than %.5f "+
                        " (Ask=%.5f + SYMBOL_TRADE_STOPS_LEVEL=%d пунктов)",
                        EnumToString(type),SL,Ask+stops_level*_Point,Ask,stops_level);
         //--- проверим TakeProfit
         TP_check=(Ask-TP>stops_level*_Point);
         if(!TP_check)
            PrintFormat("For order %s TakeProfit=%.5f must be less than %.5f "+
                        " (Ask=%.5f - SYMBOL_TRADE_STOPS_LEVEL=%d пунктов)",
                        EnumToString(type),TP,Ask-stops_level*_Point,Ask,stops_level);
         //--- вернем результат проверки
         return(TP_check&&SL_check);
        }
      break;
     }
//--- для отложенных ордеров нужна немного другая функция
   return false;
  }

Сама проверка может выглядеть так:

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- получим случайным образом тип операции
   int oper=(int)(GetTickCount()%2); // остаток от деления на два всегда либо 0 либо 1
   switch(oper)
     {
      //--- покупаем
      case  0:
        {
         //--- получим цену открытия и установим заведомо неверные TP/SL
         double price=Ask;
         double SL=NormalizeDouble(Bid+2*_Point,_Digits);
         double TP=NormalizeDouble(Bid-2*_Point,_Digits);
         //--- сделаем проверку
         PrintFormat("Buy at %.5f   SL=%.5f   TP=%.5f  Bid=%.5f",price,SL,TP,Bid);
         if(!CheckStopLoss_Takeprofit(ORDER_TYPE_BUY,SL,TP))
            Print("Уровень StopLoss или TakeProfit указан неверно!");
         //--- все равно попытаемся купить, чтобы увидеть результат выполнения
         Buy(price,SL,TP);
        }
      break;
      //--- продаем
      case  1:
        {
         //--- получим цену открытия и установим заведомо неверные TP/SL
         double price=Bid;
         double SL=NormalizeDouble(Ask-2*_Point,_Digits);
         double TP=NormalizeDouble(Ask+2*_Point,_Digits);
         //--- сделаем проверку
         PrintFormat("Sell at %.5f   SL=%.5f   TP=%.5f  Ask=%.5f",price,SL,TP,Ask);
         if(!CheckStopLoss_Takeprofit(ORDER_TYPE_SELL,SL,TP))
            Print("Уровень StopLoss или TakeProfit указан неверно!");
         //--- все равно попытаемся продать, чтобы увидеть результат выполнения
         Sell(price,SL,TP);
        }
      break;
      //---
     }
  }

Пример функции вы найдете в приложенных скриптах Check_TP_and_SL.mq4 и Check_TP_and_SL.mq5. Пример выполнения:

MQL5
Check_TP_and_SL (EURUSD,H1) Buy at 1.11433   SL=1.11425   TP=1.11421  Bid=1.11423
Check_TP_and_SL (EURUSD,H1) SYMBOL_TRADE_STOPS_LEVEL=30: StopLoss и TakeProfit должны быть не ближе 30 пунктов от цены закрытия
Check_TP_and_SL (EURUSD,H1) Для ордера ORDER_TYPE_BUY StopLoss=1.11425 должен быть меньше 1.11393 (Bid=1.11423 - SYMBOL_TRADE_STOPS_LEVEL=30 пунктов)
Check_TP_and_SL (EURUSD,H1) Для ордера ORDER_TYPE_BUY TakeProfit=1.11421 должен быть больше 1.11453 (Bid=1.11423 + SYMBOL_TRADE_STOPS_LEVEL=30 пунктов)
Check_TP_and_SL (EURUSD,H1) Уровень StopLoss или TakeProfit задан неверно!
Check_TP_and_SL (EURUSD,H1) OrderSend error 4756
Check_TP_and_SL (EURUSD,H1) retcode=10016  deal=0  order=0
MQL4
Check_TP_and_SL EURUSD,H1:  Sell at 1.11430   SL=1.11445   TP=1.11449  Ask=1.11447
Check_TP_and_SL EURUSD,H1:  SYMBOL_TRADE_STOPS_LEVEL=1: StopLoss и TakeProfit должны быть не ближе 1 пунктов от цены закрытия
Check_TP_and_SL EURUSD,H1:  Для ордера ORDER_TYPE_SELL StopLoss=1.11445 должен быть больше 1.11448  (Ask=1.11447 + SYMBOL_TRADE_STOPS_LEVEL=1 пунктов)
Check_TP_and_SL EURUSD,H1:  Для ордера ORDER_TYPE_SELL TakeProfit=1.11449 должен быть меньше 1.11446  (Ask=1.11447 - SYMBOL_TRADE_STOPS_LEVEL=1 пунктов)
Check_TP_and_SL EURUSD,H1:  Уровень StopLoss или TakeProfit задан неверно!
Check_TP_and_SL EURUSD,H1:  OrderSend error 130 

Для моделирования ситуации с неправильными значениями TakeProfit и StopLoss к статье также приложены советники Test_Wrong_TakeProfit_LEVEL.mq5 и Test_Wrong_StopLoss_LEVEL.mq5. Запускать их можно только на демо-счете. Изучите эти примеры, чтобы самостоятельно увидеть, при каких условиях возможно удачное проведение покупки.

Пример выполнения советника Test_Wrong_StopLoss_LEVEL.mq5:

Test_Wrong_StopLoss_LEVEL.mq5
Point=0.00001 Digits=5
SYMBOL_TRADE_EXECUTION=SYMBOL_TRADE_EXECUTION_INSTANT
SYMBOL_TRADE_FREEZE_LEVEL=20: ордер или позицию запрещено модифицировать, если осталось 20 пунктов до цены срабатывания
SYMBOL_TRADE_STOPS_LEVEL=30: StopLoss и TakeProfit должны быть не ближе 30 пунктов от цены закрытия
1. Buy 1.0 EURUSD at 1.11442 SL=1.11404 Bid=1.11430 ( StopLoss-Bid=-26 points ))
CTrade::OrderSend: instant buy 1.00 EURUSD at 1.11442 sl: 1.11404 [invalid stops]
2. Buy 1.0 EURUSD at 1.11442 SL=1.11404 Bid=1.11431 ( StopLoss-Bid=-27 points ))
CTrade::OrderSend: instant buy 1.00 EURUSD at 1.11442 sl: 1.11404 [invalid stops]
3. Buy 1.0 EURUSD at 1.11442 SL=1.11402 Bid=1.11430 ( StopLoss-Bid=-28 points ))
CTrade::OrderSend: instant buy 1.00 EURUSD at 1.11442 sl: 1.11402 [invalid stops]
4. Buy 1.0 EURUSD at 1.11440 SL=1.11399 Bid=1.11428 ( StopLoss-Bid=-29 points ))
CTrade::OrderSend: instant buy 1.00 EURUSD at 1.11440 sl: 1.11399 [invalid stops]
5. Buy 1.0 EURUSD at 1.11439 SL=1.11398 Bid=1.11428 ( StopLoss-Bid=-30 points ))
Buy 1.0 EURUSD done at 1.11439 with StopLoss=41 points (spread=12 + SYMBOL_TRADE_STOPS_LEVEL=30)

Пример выполнения советника Test_Wrong_TakeProfit_LEVEL.mq5:

Test_Wrong_TakeProfit_LEVEL.mq5
Point=0.00001 Digits=5
SYMBOL_TRADE_EXECUTION=SYMBOL_TRADE_EXECUTION_INSTANT
SYMBOL_TRADE_FREEZE_LEVEL=20: ордер или позицию запрещено модифицировать, если осталось 20 пунктов до цены срабатывания
SYMBOL_TRADE_STOPS_LEVEL=30: StopLoss и TakeProfit должны быть не ближе 30 пунктов от цены закрытия
1. Buy 1.0 EURUSD at 1.11461 TP=1.11478 Bid=1.11452 (TakeProfit-Bid=26 points)
CTrade::OrderSend: instant buy 1.00 EURUSD at 1.11461 tp: 1.11478 [invalid stops]
2. Buy 1.0 EURUSD at 1.11461 TP=1.11479 Bid=1.11452 (TakeProfit-Bid=27 points)
CTrade::OrderSend: instant buy 1.00 EURUSD at 1.11461 tp: 1.11479 [invalid stops]
3. Buy 1.0 EURUSD at 1.11461 TP=1.11480 Bid=1.11452 (TakeProfit-Bid=28 points)
CTrade::OrderSend: instant buy 1.00 EURUSD at 1.11461 tp: 1.11480 [invalid stops]
4. Buy 1.0 EURUSD at 1.11461 TP=1.11481 Bid=1.11452 (TakeProfit-Bid=29 points)
CTrade::OrderSend: instant buy 1.00 EURUSD at 1.11461 tp: 1.11481 [invalid stops]
5. Buy 1.0 EURUSD at 1.11462 TP=1.11482 Bid=1.11452 (TakeProfit-Bid=30 points)
Buy 1.0 EURUSD done at 1.11462 with TakeProfit=20 points (SYMBOL_TRADE_STOPS_LEVEL=30 - spread=10)

Проверка уровней StopLoss и TakeProfit в отложенных ордерах гораздо проще, эти уровни должны откладываться от цены открытия ордера. То есть, проверка уровней с учетом минимальной дистанции SYMBOL_TRADE_STOPS_LEVEL выглядит так: уровни TakeProfit и StopLoss должны быть от цены срабатывания ордера на расстоянии не менее SYMBOL_TRADE_STOPS_LEVEL пунктов.

BuyLimit и BuyStop
SellLimit и SellStop
TakeProfit - Open >= SYMBOL_TRADE_STOPS_LEVEL
Open - StopLoss >= SYMBOL_TRADE_STOPS_LEVEL
Open - TakeProfit >= SYMBOL_TRADE_STOPS_LEVEL
StopLoss - Open >= SYMBOL_TRADE_STOPS_LEVEL

В советнике Test_StopLoss_Level_in_PendingOrders.mq5 делается серия попыток установить ордера BuyStop и BuyLimt  до тех пор, пока операция не завершится успешно. С каждой попыткой уровень StopLoss или TakeProfit сдвигается на 1 пункт в правильную сторону. Пример выполнения этого советника:

Test_StopLoss_Level_in_PendingOrders.mq5
SYMBOL_TRADE_EXECUTION=SYMBOL_TRADE_EXECUTION_INSTANT
SYMBOL_TRADE_FREEZE_LEVEL=20: ордер или позицию запрещено модифицировать, если осталось 20 пунктов до цены срабатывания
SYMBOL_TRADE_STOPS_LEVEL=30: StopLoss и TakeProfit должны быть не ближе 30 пунктов от цены закрытия
1. BuyStop 1.0 EURUSD at 1.11019 SL=1.10993 (Open-StopLoss=26 points)
CTrade::OrderSend: buy stop 1.00 EURUSD at 1.11019 sl: 1.10993 [invalid stops]
2. BuyStop 1.0 EURUSD at 1.11019 SL=1.10992 (Open-StopLoss=27 points)
CTrade::OrderSend: buy stop 1.00 EURUSD at 1.11019 sl: 1.10992 [invalid stops]
3. BuyStop 1.0 EURUSD at 1.11020 SL=1.10992 (Open-StopLoss=28 points)
CTrade::OrderSend: buy stop 1.00 EURUSD at 1.11020 sl: 1.10992 [invalid stops]
4. BuyStop 1.0 EURUSD at 1.11021 SL=1.10992 (Open-StopLoss=29 points)
CTrade::OrderSend: buy stop 1.00 EURUSD at 1.11021 sl: 1.10992 [invalid stops]
5. BuyStop 1.0 EURUSD at 1.11021 SL=1.10991 (Open-StopLoss=30 points)
BuyStop 1.0 EURUSD done at 1.11021 with StopLoss=1.10991 (SYMBOL_TRADE_STOPS_LEVEL=30)
 --------- 
1. BuyLimit 1.0 EURUSD at 1.10621 TP=1.10647 (TakeProfit-Open=26 points)
CTrade::OrderSend: buy limit 1.00 EURUSD at 1.10621 tp: 1.10647 [invalid stops]
2. BuyLimit 1.0 EURUSD at 1.10621 TP=1.10648 (TakeProfit-Open=27 points)
CTrade::OrderSend: buy limit 1.00 EURUSD at 1.10621 tp: 1.10648 [invalid stops]
3. BuyLimit 1.0 EURUSD at 1.10621 TP=1.10649 (TakeProfit-Open=28 points)
CTrade::OrderSend: buy limit 1.00 EURUSD at 1.10621 tp: 1.10649 [invalid stops]
4. BuyLimit 1.0 EURUSD at 1.10619 TP=1.10648 (TakeProfit-Open=29 points)
CTrade::OrderSend: buy limit 1.00 EURUSD at 1.10619 tp: 1.10648 [invalid stops]
5. BuyLimit 1.0 EURUSD at 1.10619 TP=1.10649 (TakeProfit-Open=30 points)
BuyLimit 1.0 EURUSD done at 1.10619 with TakeProfit=1.10649 (SYMBOL_TRADE_STOPS_LEVEL=30)

Примеры проверки уровней TakeProfit и StopLoss в отложенных ордерах находятся в приложенных исходных кодах: Check_TP_and_SL.mq4 и Check_TP_and_SL.mq5.


Попытка модификации ордера или позиции в пределах уровня заморозки SYMBOL_TRADE_FREEZE_LEVEL

В настройках символа может быть задан параметр SYMBOL_TRADE_FREEZE_LEVEL, который показывает в пунктах дистанцию заморозки торговых операций для отложенных ордеров и открытых позиций. Например, если сделка по финансовому инструменту переправляется для процессинга во внешнюю торговую систему, то отложенный ордер BuyLimit может находиться в данный момент слишком близко от текущей цены Ask. И если мы отправим приказ на модификацию этого ордера в тот момент, когда цена открытия достаточно близка к цене Ask, то может получиться так, что ордер уже будет исполнен и модификация будет невозможна.

Поэтому для отложенных ордеров и открытых позиций в настройках символа может указываться дистанция заморозки, в пределах которой их нельзя модифицировать. В общем случае, прежде чем попытаться отправить приказ на модификацию, необходимо сделать проверку с учетом  SYMBOL_TRADE_FREEZE_LEVEL:

Тип  ордера/позиции
Активация по цене
Проверка
Buy Limit ордер
 Ask
Ask-OpenPrice >= SYMBOL_TRADE_FREEZE_LEVEL
Buy Stop ордер  Ask OpenPrice-Ask >= SYMBOL_TRADE_FREEZE_LEVEL
Sell Limit ордер  Bid OpenPrice-Bid >= SYMBOL_TRADE_FREEZE_LEVEL
Sell Stop ордер  Bid Bid-OpenPrice >= SYMBOL_TRADE_FREEZE_LEVEL
Buy позиция
 Bid TakeProfit-Bid >= SYMBOL_TRADE_FREEZE_LEVEL
Bid-StopLoss >= SYMBOL_TRADE_FREEZE_LEVEL
Sell позиция
 Ask Ask-TakeProfit >= SYMBOL_TRADE_FREEZE_LEVEL
StopLoss-Ask >= SYMBOL_TRADE_FREEZE_LEVEL

Полные примеры функций для проверки ордеров и позиций на уровень SYMBOL_TRADE_FREEZE_LEVEL вы найдете в приложенных скриптах Check_FreezeLevel.mq5 и Check_FreezeLevel.mq4.

//--- проверяем типа ордера
   switch(type)
     {
      //--- отложенный ордер BuyLimit
      case  ORDER_TYPE_BUY_LIMIT:
        {
         //--- проверим дистанцию от цены открытия до цены активации
         check=((Ask-price)>freeze_level*_Point);
         if(!check)
            PrintFormat("Ордер %s #%d нельзя модифицировать: Ask-Open=%d пунктов < SYMBOL_TRADE_FREEZE_LEVEL=%d пунктов",
                        EnumToString(type),ticket,(int)((Ask-price)/_Point),freeze_level);
         return(check);
        }
      //--- отложенный ордер BuyLimit
      case  ORDER_TYPE_SELL_LIMIT:
        {
         //--- проверим дистанцию от цены открытия до цены активации
         check=((price-Bid)>freeze_level*_Point);
         if(!check)
            PrintFormat("Ордер %s #%d нельзя модифицировать: Open-Bid=%d пунктов < SYMBOL_TRADE_FREEZE_LEVEL=%d пунктов",
                        EnumToString(type),ticket,(int)((price-Bid)/_Point),freeze_level);
         return(check);
        }
      break;
      //--- отложенный ордер BuyStop
      case  ORDER_TYPE_BUY_STOP:
        {
         //--- проверим дистанцию от цены открытия до цены активации
         check=((price-Ask)>freeze_level*_Point);
         if(!check)
            PrintFormat("Ордер %s #%d нельзя модифицировать: Ask-Open=%d пунктов < SYMBOL_TRADE_FREEZE_LEVEL=%d пунктов",
                        EnumToString(type),ticket,(int)((price-Ask)/_Point),freeze_level);
         return(check);
        }
      //--- отложенный ордер SellStop
      case  ORDER_TYPE_SELL_STOP:
        {
         //--- проверим дистанцию от цены открытия до цены активации
         check=((Bid-price)>freeze_level*_Point);
         if(!check)
            PrintFormat("Ордер %s #%d нельзя модифицировать: Bid-Open=%d пунктов < SYMBOL_TRADE_FREEZE_LEVEL=%d пунктов",
                        EnumToString(type),ticket,(int)((Bid-price)/_Point),freeze_level);
         return(check);
        }
      break;
     }

Вы можете самостоятельно смоделировать ситуацию, когда происходит попытка модификации отложенного ордера в пределах уровня заморозки. Для этого откройте демо-счет, на котором есть финансовые инструменты с ненулевым уровнем SYMBOL_TRADE_FREEZE_LEVEL, запустите на графике советник Test_SYMBOL_TRADE_FREEZE_LEVEL.mq5 (Test_SYMBOL_TRADE_FREEZE_LEVEL.mq4) и установите вручную любой отложенный ордер. Советник сам подтянет ордер максимально близко к рынку и начнет делать запрещенные попытки модификации. При этом будет проигрываться звук с помощью функции PlaySound().


Ошибки, возникающие при работе с символами с недостаточной историей котировок

Если советник или индикатор запускается на графике с недостаточной историей, то тут возможны два варианта:

  1. программа делает проверку на наличие требуемой истории на всю необходимую глубину.  Если доступных баров меньше, чем требуется, то программа запрашивает недостающие данные и завершает свою работу до прихода следующего тика. Этот путь самый правильный и помогает избежать множества ошибок — таких, как например, выход за пределы массива или деление на ноль;
  2. программа не делает никаких проверок и сразу начинает свою работу, как если бы вся необходимая история по всем требуемым символам и таймфреймам была доступна сразу же по первому требованию. Такой подход чреват многими непредсказуемыми ошибками.

Вы можете попробовать самостоятельно смоделировать такую ситуацию. Для этого запустите тестируемый индикатор или советник на графике, затем закройте терминал и удалите всю историю и снова запустите терминал. Если после такого перезапуска ошибки в Журналах не появились, то начните менять символы и таймфреймы на графиках, на которых запущена ваша программа. Многие индикаторы выдают ошибки при запуске на недельных или месячных таймфреймах, для которых количество баров, как правило, ограничено. Также, при резкой смене символа графика,  например, с EURUSD на CADJPY, индикатор или советник, запущенный на графике, может налететь на ошибку, вызванную отсутствием требуемой для его расчетов истории.


Выход за пределы массива (array out of range)

При работе с массивами доступ к его элементам осуществляется по номеру индекса, который не может быть отрицательным и должен быть меньше, чем размер массива. Размер массива можно получить с помощью функции ArraySize().

Эта ошибка может возникать при работе с динамическим массивом, когда его размер еще не распределен явно функцией  ArrayResize(), либо при использовании такого массива в функциях, которые самостоятельно устанавливают размер переданным им динамическим массивам. Например, функция CopyTicks() пытается записать в массив запрашиваемое количество тиков, но если тиков меньше, чем запрошено, то размер полученного массива будет меньше ожидаемого.

Другой весьма вероятный способ получить эту ошибку — попытка обратиться к данным индикаторного буфера, когда его размер еще не инициализирован. Напомним, индикаторные буферы являются динамическими массивами, и их размеры устанавливаются исполнительной системой терминала только после инициализации графика. Поэтому,  например, попытка обратиться к данным такого буфера в функции OnInit() приведет к ошибке "array out of range".

Простой пример индикатора, который выдает эту ошибку, приложен в файле Test_Out_of_range.mq5.


Деление на ноль (zero divide)

Другой критической ошибкой является попытка деления на ноль. В этом случае выполнение программы сразу же останавливается, тестер выводит в Журнал имя функции и номер строки в исходном коде, на которой произошла ошибка.


Как правило, деление на ноль происходит вследствие ситуации, непредвиденной программистом, например, получения какого-либо свойства либо вычисления выражения с "плохими" данными.

Вы можете легко воспроизвести деление на ноль с помощью простого советника TestZeroDivide.mq5, исходный код которого представлен на скриншоте. Другой критической ошибкой является использование некорректного указателя объекта. Для выяснения причины такой ошибки поможет Отладка на истории.


Отправка запроса на модификацию уровней без фактического их изменения

Если по правилам торговой системы требуется модифировать отложенные ордера или открытые позиции, то перед отправкой торгового запроса на проведение транзакции необходимо убедиться, что запрашиваемая операции действительно изменит параметры ордера или позиции. Отправка торгового запроса, который фактически не делает никаких изменений, считается ошибкой. Торговый сервер в ответ на такое действие вернет код ответа TRADE_RETCODE_NO_CHANGES=10025 (MQL5) или код ERR_NO_RESULT=1 (MQL4)

Пример проверки для MQL5 приведен в скрипте Check_OrderLevels.mq5:

//--- класс для проведения торговых операций
#include <Trade\Trade.mqh>
CTrade trade;
#include <Trade\Trade.mqh>
//--- класс для работы с ордерами
#include <Trade\OrderInfo.mqh>
COrderInfo orderinfo;
//--- класс для работы с позициями
#include <Trade\PositionInfo.mqh>
CPositionInfo positioninfo;
//+------------------------------------------------------------------+
//| проверка новых значений уровней перед модификацией ордера        |
//+------------------------------------------------------------------+
bool OrderModifyCheck(ulong ticket,double price,double sl,double tp)
  {
//--- выберем ордер по тикету
   if(orderinfo.Select(ticket))
     {
      //--- размер пункта и имя символа, по которому выставлен отложенный ордер
      string symbol=orderinfo.Symbol();
      double point=SymbolInfoDouble(symbol,SYMBOL_POINT);
      int digits=(int)SymbolInfoInteger(symbol,SYMBOL_DIGITS);
      //--- проверим - есть ли изменения в цене открытия
      bool PriceOpenChanged=(MathAbs(orderinfo.PriceOpen()-price)>point);
      //--- проверим - есть ли изменения в уровне StopLoss
      bool StopLossChanged=(MathAbs(orderinfo.StopLoss()-sl)>point);
      //--- проверим - есть ли изменения в уровне Takeprofit
      bool TakeProfitChanged=(MathAbs(OrderTakeProfit()-tp)>point);
      //--- если есть какие-то изменения в уровнях
      if(PriceOpenChanged || StopLossChanged || TakeProfitChanged)
         return(true);  // ордер можно модифицировать      
      //--- изменений в уровнях открытия, StopLoss и Takeprofit нет
      else
      //--- сообщим об ошибке
         PrintFormat("Ордер #%d уже имеет уровни Open=%.5f SL=%.5f TP=%.5f",
                     ticket,orderinfo.PriceOpen(),orderinfo.StopLoss(),orderinfo.TakeProfit());
     }
//--- дошли до конца, изменений для ордера нет
   return(false);       // нет смысла модифицировать 
  }
//+------------------------------------------------------------------+
//| проверка новых значений уровней перед модификацией ордера        |
//+------------------------------------------------------------------+
bool PositionModifyCheck(ulong ticket,double sl,double tp)
  {
//--- выберем ордер по тикету
   if(positioninfo.SelectByTicket(ticket))
     {
      //--- размер пункта и имя символа, по которому выставлен отложенный ордер
      string symbol=positioninfo.Symbol();
      double point=SymbolInfoDouble(symbol,SYMBOL_POINT);
      //--- проверим - есть ли изменения в уровне StopLoss
      bool StopLossChanged=(MathAbs(positioninfo.StopLoss()-sl)>point);
      //--- проверим - есть ли изменения в уровне Takeprofit
      bool TakeProfitChanged=(MathAbs(positioninfo.TakeProfit()-tp)>point);
      //--- если есть какие-то изменения в уровнях
      if(StopLossChanged || TakeProfitChanged)
         return(true);  // позицию можно модифицировать      
      //--- изменений в уровнях StopLoss и Takeprofit нет
      else
      //--- сообщим об ошибке
         PrintFormat("Ордер #%d уже имеет уровни Open=%.5f SL=%.5f TP=%.5f",
                     ticket,orderinfo.PriceOpen(),orderinfo.StopLoss(),orderinfo.TakeProfit());
     }
//--- дошли до конца, изменений для ордера нет
   return(false);       // нет смысла модифицировать 
  }
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- уровни цен для ордеров и позиций
   double priceopen,stoploss,takeprofit;
//--- тикет текущего ордера и позиции
   ulong orderticket,positionticket;
/*
   ... получим тикет ордера и новые уровни StopLoss/Takeprofit/PriceOpen
*/
//--- проверим уровни перед модификацией отложенного ордера   
   if(OrderModifyCheck(orderticket,priceopen,stoploss,takeprofit))
     {
      //--- проверка прошла успешно
      trade.OrderModify(orderticket,priceopen,stoploss,takeprofit,
                        orderinfo.TypeTime(),orderinfo.TimeExpiration());
     }
/*
   ... получим тикет позиции и новые уровни StopLoss/Takeprofit
*/
//--- проверим уровни перед модификацией позиции
   if(PositionModifyCheck(positionticket,stoploss,takeprofit))
     {
      //--- проверка прошла успешно
      trade.PositionModify(positionticket,stoploss,takeprofit);
     }
//---
  }

Пример проверки на языке MQL4 вы найдете в скрипте Check_OrderLevels.mq4:

#property strict
//+------------------------------------------------------------------+
//| проверка новых значений уровней перед модификацией ордера        |
//+------------------------------------------------------------------+
bool OrderModifyCheck(int ticket,double price,double sl,double tp)
  {
//--- выберем ордер по тикету
   if(OrderSelect(ticket,SELECT_BY_TICKET))
     {
      //--- размер пункта и имя символа, по которому выставлен отложенный ордер
      string symbol=OrderSymbol();
      double point=SymbolInfoDouble(symbol,SYMBOL_POINT);
      //--- проверим - есть ли изменения в цене открытия
      bool PriceOpenChanged=true;
      int type=OrderType();
      if(!(type==OP_BUY || type==OP_SELL))
        {
         PriceOpenChanged=(MathAbs(OrderOpenPrice()-price)>point);
        }
      //--- проверим - есть ли изменения в уровне StopLoss
      bool StopLossChanged=(MathAbs(OrderStopLoss()-sl)>point);
      //--- проверим - есть ли изменения в уровне Takeprofit
      bool TakeProfitChanged=(MathAbs(OrderTakeProfit()-tp)>point);
      //--- если есть какие-то изменения в уровнях
      if(PriceOpenChanged || StopLossChanged || TakeProfitChanged)
         return(true);  // ордер можно модифицировать      
      //--- изменений в уровнях открытия, StopLoss и Takeprofit нет
      else
      //--- сообщим об ошибке
         PrintFormat("Ордер #%d уже имеет уровни Open=%.5f SL=%.5f TP=%.5f",
                     ticket,OrderOpenPrice(),OrderStopLoss(),OrderTakeProfit());
     }
//--- дошли до конца, изменений для ордера нет
   return(false);       // нет смысла модифицировать 
  }
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- уровни цен для ордеров и позиций
   double priceopen,stoploss,takeprofit;
//--- тикет текущего ордера 
   int orderticket;
/*
   ... получим тикет ордeра и новые уровни StopLoss/Takeprofit/PriceOpen
*/
//--- проверим уровни перед модификацией ордера   
   if(OrderModifyCheck(orderticket,priceopen,stoploss,takeprofit))
     {
      //--- проверка прошла успешно
      OrderModify(orderticket,priceopen,stoploss,takeprofit,OrderExpiration());
     }
  }

Рекмендуем также почитать статьи:

  1. Как упростить обнаружение и устранение ошибок в коде эксперта
  2. Как разработать надежный и безопасный торговый робот на языке MQL4


Попытка импорта скомпилированных файлов (даже EX4/EX5) и DLL

Программы, распространяемые через Маркет, должны быть гарантированно безопасны для пользователей. Поэтому любые попытки использования DLL или функций из скомпилированных файлов EX4/EX5 считаются ошибкой. Такие продукты не будут опубликованы на Маркете.

Если вашей программе необходимо использовать дополнительные индикаторы, которых нет в поставке, используйте Ресурсы.


Обращение к пользовательским индикаторам через iCustom()

Если для работы вашей программы необходимо обращение к данным пользовательского индикатора, то вам необходимо поместить все нужные индикаторы в Ресурсы. Продукты из Маркета должны быть готовыми к работе в любом неподготовленном окружении, поэтому они должны содержать всё необходимое  в своем EX4/EX5 файле. Рекомендуемые по теме статьи:


Передача недопустимого параметра в функцию (ошибки времени выполнения)

Этот тип ошибок встречается редко, для многих из них есть готовые коды, которые призваны помочь найти причину.

Константа Значение Описание
ERR_INTERNAL_ERROR 4001 Неожиданная внутренняя ошибка
ERR_WRONG_INTERNAL_PARAMETER 4002 Ошибочный параметр при внутреннем вызове функции клиентского терминала
ERR_INVALID_PARAMETER 4003 Ошибочный параметр при вызове системной функции
ERR_NOTIFICATION_WRONG_PARAMETER 4516 Неверный параметр для отправки уведомления – в функцию SendNotification()  передали пустую строку или NULL
ERR_BUFFERS_WRONG_INDEX 4602 Ошибочный индекс своего индикаторного буфера
ERR_INDICATOR_WRONG_PARAMETERS 4808 Неправильное количество параметров при создании индикатора
ERR_INDICATOR_PARAMETERS_MISSING 4809 Отсутствуют параметры при создании индикатора
ERR_INDICATOR_CUSTOM_NAME 4810 Первым параметром в массиве должно быть имя пользовательского индикатора
ERR_INDICATOR_PARAMETER_TYPE 4811 Неправильный тип параметра в массиве при создании индикатора
ERR_NO_STRING_DATE 5030 В строке нет даты
ERR_WRONG_STRING_DATE 5031 В строке ошибочная дата
ERR_TOO_MANY_FORMATTERS 5038 Форматных спецификаторов больше, чем параметров
ERR_TOO_MANY_PARAMETERS 5039 Параметров больше, чем форматных спецификаторов

В таблице перечислены далеко не все ошибки, которые могут встретиться во время работы запущенной программы.

 

Access violation

Такая ошибка возникает при попытке обратиться к памяти, доступ к которой запрещен. В каждом таком случае необходимо обратиться к разработчикам в Сервисдеск через свой Профиль или через страницу Контакты. Детальное описание шагов по воспроизведению ошибки и приложенный исходный код значительно ускорят поиск причины такой ошибки и помогут улучшить компилятор исходного кода.




Потребление ресурсов процессора и памяти

При написании программы важно использовать оптимальные по времени выполнения алгоритмы, так как в противном случае может стать затруднительной или даже невозможной работа других запущенных в терминале программ.

Важно помнить, что на каждый символ в Обзоре рынка для работы терминал выделяет один общий поток, в котором крутятся все индикаторы и графики, открытые по нему.

Это означает, что если у вас открыто 5 графиков по EURUSD на разных таймфреймах,  и на этих графиках запущены 15 индикаторов, то все эти графики и индикаторы получают в распоряжение один-единственный поток для вычислений и отображения информации на графиках. Поэтому один запущенный на графике неэффективный, потребляющий много ресурсов индикатор может замедлить работу остальных индикаторов и даже затормозить отрисовку цен на всех остальных графиках этого символа.

Вы может легко проверить время, затрачиваемое вашим алгоритмом с помощью функции GetMicrosecondCount(). Сделав замеры времени между двумя строчками кода, легко получить время исполнения в микросекундах.  Для перевода в миллисекунды (ms) это время необходимо разделить на 1000 (в 1 миллисекунде вмещается 1000 микросекунд). Для индикаторов критическим местом по времени исполнения обычно является обработчик OnCalculate(). Как правило, первый расчет индикатора сильно зависит от параметра Макс. баров в окне, укажите в нем значение "Unlimited" и запустите свой индикатор на символе с историей больше 10 лет на таймфрейме M1. Если первый запуск занимает много времени (например, больше 100 ms), то необходимо оптимизировать код.

Вот пример того, как измерить время выполнения обработчика OnCalculate() в индикаторе ROC, идущем в поставке терминала в исходном коде. Вставки выделены желтым фоном:

//+------------------------------------------------------------------+
//| Rate of Change                                                   |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,const int prev_calculated,const int begin,const double &price[])
  {
//--- check for rates count
   if(rates_total<ExtRocPeriod)
      return(0);
//--- время начала расчетов
   ulong start=GetMicrosecondCount();  
//--- preliminary calculations
   int pos=prev_calculated-1; // set calc position
   if(pos<ExtRocPeriod)
      pos=ExtRocPeriod;
//--- the main loop of calculations
   for(int i=pos;i<rates_total && !IsStopped();i++)
     {
      if(price[i]==0.0)
         ExtRocBuffer[i]=0.0;
      else
         ExtRocBuffer[i]=(price[i]-price[i-ExtRocPeriod])/price[i]*100;
     }
//--- время окончания расчетов     
   ulong finish=GetMicrosecondCount();  
   PrintFormat("Функция %s в %s затратила %.1f ms",__FUNCTION__,__FILE__,(finish-start)/1000.);
//--- OnCalculate done. Return new prev_calculated.
   return(rates_total);
  }

Используемую память вы можете измерить с помощью функции  MQLInfoInteger(MQL_MEMORY_USED). Ну и, конечно же, используйте Профилировщик кода для поиска самых затратных функций в вашей программе. Рекомендуем также почитать статьи Принципы экономного пересчета индикаторов и Отладка программ на MQL5.

Эксперты работают в собственных потоках, но всё сказанное выше относится и к ним. Необходимо писать оптимальный код в любых типах программ — советниках, индикаторах, библиотеках и скриптах.


Проверок много не бывает

Все приведенные советы по проверке индикаторов и советников рекомендуются не только для публикации продуктов в Маркете, но и в обычной практике,  когда вы пишете для себя. В этой статье мы рассмотрели далеко не все ошибки, которые могут встретиться при торговле на реальных счетах. Здесь не рассмотрены правила обработки торговых ошибок, возникающих при пропаже связи с торговым сервером, реквотах, отказах в совершении сделок и многое другое, что может нарушить идеальные правила торговой системы. Для этих случаев каждый разработчик роботов имеет свои готовые рецепты, наработанные опытом.

Новичкам же рекомендуем читать все статьи, посвященные обработке ошибок, а также задавать вопросы на форуме и в комментариях к этой статье. Другие более опытные члены сообщества MQL5.community помогут вам разобраться в непонятных для вас вопросах.  Мы надеемся, что собранная в статье информация поможет вам создавать более надежных торговых роботов и в более короткий срок.


Рекомендуемые по теме статьи: