English 中文 Español Deutsch 日本語 Português
Анализ графиков Баланса/Средств по символам и ORDER_MAGIC советников

Анализ графиков Баланса/Средств по символам и ORDER_MAGIC советников

MetaTrader 5Статистика и анализ | 17 апреля 2017, 11:58
4 100 10
Vladimir Karputov
Vladimir Karputov

Содержание


Постановка задачи

С введением хеджинга в MetaTrader 5 появилась отличная возможность одновременной торговли несколькими советниками (или несколькими стратегиями) на одном торговом счёте. Весь торговый счёт можно отследить в сервисе Сигналы и получить по нему статистику. Нерешенным оставался один очень важный для меня вопрос: как визуализировать вклад каждой стратегии в графики Баланс и Средства?

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


1. Комиссия. Своп. Прибыль

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

 Result=Deal commission +Cumulative swap on close+ Deal profit

Эти свойства сделок получают при помощи HistoryDealGetDouble() со следующими идентификаторами:

DEAL_COMMISSION

Deal commission

double

DEAL_SWAP

Cumulative swap on close

double

DEAL_PROFIT

Deal profit

double


Пример получения свойств сделок из торговой истории за заданный промежуток времени в скрипте "HistoryDealGetTicket.mq5".

Результаты работы скрипта (удалены сделки с типом DEAL_ENTRY_IN, поскольку в них отсутствует финансовый результат):

...
  4: deal #36774600 at 2017.02.15 10:17:50 Entry out, sell vol: 0.01 comm: 0 swap: 0.02 profit: 1.52 NZDUSD.m (order #47802989, position ID 47770449)
...
12: deal #36798157 at 2017.02.15 16:44:17 Entry out, buy vol: 0.01 comm: 0 swap: -0.01 profit: 2.98 EURUSD.m (order #47827771, position ID 47685190)
13: deal #36798161 at 2017.02.15 16:44:17 Entry out, buy vol: 0.01 comm: 0 swap: -0.02 profit: 5.99 EURUSD.m (order #47827785, position ID 47665575)
14: deal #36798176 at 2017.02.15 16:44:17 Entry out, buy vol: 0.01 comm: 0 swap: -0.02 profit: 5.93 EURUSD.m (order #47827805, position ID 47605603)
15: deal #36798185 at 2017.02.15 16:44:18 Entry out, buy vol: 0.01 comm: 0 swap: -0.03 profit: 5.98 EURUSD.m (order #47827821, position ID 47502789)
16: deal #36798196 at 2017.02.15 16:44:18 Entry out, buy vol: 0.01 comm: 0 swap: -0.03 profit: 8.96 EURUSD.m (order #47827832, position ID 47419515)
17: deal #36798203 at 2017.02.15 16:44:18 Entry out, buy vol: 0.01 comm: 0 swap: -0.06 profit: 8.92 EURUSD.m (order #47827835, position ID 47130461)
18: deal #36798212 at 2017.02.15 16:44:19 Entry out, sell vol: 0.01 comm: 0 swap: -0.48 profit: -21.07 EURUSD.m (order #47827845, position ID 46868574)
...
25: deal #36824799 at 2017.02.15 19:57:57 Entry out, sell vol: 0.01 comm: 0 swap: 0 profit: 2.96 NZDUSD.m (order #47855548, position ID 47817757)
26: deal #36824800 at 2017.02.15 19:57:58 Entry out, sell vol: 0.01 comm: 0 swap: 0 profit: 3.01 NZDUSD.m (order #47855549, position ID 47790966)
27: deal #36824801 at 2017.02.15 19:57:58 Entry out, sell vol: 0.01 comm: 0 swap: 0.02 profit: 3.07 NZDUSD.m (order #47855550, position ID 47777495)
28: deal #36824802 at 2017.02.15 19:57:58 Entry out, sell vol: 0.01 comm: 0 swap: 0.02 profit: 3 NZDUSD.m (order #47855551, position ID 47759307)
29: deal #36824803 at 2017.02.15 19:57:59 Entry out, sell vol: 0.01 comm: 0 swap: 0.02 profit: 1.52 NZDUSD.m (order #47855552, position ID 47682775)
...
33: deal #36832775 at 2017.02.16 00:58:41 Entry out, sell vol: 0.01 comm: 0 swap: 0.05 profit: 2.96 NZDUSD.m (order #47863883, position ID 47826616)
34: deal #36832776 at 2017.02.16 00:58:41 Entry out, sell vol: 0.01 comm: 0 swap: 0.05 profit: 3.05 NZDUSD.m (order #47863884, position ID 47803010)
35: deal #36832777 at 2017.02.16 00:58:41 Entry out, sell vol: 0.01 comm: 0 swap: 0.05 profit: 2.98 NZDUSD.m (order #47863885, position ID 47792294)
36: deal #36832778 at 2017.02.16 00:58:42 Entry out, sell vol: 0.01 comm: 0 swap: 0.07 profit: 2.88 NZDUSD.m (order #47863886, position ID 47713741)
...

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


