Ошибка array out of range

Введение

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

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

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

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

  1. Ошибки компиляции
    • 1.1. Идентификатор совпадает с зарезервированным словом
    • 1.2. Специальные символы в наименованиях переменных и функций
    • 1.3. Ошибки использования оператора switch
    • 1.4. Возвращаемые значения у функций
    • 1.5. Массивы в аргументах функций
  2. Ошибки времени выполнения
    • 2.1. Выход за пределы массива (Array out of range)
    • 2.2. Деление на ноль (Zero divide)
    • 2.3. Использование 0 вместо NULL для текущего символа
    • 2.4. Строки в формате Unicodе и их использование в DLL
    • 2.5. Совместное использование файлов
    • 2.6. Особенность преобразования datetime
  3. Предупреждения компилятора
    • 3.1. Пересечения имен глобальных и локальных переменных
    • 3.2. Несоответствие типов
    • 3.3. Неиспользуемые переменные

1. Ошибки компиляции

При наличии ошибок в коде программа не может быть скомпилирована.

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

#property strict

Этот режим значительно упрощает поиск ошибок.

1.1. Идентификатор совпадает с зарезервированным словом

Если наименование переменной или функции совпадает с одним из зарезервированных слов:

int char[];  
int char1[]; 
int char()   
{
 return(0);
}

то компилятор выводит сообщения об ошибках:

Рис.1. Ошибки "unexpected token" и "name expected"

Рис.1. Ошибки «unexpected token» и «name expected»

Для исправления данной ошибки нужно исправить имя переменной или функции.

1.2. Специальные символы в наименованиях переменных и функций

Если наименования переменных или функций содержат специальные символы ($, @, точка):

int $var1; 
int @var2; 
int var.3; 
void f@()  
{
 return;
}

то компилятор выводит сообщения об ошибках:

Рис.2. Ошибки "unknown symbol" и "semicolon expected"

Рис.2. Ошибки «unknown symbol» и «semicolon expected»

Для исправления данной ошибки нужно скорректировать имена переменных или функций.

1.3. Ошибки использования оператора switch

Старая версия компилятора позволяла использовать любые значения в выражениях и константах оператора switch:

void start()
  {
   double n=3.14;
   switch(n)
     {
      case 3.14: Print("Pi");break;
      case 2.7: Print("E");break;
     }
  }

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

Рис.3. Ошибки "illegal switch expression type" и "constant expression is not integral"

Рис.3. Ошибки «illegal switch expression type» и «constant expression is not integral»

В таких случаях можно использовать явные сравнения численных значений, например:

void start()
  {
   double n=3.14;
   if(n==3.14) Print("Pi");
   else
      if(n==2.7) Print("E");
  }

1.4. Возвращаемые значений функций

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

int function()
{
}

При строгом режиме компиляции (strict) возникает ошибка:

Рис.4. Ошибка "not all control paths return a value"

Рис.4. Ошибка «not all control paths return a value»

В режиме компиляции по умолчанию компилятор выводит предупреждение:

Рис.5. Предупреждение "not all control paths return a value"

Рис.5. Предупреждение «not all control paths return a value»

Если возвращаемое значение функции не соответствует объявлению:

int init()                         
  {
   return;                          
  }

то при строгом режиме компиляции возникает ошибка:

Рис.6. Ошибка "function must return a value"

Рис.6. Ошибка «function must return a value»

В режиме компиляции по умолчанию компилятор выводит предупреждение:

Рис.7. Предупреждение 'return - function must return a value"

Рис.7. Предупреждение ‘return — function must return a value»

Для исправления таких ошибок в код функции нужно добавить оператор возврата return c возвращаемым значением соответствующего типа.

1.5. Массивы в аргументах функций

Массивы в аргументах функций теперь передаются только по ссылке.

double ArrayAverage(double a[])
{
 return(0);
}

Данный код при строгом режиме компиляции (strict) приведет к ошибке:

Рис.8. Ошибка компилятора "arrays passed by reference only"

Рис.8. Ошибка компилятора «arrays passed by reference only»

В режиме компиляции по умолчанию компилятор выводит предупреждение:

Рис.9. Предупреждение компилятора "arrays passed by reference only"

Рис.9. Предупреждение компилятора «arrays passed by reference only»

Для исправления таких ошибок нужно явно указать передачу массива по ссылке, добавив префикс & перед именем массива:

double ArrayAverage(double &a[])
{
 return(0);
}

Следует отметить, что теперь константные массивы (Time[], Open[], High[], Low[], Close[], Volume[]) не могут быть переданы по ссылке. Например, вызов:

ArrayAverage(Open);

вне зависимости от режима компиляции приводит к ошибке:

Рис.10. Ошибка 'Open' - constant variable cannot be passed as reference

Рис.10. Ошибка ‘Open’ — constant variable cannot be passed as reference

Для устранения подобных ошибок нужно скопировать необходимые данные из константного массива:

   
   double OpenPrices[];
   
   ArrayCopy(OpenPrices,Open,0,0,WHOLE_ARRAY);
   
   ArrayAverage(OpenPrices);

2. Ошибки времени выполнения

Ошибки, возникающие в процессе исполнения кода программы принято называть ошибками времени выполнения (runtime errors). Такие ошибки обычно зависят от состояния программы и связаны с некорректными значениями переменных.

Например, если переменная используется в качестве индекса элементов массива, то ее отрицательные значения неизбежно приведут к выходу за пределы массива.

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

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

Большинство индикаторов, в которых используется данный способ оптимизации вычислений, имеют вид:



int start()
  {
   
   if (Bars<100) 
     return(-1); 

   
   int counted_bars=IndicatorCounted();
   
   if(counted_bars<0) return(-1);
      
   
   int limit=Bars-counted_bars;

   
   if(counted_bars==0) 
     {
      limit--;  
      
      limit-=10;
     }
   else 
     {     
      
      limit++;
     } 
   
   for (int i=limit; i>0; i--)
   {
     Buff1[i]=0.5*(Open[i+5]+Close[i+10]) 
   }
}

Часто встречается некорректная обработка случая counted_bars==0 (начальную позицию limit нужно уменьшить на значение, равное 1 + максимальный индекс относительно переменной цикла).

Также следует помнить о том, что в момент исполнения функции start() мы можем обращаться к элементам массивов индикаторных буферов от 0 до Bars()-1. Если есть необходимость работы с массивами, которые не являются индикаторными буферами, то их размер следует увеличить при помощи функции ArrayResize() в соответствии с текущим размером индикаторных буферов. Максимальный индекс элемента для адресации также можно получить вызовом ArraySize() с одним из индикаторных буферов в качестве аргумента.

2.2. Деление на ноль (Zero divide)

Ошибка «Zero divide» возникает в случае, если при выполнении операции деления делитель оказывается равен нулю:

void OnStart()
  {

   int a=0, b=0,c;
   c=a/b;
   Print("c=",c);
  }

При выполнении данного скрипта во вкладке «Эксперты» возникает сообщение об ошибке и завершении работы программы:

Рис.11. Сообщение об ошибке "zero divide"

Рис.11. Сообщение об ошибке «zero divide»

Обычно такая ошибка возникает в случаях, когда значение делителя определяется значениями каких-либо внешних данных. Например, если анализируются параметры торговли, то величина задействованной маржи оказывается равна 0 если нет открытых ордеров. Другой пример: если анализируемые данные считываются из файла, то в случае его отсутствия нельзя гарантировать корректную работу. По этой причине желательно стараться учитывать подобные случаи и корректно их обрабатывать.

Самый простой способ — проверять делитель перед операцией деления и выводить сообщение об некорректном значении параметра:

void OnStart()
  {

   int a=0, b=0,c;
   if(b!=0) {c=a/b; Print(c);}
   else {Print("Error: b=0"); return; };
  }

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

Рис. 12. Сообщение о некорректном значении делителя

Рис. 12. Сообщение о некорректном значении делителя

