preview
Изучение MQL5 — от новичка до профи (Часть VII): Принципы отладки приложений MQL

Изучение MQL5 — от новичка до профи (Часть VII): Принципы отладки приложений MQL

MetaTrader 5Примеры |
104 0
Oleh Fedorov
Oleh Fedorov

Введение

Предыдущая статья цикла рассказывала о принципе построения советников в MQL5. Эту статью я собирался посвятить надёжным советникам для Маркета, учитывающим обработку исправимых ошибок (в том числе — ответов сервера). И даже начал её писать.

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

Все люди ошибаются — кто-то чаще, кто-то реже… Если вы — новичок в каком-то деле, вы будете совершать очень много типичных ошибок, которые профессионал видит сразу и избегает автоматически. Однако и профессионалы иногда ошибаются. Просто их ошибки зачастую труднее обнаруживаются и имеют больше последствий. И программирование в этом смысле ничем не отличается от любой другой человеческой деятельности. Однако, в отличие от многих других сфер жизни, ошибки программирования достаточно просто исправить — был бы доступен код программы, да нашлось бы достаточно времени.

Процесс исправления ошибок программы называют отладкой. И отладка является почти обязательным этапом при написании любой программы, если эта программа имеет больше 100 строк. Жить без ошибок невозможно, но исправлять их — наше "священное" право. Поэтому в этой статье я расскажу об основах отладки программ MQL5 встроенными средствами среды MetaEditor. Думаю, профи не найдут здесь каких-то особых откровений — всё же цикл для новичков. Но если я что-то упущу или расскажу неправильно — комментарии, как всегда, доступны всем.


Исследование сообщений об ошибках

Простейшим случаем ошибок являются опечатки и нарушения синтаксиса языка. Например, если вы переписываете индикатор, написанный на MQL4, вероятно, вам придётся исправить несколько вызовов функций вроде iMA или ObjectFind, поскольку эти названия существуют в обеих версиях языка, но используют разное число параметров и часто возвращают разные по смыслу значения. Такие случаи очень просты, ибо компилятор автоматически их находит и сразу сообщает в соответствующем окне подробную информацию. Например, попытайтесь скомпилировать следующий код:

//+------------------------------------------------------------------+
//|                                            ErrorsResearching.mq5 |
//|                                       Oleg Fedorov (aka certain) |
//|                                   mailto:coder.fedorov@gmail.com |
//+------------------------------------------------------------------+
#property copyright "Oleg Fedorov (aka certain)"
#property link      "mailto:coder.fedorov@gmail.com"
#property version   "1.00"
#property indicator_chart_window

int g_someInt; // Здесь всё в порядке

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
 {
//--- indicator buffers mapping
  g_some_Int = 5; // Здесь ошибка. Имя переменой указано неправильно
//---
  return(INIT_SUCCEEDED);
 }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
 {
//---

//--- return value of prev_calculated for next call
  return(rates_total);
 }
//+------------------------------------------------------------------+

Пример 1. Код, приводящий к ошибке компиляции

Результат компиляции этого примера представлен на рисунке 1. Компилятор сообщит об ошибке, поскольку имя переменной  внутри функции указано неправильно.

Сообщение об ошибке (неописанная переменная)

Рисунок 1. Сообщение об ошибке (неописанная переменная)

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

  • желтым треугольником обозначаются предупреждения (warnings); в принципе, обозначенные таким образом строки можно проигнорировать, однако, в некоторых случаях их наличие может приводить к сбою логики программы, поэтому их всё-таки стоит внимательно проанализировать;
  • красным знаком "стоп" обозначаются ошибки (errors), из-за которых компиляция становится невозможной;
  • серыми точками обозначены нейтральные сообщения, ошибками не являющиеся, а просто сообщающие какую-то информацию.

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

Иногда компилятор может выдать два и более сообщения по повод одной ошибки. Например, если операцию присвоения в примере 1 перенести в глобальную область видимости, как сделано в примере 2:

#property indicator_chart_window

int g_someInt; // Здесь всё в порядке
g_someInt = 5; // Здесь ошибка. Нельзя выполнять операции 
               // (кроме инициализации) в глобальной области видимости

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

Результат попытки компиляции примера 2 показан на рисунке 2.

Одна ошибка может быть причиной нескольких сообщений

Рисунок 2. Одна ошибка может быть источником нескольких сообщений

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

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

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

Английский
 Русский  Описание
