English 中文 Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Отладка программ на MQL5

Отладка программ на MQL5

MetaTrader 5Примеры | 1 марта 2013, 08:47
12 376 19
Mykola Demko
Mykola Demko

Введение

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

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

Существуют две дополняющие друг друга технологии отладки:

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

Итак, предположим что вы знаете MQL5: что такое переменные, зачем нужны структуры и многое другое. Но вы еще не разрабатывали программы самостоятельно. Первое действие, которое вы сделаете - это компиляция. С нее, по сути, и начинается этап отладки.


1. Компиляция

Компиляция - трансляция программы, составленной на исходном языке высокого уровня, в эквивалентную программу на низкоуровневом языке.

Хочу отдельно отметить, что компилятор MetaEditor транслирует программы в байт-код, а не нативный код (подробнее читаем по ссылке). Это дает возможность создавать защищенные шифрованием программы, не боясь взлома. Также преимуществом такой трансляции является то, что байт-код может быть запущен как в 32-х битной версии операционной системы, так и в 64-х битной.

Но мы отвлеклись. Итак, компиляция - первый этап отладки. После нажатия F7 (либо кнопки "Компилировать") MetaEditor 5 сообщит вам обо всех ошибках, которые вы допустили при написании кода. В окне "Инструменты", во вкладке "Ошибки" можно найти описание ошибки и строчку кода, в которой она находится. Выделив курсором строку описания и нажав Enter, можно прямо перейти к ошибке.

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

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

Синтаксические ошибки часто возникают по невнимательности. Например, объявляя переменные, можно легко спутать "," и ";":

int a; b; // неправильное объявление

В результате такого объявления компилятор выдаст ошибку. Правильное же объявление будет выглядеть следующим образом:

int a, b; // правильное объявление

Либо так:

int a; int b; // правильное объявление

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

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

Например, вы хотите провести сравнение двух переменных:

if(a==b) { } // если a равно b то ...

Но из-за опечатки или в результате забывчивости используете "=" вместо "==". В этом случае компилятор интерпретирует этот код следующим образом:

if(a=b) { } // присвоить b в a, если a истинно то ... (в MQL5 в отличие от MQL4 это допустимо)

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

Подведем итог: компиляция - первый этап отладки. Не стоит пренебрегать предупреждениями компилятора.

Рис. 1. Отладочная информация на этапе компиляции.

Рис. 1. Отладочная информация на этапе компиляции.


2. Отладчик

Вторым этапом отладки является использование Отладчика (функциональная клавиша F5). Отладчик запускает вашу программу в режиме эмуляции с пошаговым ее прохождением. Использование отладчика - это нововведение MetaEditor 5, в четвертом MetaEditor его нет. Поэтому нет и практики его использования программистами, переходящими из языка MQL4 на MQL5.

Интерфейс отладчика имеет три основных и три вспомогательных кнопки:

  • Начать [F5] - начать отладку.
  • Приостановить [Break] - приостановить отладку.
  • Завершить [Shift+F5] - завершить отладку.
  • Шаг с заходом [F11] - при нажатии вы попадаете внутрь функции, которая вызывается в данной строке.
  • Шаг с обходом [F10] - при нажатии отладчик проигнорирует тело функции, вызываемой в данной строке, и перейдет к следующей строке.
  • Шаг наружу [Shift+F11] - при нажатии вы выйдете из тела функции, в которой сейчас находитесь.

Таков вкратце интерфейс отладчика. Но как же им пользоваться? Отладка программы начинается со строчки, в которую программист поставит специальную функцию отладки DebugBreak(), либо с точки останова, которую можно установить при помощи клавиши F9 или кнопки на панели инструментов:

Рис. 2. Расстановка точек останова.

Рис. 2. Расстановка точек останова.

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

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

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

Переменную так же можно добавлять, воспользовавшись горячей клавишей [Shift+F9], предварительно выделив ее, либо из контекстного меню, как это показано на рисунке ниже:

Рис. 3. Добавление наблюдения за переменной при отладке.

Рис. 3. Добавление наблюдения за переменной при отладке.

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

Вы можете не беспокоиться, что переменная, которая вас интересует, объявлена локально, но вы еще не дошли до функции, в которой она объявляется. Пока вы находитесь вне области видимости данной переменной, она будет иметь значение "Unknown identifier" - неизвестный идентификатор. То есть переменная не объявлена, и ошибку отладчика это не вызовет. При этом, дойдя до области видимости переменной, вы увидите ее значение и тип.

Рис. 4. Процесс отладки. Наблюдение за значениями переменных.

Рис. 4. Процесс отладки. Наблюдение за значениями переменных.

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


3. Профилировщик

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

Профилировщик вызывается из меню MetaEditor 5 при помощи кнопки "Начало профилирования". Это не пошаговый анализ программы, как в отладчике, а общее ее прохождение. Если программа является индикатором или советником, то профилировщик будет работать до ее выгрузки. Сделать выгрузку можно как удалив индикатор или советник с чарта, так и при помощи кнопки "Завершить профилирование".

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

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

Рис. 5. Результаты работы профилировщика.

Рис. 5. Результаты работы профилировщика.


4. Интерактивность

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

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

Помимо значений переменных, иногда требуется знать последовательность вызовов самих функций. Для этих целей удобно использовать макросы "__FUNCTION__" и "__FUNCSIG__". Их особенность заключается в том, что первый возвращает в виде строки имя функции, из которой он вызывается, а второй - в дополнение к этому выводит список параметров вызываемой функции.

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

//+------------------------------------------------------------------+
//| Пример вывода информации для отладки                             |
//+------------------------------------------------------------------+
void myfunc(int a)
  {
   Print(__FUNCSIG__); // вывод информации для отладки 
//--- тут какой-то код самой функции
  }

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

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

//+------------------------------------------------------------------+
//| Пример вывода информации для отладки                             |
//+------------------------------------------------------------------+
void myfunc(int a)
  {
//--- объявление статического счетчика
   static int cnt=0;
//--- условие для вызова функции
   if(cnt==1013)
      Print(__FUNCSIG__," a=",a); // вывод информации для отладки
//--- увеличим счетчик
   cnt++;
//--- тут какой-то код самой функции
  }

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

Для того чтобы устранить этот недостаток комментария, можно применить прием дописывания новой строки в переменную. Сначала объявляется (обычно на глобальном уровне) и инициализируется пустым значением переменная типа string. Затем каждая новая строка текста помещается в начало с добавлением символа перевода строки, а предыдущее значение переменной приписывается в конец.

string com=""; // объявление глобальной переменной для хранения информации для отладки
//+------------------------------------------------------------------+
//| Пример вывода информации для отладки                             |
//+------------------------------------------------------------------+
void myfunc(int a)
  {
//--- объявление статического счетчика
   static int cnt=0;
//--- сохранение информации для отладки в глобальную переменную
   com=(__FUNCSIG__+" cnt="+(string)cnt+"\n")+com;
   Comment(com); // вывод информации для отладки
//--- увеличим счетчик
   cnt++;
//--- тут какой-то код самой функции
  }

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

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

string com=""; // объявление глобальной переменной для хранения информации для отладки
//+------------------------------------------------------------------+
//| Завершение работы программы                                      |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- сброс данных в файл при завершении работы программы
   WriteFile();
  }
//+------------------------------------------------------------------+
//| Пример вывода информации для отладки                             |
//+------------------------------------------------------------------+
void myfunc(int a)
  {
//--- объявление статического счетчика
   static int cnt=0;
//--- сохранение информации для отладки в глобальную переменную
   com+=__FUNCSIG__+" cnt="+(string)cnt+"\n";
//--- увеличим счетчик
   cnt++;
//--- тут какой-то код самой функции
  }
