Создаем торговый советник своими руками. Какие могут быть ошибки

Создание советника

Создание советника

Всем привет. Я обещал Вам рассказать о том, как создавать советники с помощью языка программирования MQL4, и сейчас попытаюсь сдержать свое слово. Собственно, здесь будет «многа букав» и не совсем понятных кодов, но разумный человек сможет разобраться во всем массиве информации, так как в реальности ничего здесь сложного нет. Однако по личному опыту хочу сказать, что программист в процессе работы над советником может столкнуться с уймой мыслимых и немыслимых проблем – ошибок в программе. Данные ошибки могут преподнести человеку очень много неудобств на этапе разработки, а также всунуть палку в колеса в процессе его использования: может как пострадать качество продукта, так и появиться негативное воздействие на торговый счет. В этой статье хочу рассказать о том, какие характерные ошибки могут возникнуть при создании торгового робота в терминале MetaTrader 4. Если Вы сейчас обратите на них внимание, то избавите себя от множества хлопот, с которыми столкнулся я.

Итак, существует 5 основных видов ошибок:

  1. Синтаксические – ошибки, характерные на этапе компиляции (трансляции программы), которые легко поддаются исправлению.
  2. Логические – компилятор не обнаруживается. Это и путаница с именами переменных, и неверный вызов функций, и работа с данными разных типов и т.п.
  3. Алгоритмические – такие ошибки вылезают в случае неверного расставления скобок или путаницы с операторами ветвления.
  4. Критические – очень нечастые ошибки. Чтобы создать себе такую проблему, нужно еще сильно постараться, или иметь руки с места, откуда ноги растут. Однако в работе с dll они могут быть даже частыми.
  5. Торговые – это такой вид ошибок, которые возникают непосредственно в процессе работы с ордерами. Данный род ошибок – настоящий бич для торговых роботов.

 

Сейчас распишу более подробно каждый вид ошибок.

Синтаксические ошибки

Эти ошибки могут быть вызваны опечатками в операторах, переменных и вызовах различных функций. При компиляции производится проверка кода программы, и все такие ошибочки выводятся в окне «Инструментарий» редактора MetaEditor. Они все легко находятся и быстро исправляются.

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

Логические, алгоритмические и критические ошибки

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

[spoiler]

bool Some = false;

void check() {
    // Много кода
    Some = true; }
// Очень много кода
int start() {
    bool Some = false; // if(Some)
      {
        //отсылка ордера
      }
   return(0); }

[/spoiler]

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

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

[spoiler]

int profit = NormalizeDouble(SomeValue*point*2 / 3, digit);

[/spoiler]

Мы пытаемся присвоить переменной типа int выражение типа double, и в итоге получаем нолик. А ведь в данном случае мы считаем уровень тейкпрофита, а это не мелочь по карманам тырить! И такая ошибка впоследствии может привести к систематически неверной торговле Вашего советника.

Алгоритмические ошибки в ветвлениях советника заключаются в том, что скобки в коде расставляются не по алгоритму, либо некорректно перекрывают операторы if операторами else. Это приводит к тому, что советник работает не по технической задаче.

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

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

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

А вот особенностью критических ошибок является то, что при их возникновении работа программы неминуемо прекращается. Слава Богу, что код ошибки непременно сохраняется в переменной   last_error. Это дает возможность узнать код ошибки с помощью простого вызова функции GetLastError().

Плавно переходим к торговым ошибкам

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

Элементарная обработка вида:

[spoiler]

ticket = OrderSend(Symbol(), OP_SELL, LotsOptimized(), Bid, 3,
         Bid + StopLoss*Point, Bid - TakeProfit*Point, 0, MAGICMA,
         0, Red);
if(ticket > 0)
  {
    err = GetLastError();
    Print("При открытии ордера возникла ошибка #", err);
  }

[/spoiler]

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

Вариант с бесконечным циклом:

[spoiler]

while (true) {
    ticket = OrderSend(Symbol(), OP_SELL, Lots, Bid, slippage,
             Bid + StopLoss*Point, Bid - TakeProfit*Point, 0,
             MAGICMA, 0, Red);
    if(ticket > 0)
      {
        err = GetLastError();
        Print("При открытии ордера возникла ошибка #", err);
        break; }
    Sleep(1000);
    RefleshRates(); }