2. Расчёт средств и баланса на истории

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

  • ищем сделки выхода из рынка. Если таковые найдены, то на основании итогового финансового результата таких сделок пересчитываем значение для графиков "Баланс" и "Средства". Корректируем список "Открытые позиции".
  • по открытым позициям рассчитываем значение для графика "Средства". 

2.1. Список "Открытые позиции"

Для ведения учета открытых позиций на выбранном промежутке нужен будет класс сделок. Реализуем это в классе "CHistoryDeal" — во включаемом файле "HistoryDeal.mqh":

МетодЗначение
TicketDealПолучает/устанавливает свойство сделки "Тикет сделки"
PosIDDealПолучает/устанавливает свойство сделки "Идентификатор позиции"
SymbolDealПолучает/устанавливает свойство сделки "Символ сделки"
TypeDealПолучает/устанавливает свойство сделки "Тип сделки" из перечисления ENUM_DEAL_TYPE
EntryDealПолучает/устанавливает свойство сделки "Направление сделки" из перечисления ENUM_DEAL_ENTRY
VolumeDealПолучает/устанавливает свойство сделки "Объём сделки"
PriceDealПолучает/устанавливает свойство сделки "Цена открытия сделки"


2.2. Формула плавающей прибыли

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

SymbolInfoString(m_name,SYMBOL_CURRENCY_BASE);

  • или просто открыв спецификацию символа в терминале:

rts specification

Р‌ис. 1. Спецификация RTS-3.17

2.3. Страховка: все ли символы доступны

В‌озможные проблемы: 

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

Для учета возможных ошибок в советнике на уровне глобальных переменных программы (внимание: программы, а не терминала!) вводится массив для "плохих" символов, отсутствующих в "Обзоре рынка" или для которых нельзя пересчитать валюту прибыли в валюту депозита:

string m_arr_defective_symbols[];                        // array for defective symbols

‌Последовательность прохождения страховки (все этапы проходят в функции "SearchDefectiveSymbols"):

  1. создаём временный (вспомогательный) массив из всех символов, доступных в "Обзоре рынка";
  2. создаём временный (вспомогательный) массив из всех символов торговой истории (в заданном интервале дат с ... по ... );
  3. ищем символы из торговой истории в "Обзоре рынка". Если какой-либо из символов не находится, то он заносится в массив "плохих" символов;
  4. символы из торговой истории добавляем (отображаем) в "Обзоре рынка". Если операция заканчивается неудачей, то символ заносится в массив "плохих" символов;
  5. получаем из свойств символа валюту прибыли. Если валюта прибыли символа отличается от валюты депозита, то переходим к подпункту 5.1.
    1. Пытаемся найти в "Обзоре рынка" символ для пересчёта, который соответствует "валюта депозита"+"валюта прибыли" или "валюта прибыли"+"валюта депозита". Если операция заканчивается неудачей, то символ заносится в массив "плохих" символов

После отработки функции "SearchDefectiveSymbols" будет сформирован массив с "плохими" символами из торговой истории. В дальнейших расчётах (при расчёте графика "Баланс" и графика "Средства") "плохие" символы участвовать не будут.

2.4. Учёт открытых и закрытых позиций на истории

Все изменения баланса отображаем в двумерном массиве "m_arr_balance_equity".

Запрашиваем историю в промежутке дат "from date" и "to date" (это входные параметры эксперта) и делим её на количество циклов продолжительностью "timer (minutes)". Работа выполняется в функции "CreateBalanceEquity". Все сделки записываем в динамический массив указателей "ArrOpenPositions". В дальнейшем работаем с этим массивом: добавляем и удаляем сделки.