//+------------------------------------------------------------------+
//| Сброс данных в файл                                              |
//+------------------------------------------------------------------+
void WriteFile(string name="Отладка")
  {
//--- открытие файла
   ResetLastError();
   int han=FileOpen(name+".txt",FILE_WRITE|FILE_TXT|FILE_ANSI," ");
//--- проверка, открылся ли файл
   if(han!=INVALID_HANDLE)
     {
      FileWrite(han,com); // печать данных
      FileClose(han);     // закрытие файла
     }
   else
      Print("File open failed "+name+".txt, error",GetLastError());
  }

Функция "WriteFile" вызывается в "OnDeinit", таким образом в файл записываются все изменения произошедшие в программе.

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

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

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

//--- открытие файла
int han=FileOpen("Отладка.txt",FILE_WRITE|FILE_TXT|FILE_ANSI," ");
//--- печать данных
if(han!=INVALID_HANDLE) FileWrite(han,com);
if(han!=INVALID_HANDLE) FileWrite(han,com);
if(han!=INVALID_HANDLE) FileWrite(han,com);
if(han!=INVALID_HANDLE) FileWrite(han,com);
//--- закрытие файла
if(han!=INVALID_HANDLE) FileClose(han);

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

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


5. Тестер

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

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

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

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

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

Рис.6. Проведение отладки при помощи тестера стратегий.

Рис.6. Проведение отладки при помощи тестера стратегий.


6. Отладка в ООП

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

Благодаря шаблонной функции можно получить тип указателя в виде значения типа string.

template<typename T> string GetTypeName(const T &t) { return(typename(T)); }

Для отладки я использую это свойство следующим образом:

//+------------------------------------------------------------------+
//| Базовый класс, содержит переменную для хранения типа             |
//+------------------------------------------------------------------+
class CFirst
  {
public:
   string            m_typename; // переменная для хранения типа
   //--- заполнение переменой собственным типом в конструкторе
                     CFirst(void) { m_typename=GetTypeName(this); }
                    ~CFirst(void) { }
  };
//+------------------------------------------------------------------+
//| Производный класс, изменяет значение переменной базового класса  |
//+------------------------------------------------------------------+
class CSecond : public CFirst
  {
public:
   //--- заполнение переменой собственным типом в конструкторе
                     CSecond(void) { m_typename=GetTypeName(this); }
                    ~CSecond(void) {  }
  };

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

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

Print((string)this); // печать номера указателя внутри класса

А снаружи так:

Print((string)GetPointer(pointer)); // печать номера указателя вне класса

Также внутри каждого класса можно использовать переменную для хранения имени самого объекта. Тогда, при создании объекта, его имя можно передать в качестве параметра конструктора. В этом случае вы не только сможете разделять объекты по их номерам, но и понимать, что каждый из объектов означает (ведь вы сами будете давать им имена). Этот подход можно реализовать по аналогии с заполнением переменной m_typename.


7. Трассировка

Все вышеперечисленные методы дополняют друг друга и очень важны для отладки. Но есть еще один не очень распространенный метод - это трассировка.

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

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

Трассировка проводится следующим образом. Создаем два макроса:

//--- открывающая подстановка  
#define zx Print(__FUNCSIG__+"{");
//--- закрывающая подстановка
#define xz Print("};");

Это соответственно открывающий zx и закрывающий xz макрос. Затем размещаем их в телах функций, которые нужно трассировать:

//+------------------------------------------------------------------+
//| Пример трассировки функции                                       |
//+------------------------------------------------------------------+
void myfunc(int a,int b)
  {
   zx
//--- тут какой-то код самой функции
   if(a!=b) { xz return; } // выход в середине функции
//--- тут какой-то код самой функции
   xz return;
  }

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

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

if() {...}

Результирующий же файл задаю с расширением ".mqh", что дает возможность открыть его в MetaEditor и воспользоваться стайлером [Ctrl+,] для наглядного отображения структуры трассировки.

Полный код для трассировки приведен ниже:

string com=""; // объявление глобальной переменной для хранения информации для отладки
//--- открывающая подстановка
#define zx com+="if("+__FUNCSIG__+"){\n";
//--- закрывающая подстановка
#define xz com+="};\n"; 
//+------------------------------------------------------------------+
//| Завершение работы программы                                      |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- //--- сброс данных в файл при завершении работы программы
   WriteFile();
  }
//+------------------------------------------------------------------+
//| Пример трассировки функции                                       |
//+------------------------------------------------------------------+
void myfunc(int a,int b)
  {
   zx
//--- тут какой-то код самой функции
   if(a!=b) { xz return; } // выход в середине функции
//--- тут какой-то код самой функции
   xz return;
  }
//+------------------------------------------------------------------+
//| Сброс данных в файл                                              |
//+------------------------------------------------------------------+
void WriteFile(string name="Трассировка")
  {
//--- открытие файла
   ResetLastError();
   int han=FileOpen(name+".mqh",FILE_WRITE|FILE_TXT|FILE_ANSI," ");
//--- проверка, открылся ли файл
   if(han!=INVALID_HANDLE)
     {
      FileWrite(han,com); // печать данных
      FileClose(han);     // закрытие файла
     }
   else
      Print("File open failed "+name+".mqh, error",GetLastError());
  }

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

bool trace=0; // переменная для защиты трассировки условием
//--- открывающая подстановка
#define zx if(trace) com+="if("+__FUNCSIG__+"){\n";
//--- закрывающая подстановка
#define xz if(trace) com+="};\n";

Тогда, установив после определенного события или в определенном месте в переменную "trace" значение "true" или "false", можно включать или отключать трассирование.

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

//--- подстановка пустых значений
#define zx
#define xz

Файл стандартного советника с изменениями для трассировки прикреплен ниже. Результаты трассировки вы можете увидеть после запуска советника на чарте, в директории Files (будет создан файл Трассировка.mqh). Вот часть текста результирующего файла:

if(int OnInit()){
};
if(void OnTick()){
if(void CheckForOpen()){
};
};
if(void OnTick()){
if(void CheckForOpen()){
};
};
if(void OnTick()){
if(void CheckForOpen()){
};
};
//--- ...

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