[/spoiler]

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

  1. Брокер не одобрит частые запросы с Вашего терминала.
  2. Ошибка является фатальной, а это значит, что запрос все равно не отправится.
  3. Надолго зависнет Ваш эксперт.
  4. Сервер принципиально не принимает торговые запросы – выходные, праздники, профилактические работы и т.д.

Запомните, что почти каждая ошибка уникальна в своем роде и требует соответствующей обработки. Для этого предлагаю рассмотреть вариант с оператором Switch и проработать каждую ошибку более-менее индивидуально. Стандартная ошибка №146 – «Торговый поток занят», обрабатывается с применением семафора, реализованного с библиотеки TradeContext.mqh.

[spoiler]

//Библиотека для разграничения работы с торговым потоком
//written by komposter
#include <TradeContext.mqh>

//параметры для сигналов
extern double MACDOpenLevel=3;
extern double MACDCloseLevel=2;
extern double MATrendPeriod=26;

// максимальное допустимое проскальзывание
int  slippage = 3;
//общее количество сделок
int deals = 0;
//время для отдыха после сделки
int TimeForSleep = 10;
//период запроса
int time_for_action = 1;
//количество попыток открытия/закрытия позиции
int count = 5;
//флаг работоспособности эксперта
bool Trade = true;
//флаг наличия денег для открытия позиции
bool NoOpen = false;
//+-----+ //| В выходные не запрашиваем котировки у сервера | //+-----+
bool ServerWork()
  { if(DayOfWeek() == 0 || DayOfWeek() == 6)
       return(false); return(true);
  }
//
+-----+ //| Генерация magik | // +-----+
int GenericMagik() {
   return(deals); }
//
+-----+ //| Закрытие сделок | // +-----+
bool CloseOrder(int magik) { int ticket,i;
   double Price_close; int err; int N;
//Функция пытается закрыть ордер за count попыток, если ей этого не удается 
//то выдает сообщение об ошибке в журнал
   while(N < count)
{
for(i = OrdersTotal() - 1; i >= 0; i--)
{ if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) if(OrderSymbol() == Symbol())
if(OrderMagicNumber() == magik)
{ if(OrderType() == OP_BUY)
  Price_close = NormalizeDouble(Bid, Digits); if(OrderType() == OP_SELL)
  Price_close = NormalizeDouble(Ask, Digits);
   if(OrderClose(OrderTicket(), OrderLots(),
  Price_close,slippage))
{
//уменьшаем количество сделок для этого эксперта
deals--;
//часть маржи освободилась - можно снова открываться
NoOpen = false; return(true); }
//дошли до этого места, значит ордер не отправился
N++;
//обработка возможных ошибок
err = ErrorBlock();
//если ошибка серьезная
if(err > 1)
{
Print("Требуется ручное закрытие ордера №",
      OrderTicket()); return(false); } } }
// отдыхаем 5 секунд и пытаемся снова закрыть сделку
Sleep(5000);
RefreshRates();
}
//если дошли до этого места, то сделка не закрылась за count попыток
Print("Требуется ручное закрытие ордера №",OrderTicket()); return(false); }
//
+-----+ //|Сделка для act 1-бай, 2-селл, второй параметр - к-во лотов | // +-----+
int Deal(int act, double Lot)
  {
   int N = 0;
   int ticket;
   int err;
   double Price_open;
   double Lots;
   int cmd;
   int magik;
   magik = GenericMagik();
   Lots = NormalizeDouble(Lot,1);
   if(act == 1) {
Price_open = NormalizeDouble(Ask, Digits);
cmd = OP_BUY; } if(act == 2)
     {
Price_open = NormalizeDouble(Bid, Digits);
cmd = OP_SELL;
     }
//проверка маржи для открытия позиции
AccountFreeMarginCheck(Symbol(), cmd,Lots);
err = GetLastError(); if(err>0) {
 Print("No money for new position");
 NoOpen = true; return(0); }
//Отсылаем ордер
ticket = OrderSend(Symbol(), cmd, Lots, Price_open, slippage,
0, 0, 0, magik); if(ticket > 0)
     {
  deals++;
return(ticket); }
//Если не отослался, то пытаемся 5 раз снова его открыть 
else {
  while(N < count) {
  N++;
  err = ErrorBlock(); if(err == 1) {
Sleep(5000);
RefreshRates(); if(act == 1)
Price_open = NormalizeDouble(Ask, Digits); if(act == 2)
Price_open = NormalizeDouble(Bid, Digits);
ticket = OrderSend(Symbol(), cmd, Lots, Price_open,
   slippage, 0, 0, 0, magik); if(ticket > 0) {
deals++; return(ticket); } }
// получили серьезную ошибку 
           if(err > 1) return(0); } } return(0);
  }
