Брейкпойнты в тестере – это возможно!

Christo Tsvetanov | 20 июля, 2007


Введение

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

Вывод отладочных данных возможен и сейчас благодаря функциям типа Print, Comment и так далее, но еще хочется в какой-то момент временно остановить программу в точно определенном месте, чтобы проанализировать ситуацию. Тут есть еще и дополнительный нюанс: обычно программа запускается для торговли либо в режиме Demo, либо в режиме Real. Это означает, что на результаты можно будет посмотреть только через несколько месяцев… Таким образом, отладочный режим имеет смысл только в режиме тестирования советников.


Принцип действия

С появлением в тестере "Визуального режима тестирования" стало возможно следить за реакциями нашего советника во время быстрого прогона программы. Если мы захотим временно остановить выполнение, то можем нажать на клавишу "Pause" или кликнуть мышкой по одноимённой кнопке на панели тестера. Разработчики терминала предоставили библиотеку WinUser32.mqh, содержащую некоторые любопытнейшие функции. Одна из них – keybd_event. Она позволяет осуществлять нажатие произвольной клавиши.

Тут и рождается идея – написать функцию, которая нажимала бы на паузу программным образом и печатала необходимую отладочную информацию. Так как наш советник будет использовать DLL, то сначала нужно это разрешить. Нажимаем Ctrl+O и ставим/снимаем галочки:

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

#include <WinUser32.mqh>

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

void BreakPoint()

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

if (!IsVisualMode()) return(0);

Потом нужно визуализировать кое-какие данные. По-моему, самый наглядный способ – это Comment(). Предположим, что пока нам нужны только Bid и Ask.

string Comm="";
Comm=Comm+"Bid="+Bid+"\n";
Comm=Comm+"Ask="+Ask+"\n";
   
Comment(Comm);

Тут “\n” означает, что идущие потом данные будут отображены на следующей строчке. Под конец нажимаем на клавишу Pause.

keybd_event(19,0,0,0);
Sleep(10);
keybd_event(19,0,2,0);

Первая строка нажимает на клавишу, последняя – отжимает её. Пауза (Sleep) нужна, так как слишком быстрое нажатие/отжатие может и не обработаться. 19 – это виртуальный код клавиши паузы, 2 в последней строке показывает, что нужно эмулировать отжатые.

Всё, что осталось, – подставить брейкпойнт в коде советника, например, сразу после момента открытия длинной позиции в примере из статьи "Пример создания эксперта".

ticket=OrderSend(Symbol(),OP_BUY,Lots,Ask,3,0,Ask+TakeProfit*Point,"macd sample",16384,0,Green);
BreakPoint();

Вот и весь вставляемый код:

//We will use a function, described in header file
#include 
 
//Breakpoint neither receive nor send back any parameters
void BreakPoint()
{
   //It is expecting, that this function should work
   //only in tester
   if (!IsVisualMode()) return(0);
   
   //Preparing a data for printing
   //Comment() function is used as 
   //it give quite clear visualisation
   string Comm="";
   Comm=Comm+"Bid="+Bid+"\n";
   Comm=Comm+"Ask="+Ask+"\n";
   
   Comment(Comm);
   
   //Press/release Pause button
   //19 is a Virtual Key code of "Pause" button
   //Sleep() is needed, because of the probability
   //to misprocess too quick pressing/releasing
   //of the button
   keybd_event(19,0,0,0);
   Sleep(10);
   keybd_event(19,0,2,0);
}


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

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

void BreakPoint(double MacdCurrent)
{
   if (!IsVisualMode()) return(0);
   
   Comment("MacdCurrent = ",MacdCurrent);


Условные точки перерыва

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

void BreakPoint(double MacdCurrent, bool Condition)
{
   if (!IsVisualMode() || (!Condition)) return(0);
   //Or - which is the same:
   //if (!(IsVisualMode()&&Condition)) return(0);
   
   Comment("MacdCurrent = ",MacdCurrent);

Вызываем следующим образом:

for(cnt=0;cnt<total;cnt++)     
   {
      OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES);
      BreakPoint(MacdCurrent, cnt==1);


Заключение

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

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