В зависимости от параметра торговой сделки "Направление сделки" — ENUM_DEAL_ENTRY — б‌удем по-разному рассчитывать графики "Баланс" и "Средства". Если вы хотите проверить поведение открытия, закрытия, частичного закрытия или переворота позиций на хеджинговых и неттинговых счетах, то можете запустить скрипт "HistorySelect.mq5".

  • ‌DEAL_ENTRY_IN — Вход в рынок. Записываем сделку в массив "ArrOpenPositions"
  • DEAL_ENTRY_OUT — Выход из рынка. Ищем в массиве "ArrOpenPositions" сделку с типом DEAL_ENTRY_IN с таким же DEAL_POSITION_ID
    • если поиск вернул false, значит, в графики "Баланс" и "Средства" изменения не вносим
    • если поиск вернул true, сверяем объём (в лотах)
      • объёмы одинаковые — удаляем сделку из массива "ArrOpenPositions" и вносим изменения в графу "Баланс" (массив "m_arr_balance_equity")
      • объём сделки из массива "ArrOpenPositions" больше — корректируем объём сделки в массиве "ArrOpenPositions" и вносим изменения в графу "Баланс" (массив "m_arr_balance_equity")
      • объём сделки из массива "ArrOpenPositions" меньше — удаляем сделку из массива "ArrOpenPositions" и вносим изменения в графу "Баланс" (массив "m_arr_balance_equity")
  • DEAL_ENTRY_INOUT — Разворот. Ищем в массиве "ArrOpenPositions" сделку с типом DEAL_ENTRY_IN с таким же DEAL_POSITION_ID
    • если поиск вернул false, значит в графики "Баланс" и "Средства" изменения не вносим
    • если поиск вернул true, корректируем объём сделки в массиве "ArrOpenPositions", корректируем тип сделки в массиве "ArrOpenPositions" и вносим изменения в графу "Баланс" (массив "m_arr_balance_equity")
  • DEAL_ENTRY_OUT_BY — Закрытие встречной позицией. Ищем в массиве "ArrOpenPositions" сделку с типом DEAL_ENTRY_IN с таким же DEAL_POSITION_ID
    • объёмы одинаковые — удаляем сделку из массива "ArrOpenPositions" и вносим изменения в графу "Баланс" (массив "m_arr_balance_equity")
    • объёмы разные — корректируем объём сделки в массиве "ArrOpenPositions" и вносим изменения в графу "Баланс" (массив "m_arr_balance_equity")

2.5. Как считать плавающую прибыль (Средства)

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

  • символ
  • тип (BUY or SELL)
  • объём
  • цена (цена по которой была открыта сделка),

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

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

