English Deutsch 日本語
preview
Индикатор исторических позиций на графике в виде диаграммы их прибыли/убытка

Индикатор исторических позиций на графике в виде диаграммы их прибыли/убытка

MetaTrader 5Примеры | 19 декабря 2023, 11:39
1 529 10
Artyom Trishkin
Artyom Trishkin

Содержание


Введение

Позиция... Позиция является следствием исполнения (сделка) торгового приказа (ордер), отосланного на сервер: Order --> Deal --> Position.

Список открытых позиций мы всегда можем получить в своей программе посредством функций PositionSelect() при неттинговом учёте позиций с указанием имени символа, на котором открыта позиция:

   if(PositionSelect(symbol_name))
     {
      /*
      Работаем с данными выбранной позиции
      PositionGetDouble();
      PositionGetInteger();
      PositionGetString();
      */
     }

Для счетов с независимым представлением позиций (хедж) сначала нужно получить количество позиций при помощи PositionsTotal(), затем в цикле по количеству позиций нужно получить тикет позиции по её индексу в списке открытых позиций при помощи PositionGetTicket(), и далее выбрать позицию по полученному тикету для работы с ней при помощи PositionSelectByTicket():

   int total=PositionsTotal();
   for(int i=total-1;i>=0;i--)
     {
      ulong ticket=PositionGetTicket(i);
      if(ticket==0 || !PositionSelectByTicket(ticket))
         continue;
      /*
      Работаем с данными выбранной позиции
      PositionGetDouble();
      PositionGetInteger();
      PositionGetString();
      */
     }

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

В этом случае нужно помнить и знать, что у каждой позиции есть собственный уникальный идентификатор. И этот идентификатор прописывается в сделках, которые повлияли на позицию — привели к её открытию, изменению, либо закрытию. А список сделок, а из него сделку и идентификатор позиции, в которой участвовала эта сделка, мы можем получить при помощи функций HistorySelect(), HistoryDealsTotal() и HistoryDealGetTicket() (HistoryDealSelect()).

В общем случае идентификатор позиции, в которой участвовала сделка, можно получить так:

   if(HistorySelect(0,TimeCurrent()))
     {
      int total=HistoryDealsTotal();
      for(int i=0;i<total;i++)
        {
         ulong ticket=HistoryDealGetTicket(i);
         if(ticket==0)
            continue;
         long pos_id=HistoryDealGetInteger(ticket,DEAL_POSITION_ID); // Идентификатор позиции, в которой участвовала сделка
         /*
         Работаем с данными выбранной сделки
         HistoryDealGetDouble();
         HistoryDealGetInteger();
         HistoryDealGetString();
         */
        }
     }

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

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

Нам потребуется три класса:

  1. Класс сделки. Содержит свойства сделки, необходимые для идентификации позиции и её свойств.
  2. Класс позиции. Содержит список сделок, участвовавших в ней за время жизни позиции и свойства, присущие позициям.
  3. Класс-список исторических позиций. Список найденных исторических позиций с возможностью выбора позиции по указанным свойствам.

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

В папке Indicators создадим новый файл индикатора с названием PositionInfoIndicator. Укажем рисование индикатора с одним рисуемым буфером со стилем Filling в отдельном окне графика с цветами заливки Green и Red.

Будет создан шаблон индикатора с такой шапкой:

//+------------------------------------------------------------------+
//|                                        PositionInfoIndicator.mq5 |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property indicator_separate_window
#property indicator_buffers 2
#property indicator_plots   1
//--- plot Fill
#property indicator_label1  "Profit;ZeroLine"
#property indicator_type1   DRAW_FILLING
#property indicator_color1  clrGreen,clrRed
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1

Ниже по коду будем вписывать создаваемые классы.

Для создания списков сделок и позиций будем использовать класс динамического массива указателей на экземпляры класса CObject и его наследников Стандартной Библиотеки.

Подключим файл этого класса к созданному файлу:

//+------------------------------------------------------------------+
//|                                        PositionInfoIndicator.mq5 |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property indicator_separate_window
#property indicator_buffers 2
#property indicator_plots   1
//--- plot Fill
#property indicator_label1  "Profit;ZeroLine"
#property indicator_type1   DRAW_FILLING
#property indicator_color1  clrGreen,clrRed
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1
//--- includes
#include <Arrays\ArrayObj.mqh>

Класс содержит метод Search() для поиска элементов, равных образцу, в сортированном массиве:

//+------------------------------------------------------------------+
//| Search of position of element in a sorted array                  |
//+------------------------------------------------------------------+
int CArrayObj::Search(const CObject *element) const
  {
   int pos;
//--- check
   if(m_data_total==0 || !CheckPointer(element) || m_sort_mode==-1)
      return(-1);
//--- search
   pos=QuickSearch(element);
   if(m_data[pos].Compare(element,m_sort_mode)==0)
      return(pos);
//--- not found
   return(-1);
  }

Массив должен быть сортированным по какому-либо свойству объектов, указатели на которые содержатся в нём. Режим сортировки устанавливается передачей в переменную m_sort_mode целочисленного значения. По умолчанию используется значение 0. Значение -1 обозначает, что массив не сортированный. Для сортировки по разным свойствам нужно устанавливать различные режимы сортировки установкой в переменную m_sort_mode значений от нуля и выше. Для этого удобно использовать перечисления, в которых определены различные режимы сортировки списков. Эти режимы используются в виртуальном методе сравнения двух объектов Compare(). Он определён в файле класса CObject и возвращает значение 0, что означает идентичность сравниваемых объектов:

   //--- method of comparing the objects
   virtual int       Compare(const CObject *node,const int mode=0) const { return(0);      }

Этот метод необходимо переопределять в своих классах, где можно организовать сравнение двух однотипных объектов по различным их свойствам (при значении m_sort_mode равным 0 объекты сравниваются по одному свойству, при значении m_sort_mode, равным 1 — по другому свойству, равным 2 — по третьему, и т.д.).

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

//+------------------------------------------------------------------+
//|                                        PositionInfoIndicator.mq5 |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property indicator_separate_window
#property indicator_buffers 2
#property indicator_plots   1
//--- plot Fill
#property indicator_label1  "Profit;ZeroLine"
#property indicator_type1   DRAW_FILLING
#property indicator_color1  clrGreen,clrRed
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1
//--- includes
#include <Arrays\ArrayObj.mqh>
//--- enums
//--- Режимы сортировки объектов сделок
enum ENUM_DEAL_SORT_MODE
  {
   DEAL_SORT_MODE_TIME_MSC,   // Время совершения сделки в миллисекундах
   DEAL_SORT_MODE_TIME,       // Время совершения сделки
   DEAL_SORT_MODE_TIKET,      // Тикет сделки
   DEAL_SORT_MODE_POS_ID,     // Идентификатор позиции
   DEAL_SORT_MODE_MAGIC,      // Magic number для сделки
   DEAL_SORT_MODE_TYPE,       // Тип сделки
   DEAL_SORT_MODE_ENTRY,      // Направление сделки – вход в рынок, выход из рынка или разворот
   DEAL_SORT_MODE_VOLUME,     // Объем сделки
   DEAL_SORT_MODE_PRICE,      // Цена сделки
   DEAL_SORT_MODE_COMISSION,  // Комиссия по сделке
   DEAL_SORT_MODE_SWAP,       // Накопленный своп при закрытии
   DEAL_SORT_MODE_PROFIT,     // Финансовый результат сделки
   DEAL_SORT_MODE_FEE,        // Оплата за проведение сделки
   DEAL_SORT_MODE_SYMBOL,     // Имя символа, по которому произведена сделка
  };
