Обсуждение статьи "Визуальная оценка и корректировка торговли в MetaTrader 5"

 

Опубликована статья Визуальная оценка и корректировка торговли в MetaTrader 5:

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

Представим ситуацию: на каком-либо счёте достаточно долгое время ведётся более-менее активная торговля на разных инструментах разными советниками и где-то даже вручную. И вот, по истечении некоторого времени, мы хотим видеть результаты работы. Естественно, можно посмотреть стандартные отчёты по торговле в терминале, нажав сочетание клавиш Alt+E. Можно загрузить значки сделок на график и посмотреть входы и выходы позиций. Но что, если хочется в динамике посмотреть как велась торговля, где и как открывались и закрывались позиции? Посмотреть раздельно по каждому символу, либо сразу все вместе, открытие и закрытие позиций, на каких уровнях ставились стоп-приказы, и обоснован ли был их размер. А что если далее мы зададимся вопросом "что бы было, если бы ..." (и тут много вариантов — другие стопы, по иным алгоритмам и критериям, использование трейлинга позиций, либо перенос стопов в безубыток и т.д.); а потом ещё и протестировать все свои "если" с наглядным видимым результатом. Как бы могла измениться торговля, если бы...

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

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

Автор: Artyom Trishkin

 

У меня немного другой подход. Поскольку у меня каждый робот и так пишет лог своих сделок, достаточно сделать скрипт. Который, при запуске его на графике инструмента запрашивает номер (magic) робота и из файла с этим инструментом и этим номером в названии загружает на график сделки - все или только лонг/только шорт (бывает полезно разделить). Такой подход позволяет работать и с неттингом, что для меня принципиально важно. И даже с роботами, торгующими в тестовом режиме - не совершая сделки, а лишь имитируя, обсчитывая и записывая результаты в файл.

 
Спасибо большое за статью - обязательно буду тестировать и смотреть....
Как раз нужны фрагменты кода для учета значений и показателей сделок в структурах.
 
//+------------------------------------------------------------------+
//|  Структура сделки. Используется для создания файла истории сделок|
//+------------------------------------------------------------------+
struct SDeal
  {
   ulong             ticket;                 // Тикет сделки
   long              order;                  // Ордер, на основании которого была открыта сделка
   long              pos_id;                 // Идентификатор позиции
   long              time_msc;               // Время в миллисекундах
   datetime          time;                   // Время
   double            volume;                 // Объём
   double            price;                  // Цена
   double            profit;                 // Прибыль
   double            commission;             // Комиссия по сделке
   double            swap;                   // Накопленный своп при закрытии
   double            fee;                    // Оплата за проведение сделки, начисляется сразу после совершения сделки
   double            sl;                     // Уровень Stop Loss
   double            tp;                     // Уровень Take Profit
   ENUM_DEAL_TYPE    type;                   // Тип
   ENUM_DEAL_ENTRY   entry;                  // Способ изменения позиции
   ENUM_DEAL_REASON  reason;                 // Причина или источник проведения сделки
   long              magic;                  // Идентификатор эксперта
   int               digits;                 // Digits символа
   ushort            symbol[16];             // Символ
   ushort            comment[64];            // Комментарий к сделке
   ushort            external_id[256];       // Идентификатор сделки во внешней торговой системе (на бирже)
   
//--- Установка строковых свойств
   bool              SetSymbol(const string deal_symbol)          { return(::StringToShortArray(deal_symbol, symbol)==deal_symbol.Length());                }
   bool              SetComment(const string deal_comment)        { return(::StringToShortArray(deal_comment, comment)==deal_comment.Length());             }
   bool              SetExternalID(const string deal_external_id) { return(::StringToShortArray(deal_external_id, external_id)==deal_external_id.Length()); }
                       
//--- Возврат строковых свойств
   string            Symbol(void)                                 { return(::ShortArrayToString(symbol));                                                   }
   string            Comment(void)                                { return(::ShortArrayToString(comment));                                                  }
   string            ExternalID(void)                             { return(::ShortArrayToString(external_id));                                              }
  };