unexpected неожиданный Чаще всего возникает, когда пропущено какое-то действие или важный символ вроде точки с запятой в конце оператора. Например, в примере 2 компилятор считает, что при описании переменной пропущено описание типа данных.
undeclared неописанный Как правило, возникает, когда программист делает ошибку в имени переменной или забывает описать функцию, которую где-то вызывает.
unbalanced
несбалансированный Обычно о скобках. Допустим, есть открывающая, но нет закрывающей, или наоборот.
missing отсутствует Компилятор не нашёл чего-то важного.
already уже́ Обычно в контексте "уже́ существует".
wrong ошибочный, неправильный Чаще всего встречается в сообщениях о неверном количестве параметров при вызове функции.
invalid некорректный, недопустимый Например, следующий код
int array_variable[];
array_variable = 5;
вызовет ошибку "invalid array access" — "некорректный доступ к массиву", поскольку я попытался записать число не в ячейку массива, а непосредственно в переменную.

Таблица 1. Краткий словарь английских слов в сообщениях об ошибках


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

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

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

К моему сожалению, компилятор эту ошибку свободно пропустит, поскольку формально всё будет соответствовать синтаксису языка. Однако, в момент выполнения программа выдаст сообщение об ошибке: "индекс массива находится за пределами допустимого диапазона значений" ("array out of range"). Посмотреть такие сообщения (как и все остальные сообщения вашей программы) можно на вкладке "Experts" терминала (рисунок 3).

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

Рисунок 3. Сообщение об ошибке времени выполнения

Кстати, ошибки "out of range" (как в данном примере) встречаются очень часто. Наиболее типичные случаи:

  • данные о котировках за какой-то период ещё не подгрузились, но программа пытается их прочитать; 
  • неверно указан индекс массива, например, программист забывает, что последний индекс массива на 1 меньше, чем его длина, как в третьей (закомментированной) строке следующего примера:
    int testArray[3]={3,4,5};
    int size = ArraySize(testArray);
    // Print (testArray[size]); // Ошибка! Программа пытается обратиться к несуществующему элементу массива
    Print (testArray[size-1]); // Всё в порядке

     Пример 3. Типичный случай возможной ошибки неверного индекса массива

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


С чего начинается отладка

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

Начните с начала. Если это ваш код, то, конечно, вы его знаете лучше всех. Если же чужой — попробуйте понять, что он делает. Только для начала не вникайте в конкретику. Первый взгляд бросьте как бы "с высоты".

  • Вспомните, что в экспертах и индикаторах всё начинается с функции OnInit, а в скриптах и сервисах — с OnStart. Каждая из этих функций отрабатывает ровно один раз — при старте программы (хотя в сервисах, как правило, добавляют бесконечный цикл, обрабатывающий какие-то события при торговле).
  • В индикаторах на каждом тике вызывается функция OnCalculate, в экспертах — OnTick. Также в экспертах или индикаторах может быть установлен таймер, который обрабатывается функцией OnTimer через регулярные промежутки времени. 
  • Эти пять функций (OnInit, OnStart, OnCalculate, OnTick, OnTimer) являются точками входа. С них должен начинаться любой анализ чужого кода. Если в них есть комментарии — это большой плюс, просто просмотрите их. Велика вероятность, что с помощью комментариев выделяются основные блоки логики. Если комментариев нет, попробуйте быстро увидеть, какие функции используются, и по названиям попытайтесь понять, что они делают. Если код хорошо структурирован, и автор не выполняет всё в одной функции — вам повезло. Если нет — тоже не беда, хотя, возможно, отладка потребует больше времени. В любом случае, постарайтесь понять этапы выполнения алгоритма, относительно большие блоки, такие как "расчёт параметров", "проверка условий" (скажем, рисовать/не рисовать), "проход по всем свечам" — ну, и так далее. Понятно, что список далеко не полон, и он очень сильно зависит от задачи.
  • Посмотрите список дополнительных функций, описанных в данном файле, с помощью специальной кнопки на панели инструментов ( кнопка для отображения списка функций ) или сочетанием клавиш <Alt>+<M> и постарайтесь по названиям понять, для чего они нужны;
  • Начинающим крайне полезно бывает в ключевых точках расставить комментарии с названиями блоков (если этих комментариев нет). Также очень полезно нарисовать блок-схему алгоритма (например, с помощью языка ДРАКОН или любого другого удобного для вас инструмента). Еще раз повторюсь: схему на данном этапе рисовать сто́ит относительно крупными блоками.
  • Если вы добавили буфер в индикатор, и индикатор вдруг перестал рисовать вообще, проверьте, что:
    • при соотнесении буферов с массивами (вызовы функции SetIndexBuffer) у вас не сбилась нумерация, то есть все номера идут последовательно и без пропусков, начиная с 0;
    • свойство indicator_buffers точно отображает общее количество буферов, а свойство indicator_plots точно описывает количество буферов, видимых на экране;
    • свойства indicator_type, indicator_color и т.д. также имеют правильную нумерацию, однако их номера начинаются с 1, и при этом вы описали каждый отображаемый буфер — не важно, с помощью свойств или с помощью встроенных функций.