2.3. Использование 0 вместо NULL для текущего символа

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

Например, значение технического индикатора Moving Average для текущего символа можно было запрашивать следующим образом:

AlligatorJawsBuffer[i]=iMA(0,0,13,8,MODE_SMMA,PRICE_MEDIAN,i); 

В новом компиляторе для указания текущего символа нужно явно указывать NULL:

AlligatorJawsBuffer[i]=iMA(NULL,0,13,8,MODE_SMMA,PRICE_MEDIAN,i); 

Кроме того, текущий символ и период графика можно указать при помощи функций Symbol() и Period().

AlligatorJawsBuffer[i]=iMA(Symbol(),Period(),13,8,MODE_SMMA,PRICE_MEDIAN,i); 

2.4. Строки в формате Unicodе и их использование в DLL

Строки теперь представляют собой последовательность символов Unicode.

Следует учитывать этот факт и использовать соответствующие функции Windows. Например, при использовании функций библиотеки wininet.dll вместо InternetOpenA() и InternetOpenUrlA() следует вызывать InternetOpenW() и InternetOpenUrlW().

В MQL4 изменилась внутренняя структура строк (теперь она занимает 12 байт), поэтому при передаче строк в DLL следует использовать структуру MqlString:

#pragma pack(push,1)
struct MqlString
  {
   int      size;       
   LPWSTR   buffer;     
   int      reserved;   
  };
#pragma pack(pop,1)

2.5. Совместное использование файлов

В новом MQL4 при открытии файлов необходимо явно указывать флаги FILE_SHARE_WRITE и FILE_SHARE_READ для совместного использования.

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

Например, при работе с оффлайновыми графиками требуется явно указывать флаги совместного доступа:

   
   ExtHandle=FileOpenHistory(c_symbol+i_period+".hst",FILE_BIN|FILE_WRITE|FILE_SHARE_WRITE|FILE_SHARE_READ);

Подробности можно найти в статье в статье «Оффлайновые графики и новый MQL4«.

2.6. Особенность преобразования datetime

Следует иметь ввиду, что преобразование типа datetime в строку теперь зависит от режима компиляции:

  datetime date=D'2014.03.05 15:46:58';
  string str="mydate="+date;

Например, попытка работы с файлами, имя которых содержит двоеточие, приведет к ошибке.

3. Предупреждения компилятора

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

Чистый код не должен содержать предупреждений.

3.1. Пересечения имен глобальных и локальных переменных

Если на глобальном и локальном уровнях присутствуют переменные с одинаковыми именами:

int i; 
void OnStart()
  {

   int i=0,j=0; 
   for (i=0; i<5; i++) {j+=i;}
   PrintFormat("i=%d, j=%d",i,j);
  }

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

Рис.13. Предупреждение "declaration of '%' hides global declaration at line %"

Рис.13. Предупреждение «declaration of ‘%’ hides global declaration at line %»

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

3.2. Несоответствие типов

В новой версии компилятора введена операция приведения типов.

#property strict
void OnStart()
  {
   double a=7;
   float b=a;
   int c=b;
   string str=c;
   Print(c);
  }

В строгом режиме компиляции при несоответствии типов компилятор выводит предупреждения:

Рис.14. Предупреждения "possible loss of data due to type conversion" и "implicit conversion from 'number' to 'string'

Рис.14. Предупреждения «possible loss of data due to type conversion» и «implicit conversion from ‘number’ to ‘string’

В данном примере компилятор предупреждает о возможной потере точности при присвоении различных типов данных и неявном преобразовании типа int в string.

Для исправления нужно использовать явное приведение типов:

#property strict
void OnStart()
  {
   double a=7;
   float b=(float)a;
   int c=(int)b;
   string str=(string)c;
   Print(c);
  }

3.3. Неиспользуемые переменные

Наличие переменных, которые не используются в коде программы (лишние сущности) не является хорошим тоном.

void OnStart()
  {
   int i,j=10,k,l,m,n2=1;
   for(i=0; i<5; i++) {j+=i;}
  }

Сообщения о таких переменных выводятся вне зависимости от режима компиляции:

Рис.15. Предупреждения "variable '%' not used'

Рис.15. Предупреждения «variable ‘%’ not used’

Для исправления нужно убрать неиспользуемые переменные из кода программы.

Выводы

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

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

Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.

While the arrays (problem) look similar, there is a major difference

MQL4 Indicators use «other» mechanics for handling arrays than «ordinary» arrays have.

...  Testing pass stopped due to a critical error in the EA
...  array out of range in '!2015-09-08___!EA 2xAMA 01 2015-09-08_msMOD_0.00.mq4' (519,39)

Yes, MT4 throws Fatal Error on an attempt to handle a wrong ptr->array[aStaticSIZE] and one has to take due care to either avoid such case, or trim ptr ( alike in low-latency circular-buffer scenarios ) not to direct past the array boundary ( underflow / overflow ) or extend the array[] via ArrayResize() so as to keep pace with the ptr growth ( until memory allows ) ) on arrays declared as dynamic double Array[];, however MQL4 Technical Indicators have completely other situation.


Limits? Yes. . . . . . ( And one ought to review on each New-MQL4.56789 stealth update )

As of a "New"-MQL4.56789-Build-840, your «ordinary» array cannot have more than 2.147.483.647 elements, if O/S memory-pool manager allows, so you ought to have plenty of space, even if using higher dimensionality mappings { 2D | 3D | 4D }.

Years ago ‘ve used many parallel 2D / 3D-arrays for fast and private ( safely encapsulated ) heap / stack-handlers for maintaining highly dynamic entities in scales of 100k+ rows / 2D-planes and all worked well-oiled in old-MQL4.

So some 7k+ elements should not make you any worries.


Indicators use array indirectly, via an automated Buffer-handler

In this sense, your code need not care about these issues.

/*
#property "pragmas" help MQL4-compiler decide about setup of internal handlers
          so this part of code "speaks" to MetaLang.exe at compile-time*/

#property indicator_buffers      3             // .DEF N-Buffs

#property indicator_color1       White         // .SET Buf[0].color
#property indicator_color2       SeaGreen      // .SET Buf[1].color
#property indicator_color3       FireBrick     // .SET Buf[2].color

#property indicator_width1       1             // .SET Buf[0].width
#property indicator_width2       2             // .SET Buf[1].width
#property indicator_width3       2             // .SET Buf[2].width

         double   buffer_line_up[],            // .DEF Arrays as dynamic ...[]
                  buffer_line_dn[],            //      with human-readable-names
                  buffer_line_ax[];            //      and MT4 will take care