//
+-----+ //| // 0-нет ошибки, 1-надо ждать и обновлять, 2-сделка бракуется, | 
//| 3-фатальная ошибка | //+-----+ //Блок контроля ошибки
int ErrorBlock() {
   int err = GetLastError();
   switch(err) {
       case 0: return(0); case 2: {
           Print("Сбой системы. Перезагрузить компьютер/проверить сервер");
           Trade = false; return(3);
         }
       case 3:
         {
           Print("Ошибка в логике эксперта");
           Trade = false; return(3); }
       case 4: {
           Print("Торговый сервер занят. Ждем 2 минуты");
           Sleep(120000); return(2);
        } case 6: {
           bool connect = false; int iteration = 0;
           Print("Disconnect "); while((!connect) || (iteration > 60)) {
               Sleep(10000);
               Print("Связь не восстановлена, прошло ", iteration*10,
                     " секунд");
               connect = IsConnected(); if(connect) {
                   Print("Связь восстановлена"); return(2); }
               iteration++; }
           Trade = false;
           Print("Проблемы с соединением"); return(3); } case 8: {
           Print("Частые запросы");
           Trade = false; return(3); } case 64: {
           Print("Счет заблокирован!");
           Trade = false; return(3); } case 65: {
           Print("Неправильный номер счета???");
           Trade = false; return(3); } case 128: {
           Print("Истек срок ожидания сделки"); return(2); } case 129: {
           Print("Неверная цена"); return(1); } case 130: {
           Print("Неверный стоп"); return(1); } case 131: {
           Print("Неверно рассчитывается объем сделки");
           Trade = false; return(3); } case 132: {
           Print("Рынок закрыт");
           Trade = false; return(2); } case 134: {
           Print("Не хватает маржи для проведения операции");
           Trade = false; return(2); } case 135: {
           Print("Цены изменились"); return (1); } case 136: {
           Print("Цен нет!"); return(2); } case 138: {
           Print("Реквот, снова!"); return(1); } case 139: {
           Print("Ордер в обработке. Глюк программы"); return(2); } case 141: {
           Print("Слишком много запросов");
           Trade = false; return(2); } case 148: {
           Print("Слишком большой объем сделки");
           Trade = false; return(2); } } return (0); }
//
+-----+//| формирование сигналов на открытие/закрытие позиции по Macd | //+-----+
int GetAction(int &action, double &lot, int &magik)
   {
   double MacdCurrent, MacdPrevious, SignalCurrent; double SignalPrevious, MaCurrent, MaPrevious; int cnt,total;

   MacdCurrent=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_MAIN,0);
   MacdPrevious=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_MAIN,1);
   SignalCurrent=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_SIGNAL,0);
   SignalPrevious=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_SIGNAL,1);
   MaCurrent=iMA(NULL,0,MATrendPeriod,0,MODE_EMA,PRICE_CLOSE,0);
   MaPrevious=iMA(NULL,0,MATrendPeriod,0,MODE_EMA,PRICE_CLOSE,1);

  if(MacdCurrent<0 && MacdCurrent>SignalCurrent
 && MacdPrevious<SignalPrevious &&
         MathAbs(MacdCurrent)>(MACDOpenLevel*Point
 && MaCurrent>MaPrevious)
      {
         action=1;
         lot=1; return (0); } if(MacdCurrent>0 && MacdCurrent<SignalCurrent
 && MacdPrevious>SignalPrevious &&
         MacdCurrent>(MACDOpenLevel*Point
 && MaCurrent<MaPrevious) {
         action=2;
         lot=1; return (0); }
   total=OrdersTotal();
   for(cnt=0;cnt<total;cnt++) {
      OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES); if(OrderType()<=OP_SELL &&   // check for opened position
         OrderSymbol()==Symbol())  // check for symbol
        { if(OrderType()==OP_BUY)   // long position is opened
           {
            // should it be closed?
if(MacdCurrent>0 && MacdCurrent<SignalCurrent
 && MacdPrevious>SignalPrevious &&
   MacdCurrent>(MACDCloseLevel*Point)) {
action=3;
magik=OrderMagicNumber();
return(0); // exit
 } }
else // go to short position
     {
// should it be closed?
if(MacdCurrent<0 && MacdCurrent>SignalCurrent
 &&   MacdPrevious<SignalPrevious
 && MathAbs(MacdCurrent)>(MACDCloseLevel*Point)) {
action=3;
magik=OrderMagicNumber(); return(0); } } } } }
//
+-----+//| expert initialization function | //+-----+
int init() { if(!IsTradeAllowed())
      {
        Print("Торговля не разрешена!"); return(0); } }