И вот только теперь, когда вы понимаете логику работы отлаживаемой программы и точно понимаете, что простейших опечаток для индикатора вы избежали, но программа всё равно отказывается работать так, как нужно вам, и при этом не выводит ошибок во время выполнения, можно попробовать приступить к отладке.
 


Использование отладочных сообщений

Самым давним способом поиска ошибок является вывод сообщений с помощью функции Print (или PrintFormat, кому как больше нравится) в ключевых точках программы.

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

Понятно, что такие сообщения должны быть информативны и точно давать программисту понять, где именно он находится, и что именно там происходит. Часто в них добавляют специальные комбинации символов, в обычном обиходе не встречающиеся (вроде "~~" или чего-то подобного), чтобы понимать, какого типа это сообщение, или откуда оно поступило. Основной принцип применения очень прост: сообщения расставляются в ключевых местах кода, и с их помощью программист может контролировать ситуацию в выполняющейся программе.

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

void OnStart()
 {
//---
  int i;
//---
  Print("Program starting, 'i' hasn't initialized");
  for(i=0;i<3;i++);
   {
    Print("In the cycle 'i="+i+"'");
   }
  Print("After cycle 'i="+i+"'");
 }

Пример 4. Код программы, которая работает не так.

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

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

Program starting, 'i' hasn't initialized
In the cycle 'i=3'
After cycle 'i=3'

Пример 5. Вывод программы из примера 3.

Результат неожиданный… Почему только три сообщения? Почему внутри цикла выводится число '3', хотя должны быть числа от 0 до 2? Должно ли число во второй строке быть равным числу в третьей? Начинаем думать.

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

Исходя из вышесказанного, можно предположить, что проблема — где-то между выражением for(i=0; …) и выводом нужных нам данных. Присмотримся, что там — между… Собственно, там есть фигурная скобка и точка с запятой.

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

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

Program starting, 'i' hasn't initialized
In the cycle 'i=0'
In the cycle 'i=1'
In the cycle 'i=2'
After cycle 'i=3'

Пример 6. Вывод исправленного примера

Получилось! Задача решена.

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

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

#define DEBUG
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
 {
//---
  int i;
//---
#ifdef DEBUG
  Print("Program starting, 'i' hasn't initialized");
#endif

  for(i=0;i<3;i++) // NO semicolon!!!
   {
    Print("In the cycle 'i="+i+"'");
   }

#ifdef DEBUG
  Print("After cycle 'i="+i+"'");
#endif

 }

Пример 7. Использование директив условной компиляции.

В данном примере я объявил некоторый макрос с именем DEBUG, а затем, в тех местах программы, которые нужны только при отладке, попросил препроцессор включать код вывода сообщений в скомпилированный файл только в том случае, если макрос DEBUG существует. Существование макроса проверяется с помощью директивы #ifdef. Иными словами, всё, что находится между #ifdef и #endif, не будет скомпилировано, если макроса с именем DEBUG в программе не окажется. Таким образом можно включать и выключать эти фрагменты простым удалением или добавлением первой строчки — в одном месте вашего проекта, даже если проект содержит несколько файлов.

Ну и в завершение главы скажу, что конкретно эту ошибку (из примера 4) можно было бы найти еще проще, если бы мы сразу при компиляции обратили внимание на предупреждение, которое компилятор пытался до нас донести:

empty controlled statement found        ErrorsResearching.mq5   23      19

Пример 8. Предупреждение компилятора о пустом управляющем выражении

В этом сообщении сказано, что компилятор нашёл "пустое управляющее выражение". Если вы видите такое сообщение, вероятнее всего, по приведённым координатам как раз и стоит лишняя точка с запятой. Обязательно проверяйте!