if(int OnInit())
  {
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
if(void OnTick())
  {
   if(void CheckForOpen())
     {
     };
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
if(void OnTick())
  {
   if(void CheckForOpen())
     {
     };
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
if(void OnTick())
  {
   if(void CheckForOpen())
     {
     };
  };
//--- ...

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


Важное замечание по отладке

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

//+------------------------------------------------------------------+
//| Пример оборачивания стандартной функции, функцией оболочкой      |
//+------------------------------------------------------------------+
void DebugPrint(string text) { Print(text); }

Это позволит вам почистить код, когда отладка будет закончена довольно простым способом:

  • уберите объявление функции "DebugPrint",
  • нажмите компилировать,
  • и удалите вызовы этой функции в строчках, в которых MetaEditor выдаст вам ошибки компиляции.

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


Заключение

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

Удачной отладки!

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (19)
Vladimir Karputov
Vladimir Karputov | 4 сент. 2018 в 12:00
rel18:

Вопрос про отладку на реальных данных:

В режиме отладки на реальных данных торговые операции будут выполняться по настоящему? Или их выполнение будет моделироваться как в тестере стратегий?

Чтобы не наломать дров (не совпадение терминологии), подробно опишите ГДЕ и  ЧТО Вы нажимаете.

rel18
rel18 | 4 сент. 2018 в 12:27
Vladimir Karputov:

Чтобы не наломать дров (не совпадение терминологии), подробно опишите ГДЕ и  ЧТО Вы нажимаете.

Есть код эксперта, открытый в MetaEditor. Я расставляю точки останова и запускаю отладчик. У отладчика два режима: отладка на исторических данных и отладка на реальных данных. И две соответвующие кнопки на панели MetaEditor. Отладку на исторических данных MetaEditor запускает в тестере, а отладку на реальных данных - в торговом терминале, как написано в справке, на "специальном графике". Будет ли при эксперт, запущенный в отладку на реальных данных на этом специальном графике, торговать по настоящему?

Rashid Umarov
Rashid Umarov | 4 сент. 2018 в 12:39
rel18:

Есть код эксперта, открытый в MetaEditor. Я расставляю точки останова и запускаю отладчик. У отладчика два режима: отладка на исторических данных и отладка на реальных данных. И две соответвующие кнопки на панели MetaEditor. Отладку на исторических данных MetaEditor запускает в тестере, а отладку на реальных данных - в торговом терминале, как написано в справке, на "специальном графике". Будет ли при эксперт, запущенный в отладку на реальных данных на этом специальном графике, торговать по настоящему?

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

Veresk710
Veresk710 | 30 июн. 2020 в 23:18
Один и тот же индикатор на одной и той же свечке, таймфрейме и при тех же значениях параметров показывает разные значения при тестировании на реальных данных и при тестировании на исторических данных. 
Отладка на реальных данных показывает то же значение, что и в графике в окне терминала, сигнал есть, а отладка на исторических данных "подвирает", сигнал теряется.

Как вылечить?
Vladimir Karputov
Vladimir Karputov | 1 июл. 2020 в 06:00
Veresk710:
Один и тот же индикатор на одной и той же свечке, таймфрейме и при тех же значениях параметров показывает разные значения при тестировании на реальных данных и при тестировании на исторических данных. 
Отладка на реальных данных показывает то же значение, что и в графике в окне терминала, сигнал есть, а отладка на исторических данных "подвирает", сигнал теряется.

Как вылечить?

Лечение: как минимум тестирование на истории проводить в режиме "Каждый тик на основе реальных тиков". Лечение два: исправлять ошибки в своём коде.

Рецепты MQL5 - Свойства позиции на пользовательской информационной панели Рецепты MQL5 - Свойства позиции на пользовательской информационной панели
На этот раз создадим простого эксперта, который во время ручной торговли будет показывать свойства позиции по текущему символу на пользовательской информационной панели, которая будет собрана из графических объектов. Данные будут обновляться на каждом тике, что уже намного удобнее, чем постоянно запускать вручную скрипт, который описывался в предыдущей статье "Рецепты MQL5 - Как получить свойства позиции?".
Рецепты MQL5 - Как получить свойства позиции? Рецепты MQL5 - Как получить свойства позиции?
В этой статье мы создадим скрипт, который получает все свойства позиции и показывает их пользователю в диалоговом окне. При запуске скрипта во внешних параметрах можно будет выбрать из выпадающего списка один из двух режимов: показать свойства позиции только на текущем символе или просмотреть свойства позиций на всех символах.
Рецепты MQL5 - Изучение свойств позиции в тестере MetaTrader 5 Рецепты MQL5 - Изучение свойств позиции в тестере MetaTrader 5
Модифицированная версия эксперта из предыдущей статьи "Рецепты MQL5 - Свойства позиции на пользовательской информационной панели". Рассмотрим ряд вопросов: получение данных баров, отслеживание события "новый бар" на текущем символе, подключение торгового класса стандартной библиотеки, создание функции поиска торговых сигналов, создание функции для выполнения торговых операций, а также определение торгового события в функции OnTrade().
Рецепты MQL5 -  Вывод информации на печать в разных режимах Рецепты MQL5 - Вывод информации на печать в разных режимах
Это первая статья из серии "Рецепты MQL5". Я начну с простых примеров, чтобы те, кто только начинает изучать программирование, могли плавно погрузиться в изучение этого языка. Я вспоминаю, как я начинал изучать разработку и программирование торговых систем, и, признаться, мне это было довольно сложно, так как это мой первый язык. Но всё оказалось не так сложно, и уже через несколько месяцев я создал довольно сложную программу.