//--- caching results "CopyTicks"
   static string arr_name[];
   static double arr_ask[];
   static double arr_bid[];
   static datetime prev_time=0;
   int number=-1;
   if(time>prev_time)
     {
      prev_time=time;
      ArrayFree(arr_name);
      ArrayFree(arr_ask);
      ArrayFree(arr_bid);
     }
   found=false;
   size=ArraySize(arr_name);
   if(size>0)
      for(int i=0;i<size;i++)
        {
         if(name==arr_name[i])
           {
            number=i;
            found=true;
            break;
           }
        }
   if(found)
     {
      ArrayResize(ticks_array,1);
      ticks_array[0].ask=arr_ask[number];
      ticks_array[0].bid=arr_bid[number];
     }
   else
     {
      //---
      int copy_ticks=-1;
      int count=0;
      while(copy_ticks==-1 && count<5)
        {
         copy_ticks=CopyTicks(name,ticks_array,COPY_TICKS_INFO,1000*(ulong)time,1);
         //if(copy_ticks==-1)

— результаты bid и ask сохраняются в локальные массивы: если в данном временном промежутке для символа уже были получены тики, то эти тики просто берутся из локальных массивов. Такой подход позволил ускориться в 10-15 раз. 


3. Интегрируем советник в панель диалогов

Теперь, когда основа советника работоспособна, можно снабдить советник более удобным интерфейсом. Исходя из названия статьи "Анализ графиков Баланса/Средств по символам и ORDER_MAGIC советников", необходимо предусмотреть несколько возможностей фильтрации:

  • показывать все символы/показывать один или несколько выбранных символов;
  • показывать по всем magic'ам/показывать только по одному или нескольким выбранным символам.

Для учета всех торговых символов у нас уже есть массив "m_arr_all_trade_symbols". Он объявлен на глобальном программном уровне. Нужно ввести ещё один массив — "m_arr_all_magics", для учета всех magic'ов. Для этого модернизируем функцию "FillArrayTradeSymbols": теперь в ней будет заполняться ещё и массив "m_arr_all_magics".

3.1. Общий вид панели диалогов

panel

Рис. 2. Общий вид панели

После формирования массивов "плохих" символов, всех торговых символов и magic'ов в панели заполняются два списка (элементы на базе класса CComboBox): левый список — всеми торговыми символами, правый список — всеми magic'ами. В списках на первом месте стоит выбор всех символов и всех magic'ов:

panel combo box

Рис. 3. Выпадающие списки

Логика работы советника панели будет следующая: после и только после нажатия на кнопку "Start" будет проходить проверка того, что именно выбрано в двух выпадающих списках. В зависимости от выбранных параметров, в этих списках будет проходить построение графиков Баланс / Средства на базе торговой истории.

3.2. Взаимодействие с панелью

Я решил, что перенос всего кода советника в класс панели ("APHDialog.mqh") — это слишком долго. Альтернативное решение: в классе панели вводится внутренние переменные m_ready, m_symbol и m_magic:

//+------------------------------------------------------------------+
//| Class CAPHDialog                                                 |
//| Usage: main dialog of the Controls application                   |
//+------------------------------------------------------------------+
class CAPHDialog : public CAppDialog
  {
private:
   CLabel            m_label_symbols;                 // the label object
   CComboBox         m_combo_box_symbols;             // the combo boxp object
   CLabel            m_label_magics;                  // the label object
   CComboBox         m_combo_box_magics;              // the combo box object
   CButton           m_button_start;                  // the button object
   //---
   bool              m_ready;                         // true -> you can build graphics
   string            m_symbol;
   ulong             m_magic;

public:

Решение о состоянии переменной m_ready будет приниматься в процедуре обработки клика на кнопке "Start":

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CAPHDialog::OnClickButtonStart(void)
  {
   if(m_combo_box_symbols.Select()!=" " && m_combo_box_magics.Select()!=" ")
      m_ready=true;
   else
      m_ready=false;
//Comment(__FUNCTION__+" ButtonStartclick"+"\n"+
//        "Symbols: "+"\""+m_combo_box_symbols.Select()+"\""+"\n"+
//        "Magic: "+"\""+m_combo_box_magics.Select()+"\""+"\n"+
//        "m_ready: "+IntegerToString(m_ready));
  }

Обратите внимание на выделенную строку: сразу после создания и заполнения выпадающих списков (у нас это элементы m_combo_box_symbols и m_combo_box_magics) в списках установлен элемент со значением " ", то есть пробел.

Решение о состоянии переменных m_symbol и m_magic будет приниматься в процедурах обработки клика на соответствующих выпадающих списках:

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CAPHDialog::OnChangeComboBoxSymbols(void)
  {
//Comment(__FUNCTION__+" \""+m_combo_box_symbols.Select()+"\"");
   if(m_combo_box_symbols.Select()=="All symbols")
      m_symbol="";
   else
      m_symbol=m_combo_box_symbols.Select();
  }
//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CAPHDialog::OnChangeComboBoxMagics(void)
  {
//Comment(__FUNCTION__+" \""+m_combo_box_magics.Select()+"\"");
   if(m_combo_box_magics.Select()=="All magics")
      m_magic=-1;
   else
      m_magic=StringToInteger(m_combo_box_magics.Select());
  }

Таким образом, после клика на кнопке "Start" будут заполнены три переменные: m_ready, m_symbol и m_magic. Осталось придумать, как сообщить советнику, что на панели осуществлён выбор параметров. Решение простое: в советнике запускаем таймер с интервалом 3 секунды, который и будет опрашивать панель. Для этого в панели напишем метод "CAPHDialog::IsReady"

//+------------------------------------------------------------------+
//| On the panel there are chosen parameters                         |
//+------------------------------------------------------------------+
bool CAPHDialog::IsReady(string &symbol,ulong &magic)
  {
   if(m_ready)
     {
      symbol=m_symbol;
      magic=m_magic;
      m_ready=false;
      return(true);
     }
   else
      return(false);
  }

В этом методе записываем значение внутренних переменных в переменные переданные по ссылке и сбрасываем внутреннюю переменную m_ready.

3.3. Небольшая корректировка — учёт выбранного magic'а

Отбор сделок происходит согласно заданным условиям: по символу или по всем символам и по magic'у или по всем magic'ам, в "GetHistory":

//--- for all deals 
   for(int i=0;i<deals;i++)
     {
      deal_ticket          = HistoryDealGetTicket(i);
      deal_position_ID     = HistoryDealGetInteger(deal_ticket,DEAL_POSITION_ID);
      deal_symbol          = HistoryDealGetString(deal_ticket,DEAL_SYMBOL);
      deal_type            = (ENUM_DEAL_TYPE)HistoryDealGetInteger(deal_ticket,DEAL_TYPE);
      deal_entry           = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(deal_ticket,DEAL_ENTRY);
      deal_volume          = HistoryDealGetDouble(deal_ticket,DEAL_VOLUME);
      deal_price           = HistoryDealGetDouble(deal_ticket,DEAL_PRICE);
      deal_commission      = HistoryDealGetDouble(deal_ticket,DEAL_COMMISSION);
      deal_swap            = HistoryDealGetDouble(deal_ticket,DEAL_SWAP);
      deal_profit          = HistoryDealGetDouble(deal_ticket,DEAL_PROFIT);
      deal_magic           = HistoryDealGetInteger(deal_ticket,DEAL_MAGIC);
      if(sSymbol!="")
         if(deal_symbol!=sSymbol)
            continue;
      if(uMagic!=ULONG_MAX)
         if(deal_magic!=uMagic)
            continue;
      //--- onle BUY or SELL

Обратите внимание, что для переменной uMagic значение ULONG_MAX означает "все magic'и", а для переменной sSymbol значение "" означает "Все символы".

Видео работы построения баланса и плавающей прибыли на основе торговой истории (сначала для всех символов, затем — только для одного символа):


Видео


4. Графики распределения MFE и MAE

Для каждой открытой позиции в течение ее жизни записываются значения максимальной прибыли (MFE) и максимального убытка (MAE). Эти показатели дополнительно характеризуют каждую закрытую позицию значениями максимального нереализованного потенциала и максимального допущенного риска. На графиках распределения MFE/Profit и MAE/Profit каждой позиции соответствует точка, где по горизонтали дается значение полученной прибыли/убытка, а по вертикали — максимально показанных значений потенциальной прибыли (MFE) и потенциального убытка (MAE).

4.1. Принцип учёта MFE и MAE

На глобальном программном уровне объявляются два массива — "m_arr_pos_id" для учета позиций и "m_arr_mfe_mae" для учета MFE, итогового финансового результата и MAE: 

long   m_arr_pos_id[];
double m_arr_mfe_mae[][3];  // [][0] - mfe, [][1] - final financial result,[][2] - mae

Впоследствии, на основании массива "m_arr_mfe_mae", будут строиться точечные графики распределения MFE и MAE.

Массивы "m_arr_pos_id" для учета позиций и "m_arr_mfe_mae" для учета mfe всегда имеют один размер в первом измерении — таким образом, для позиции с индексом "i" (m_arr_pos_id[i]) всегда будет однозначное соответствие m_arr_mfe_mae[i][][][]. Размеры этих двух массивов задаются в "GetHistory":

         if(deal_symbol=="")
            DebugBreak();
         ArrOpenPositions.Add(HistoryDeal);
         //--- mfe, mae
         int size=ArraySize(m_arr_pos_id);
         ArrayResize(m_arr_pos_id,size+1,10);
         ArrayResize(m_arr_mfe_mae,size+1,10);
         m_arr_pos_id[size]=deal_position_ID;
         // [][0] - mfe, [][1] - final financial result,[][2] - mae
         m_arr_mfe_mae[size][0]=0.0;
         m_arr_mfe_mae[size][1]=0.0;
         m_arr_mfe_mae[size][2]=0.0;
         continue;
        }
      //--- 
      if(deal_entry==DEAL_ENTRY_OUT)

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

//+------------------------------------------------------------------+
//| Add Result Mfe Mae                                               |
//+------------------------------------------------------------------+
void AddResultMfeMae(const long pos_id,const double floating_profit,const double financial_result)
  {
// [][0] - mfe (profit), [][1] - final financial result,[][2] - mae (loss)
//--- search pos_id
   int position=-1;
   int size=ArraySize(m_arr_pos_id);
   for(int i=0;i<size;i++)
      if(m_arr_pos_id[i]==pos_id)
        {
         position=i;
         break;
        }
   if(position==-1)
      return;

//---
   if(floating_profit==0.0)
      return;

   if(floating_profit>0.0) // profit
     {
      if(m_arr_mfe_mae[position][0]<floating_profit)
         m_arr_mfe_mae[position][0]=floating_profit;
     }
   else // loss
     {
      if(m_arr_mfe_mae[position][2]>floating_profit)
         m_arr_mfe_mae[position][2]=floating_profit;
     }
   m_arr_mfe_mae[position][1]=financial_result;
  }

Здесь правило: нужно передавать все три параметра. То есть, если наша виртуальная позиция ещё находится в списке открытых позиций "ArrOpenPositions" и её плавающая прибыль равна "-20.2", то вызов будет иметь вид:

    AddResultMfeMae(pos_id,-20.2,0.0);

если наша виртуальная позиция ещё находится в списке открытых позиций "ArrOpenPositions" и её плавающая прибыль равна "+5.81", то вызов будет иметь вид:

    AddResultMfeMae(pos_id,5.81,0.0);

а если наша виртуальная позиция удаляется из списка открытых позиций "ArrOpenPositions" и её итоговый финансовый результат равен  "-3.06", то вызов будет иметь вид:

    AddResultMfeMae(pos_id,0.0,-3.06);

То есть, если есть плавающая прибыль, значит, итоговый финансовый результат равен "0", если позиция закрывается — значит плавающая прибыль равна "0".

4.2. Обработка позиций

  • ‌DEAL_ENTRY_IN - Вход в рынок. Записываем сделку в массив "ArrOpenPositions". Создаём новый элемент в массивах "m_arr_pos_id" и "m_arr_mfe_mae"
  • DEAL_ENTRY_OUT - Выход из рынка. Ищем в массиве "ArrOpenPositions" сделку с типом DEAL_ENTRY_IN с таким же DEAL_POSITION_ID
    • если поиск вернул false, значит в графики "Баланс" и "Средства" изменения не вносим
    • если поиск вернул true, сверяем объём (в лотах)
      • объёмы одинаковые - удаляем сделку из массива "ArrOpenPositions" и вносим изменения в массив "m_arr_balance_equity" - в графу "Баланс". В массив "m_arr_mfe_mae" записываем [плавающая прибыль 0.0][итоговый финансовый результат].
      • объём сделки из массива "ArrOpenPositions" больше - корректируем объём сделки в массиве "ArrOpenPositions" и вносим изменения в массив "m_arr_balance_equity" - в графу "Баланс". В массив "m_arr_mfe_mae" записываем [плавающая прибыль 0.0][итоговый финансовый результат].
      • объём сделки из массива "ArrOpenPositions" меньше - удаляем сделку из массива "ArrOpenPositions" и вносим изменения в массив "m_arr_balance_equity" - в графу "Баланс". В массив "m_arr_mfe_mae" записываем [плавающая прибыль 0.0][итоговый финансовый результат].
  • DEAL_ENTRY_INOUT - Разворот. Ищем в массиве "ArrOpenPositions" сделку с типом DEAL_ENTRY_IN с таким же DEAL_POSITION_ID
    • если поиск вернул false, значит в графики "Баланс" и "Средства" изменения не вносим
    • если поиск вернул true, корректируем объём сделки в массиве "ArrOpenPositions", корректируем тип сделки в массиве "ArrOpenPositions" и вносим изменения в массив "m_arr_balance_equity" - в графу "Баланс". В массив "m_arr_mfe_mae" записываем [плавающая прибыль 0.0][итоговый финансовый результат].
  • DEAL_ENTRY_OUT_BY - Закрытие встречной позицией. Ищем в массиве "ArrOpenPositions" сделку с типом DEAL_ENTRY_IN с таким же DEAL_POSITION_ID
    • объёмы одинаковые - удаляем сделку из массива "ArrOpenPositions" и вносим изменения в массив "m_arr_balance_equity" - в графу "Баланс". В массив "m_arr_mfe_mae" записываем [плавающая прибыль 0.0][итоговый финансовый результат].
    • объёмы разные - корректируем объём сделки в массиве "ArrOpenPositions" и вносим изменения в массив "m_arr_balance_equity" - в графу "Баланс". В массив "m_arr_mfe_mae" записываем [плавающая прибыль 0.0][итоговый финансовый результат].

Также, в "GetHistory", если нужно провести пересчёт плавающей прибыли, то для каждой позиции в массив "m_arr_mfe_mae" записываем [плавающая прибыль][итоговый финансовый результат 0.0].

4.3. Изменённая панель

Для того, чтобы отображать и график Баланс / Средства и MFE и MAE немного видоизменится панель:

panel 2

Рис. 4. Изменённая панель

Графики MFE (максимальная прибыль) и MAE (максимальный убыток) строятся по двум координатам: координата "X" - значение итогового финансового результата позиции, а по оси "Y" - значение MFE или MAE соответственно.

MFE

Рис. 5. MFE

MAE

Рис. 6. MAE


Заключение

Теперь на hedge счетах, при одновременной торговли нескольких советников, можно просмотреть статистику баланса и средств по каждому символу и каждому magic'у - то есть визуально определить, каков вклад конкретного советника (ORDER_MAGIC) в общий баланс и, самое главное, какие были просадки у каждого конкретного советника.

Программы, используемые в статье:

#
 Имя
Тип
Описание
1
HistoryDeal.mqhБиблиотека Класс ведения учета открытых позиций на выбранном промежутке
2
HistoryDealGetTicket.mq5Эксперт Пример получения свойств сделок из торговой истории за заданный промежуток времени
3
APHDialog.mqhБиблиотека Класс панели диалога советника
4
Accounting_positions_on_history.mq5Эксперт Главный советник - по мотивам статьи
5
MQL5.zipАрхив Архив с главным советником и его включаемыми файлами.


Прикрепленные файлы |
HistoryDeal.mqh (6.66 KB)
APHDialog.mqh (28.62 KB)
MQL5.zip (26.34 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (10)
Alexey Viktorov
Alexey Viktorov | 18 апр. 2017 в 09:20
Vladimir Karputov:


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

Код и текст будут исправлены.

А демку с комиссией найти мама не разрешает? Странный подход. Как можно браться за написание статьи не изучив тему... Да, в общем-то наплевать... Кому надо разберутся, а остальным оно не надо...
fxsaber
fxsaber | 19 апр. 2017 в 19:36
fxsaber:

Не понял только, почему так медленно идут расчеты? Сначала подумал, что используется специальное замедление, но "Sleep" и "OnTimer" в исходниках не обнаружил.

Предполагаю, что дело и в этом

Форум по трейдингу, автоматическим торговым системам и тестированию торговых стратегий

Особенности языка mql5, тонкости и приёмы работы

fxsaber, 2017.04.19 19:34

HistorySelect - очень дорогая функция. Но HistorySelectByPosition - еще дороже.

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

  1. Сделать HistorySelectByPosition и затем найти нужную сделку из полученного небольшого списка. Но список этот формируется следующим образом Сначала формируется ВСЯ история (равносильно вызову "бесконечного" HistorySelect). Затем идет ПОЛНЫЙ цикл for по этому списку и выбираются только те сделки, которые имеют соответствующий POSITION_IDENTIFIER.
  2. Сделать HistorySelect (можно "бесконечно", но лучше воспользоваться интервалом, если известен), затем в цикле for сделать break, когда дойдешь до соответствующего DEAL_ENTRY.

Второй пункт может быть значительно дешевле. Но точно не дороже.

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

Gladiator WXT
Gladiator WXT | 7 апр. 2018 в 20:46

"С введением хеджинга в MetaTrader 5 появилась отличная возможность одновременной торговли несколькими советниками на одном торговом счёте. При этом возможна ситуация, когда одна стратегия прибыльна, вторая убыточна, а в итоге график прибыли болтается около нуля. В таком случае полезно построить графики Баланса и Средств для каждой торговой стратегии по отдельности."

Тема реально очень важная, проблема поднята серьезная - MT5 не имеет функций расчета PnL в разрезе торговых стратегий, работающих на одном счету и символе (отличаемых по MAGICкам), НО ... автор просто гонит просто фуфло! Судя по всему Владимир просто программер, а не трейдер и соответственно совершенно не сечет тему.

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

Реальная  проблема состоит в том, что MT5 не учитывает MAGIC при расчете значений PnL нигде, и соответственно  DEAL_PROFIT по сделкам считается в общем случае неверно. Т.е. если у меня торгуют  три советника на символе: один тренд-катчер открывает позиции в долгосрок, а два осцилятора ловят периоды перекупленности/перепроданности на Н1/Н4, и торгуют на возврат к среднему, то PnL рассчитанная по сделкам без учета MAGICов представляет собой просто "температуру по больнице". А когда такие три советника на 20ти разных символах???

Соответственно по всем сделкам на закрытие позиций суммирование (_balance=m_balance+deal_commission+deal_swap+deal_profit;)в функции GetHistory НЕ КОРРЕКТНО.  До суммирования нужно пересчитывать DEAL_PROFIT. Причем пересчитывать с учетом валютных курсов на дату закрытия позиций, н-р в ситуации когда торговля ведется фьчерсами на MOEX с торгового счета с базовой валютой в usd.



EgorKim
EgorKim | 2 окт. 2018 в 22:52

Очень полезная штука. Владимир,заметил такой баг -

Счет открыт 4 дня назад. Выбираем в советнике дату старта по умолчанию т.е какой то там 2017год.

Дату окончания выбираем сегодняшний день 02.10.2018.

Советник зависает, на закрытия крестиком не реагирует. Терминал при этом отжирает 2гб оперативки.

Мне кажется правильней бы было перед поиском сравнить дату открытия счета с датой старта поиска указанной пользователем

Vladimir Karputov
Vladimir Karputov | 3 окт. 2018 в 06:21
EgorKim:

Очень полезная штука. Владимир,заметил такой баг -

Счет открыт 4 дня назад. Выбираем в советнике дату старта по умолчанию т.е какой то там 2017год.

Дату окончания выбираем сегодняшний день 02.10.2018.

Советник зависает, на закрытия крестиком не реагирует. Терминал при этом отжирает 2гб оперативки.

Мне кажется правильней бы было перед поиском сравнить дату открытия счета с датой старта поиска указанной пользователем

Уточните, пожалуйста, название файла, который Вы запускает. Тип торгового счёта. На каком символе. 

Графические интерфейсы X: Выделение текста в многострочном поле ввода (build 13) Графические интерфейсы X: Выделение текста в многострочном поле ввода (build 13)
В этой статье будет реализована возможность выделения текста с помощью различных клавишных комбинаций и удаление выделенного текста, по образцу того, как это сделано в любом другом текстовом редакторе. Кроме этого, продолжим оптимизировать код и подготовим классы для перехода к завершающему процессу второго этапа развития библиотеки, когда все элементы управления будут нарисованными на отдельных картинках (холстах для рисования).
Волны Вульфа Волны Вульфа
Графический метод, предложенный Биллом Вульфом, позволяет не только выявить фигуру и тем самым определить момент и направление входа, но и спрогнозировать цель, которую должна достигнуть цена, и время ее достижения. В статье описано, как на основе индикатора Зигзаг создать индикатор для поиска волн Вульфа и простой советник, торгующий по его сигналам.
Создание документации на основе исходных кодов MQL5 Создание документации на основе исходных кодов MQL5
В статье рассматривается создание документации к коду на MQL5, начиная с автоматизации простановки необходимых тэгов. Далее описана работа с программой Doxygen, её правильная настройка и получение результатов в различных форматах: в html, в HtmlHelp и в PDF.
Рецепты MQL5 - Создаем кольцевой буфер для быстрого расчета индикаторов в скользящем окне Рецепты MQL5 - Создаем кольцевой буфер для быстрого расчета индикаторов в скользящем окне
Кольцевой буфер — самый простой и в то же время наиболее эффективный способ организации данных для расчетов в скользящем окне. В статье описано, как устроен этот алгоритм, и показано, как с его помощью сделать вычисление в скользящем окне простым и эффективным процессом.