Использование интерактивного отладчика в программах на MQL5

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

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

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

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

      Переключение точки останова двойным щелчком

      Рисунок 4. Переключение точки останова двойным щелчком

    • клавиша <F9>;
    • контекстное меню нужной строки или пункт "Отладка" в главном меню -> "переключить точку останова" (рисунок 5).

      Меню для точек останова

      Рисунок 5. Пункт меню "Переключить точку останова".

  • Если вы поставили несколько точек останова, но они вам перестали быть нужными, можно удалить их сразу все с помощью меню "Отладка" (на рисунке 5 нужный пункт находится в меню справа, сразу под красной рамкой) или сочетанием клавиш <Ctrl>+<Shift>+<F9>.

Работать с программой в режиме отладки можно с помощью фрагмента панели инструментов, показанного на рисунке 6.

Панель отладки

Рисунок 6. Фрагмент панели инструментов, предназначенный для отладки.

Левая кнопка ( Запуск отладки на исторических данных)  запускает отладку на исторических данных, правая ( Запуск отладки на реальных данных) — на реальных. Синяя кнопка открывает специальную программу-тестер, зеленая открывает новый график прямо в терминале. Вот некоторые различия режимов:

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

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

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

Кнопка ( Завершить текущую сессию отладки) завершает текущий сеанс отладки.

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

В панели снизу окна отладки слева показан весь стек вызовов: последние функции, которые были вызваны, пока не была вызвана текущая. Справа — список всех переменных данной функции. Значения в этом окне будут меняться автоматически, как только они изменятся в программе.

Стэк вызовов и список переменных

Рисунок 7. Нижняя панель в режиме отладки: список переменных и стек вызовов

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

  • выделить его в тексте программы и нажать <Shift>+<F9>;
  • по выделенному выражению щелкнуть правой кнопкой и выбрать в контекстном меню "Добавить наблюдение" (англ. "Add watch");
  • сделать двойной щелчок по свободной строке в окне списка переменных и ввести выражение напрямую.

Для того, чтобы просмотреть содержимое массива или структуры, нужно по нему дважды кликнуть. Однако раскрывать массивы типа high или time в индикаторах лучше не пытаться: иногда можно "завесить" даже мощный компьютер на довольно продолжительное время. Лучше просто ввести отдельной строкой имя массива с индексом, например, time[i].

Если нужно вывести конкретный член какой-нибудь структуры или класса, отлично работает автодополнение: можно поставить в конце имени точку или нажать <Ctrl>+<Пробел> — должна появиться обычная всплывающая подсказка. Но вот вызовы функций добавить, увы, не получится.

Предлагаю в качестве упражнения разобраться в каком-нибудь стандартном индикаторе, например, ZigZag. Откройте файл "Indicators -> Examples -> ZigZag.mq5" и выполните каждое описываемое в следующих пунктах действие в своём редакторе.

  1. Первое, что нам нужно сделать — понять, какие пользовательские функции есть у этого индикатора. Для того, чтобы перейти к функции, нужно просто кликнуть по её имени в списке (рисунок 8).

    Список функций индикатора ZigZag

    Рисунок 8. Список функций индикатора ZigZag

  2. Расставим точки останова. Начнём с функции OnInit, где отметим начало (на момент написания статьи у меня это строка 41), и в OnCalculate — в начале (строка 66) и там, где начинается поиск экстремумов (строка 118) — чтобы посмотреть, чем отличается работа кнопок ( Шаг с заходом) и ( Шаг в обход).

  3. Теперь можно запускать нашу программу на выполнение. В данном случае нам подойдёт любая из кнопок слева. Допустим, нажмём зелёную ( Запуск отладки на реальных данных) .

  4. Как только получим первую остановку в функции OnInit, можно бегло просмотреть её, убедиться, что она тривиальна — в ней всё понятно, и смело нажать кнопку ( Шаг наружу), чтобы перейти к следующей функции.

  5. Следующая остановка — в функции OnCalculate, на первой строке, если вы следовали рекомендациям. С этой функцией имеет смысл разобраться подробно, потому что в ней много полезных для писателей индикаторов решений. Поэтому чувствуйте себя свободно в исследовании, пользуйтесь кнопками ( Шаг с заходом) или ( Шаг в обход) для перехода на следующий шаг.
  6. Чтобы исследовать циклы for, while, do … while, можно пару раз пройти их полностью, шаг за шагом, а потом использовать точки останова после завершения каждого цикла и продолжать выполнение циклов кнопкой ( Запуск отладки на исторических данных)  или ( Запуск отладки на реальных данных), пока не дойдёте до строки 118.
  7. Пройдите следующий цикл хотя бы один раз с помощью кнопки ( Шаг с заходом) (шаг с заходом) и обратите внимание, как меняются значения в стеке вызовов (отображенном на рисунке 7, слева).
  8. Второй проход цикла сделайте с помощью кнопки ( Шаг в обход) (шаг с обходом), чтобы посмотреть на разницу.
  9. Дальнейший код индикатора ничего нового в плане приёмов отладки не принесёт, поэтому используйте уже изученные приёмы, чтобы понять, что происходит.
  10. Для завершения отладки можно нажать кнопку ( Завершить текущую сессию отладки) (завершение отладки).