int   init()   {

      SetIndexBuffer(   0, buffer_line_ax );   // .ASSOC IndexBuffer.0<-array[]
      SetIndexLabel(    0, "SuperTrend"     );

      SetIndexBuffer(   1, buffer_line_up   ); // .ASSOC IndexBuffer.0<-array[]
      SetIndexLabel(    1, "Up Trend"       );
      SetIndexStyle(    1, DRAW_LINE,
                           STYLE_SOLID,
                           1 + int( ATR_Multiplier / 5 ),
                           SeaGreen
                           );

      SetIndexBuffer(   2, buffer_line_dn );   // .ASSOC IndexBuffer.0<-array[]
      SetIndexLabel(    2, "Down Trend"     );
      SetIndexStyle(    2, DRAW_LINE,
                           STYLE_SOLID,
                           1 + int( ATR_Multiplier / 5 ),
                           FireBrick
                           );

      SetIndexDrawBegin(0, ATR_Period );       // .DEF initial depth of Buffer before 1st GUI output

      IndicatorShortName( "xxxx[" + ATR_Period + "," + ATR_Multiplier + "]" );

      IndicatorDigits( MarketInfo( Symbol(), MODE_DIGITS ) );

В программах на MQL4 часто невольно допускаются различные ошибки. В этом топике мы разберем наиболее часто встречающиеся из них.

Первое место в нашем топе ошибок занимает «крик души» терминала: «Потеряли индикатор!»

В таких случаях возмущению пользователей эксперта нет предела: «Советник не работает!» или «Ставлю советник на график и ничего не происходит!» Решение этого вопроса как всегда оказывается простым как все гениальное. Достаточно заглянуть на вкладку «Журнал» терминала и обнаружив там запись вроде:

2016.09.08 09:15:44.957 2016.01.04 00:51 cannot open file ‘C:\Users\1\AppData\Roaming\MetaQuotes\Terminal\MQL4\indicators\KELTNER_F12.ex4’ [2]

привести в соответствие имена индикаторов в советнике и в папке терминала. Если индикатор отсутствует, добавить его в папку с индикаторами.

Второе место по праву могут разделить ошибки Zero divide и Array out of range

Zero divide — Деление на ноль может возникнуть например в таком участке кода:


  all=NormalizeDouble(all/count,Digits);

Устраняется ошибка проверкой делителя на корректное значение:


   double all=0,count=0;
   if(count!=0) all=NormalizeDouble(all/count,Digits);

Ошибка Array out of range возникнет если в советнике скрипте или индикаторе вы обратитесь к несуществующей ячейке массива:


int a[3]={1,2,3};

void OnStart()
  {
   int b=a[4];
  }

В нашем массиве задано при инициализации 3 элемента:

int a[3]={1,2,3}; 

Нумерация элементов начинается с нуля: 0,1,2. Когда мы обращаемся к ячейке с номером 3 или 4 мы выходим за пределы массива, отсюда и ошибка. Правильным будет такой вариант:


int a[3]={1,2,3};

void OnStart()
  {
   int b;
   b=a[0];
   b=a[1];
   b=a[2];
  }

Так компилируется без ошибок и показывает что к указанным элементам массива, можно обращаться:

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

Причиной может являться спешка или невнимательность. Например случайно задели не ту клавишу:


int a[3]={1,2,3};

void OnStart()
  {
   int b;
   b=a[0];
   b=a[1];
   b=a[2];6
  }

Нажав CTRL+Z или дождавшись окончания компиляции мы решим и этот вопрос.

И на последок рассмотрим еще одну ошибку которая встретилась мне совсем недавно в одном из топиков в «Столе заказов MQL»: zakaz.opentraders.ru/34761.html

Андрей посмотри не компилируется.Пишет ошибка ‘MathMod’ — illegal switch expression type.

Это ошибка использования оператора switch.
В коде выражение:

switch(MathMod(day_48, 10)) 

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

switch((int)MathMod(day_48, 10)) 

Желающие посмотреть более объемный материал по этой теме могут пройти по ссылке: www.mql5.com/ru/articles/1391

Вступите в группу «Программирование на MQL», чтобы следить за обновлениями

ПРИСОЕДИНИТЬСЯ К ГРУППЕ

присоединиться

Разработка торговых экспертов на языке MQL4 является не такой уж простой задачей. Во-первых — алгоритмизация любой сложной торговой системы уже представляет собой проблему, так как нужно учесть очень много деталей, начиная с особенностей ТС и заканчивая спецификой среды MetaTrader 4. Во-вторых, даже наличие детальнейшего алгоритма не избавляет от сложностей, возникающих при переносе разработанного алгоритма на язык программирования MQL4.

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

MQL4 — Ошибки и как их исправить

Самые распространенные ошибки компиляции

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

Этот режим значительно упрощает поиск ошибок. Теперь перейдем к самым распространенным ошибкам при компиляции.

Идентификатор совпадает с зарезервированным словом

Если наименование переменной или функции совпадает с одним из зарезервированных слов:

int char[];  // неправильно
int char1[]; // правильно
int char()   // неправильно
{
    return(0);
}

то компилятор выводит сообщения об ошибках:

Для исправления данной ошибки нужно исправить имя переменной или функции. Я рекомендую придерживаться следующей системы для именования:

Все функции должны обозначать действие. То есть это должен быть глагол. Например, OpenLongPosition() или ModifyStopLoss(). Ведь функции всегда именно что-то делают, верно?

Кроме того, функции желательно называть в так называемом CamelCase стиле. А переменные в cebab_case стиле. Это общепринятая практика.

Кстати, об именах переменных. Переменные — это существительные. Например, my_stop_loss, day_of_week, current_month. Не так страшно назвать переменную длинным именем, гораздо страшнее назвать ее непонятно. Что такое dow, индекс Dow Jones? Нет, это, оказывается, день недели. Конечно, сегодня вам и так понятно, что это за переменная. Но когда вы откроете код советника месяц спустя, все будет уже не так явно. А это время, упущенное на расшифровку посланий из прошлого – оно вам надо?

Специальные символы в наименованиях переменных и функций

Идем дальше. Если наименования переменных или функций содержат специальные символы ($, @, точка):

int $var1; // неправильно
int @var2; // неправильно
int var.3; // неправильно
void f@()  // неправильно
{
    return;
}

то компилятор выводит сообщения об ошибках:

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

Ошибки использования оператора switch

Старая версия компилятора позволяла использовать любые значения в выражениях и константах оператора switch:

void start()
{
    double n=3.14;
    switch(n)
    {
        case 3.14: 
            Print("Pi");
            break;
        case 2.7: 
            Print("E");
            break;
    }
}

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

Поэтому, когда вы разбираете код классики, такой, как WallStreet, Ilan и прочей нетленки (что очень полезно для саморазвития), можно натолкнуться на эту ошибку. Лечится она очень просто, например, при использовании такой вот строки:

switch(MathMod(day_48, 10))

Вот так можно запросто решить проблему:

switch((int)MathMod(day_48, 10))

Возвращаемые значений функций

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

При строгом режиме компиляции (strict) возникает ошибка:

В режиме компиляции по умолчанию компилятор выводит предупреждение:

Если возвращаемое значение функции не соответствует объявлению:

Тогда при строгом режиме компиляции возникает ошибка:

В режиме компиляции по умолчанию компилятор выводит предупреждение:

Для исправления таких ошибок в код функции всего-навсего нужно добавить оператор возврата return c возвращаемым значением соответствующего типа.

Массивы в аргументах функций

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

double ArrayAverage(double a[])
{
    return(0);
}

Данный код при строгом режиме компиляции (strict) приведет к ошибке:

В режиме компиляции по умолчанию компилятор выводит предупреждение:

Для исправления таких ошибок нужно явно указать передачу массива по ссылке, добавив префикс & перед именем массива:

double ArrayAverage(double &a[])
{
    return(0);
}

Кстати, константные массивы (Time[], Open[], High[], Low[], Close[], Volume[]) не могут быть переданы по ссылке. Например, вызов:

вне зависимости от режима компиляции приводит к ошибке:

Для устранения подобных ошибок нужно скопировать необходимые данные из константного массива:

//--- массив для хранения значений цен открытия
double OpenPrices[];
//--- копируем значения цен открытия в массив OpenPrices[]
ArrayCopy(OpenPrices,Open,0,0,WHOLE_ARRAY);
//--- вызываем функцию
ArrayAverage(OpenPrices);

Одна из самых распространенных ошибок – потеря советником индикатора. В таких случаях обычно пользователи эксперта на форумах гневно пишут: «Советник не работает!» или «Ставлю советник на график и ничего не происходит!». Решение этого вопроса на самом деле очень простое. Как всегда, достаточно просто заглянуть на вкладку «Журнал» терминала и обнаружить там запись вроде:

2018.07.08 09:15:44.957 2016.01.04 00:51 cannot open file 
'C:Users1AppDataRoamingMetaQuotesTerminal
MQL4indicatorsKELTNER_F12.ex4' [2]

Говорит это нам о том, что индикатор в папку положить забыли, или же он назван по-другому. Если индикатор отсутствует, нужно добавить его в папку с индикаторами. Если он есть, стоит проверить его название в коде советника – скорее всего там он называется по-другому.

Предупреждения компилятора

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

Пересечения имен глобальных и локальных переменных

Если на глобальном и локальном уровнях присутствуют переменные с одинаковыми именами:

int i; // глобальная переменная
void OnStart()
{
    int i=0,j=0; // локальные переменные

    for (i=0; i<5; i++) {
        j+=i;
    }
    PrintFormat("i=%d, j=%d",i,j);
}

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

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

Несоответствие типов

В следующем примере:

#property strict
void OnStart()
{
    double a=7;
    float b=a;
    int c=b;
    string str=c;
    Print(c);
}

при строгом режиме компиляции при несоответствии типов компилятор выводит предупреждения:

В данном примере компилятор предупреждает о возможной потере точности при присвоении различных типов данных и неявном преобразовании типа int в string.

Для исправления нужно использовать явное приведение типов:

#property strict
void OnStart()
{
    double a=7;
    float b=(float)a;
    int c=(int)b;
    string str=(string)c;
    Print(c);
}

Неиспользуемые переменные

Наличие переменных, которые не используются в коде программы (лишние сущности) не является хорошим тоном.

void OnStart()
{
    int i,j=10,k,l,m,n2=1;
    for(i=0; i<5; i++) {j+=i;}
}

Сообщения о таких переменных выводятся вне зависимости от режима компиляции:

Для исправления нужно просто убрать неиспользуемые переменные из кода программы.

Диагностика ошибок при компиляции

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

Нередко у людей уходит немало времени и масса нервов на поиски какой-нибудь лишней скобки. Однако, есть способ быстрого обнаружения ошибок, который основан на использовании комментирования.

Написать достаточно большой код без единой ошибки – очень приятно. Но, к сожалению, так получается не часто. Я не рассматриваю здесь ошибки, которые приводят к неверному исполнению кода. Здесь пойдёт речь об ошибках, из-за которых становится невозможной компиляция.

Весьма распространённые ошибки – вставка лишней скобки в сложном условии, нехватка скобки, не выставление двоеточия, запятой при объявлении переменных, опечатка в названии переменной и так далее. Часто при компиляции можно сразу увидеть, в какой строке допущена подобная ошибка. Но бывают и случаи, когда найти такую ошибку не так просто. Ни компилятор, ни зоркий глаз нам не могут помочь сразу найти ошибку. В таких случаях, как правило, начинающие программисты начинают «обходить» весь код, пытаясь визуально определить ошибку. И снова, и снова, пока выдерживают нервы.

Однако MQL, как и другие языки программирования, предлагает отличный инструмент – комментирование. Используя его, можно отключать какие-то участки кода. Обычно комментирование используют именно для вставки каких-то комментариев, или же отключения неиспользуемых участков кода. Комментирование можно также успешно применять и при поиске ошибок.

Поиск ошибок обычно сводится к определению участка кода, где допущена ошибка, а затем, в этом участке, визуально находится ошибка. Думаю, вряд ли кто-то будет сомневаться в том, что исследовать «на глаз» 5-10 строчек кода проще и быстрей, чем 100-500, а то и несколько тысяч.

При использовании комментирования задача предельно проста. Сначала нужно закомментировать различные участки кода (иногда чуть ли не весь код), тем самым «отключив» его. Затем, по очереди, комментирование снимается с этих участков кода. После очередного снятия комментирования совершается попытка компиляции. Если компиляция прошла успешно – ошибка не в этом участке кода. Затем открывается следующий участок кода и так далее. Когда находится проблемный участок кода, визуально ищется ошибка, затем устраняется. Опять происходит попытка компиляции. Если всё прошло успешно, ошибка устранена.

Важно правильно определять участки кода, которые необходимо комментировать. Если это условие (или иная логическая конструкция), то оно должно комментироваться полностью. Если комментируется участок кода, где объявляются переменные, важно, чтобы не был открыт участок, где происходит обращение к этим переменным. Иначе говоря — комментирование должно применяться по логике программирования. Несоблюдения такого подхода приводит к возникновению новых, вводящих в заблуждение, ошибок при компиляции.

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

Ошибки времени выполнения

Ошибки, возникающие в процессе исполнения кода программы, принято называть ошибками времени выполнения (runtime errors). Такие ошибки обычно зависят от состояния программы и связаны с некорректными значениями переменных.

Например, если переменная используется в качестве индекса элементов массива, то ее отрицательные значения неизбежно приведут к выходу за пределы массива.

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

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

Большинство индикаторов, в которых используется данный способ оптимизации вычислений, имеют такой вид:

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+

int start()
{
    //--- иногда для расчета требуется не менее N баров (например, 100)
    // если на графике нет такого количества баров (например, на таймфрейме MN)
    if (Bars<100) {
        return(-1); // прекращаем расчет и выходим досрочно
    }

    //--- количество баров, не изменившихся с момента последнего вызова индикатора
    int counted_bars=IndicatorCounted();
    //--- в случае ошибки выходим
    if (counted_bars<0) {
        return(-1);
    }
    //--- позиция бара, с которого начинается расчет в цикле
    int limit=Bars-counted_bars;

    //--- если counted_bars=0, то начальную позицию в цикле нужно уменьшить на 1,
    if (counted_bars==0) {
        limit--;  // чтобы не выйти за пределы массива при counted_bars==0
        //--- в расчетах используется смещение на 10 баров вглубь 
        //--- истории, поэтому добавим это смещение при первом расчете
        limit-=10;
    } else {
        //--- индикатор уже рассчитывался ранее, counted_bars>0
        //--- при повторных вызовах увеличим limit на 1, чтобы 
        //--- гарантированно обновлять значения индикатора для последнего бара
        limit++;
    }
    //--- основной цикл расчета
    for (int i=limit; i>0; i--) {
        // используются значения баров, уходящих вглубь истории на 5 и 10
        Buff1[i]=0.5*(Open[i+5]+Close[i+10]) 
    }
}

Часто встречается некорректная обработка случая counted_bars==0 (начальную позицию limit нужно уменьшить на значение, равное 1 + максимальный индекс относительно переменной цикла).

Также следует помнить о том, что в момент исполнения функции start() мы можем обращаться к элементам массивов индикаторных буферов от 0 до Bars()-1. Если есть необходимость работы с массивами, которые не являются индикаторными буферами, то их размер следует увеличить при помощи функции ArrayResize() в соответствии с текущим размером индикаторных буферов. Максимальный индекс элемента для адресации также можно получить вызовом ArraySize() с одним из индикаторных буферов в качестве аргумента.

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

Ошибка «Zero divide» возникает в случае, если при выполнении операции деления делитель оказывается равен нулю:

void OnStart()
{
    int a=0, b=0,c;
    c=a/b;
    Print("c=",c);
}

При выполнении данного скрипта во вкладке «Эксперты» возникает сообщение об ошибке и завершении работы программы:

Обычно такая ошибка возникает в случаях, когда значение делителя определяется значениями каких-либо внешних данных. Например, если анализируются параметры торговли, то величина задействованной маржи оказывается равна 0, если нет открытых ордеров. Другой пример: если анализируемые данные считываются из файла, то в случае его отсутствия нельзя гарантировать корректную работу. По этой причине желательно стараться учитывать подобные случаи и корректно их обрабатывать.

Самый простой способ — проверять делитель перед операцией деления и выводить сообщение о некорректном значении параметра:

void OnStart()
{
    int a=0, b=0,c;
    if (b!=0) {
        c=a/b; Print(c);
    } else {
        Print("Error: b=0"); 
        return; 
    }
}

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

Использование 0 вместо NULL для текущего символа

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

Например, значение технического индикатора Moving Average для текущего символа можно было запрашивать следующим образом:

AlligatorJawsBuffer[i]=iMA(0,0,13,8,MODE_SMMA,PRICE_MEDIAN,i);    // неправильно

В новом компиляторе для указания текущего символа нужно явно указывать NULL:

AlligatorJawsBuffer[i]=iMA(NULL,0,13,8,MODE_SMMA,PRICE_MEDIAN,i); // правильно

Кроме того, текущий символ и период графика можно указать при помощи функций Symbol() и Period().

AlligatorJawsBuffer[i]=iMA(Symbol(),Period(),13,8,MODE_SMMA,PRICE_MEDIAN,i); // правильно

Еще лучше, если вы будете использовать предопределенные переменные _Symbol и _Period – они обрабатываются быстрее:

AlligatorJawsBuffer[i]=iMA(_Symbol,_Period,13,8,MODE_SMMA,PRICE_MEDIAN,i); // правильно

Строки в формате Unicodе и их использование в DLL

Строки представляют собой последовательность символов Unicode. Следует учитывать этот факт и использовать соответствующие функции Windows. Например, при использовании функций библиотеки wininet.dll вместо InternetOpenA() и InternetOpenUrlA() следует вызывать InternetOpenW() и InternetOpenUrlW(). При передаче строк в DLL следует использовать структуру MqlString:

#pragma pack(push,1)
struct MqlString
{
    int      size;       // 32-битное целое, содержит размер распределенного для строки буфера
    LPWSTR   buffer;     // 32-разрядный адрес буфера, содержащего строку
    int      reserved;   // 32-битное целое, зарезервировано, не использовать
};
#pragma pack(pop,1)

Совместное использование файлов

При открытии файлов необходимо явно указывать флаги FILE_SHARE_WRITE и FILE_SHARE_READ для совместного использования.

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

Например, при работе с оффлайновыми графиками требуется явно указывать флаги совместного доступа:

// 1-st change - add share flags
ExtHandle=FileOpenHistory(c_symbol+i_period+".hst",
    FILE_BIN|FILE_WRITE|FILE_SHARE_WRITE|FILE_SHARE_READ);

Особенность преобразования datetime

Следует иметь ввиду, что преобразование типа datetime в строку зависит от режима компиляции:

datetime date=D'2014.03.05 15:46:58';
string str="mydate="+date;
//--- str="mydate=1394034418" - без директивы #property strict
//--- str="mydate=2014.03.05 15:46:58" - с директивой #property strict

Например, попытка работы с файлами, имя которых содержит двоеточие, приведет к ошибке.

Обработка ошибок времени выполнения

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

В наборе «из коробки» доступны некоторые библиотеки для упрощения написания советников, в том числе и для работы с ошибками. Хранятся они в папке MQL4/Include:

Нам понадобятся две библиотеки:

  • stderror.mqh — содержит константы для номера каждой ошибки;
  • stdlib.mqh — содержит несколько вспомогательных функций, в том числе и функцию возврата описания ошибки в виде строки:
string ErrorDescription(int error_code)

Поэтому подключим в наш проект обе эти библиотеки:

#include <stderror.mqh>
#include <stdlib.mqh>

Сами описания ошибок находятся в файле MQL4/Library/stdlib.mql4 и они на английском языке. Поэтому, если вы против иностранных языков, всегда можно переписать описания на свой родной.

Еще одна встроенная необходимая нам функция — GetLastError(). Именно она возвращает коды ошибок в виде целого числа (int), который мы потом будем обрабатывать. Сами коды ошибок и их описания на русском можно посмотреть в руководстве по mql4 от MetaQuotes. Оттуда же можно взять информацию для перевода файла stdlib.mql4 на русский.

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

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

Рассмотрим для примера две типичные для экспертов на MQL4 ошибки:

  1. Ошибка 130 — ERR_INVALID_STOPS
  2. Ошибка 146 — ERR_TRADE_CONTEXT_BUSY

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

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

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

void logError(string functionName, string msg, int errorCode = -1)
{
    Print("ERROR: in " + functionName + "()");
    Print("ERROR: " + msg );

    int err = GetLastError();

    if (errorCode != -1) {
        err = errorCode;
    }

    if (err != ERR_NO_ERROR) {
        Print("ERROR: code=" + err + " - " + ErrorDescription( err ));
    }
}

Использовать ее мы будем следующим образом:

void openLongTrade()
{
    int ticket = OrderSend(Symbol(), OP_BUY, 1.0, Ask + 5, 5, 0, 0);
    if (ticket == -1) {
        logError("openLongTrade", "could not open order");
    }
}

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

Первым параметром в функцию logError() передается имя функции, в которой была обнаружена ошибка, в нашем примере — в функции openLongTrade(). Если наш эксперт вызывает функцию OrderSend() в нескольких местах, это позволит нам точно установить, в каком из них произошла ошибка. Вторым параметром передается описание ошибки, чтобы можно было понять, где именно внутри функции openLongTrade() была обнаружена ошибка. Это может быть как краткое описание ошибки, так и более развернутое, с перечислением значений всех параметров, переданных во встроенную функцию.

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

ERROR: in openLongTrade()
ERROR: could not open order
ERROR: code=138 - requote

То есть сразу будет видно:

  • в какой функции произошла ошибка;
  • к чему она относится (в данном случае — к попытке открыть позицию);
  • какая именно ошибка возникла (код ошибки и ее описание).

Теперь рассмотрим третий, необязательный, параметр функции logError(). Он необходим в тех случаях, когда мы хотим обработать конкретный вид ошибки, а об остальных будем отчитываться в протоколе работы эксперта, как и прежде:

void updateStopLoss(double newStopLoss)
{
    bool modified = OrderModify(OrderTicket(), OrderOpenPrice(), 
        newStopLoss, OrderTakeProfit(), OrderExpiration());
    if (!modified) {
        int errorCode = GetLastError();
        if (errorCode != ERR_NO_RESULT ) {
            logError("updateStopLoss", "failed to modify order", errorCode);
        }
    }
}

Здесь в функции updateStopLoss() вызывается встроенная функция OrderModify(). Эта функция несколько отличается в плане обработки ошибок от OrderSend(). Если ни один из параметров изменяемого ордера не отличается от его текущих параметров, то функция вернет ошибку ERR_NO_RESULT. Если в нашем эксперте такая ситуация допустима, то мы должны игнорировать конкретно эту ошибку. Для этого мы анализируем значение, возвращаемое GetLastError(). Если произошла ошибка с кодом ERR_NO_RESULT, то мы ничего не выводим в протокол.

Однако если произошла другая ошибка, то необходимо полностью отрапортовать о ней, как мы делали это раньше. Именно для этого мы сохраняем результат функции GetLastError() в промежуточной переменной и передаем его третьим параметром в функцию logError(). Дело в том, что встроенная функция GetLastError() автоматически обнуляет код последней ошибки после своего вызова. Если бы мы не передали код ошибки явно в logError(), то в протоколе была бы отражена ошибка с кодом 0 и описанием «no error».

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

Диагностика логических ошибок

Логические ошибки в коде эксперта могут доставить много проблем. Отсутствие возможности пошаговой отладки экспертов делают борьбу с такими ошибками не очень приятным занятием. Основным средством для диагностики этого на данный момент является встроенная функция Print(). С ее помощью можно выполнять распечатку текущих значений важных переменных, а также протоколировать ход работы эксперта прямо в терминале во время тестирования. При отладке эксперта во время тестирования с визуализацией также может помочь встроенная функция Comment(), которая выводит сообщения на график. Как правило, убедившись, что эксперт работает не так, как было задумано, приходится добавлять временные вызовы функции Print() и протоколировать внутреннее состояние эксперта в предполагаемых местах возникновения ошибки.

Однако, для обнаружения сложных ошибочных ситуаций порой приходится добавлять десятки таких вызовов функции Print(), а после обнаружения и устранения проблемы их приходится удалять или комментировать, чтобы не загромождался код эксперта и не замедлялось его тестирование. Ситуация ухудшается, если в коде эксперта функция Print() уже используется для периодического протоколирования различных состояний. Тогда удаление временных вызовов Print() не удается выполнить путем простого поиска фразы ‘Print’ в коде эксперта. Приходится задумываться, чтобы не удалить еще и полезные вызовы этой функции.

Например, при протоколировании ошибок функций OrderSend(), OrderModify() и OrderClose() полезным бывает печатать в протокол текущее значение переменных Bid и Ask. Это несколько облегчает распознавание причин таких ошибок, как ERR_INVALID_STOPS и ERR_OFF_QUOTES.

Чтобы выделить такие диагностические выводы в протокол, я рекомендую использовать такую вспомогательную функцию:

void logInfo(string msg)
{
    Print("INFO: " + msg);
}

Это желательно сделать по нескольким причинам. Во-первых, теперь такие вызовы не будут попадаться при поиске ‘Print’ в коде эксперта, ведь искать мы будем logInfo. Во-вторых, у этой функции есть еще одна полезная особенность, о которой мы поговорим чуть позже.

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

void openLongTrade(double stopLoss)
{
    int ticket = OrderSend(Symbol(), OP_BUY, 1.0, Ask, 5, stopLoss, 0);
    if (ticket == -1) {
        logError("openLongTrade", "could not open order");
    }
}

В данном случае, так как мы открываем длинную позицию, совершенно очевидно, что при нормальной работе эксперта значение параметра stopLoss никогда не будет больше или равно текущей цене Bid. То есть, при вызове функции openLongTrade() всегда выполняется условие stopLoss < Bid. Так как мы знаем об этом еще на этапе написания рассматриваемой функции, то мы сразу же можем этим воспользоваться следующим образом:

void openLongTrade( double stopLoss )
{
    assert("openLongTrade", stopLoss < Bid, "stopLoss < Bid");
 
    int ticket = OrderSend(Symbol(), OP_BUY, 1.0, Ask, 5, stopLoss, 0);
    if (ticket == -1) {
        logError("openLongTrade", "could not open order");
    }
}

То есть мы логируем наше утверждение в коде при помощи новой вспомогательной функции assert(). Сама функция выглядит довольно просто:

void assert(string functionName, bool assertion, string description = "")
{
    if (!assertion) {
        Print("ASSERT: in " + functionName + "() - " + description);
    }
}

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

  • название функции, в которой условие было нарушено;
  • описание нарушенного условия.

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

Конечно же, рассмотренный пример максимально упрощен. Но, надеюсь, основную идею отражает достаточно хорошо. В процессе наращивания функциональности эксперта мы отдаем себе отчет в том, как он должен работать и какие состояния и входные параметры функций допустимы, а какие нет. Фиксируя это в коде эксперта при помощи функции assert() мы получаем ценную информацию о месте, в котором нарушается логика работы эксперта. Более того, мы частично избавляем себя от необходимости добавлять и удалять временные вызовы функции Print(), так как функция assert() выдает диагностические сообщения в протокол только в момент обнаружения несоответствий в ожидаемых нами условиях.

Еще одним полезным приемом является использование этой функции перед каждой операцией деления. Дело в том, что иногда в результате той или иной логической ошибки иногда происходит деление на ноль. Работа эксперта в этом случае прекращается, а в протоколе появляется одна лишь строка с печальным диагнозом: ‘zero divide’. Узнать, в каком именно месте произошла эта ошибка, если операция деления используется в коде неоднократно, достаточно сложно. Вот здесь и поможет функция assert(). Вставляем соответствующие проверки перед каждой операцией деления:

assert("buildChannel", distance > 0, "distance > 0");
double slope = delta / distance;

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

Обработка состояний

Во время работы эксперта на вашем счете могут возникнуть некоторые ситуации, которые не являются ошибками – так называемые состояния эксперта. Такие состояния не являются ошибками, но все же их стоит логировать. В этом помогают специальные функции языка mql4.

Функция IsExpertEnabled() возвращает информацию о возможности запуска экспертов. Функция вернет true, если в клиентском терминале разрешен запуск экспертов, иначе возвращает false. В случае возврата false полезно будет известить об этом пользователя с просьбой включить соответствующую настройку. Пример:

void OnStart()
{
    if (!IsExpertEnabled() {
        //советникам не разрешено торговать
        Alert("Attention! Please press the "Expert Advisors" button in MT4");
    }
    //рабочий алгоритм советника
    return;
}

Если эксперт использует внешние библиотеки, пригодится функция IsLibrariesAllowed(). Она возвращает true, если эксперт может вызвать библиотечную функцию, иначе возвращает false.

Если библиотека в виде dll файла, пригодится функция IsDllsAllowed(). Также нелишним будет проверить, есть ли вообще возможность торговать при помощи экспертов с помощью функции IsTradeAllowed().

Если вы хотите узнать, является ли счет демонстрационным, или же реальным, можно использовать функцию IsDemo().

Все вышеперечисленные проверки стоит сделать в функции OnInit().

Конечно же, стоит проверять периодически связь с сервером. В этом поможет функция IsConnected().

Следующие три функции помогут определить, в каком режиме находится советник. Если IsOptimisation() возвращает true, проводится оптимизация, если IsTesting(), то тестирование, IsVisualMode() – тестирование в режиме визуализации. Под каждый из этих вариантов в советнике может быть предусмотрена своя логика. Например, для режима визуализации можно что-то выводить на график (и не выводить в других режимах ради экономии ресурсов). В режиме тестирования можно выводить отладочную информацию, в режиме оптимизации максимально облегчить код, чтобы сэкономить время.

И последняя функция – IsTradeContextBusy(). Она вернет true, если поток для выполнения торговых операций занят. Это бывает полезно при совершении экспертом торговых операций. Можно применить функцию Sleep для ожидания некоторого момента и новой попытки.

Еще одна полезная функция — UninitializeReason()

int deinit()
{
    switch(UninitializeReason())
    {
        case REASON_CHARTCLOSE:
        case REASON_REMOVE:      
            CleanUp(); 
            break;    // очистка и освобождение ресурсов.
        case REASON_RECOMPILE:
        case REASON_CHARTCHANGE:
        case REASON_PARAMETERS:
        case REASON_ACCOUNT:     
            StoreData(); 
            break;  // подготовка к рестарту.
    }
    //...
}

Можно также логировать причину выхода советника.

Коды самых распространенных ошибок и их вероятное решение

№ ошибки Значение Проблема Решение
4, 146 Торговый сервер занят Советник подал слишком много приказов одновременно или не дождавшись ответа от сервера, при выполнении операции — советник пытается отправить новый приказ Перезагрузка терминала или оптимизация кода советника с помощью функций обработки ошибок
8, 141 Слишком частые запросы Предыдущие причины ошибки, в сильно частом запросе Аналогичное решение
129 Неправильная цена Цена по которой Вы пытаетесь открыть позицию (BUY или SELL) неправильная BUY нужно открывать по Ask а закрывать по BID;
SELL нужно открывать по BID а закрывать по ASK
130, 145 Неправильные стопы Стоп лосс, тейк профит или уровень открытия отложки или лимитника неверные.
Стопы расположены слишком близко к цене.
Ваш счет открыт в группе ECN (ЕЦН) или NDD (НДД), что не дает сразу выставлять стопы
Проверьте значения Ваших стоп лоссов, тейк профитов, уточните минимальный стоп уровень по Вашему инструменту у брокера, при выставлении стопов — соблюдайте уровень минимальной дистанции. В хорошо написанном советнике должны быть функции работы на счетах ECN и NDD – это происходит путем модификации ордера уже после его открытия
131 Неправильный объем Неправильный лот при открытии сделки, или меньше минимального (больше максимального). Разрядность лота тоже может отличаться от разрядности брокера Проверьте правильность открытия лота, изучите спецификацию контракта и прочтите условия торговли в Вашем ДЦ, проверьте минимальный и максимальный лот в Вашем ДЦ и на Вашем счете
132 Рынок закрыт Рынок закрыт на выходные дни Пробуйте связаться с рынком после выходных
133 Торговля запрещена В данный момент торговля запрещена По данной валютной паре запрещено торговать – в конкретный момент времени или вообще. Часто у брокеров есть перерыв в несколько минут в полночь
134 Недостаточно денег для совершения операции Лот, который Вы пытаетесь открыть, слишком большой, на него не хватает маржи Проверьте уровень свободных средств и рассчитайте средства, которые Вам нужны для открытия лота, следите за уровнем Ваших свободных средств
135-138 Цена изменилась Реквот, слишком быстрый рынок (новости), Брокер или ДЦ не дает Вам поставить позицию по заявленной цене Не торгуйте в такие моменты, увеличьте уровень проскальзывания, но помните, что это влечет за собой открытие позиций не по заявленной Вами цене. Предусмотрите в советнике функцию обработки ошибок и количество попыток открытия позиций
147 Использование даты истечения ордера запрещено брокером Ваш советник или Вы пытаетесь установить срок истечения отложенного ордера В советнике, в функции OrderSend в параметре срок истечения поставьте 0 (ноль). Не устанавливайте срок истечения ордера
148 Количество открытых и отложенных ордеров достигло предела, установленного брокером Максимальное количество открытых ордеров и позиций достигло предела, установленного брокером Удалите или закройте часть позиций. Остановите процесс открытия новых позиций
4012, 4013 Остаток от деления на ноль Вы пытаетесь поделить число на 0 (ноль) Проверьте код советника на наличие ошибки, или же проверьте все значения из MarketInfo функций на момент возвращения 0, иногда при MarketInfo(Symbol(),MODE_SPREAD) возвращается не спред, а 0 (у брокеров с плавающим спредом)
4017 Вызовы DLL не разрешены В Вашем терминале запрещен вызов DLL Разрешите вызов DLL через Меню – сервис – Настройки – Советник – Разрешить вызов DLL
4018, 4019 Невозможно загрузить библиотеку Библиотека повреждена или ее вызов завершается с ошибкой, возможно она вообще отсутствует Проверьте библиотеку DLL
4020 Вызовы внешних библиотечных функций не разрешены В Вашем терминале запрещен вызов функций из внешних экспертов Разрешите вызов функций через Меню – сервис – Настройки – Советник – Разрешить вызов внешних экспертов
4103 Невозможно открыть файл Данный файл не существует или заблокирован другим процессом Проверьте наличие указанного файла. Проверьте, не заблокирован ли файл системой антивируса, разрешен ли режим записи-чтения файла
4106 Неизвестный символ Символа нет в обзоре рынка В обзоре рынка – правой кнопкой мыши – показать все символы. Проверить названия символа в советнике и наличие его в обзоре рынка. Некоторые советники используют четкие названия без суффиксов, а брокеры намеренно ставят суффиксы, например EURUSDx где х – суффикс
4108 Неверный номер тикета Тикет ордера, который выбирает эксперт – не существует. Эксперт пытается выбрать тикет, но данный ордер был закрыт другим советником или руками. При попытке осуществления приказа над ордером, тикет был исполнен и закрыт брокером Если данная ошибка появляется очень часто, 100-1000 раз за минуту, проверьте функции Вашего советника. Отключите другие советники или настройте их так, чтобы они не конфликтовали, не закрывайте ордер руками, когда эксперт выполняет операцию. Иногда такое случается, когда несколько советников используют одинаковый MagicNumber
4109 Торговля не разрешена Советнику запрещено торговать, на графике грустный смайл или крестик Включите галочку «Разрешить советнику торговать» во вкладе при установке советника, либо в меню — сервис – настройки – советники
4110, 4111 Длинные/короткие позиции не разрешены В настройках советника, во вкладке Общие не разрешен тип позиций Во вкладке Общие, при установке советника, есть выбор позиций, разрешенных к открытию

Заключение

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

Тема на форуме

С уважением, Дмитрий аkа Silentspec
TradeLikeaPro.ru

Pages 1

You must login or register to post a reply

1 2020-01-19 10:14:59 (edited by Roughey 2020-01-19 10:16:41)

  • Roughey
  • Licensed Member
  • Offline
  • Registered: 2018-12-16
  • Posts: 309

Topic: Array out of range in ‘Portfolio Expert EURUSD H1.mq4’ (592,16)

Hi,

now i use Popov Datafeed. All looks nice. I have data from 2007 to 2020.

Now when i test in MT4 i get this error.

2020.01.19 10:08:29.790    2017.01.02 00:00:00  Portfolio Expert EURUSD H1 EURUSD,H1: array out of range in 'Portfolio Expert EURUSD H1.mq4' (592,16)

It is this line of code in Portfolio.

//+------------------------------------------------------------------+
double Close(int bar)
  {
   return Close[bar];
  }

When i change my testing data time between x and x nothing happen.

What is this and how can i resolve this.

The Close is needed in a strategie with this code.

   {
      int consecutiveBullish = 0;
      int consecutiveBearish = 0;
      double pipVal = pip * 8;

      for (int b = 1 + 2; b > 0; b--)
        {
         consecutiveBullish = Close(b) - Open(b) >= pipVal ? consecutiveBullish + 1 : 0;
         consecutiveBearish = Open(b) - Close(b) >= pipVal ? consecutiveBearish + 1 : 0;
        }

      ind2long  = consecutiveBullish >= 1;
      ind2short = consecutiveBearish >= 1;
   }

It is something with the Indicator Candle Color.

2 Reply by Popov 2020-01-19 18:04:43

  • Popov
  • Popov
  • Lead Developer
  • Offline
  • From: Bulgaria
  • Registered: 2006-11-30
  • Posts: 7,777

Re: Array out of range in ‘Portfolio Expert EURUSD H1.mq4’ (592,16)

Hello Roughey,

I’m in the Netherlands now and have only a Linux laptop with me. Please try to find a solution and post an update.

I’ll be back in Bulgaria on 27th of January and will be able to make tests.

Anyway, I’ll try to find time to post further updates.

Trade Safe!

3 Reply by Roughey 2020-01-21 18:28:38

  • Roughey
  • Licensed Member
  • Offline
  • Registered: 2018-12-16
  • Posts: 309

Re: Array out of range in ‘Portfolio Expert EURUSD H1.mq4’ (592,16)

i didnt get it work.

4 Reply by Roughey 2020-01-29 17:15:47

  • Roughey
  • Licensed Member
  • Offline
  • Registered: 2018-12-16
  • Posts: 309

Re: Array out of range in ‘Portfolio Expert EURUSD H1.mq4’ (592,16)

can someone check if have same problem with candle color indicator

5 Reply by Popov 2020-01-31 06:44:55

  • Popov
  • Popov
  • Lead Developer
  • Offline
  • From: Bulgaria
  • Registered: 2006-11-30
  • Posts: 7,777

Re: Array out of range in ‘Portfolio Expert EURUSD H1.mq4’ (592,16)

I tested an expert with Candle Color and it works fine.

Please upload your expert for further analysis. It can be another reason and we must find and fix it.

6 Reply by Roughey 2020-02-16 16:54:28

  • Roughey
  • Licensed Member
  • Offline
  • Registered: 2018-12-16
  • Posts: 309

Re: Array out of range in ‘Portfolio Expert EURUSD H1.mq4’ (592,16)

Popov wrote:

I tested an expert with Candle Color and it works fine.

Please upload your expert for further analysis. It can be another reason and we must find and fix it.

Hi,

i have now checked a little. It must be something with Open Prices Only test and Tick data test.

When i use Tickdata test than i get the out of range error when i use open prices only it is working.

But why it is ? Can you check . Here the Ea.  Use date from 02.01.2015 — 31.01.2020

Post’s attachments

Portfolio Expert EURUSD H1.mq4 244.67 kb, 14 downloads since 2020-02-16 

You don’t have the permssions to download the attachments of this post.

7 Reply by Roughey 2020-02-27 18:32:07

  • Roughey
  • Licensed Member
  • Offline
  • Registered: 2018-12-16
  • Posts: 309

Re: Array out of range in ‘Portfolio Expert EURUSD H1.mq4’ (592,16)

any idea about that?

8 Reply by Popov 2020-03-05 16:29:48

  • Popov
  • Popov
  • Lead Developer
  • Offline
  • From: Bulgaria
  • Registered: 2006-11-30
  • Posts: 7,777

Re: Array out of range in ‘Portfolio Expert EURUSD H1.mq4’ (592,16)

I tested the attached Portfolio Expert on «every tick» and found it working fine.

https://image-holder.forexsb.com/store/portfolio-ea-tick-testing-found-ok-thumb.png

It must be something of how MT generates the ticks. Generally, you don’t need to use «every tick» in MT with EA Studio experts. Please check if you have similar results when you use the «bar open» or the «control points» methods with EA Studio.

9 Reply by bobstar 2021-03-17 17:41:28 (edited by bobstar 2021-03-17 22:29:41)

  • bobstar
  • bobstar
  • New member
  • Offline
  • Registered: 2020-05-10
  • Posts: 6

Re: Array out of range in ‘Portfolio Expert EURUSD H1.mq4’ (592,16)

This bug can happen when you start the backtest at the exact beginning of a historical data, and for some strategies requires an index that go outside the array size (for these Time[], Open[], High[], Low[], Close[]).
I solved and fixed this bug by modyfing the code like that:

datetime Time(int bar){ if(bar >= ArraySize(Time)) return 0; return Time[bar];}
double Open(int bar){   if(bar >= ArraySize(Open)) return 0; return Open[bar];}
double High(int bar){    if(bar >= ArraySize(High))  return 0; return High[bar];}
double Low(int bar){     if(bar >= ArraySize(Low))   return 0; return Low[bar];}
double Close(int bar){   if(bar >= ArraySize(Close)) return 0; return Close[bar];}

@Popov I thinnk you can modify this code in the current MQL template, in order to fix this bug with that solution.

10 Reply by Popov 2021-03-17 17:55:02

  • Popov
  • Popov
  • Lead Developer
  • Offline
  • From: Bulgaria
  • Registered: 2006-11-30
  • Posts: 7,777

Re: Array out of range in ‘Portfolio Expert EURUSD H1.mq4’ (592,16)

How you can start the backtest from the first bar?
Usually, MetaTrader starts from bar number 100.

Please check if you have any data. Print the size of the data arrays.
Is it possible to require more bars than you have?

11 Reply by bobstar 2021-03-17 18:19:56 (edited by bobstar 2021-03-17 18:24:35)

  • bobstar
  • bobstar
  • New member
  • Offline
  • Registered: 2020-05-10
  • Posts: 6

Re: Array out of range in ‘Portfolio Expert EURUSD H1.mq4’ (592,16)

Yes, it’s possible to start the backtest from the first bar (when you download data from Tickstory for example or other third-party providers), in this case when the backtest starts at the beginning of downloaded datas, it doesn’t show any previous bar and therefore the size of these standard arrays (Time[], Open[], etc…) are limited to the actual bars.
With some strategies, that uses these values, this can happen.

@Popov here a screenshot where I made a Print of the value of the ArraySize of Time and it shows also the number of Bars on the chart (that of course are equal to ArraySize(Time).

PunBB bbcode test

Post’s attachments

solution for Array out of range.jpg 194.96 kb, file has never been downloaded. 

You don’t have the permssions to download the attachments of this post.

12 Reply by Popov 2021-03-17 21:20:00

  • Popov
  • Popov
  • Lead Developer
  • Offline
  • From: Bulgaria
  • Registered: 2006-11-30
  • Posts: 7,777

Re: Array out of range in ‘Portfolio Expert EURUSD H1.mq4’ (592,16)

In that case, the better solution is to set a starting date of the backtest. Let’s say 2020-01-03.

What is the purpose of importing data in MetaTrader?

The backtest on third party data may not correspond to the following real trading.

13 Reply by bobstar 2021-03-17 22:13:40 (edited by bobstar 2021-03-17 22:15:00)

  • bobstar
  • bobstar
  • New member
  • Offline
  • Registered: 2020-05-10
  • Posts: 6

Re: Array out of range in ‘Portfolio Expert EURUSD H1.mq4’ (592,16)

The purpose of importing data into MT4 is to make high quality backtests from tick by tick data; MT4 by default uses the global history data that are not good for tick by tick backtests, instead Tickstory or Tickdata Suite are optimal choices for professional backtest and these data are taken from Dukascopy database (@Popov the same provider that you are using for Premium Data on EA Studio).

It’s possible to overcome these «Array out of range» problems by starting the backtest 1 or 2 days later, but @Popov you know that «Array out of range» is not a problem from a backtest but from a source code and needs to be managed properly.

Like I told you, a possible solution is to put a simple condition before accessing to the actual array, like that:

datetime Time(int bar){
    if(bar >= ArraySize(Time)) return 0;
    return Time[bar];
}

instead of

datetime Time(int bar){
    return Time[bar];
}

Think about that, maybe for the next release.
Thank you @Popov

14 Reply by Popov 2021-03-17 23:05:17

  • Popov
  • Popov
  • Lead Developer
  • Offline
  • From: Bulgaria
  • Registered: 2006-11-30
  • Posts: 7,777

Re: Array out of range in ‘Portfolio Expert EURUSD H1.mq4’ (592,16)

A better solution would be to skip all the calculations until we have enough bars.

Something like:

const int requiredBars = 100;

void OnTick()
{
    if ( ArraySize(Time) < requiredBars ) return;

    ...
}

requiredBars can be set from EA Studio on export depending on the strategy.

15 Reply by bobstar 2021-03-17 23:33:38

  • bobstar
  • bobstar
  • New member
  • Offline
  • Registered: 2020-05-10
  • Posts: 6

Re: Array out of range in ‘Portfolio Expert EURUSD H1.mq4’ (592,16)

yes, it’s a solution

16 Reply by Popov 2021-03-18 00:16:37

  • Popov
  • Popov
  • Lead Developer
  • Offline
  • From: Bulgaria
  • Registered: 2006-11-30
  • Posts: 7,777

Re: Array out of range in ‘Portfolio Expert EURUSD H1.mq4’ (592,16)

Done.

Please reload EA Studio with Ctrl + F5 and try.

17 Reply by bobstar 2021-03-18 12:07:34 (edited by bobstar 2021-03-18 12:07:56)

  • bobstar
  • bobstar
  • New member
  • Offline
  • Registered: 2020-05-10
  • Posts: 6

Re: Array out of range in ‘Portfolio Expert EURUSD H1.mq4’ (592,16)

@Popov, you forgot to define the macro REQUIRED_BARS, in fact it doesn’t compile because of this error (both on MQL for single EA and Portfolio)

Post’s attachments

error macro.jpg
error macro.jpg 120.15 kb, 2 downloads since 2021-03-18 

You don’t have the permssions to download the attachments of this post.

18 Reply by Popov 2021-03-18 12:46:49

  • Popov
  • Popov
  • Lead Developer
  • Offline
  • From: Bulgaria
  • Registered: 2006-11-30
  • Posts: 7,777

Re: Array out of range in ‘Portfolio Expert EURUSD H1.mq4’ (592,16)

Please reload EA Studio with Ctrl + F5 and try.

But you are right. There are many users that don’t reload EA Studio for weeks. So I changed the MQL to have requiredBars = 100 by default.

19 Reply by bobstar 2021-03-18 13:03:50

  • bobstar
  • bobstar
  • New member
  • Offline
  • Registered: 2020-05-10
  • Posts: 6

Re: Array out of range in ‘Portfolio Expert EURUSD H1.mq4’ (592,16)

all right

Pages 1

You must login or register to post a reply

Similar topics in this forum

Понравилась статья? Поделить с друзьями:
  • Ошибка audi 01304
  • Ошибка arizona mobile
  • Ошибка argument out of range
  • Ошибка argc is error
  • Ошибка audi 01043