//+------------------------------------------------------------------+
//| Сохраняет сделки из истории в массив                             |
//+------------------------------------------------------------------+
int SaveDealsToArray(SDeal &array[], bool logs=false)
  {
//--- структура сделки
   SDeal deal={};
   
//--- запросим историю сделок в интервале с самого начала по текущий момент 
   if(!HistorySelect(0, TimeCurrent()))
     {
      Print("HistorySelect() failed. Error ", GetLastError());
      return 0;
     }
   
//--- общее количество сделок в списке 
   int total=HistoryDealsTotal(); 

//--- обработаем каждую сделку 
   for(int i=0; i<total; i++) 
     { 
      //--- получаем тикет очередной сделки (сделка автоматически выбирается для получения её свойств)
      ulong ticket=HistoryDealGetTicket(i);
      if(ticket==0)
         continue;
      
      //--- сохраняем только балансовые и торговые сделки
      ENUM_DEAL_TYPE deal_type=(ENUM_DEAL_TYPE)HistoryDealGetInteger(ticket, DEAL_TYPE);
      if(deal_type!=DEAL_TYPE_BUY && deal_type!=DEAL_TYPE_SELL && deal_type!=DEAL_TYPE_BALANCE)
         continue;
      
      //--- сохраняем свойства сделки в структуре
      deal.ticket=ticket;
      deal.type=deal_type;
      deal.order=HistoryDealGetInteger(ticket, DEAL_ORDER);
      deal.entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(ticket, DEAL_ENTRY);
      deal.reason=(ENUM_DEAL_REASON)HistoryDealGetInteger(ticket, DEAL_REASON);
      deal.time=(datetime)HistoryDealGetInteger(ticket, DEAL_TIME);
      deal.time_msc=HistoryDealGetInteger(ticket, DEAL_TIME_MSC);
      deal.pos_id=HistoryDealGetInteger(ticket, DEAL_POSITION_ID);
      deal.volume=HistoryDealGetDouble(ticket, DEAL_VOLUME);
      deal.price=HistoryDealGetDouble(ticket, DEAL_PRICE);
      deal.profit=HistoryDealGetDouble(ticket, DEAL_PROFIT);
      deal.commission=HistoryDealGetDouble(ticket, DEAL_COMMISSION);
      deal.swap=HistoryDealGetDouble(ticket, DEAL_SWAP);
      deal.fee=HistoryDealGetDouble(ticket, DEAL_FEE);
      deal.sl=HistoryDealGetDouble(ticket, DEAL_SL);
      deal.tp=HistoryDealGetDouble(ticket, DEAL_TP);
      deal.magic=HistoryDealGetInteger(ticket, DEAL_MAGIC);
      deal.SetSymbol(HistoryDealGetString(ticket, DEAL_SYMBOL));
      deal.SetComment(HistoryDealGetString(ticket, DEAL_COMMENT));
      deal.SetExternalID(HistoryDealGetString(ticket, DEAL_EXTERNAL_ID));
      deal.digits=(int)SymbolInfoInteger(deal.Symbol(), SYMBOL_DIGITS);
      
      //--- увеличиваем массив и
      int size=(int)array.Size();
      ResetLastError();
      if(ArrayResize(array, size+1, total)!=size+1)
        {
         Print("ArrayResize() failed. Error ", GetLastError());
         continue;
        }
      //--- сохраняем в массиве сделку
      array[size]=deal;
      //--- если разрешено, выводим описание сохранённой сделки в журнал
      if(logs)
         DealPrint(deal, i);
     }
//--- возвращаем количество сохранённых в массиве сделок
   return (int)array.Size();
  }

Не понимаю, почему игнорируется логика архитектуры.

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


Почему не писать, например, так?