//
+-----+//| expert deinitialization function | //+-----+
int deinit() {
//Закрываем все ордера
for(int k = OrdersTotal() - 1; k >= 0 ; k--) if(OrderSymbol() == Symbol()) { if(OrderType() == OP_BUY)
    OrderClose(OrderTicket(), OrderLots(),
          NormalizeDouble(Bid,Digits), 10); if(OrderType() == OP_SELL)
    OrderClose(OrderTicket(), OrderLots(),
          NormalizeDouble(Ask, Digits),10); } }
//
+-----+//| expert start function | // +-----+

int start() {
   int action =0;
   double lot = 1;
   int magik = 0;
   while(Trade) {
       Sleep(time_for_action*1000);
       RefreshRates();
/*Логика эксперта, в которой вычисляем наше действие, 
размер позиции и magik для закрытия ордера action 1-buy, 2-sell, 3-close 
для примера возьмем эксперта на Macd*/
       GetAction(action,lot,magik); if(ServerWork()) { if(((action == 1) || (action == 2)) 
 && (!NoOpen)) { if(TradeIsBusy() < 0)
                   return(-1);
               Deal(action, lot);
               Sleep(TimeForSleep*1000);
               TradeIsNotBusy(); } if(action == 3) { if(TradeIsBusy() < 0) { return(-1); if(!CloseOrder(magik))
                       Print("ТРЕБУЕТСЯ РУЧНОЕ ЗАКРЫТИЕ СДЕЛКИ");
                   Sleep(TimeForSleep*1000);
                   TradeIsNotBusy(); } } } else {
            Print("Выходные"); if(TradeIsBusy() < 0) return(-1);
            Sleep(1000*3600*48);
            TradeIsNotBusy(); }
       action = 0;
       lot = 0;
       magik = 0;
     }
   Print("Возникла серьезная ошибка и эксперт остановил свою работу");
 return(0); }
//+------------------------------------------------------------------+

[/spoiler]

Рассматриваемая версия торгового робота работает по бесконечному циклу. Это нужно при создании скальпирующего мультивалютного эксперта. Алгоритм работы советника таков:

  1. Получает сигнал от аналитического блока GetAction();
  2. Совершает необходимую операцию в функциях Deal() и CloseOrder();
  3. Возвращается к пункту 1 после небольшой паузы time_for_action при условии, если не было критических сбоев.

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

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

0 – нет ошибки;

1 – ошибка на основе волатильности рынка. При этом можно попытаться еще раз оправить ордер;

2 – произошла критическая ошибка при отправке ордера. На время прекращается открытие позиций;

3 – критический сбой в работе эксперта, разрыв соединения. Прекращается любая торговля, выясняются обстоятельства.

Вывод

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

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

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

Отзывы о “Создаем торговый советник своими руками. Какие могут быть ошибки”

  1. George
    25 марта 2013 at 8:03

    Создание советников очень обширная тема и одна статья не достаточно для этой темы.

Добавить отзыв

Ваш e-mail не будет опубликован. Обязательные поля помечены *

три × 1 =