Введение
При использовании новой версии компилятора языка MQL4 некоторые старые программы могут выдавать ошибки.
В старой версии компилятора во избежание критического завершения программ многие ошибки обрабатывались средой исполнения и не приводили к остановке работы. Например, деление на ноль или выход за пределы массива являются критическими ошибками и обычно приводят к аварийному завершению. Проявляются такие ошибки лишь в некоторых состояниях при определенных значениях переменных, однако о таких ситуациях следует знать и корректно их обрабатывать.
Новый компилятор позволяет обнаружить реальные или потенциальные источники ошибок и повысить качество кода.
В этой статье мы рассмотрим возможные ошибки, возникающие при компиляции старых программ, и методы их устранения.
- Ошибки компиляции
- 1.1. Идентификатор совпадает с зарезервированным словом
- 1.2. Специальные символы в наименованиях переменных и функций
- 1.3. Ошибки использования оператора switch
- 1.4. Возвращаемые значения у функций
- 1.5. Массивы в аргументах функций
- Ошибки времени выполнения
- 2.1. Выход за пределы массива (Array out of range)
- 2.2. Деление на ноль (Zero divide)
- 2.3. Использование 0 вместо NULL для текущего символа
- 2.4. Строки в формате Unicodе и их использование в DLL
- 2.5. Совместное использование файлов
- 2.6. Особенность преобразования datetime
- Предупреждения компилятора
- 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.2. Специальные символы в наименованиях переменных и функций
Если наименования переменных или функций содержат специальные символы ($, @, точка):
int $var1; int @var2; int var.3; void f@() { return; }
то компилятор выводит сообщения об ошибках:
Рис.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»
В таких случаях можно использовать явные сравнения численных значений, например:
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»
В режиме компиляции по умолчанию компилятор выводит предупреждение:
Рис.5. Предупреждение «not all control paths return a value»
Если возвращаемое значение функции не соответствует объявлению:
int init() { return; }
то при строгом режиме компиляции возникает ошибка:
Рис.6. Ошибка «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»
В режиме компиляции по умолчанию компилятор выводит предупреждение:
Рис.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
Для устранения подобных ошибок нужно скопировать необходимые данные из константного массива:
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»
Обычно такая ошибка возникает в случаях, когда значение делителя определяется значениями каких-либо внешних данных. Например, если анализируются параметры торговли, то величина задействованной маржи оказывается равна 0 если нет открытых ордеров. Другой пример: если анализируемые данные считываются из файла, то в случае его отсутствия нельзя гарантировать корректную работу. По этой причине желательно стараться учитывать подобные случаи и корректно их обрабатывать.
Самый простой способ — проверять делитель перед операцией деления и выводить сообщение об некорректном значении параметра:
void OnStart() { int a=0, b=0,c; if(b!=0) {c=a/b; Print(c);} else {Print("Error: b=0"); return; }; }
В результате критической ошибки не возникает, но выводится сообщение о некорректном значении параметра и работа завершается:
Рис. 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 %»
Для исправления таких предупреждений нужно скорректировать имена глобальных переменных.
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’
В данном примере компилятор предупреждает о возможной потере точности при присвоении различных типов данных и неявном преобразовании типа 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’
Для исправления нужно убрать неиспользуемые переменные из кода программы.
Выводы
В статье рассмотрены типичные проблемы, с которыми могут столкнуться программисты при компиляции старых программ, содержащих ошибки.
Во всех случаях при отладке программ рекомендуется использовать строгий режим компиляции.
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
-
#1
Решил вот создать тему. Как-то хотел выучить MQL4, прочел весь справочник и видеоуроки смотрел. Но времени, чтобы практикаваться как-то пожалел. Но польза всё таки была и мало-мальскими знаниями я овладел. Думаю будет полезно, за одно и сам чего-нибудь нового узнаю. Глядишь и нормально разбираться начну
-
#2
Ошибки со старых билдов.
Исправляем ошибку char .
1) Попался индикатор под названием @Ratio_MACD_v3.mq4
2) Кидаем его на график и видим ошибку во вкладке «Эксперты»
3) В навигаторе щелкаем правой кнопкой мыши по нашему индикатору и выбираем «Изменить»,щёлкаем левой кнопкой мыши.
4) После этого у нас откроется MetaEditor — это программка предназначена для написания, редактирования кода.
5) Нажимем кнопку «Компилировать»
6) Внизу во вкладке «Ошибки» увидим следующую картину
7) Наводим курсор на первую ошибку и два раза щелкаем левой кнопкой мыши. При этом мы переместимся на строку этой ошибки.
Если хотим узнать, почему возникла эта ошибк, нажимаем F1. При нажатии этой клавиши открывается справочник, где в нашем случае мы увидим следующее.
Из этого следует, что (так скажем) слово char выражает, показывает нам какой тип данных перед нами. Иными словами, это слово занято программой и его использовать нельзя.
Вот что по этому поводу говорят на сайте самого mql
h_ttps://www.mql5.com/ru/articles/1391 (уберите черточку после h, чтобы перейти на сайт)
9) Меняем это слово на любое другое, или просто добавляем какой-нибудь символ к нему.
На картинке выше, добавили 1. Я обычно добавляю букву p.
При этом слово становится черного цвета, а не синего как раньше.
В нашем примере всего 9 слов надо исправить.
10) Снова нажимаем кнопку компилировать. Внизу видим, что никаких ошибок нет.
11) Закрываем метаэдитор. Заходим в терминал, кидаем индикатор на график и видим что он работает.
Последнее редактирование модератором:
-
#3
Нерабочий вариант индикатора.
Чтобы работал, надо исправить как описано выше.
-
@Ratio_MACD_v3.mq4
9,5 КБ · Просмотры: 28
-
#4
Перед тем как использовать переменную нужно её объявить (захвалить как в игре 1000).
При объявлении надо указать тип и имя переменной, можно сразу присвоить значение.
Типы переменных заданы в языке и определяет свойство переменной (диапазон значений и точность), имя сочиняет программист на своё усмотрение.
Формат: тип имя; или тип имя=значение. Например: int peremennaya=0;
Здесь int тип переменной. Означает что она целочисленная и её значения от -2 147 483 648 до 2 147 483 647. Это можно посмотреть в справочнике. peremennaya это имя переменной. =0 присвоенное значение.
char то же тип целочисленной переменной и её значения от -128 до 127.
Проблема оказалась в том что нельзя в качестве имени переменной использовать тип переменной. В старом языке не было такого типа как char, по этому программист спокойно использовал это имя переменной. В ново языке такое имя использовать нельзя. Именно по этому переименование этих переменных помогло.
-
#5
В силу разных причин основная масса подходит к штанге, пробует и не поднимает
Перед купанием многие стоят в воде
Простые надобности на примерах
1. Ошибки со старых билдов
2. Толщина и цвет линии
3. Ограничение истории
4. Выставление стрелки и отступ
5. Вывод в настройки
6. Как найти буфер сигнала
7. Сообщения
8. Вывод сигнала или обьединение в другой индюк или сов
Это навскидку
Мона дополнять
-
#6
При изучении любого языка программирования, на начальном этапе, очень полезно решать простейшие задачки.
Напишите скрипт который выводит все числа от 1 до 1000, которые отвечают хотя бы одному условию.
- Число четное
- Число оканчивается цифрой 1 (пример 771).
-
#7
При изучении любого языка программирования, на начальном этапе, очень полезно решать простейшие задачки.
Напишите скрипт который выводит все числа от 1 до 1000, которые отвечают хотя бы одному условию.
- Число четное
- Число оканчивается цифрой 1 (пример 771).
Да все это хорошо и по карйней мере мне интересно. И я благодарен всем кто участвует.
Но как мне кажется, вот эти задачки надо все же перекинуть в тему вопросы по MQL4. А тут оставить конкретные примеры,ситуации, которые возникают у большинства обычных пользователей, как предложил Dersu.
Оставить только посты по делу, а то темы разрастаются, как снежный ком, где чтобы найти нужную инфу, нужно перечитать кучу страниц.
-
#8
Да все это хорошо и по карйней мере мне интересно. И я благодарен всем кто участвует.
Но как мне кажется, вот эти задачки надо все же перекинуть в тему вопросы по MQL4. А тут оставить конкретные примеры,ситуации, которые возникают у большинства обычных пользователей, как предложил Dersu.Оставить только посты по делу, а то темы разрастаются, как снежный ком, где чтобы найти нужную инфу, нужно перечитать кучу страниц.
Хорошо.
Начинающих изучение языка программирования MQL4, приглашаю в специально созданную ветку для обучения.
-
#9
Ошибки со старых билодов.
Ошибка
‘.’ — semicolon expected — другими словами, недопустимый символ.
1) Открываем индикатор в метаэдиторе, нажимаем «компилировать», видим в окне ошибки. Щелкаем по первой два раза левой кнопкой мыши, попадаем на строку ошибки.
2) Выделяем точку в этой строке, нажимаем «поиск» — «заменить».
3) Откроется окошко. Где в поле «Найти» будет вписана точка, в поле «Заменить на» вписываем какой-либо символ кроме запрещенных $ @ . Нажимаем «Заменить».
Так заменяем точку в строках 40, 43-48
Как вариант, можно вообще просто удалить точку в этих строках, но с заменой быстрее.
4) Далее курсор перейдет на строку, где точка находится между цифрами.
Там и далее пропускаем замену, нажав на кнопку «Найти далее».
5) Далее делаем замену во всех предложенных случаях, в строках от 170 до 227.
6) Все последующие строки, где точка является частью дробного числа, пропускаем.
Просматриваем точки до конца кода, после этого курсор перейдет на начало документа.
7) Нажимаем «Компилировать», если увидим такую картину, то все сделали правильно.
Желтый треугольник это предупреждение, и оно не является критической ошибкой, его можно пропустить.
Если там все еще есть ошибки, то значит что-то пропустили. Исправляем.
9) Закрываем редактор и пользуемся индикатором.
Нерабочий индикатор прикрепляю.
-
FXi 3 Semafor.mq4
11,2 КБ · Просмотры: 22
Последнее редактирование модератором:
-
#10
Например вот здесь , точно такая же ошибка с точкой, о которой написал выше.
-
#11
В данном примере лучше использовать замену Bars.Count на Bars_Count, чтобы не попадать на точки в числах.
В поле «Найти» вставляем Bars.Count, а в поле «Заменить на» вставляем Bars_Count, это удобнее.
-
#12
В данном примере лучше использовать замену Bars.Count на Bars_Count, чтобы не попадать на точки в числах.
В поле «Найти» вставляем Bars.Count, а в поле «Заменить на» вставляем Bars_Count, это удобнее.
Там дело в том, что не только в Bars.Count надо заменить точку, там разных имен еще штук 5.
Чтоб не запутаться где надо,а где нет. Можно щелкать по каждой ошибке в окне ошибок и исправлять по отдельности. Но долго)
-
#13
Там дело в том, что не только в Bars.Count надо заменить точку, там разных имен еще штук 5.
Чтоб не запутаться где надо,а где нет. Можно щелкать по каждой ошибке в окне ошибок и исправлять по отдельности. Но долго)
В этих вещах всегда нужно искать золотую середину.
-
#14
Нельзя бездумно уничтожать точки. Так можно не исправить, а натворить ошибок. Точки могут не только разделять дробную часть числа, при использовании структур и классов то же точки. При том в структурах они похожи на неправильную переменную. Например in.dist может быть переменной из старого языка, новый компилятор будет ругаться, а может быть элементом структуры in из нового языка и компилятор не будет ругаться. По этому, надо обязательно ориентироваться на компилятор пока не знаете как отличить.
-
#15
Ограничение истории (подвал, гистограмма)
В теме «Доработка ботов» попросили добавить историю отображения.
Попробовал разобраться, именно в этом индикаторе, сделать это оказалось просто.
Но думаю, не все случаи такие простые. Так как я до этого хотел попробовать сделать это в индикаторе, который выложил Dersu и я не додумался, как это делается.
1) Открываем индикатор в метаэдиторе и находим выведенные настройки индикатора. Они начинаются со слова extern (англ. «внешний»)
2) Так как нам тоже надо вывести историю в настройки, мы пишем строку
extern (выводим во внешние настройки)
int (так как количество баров у нас целое число)
CountBars (задаем имя для выводимого параметра, оно и будет видно в настройках)
3) Идем ниже, находим функцию int start ,
(эта функция осталась в индикаторах написанных раньше, на старых билдах.На новыйх функция int OnInit() )
4) И меняем переменную Bars (количество баров на текущем графике) на имя, которое мы задали в строке 33. Меняем его только в выражении Bars-counted_bars. Компилируем.
Вообще я сам только разбираюсь в этих делах. И если где-то накосячил, прошу товарищей поопытнее поправить. И может они приведут примеры добавления этой функции в других типах индикаторов.
Последнее редактирование:
-
#16
Ошибки со старых билдов.
По умолчанию не отображаются уровни.
Чтобы исправить, надо просто перекомпилировать индикатор.
Пункты 3 — 5,11.
-
Не видно уровней.jpg
62,8 КБ · Просмотры: 36
-
После перекомпилирования.jpg
81,8 КБ · Просмотры: 35
-
#17
Errors Fixed in @Ratio_MACD_v3
-
@Ratio_MACD_v3.mq4
9,5 КБ · Просмотры: 29
-
@Ratio_MACD_v3.ex4
28 КБ · Просмотры: 18
-
#18
Errors Fixed in FXi Semafor Indicator
-
FXi 3 Semafor.ex4
23,9 КБ · Просмотры: 16
-
FXi 3 Semafor.mq4
22,3 КБ · Просмотры: 29
-
#19
If any one want to fix errors in MetaTrader 4 (mq4) indicators, i will try to fix issues for free of cost. based on my free time. I am learning mql coding, for my skills improvement only i am doing this one.
-
#20
If any one want to fix errors in MetaTrader 4 (mq4) indicators, i will try to fix issues for free of cost. based on my free time. I am learning mql coding, for my skills improvement only i am doing this one.
Я специально выкладывал не исправленные версии индикаторов, чтобы люди сами исправляли, а не скачивали готовое решение =)
100
File reading error
101
Error of opening an *. EX4 for writing
103
Not enough free memory to complete compilation
104
Empty syntactic unit unrecognized by compiler
105
Incorrect file name in #include
106
Error accessing a file in #include (probably the file does not exist)
108
Inappropriate name for #define
109
Unknown command of preprocessor (valid #include, #define, #property, #import)
110
Symbol unknown to compiler
111
Function not implemented (description is present, but no body)
112
Double quote («) omitted
113
Opening angle bracket (<) or double quote («) omitted
114
Single quote (‘) omitted
115
Closing angle bracket «>» omitted
116
Type not specified in declaration
117
No return operator or return is found not in all branches of the implementation
118
Opening bracket of call parameters was expected
119
Error writing EX4
120
Invalid access to an array
121
The function is not of void type and the return operator must return a value
122
Incorrect declaration of the destructor
123
Colon «:» is missing
124
Variable is already declared
125
Variable with such identifier already declared
126
Variable name is too long (> 250 characters)
127
Structure with such identifier already defined
128
Structure is not defined
129
Structure member with the same name already defined
130
No such structure member
131
Breached pairing of brackets
132
Opening parenthesis «(» expected
133
Unbalanced braces (no «}»)
134
Difficult to compile (too much branching, internal stack levels are overfilled)
135
Error of file opening for reading
136
Not enough memory to download the source file into memory
137
Variable is expected
138
Reference cannot be initialized
140
Assignment expected (appears at declaration)
141
Opening brace «{» expected
142
Parameter can be a dynamic array only
143
Use of «void» type is unacceptable
144
No pair for «)» or «]», i.e. «(or» [ » is absent
145
No pair for «(or» [ «, i.e. «) «or»] » is absent
146
Incorrect array size
147
Too many parameters (> 64)
149
This token is not expected here
150
Invalid use of operation (invalid operands)
151
Expression of void type not allowed
152
Operator is expected
153
Misuse of break
154
Semicolon «;» expected
155
Comma «,» expected
156
Must be a class type, not struct
157
Expression is expected
158
«non HEX character» found in HEX or too long number (number of digits> 511)
159
String-constant has more than 65534 characters
160
Function definition is unacceptable here
161
Unexpected end of program
162
Forward declaration is prohibited for structures
163
Function with this name is already defined and has another return type
164
Function with this name is already defined and has a different set of parameters
165
Function with this name is already defined and implemented
166
Function overload for this call was not found
167
Function with a return value of void type cannot return a value
168
Function is not defined
170
Value is expected
171
In case expression only integer constants are valid
172
The value of case in this switch is already used
173
Integer is expected
174
In #import expression file name is expected
175
Expressions are not allowed on global level
176
Omitted parenthesis «)» before «;»
177
To the left of equality sign a variable is expected
178
The result of expression is not used
179
Declaring of variables is not allowed in case
180
Implicit conversion from a string to a number
181
Implicit conversion of a number to a string
182
Ambiguous call of an overloaded function (several overloads fit)
183
Illegal else without proper if
184
Invalid case or default without a switch
185
Inappropriate use of ellipsis
186
The initializing sequence has more elements than the initialized variable
187
A constant for case expected
188
A constant expression required
189
A constant variable cannot be changed
190
Closing bracket or a comma is expected (declaring array member)
191
Enumerator identifier already defined
192
Enumeration cannot have access modifiers (const, extern, static)
193
Enumeration member already declared with a different value
194
There is a variable defined with the same name
195
There is a structure defined with the same name
196
Name of enumeration member expected
197
Integer expression expected
198
Division by zero in constant expression
199
Wrong number of parameters in the function
200
Parameter by reference must be a variable
201
Variable of the same type to pass by reference expected
202
A constant variable cannot be passed by a non-constant reference
203
Requires a positive integer constant
204
Failed to access protected class member
205
Import already defined in another way
208
Executable file not created
209
‘OnCalculate’ entry point not found for the indicator
210
The continue operation can be used only inside a loop
211
Error accessing private (closed) class member
213
Method of structure or class is not declared
214
Error accessing private (closed) class method
216
Copying of structures with objects is not allowed
218
Index out of array range
219
Array initialization in structure or class declaration not allowed
220
Class constructor cannot have parameters
221
Class destructor can not have parameters
222
Class method or structure with the same name and parameters have already been declared
223
Operand expected
224
Class method or structure with the same name exists, but with different parameters (declaration!=implementation)
225
Imported function is not described
226
ZeroMemory() is not allowed for objects with protected members or inheritance
227
Ambiguous call of the overloaded function (exact match of parameters for several overloads)
228
Variable name expected
229
A reference cannot be declared in this place
230
Already used as the enumeration name
232
Class or structure expected
235
Cannot call ‘delete’ operator to delete the array
236
Operator ‘ while’ expected
237
Operator ‘delete’ must have a pointer
238
There is ‘default’ for this ‘switch’ already
239
Syntax error
240
Escape-sequence can occur only in strings (starts with ‘\’)
241
Array required — square bracket ‘[‘ does not apply to an array, or non arrays are passed as array parameters
242
Can not be initialized through the initialization sequence
243
Import is not defined
244
Optimizer error on the syntactic tree
245
Declared too many structures (try to simplify the program)
246
Conversion of the parameter is not allowed
247
Incorrect use of the ‘delete’ operator
248
It’s not allowed to declare a pointer to a reference
249
It’s not allowed to declare a reference to a reference
250
It’s not allowed to declare a pointer to a pointer
251
Structure declaration in the list of parameter is not allowed
252
Invalid operation of typecasting
253
A pointer can be declared only for a class or structure
256
Undeclared identifier
257
Executable code optimizer error
258
Executable code generation error
260
Invalid expression for the ‘switch’ operator
261
Pool of string constants overfilled, simplify program
262
Cannot convert to enumeration
263
Do not use ‘virtual’ for data (members of a class or structure)
264
Cannot call protected method of class
265
Overridden virtual functions return a different type
266
Class cannot be inherited from a structure
267
Structure cannot be inherited from a class
268
Constructor cannot be virtual (virtual specifier is not allowed)
269
Method of structure cannot be virtual
270
Function must have a body
271
Overloading of system functions (terminal functions) is prohibited
272
Const specifier is invalid for functions that are not members of a class or structure
274
Not allowed to change class members in constant method
276
Inappropriate initialization sequence
277
Missed default value for the parameter (specific declaration of default parameters)
278
Overriding the default parameter (different values in declaration and implementation)
279
Not allowed to call non-constant method for a constant object
280
An object is necessary for accessing members (a dot for a non class/structure is set)
281
The name of an already declared structure cannot be used in declaration
284
Unauthorized conversion (at closed inheritance)
285
Structures and arrays cannot be used as input variables
286
Const specifier is not valid for constructor/destructor
287
Incorrect string expression for a datetime
288
Unknown property (#property)
289
Incorrect value of a property
290
Invalid index for a property in #property
291
Call parameter omitted — <func (x,)>
293
Object must be passed by reference
294
Array must be passed by reference
295
Function was declared as exportable
296
Function was not declared as exportable
297
It is prohibited to export imported function
298
Imported function cannot have this parameter (prohibited to pass a pointer, class or structure containing a dynamic array, pointer, class, etc.)
299
Must be a class
300
#import was not closed
302
Type mismatch
303
Extern variable is already initialized
304
No exported function or entry point found
305
Explicit constructor call is not allowed
306
Method was declared as constant
307
Method was not declared as constant
308
Incorrect size of the resource file
309
Incorrect resource name
310
Resource file opening error
311
Resource file reading error
312
Unknown resource type
313
Incorrect path to the resource file
314
The specified resource name is already used
315
Argument expected for the function-like macro
316
Unexpected symbol in macro definition
317
Error in formal parameters of the macro
318
Invalid number of parameters for a macro
319
Too many parameters for a macro
320
Too complex, simplify the macro
321
Parameter for EnumToString() can be only an enumeration
322
The resource name is too long
323
Unsupported image format (only BMP with 24 or 32 bit color depth is supported)
324
An array cannot be declared in operator
325
The function can be declared only in the global scope
326
The declaration is not allowed for the current scope
327
Initialization of static variables with the values of local variables is not allowed
328
Illegal declaration of an array of objects that do not have a default constructor
329
Initialization list allowed only for constructors
330
No function definition after initialization list
331
Initialization list is empty
332
Array initialization in a constructor is not allowed
333
Initializing members of a parent class in the initialization list is not allowed
334
Expression of the integer type expected
335
Memory required for the array exceeds the maximum value
336
Memory required for the structure exceeds the maximum value
337
Memory required for the variables declared on the global level exceeds the maximum value
338
Memory required for local variables exceeds the maximum value
339
Constructor not defined
340
Invalid name of the icon file
341
Could not open the icon file at the specified path
342
The icon file is incorrect and is not of the ICO format
343
Reinitialization of a member in a class/structure constructor using the initialization list
344
Initialization of static members in the constructor initialization list is not allowed
345
Initialization of a non-static member of a class/structure on a global level is not allowed
346
The name of the class/structure method matches the name of an earlier declared member
347
The name of the class/structure member matches the name of an earlier declared method
348
Virtual function cannot be declared as static
349
The const modifier is not allowed for static functions
350
Constructor or destructor cannot be static
351
Non-static member/method of a class or a structure cannot be accessed from a static function
352
An overload operation (+,-,[],++,— etc.) is expected after the operator keyword
353
Not all operations can be overloaded in MQL4
354
Definition does not match declaration
355
An invalid number of parameters is specified for the operator
356
Event handling function not found
357
Method cannot be exported
358
A pointer to the constant object cannot be normalized by a non-constant object
359
Class templates are not supported yet
360
Function template overload is not supported yet
361
Function template cannot be applied
362
Ambiguous parameter in function template (several parameter types can be applied)
363
Unable to determine the parameter type, by which the function template argument should be normalized
364
Incorrect number of parameters in the function template
365
Function template cannot be virtual
366
Function templates cannot be exported
367
Function templates cannot be imported
368
Structures containing the objects are not allowed
369
String arrays and structures containing the objects are not allowed
370
A static class/structure member must be explicitly initialized
371
Compiler limitation: the string cannot contain more than 65 535 characters
372
Inconsistent #ifdef/#endif
373
Object of class cannot be returned, copy constructor not found
374
Non-static members and methods cannot be used
375
OnTesterInit() impossible to use without OnTesterDeinit()
376
Redefinition of formal parameter ‘%s’
377
Macro __FUNCSIG__ and __FUNCTION__ cannot appear outside of a function body
378
Invalid returned type. For example, this error will be produced for functions imported from DLL that return structure or pointer.
379
Template usage error
380
Not used
381
Illegal syntax when declaring pure virtual function, only «=NULL» or «=0» are allowed
382
Only virtual functions can be declared with the pure-specifier («=NULL» or «=0»)
383
Abstract class cannot be instantiated
384
A pointer to a user-defined type should be applied as a target type for dynamic casting using the dynamic_cast operator
385
«Pointer to function» type is expected
386
Pointers to methods are not supported
387
Error — cannot define the type of a pointer to function
388
Type cast is not available due to private inheritance
389
A variable with const modifier should be initialized during declaration
Don’t listen to the trolls at the mql5.com website who are spreading false information. Their only goal is to confuse you, so you don’t know how to continue with your struggle and finally spend money buying useless services from them scammers.
This project is written in classical MQL4. Classical MQL4 is not the same as the MQL5 dialect distributed with current versions of MetaTrader4. Officially it’s labeled as MQL4 but have a look at the compiler’s (MetaEditor) «About» dialog. It says «version 5.+» which stands for MQL5.
MetaTrader4 supports both classical MQL4 and MQL5 and will do so until the world ends. Again, this project is written in classical MQL4 (which I admit is not clearly enough stated).
In the README in the bin directory I describe how to compile classical MQL4. And I understand that most non-technical users have issues with following those instructions.
The most asked question is «Why don’t you convert classical MQL4 to new MQL4/MQL5?» Because ecosystem/functionalities in the framework are large and migration/conversion causes a massive amount of work. Furthermore everything would need to be tested again on multiple platforms to run reliably. What would be the advantage of such an effort? The project would support images in charts, graphic UI elements (buttons, dialogs, etc) and other non-important gimmicks. This little gain is not worth the massive effort.
Together with the MT4Expander indicators/experts build with this framework are much more powerful than any MetaTrader version ever released by MetaQuotes. Long story short: I don’t have the incentive to migrate the project to another language because the gain is too little. Unfortunately interest from the community also doesn’t justify such a decision.
Nonetheless I’m thinking about an improvement for affected users. There’s a Github Release page, but again updates are few and not often enough. For the future my solution will be to provide pre-compiled nightly builds. I will setup an automated CI/CD chain which will compile ready to use binaries every night.
Рассмотрим дополнительные функции и возможности MQL4, которые могут быть полезны в программировании советников.
Escape символы
Если вы хотите добавить кавычки или символ обратной черты в строку, вам нужно экранировать символ, используя обратную косую черту (\). Например, если вам нужно вставить двойную кавычку, escape-символ будет \». Для одиночной кавычки escape-символ — \’. Для обратной косой черты в качестве escape-символа используйте две обратные косые черты: \\
string EscQuotes = "Эта строка содержит \"двойные кавычки\""; // Эта строка содержит "двойные кавычки" string EscQuote = "Эта строка содержит \'одинарные кавчки\'"; // Эта строка содержит 'одинарные кавычки' string EscSlash = "Эта строка содержит обратную черту \\"; // Эта строка содержит обратную черту \
Если вам нужно, чтобы строка занимала несколько строк, используйте escape-символ \n для добавления новой строки:
string NewLine = "Эта строка \n новая строка"; // Output: Эта строка новая строка
Использование комментариев на графике
Вы можете напечатать текст в верхнем левом углу графика, используя функцию Comment(). Это можно использовать для вывода информации о состоянии, настроек индикатора или любой другой информации, которая может оказаться полезной.
Один из способов отображения комментариев к графику — объявить несколько строковых переменных и объединить их вместе с символами новой строки. Одна строка может использоваться для отображения настроек, другая — для отображения информационных сообщений или статуса ордера и т. д. Строка будет передана в функцию Comment().
Поместите функцию Comment() в конец функции start(), чтобы обновить комментарий на графике:
string SettingsComment = "FastMAPeriod: "+FastMAPeriod+" SlowMAPeriod: "+SlowMAPeriod; string StatusComment = "Размещен ордер на покупку"; Comment(SettingsComment+"\n"+StatusComment);
Мы объявляем и устанавливаем значения строк SettingsComment и StatusComment внутри функции start(). В конце функции start мы вызываем функцию Comment() и используем ее для вывода наших комментариев на графике. Мы используем символ новой строки (\n), чтобы разделить комментарий на две строки.
Проверка настроек
Существует несколько настроек советника, которые должны быть проверены, прежде чем советнику будет разрешено торговать. Эти настройки находятся на вкладке «Общие» в диалоговом окне «Свойства эксперта».
Параметр Allow live trading должен быть включен до начала торговли. Если он не включен, в правом верхнем углу графика рядом с именем советника появится эмоция недовольство. Вы можете проверить это условие в своем советнике, используя функцию IsTradeAllowed(). Если она возвращает false, настройка Allow live trading отключена.
Если вы хотите отобразить пользователю сообщение о том, что этот параметр должен быть активирован, вы можете сделать следующее:
if(IsTradeAllowed() == false) Alert("Активируйте настройку \'Allow live trading\' в свойствах советника!");
Если ваш эксперт использует внешнюю библиотеку .ex4, в свойствах эксперта должен быть включен параметр Разрешить импорт внешних экспертов. Вы можете проверить это с помощью функции IsLibrariesAllowed():
if(IsLibrariesAllowed() == false) Alert("Активруйте настройку \'Allow import of external experts\' в свойствах советника!");
То же самое можно сделать для DLL, используя функцию IsDllsAllowed():
if(IsDllsAllowed() == false) Alert("Активруйте настройку \'Allow DLL imports\' в свойствах советника!");
Ограчения аккаунтов
В какой-то момент вы можете решить продать своего советника другим трейдерам. Вы также можете предоставить демо-версию для потенциальных покупателей. Чтобы ваш советник не мог свободно распространяться посторонними лицами, вы должны включить некоторые ограничения учетной записи, которые ограничивают использование советника покупателям или брокером.
Чтобы ограничить использование советника только для демо-счета, используйте функцию IsDemo(), чтобы проверить, является ли текущий активный аккаунт демо-счетом. Если текущий счет не является демо-счетом, мы выведем предупреждение и остановим выполнение работы советника.
if(IsDemo() == false) { Alert("Этот советник работает только на демо-аккаунте!"); return(0); }
Вы можете использовать функции учетной записи AccountName(), AccountNumber() и AccountBroker(), чтобы проверить имя учетной записи, номер аккаунта и брокера соответственно. Ограничение использования по номеру счета является распространенным и простым в реализации способом защиты:
int CustomerAccount = 123456; if(AccountNumber() != CustomerAccount) { Alert("Номер аккаунта не совпадает!"); return(0); }
Вы можете использовать AccountName() или AccountBroker() аналогичным образом. Для AccountBroker() вам сначала нужно использовать Print(), чтобы получить правильное возвращаемое имя брокера. Это значение будет напечатано в журнале экспертов.
Если вы решили продавать советник на коммерческой основе, имейте в виду, что файлы MQL легко декомпилировать. Существуют различные методы, которые могут использовать хакеры для взлома вашего советника, например, размещение функций во внешних библиотеках или DLL.
MessageBox()
До сих пор в этой книге мы использовали встроенную функцию Alert() для отображения сообщений об ошибках. Но что, если вы хотите настроить диалоги оповещений или запросить ввод данных у пользователя? Функция MessageBox() позволит вам создать собственный всплывающий диалог с помощью Windows API.
Чтобы использовать функцию MessageBox(), мы должны сначала выполнить #include файл WinUser32.mqh, который устанавливается вместе с MetaTrader. Этот файл импортирует функции из файла Windows user32.dll и определяет константы, необходимые для работы функции MessageBox(). Вот синтаксис функции MessageBox():
int MessageBox(string Text, string Title, int Flags);
Чтобы использовать функцию MessageBox(), мы должны определить текст, который будет отображаться во всплывающем диалоговом окне, а также заголовок, отображаемый в строке заголовка. Нам также нужно будет указать флаги, которые указывают, какие кнопки и значки должны появляться в нашем всплывающем окне. Если флаги не указаны, кнопка ОК будет использоваться по умолчанию. Флаги должны быть разделены символом (|).
Вот пример окна сообщения с кнопками Да / Нет и значком вопросительного знака:
// #include <WinUser32.mqh> // start() function int YesNoBox = MessageBox("Открыть сделку?","Подтверждение торговли", MB_YESNO|MB_ICONQUESTION); if(YesNoBox == IDYES) { // Разместить ордер }
Флаг MB_YESNO указывает, что мы будем использовать кнопки «Да / Нет» в нашем окне сообщений, в то время как флаг MB_ICONQUESTION помещает значок вопросительного знака в диалоговом окне. Переменная YesNoBox содержит возвращаемое значение функций MessageBox(), которое будет указывать, какая кнопка была нажата.
Если кнопка «Да» была нажата, значением YesNoBox будет IDYES, и будет размещен ордер. Если кнопка «Нет» была нажата, флаг возврата будет IDNO. Вы можете использовать возвращаемое значение MessageBox() в качестве входных данных, чтобы определить порядок действий, таких как размещение ордера.
Ниже приведен неполный список флагов, которые можно использовать в окнах сообщений.
Флаги для кнопок
Эти флаги указывают, какие кнопки появляются в вашем окне сообщения:
- MB_OKCANCEL — кнопки ОК и Отмена.
- MB_YESNO — кнопки Да и Нет.
- MB_YESNOCANCEL — кнопки Да, Нет и Отмена.
Флаги для иконок
Эти флаги указывают на значки, которые появляются рядом с текстом в окне сообщения.
- MB_ICONSTOP — значок знака остановки.
- MB_ICONQUESTION — значок знака вопроса.
- MB_ICONEXCLAMATION — значок с восклицательным знаком.
- MB_ICONINFORMATION — информационный значок.
Возвращаемые флаги
Эти флаги являются возвращаемым значением функции MessageBox() и указывают, какая кнопка была нажата.
- IDOK — нажата кнопка «ОК».
- IDCANCEL — была нажата кнопка «Отмена».
- IDYES — была нажата кнопка «Да».
- IDNO — была нажата кнопка «Нет».
Уведомления по E-mail
Ваш эксперт может уведомить вас по электронной почте о размещенных сделках, возможных настройках торговли и многом другом. Функция SendMail() отправит электронное письмо с выбранной темой и текстом на адрес электронной почты, указанный в диалоговом окне «Сервис — Параметры» на вкладке «Электронная почта».
На вкладке «Электронная почта» необходимо сначала указать почтовый сервер SMTP с номером порта, например, mail.yourdomain.com:25, а также имя пользователя и пароль, если это необходимо.
Вы можете использовать любой адрес электронной почты в поле От. Поле «Кому» — это адрес электронной почты, на который отправляются сообщения.
Функция SendMail() имеет два аргумента: первый — это строка темы письма, а второй — содержимое самого письма. Вы можете использовать новые строки, экранированные символы, переменные и константы в теле вашего письма.
Вот пример использования функции SendMail():
string EmailSubject = "Ордер на покупку открыт"; string EmailBody = Ордер на покупку "+Ticket+" открыт на "+Symbol()+" по цене "+Ask; // "Ордер на покупку 12584 открыт на EURDUSD по цене 1.4544" SendMail(EmailSubject,EmailBody);
Повтор после ошибки
Мы пытались проверить параметры ордера, прежде чем пытаться разместить ордер, чтобы избежать распространенных сообщений об ошибках из-за неправильных настроек или цены. Тем не менее, ошибки все еще могут возникать, к примеру, из-за реквот или проблем с сервером. Этих ошибок не всегда можно избежать, но мы можем попробовать разместить ордер снова, когда это произойдет.
Чтобы повторить ордер, мы поместим функцию OrderSend() в цикл while. Если OrderSend() не возвращает номер тикета, мы попробуем открыть ордер еще раз:
int Ticket = 0; while(Ticket <= 0) { Ticket = OrderSend(Symbol(),OP_BUY,LotSize,OpenPrice,UseSlippage,BuyStopLoss,BuyTakeProfit); }
Сначала мы объявляем переменную для номера тикета, в данном случае Ticket. Пока Ticket не больше 0, цикл while с функцией OrderSend() будет выполняться снова и снова.
В случае ошибки или какой-либо другой неисправленной торговой ошибки, цикл будет повторяться бесконечно, и ваш эксперт будет зависать. Мы можем исправить это, добавив максимальное количество повторов:
int Retries = 0; int MaxRetries = 5; int Ticket = 0; while(Ticket <= 0) { Ticket = OrderSend(Symbol(),OP_BUY,LotSize,OpenPrice,UseSlippage,BuyStopLoss,BuyTakeProfit); if(Retries <= MaxRetries) Retries++; else break; }
Мы объявляем переменную для использования в качестве счетчика (Retries) и максимальной настройки повторов (MaxRetries). Пока мы не превысили MaxRetries, переменная Retries увеличивается, и цикл повторяется снова. Как только MaxRetries достигнут, цикл завершается. После этого вы можете при необходимости предупредить пользователя об ошибке.
Если вы хотите сделать цикл повторных попыток зависимым от определенного состояния ошибки, мы можем проверить код ошибки по списку и вернуть значение true, если оно совпадение. Эта функция содержит некоторые распространенные коды ошибок:
bool ErrorCheck(int ErrorCode) { switch(ErrorCode) { case 128: // Trade timeout return(true); case 136: // Off quotes return(true); case 138: // Requotes return(true); case 146: // Trade context busy return(true); default: return(false); } }
Эта функция использует оператор switch. Мы ищем метку, значение которой соответствует выражению, назначенному оператору switch (в данном примере ErrorCode).
Когда совпадение наблюдений найдено, блок switch должен быть завершен с оператором прерывания или возврата. В этом примере мы используем оператор return для возврата значения true / false вызывающей функции.
Вот как мы используем ErrorCheck(), чтобы повторить попытку размещения ордера:
int Retries; int MaxRetries = 5; int Ticket; while(Ticket <= 0) { Ticket = OrderSend(Symbol(),OP_BUY,LotSize,OpenPrice,UseSlippage,BuyStopLoss,BuyTakeProfit); if(Ticket == -1) int ErrCode = GetLastError(); if(Retries <= MaxRetries && ErrorCheck(ErrCode) == true) Retries++; else break; }
Если Ticket возвращает -1, указывая на то, что произошла ошибка, мы получаем код ошибки с помощью GetLastError(). Мы передаем код ошибки в нашу функцию ErrorCheck(). Если код ошибки соответствует какой-либо из ошибок в функции проверки ошибок, ErrorCheck() вернет true, а функция OrderSend() будет повторена до 5 раз.
Использование комментариев к ордеру в качестве идентификатора
Мы использовали «магическое число» в качестве идентификатора ордера, который однозначно идентифицирует ордера, размещаемые конкретным советником. Если ваш эксперт размещает несколько ордеров одновременно, и вы хотите иметь возможность обрабатывать каждый из этих ордеров, вы можете использовать комментарий к ордеру в качестве необязательного идентификатора.
Допустим, ваш эксперт разместит два типа ордеров. Вы хотите иметь возможность изменить или закрыть эти ордера по отдельности. Вы можете использовать две функции OrderSend() и разместить разные комментарии к каждому из них. Затем, при выборе ордеров с использованием цикла, вы можете использовать OrderComment() в качестве одного из условий для нахождения ордеров для их изменения или закрытия.
string OrderComment1 = "Первый ордер"; string OrderComment2 = "Второй ордер"; // Размещение ордеров int Ticket1 = OrderSend(Symbol(),OP_BUY,LotSize,OpenPrice,UseSlippage,BuyStopLoss,BuyTakeProfit,OrderComment1,MagicNumber,0,Green); int Ticket2 = OrderSend(Symbol(),OP_BUY,LotSize,OpenPrice,UseSlippage,BuyStopLoss,BuyTakeProfit,OrderComment2,MagicNumber,0,Green); // Изменение ордера for(int Counter = 0; Counter <= OrdersTotal()-1; Counter++) { OrderSelect(Counter,SELECT_BY_POS); if(OrderMagicNumber() == MagicNumber && OrderSymbol() == Symbol() && OrderComment() == OrderComment1) { // Изменение первого ордера } else if(OrderMagicNumber() == MagicNumber && OrderSymbol() == Symbol() && OrderComment() == OrderComment2) { // Изменение второго ордера } }
Мы объявляем две строковые переменные для использования в качестве комментариев к ордеру. Функции OrderSend() размещают два ордера, каждый со своим комментарием. В следующем примере цикла модификации ордеров функция OrderComment() используется в качестве условия при выборе ордеров для модификации.
Вы можете использовать проверку OrderComment(), чтобы закрывать ордера независимо от других ордеров, использовать другие настройки трейлинг-стопа или что-либо другое, что требует ваша торговая система.
Проверка маржинальных требований
MetaTrader поставляется с функциями, которые позволяют вам проверить текущую свободную маржу или уровень стопов перед размещением ордера. Уровень Stop Out — это процент или сумма свободной маржи, ниже которой вы не сможете размещать ордера. Однако ручная проверка уровня свободной маржи или уровня стоп-ордера на самом деле не нужна, поскольку при попытке разместить ордер с слишком малым запасом маржипроизойдет ошибка.
Более полезная идея — определить собственный уровень стоп-аута и прекратить торговлю, если текущий капитал опустится ниже этого уровня. Давайте начнем с объявления внешней переменной MinimumEquity, которая является минимальной суммой капитала, необходимой на нашем счете, прежде чем мы сможем разместить ордер.
Мы сравним MinimumEquity с нашим текущим счетом. Если текущий эквити меньше нашего минимального эквити, ордер не будет размещен, и предупреждающее сообщение проинформирует пользователя о состоянии. Предположим, у нас есть баланс на счете 10000 долларов. Если мы потеряем более 20% этого капитала, мы не будет размещать ордера. Вот код для проверки минимального эквити:
// Внешние переменные extern int MinimumEquity = 8000; // Размещение ордера if(AccountEquity() > MinimumEquity) { // Открываем ордер } else if(AccountEquity() <= MinimumEquity) { Alert("Размер эквити слишком низкий!"); }
Внешняя переменная MinimumEquity помещается в начало файла. Если текущий капитал, как указано AccountEquity(), больше MinimumEquity, ордер будет размещен. В противном случае заказ не будет размещен, и будет отображено предупреждающее сообщение.
Проверка спреда
Возможно, вы захотите избегать размещения ордеров в периоды, когда спред значительно расширяется. Мы можем установить максимальный спред и проверить текущий спред перед торговлей. Мы объявим внешнюю переменную MaximumSpread и используем MarketInfo() для проверки текущего спреда.
// внешние переменные extern int MaximumSpread = 5; extern int MinimumEquity = 8000; if(AccountEquity() > MinimumEquity && MarketInfo(Symbol(),MODE_SPREAD) < MaximumSpread) { // Place order } else { } if(AccountEquity() <= MinimumEquity) Alert("Размер эквити ниже минимального!"); if(MarketInfo(Symbol(),MODE_SPREAD) > MaximumSpread) Alert("Размер спреда слишком большой!"); }
Обратите внимание, что мы выполняем проверку минимального эквити и спреда перед размещением ордера. Если одно из условий ложно, мы переходим в блок else и проверяем, какое из условий привело к тому, что ордер не был размещен. Мы будем отображать одно или несколько предупреждений в зависимости от того, какое условие выполняется.
Размещение нескольких ордеров
Вы можете разместить несколько ордеров с разными уровнями стоп-лосс и тейк-профит, а также размерами лота. Есть несколько способов сделать это. Один из способов — просто использовать разные операторы OrderSend() для каждого ордера. Это предполагает, что вы планируете размещать одинаковое количество ордеров каждый раз.
Другой способ — использовать цикл for для размещения заказов. Таким образом, вы можете настроить несколько ордеров одновременно. Вы можете предварительно загрузить свой стоп-лосс и тейк-профит в массивы, а также увеличивать их в циклах for.
Начнем с определения внешних переменных для трех уровней стоп-лосс и тейк-профит. Любые дополнительные ордера выше трех не будут иметь стоп-лосс или тейк-профит. Мы также добавим внешнюю переменную, чтобы настроить количество размещаемых ордеров.
extern int StopLoss1 = 20; extern int StopLoss2 = 40; extern int StopLoss3 = 60; extern int TakeProfit1 = 40; extern int TakeProfit2 = 80; extern int TakeProfit3 = 120; extern int MaxOrders = 3;
Далее мы объявим наши массивы, рассчитаем стоп-лосс и тейк-профит и загрузим рассчитанные цены в массив:
double BuyTakeProfit[3]; double BuyStopLoss[3]; BuyTakeProfit[0] = CalcBuyTakeProfit(Symbol(),TakeProfit1,Ask); BuyTakeProfit[1] = CalcBuyTakeProfit(Symbol(),TakeProfit2,Ask); BuyTakeProfit[2] = CalcBuyTakeProfit(Symbol(),TakeProfit3,Ask); BuyStopLoss[0] = CalcBuyStopLoss(Symbol(),StopLoss1,Ask); BuyStopLoss[1] = CalcBuyStopLoss(Symbol(),StopLoss2,Ask); BuyStopLoss[2] = CalcBuyStopLoss(Symbol(),StopLoss3,Ask);
Мы начнем с объявления массивов для хранения стоп-лосса и тейк-профита, BuyTakeProfit и BuyStopLoss. Количество элементов массива должно быть указано при объявлении массива. Индексы массива начинаются с нуля, поэтому, объявив размер измерения массива 3, наш начальный индекс равен 0, а наш самый большой индекс равен 2.
Далее мы рассчитываем цены стоп-лосс и тейк-профит, используя функции CalcBuyStopLoss() и CalcBuyTakeProfit(). Мы присваиваем рассчитанный стоп-лосс или тейк-профит соответствующему элементу массива. Обратите внимание, что первый индекс массива равен 0, а третий индекс массива равен 2.
Вот цикл for для размещения ордеров:
for(int Count = 0; Count <= MaxOrders - 1; Count++) { int OrdInt = Count + 1; OrderSend(Symbol(),OP_BUY,LotSize,Ask,UseSlippage,BuyStopLoss[Count], BuyTakeProfit[Count],"Buy Order "+OrdInt,MagicNumber,0,Green); }
Переменная Count начинается с 0 и соответствует первому элементу массива. Количество циклов (т.е. количество размещаемых ордеров) определяется MaxOrders — 1. Для каждой итерации цикла мы увеличиваем стоп-лосс и тейк-профит на единицу.
Мы используем переменную OrdInt для увеличения количества ордеров в комментарии к ордеру. Первым комментарием к ордеру будет «Ордер на покупку 1», следующим будет «Ордер на покупку 2» и так далее. Функция OrderSend() размещает ордер с соответствующим стоп-лоссом и значением тейк-профита, используя переменную Count для выбора соответствующего элемента массива.
Это всего лишь один из способов обработки нескольких ордеров. Основным недостатком этого подхода является то, что мы можем рассчитывать только стоп-лосс и тейк-профит цены для ограниченного числа ордеров. Кроме того, мы можем масштабировать значения тейк-профита и стоп-лосса на указанную сумму и размещать потенциально неограниченное количество ордеров:
extern int StopLossStart = 20; extern int StopLossIncr = 20; extern int TakeProfitStart = 40; extern int TakeProfitIncr = 40; extern int MaxOrders = 5;
В приведенном выше примере стоп-лосс для нашего первого ордера составит 20 пунктов. Мы будем увеличивать стоп-лосс на 20 пунктов за каждый дополнительный ордер. То же самое для тейк-профита, за исключением того, что мы начнем с 40 и увеличиваем на 40. Вместо использования массивов мы рассчитаем стоп-лосс и тейк-профит в цикле for:
for(int Count = 0; Count <= MaxOrders - 1; Count++) { int OrdInt = Count + 1; int UseStopLoss = StopLossStart + (StopLossIncr * Count); int UseTakeProfit = TakeProfitStart + (TakeProfitIncr * Count); double BuyStopLoss = CalcBuyStopLoss(Symbol(),UseStopLoss,Ask); double BuyTakeProfit = CalcBuyTakeProfit(Symbol(),UseTakeProfit,Ask); OrderSend(Symbol(),OP_BUY,LotSize,Ask,UseSlippage,BuyStopLoss, BuyTakeProfit,"Buy Order "+OrdInt,MagicNumber,0,Green); }
Мы определяем уровень тейк-профита и стоп-лосса в пунктах, умножая переменную StopLossIncr или TakeProfitIncr на значение Count и добавляя его к значению StopLossStart или TakeProfitStart. Для первого ордера уровень стоп-лосса или тейк-профита будет равен StopLossStart или TakeProfitStart.
Далее мы рассчитываем стоп-лосс и цену тейк-профита для ордера. Наконец, мы размещаем ордер с помощью OrderSend().
Цикл продолжится до открытия количества ордеров, указанных в MaxOrders. Этот метод позволяет нам указать столько ордеров, сколько мы хотим, используя переменную MaxOrders, гарантируя, что каждый размещаемый нами ордер будет иметь стоп-лосс и тейк-профит.
Глобальные переменные
В этой книге мы упоминаем переменные с глобальной областью действия как «глобальные переменные». MetaTrader имеет набор функций для установки переменных на уровне терминала, что означает, что эти переменные доступны каждому работающему в данный момент советнику, при условии, что мы знаем имя переменной.
Текущий список глобальных переменных в терминале можно просмотреть, выбрав «Глобальные переменные» в меню «Инструменты» или нажав клавишу F3 на клавиатуре.
Одним из способов использования этих переменных является сохранение определенных глобальных или статических переменных в терминале, чтобы, если советник был закрыт, мы могли продолжить работу с того места, где остановились. Не все советники требуют этого, но более сложные советники будут поддерживать определенное состояние, которое, в случае прерывания, приведет к отказу в работе советника.
Лучший способ предотвратить это — избегать создания советников, требующих такого уровня сложности. Но если этого нельзя избежать, то использование функций глобальных переменных для сохранения текущего состояния в терминале может быть полезным в случае отключения.
Чтобы объявить глобальную переменную, используйте функцию GlobalVariableSet(). Первый аргумент — это строка, обозначающая имя глобальной переменной, а второй аргумент — это значение типа double, присваиваемое ей.
GlobalVariableSet(GlobalVariableName,DoubleValue);
Чтобы ваши имена переменных были уникальными, вы можете создать префикс глобальной переменной. Объявите глобальную переменную в своем эксперте и установите значение в функции init(), используя текущий символ, точку, имя советника и магический номер, чтобы создать уникальный префикс переменной.
// Глобальные переменные string GlobalVariablePrefix; int init() { GlobalVariablePrefix = Symbol()+Period()+"_"+"ProfitBuster"+"_"+MagicNumber+"_"; }
Мы используем текущий символ и период, а также идентификатор советника и внешнюю переменную MagicNumber. Теперь, когда мы устанавливаем глобальную переменную с помощью GlobalVariableSet(), мы используем префикс, который мы определили выше вместе с фактическим именем переменной:
GlobalVariableSet(GlobalVariablePrefix+Counter,Counter);
Поэтому, если мы торгуем на EURUSD на таймфрейме M15 с советником с именем «ProfitBuster», используя 11 в качестве магического числа и Counter в качестве имени переменной, именем нашей глобальной переменной будет EURUSD15_ProfitBuster_11_Counter.
Чтобы получить значение глобальной переменной, используйте функцию GlobalVariableGet() с именем переменной в качестве аргумента:
Counter = GlobalVariableGet(GlobalVariablePrefix+Counter);
Чтобы удалить глобальную переменную, используйте функцию GlobalVariableDel() с именем переменной в качестве аргумента. Чтобы удалить все глобальные переменные, размещенные вашим советником, используйте функцию GlobalVariableDeleteAll() с префиксом в качестве аргумента.
GlobalVariableDel(GlobalVariablePrefix+Counter); GlobalVariableDeleteAll(GlobalVariablePrefix);
Проверка прибыли ордера
Иногда может быть полезно проверить текущую прибыль по ордеру или проверить общую прибыль по ордеру, который уже закрыт. Есть два способа проверить прибыль. Чтобы получить прибыль в валюте депозита, используйте функцию OrderProfit(). Сначала вы должны выбрать ордер, используя OrderSelect().
OrderSelect(Ticket,SELECT_BY_TICKET); double GetProfit = OrderProfit(Ticket);
Результат функции OrderProfit() должен совпадать с общей прибылью или убытком, который указан в истории для выбранного ордеров.
Чтобы получить прибыль или убыток в пунктах, вам необходимо рассчитать разницу между ценой открытия ордера и ценой закрытия ордера. Вам также нужно будет использовать функцию OrderSelect() для получения цен открытия и закрытия.
OrderSelect(Ticket,SELECT_BY_TICKET); if(OrderType() == OP_BUY) double GetProfit = OrderClosePrice() - OrderOpenPrice(); else if(OrderType() == OP_SELL) GetProfit = OrderOpenPrice() - OrderClosePrice(); GetProfit /= PipPoint(Symbol());
Для заявок на покупку мы рассчитываем прибыль, вычитая цену открытия из цены закрытия. Для ордеров на продажу мы делаем наоборот. После того, как мы вычислили разницу, мы можем преобразовать прибыль или убыток в целое число, разделив его на пункт, используя нашу функцию PipPoint().
Например, если цена открытия ордера на покупку составляет 1,4650, а цена закрытия — 1,4700, разница между OrderClosePrice() и OrderOpenPrice() составляет 0,0050. Когда мы делим это на нашу функцию PipPoint(), результат равен 50. Таким образом, для этого ордера мы получаем 50 пунктов прибыли. Если бы цена закрытия ордера была 1.4600, то мы бы потеряли 50 пунктов.
Мартингейл
Мартингейл — это система ставок, обычно используемая в рулетке и блэкджеке, где размер ставки удваивается после каждого последовательного проигрыша. Теория состоит в том, что одна выигрышная ставка вернет наш баланс к безубыточности. Недостатком мартингейла является то, что вам потребуется большой размер капитала, чтобы выдержать просадки.
Например, если размер вашего начального лота составляет 0,1 лота, то после 4 последовательных потерь ваш размер лота составит 1,6 лота — в 16 раз больше исходного размера лота. После 7 последовательных проигрышей размер вашего лота составит 12,8 лота — в 128 раз больше исходного размера лота! Долгая полоса неудач может легко уничтожить ваш аккаунт, прежде чем вы сможете вернуть его в безубыточность.
Тем не менее, вы можете включить систему увеличения размера лота при последовательных выигрышах или проигрышах, и это можно сделать, не уничтожая свой счет. Самый простой способ — установить ограничение на количество раз, чтобы увеличить размер лота. Надежная торговая система не должна иметь более 3 или 4 максимальных последовательных убытков. Вы можете определить это, изучив максимальное количество последовательных потерь на вкладке «Отчет» в окне «Тестер стратегий».
Другой способ — увеличить размер вашего лота на меньший множитель. Классическая стратегия Мартингейла удваивает размер лота после каждого последующего проигрыша. Возможно, вы захотите использовать множитель меньше 2. Существует также стратегия анти-мартингейл, где вы увеличиваете размер лота после каждого последовательного выигрыша.
Стратегия Мартингейла работает лучше всего, когда вы размещаете один ордер за раз, поэтому мы будем предполагать, что каждая позиция состоит из одной сделки.
Пользователь сможет выбирать между стратегией Мартингейл (проигрыш) или Анти-Мартингейл (выигрыш). Будет включена настройка ограничения максимального количества последовательных увеличений лота, и множитель лота будет настраиваться.
Сначала посчитаем количество последовательных побед или проигрышей. Нам нужно будет перебрать пул истории ордеров в обратном порядке, начиная с самого последнего закрытого ордера. Мы будем увеличивать счетчик для каждого выигрыша или проигрыша. Пока сохраняется схема последовательных выигрышей или проигрышей, мы продолжим цикл. Как только паттерн нарушается (выигрыш определяется после одного или нескольких проигрышей или наоборот), цикл завершается.
int WinCount; int LossCount; for(int Count = OrdersHistoryTotal()-1; ; Count--) { OrderSelect(Count,SELECT_BY_POS,MODE_HISTORY); if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber) { if(OrderProfit() > 0 && LossCount == 0) WinCount++; else if(OrderProfit() < 0 && WinCount == 0) LossCount++; else break; } }
Мы начнем с объявления переменных для счетчиков выигрышей и проигрышей. Обратите внимание, что в операторе for мы используем OrdersHistoryTotal() для определения начальной позиции. OrdersHistoryTotal() возвращает количество ордеров в пуле истории. Вычитаем 1, чтобы определить позицию индекса для самого последнего ордера, который хранится в переменной Count.
Обратите внимание, что мы пропустили второе выражение в цикле for — то, которое определяет условие прекращения цикла. Мы будем уменьшать переменную Count на каждой итерации цикла.
Мы используем MODE_HISTORY в качестве третьего аргумента в функции OrderSelect(), чтобы указать, что мы перебираем закрытый пул истории ордеров. По умолчанию OrderSelect() использует пул открытых ордеров, поэтому мы должны указать MODE_HISTORY при проверке пула закрытых ордеров.
Мы проверяем, что текущий выбранный ордер соответствует нашему символу графика и нашему магическому номеру. Затем мы проверяем прибыль по ордеру с помощью функции OrderProfit(). Если возвращаемое значение указывает на прибыль (то есть больше нуля), то мы увеличиваем переменную WinCount. Если это потеря, мы увеличиваем LossCount.
Поскольку мы ищем последовательные выигрыши или проигрыши, нам нужно завершить цикл, как только будет найдено альтернативное условие. Для этого мы проверяем переменную WinCount или LossCount при проверке прибыли ордера.
Например, если у нас есть 2 последовательных убытка — это означает, что LossCount = 2 — и наш следующий ордер будет выигрышным, тогда оба наших оператора if будут ложными, и управление перейдет к оператору break, который завершит цикл.
Преимущество этого метода в том, что он не подведет, если советник случайно отключится. Советник продолжит свою работу прямо там, где остановился. Конечно, это означает, что при первом запуске советника он будет использовать любую предыдущую серию выигрышей / проигрышей при определении размера лота. Но, как вы можете видеть, преимущества перевешивают недостатки.
Переменная WinCount или LossCount будет содержать количество последовательных выигрышей или проигрышей. Если мы хотим реализовать стратегию Мартингейла, мы используем LossCount, чтобы определить фактор, с помощью которого можно увеличить размер лота. Если мы делаем анти-Мартингейл, вместо этого мы используем WinCount.
Мы будем использовать внешнюю целочисленную переменную с именем MartingaleType. Если MartingaleType установлен в 0, мы будем использовать стратегию Martingale. Если установлено значение 1, мы будем использовать стратегию анти-мартингейла.
// Внешние переменные extern int MartingaleType = 0; // 0: Martingale, 1: Anti-Martingale extern int LotMultiplier = 2; extern int MaxMartingale = 4; extern double BaseLotSize = 0.1; // Расчет размера лота if(MartingaleType == 0) int ConsecutiveCount = LossCount; else if(MartingaleType = 1) ConsecutiveCount = WinCount; if(ConsecutiveCount > MaxMartingale) ConsecutiveCount = MaxMartingale; double LotSize = BaseLotSize * MathPow(LotMultiplier,ConsecutiveCount);
Мы устанавливаем значение ConsecutiveCount либо в WinCount, либо в LossCount, в зависимости от настройки MartingaleType. Мы сравним это с нашей настройкой MaxMartingale. Если количество наших последовательных ордеров больше, чем MaxMartingale, мы изменим его размер, чтобы он был равен MaxMartingale. (Вы также можете изменить его размер до размера лота по умолчанию, если хотите). Размер лота будет оставаться на этом размере до тех пор, пока выигрыш или проигрыш не нарушат нашу последовательную серию ордеров.
Размер лота определяется умножением нашего BaseLotSize на LotMultiplier, который экспоненциально увеличивается на ConsecutiveCount. Функция MathPow() увеличивает число до указанной степени. Например, если размер нашего начального лота равен 0,1, множитель лота равен 2, и у нас есть четыре последовательных ордера, уравнение равно 0,1 * 24 = 1,6.
Настраивая LotMultiplier и используя стратегии как Мартингейл, так и Анти-Мартингейл, это даст вам достаточно возможностей для экспериментов с использованием экспоненциального размера лотов. Вы можете легко изменить код выше, чтобы использовать другие варианты. Например, вы можете масштабировать размеры лотов в обратном порядке, от самого большого до самого маленького. Или вы можете использовать внешний счетчик вместо ConsecutiveCount.
Отладка работы вашего советника
В отличие от большинства программных средств разработки, MetaEditor не поддерживает современные методы отладки. Поэтому вам придется использовать операторы Print() и журнал для отладки ваших экспертов.
Распечатав содержимое переменных и функций в журнал, вы можете проверить вывод вашего кода и исправить любые ошибки.
Вы можете использовать Тестер стратегий, чтобы запустить симуляцию торговли и проверить вывод логов. Журнал тестера стратегий отображается на вкладке «Журнал» в окне «Тестер стратегий». Существует ограничение на количество информации, которая указана на вкладке Журнал, поэтому вы можете захотеть просмотреть реальный журнал.
Журналы тестера стратегий хранятся в папке \tester\logs. Щелкните правой кнопкой мыши в любом месте окна журнала и выберите «Открыть» во всплывающем меню. Откроется окно проводника Windows, отображающее содержимое папки журнала. Имена файлов имеют формат yyyymmdd.log, где yyyy — год из четырех цифр, mm — месяц из двух цифр, а dd — дата из двух цифр. Вы можете просматривать журналы в блокноте или любом текстовом редакторе.
Давайте проиллюстрируем пример того, как вы можете использовать журнал, чтобы найти проблему. В приведенном ниже коде есть ошибка, и она не работает так, как мы ожидали. Чтобы иметь возможность диагностировать проблему, нам нужно проверить ввод или вывод функции. Давайте создадим оператор Print() и напечатаем содержимое всех соответствующих переменных в журнале.
Мы запустим советника в тестере стратегий, используя цены открытия в качестве модели тестирования. Убедитесь, что вы тестируете советника в течение достаточно длительного периода времени, чтобы он разместил достаточно сделок для анализа. Если вам нужно проверить цены на графике, нажмите кнопку «Открыть график», чтобы открыть график, показывающий симулированные сделки.
Далее мы перейдем на вкладку «Журнал» и проверим, какая информация нам нужна. Если нам нужно просмотреть весь журнал или если на вкладке «Журнал» не отображаются сделки, мы можем щелкнуть правой кнопкой мыши и выбрать «Открыть» во всплывающем меню, а затем напрямую открыть файл журнала.
Этот код дает нам ошибку 130: «недействительные стопы» каждый раз, когда мы размещаем заказ на покупку. Мы знаем, что ошибка 130 означает, что либо стоп-лосс, либо тейк-профит неверны.
if(Close[0] > MA && BuyTicket == 0) { double OpenPrice = Ask; double BuyStopLoss = OpenPrice + (StopLoss * UsePoint); double BuyTakeProfit = OpenPrice + (TakeProfit * UsePoint); BuyTicket = OrderSend(Symbol(),OP_BUY,LotSize,OpenPrice,UseSlippage, BuyStopLoss,BuyTakeProfit,"Buy Order",MagicNumber,0,Green); SellTicket = 0; }
Мы будем использовать функцию Print() для проверки параметров, передаваемых в функцию OrderSend(). Мы сосредоточимся на цене открытия ордера, стоп-лоссе и тейк-профите.
Print("Price:"+OpenPrice+" Stop:"+BuyStopLoss+" Profit:"+BuyTakeProfit);
Вот вывод, когда мы запускаем советник в тестере стратегий. Стоп-лосс и тейк-профит в размере 50 пунктов:
11:52:12 2009.11.02 02:00 Example EURUSD,H1: OrderSend error 130 11:52:12 2009.11.02 02:00 Example EURUSD,H1: Price:1.47340000 Stop:1.47840000 Profit:1.47840000
Мы знаем, что стоп-лосс должен быть ниже цены открытия ордера на покупку. Здесь это значение выше цены. На самом деле это та же цена, что и тейк-профит. Беглый взгляд на наш код, и мы понимаем, что случайно вставили знак плюс в уравнение покупки стоп-лосса. Вот правильный код:
double BuyStopLoss = OpenPrice - (StopLoss * UsePoint);
Если вы получаете сообщение об ошибке при попытке разместить, закрыть или изменить ордер, сфокусируйте свои усилия на проблеме, указанной в сообщении об ошибке. Вот несколько наиболее распространенных сообщений об ошибках, вызванных ошибками программирования:
Ошибка 129: недействительная цена — недействительная цена открытия. Для рыночных ордеров убедитесь, что текущая цена Bid или Ask передается в соответствии с типом ордера. Для отложенных ордеров убедитесь, что цена выше или ниже текущей цены, как того требует тип ордера. Также убедитесь, что цена отложенного ордера не слишком близка к текущей цене.
Ошибка 130: недопустимые стопы — неверный стоп-лосс или тейк-профит. Убедитесь, что цены стоп-лосс и тейк-профит расположены выше или ниже текущей цены, в зависимости от того, является ли тип ордера покупкой или продажей. Также убедитесь, что цена стоп-лосса или тейк-профита не слишком близка к текущей цене.
Ошибка 131: неверный объем сделки — неверный размер лота. Убедитесь, что размер лота не превышает минимального или максимального значения брокера и что размер лота нормализован до правильного значения шага (0,1 или 0,01 для большинства брокеров).
Описания всех сообщений об ошибках можно найти в справочнике по MQL в разделе Стандартные константы — коды ошибок.
Устранение ошибок в случае прерывания торговли
В то время как наиболее серьезные ошибки могут быть обнаружены просто путем бэк-тестирования, другие будут появляться только во время торговли в реальном времени. Ошибки могут привести к тому, что сделки будут размещены неправильно, и эти ошибки могут потребовать определенных усилий для их обнаружения.
Мы добавим дополнительную функцию для регистрации информации о сделках и статусе в режиме реального времени, чтобы у нас была запись об этом при устранении неполадок. Мы будем использовать операторы Print(), как и ранее, но мы будем регистрировать значения индикаторов цены — любую информацию, которая будет полезна при отладке.
// Внешние переменные extern bool Debug = true; if(Debug == true) Print(StringConcatenate("Bid:",Bid," Ask:",Ask," MA:",MA," BuyTicket:",BuyTicket," SellTicket:",SellTicket));
Приведенный выше код будет регистрировать информацию о цене и индикаторе, а также содержимое переменных BuyTicket и SellTicket. Если есть какие-либо вопросы о том, как была открыта сделка или почему она не была открыта, журнал в этот конкретный момент покажет состояние всех соответствующих условий сделки. Вы можете включать и выключать вход в систему с помощью внешней переменной Debug.
Оператор отладки Print() должен быть помещен в конце функции start() после всех торговых функций. Если вы используете таймер, поместите оператор debug Print() в блок таймера, чтобы он работал только при необходимости. В противном случае строка отладки будет выводиться в журнал при каждом тике, что может привести к разрастанию файла журнала.
Обработка ошибок компиляции
Когда вы компилируете свой советник, компилятор проверит синтаксис вашего кода и убедится, что все пользовательские функции и переменные были правильно объявлены. Если вы что-то пропустили, компилятор остановится, и любые ошибки компиляции появятся на вкладке Errors в окне Toolbox.
Когда сталкиваешься с длинным списком ошибок компиляции, всегда начинайте с первой. Дважды щелкните ошибку в списке, и редактор перейдет непосредственно к строке с ошибкой. Исправьте ошибку и перекомпилируйте. Иногда простая синтаксическая ошибка приводит к нескольким несвязанным ошибкам.
Вот список распространенных ошибок компиляции и их решения:
Variable not defined — вы забыли объявить переменную с типом данных. Если это глобальная или внешняя переменная, объявите ее в верхней части файла. Если это локальная переменная, найдите первое вхождение и поместите перед ним объявление типа данных. В противном случае проверьте правильность написания или регистр (верхний / нижний) имени переменной.
Variable already defined — вы объявили одну и ту же переменную дважды. Удалите объявление типа данных из всех дублированных объявлений переменных.
Function is not defined — если рассматриваемая функция находится в файле включения или библиотеки, убедитесь, что директива #include или #import находится в верхней части файла и является правильной. В противном случае проверьте правильность написания или регистр имени функции и убедитесь, что оно существует либо в текущем файле, либо в соответствующих файлах включений или библиотек.
Illegal assignment used — обычно это относится к знаку равенства (=). Помните, что один знак равенства предназначен для присваивания переменной, а два знака равенства (==) — оператор сравнения. Исправьте оператор присваивания в соответствующем операторе сравнения.
Assignment expected — обычно это относится к оператору сравнения «равно» (==). Вы использовали два знака равенства вместо одного в присваивании переменной. Исправьте оператор в один знак равенства.
Unbalanced right parenthesis — обычно это происходит в операторе if при использовании вложенных скобок. Перейдите к строке, обозначенной первой ошибкой, и вставьте левую скобку в соответствующее место.
Unbalanced left parenthesis — это сложная задача. Ошибка обычно указывает на конец строки программы. По сути, вы где-то забыли правильную скобку. Дважды проверьте код, который вы недавно редактировали, и найдите пропущенную правую скобку. Возможно, вам придется закомментировать строки кода, чтобы найти проблему.
Wrong parameters count — у вас слишком мало или слишком много аргументов в функции. Дважды проверьте синтаксис функции в MQL Reference и исправьте аргументы.
Semicolon expected — вы, вероятно, забыли поставить точку с запятой в конце строки. Поставьте точку с запятой в конце предыдущей строки. Обратите внимание, что пропущенная точка с запятой может вызвать любую из перечисленных выше ошибок, поэтому обязательно ставьте точки с запятой.