//+------------------------------------------------------------------+
//|  Структура сделки. Используется для создания файла истории сделок|
//+------------------------------------------------------------------+
struct SDeal
  {
   ulong             ticket;                 // Тикет сделки
   long              order;                  // Ордер, на основании которого была открыта сделка
   long              pos_id;                 // Идентификатор позиции
   long              time_msc;               // Время в миллисекундах
   datetime          time;                   // Время
   double            volume;                 // Объём
   double            price;                  // Цена
   double            profit;                 // Прибыль
   double            commission;             // Комиссия по сделке
   double            swap;                   // Накопленный своп при закрытии
   double            fee;                    // Оплата за проведение сделки, начисляется сразу после совершения сделки
   double            sl;                     // Уровень Stop Loss
   double            tp;                     // Уровень Take Profit
   ENUM_DEAL_TYPE    type;                   // Тип
   ENUM_DEAL_ENTRY   entry;                  // Способ изменения позиции
   ENUM_DEAL_REASON  reason;                 // Причина или источник проведения сделки
   long              magic;                  // Идентификатор эксперта
   int               digits;                 // Digits символа
   ushort            symbol[16];             // Символ
   ushort            comment[64];            // Комментарий к сделке
   ushort            external_id[256];       // Идентификатор сделки во внешней торговой системе (на бирже)
   
//--- Установка строковых свойств
   bool              SetSymbol(const string deal_symbol)          { return(::StringToShortArray(deal_symbol, symbol)==deal_symbol.Length());                }
   bool              SetComment(const string deal_comment)        { return(::StringToShortArray(deal_comment, comment)==deal_comment.Length());             }
   bool              SetExternalID(const string deal_external_id) { return(::StringToShortArray(deal_external_id, external_id)==deal_external_id.Length()); }
                       
//--- Возврат строковых свойств
   string            Symbol(void)                                 { return(::ShortArrayToString(symbol));                                                   }
   string            Comment(void)                                { return(::ShortArrayToString(comment));                                                  }
   string            ExternalID(void)                             { return(::ShortArrayToString(external_id));                                              }
   
   bool Set( const ulong _ticket )   
   {
      this.ticket=_ticket;
      this.type=(ENUM_DEAL_TYPE)HistoryDealGetInteger(_ticket, DEAL_TYPE);
      this.order=HistoryDealGetInteger(_ticket, DEAL_ORDER);
      this.entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(_ticket, DEAL_ENTRY);
      this.reason=(ENUM_DEAL_REASON)HistoryDealGetInteger(_ticket, DEAL_REASON);
      this.time=(datetime)HistoryDealGetInteger(_ticket, DEAL_TIME);
      this.time_msc=HistoryDealGetInteger(_ticket, DEAL_TIME_MSC);
      this.pos_id=HistoryDealGetInteger(_ticket, DEAL_POSITION_ID);
      this.volume=HistoryDealGetDouble(_ticket, DEAL_VOLUME);
      this.price=HistoryDealGetDouble(_ticket, DEAL_PRICE);
      this.profit=HistoryDealGetDouble(_ticket, DEAL_PROFIT);
      this.commission=HistoryDealGetDouble(_ticket, DEAL_COMMISSION);
      this.swap=HistoryDealGetDouble(_ticket, DEAL_SWAP);
      this.fee=HistoryDealGetDouble(_ticket, DEAL_FEE);
      this.sl=HistoryDealGetDouble(_ticket, DEAL_SL);
      this.tp=HistoryDealGetDouble(_ticket, DEAL_TP);
      this.magic=HistoryDealGetInteger(_ticket, DEAL_MAGIC);
      this.SetSymbol(HistoryDealGetString(_ticket, DEAL_SYMBOL));
      this.SetComment(HistoryDealGetString(_ticket, DEAL_COMMENT));
      this.SetExternalID(HistoryDealGetString(_ticket, DEAL_EXTERNAL_ID));
      this.digits=(int)SymbolInfoInteger(this.Symbol(), SYMBOL_DIGITS);
      
      return((bool)this.time);
   }
  };