Профилирование

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

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

Попробуем профилировать работу того же ZigZag-а. Для этого можно воспользоваться либо соответствующей панелью инструментов (рисунок 9), либо меню "Отладка" (рисунок 10). Процедура профилирования требуется не слишком часто, поэтому сочетаний клавиш для неё разработчики не предусмотрели.

Панель управления профилированием.

Рисунок 9. Панель управления профилированием.

Раздел профилирования в меню "Отладка"

Рисунок 10. Раздел профилирования в меню "Отладка".

Как и при отладке, профилирование можно запустить либо на реальных, либо на исторических данных (зелёная и синяя кнопки соответственно). Вторая команда ( Кнпка — остновка профилирования) останавливает процедуру профилирования (на рисунках 9 и 10 находится в неактивном состоянии).

Весь процесс прост и примитивен:

  • запускаем процедуру профилирования кнопкой "Старт";
  • ждём какое-то время, чтобы программа успела отработать полностью;
  • останавливаем процедуру кнопкой "Stop";
  • исследуем графики и исправляем, что можем (в случае зигзага, вероятно, исправлять ничего не нужно…).

У меня при профилировании стандартного ZigZag-a (на одном из тех самых старых нетбуков) получилась вот такая таблица (рисунок 11):

Результаты профилирования стандартного индикатора ZigZag

Рисунок 11. Результат профилирования стандартного индикатора ZigZag.

Оказалось, что работа SetIndexBuffer занимает львиную долю времени OnInit, а Highest и Lowest настолько быстры, что в данном сравнении даже не участвуют. Самой медленной ожидаемо оказалась OnCalculate — ведь в ней действительно происходит основная часть работы, причем вызывается она намного чаще OnInit. Не исключено, что скорость работы этой функции можно заметно улучшить.

Разработчики MetaQuotes очень хорошо оптимизировали код этого индикатора, но… Может, как раз вы придумаете алгоритм, который будет быстрее?


Заключение

Отладка приложений — один из самых сложных этапов цикла программирования. В некоторых проектах она может занимать до 70% времени, а иногда — и больше. Даже проекты, которые успешно работают и имеют тысячи пользователей, могут содержать ошибки. Что уж говорить о коде начинающих? Но я надеюсь, что методы отладки, описанные в этой статье, помогут начинающим программистам облегчить поиск своих ошибок и эффективно изучать чужие алгоритмы. 

Особенности написания Пользовательских Индикаторов Особенности написания Пользовательских Индикаторов
Написание пользовательских индикаторов в торговой системе MetaTrader 4
Нейросети в трейдинге: Модель темпоральных запросов (TQNet) Нейросети в трейдинге: Модель темпоральных запросов (TQNet)
Фреймворк TQNet открывает новые возможности в моделировании и прогнозировании финансовых временных рядов, сочетая модульность, гибкость и высокую производительность. В статье раскрывается возможность реализации сложных механизмом работы с глобальными корреляциями, включая продвинутые методы инициализации параметров.
Особенности написания экспертов Особенности написания экспертов
Написание и тестирование экспертов в торговой системе MetaTrader 4.
Моделирование рынка (Часть 04): Создание класса C_Orders (I) Моделирование рынка (Часть 04): Создание класса C_Orders (I)
В данной статье мы начнем создание класса C_Orders, чтобы иметь возможность отправлять ордеры на торговый сервер. Мы будем делать это понемногу, поскольку наша цель состоит в том, чтобы подробно объяснить, как это будет происходить с помощью системы обмена сообщениями.