//--- Режимы сортировки объектов позиций
enum ENUM_POS_SORT_MODE
  {
   POS_SORT_MODE_TIME_IN_MSC, // Время открытия в миллисекундах
   POS_SORT_MODE_TIME_OUT_MSC,// Время закрытия в миллисекундах
   POS_SORT_MODE_TIME_IN,     // Время открытия
   POS_SORT_MODE_TIME_OUT,    // Время закрытия
   POS_SORT_MODE_DEAL_IN,     // Тикет сделки открытия
   POS_SORT_MODE_DEAL_OUT,    // Тикет сделки закрытия
   POS_SORT_MODE_ID,          // Идентификатор позиции
   POS_SORT_MODE_MAGIC,       // Магик позиции
   POS_SORT_MODE_PRICE_IN,    // Цена открытия
   POS_SORT_MODE_PRICE_OUT,   // Цена закрытия
   POS_SORT_MODE_VOLUME,      // Объем позиции
   POS_SORT_MODE_SYMBOL,      // Символ позиции
  };  
//--- classes

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

Ниже будем вписывать коды создаваемых классов.


    Класс сделок

    Рассмотрим класс сделки целиком:
    //+------------------------------------------------------------------+
    //| Класс сделки                                                     |
    //+------------------------------------------------------------------+
    class CDeal : public CObject
      {
    private:
       long              m_ticket;                        // Тикет сделки
       long              m_magic;                         // Magic number для сделки
       long              m_position_id;                   // Идентификатор позиции
       long              m_time_msc;                      // Время совершения сделки в миллисекундах
       datetime          m_time;                          // Время совершения сделки
       ENUM_DEAL_TYPE    m_type;                          // Тип сделки
       ENUM_DEAL_ENTRY   m_entry;                         // Направление сделки – вход в рынок, выход из рынка или разворот
       double            m_volume;                        // Объем сделки
       double            m_price;                         // Цена сделки
       double            m_comission;                     // Комиссия по сделке
       double            m_swap;                          // Накопленный своп при закрытии
       double            m_profit;                        // Финансовый результат сделки
       double            m_fee;                           // Оплата за проведение сделки
       string            m_symbol;                        // Имя символа, по которому произведена сделка
    //--- Возвращает описание направления сделки
       string            EntryDescription(void) const
                           {
                            return(this.m_entry==DEAL_ENTRY_IN ? "Entry In" : this.m_entry==DEAL_ENTRY_OUT ? "Entry Out" : this.m_entry==DEAL_ENTRY_INOUT ? "Reverce" : "Close a position by an opposite one");
                           }
    //--- Возвращает описание типа сделки
       string            TypeDescription(void) const
                           {
                            switch(this.m_type)
                              {
                               case DEAL_TYPE_BUY                     :  return "Buy";
                               case DEAL_TYPE_SELL                    :  return "Sell";
                               case DEAL_TYPE_BALANCE                 :  return "Balance";
                               case DEAL_TYPE_CREDIT                  :  return "Credit";
                               case DEAL_TYPE_CHARGE                  :  return "Additional charge";
                               case DEAL_TYPE_CORRECTION              :  return "Correction";
                               case DEAL_TYPE_BONUS                   :  return "Bonus";
                               case DEAL_TYPE_COMMISSION              :  return "Additional commission";
                               case DEAL_TYPE_COMMISSION_DAILY        :  return "Daily commission";
                               case DEAL_TYPE_COMMISSION_MONTHLY      :  return "Monthly commission";
                               case DEAL_TYPE_COMMISSION_AGENT_DAILY  :  return "Daily agent commission";
                               case DEAL_TYPE_COMMISSION_AGENT_MONTHLY:  return "Monthly agent commission";
                               case DEAL_TYPE_INTEREST                :  return "Interest rate";
                               case DEAL_TYPE_BUY_CANCELED            :  return "Canceled buy deal";
                               case DEAL_TYPE_SELL_CANCELED           :  return "Canceled sell deal";
                               case DEAL_DIVIDEND                     :  return "Dividend operations";
                               case DEAL_DIVIDEND_FRANKED             :  return "Franked (non-taxable) dividend operations";
                               case DEAL_TAX                          :  return "Tax charges";
                               default                                :  return "Unknown: "+(string)this.m_type;
                              }
                           }
    //--- Возвращает время с миллисекундами
       string            TimeMSCtoString(const long time_msc,int flags=TIME_DATE|TIME_MINUTES|TIME_SECONDS)
                           {
                            return ::TimeToString(time_msc/1000,flags)+"."+::IntegerToString(time_msc%1000,3,'0');
                           }
    public:
    //--- Методы возврата свойств сделки
       long              Ticket(void)                     const { return this.m_ticket;       }  // Тикет сделки
       long              Magic(void)                      const { return this.m_magic;        }  // Magic number для сделки
       long              PositionID(void)                 const { return this.m_position_id;  }  // Идентификатор позиции
       long              TimeMsc(void)                    const { return this.m_time_msc;     }  // Время совершения сделки в миллисекундах
       datetime          Time(void)                       const { return this.m_time;         }  // Время совершения сделки
       ENUM_DEAL_TYPE    TypeDeal(void)                   const { return this.m_type;         }  // Тип сделки
       ENUM_DEAL_ENTRY   Entry(void)                      const { return this.m_entry;        }  // Направление сделки – вход в рынок, выход из рынка или разворот
       double            Volume(void)                     const { return this.m_volume;       }  // Объем сделки
       double            Price(void)                      const { return this.m_price;        }  // Цена сделки
       double            Comission(void)                  const { return this.m_comission;    }  // Комиссия по сделке
       double            Swap(void)                       const { return this.m_swap;         }  // Накопленный своп при закрытии
       double            Profit(void)                     const { return this.m_profit;       }  // Финансовый результат сделки
       double            Fee(void)                        const { return this.m_fee;          }  // Оплата за проведение сделки
       string            Symbol(void)                     const { return this.m_symbol;       }  // Имя символа, по которому произведена сделка
    //--- Методы установки свойств сделки
       void              SetTicket(const long ticket)           { this.m_ticket=ticket;       }  // Тикет сделки
       void              SetMagic(const long magic)             { this.m_magic=magic;         }  // Magic number для сделки
       void              SetPositionID(const long id)           { this.m_position_id=id;      }  // Идентификатор позиции
       void              SetTimeMsc(const long time_msc)        { this.m_time_msc=time_msc;   }  // Время совершения сделки в миллисекундах
       void              SetTime(const datetime time)           { this.m_time=time;           }  // Время совершения сделки
       void              SetType(const ENUM_DEAL_TYPE type)     { this.m_type=type;           }  // Тип сделки
       void              SetEntry(const ENUM_DEAL_ENTRY entry)  { this.m_entry=entry;         }  // Направление сделки – вход в рынок, выход из рынка или разворот
       void              SetVolume(const double volume)         { this.m_volume=volume;       }  // Объем сделки
       void              SetPrice(const double price)           { this.m_price=price;         }  // Цена сделки
       void              SetComission(const double comission)   { this.m_comission=comission; }  // Комиссия по сделке
       void              SetSwap(const double swap)             { this.m_swap=swap;           }  // Накопленный своп при закрытии
       void              SetProfit(const double profit)         { this.m_profit=profit;       }  // Финансовый результат сделки
       void              SetFee(const double fee)               { this.m_fee=fee;             }  // Оплата за проведение сделки
       void              SetSymbol(const string symbol)         { this.m_symbol=symbol;       }  // Имя символа, по которому произведена сделка
    //--- Метод сравнения двух объектов
       virtual int       Compare(const CObject *node,const int mode=0) const
                           {
                            const CDeal *compared_obj=node;
                            switch(mode)
                              {
                               case DEAL_SORT_MODE_TIME      :  return(this.Time()>compared_obj.Time()             ?  1  :  this.Time()<compared_obj.Time()              ?  -1 :  0);
                               case DEAL_SORT_MODE_TIME_MSC  :  return(this.TimeMsc()>compared_obj.TimeMsc()       ?  1  :  this.TimeMsc()<compared_obj.TimeMsc()        ?  -1 :  0);
                               case DEAL_SORT_MODE_TIKET     :  return(this.Ticket()>compared_obj.Ticket()         ?  1  :  this.Ticket()<compared_obj.Ticket()          ?  -1 :  0);
                               case DEAL_SORT_MODE_MAGIC     :  return(this.Magic()>compared_obj.Magic()           ?  1  :  this.Magic()<compared_obj.Magic()            ?  -1 :  0);
                               case DEAL_SORT_MODE_POS_ID    :  return(this.PositionID()>compared_obj.PositionID() ?  1  :  this.PositionID()<compared_obj.PositionID()  ?  -1 :  0);
                               case DEAL_SORT_MODE_TYPE      :  return(this.TypeDeal()>compared_obj.TypeDeal()     ?  1  :  this.TypeDeal()<compared_obj.TypeDeal()      ?  -1 :  0);
                               case DEAL_SORT_MODE_ENTRY     :  return(this.Entry()>compared_obj.Entry()           ?  1  :  this.Entry()<compared_obj.Entry()            ?  -1 :  0);
                               case DEAL_SORT_MODE_VOLUME    :  return(this.Volume()>compared_obj.Volume()         ?  1  :  this.Volume()<compared_obj.Volume()          ?  -1 :  0);
                               case DEAL_SORT_MODE_PRICE     :  return(this.Price()>compared_obj.Price()           ?  1  :  this.Price()<compared_obj.Price()            ?  -1 :  0);
                               case DEAL_SORT_MODE_COMISSION :  return(this.Comission()>compared_obj.Comission()   ?  1  :  this.Comission()<compared_obj.Comission()    ?  -1 :  0);
                               case DEAL_SORT_MODE_SWAP      :  return(this.Swap()>compared_obj.Swap()             ?  1  :  this.Swap()<compared_obj.Swap()              ?  -1 :  0);
                               case DEAL_SORT_MODE_PROFIT    :  return(this.Profit()>compared_obj.Profit()         ?  1  :  this.Profit()<compared_obj.Profit()          ?  -1 :  0);
                               case DEAL_SORT_MODE_FEE       :  return(this.Fee()>compared_obj.Fee()               ?  1  :  this.Fee()<compared_obj.Fee()                ?  -1 :  0);
                               case DEAL_SORT_MODE_SYMBOL    :  return(this.Symbol()>compared_obj.Symbol()         ?  1  :  this.Symbol()<compared_obj.Symbol()          ?  -1 :  0);
                               default                       :  return(this.TimeMsc()>compared_obj.TimeMsc()       ?  1  :  this.TimeMsc()<compared_obj.TimeMsc()        ?  -1 :  0);
                              }
                           }
                           
    //--- Распечатывает в журнал свойства сделки
       void              Print(void)
                           {
                            ::PrintFormat("  Deal: %s type %s #%lld at %s",this.EntryDescription(),this.TypeDescription(),this.Ticket(),this.TimeMSCtoString(this.TimeMsc()));
                           }
    //--- Конструктор
                         CDeal(const long deal_ticket)
                           {
                            this.m_ticket=deal_ticket;
                            this.m_magic=::HistoryDealGetInteger(deal_ticket,DEAL_MAGIC);                    // Magic number для сделки
                            this.m_position_id=::HistoryDealGetInteger(deal_ticket,DEAL_POSITION_ID);        // Идентификатор позиции
                            this.m_time_msc=::HistoryDealGetInteger(deal_ticket,DEAL_TIME_MSC);              // Время совершения сделки в миллисекундах
                            this.m_time=(datetime)::HistoryDealGetInteger(deal_ticket,DEAL_TIME);            // Время совершения сделки
                            this.m_type=(ENUM_DEAL_TYPE)::HistoryDealGetInteger(deal_ticket,DEAL_TYPE);      // Тип сделки
                            this.m_entry=(ENUM_DEAL_ENTRY)::HistoryDealGetInteger(deal_ticket,DEAL_ENTRY);   // Направление сделки – вход в рынок, выход из рынка или разворот
                            this.m_volume=::HistoryDealGetDouble(deal_ticket,DEAL_VOLUME);                   // Объем сделки
                            this.m_price=::HistoryDealGetDouble(deal_ticket,DEAL_PRICE);                     // Цена сделки
                            this.m_comission=::HistoryDealGetDouble(deal_ticket,DEAL_COMMISSION);            // Комиссия по сделке
                            this.m_swap=::HistoryDealGetDouble(deal_ticket,DEAL_SWAP);                       // Накопленный своп при закрытии
                            this.m_profit=::HistoryDealGetDouble(deal_ticket,DEAL_PROFIT);                   // Финансовый результат сделки
                            this.m_fee=::HistoryDealGetDouble(deal_ticket,DEAL_FEE);                         // Оплата за проведение сделки
                            this.m_symbol=::HistoryDealGetString(deal_ticket,DEAL_SYMBOL);                   // Имя символа, по которому произведена сделка
                           }
                        ~CDeal(void){}
      };
    

    В принципе, тут всё просто: в приватной секции объявлены переменные-члены класса для хранения свойств сделки. В публичной секции реализованы методы для установки и возврата свойств сделки. Виртуальный метод Compare() реализует сравнение по каждому свойству сделки в зависимости от того, какое свойство передано в метод как режим сравнения. Если значение проверяемого свойства у текущего объекта больше значения этого же свойства у сравниваемого, то возвращается 1, если меньше — возвращается -1, иначе, при равенстве значений, возвращается 0. В конструкторе класса считается, что сделка уже выбрана, и её свойства записываются в соответствующие приватные переменные класса. И этого уже достаточно для создания объекта-сделки, хранящего все нужные свойства выбранной сделки из списка исторических сделок терминала. Для каждой сделки будет создаваться объект этого класса, из свойств объекта будет извлекаться идентификатор позиции и создаваться объект класса исторической позиции. Все сделки, принадлежащие этой позиции, будут заноситься в список сделок класса позиции. Таким образом, объект исторической позиции будет содержать в себе список всех своих сделок. И уже из них можно будет извлекать историю жизни этой позиции.

    Рассмотрим класс исторической позиции.

    Класс позиций

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

    //+------------------------------------------------------------------+
    //| Класс позиции                                                    |
    //+------------------------------------------------------------------+
    class CPosition : public CObject
      {
    private:
       CArrayObj         m_list_deals;                    // Список сделок позиции
       long              m_position_id;                   // Идентификатор позиции
       long              m_time_in_msc;                   // Время открытия в миллисекундах
       long              m_time_out_msc;                  // Время закрытия в миллисекундах
       long              m_magic;                         // Магик позиции
       datetime          m_time_in;                       // Время открытия
       datetime          m_time_out;                      // Время закрытия
       ulong             m_deal_in_ticket;                // Тикет сделки открытия
       ulong             m_deal_out_ticket;               // Тикет сделки закрытия
       double            m_price_in;                      // Цена открытия
       double            m_price_out;                     // Цена закрытия
       double            m_volume;                        // Объём позиции
       ENUM_POSITION_TYPE m_type;                         // Тип позиции
       string            m_symbol;                        // Символ позиции
       int               m_digits;                        // Digits символа
       double            m_point;                         // Значение одного пункта символа
       double            m_contract_size;                 // Размер торгового контракта символа
       string            m_currency_profit;               // Валюта прибыли символа
       string            m_account_currency;              // Валюта депозита
    //--- Возвращает время с миллисекундами
       string            TimeMSCtoString(const long time_msc,int flags=TIME_DATE|TIME_MINUTES|TIME_SECONDS)
                           {
                            return ::TimeToString(time_msc/1000,flags)+"."+::IntegerToString(time_msc%1000,3,'0');
                           }
    //--- Рассчитывает и возвращает время открытия заведомо существующего бара на указанном периоде графика по заданному времени
    //--- (https://www.mql5.com/ru/forum/170952/page234#comment_50523898)
       datetime          BarOpenTime(const ENUM_TIMEFRAMES timeframe,const datetime time) const
                           {
                            ENUM_TIMEFRAMES period=(timeframe==PERIOD_CURRENT ? ::Period() : timeframe);
                            //--- Расчёт времени открытия бара на периодах, меньших W1
                            if(period<PERIOD_W1)
                               return time-time%::PeriodSeconds(period);
                            //--- Расчёт времени открытия бара на периоде W1
                            if(period==PERIOD_W1)
                               return time-(time+4*24*60*60)%::PeriodSeconds(period);
                            //--- Расчёт времени открытия бара на периоде MN1
                            else
                              {
                               MqlDateTime dt;
                               ::ResetLastError();
                               if(!::TimeToStruct(time,dt))
                                 {
                                  ::PrintFormat("%s: TimeToStruct failed. Error %lu",__FUNCTION__,::GetLastError());
                                  return 0;
                                 }
                               return time-(time%(24*60*60))-(dt.day-1)*(24*60*60);
                              }
                           }
    //--- Возвращает флаг наличия символа на сервере. Добавляет символ в окно MarketWatch
       bool              SymbolIsExist(const string symbol) const
                           {
                            bool custom=false;
                            if(!::SymbolExist(symbol,custom))
                               return false;
                            return ::SymbolSelect(symbol,true);
                           }
    //--- Возвращает стоимость одного пункта
       double            GetOnePointPrice(const datetime time) const
                           {
                            if(time==0)
                               return 0;
                            //--- Если валюта прибыли символа совпадает с валютой счёта, возвращаем размер контракта * Point символа
                            if(this.m_currency_profit==this.m_account_currency)
                               return this.m_point*this.m_contract_size;
                            //--- Иначе проверяем наличие символа с названием "Валюта счёта" + "Валюта прибыли символа"
                            double array[1];
                            string reverse=this.m_account_currency+this.m_currency_profit;
                            //--- Если такой символ существует и добавлен в Обзор рынка
                            if(this.SymbolIsExist(reverse))
                              {
                               //--- Если цена закрытия бара по времени time получена, возвращаем размер контракта * Point символа, делённые на цену Close бара
                               if(::CopyClose(reverse,PERIOD_CURRENT,time,1,array)==1 && array[0]>0)
                                  return this.m_point*this.m_contract_size/array[0];
                               //--- Если не удалось получить цену закрытия бара по времени time , возвращаем ноль
                               else
                                  return 0;
                              }
                            //--- Проверяем наличие символа с названием "Валюта прибыли символа" + "Валюта счёта"
                            string direct=this.m_currency_profit+this.m_account_currency;
                            //--- Если такой символ существует и добавлен в Обзор рынка
                            if(this.SymbolIsExist(direct))
                              {
                               //--- Если цена закрытия бара по времени time получена, возвращаем размер контракта * Point символа, умноженные на цену Close бара
                               if(::CopyClose(direct,PERIOD_CURRENT,time,1,array)==1)
                                  return this.m_point*this.m_contract_size*array[0];
                              }
                            //--- Не удалось получить символы, у которых валюта прибыли символа не совпадает с валютой счёта, ни обратный, ни прямой - возвращаем ноль
                            return 0;
                           }
    public:
    //--- Методы возврата свойств позиции
       long              ID(void)                         const { return this.m_position_id;              }  // Идентификатор позиции
       long              Magic(void)                      const { return this.m_magic;                    }  // Магик позиции
       long              TimeInMsc(void)                  const { return this.m_time_in_msc;              }  // Время открытия
       long              TimeOutMsc(void)                 const { return this.m_time_out_msc;             }  // Время закрытия
       datetime          TimeIn(void)                     const { return this.m_time_in;                  }  // Время открытия
       datetime          TimeOut(void)                    const { return this.m_time_out;                 }  // Время закрытия
       ulong             DealIn(void)                     const { return this.m_deal_in_ticket;           }  // Тикет сделки открытия
       ulong             DealOut(void)                    const { return this.m_deal_out_ticket;          }  // Тикет сделки закрытия
       ENUM_POSITION_TYPE TypePosition(void)              const { return this.m_type;                     }  // Тип позиции
       double            PriceIn(void)                    const { return this.m_price_in;                 }  // Цена открытия
       double            PriceOut(void)                   const { return this.m_price_out;                }  // Цена закрытия
       double            Volume(void)                     const { return this.m_volume;                   }  // Объём позиции
       string            Symbol(void)                     const { return this.m_symbol;                   }  // Символ позиции
    //--- Методы установки свойств позиции
       void              SetID(long id)                         { this.m_position_id=id;                  }  // Идентификатор позиции
       void              SetMagic(long magic)                   { this.m_magic=magic;                     }  // Магик позиции
       void              SetTimeInMsc(long time_in_msc)         { this.m_time_in_msc=time_in_msc;         }  // Время открытия
       void              SetTimeOutMsc(long time_out_msc)       { this.m_time_out_msc=time_out_msc;       }  // Время закрытия
       void              SetTimeIn(datetime time_in)            { this.m_time_in=time_in;                 }  // Время открытия
       void              SetTimeOut(datetime time_out)          { this.m_time_out=time_out;               }  // Время закрытия
       void              SetDealIn(ulong ticket_deal_in)        { this.m_deal_in_ticket=ticket_deal_in;   }  // Тикет сделки открытия
       void              SetDealOut(ulong ticket_deal_out)      { this.m_deal_out_ticket=ticket_deal_out; }  // Тикет сделки закрытия
       void              SetType(ENUM_POSITION_TYPE type)       { this.m_type=type;                       }  // Тип позиции
       void              SetPriceIn(double price_in)            { this.m_price_in=price_in;               }  // Цена открытия
       void              SetPriceOut(double price_out)          { this.m_price_out=price_out;             }  // Цена закрытия
       void              SetVolume(double new_volume)           { this.m_volume=new_volume;               }  // Объём позиции
       void              SetSymbol(string symbol)                                                            // Символ позиции
                           {
                            this.m_symbol=symbol;
                            this.m_digits=(int)::SymbolInfoInteger(this.m_symbol,SYMBOL_DIGITS);
                            this.m_point=::SymbolInfoDouble(this.m_symbol,SYMBOL_POINT);
                            this.m_contract_size=::SymbolInfoDouble(this.m_symbol,SYMBOL_TRADE_CONTRACT_SIZE);
                            this.m_currency_profit=::SymbolInfoString(this.m_symbol,SYMBOL_CURRENCY_PROFIT);
                           }
    //--- Добавляет сделку в список сделок
       bool              DealAdd(CDeal *deal)
                           {
                            //--- Объявляем переменную результата добавления сделки в список
                            bool res=false;
                            //--- Устанавливаем списку флаг сортировки по тикету сделки
                            this.m_list_deals.Sort(DEAL_SORT_MODE_TIKET);
                            //--- Если сделки с таким тикетом нет в списке -
                            if(this.m_list_deals.Search(deal)==WRONG_VALUE)
                              {
                               //--- Устанавливаем списку флаг сортировки по времени в миллисекундах и
                               //--- возвращаем результат добавления сделки в список в порядке сортировки по времени
                               this.m_list_deals.Sort(DEAL_SORT_MODE_TIME_MSC);
                               res=this.m_list_deals.InsertSort(deal);
                              }
                            //--- Если сделка уже есть в списке - возвращаем false
                            else
                               this.m_list_deals.Sort(DEAL_SORT_MODE_TIME_MSC);
                            return res;
                           }
    //--- Возвращает время начала бара (1) открытия, (2) закрытия позиции на текущем периоде графика
       datetime          BarTimeOpenPosition(void) const
                           {
                            return this.BarOpenTime(PERIOD_CURRENT,this.TimeIn());
                           }
       datetime          BarTimeClosePosition(void) const
                           {
                            return this.BarOpenTime(PERIOD_CURRENT,this.TimeOut());
                           }
    //--- Возвращает флаг существования позиции в указанное время
       bool              IsPresentInTime(const datetime time) const
                           {
                            return(time>=this.BarTimeOpenPosition() && time<=this.BarTimeClosePosition());
                           }
    //--- Возвращает профит позиции в количестве пунктов или в стоимости количества пунктов относительно цены закрытия
       double            ProfitRelativeClosePrice(const double price_close,const datetime time,const bool points) const
                           {
                            //--- Если на указанном времени позиции не было - возвращаем 0
                            if(!this.IsPresentInTime(time))
                               return 0;
                            //--- Рассчитываем количество пунктов прибыли в зависимости от направления позиции
                            int pp=int((this.TypePosition()==POSITION_TYPE_BUY ? price_close-this.PriceIn() : this.PriceIn()-price_close)/this.m_point);
                            //--- Если прибыль в пунктах - возвращаем рассчитанное количество пунктов
                            if(points)
                               return pp;
                            //--- Иначе - возвращаем рассчитанное количество пунктов, умноженное на (стоимость одного пункта * объём позиции)
                            return pp*this.GetOnePointPrice(time)*this.Volume();
                           }
    //--- Метод сравнения двух объектов
       virtual int       Compare(const CObject *node,const int mode=0) const
                           {
                            const CPosition *compared_obj=node;
                            switch(mode)
                              {
                               case POS_SORT_MODE_TIME_IN_MSC   :  return(this.TimeInMsc()>compared_obj.TimeInMsc()   ?  1  :  this.TimeInMsc()<compared_obj.TimeInMsc()    ?  -1 :  0);
                               case POS_SORT_MODE_TIME_OUT_MSC  :  return(this.TimeOutMsc()>compared_obj.TimeOutMsc() ?  1  :  this.TimeOutMsc()<compared_obj.TimeOutMsc()  ?  -1 :  0);
                               case POS_SORT_MODE_TIME_IN       :  return(this.TimeIn()>compared_obj.TimeIn()         ?  1  :  this.TimeIn()<compared_obj.TimeIn()          ?  -1 :  0);
                               case POS_SORT_MODE_TIME_OUT      :  return(this.TimeOut()>compared_obj.TimeOut()       ?  1  :  this.TimeOut()<compared_obj.TimeOut()        ?  -1 :  0);
                               case POS_SORT_MODE_DEAL_IN       :  return(this.DealIn()>compared_obj.DealIn()         ?  1  :  this.DealIn()<compared_obj.DealIn()          ?  -1 :  0);
                               case POS_SORT_MODE_DEAL_OUT      :  return(this.DealOut()>compared_obj.DealOut()       ?  1  :  this.DealOut()<compared_obj.DealOut()        ?  -1 :  0);
                               case POS_SORT_MODE_ID            :  return(this.ID()>compared_obj.ID()                 ?  1  :  this.ID()<compared_obj.ID()                  ?  -1 :  0);
                               case POS_SORT_MODE_MAGIC         :  return(this.Magic()>compared_obj.Magic()           ?  1  :  this.Magic()<compared_obj.Magic()            ?  -1 :  0);
                               case POS_SORT_MODE_SYMBOL        :  return(this.Symbol()>compared_obj.Symbol()         ?  1  :  this.Symbol()<compared_obj.Symbol()          ?  -1 :  0);
                               case POS_SORT_MODE_PRICE_IN      :  return(this.PriceIn()>compared_obj.PriceIn()       ?  1  :  this.PriceIn()<compared_obj.PriceIn()        ?  -1 :  0);
                               case POS_SORT_MODE_PRICE_OUT     :  return(this.PriceOut()>compared_obj.PriceOut()     ?  1  :  this.PriceOut()<compared_obj.PriceOut()      ?  -1 :  0);
                               case POS_SORT_MODE_VOLUME        :  return(this.Volume()>compared_obj.Volume()         ?  1  :  this.Volume()<compared_obj.Volume()          ?  -1 :  0);
                               default                          :  return(this.TimeInMsc()>compared_obj.TimeInMsc()   ?  1  :  this.TimeInMsc()<compared_obj.TimeInMsc()    ?  -1 :  0);
                              }
                           }
    //--- Возвращает описание типа позиции
       string            TypeDescription(void) const
                           {
                            return(this.m_type==POSITION_TYPE_BUY ? "Buy" : this.m_type==POSITION_TYPE_SELL ? "Sell" : "Unknown");
                           }
    //--- Распечатывает в журнале свойства позиции и её сделок
       void              Print(void)
                           {
                            //--- Выводим заголовок с описанием позиции
                            ::PrintFormat
                              (
                               "Position %s %s #%lld, Magic %lld\n-Opened at %s at a price of %.*f\n-Closed at %s at a price of %.*f:",
                               this.TypeDescription(),this.Symbol(),this.ID(),this.Magic(),
                               this.TimeMSCtoString(this.TimeInMsc()), this.m_digits,this.PriceIn(),
                               this.TimeMSCtoString(this.TimeOutMsc()),this.m_digits,this.PriceOut()
                              );
                            //--- В цикле по всем сделкам позиции выводим их описания
                            for(int i=0;i<this.m_list_deals.Total();i++)
                              {
                               CDeal *deal=this.m_list_deals.At(i);
                               if(deal==NULL)
                                  continue;
                               deal.Print();
                              }
                           }
    //--- Конструктор
                         CPosition(const long position_id) : m_time_in(0), m_time_in_msc(0),m_time_out(0),m_time_out_msc(0),m_deal_in_ticket(0),m_deal_out_ticket(0),m_type(WRONG_VALUE)
                           {
                            this.m_list_deals.Sort(DEAL_SORT_MODE_TIME_MSC);
                            this.m_position_id=position_id;
                            this.m_account_currency=::AccountInfoString(ACCOUNT_CURRENCY);
                           }
      };
    

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

    Метод BarOpenTime() рассчитывает и возвращает время открытия бара на заданном таймфрейме по некоему времени, передаваемому в метод:

       datetime          BarOpenTime(const ENUM_TIMEFRAMES timeframe,const datetime time) const
                           {
                            ENUM_TIMEFRAMES period=(timeframe==PERIOD_CURRENT ? ::Period() : timeframe);
                            //--- Расчёт времени открытия бара на периодах, меньших W1
                            if(period<PERIOD_W1)
                               return time-time%::PeriodSeconds(period);
                            //--- Расчёт времени открытия бара на периоде W1
                            if(period==PERIOD_W1)
                               return time-(time+4*24*60*60)%::PeriodSeconds(period);
                            //--- Расчёт времени открытия бара на периоде MN1
                            else
                              {
                               MqlDateTime dt;
                               ::ResetLastError();
                               if(!::TimeToStruct(time,dt))
                                 {
                                  ::PrintFormat("%s: TimeToStruct failed. Error %lu",__FUNCTION__,::GetLastError());
                                  return 0;
                                 }
                               return time-(time%(24*60*60))-(dt.day-1)*(24*60*60);
                              }
                           }
    

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

    //--- Возвращает флаг существования позиции в указанное время
       bool              IsPresentInTime(const datetime time) const
                           {
                            return(time>=this.BarTimeOpenPosition() && time<=this.BarTimeClosePosition());
                           }
    

    или просто можем получить бар открытия или закрытия позиции:

    //--- Возвращает время начала бара (1) открытия, (2) закрытия позиции на текущем периоде графика
       datetime          BarTimeOpenPosition(void) const
                           {
                            return this.BarOpenTime(PERIOD_CURRENT,this.TimeIn());
                           }
       datetime          BarTimeClosePosition(void) const
                           {
                            return this.BarOpenTime(PERIOD_CURRENT,this.TimeOut());
                           }
    

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

       double            ProfitRelativeClosePrice(const double price_close,const datetime time,const bool points) const
                           {
                            //--- Если на указанном времени позиции не было - возвращаем 0
                            if(!this.IsPresentInTime(time))
                               return 0;
                            //--- Рассчитываем количество пунктов прибыли в зависимости от направления позиции
                            int pp=int((this.TypePosition()==POSITION_TYPE_BUY ? price_close-this.PriceIn() : this.PriceIn()-price_close)/this.m_point);
                            //--- Если прибыль в пунктах - возвращаем рассчитанное количество пунктов
                            if(points)
                               return pp;
                            //--- Иначе - возвращаем рассчитанное количество пунктов, умноженное на (стоимость одного пункта * объём позиции)
                            return pp*this.GetOnePointPrice(time)*this.Volume();
                           }
    

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

    В конструктор класса передаётся идентификатор позиции, объект которой будет создан:

                         CPosition(const long position_id) : m_time_in(0), m_time_in_msc(0),m_time_out(0),m_time_out_msc(0),m_deal_in_ticket(0),m_deal_out_ticket(0),m_type(WRONG_VALUE)
                           {
                            this.m_list_deals.Sort(DEAL_SORT_MODE_TIME_MSC);
                            this.m_position_id=position_id;
                            this.m_account_currency=::AccountInfoString(ACCOUNT_CURRENCY);
                           }
    

    Идентификатор на момент создания объекта класса уже известен — его мы получаем из свойств сделки. А выбранную сделку, из которой был получен идентификатор позиции, после создания объекта класса позиции мы добавляем в её список сделок. Всё это делается в классе списка исторических позиций, который далее и рассмотрим.


    Класс списка исторических позиций

    //+------------------------------------------------------------------+
    //| Класс списка исторических позиций                                |
    //+------------------------------------------------------------------+
    class CHistoryPosition
      {
    private:
      CArrayObj          m_list_pos;          // Список исторических позиций
    public:
    //--- Создаёт список исторических позиций
       bool              CreatePositionList(const string symbol=NULL);
    //--- Возвращает объект-позицию из списка по (1) индексу, (2) идентификатору
       CPosition        *GetPositionObjByIndex(const int index)
                           {
                            return this.m_list_pos.At(index);
                           }
       CPosition        *GetPositionObjByID(const long id)
                           {
                            //--- Создаём временный объект позиции
                            CPosition *tmp=new CPosition(id);
                            //--- Устанавливаем списку позиций сортировку по идентификатору позиции
                            this.m_list_pos.Sort(POS_SORT_MODE_ID);
                            //--- Получаем индекс объекта в списке позиций с таким идентификатором
                            int index=this.m_list_pos.Search(tmp);
                            delete tmp;
                            this.m_list_pos.Sort(POS_SORT_MODE_TIME_IN_MSC);
                            return this.m_list_pos.At(index);
                           }
    //--- Добавляет сделку в список сделок
       bool              DealAdd(const long position_id,CDeal *deal)
                           {
                            CPosition *pos=this.GetPositionObjByID(position_id);
                            return(pos!=NULL ? pos.DealAdd(deal) : false);
                           }
    //--- Возвращает флаг нахождения указанной позиции в указанном времени
       bool              IsPresentInTime(CPosition *pos,const datetime time) const
                           {
                            return pos.IsPresentInTime(time);
                           }
    //--- Возвращает профит позиции относительно цены закрытия
       double            ProfitRelativeClosePrice(CPosition *pos,const double price_close,const datetime time,const bool points) const
                           {
                            return pos.ProfitRelativeClosePrice(price_close,time,points);
                           }
    //--- Возвращает количество исторических позиций
       int               PositionsTotal(void) const { return this.m_list_pos.Total();   }
    //--- Распечатывает в журнале свойства позиций и их сделок
       void              Print(void)
                           {
                            for(int i=0;i<this.m_list_pos.Total();i++)
                              {
                               CPosition *pos=this.m_list_pos.At(i);
                               if(pos==NULL)
                                  continue;
                               pos.Print();
                              }
                           }
    //--- Конструктор
                         CHistoryPosition(void)
                           {
                            this.m_list_pos.Sort(POS_SORT_MODE_TIME_IN_MSC);
                           }
      };
    

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

    Рассмотрим их подробнее.

    Метод CreatePositionList() создаёт список исторических позиций:

    //+------------------------------------------------------------------+
    //| CHistoryPosition::Создаёт список исторических позиций            |
    //+------------------------------------------------------------------+
    bool CHistoryPosition::CreatePositionList(const string symbol=NULL)
      {
    //--- Если запросить историю сделок и ордеров не удалось - возвращаем false
       if(!::HistorySelect(0,::TimeCurrent()))
          return false;
    //--- Объявляем переменную результата и указатель на объект позиции
       bool res=true;
       CPosition *pos=NULL;
    //--- В цикле по количеству сделок истории
       int total=::HistoryDealsTotal();
       for(int i=0;i<total;i++)
         {
          //--- получаем тикет очередной сделки в списке
          ulong ticket=::HistoryDealGetTicket(i);
          //--- Если тикет сделки не получен, или это балансовая операция, или если указан стмвол сделки, но сделка не по этому символу - идём дальше
          if(ticket==0 || ::HistoryDealGetInteger(ticket,DEAL_TYPE)==DEAL_TYPE_BALANCE || (symbol!=NULL && symbol!="" && ::HistoryDealGetString(ticket,DEAL_SYMBOL)!=symbol))
             continue;
          //--- Создаём объект-сделку и, если объект создать не удалось - добавляем к переменной res значение false и идём далее
          CDeal *deal=new CDeal(ticket);
          if(deal==NULL)
            {
             res &=false;
             continue;
            }
          //--- Получаем из сделки значение идентификатора позиции
          long pos_id=deal.PositionID();
          //--- Получаем указатель на объект-позицию из списка
          pos=this.GetPositionObjByID(pos_id);
          //--- Если такой позиции в списке ещё нет
          if(pos==NULL)
            {
             //--- создаём новый объект позиции и, если объект создать не удалось, - добавляем к переменной res значение false, удаляем объект сделки и идём далее
             pos=new CPosition(pos_id);
             if(pos==NULL)
               {
                res &=false;
                delete deal;
                continue;
               }
             //--- Ставим списку позиций флаг сортировки по времени в миллисекундах и добавляем объект позиции в соответствующее место списка
             this.m_list_pos.Sort(POS_SORT_MODE_TIME_IN_MSC);
             //--- Если объект позиции не удалось добавить в список - добавляем к переменной res значение false, удаляем объекты сделки и позиции и идём далее
             if(!this.m_list_pos.InsertSort(pos))
               {
                res &=false;
                delete deal;
                delete pos;
                continue;
               }
            }
          //--- Если объект позиции создан и добавлен в список
          //--- Если объект сделки не удалось добавить в список сделок объекта позиции - добавляем к переменной res значение false, удаляем объект сделки и идём далее
          if(!pos.DealAdd(deal))
            {
             res &=false;
             delete deal;
             continue;
            }
          //--- Всё успешно.
          //--- В зависимости от типа сделки устанавливаем свойства позиции
          if(deal.Entry()==DEAL_ENTRY_IN)
            {
             pos.SetSymbol(deal.Symbol());
             pos.SetDealIn(deal.Ticket());
             pos.SetTimeIn(deal.Time());
             pos.SetTimeInMsc(deal.TimeMsc());
             ENUM_POSITION_TYPE type=ENUM_POSITION_TYPE(deal.TypeDeal()==DEAL_TYPE_BUY ? POSITION_TYPE_BUY : deal.TypeDeal()==DEAL_TYPE_SELL ? POSITION_TYPE_SELL : WRONG_VALUE);
             pos.SetType(type);
             pos.SetPriceIn(deal.Price());
             pos.SetVolume(deal.Volume());
            }
          if(deal.Entry()==DEAL_ENTRY_OUT || deal.Entry()==DEAL_ENTRY_OUT_BY)
            {
             pos.SetDealOut(deal.Ticket());
             pos.SetTimeOut(deal.Time());
             pos.SetTimeOutMsc(deal.TimeMsc());
             pos.SetPriceOut(deal.Price());
            }
          if(deal.Entry()==DEAL_ENTRY_INOUT)
            {
             ENUM_POSITION_TYPE type=ENUM_POSITION_TYPE(deal.TypeDeal()==DEAL_TYPE_BUY ? POSITION_TYPE_BUY : deal.TypeDeal()==DEAL_TYPE_SELL ? POSITION_TYPE_SELL : WRONG_VALUE);
             pos.SetType(type);
             pos.SetVolume(deal.Volume()-pos.Volume());
            }
         }
    //--- Возвращаем результат создания и добавления позиции в список
       return res;
      }
    

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

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

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

    Метод GetPositionObjByIndex() возвращает указатель на объект исторической позиции из списка по индексу, переданному в метод.
    Возвращается указатель на объект, расположенный в списке по указанному индексу, либо NULL, если объект не найден.

    Метод GetPositionObjByID() возвращает указатель на объект исторической позиции из списка по идентификатору позиции, переданному в метод:

       CPosition        *GetPositionObjByID(const long id)
                           {
                            //--- Создаём временный объект позиции
                            CPosition *tmp=new CPosition(id);
                            //--- Устанавливаем списку позиций сортировку по идентификатору позиции
                            this.m_list_pos.Sort(POS_SORT_MODE_ID);
                            //--- Получаем индекс объекта в списке позиций с таким идентификатором
                            int index=this.m_list_pos.Search(tmp);
                            //--- Удаляем временный объект и устанавливаем списку флаг сортировки по времени в миллисекундах.
                            delete tmp;
                            this.m_list_pos.Sort(POS_SORT_MODE_TIME_IN_MSC);
                            //--- Возвращаем указатель на объект в списке по полученному индексу, либо NULL, если индекс не получен
                            return this.m_list_pos.At(index);
                           }
    

    В списке ищется объект, идентификатор позиции которого равен переданному в метод, и возвращается указатель на найденный объект, либо NULL, если объект не найден.

    Метод DealAdd() добавляет сделку в список сделок:

       bool              DealAdd(const long position_id,CDeal *deal)
                           {
                            CPosition *pos=this.GetPositionObjByID(position_id);
                            return(pos!=NULL ? pos.DealAdd(deal) : false);
                           }
    

    В метод передаётся идентификатор позиции, в которую нужно добавить сделку, указатель на которую тоже передаётся в метод. При успешном добавлении сделки в список сделок позиции метод возвращает true, при ошибке — false.

    Метод IsPresentInTime() возвращает флаг нахождения указанной позиции в указанном времени:

       bool              IsPresentInTime(CPosition *pos,const datetime time) const
                           {
                            return pos.IsPresentInTime(time);
                           }
    

    В метод передаётся указатель на позицию, которую необходимо проверить, и время, внутри которого нужно знать, была ли позиция или нет. Если позиция в указанное время существовала, метод возвращает true, иначе — false.

    Метод ProfitRelativeClosePrice() возвращает профит позиции относительно цены закрытия.

       double            ProfitRelativeClosePrice(CPosition *pos,const double price_close,const datetime time,const bool points) const
                           {
                            return pos.ProfitRelativeClosePrice(price_close,time,points);
                           }
    

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

    Метод PositionsTotal() возвращает количество исторических позиций в списке:

       int               PositionsTotal(void) const { return this.m_list_pos.Total();   }
    

    Метод Print() распечатывает в журнале свойства позиций и их сделок:

       void              Print(void)
                           {
                            //--- В цикле по списку исторических позиций
                            for(int i=0;i<this.m_list_pos.Total();i++)
                              {
                               //--- получаем указатель на объект позиции и распечатываем свойства позиции и её сделок
                               CPosition *pos=this.m_list_pos.At(i);
                               if(pos==NULL)
                                  continue;
                               pos.Print();
                              }
                           }
    

    В конструкторе класса списку исторических позиций устанавливается флаг сортировки по времени в миллисекундах:

    //--- Конструктор
                         CHistoryPosition(void)
                           {
                            this.m_list_pos.Sort(POS_SORT_MODE_TIME_IN_MSC);
                           }
    

    Все классы для реализации намеченного плана созданы.

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


    Индикатор диаграммы профита позиций

    Наиболее подходящий стиль рисования индикаторного буфера для рисования в виде диаграмм — цветная область между двух буферов индикатора, DRAW_FILLING.

    Стиль DRAW_FILLING рисует цветную область между значениями двух индикаторных буферов. Фактически этот стиль рисует две линии и закрашивает пространство между ними одним из двух заданных цветов. Предназначен для создания индикаторов, рисующих каналы. Ни один из буферов не может содержать только пустые значения, так как в этом случае отрисовка не происходит.

    Можно задавать два цвета заливки:

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

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

    Индикатор рассчитывается для всех баров, для которых значения обоих индикаторных буферов не равны 0 и не равны пустому значению. Чтобы указать, какое значение следует считать "пустым", установите это значение в свойстве PLOT_EMPTY_VALUE:

       #define INDICATOR_EMPTY_VALUE -1.0
       ...
    //--- значение INDICATOR_EMPTY_VALUE (пустое значение) не будет участвовать в расчете
       PlotIndexSetDouble(индекс_построения_DRAW_FILLING,PLOT_EMPTY_VALUE,INDICATOR_EMPTY_VALUE);

    Отрисовка на барах, которые не участвуют в расчете индикатора, будет зависеть от значений в индикаторных буферах:

    • Бары, для которых значения обоих индикаторных буферов равны 0, не участвуют в отрисовке индикатора. То есть область с нулевыми значениями не будет закрашиваться.


    • Бары, для которых значения индикаторных буферов равны "пустому значению", участвуют в отрисовке индикатора. Область с пустыми значениями будет закрашиваться таким образом, чтобы соединять области со значащими значениями.


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

    Количество требуемых буферов для построения DRAW_FILLING — 2.

    Добавим один входной параметр для выбора в каких единицах возвращать профит позиции на баре, объявим указатель на экземпляр класса списка исторических позиций и допишем немного обработчик OnInit():

    //+------------------------------------------------------------------+
    //| Индикатор                                                        |
    //+------------------------------------------------------------------+
    //--- input parameters
    input bool     InpProfitPoints   =  true;    // Profit in Points
    //--- indicator buffers
    double         BufferFilling1[];
    double         BufferFilling2[];
    //--- global variables
    CHistoryPosition *history=NULL;
    //+------------------------------------------------------------------+
    //| Custom indicator initialization function                         |
    //+------------------------------------------------------------------+
    int OnInit()
      {
    //--- indicator buffers mapping
       SetIndexBuffer(0,BufferFilling1,INDICATOR_DATA);
       SetIndexBuffer(1,BufferFilling2,INDICATOR_DATA);
    //--- Устанавливаем индексацию массивов буферов как в таймсерии
       ArraySetAsSeries(BufferFilling1,true);
       ArraySetAsSeries(BufferFilling2,true);
    //--- Устанавливаем Digits данных индикатора, равный 2 и один уровень, равный 0
       IndicatorSetInteger(INDICATOR_DIGITS,2);
       IndicatorSetInteger(INDICATOR_LEVELS,1);
       IndicatorSetDouble(INDICATOR_LEVELVALUE,0);
    //--- Создаём новый объект исторических данных
       history=new CHistoryPosition();
    //--- Возвращаем результат создания объекта исторических данных
       return(history!=NULL ? INIT_SUCCEEDED : INIT_FAILED);
      }
    

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

    //+------------------------------------------------------------------+
    //| Custom indicator deinitialization function                       |
    //+------------------------------------------------------------------+
    void OnDeinit(const int reason)
      {
    //--- Удаляем объект исторических позиций и комментарии на графике
       if(history!=NULL)
          delete history;
       Comment("");
      }
    

    В обработчике OnCalculate() на первом тике создадим список всех исторических позиций и далее в основном цикле будем обращаться к созданному списку посредством функции расчёта прибыли на текущем баре цикла индикатора:

    //+------------------------------------------------------------------+
    //| 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[])
      {
    //--- Устанавливаем индексацию массивов close и time как в таймсерии
       ArraySetAsSeries(close,true);
       ArraySetAsSeries(time,true);
    //--- Флаг успешного создания списка позиций
       static bool done=false;
    //--- Если объект данных позиций создан
       if(history!=NULL)
         {
          //--- Если ещё не создавался список позиций
          if(!done)
            {
             //--- Если список позиций по текущему инструменту успешно создан,
             if(history.CreatePositionList(Symbol()))
               {
                //--- распечатываем в журнале позиции и устанавливаем флаг успешного создания списка позиций
                history.Print();
                done=true;
               }
            }
         }
    //--- Количество баров, необходимое для расчёта индикатора
       int limit=rates_total-prev_calculated;
    //--- Если limit больше 1 - значит это первый запуск или изменение в исторических данных
       if(limit>1)
         {
          //--- Устанавливаем количество баров для расчёта, равное всей доступной истории и инициализируем буферы "пустыми" значениями
          limit=rates_total-1;
          ArrayInitialize(BufferFilling1,EMPTY_VALUE);
          ArrayInitialize(BufferFilling2,EMPTY_VALUE);
         }
    //--- В цикле по барам истории символа
       for(int i=limit;i>=0;i--)
         {
          //--- получаем профит позиций, присутствующих на баре с индексом цикла i и записываем в первый буфер полученное значение
          double profit=Profit(close[i],time[i]);
          BufferFilling1[i]=profit;
          //--- Во второй буфер всегда записываем ноль. В зависимости от того, больше или меньше нуля значение в первом буфере,
          //--- будет меняться цвет рисуемой заливки между массивами 1 и 2 буфера индикатора
          BufferFilling2[i]=0;
         }
    //--- return value of prev_calculated for next call
       return(rates_total);
      }
    

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

    //+------------------------------------------------------------------+
    //| Возвращает профит всех позиций из списка на указанном времени    |
    //+------------------------------------------------------------------+
    double Profit(const double price,const datetime time)
      {
    //--- Переменная для записи и возврата результата подсчёта прибыли на баре
       double res=0;
    //--- В цикле по списку исторических позиций
       for(int i=0;i<history.PositionsTotal();i++)
         {
          //--- получаем указатель на очередную позицию
          CPosition *pos=history.GetPositionObjByIndex(i);
          if(pos==NULL)
             continue;
          //--- добавляем к результату значение расчёта прибыли текущей позиции относительно цены price на баре со временем time
          res+=pos.ProfitRelativeClosePrice(price,time,InpProfitPoints);
         }
    //--- Возвращаем рассчитанную сумму прибыли всех позиций относительно цены price на баре со временем time
       return res;
      }
    

    В функцию передаётся цена (Close бара), относительно которой нужно получать количество пунктов прибыли позиции, и время, на котором проверяется существование позиции (Time open бара). Далее профит всех позиций, полученный от каждого объекта исторических позиций, суммируется и возвращается.

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



    Заключение

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


    Прикрепленные файлы |
    Последние комментарии | Перейти к обсуждению на форуме трейдеров (10)
    Roman Kutemov
    Roman Kutemov | 3 янв. 2024 в 14:57

    Здравствуйте,

    как можно сделать чтобы индикатор показывал не каждый профит/убыток, а накопительным итогом ?

    и если можно - линией ?!!

    Artyom Trishkin
    Artyom Trishkin | 3 янв. 2024 в 15:30
    Roman Kutemov #:

    Здравствуйте,

    как можно сделать чтобы индикатор показывал не каждый профит/убыток, а накопительным итогом ?

    и если можно - линией ?!!

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

    Как-то так:

    //+------------------------------------------------------------------+
    //| 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[])
      {
    //--- Устанавливаем индексацию массивов close и time как в таймсерии
       ArraySetAsSeries(close,true);
       ArraySetAsSeries(time,true);
    //--- Флаг успешного создания списка позиций
       static bool done=false;
    //--- Если объект данных позиций создан
       if(history!=NULL)
         {
          //--- Если ещё не создавался список позиций
          if(!done)
            {
             //--- Если список позиций по текущему инструменту успешно создан,
             if(history.CreatePositionList(Symbol()))
               {
                //--- распечатываем в журнале позиции и устанавливаем флаг успешного создания списка позиций
                history.Print();
                done=true;
               }
            }
         }
    //--- Количество баров, необходимое для расчёта индикатора
       int limit=rates_total-prev_calculated;
    //--- Если limit больше 1 - значит это первый запуск или изменение в исторических данных
       if(limit>1)
         {
          //--- Устанавливаем количество баров для расчёта, равное всей доступной истории и инициализируем буферы "пустыми" значениями
          limit=rates_total-1;
          ArrayInitialize(BufferFilling1,EMPTY_VALUE);
          ArrayInitialize(BufferFilling2,EMPTY_VALUE);
         }
    //--- В цикле по барам истории символа
       static double profit=0;
       for(int i=limit;i>=0;i--)
         {
          //--- получаем профит позиций, присутствующих на баре с индексом цикла i и записываем в первый буфер полученное значение
          profit+=Profit(close[i],time[i]);
          BufferFilling1[i]=profit;
          //--- Во второй буфер всегда записываем ноль. В зависимости от того, больше или меньше нуля значение в первом буфере,
          //--- будет меняться цвет рисуемой заливки между массивами 1 и 2 буфера индикатора
          BufferFilling2[i]=0;
         }
    //--- return value of prev_calculated for next call
       return(rates_total);
      }
    

    Ну и с буфером как линией - это сами. Там убрать один лишний буфер нужно, так как заливка всегда использует два буфера. А для линии нужен один.

    Roman Kutemov
    Roman Kutemov | 3 янв. 2024 в 17:13

    Вроде получилось.

    Внёс ваши изменения, и просто указал тип - графика линия.

    Буферы не убирал.

    https://www.mql5.com/ru/charts/18738352/nzdcad-d1-roboforex-ltd

    Roman Kutemov
    Roman Kutemov | 4 янв. 2024 в 05:53
    На  валютах хорошо вроде всё, а на серебре почему то постоянно перерисовывается.
    Artyom Trishkin
    Artyom Trishkin | 4 янв. 2024 в 06:29
    Roman Kutemov #:
    На  валютах хорошо вроде всё, а на серебре почему то постоянно перерисовывается.

    Нужно смотреть какие данные получает индикатор и почему не просчитывается. Перерисовку может вызывать значение limit, большее 1. А это значение рассчитывается как разница между rates_total и prev_calculated. Вот эти значения нужно глядеть что в них содержится на каждом тике

    Популяционные алгоритмы оптимизации: Алгоритмы эволюционных стратегий (Evolution Strategies, (μ,λ)-ES и (μ+λ)-ES) Популяционные алгоритмы оптимизации: Алгоритмы эволюционных стратегий (Evolution Strategies, (μ,λ)-ES и (μ+λ)-ES)
    В этой статье будет рассмотрена группа алгоритмов оптимизации, известных как "Эволюционные стратегии" (Evolution Strategies или ES). Они являются одними из самых первых популяционных алгоритмов, использующих принципы эволюции для поиска оптимальных решений. Будут представлены изменения, внесенные в классические варианты ES, а также пересмотрена тестовая функция и методика стенда для алгоритмов.
    Тесты на перестановку Монте-Карло в MetaTrader 5 Тесты на перестановку Монте-Карло в MetaTrader 5
    В статье рассматриваются тесты на перестановку на основе перетасованных тиковых данных на любом советнике исключительно силами MetaTrader 5.
    Теория категорий в MQL5 (Часть 18): Квадрат естественности Теория категорий в MQL5 (Часть 18): Квадрат естественности
    Статья продолжает серию о теории категорий, представляя естественные преобразования, которые являются ключевым элементом теории. Мы рассмотрим сложное на первый взгляд определение, затем углубимся в примеры и способы применения преобразований в прогнозировании волатильности.
    Теория категорий в MQL5 (Часть 17): Функторы и моноиды Теория категорий в MQL5 (Часть 17): Функторы и моноиды
    Это последняя статья серии, посвященная функторам. В ней мы вновь рассматриваем моноиды как категорию. Моноиды, которые мы уже представили в этой серии, используются здесь для помощи в определении размера позиции вместе с многослойными перцептронами.