bool SetDeal( SDeal &deal, const ulong ticket )
{
  //--- сохраняем только балансовые и торговые сделки
  const ENUM_DEAL_TYPE deal_type = (ENUM_DEAL_TYPE)HistoryDealGetInteger(ticket, DEAL_TYPE);

  return((deal_type!=DEAL_TYPE_BUY && deal_type!=DEAL_TYPE_SELL && deal_type!=DEAL_TYPE_BALANCE) && deal.Set(ticket));
}

//+------------------------------------------------------------------+
//| Сохраняет сделки из истории в массив                             |
//+------------------------------------------------------------------+
int SaveDealsToArray(SDeal &array[], bool logs=false)
  {
   int Amount = 0;
   
//--- запросим историю сделок в интервале с самого начала по текущий момент 
   if(HistorySelect(0, INT_MAX))
   {
  //--- общее количество сделок в списке 
     const int total=ArrayResize(array, HistoryDealsTotal()); 
  
  //--- обработаем каждую сделку 
     for(int i=0; i<total; i++) 
       if (SetDeal(array[Amount], HistoryDealGetTicket(i))) 
         Amount++;
   }

//--- возвращаем количество сохранённых в массиве сделок
   return(ArrayResize(array, Amount));
  }


Сам я очень далек от образцового написания кода. Но давайте как-то более осмысленно подходить.

 
fxsaber #:
  • Если элемент является структурой, то он должен сам заполняться.

Вот снова.

//+------------------------------------------------------------------+
//| Возвращает описание сделки                                       |
//+------------------------------------------------------------------+
string DealDescription(SDeal &deal, const int index)
  {
   string indexs=StringFormat("% 5d", index);
   if(deal.type!=DEAL_TYPE_BALANCE)
      return(StringFormat("%s: deal #%I64u %s, type %s, Position #%I64d %s (magic %I64d), Price %.*f at %s, sl %.*f, tp %.*f",
                          indexs, deal.ticket, DealEntryDescription(deal.entry), DealTypeDescription(deal.type),
                          deal.pos_id, deal.Symbol(), deal.magic, deal.digits, deal.price,
                          TimeToString(deal.time, TIME_DATE|TIME_MINUTES|TIME_SECONDS), deal.digits, deal.sl, deal.digits, deal.tp));
   else
      return(StringFormat("%s: deal #%I64u %s, type %s %.2f %s at %s",
                          indexs, deal.ticket, DealEntryDescription(deal.entry), DealTypeDescription(deal.type),
                          deal.profit, AccountInfoString(ACCOUNT_CURRENCY), TimeToString(deal.time)));
  }

Почему структура себя не распечатывает?

 
fxsaber #:
Если элемент является структурой, то он должен сам заполняться.

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

Эти коды пишутся для упрощения понимания.

Нет цели всё завернуть в неудобочитаемую, упакованную в одну строчку монструозность, пусть и работающую.

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

Статьи же несут иную цель.

 
Artyom Trishkin #:

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

Если речь идет про структуры от MQ, то таких нет в MQL5. Авторы не могли знать, что и кому понадобится. Поэтому там только база и возможность наследования от них, чтобы наделять стандартные структуры необходимым пользователю функционалом.

Эти коды пишутся для упрощения понимания.

Нет цели всё завернуть в неудобочитаемую, упакованную в одну строчку монструозность, пусть и работающую.

Такого не предлагаю.

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

Статьи же несут иную цель.

Виртуозность - это автор Учебника. Выступаю за логичность.


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

 
fxsaber #:

Если речь идет про структуры от MQ, то таких нет в MQL5. Авторы не могли знать, что и кому понадобится. Поэтому там только база и возможность наследования от них, чтобы наделять стандартные структуры необходимым пользователю функционалом.

Такого не предлагаю.

Виртуозность - это автор Учебника. Выступаю за логичность.


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

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

 
//+------------------------------------------------------------------+
//| Открывает файл для записи, возвращает хэндл                      |
//+------------------------------------------------------------------+
bool FileOpenToWrite(int &handle)
  {
   ResetLastError();
   handle=FileOpen(PATH, FILE_WRITE|FILE_BIN|FILE_COMMON);
   if(handle==INVALID_HANDLE)
     {
      PrintFormat("%s: FileOpen() failed. Error %d",__FUNCTION__, GetLastError());
      return false;
     }
//--- успешно
   return true;
  }
//+------------------------------------------------------------------+
//| Открывает файл для чтения, возвращает хэндл                      |
//+------------------------------------------------------------------+
bool FileOpenToRead(int &handle)
  {
   ResetLastError();
   handle=FileOpen(PATH, FILE_READ|FILE_BIN|FILE_COMMON);
   if(handle==INVALID_HANDLE)
     {
      PrintFormat("%s: FileOpen() failed. Error %d",__FUNCTION__, GetLastError());
      return false;
     }
//--- успешно
   return true;
  }
//+------------------------------------------------------------------+
//| Сохраняет в файл данные сделок из массива                        |
//+------------------------------------------------------------------+
bool FileWriteDealsFromArray(SDeal &array[], ulong &file_size)
  {
//--- если передан пустой массив - сообщаем об этом и возвращаем false
   if(array.Size()==0)
     {
      PrintFormat("%s: Error! Empty deals array passed",__FUNCTION__);
      return false;
     }
     
//--- откроем файл для записи, получим его хэндл
   int handle=INVALID_HANDLE;
   if(!FileOpenToWrite(handle))
      return false;
   
//--- переместим файловый указатель на конец файла 
   bool res=true;
   ResetLastError();
   res&=FileSeek(handle, 0, SEEK_END);
   if(!res)
      PrintFormat("%s: FileSeek(SEEK_END) failed. Error %d",__FUNCTION__, GetLastError());
   
//--- запишем данные массива в конец файла 
   file_size=0;
   res&=(FileWriteArray(handle, array)==array.Size());
   if(!res)
      PrintFormat("%s: FileWriteArray() failed. Error ",__FUNCTION__, GetLastError());
   else
      file_size=FileSize(handle);

//--- закрываем файл 
   FileClose(handle);
   return res;
  }
//+------------------------------------------------------------------+
//| Загружает в массив данные сделок из файла                        |
//+------------------------------------------------------------------+
bool FileReadDealsToArray(SDeal &array[], ulong &file_size)
  {
//--- откроем файл для чтения, получим его хэндл
   int handle=INVALID_HANDLE;
   if(!FileOpenToRead(handle))
      return false;
   
//--- переместим файловый указатель на конец файла 
   bool res=true;
   ResetLastError();
   
//--- прочитаем данные из файла в массив
   file_size=0;
//   res=(FileReadArray(handle, array)>0);
   res=(ArrayResize(array, FileReadArray(handle, array))>0);
   if(!res)
      PrintFormat("%s: FileWriteArray() failed. Error ",__FUNCTION__, GetLastError());
   else
      file_size=FileSize(handle);

//--- закрываем файл 
   FileClose(handle);
   return res;
  }

В выделении частая ошибка и ее исправление.

Но все же почему не написать иначе весь этот код?

//+------------------------------------------------------------------+
//| Сохраняет в файл данные сделок из массива                        |
//+------------------------------------------------------------------+
bool FileWriteDealsFromArray(SDeal &array[], long &file_size)
{
  return((file_size = FileSave(PATH, array, FILE_COMMON) * sizeof(SDeal)) > 0);
}

//+------------------------------------------------------------------+
//| Загружает в массив данные сделок из файла                        |
//+------------------------------------------------------------------+  
bool FileReadDealsToArray2(SDeal &array[], long &file_size)
{
  return((file_size = ArrayResize(array, (int)FileLoad(PATH, array, FILE_COMMON)) * sizeof(SDeal)) > 0);
}
 
fxsaber #:
Но все же почему не написать иначе весь этот код?

Статья рассчитана на достаточно низкий уровень подготовки. Для охвата широкой части читающих. Для опытных и прожжёных эти статьи не нужны. Они - сами с усами )

 
Artyom Trishkin #:

Статья рассчитана на достаточно низкий уровень подготовки. Для охвата широкой части читающих.