Русский Deutsch 日本語
preview
Indicator of historical positions on the chart as their profit/loss diagram

Indicator of historical positions on the chart as their profit/loss diagram

MetaTrader 5Examples | 22 April 2024, 13:30
1 143 10
Artyom Trishkin
Artyom Trishkin

Contents


Introduction

Position... A position is a consequence of executing a trading order sent to the server: Order --> Deal --> Position.

We can always get a list of open positions in our program using the PositionSelect() functions in case of netting position accounting, while indicating the name of a symbol the position is open on:

   if(PositionSelect(symbol_name))
     {
      /*
      Work with selected position data
      PositionGetDouble();
      PositionGetInteger();
      PositionGetString();
      */
     }

For accounts with independent position presentation (hedge), we first need to obtain the number of positions using PositionsTotal(). Then in the loop by the number of positions, we need to get the position ticket by its index in the list of open positions using PositionGetTicket(). After that, select a position by the received ticket using PositionSelectByTicket():

   int total=PositionsTotal();
   for(int i=total-1;i>=0;i--)
     {
      ulong ticket=PositionGetTicket(i);
      if(ticket==0 || !PositionSelectByTicket(ticket))
         continue;
      /*
      Work with selected position data
      PositionGetDouble();
      PositionGetInteger();
      PositionGetString();
      */
     }

Everything here is simple and clear. But it is a completely different matter when we need to find out something about an already closed position - there are no functions for working with historical positions...

In this case, we need to remember and know that each position has its own unique ID. This ID is registered in deals that influenced the position - led to its opening, modification or closure. We can get the list of deals (including the deal and the ID of the position the deal participated in) using the functions HistorySelect(), HistoryDealsTotal() and HistoryDealGetTicket() (HistoryDealSelect()).

In general, the ID of the position the deal was involved in can be obtained as follows:

   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); // ID of a position a deal participated in
         /*
         Work with selected deal data
         HistoryDealGetDouble();
         HistoryDealGetInteger();
         HistoryDealGetString();
         */
        }
     }

In other words, having a list of deals, we can always find out which position belonged to a certain deal. For example, we can get a position ID and look at the deals with the same ID to find market entry and exit deals. Using the properties of these deals, we can find out the time, the lot and other necessary properties of the desired historical position.

To search for closed historical positions, we will get a list of historical deals. In the loop by this list, we will get each next deal and check the position ID the deal participated in. If such a position is not yet in the list, then we create a position object, and if it already exists, then we use an existing position with the same ID. Add the current deal (if it is not already in the list) to the list of deals of the found position. Thus, having gone through the entire list of historical deals, we will find all the positions deals participated in, create a list of all historical positions and add all deals that participated in this position to each object of the historical position (namely to its list of deals).

We need three classes:

  1. Deal class. Contains deal properties necessary to identify a position and its properties.
  2. Position class. Contains a list of deals that participated in a position and the properties inherent to positions.
  3. Historical positions list class. The list of found historical positions with the ability to select a position based on specified properties.

These three small classes will make it possible to easily find all historical deals, save them in a list, and then use the data of these positions in the indicator to draw a diagram of the profit/loss of positions for the selected symbol on the account.

In the Indicators folder, create a new indicator file called PositionInfoIndicator. Specify drawing an indicator with one drawable buffer with the Filling style in a separate chart window with Green and Red fill colors.

The indicator template with the following header will be created:

//+------------------------------------------------------------------+
//|                                        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

Enter the classes being created below.

To create lists of deals and positions, we will use the class of dynamic array of pointers to instances of the CObject class and its descendants of the Standard Library.

Include the file of the class to the created file:

//+------------------------------------------------------------------+
//|                                        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>

The class contains the Search() method to search for the elements equal to the example in a sorted array:

//+------------------------------------------------------------------+
//| 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);
  }

The array should be sorted by some property of the objects, to which it contains pointers. The sorting mode is set by passing an integer to the m_sort_mode variable. Value 0 is used by default. The value of -1 indicates that the array is not sorted. To sort by different properties, we need to set different sorting modes by setting the m_sort_mode variable to values from zero and higher. To do this, it is convenient to use enumerations that define different sorting modes for lists. These modes are used in the virtual method of comparing two Compare() objects. It is defined in the CObject class and return the value of 0, which means the objects being compared are identical:

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

This method should be overridden in custom classes, where you can arrange a comparison of two objects of the same type according to their various properties (if the value of m_sort_mode is equal to 0, the objects are compared by one property, if it is equal to 1 - by another property, equal to 2 - by a third, etc.).

Each class object created here will have its own set of properties. Objects should be sorted by these properties to perform a search. Therefore, we need to first create enumerations of object properties:

//+------------------------------------------------------------------+
//|                                        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
//--- Deal object sorting modes
enum ENUM_DEAL_SORT_MODE
  {
   DEAL_SORT_MODE_TIME_MSC,   // Deal execution time in milliseconds
   DEAL_SORT_MODE_TIME,       // Deal execution time
   DEAL_SORT_MODE_TIKET,      // Deal ticket
   DEAL_SORT_MODE_POS_ID,     // Position ID
   DEAL_SORT_MODE_MAGIC,      // Magic number for a deal
   DEAL_SORT_MODE_TYPE,       // Deal type
   DEAL_SORT_MODE_ENTRY,      // Deal entry - entry in, entry out, reverse
   DEAL_SORT_MODE_VOLUME,     // Deal volume
   DEAL_SORT_MODE_PRICE,      // Deal price
   DEAL_SORT_MODE_COMISSION,  // Deal commission
   DEAL_SORT_MODE_SWAP,       // Accumulated swap when closing
   DEAL_SORT_MODE_PROFIT,     // Deal financial result
   DEAL_SORT_MODE_FEE,        // Deal fee
   DEAL_SORT_MODE_SYMBOL,     // Name of the symbol for which the deal is executed
  };
//--- Position object sorting modes
enum ENUM_POS_SORT_MODE
  {
   POS_SORT_MODE_TIME_IN_MSC, // Open time in milliseconds
   POS_SORT_MODE_TIME_OUT_MSC,// Close time in milliseconds
   POS_SORT_MODE_TIME_IN,     // Open time
   POS_SORT_MODE_TIME_OUT,    // Close time
   POS_SORT_MODE_DEAL_IN,     // Open deal ticket
   POS_SORT_MODE_DEAL_OUT,    // Close deal ticket
   POS_SORT_MODE_ID,          // Position ID
   POS_SORT_MODE_MAGIC,       // Position magic
   POS_SORT_MODE_PRICE_IN,    // Open price
   POS_SORT_MODE_PRICE_OUT,   // Close price
   POS_SORT_MODE_VOLUME,      // Position volume
   POS_SORT_MODE_SYMBOL,      // Position symbol
  };  
//--- classes

The properties corresponding to the constants of these two enumerations will be contained in the deal and position object classes. It will be possible to sort lists by these properties to find equality of objects.

Below we will enter the codes of the created classes.


    Deal class

    Let's look at the deal class as a whole:
    //+------------------------------------------------------------------+
    //| Deal class                                                       |
    //+------------------------------------------------------------------+
    class CDeal : public CObject
      {
    private:
       long              m_ticket;                        // Deal ticket
       long              m_magic;                         // Magic number for a deal
       long              m_position_id;                   // Position ID
       long              m_time_msc;                      // Deal execution time in milliseconds
       datetime          m_time;                          // Deal execution time
       ENUM_DEAL_TYPE    m_type;                          // Deal type
       ENUM_DEAL_ENTRY   m_entry;                         // Deal entry - entry in, entry out, reverse
       double            m_volume;                        // Deal volume
       double            m_price;                         // Deal price
       double            m_comission;                     // Deal commission
       double            m_swap;                          // Accumulated swap when closing
       double            m_profit;                        // Deal financial result
       double            m_fee;                           // Deal fee
       string            m_symbol;                        // Name of the symbol for which the deal is executed
    //--- Return the deal direction description
       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");
                           }
    //--- Return the deal type description
       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;
                              }
                           }
    //--- Return time with milliseconds
       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:
    //--- Methods for returning deal properties
       long              Ticket(void)                     const { return this.m_ticket;       }  // Deal ticket
       long              Magic(void)                      const { return this.m_magic;        }  // Magic number for a deal
       long              PositionID(void)                 const { return this.m_position_id;  }  // Position ID
       long              TimeMsc(void)                    const { return this.m_time_msc;     }  // Deal execution time in milliseconds
       datetime          Time(void)                       const { return this.m_time;         }  // Deal execution time
       ENUM_DEAL_TYPE    TypeDeal(void)                   const { return this.m_type;         }  // Deal type
       ENUM_DEAL_ENTRY   Entry(void)                      const { return this.m_entry;        }  // Deal entry - entry in, entry out, reverse
       double            Volume(void)                     const { return this.m_volume;       }  // Deal volume
       double            Price(void)                      const { return this.m_price;        }  // Deal price
       double            Comission(void)                  const { return this.m_comission;    }  // Deal commission
       double            Swap(void)                       const { return this.m_swap;         }  // Accumulated swap when closing
       double            Profit(void)                     const { return this.m_profit;       }  // Deal financial result
       double            Fee(void)                        const { return this.m_fee;          }  // Deal fee
       string            Symbol(void)                     const { return this.m_symbol;       }  // Name of the symbol, for which the deal is executed
    //--- Methods for setting deal properties
       void              SetTicket(const long ticket)           { this.m_ticket=ticket;       }  // Deal ticket
       void              SetMagic(const long magic)             { this.m_magic=magic;         }  // Magic number for a deal
       void              SetPositionID(const long id)           { this.m_position_id=id;      }  // Position ID
       void              SetTimeMsc(const long time_msc)        { this.m_time_msc=time_msc;   }  // Deal execution time in milliseconds
       void              SetTime(const datetime time)           { this.m_time=time;           }  // Deal execution time
       void              SetType(const ENUM_DEAL_TYPE type)     { this.m_type=type;           }  // Deal type
       void              SetEntry(const ENUM_DEAL_ENTRY entry)  { this.m_entry=entry;         }  // Deal entry - entry in, entry out, reverse
       void              SetVolume(const double volume)         { this.m_volume=volume;       }  // Deal volume
       void              SetPrice(const double price)           { this.m_price=price;         }  // Deal price
       void              SetComission(const double comission)   { this.m_comission=comission; }  // Deal commission
       void              SetSwap(const double swap)             { this.m_swap=swap;           }  // Accumulated swap when closing
       void              SetProfit(const double profit)         { this.m_profit=profit;       }  // Deal financial result
       void              SetFee(const double fee)               { this.m_fee=fee;             }  // Deal fee
       void              SetSymbol(const string symbol)         { this.m_symbol=symbol;       }  // Name of the symbol, for which the deal is executed
    //--- Method for comparing two objects
       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);
                              }
                           }
                           
    //--- Print deal properties in the journal
       void              Print(void)
                           {
                            ::PrintFormat("  Deal: %s type %s #%lld at %s",this.EntryDescription(),this.TypeDescription(),this.Ticket(),this.TimeMSCtoString(this.TimeMsc()));
                           }
    //--- Constructor
                         CDeal(const long deal_ticket)
                           {
                            this.m_ticket=deal_ticket;
                            this.m_magic=::HistoryDealGetInteger(deal_ticket,DEAL_MAGIC);                    // Magic number for a deal
                            this.m_position_id=::HistoryDealGetInteger(deal_ticket,DEAL_POSITION_ID);        // Position ID
                            this.m_time_msc=::HistoryDealGetInteger(deal_ticket,DEAL_TIME_MSC);              // Deal execution time in milliseconds
                            this.m_time=(datetime)::HistoryDealGetInteger(deal_ticket,DEAL_TIME);            // Deal execution time
                            this.m_type=(ENUM_DEAL_TYPE)::HistoryDealGetInteger(deal_ticket,DEAL_TYPE);      // Deal type
                            this.m_entry=(ENUM_DEAL_ENTRY)::HistoryDealGetInteger(deal_ticket,DEAL_ENTRY);   // Deal entry - entry in, entry out, reverse
                            this.m_volume=::HistoryDealGetDouble(deal_ticket,DEAL_VOLUME);                   // Deal volume
                            this.m_price=::HistoryDealGetDouble(deal_ticket,DEAL_PRICE);                     // Deal volume
                            this.m_comission=::HistoryDealGetDouble(deal_ticket,DEAL_COMMISSION);            // Deal commission
                            this.m_swap=::HistoryDealGetDouble(deal_ticket,DEAL_SWAP);                       // Accumulated swap when closing
                            this.m_profit=::HistoryDealGetDouble(deal_ticket,DEAL_PROFIT);                   // Deal financial result
                            this.m_fee=::HistoryDealGetDouble(deal_ticket,DEAL_FEE);                         // Deal fee
                            this.m_symbol=::HistoryDealGetString(deal_ticket,DEAL_SYMBOL);                   // Name of the symbol, for which the deal is executed
                           }
                        ~CDeal(void){}
      };
    
    

    Everything is pretty simple here: class member variables to store deal properties are declared in the private section. The public section implements methods for setting and returning deal properties. The Compare() virtual method implements comparison for each deal property, depending on which property is passed to the method as the comparison mode. If the value of the property being checked for the current object is greater than the value of the same property for the one being compared, then 1 is returned, if less, -1. If the values are equal, we have 0. In the class constructor, it is considered that the deal has already been selected, and its properties are written to the corresponding private class variables. This is already enough to create a deal object that stores all the necessary properties of the selected deal from the list of historical deals of the terminal. The class object will be created for each deal. The position ID will be extracted from the object properties and an object of the historical position class will be created. All deals belonging to this position will be listed in the list of deals of the position class. Thus, the historical position object will contain a list of all its deals. It will be possible to extract the position history from them.

    Let's consider the historical position class.

    Position class

    The position class will contain a list of deals for this position and auxiliary methods for calculating values obtaining and returning information about the position lifetime:

    //+------------------------------------------------------------------+
    //| Position class                                                   |
    //+------------------------------------------------------------------+
    class CPosition : public CObject
      {
    private:
       CArrayObj         m_list_deals;                    // List of position deals
       long              m_position_id;                   // Position ID
       long              m_time_in_msc;                   // Open time in milliseconds
       long              m_time_out_msc;                  // Close time in milliseconds
       long              m_magic;                         // Position magic
       datetime          m_time_in;                       // Open time
       datetime          m_time_out;                      // Close time
       ulong             m_deal_in_ticket;                // Open deal ticket
       ulong             m_deal_out_ticket;               // Close deal ticket
       double            m_price_in;                      // Open price
       double            m_price_out;                     // Close price
       double            m_volume;                        // Position volume
       ENUM_POSITION_TYPE m_type;                         // Position type
       string            m_symbol;                        // Position symbol
       int               m_digits;                        // Symbol digits
       double            m_point;                         // One symbol point value
       double            m_contract_size;                 // Symbol trade contract size
       string            m_currency_profit;               // Symbol profit currency
       string            m_account_currency;              // Deposit currency
    //--- Return time with milliseconds
       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');
                           }
    //--- Calculate and return the open time of a known bar on a specified chart period by a specified time
    //--- (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);
                            //--- Calculate the bar open time on periods less than W1
                            if(period<PERIOD_W1)
                               return time-time%::PeriodSeconds(period);
                            //--- Calculate the bar open time on W1
                            if(period==PERIOD_W1)
                               return time-(time+4*24*60*60)%::PeriodSeconds(period);
                            //--- Calculate the bar open time on 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);
                              }
                           }
    //--- Return the symbol availability flag on the server. Add a symbol to MarketWatch window
       bool              SymbolIsExist(const string symbol) const
                           {
                            bool custom=false;
                            if(!::SymbolExist(symbol,custom))
                               return false;
                            return ::SymbolSelect(symbol,true);
                           }
    //--- Return the price of one point
       double            GetOnePointPrice(const datetime time) const
                           {
                            if(time==0)
                               return 0;
                            //--- If the symbol's profit currency matches the account currency, return the contract size * Point of the symbol
                            if(this.m_currency_profit==this.m_account_currency)
                               return this.m_point*this.m_contract_size;
                            //--- Otherwise, check for the presence of a symbol with the name "Account currency" + "Symbol profit currency" 
                            double array[1];
                            string reverse=this.m_account_currency+this.m_currency_profit;
                            //--- If such a symbol exists and is added to the Market Watch
                            if(this.SymbolIsExist(reverse))
                              {
                               //--- If the closing price of the bar by 'time' is received, return the contract size * Point of the symbol divided by the Close price of the bar 
                               if(::CopyClose(reverse,PERIOD_CURRENT,time,1,array)==1 && array[0]>0)
                                  return this.m_point*this.m_contract_size/array[0];
                               //--- If failed to get the closing price of the bar by 'time', return zero
                               else
                                  return 0;
                              }
                            //--- Check for the presence of a symbol with the name "Symbol profit currency" + "Account currency"
                            string direct=this.m_currency_profit+this.m_account_currency;
                            //--- If such a symbol exists and is added to the Market Watch
                            if(this.SymbolIsExist(direct))
                              {
                               //--- If the closing price of the bar by 'time' is received, return the contract size * Point of the symbol multiplied by the Close price of the bar
                               if(::CopyClose(direct,PERIOD_CURRENT,time,1,array)==1)
                                  return this.m_point*this.m_contract_size*array[0];
                              }
                            //--- Failed to get symbols whose profit currency does not match the account currency, neither reverse nor direct - return zero
                            return 0;
                           }
    public:
    //--- Methods for returning position properties
       long              ID(void)                         const { return this.m_position_id;              }  // Position ID 
       long              Magic(void)                      const { return this.m_magic;                    }  // Position magic 
       long              TimeInMsc(void)                  const { return this.m_time_in_msc;              }  // Open time
       long              TimeOutMsc(void)                 const { return this.m_time_out_msc;             }  // Close time
       datetime          TimeIn(void)                     const { return this.m_time_in;                  }  // Open time
       datetime          TimeOut(void)                    const { return this.m_time_out;                 }  // Close time
       ulong             DealIn(void)                     const { return this.m_deal_in_ticket;           }  // Open deal ticket
       ulong             DealOut(void)                    const { return this.m_deal_out_ticket;          }  // Close deal ticket
       ENUM_POSITION_TYPE TypePosition(void)              const { return this.m_type;                     }  // Position type
       double            PriceIn(void)                    const { return this.m_price_in;                 }  // Open price
       double            PriceOut(void)                   const { return this.m_price_out;                }  // Close price
       double            Volume(void)                     const { return this.m_volume;                   }  // Position volume
       string            Symbol(void)                     const { return this.m_symbol;                   }  // Position symbol
    //--- Methods for setting position properties
       void              SetID(long id)                         { this.m_position_id=id;                  }  // Position ID
       void              SetMagic(long magic)                   { this.m_magic=magic;                     }  // Position magic
       void              SetTimeInMsc(long time_in_msc)         { this.m_time_in_msc=time_in_msc;         }  // Open time
       void              SetTimeOutMsc(long time_out_msc)       { this.m_time_out_msc=time_out_msc;       }  // Close time
       void              SetTimeIn(datetime time_in)            { this.m_time_in=time_in;                 }  // Open time
       void              SetTimeOut(datetime time_out)          { this.m_time_out=time_out;               }  // Close time
       void              SetDealIn(ulong ticket_deal_in)        { this.m_deal_in_ticket=ticket_deal_in;   }  // Open deal ticket
       void              SetDealOut(ulong ticket_deal_out)      { this.m_deal_out_ticket=ticket_deal_out; }  // Close deal ticket
       void              SetType(ENUM_POSITION_TYPE type)       { this.m_type=type;                       }  // Position type
       void              SetPriceIn(double price_in)            { this.m_price_in=price_in;               }  // Open price
       void              SetPriceOut(double price_out)          { this.m_price_out=price_out;             }  // Close price
       void              SetVolume(double new_volume)           { this.m_volume=new_volume;               }  // Position volume
       void              SetSymbol(string symbol)                                                            // Position 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);
                           }
    //--- Add a deal to the list of deals
       bool              DealAdd(CDeal *deal)
                           {
                            //--- Declare a variable of the result of adding a deal to the list
                            bool res=false;
                            //--- Set the flag of sorting by deal ticket for the list
                            this.m_list_deals.Sort(DEAL_SORT_MODE_TIKET);
                            //--- If a deal with such a ticket is not in the list -
                            if(this.m_list_deals.Search(deal)==WRONG_VALUE)
                              {
                               //--- Set the flag of sorting by time in milliseconds for the list and
                               //--- return the result of adding a deal to the list in order of sorting by time
                               this.m_list_deals.Sort(DEAL_SORT_MODE_TIME_MSC);
                               res=this.m_list_deals.InsertSort(deal);
                              }
                            //--- If the deal is already in the list, return 'false'
                            else
                               this.m_list_deals.Sort(DEAL_SORT_MODE_TIME_MSC);
                            return res;
                           }
    //--- Returns the start time of the bar (1) opening, (2) closing a position on the current chart period
       datetime          BarTimeOpenPosition(void) const
                           {
                            return this.BarOpenTime(PERIOD_CURRENT,this.TimeIn());
                           }
       datetime          BarTimeClosePosition(void) const
                           {
                            return this.BarOpenTime(PERIOD_CURRENT,this.TimeOut());
                           }
    //--- Return the flag of the existence of a position at the specified time
       bool              IsPresentInTime(const datetime time) const
                           {
                            return(time>=this.BarTimeOpenPosition() && time<=this.BarTimeClosePosition());
                           }
    //--- Return the profit of the position in the number of points or in the value of the number of points relative to the close price
       double            ProfitRelativeClosePrice(const double price_close,const datetime time,const bool points) const
                           {
                            //--- If there was no position at the specified time, return 0
                            if(!this.IsPresentInTime(time))
                               return 0;
                            //--- Calculate the number of profit points depending on the position direction
                            int pp=int((this.TypePosition()==POSITION_TYPE_BUY ? price_close-this.PriceIn() : this.PriceIn()-price_close)/this.m_point);
                            //--- If the profit is in points, return the calculated number of points
                            if(points)
                               return pp;
                            //--- Otherwise, return the calculated number of points multiplied by (cost of one point * position volume)
                            return pp*this.GetOnePointPrice(time)*this.Volume();
                           }
    //--- Method for comparing two objects
       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);
                              }
                           }
    //--- Return a position type description
       string            TypeDescription(void) const
                           {
                            return(this.m_type==POSITION_TYPE_BUY ? "Buy" : this.m_type==POSITION_TYPE_SELL ? "Sell" : "Unknown");
                           }
    //--- Print the properties of the position and its deals in the journal
       void              Print(void)
                           {
                            //--- Display a header with a position description
                            ::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()
                              );
                            //--- Display deal descriptions in a loop by all position deals
                            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();
                              }
                           }
    //--- Constructor
                         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);
                           }
      };
    
    

    In the private section, class member variables to store position properties are declared and auxiliary methods are also written.

    The BarOpenTime() method calculates and returns the opening time of a bar on a given timeframe based on a certain time passed to the method:

       datetime          BarOpenTime(const ENUM_TIMEFRAMES timeframe,const datetime time) const
                           {
                            ENUM_TIMEFRAMES period=(timeframe==PERIOD_CURRENT ? ::Period() : timeframe);
                            //--- Calculate the bar open time on periods less than W1
                            if(period<PERIOD_W1)
                               return time-time%::PeriodSeconds(period);
                            //--- Calculate the bar open time on W1
                            if(period==PERIOD_W1)
                               return time-(time+4*24*60*60)%::PeriodSeconds(period);
                            //--- Calculate the bar open time on 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);
                              }
                           }
    
    

    For example, we know some time and now want to find out the open time of the bar on a certain chart period this time is located within. The method will return the open time of the calculated bar. This way we can always find out whether a position existed at a certain time on a certain chart bar:

    //--- Return the flag of the existence of a position at the specified time
       bool              IsPresentInTime(const datetime time) const
                           {
                            return(time>=this.BarTimeOpenPosition() && time<=this.BarTimeClosePosition());
                           }
    
    

    or we can simply get the position open or close bar:

    //--- Returns the start time of the bar (1) opening, (2) closing a position on the current chart period
       datetime          BarTimeOpenPosition(void) const
                           {
                            return this.BarOpenTime(PERIOD_CURRENT,this.TimeIn());
                           }
       datetime          BarTimeClosePosition(void) const
                           {
                            return this.BarOpenTime(PERIOD_CURRENT,this.TimeOut());
                           }
    
    

    To calculate the profit/loss of a position on the current chart bar, we will use a simplified approximate calculation principle - in one case, we will consider the number of profit points relative to the opening price of the position and the closing price of the current bar. In the second case, we will calculate the cost of these profit/loss points:

       double            ProfitRelativeClosePrice(const double price_close,const datetime time,const bool points) const
                           {
                            //--- If there was no position at the specified time, return 0
                            if(!this.IsPresentInTime(time))
                               return 0;
                            //--- Calculate the number of profit points depending on the position direction
                            int pp=int((this.TypePosition()==POSITION_TYPE_BUY ? price_close-this.PriceIn() : this.PriceIn()-price_close)/this.m_point);
                            //--- If the profit is in points, return the calculated number of points
                            if(points)
                               return pp;
                            //--- Otherwise, return the calculated number of points multiplied by (cost of one point * position volume)
                            return pp*this.GetOnePointPrice(time)*this.Volume();
                           }
    
    

    Both methods will not give a complete picture of the floating profit during the position lifetime, but this is not necessary here. Here it is more important for us to show how to obtain data about a closed position. For an accurate calculation, we will need much more different data on the state of the market on each bar in the life of the position - we need to get ticks from the open time to the close time of the bar and use them to model the state of the position in this period of time. Then we should do the same for each bar. At the moment there is no such task.

    The ID of the position, whose object will be created, is passed to the class constructor:

                         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);
                           }
    
    

    The ID is already known at the time the class object is created. We get it from the transaction properties. After creating the position class object, we add the selected deal the position ID was obtained from to its list of deals. All this is done in the historical position list class, which we will consider further.


    Historical position list class

    //+------------------------------------------------------------------+
    //| Historical position list class                                   |
    //+------------------------------------------------------------------+
    class CHistoryPosition
      {
    private:
      CArrayObj          m_list_pos;          // Historical position list
    public:
    //--- Create historical position list
       bool              CreatePositionList(const string symbol=NULL);
    //--- Return a position object from the list by (1) index and (2) ID
       CPosition        *GetPositionObjByIndex(const int index)
                           {
                            return this.m_list_pos.At(index);
                           }
       CPosition        *GetPositionObjByID(const long id)
                           {
                            //--- Create a temporary position object
                            CPosition *tmp=new CPosition(id);
                            //--- Set the list of positions to be sorted by position ID
                            this.m_list_pos.Sort(POS_SORT_MODE_ID);
                            //--- Get the index of the object in the list of positions with this 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);
                           }
    //--- Add a deal to the list of deals
       bool              DealAdd(const long position_id,CDeal *deal)
                           {
                            CPosition *pos=this.GetPositionObjByID(position_id);
                            return(pos!=NULL ? pos.DealAdd(deal) : false);
                           }
    //--- Return the flag of the location of the specified position at the specified time
       bool              IsPresentInTime(CPosition *pos,const datetime time) const
                           {
                            return pos.IsPresentInTime(time);
                           }
    //--- Return the position profit relative to the close price
       double            ProfitRelativeClosePrice(CPosition *pos,const double price_close,const datetime time,const bool points) const
                           {
                            return pos.ProfitRelativeClosePrice(price_close,time,points);
                           }
    //--- Return the number of historical positions
       int               PositionsTotal(void) const { return this.m_list_pos.Total();   }
    //--- Print the properties of positions and their deals in the journal
       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();
                              }
                           }
    //--- Constructor
                         CHistoryPosition(void)
                           {
                            this.m_list_pos.Sort(POS_SORT_MODE_TIME_IN_MSC);
                           }
      };
    
    

    In the private section of the class, declare a list to store the pointers to objects of historical positions. Public methods are, to one degree or another, designed to work with this list.

    Let's consider them in more detail.

    The CreatePositionList() method creates the historical position list:

    //+------------------------------------------------------------------+
    //| CHistoryPosition::Create historical position list                |
    //+------------------------------------------------------------------+
    bool CHistoryPosition::CreatePositionList(const string symbol=NULL)
      {
    //--- If failed to request the history of deals and orders, return 'false'
       if(!::HistorySelect(0,::TimeCurrent()))
          return false;
    //--- Declare a result variable and a pointer to the position object
       bool res=true;
       CPosition *pos=NULL;
    //--- In a loop based on the number of history deals
       int total=::HistoryDealsTotal();
       for(int i=0;i<total;i++)
         {
          //--- get the ticket of the next deal in the list
          ulong ticket=::HistoryDealGetTicket(i);
          //--- If the deal ticket has not been received, or this is a balance deal, or if the deal symbol is specified, but the deal has been performed on another symbol, move on 
          if(ticket==0 || ::HistoryDealGetInteger(ticket,DEAL_TYPE)==DEAL_TYPE_BALANCE || (symbol!=NULL && symbol!="" && ::HistoryDealGetString(ticket,DEAL_SYMBOL)!=symbol))
             continue;
          //--- Create a deal object and, if the object could not be created, add 'false' to the 'res' variable and move on
          CDeal *deal=new CDeal(ticket);
          if(deal==NULL)
            {
             res &=false;
             continue;
            }
          //--- Get the value of the position ID from the deal 
          long pos_id=deal.PositionID();
          //--- Get the pointer to a position object from the list
          pos=this.GetPositionObjByID(pos_id);
          //--- If such a position is not yet in the list,
          if(pos==NULL)
            {
             //--- create a new position object. If failed to do that, add 'false' to the 'res' variable, remove the deal object and move on
             pos=new CPosition(pos_id);
             if(pos==NULL)
               {
                res &=false;
                delete deal;
                continue;
               }
             //--- Set the sorting by time flag in milliseconds to the list of positions and add the position object to the corresponding place in the list
             this.m_list_pos.Sort(POS_SORT_MODE_TIME_IN_MSC);
             //--- If failed to add the position object to the list, add 'false' to the 'res' variable, remove the deal and position objects and move on
             if(!this.m_list_pos.InsertSort(pos))
               {
                res &=false;
                delete deal;
                delete pos;
                continue;
               }
            }
          //--- If a position object is created and added to the list
          //--- If the deal object could not be added to the list of deals of the position object, add 'false' to the 'res' variable, remove the deal object and move on
          if(!pos.DealAdd(deal))
            {
             res &=false;
             delete deal;
             continue;
            }
          //--- All is successful.
          //--- Set position properties depending on the deal type
          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 the result of creating and adding a position to the list
       return res;
      }
    
    

    In a loop through the list of historical deals in the terminal, get the next deal, retrieve the position ID from it and check whether a position object with such an ID exists in the historical position list. If there is no such position in the list yet, create a new position object and add it to the list. If such a position already exists, get a pointer to this object from the list. Next, add the deal to the list of deals of the historical position. Then, based on a deal type, we enter the properties, inherent to the position and recorded in the deal properties, into the position object.

    Thus, while going through all historical deals in a loop, we create a list of historical positions, within which the deals belonging to each position are stored in the list. When creating a list of positions based on a list of deals, the change in the volume of partially closed positions is not taken into account. We should add such functionality if necessary. At the moment, this is beyond the scope of the article, since here we are looking at an example of creating a list of historical positions, and not an exact reproduction of changes in the properties of positions during their lifetime.

    We need to add the described functionality in the code block marked with color. Alternatively, if we follow the principles of OOP, then we can make the CreatePositionList() method virtual, inherit from the class of the list of historical positions and make our own implementation of this method in our own class. Or we can move changing the position properties by the deal ones (block of code marked with color ) into a protected virtual method and override this protected method in the inherited class without completely rewriting CreatePositionList().

    The GetPositionObjByIndex() method returns the pointer to a historical position object from the list by the index passed to the method.
    We get the pointer to the object located in the list by the specified index or NULL if the object is not found.

    The GetPositionObjByID() method returns the pointer to a historical position object from the list by the position ID passed to the method:

       CPosition        *GetPositionObjByID(const long id)
                           {
                            //--- Create a temporary position object
                            CPosition *tmp=new CPosition(id);
                            //--- Set the list of positions to be sorted by position ID
                            this.m_list_pos.Sort(POS_SORT_MODE_ID);
                            //--- Get the index of the object in the list of positions with this ID
                            int index=this.m_list_pos.Search(tmp);
                            //--- Remove the temporary object and set the sorting by time flag for the list in milliseconds.
                            delete tmp;
                            this.m_list_pos.Sort(POS_SORT_MODE_TIME_IN_MSC);
                            //--- Return the pointer to the object in the list at the received index, or NULL if the index was not received 
                            return this.m_list_pos.At(index);
                           }
    
    

    The object whose position ID is equal to the one passed to the method is searched for in the list. The result is the pointer to the found object or NULL if the object is not found.

    The DealAdd() method adds a deal to the list of deals:

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

    The method receives the ID of the position the deal is to be added to. The pointer to the deal is also passed to the method. If a deal is successfully added to the list of deals of a position, the method returns true, otherwise — false.

    The IsPresentInTime() method returns the flag of the location of the specified position at the specified time:

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

    The method receives the pointer to the position, that needs to be checked, and the time period, within which it is necessary to check the position. If the position existed at the specified time, the method returns true, otherwise - false.

    The ProfitRelativeClosePrice() method returns the position profit relative to the close price.

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

    The method receives the pointer to the position whose data needs to be obtained, bar close price and its time, relative to which the profit of the position should be obtained, as well as the flag indicating the data units — the number of points or the point number cost.

    The PositionsTotal() method returns the number of historical positions in the list:

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

    The Print() method prints out the properties of positions and their deals in the journal:

       void              Print(void)
                           {
                            //--- In the loop through the list of historical positions 
                            for(int i=0;i<this.m_list_pos.Total();i++)
                              {
                               //--- get the pointer to the position object and print the properties of the position and its deals
                               CPosition *pos=this.m_list_pos.At(i);
                               if(pos==NULL)
                                  continue;
                               pos.Print();
                              }
                           }
    
    

    Set the sorting by time flag in milliseconds to the historical position list in the class constructor:

    //--- Constructor
                         CHistoryPosition(void)
                           {
                            this.m_list_pos.Sort(POS_SORT_MODE_TIME_IN_MSC);
                           }
    
    

    We have created all classes for implementing the plan.

    Now let’s write an indicator code that will create a list of all positions for the current symbol that have ever existed on the account and draw a chart of their profit/loss on each bar of historical data.


    Position profit graph indicator

    The most suitable drawing style of the indicator buffer for displaying them as diagrams — colored area between two indicator buffers, DRAW_FILLING.

    The DRAW_FILLING style plots a colored area between the values of two indicator buffers. In fact, this style draws two lines and fills the space between them with one of two specified colors. Is is used for creating indicators that draw channels. None of the buffers can contain only null values, since in this case nothing is plotted.

    You can set two fill colors:

    • the first color is used for the areas where values in the first buffer are greater than the values in the second indicator buffer;
    • the second color is used for the areas where values in the second buffer are greater than the values in the first indicator buffer.

    The filling color can be set using the compiler directives or dynamically - using the PlotIndexSetInteger() function. Dynamic changes of the plotting properties allows "to enliven" indicators, so that their appearance changes depending on the current situation.

    The indicator is calculated for all bars, for which the values of the both indicator buffers are equal neither to 0 nor to the empty value. To specify what value should be considered as "empty", set this value in the PLOT_EMPTY_VALUE property:

       #define INDICATOR_EMPTY_VALUE -1.0
       ...
    //--- the INDICATOR_EMPTY_VALUE (empty) value will not be used in the calculation
       PlotIndexSetDouble(plot_index_DRAW_FILLING,PLOT_EMPTY_VALUE,INDICATOR_EMPTY_VALUE);
    

    Drawing on the bars that do not participate in the indicator calculation will depend on the values ​​in the indicator buffers:

    • Bars, for which the values ​​of both indicator buffers are equal to 0, do not participate in drawing the indicator. It means that the area with zero values is not filled out.


    • Bars, for which the values ​​of the indicator buffers are equal to "the empty value", participate in drawing the indicator. The area with empty values will be filled out so that to connect the areas with significant values.


    It should be noted that if "the empty value" is equal to zero, the bars that do not participate in the indicator calculation are also filled out.

    The number of buffers required for plotting DRAW_FILLING is 2.

    Add one input to select bar position profit measurement units. Declare the pointer to the instance of the historical position list class and supplement the OnInit() handler a bit:

    //+------------------------------------------------------------------+
    //| Indicator                                                        |
    //+------------------------------------------------------------------+
    //--- 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);
    //--- Set the indexing of buffer arrays as in a timeseries
       ArraySetAsSeries(BufferFilling1,true);
       ArraySetAsSeries(BufferFilling2,true);
    //--- Set Digits of the indicator data equal to 2 and one level equal to 0
       IndicatorSetInteger(INDICATOR_DIGITS,2);
       IndicatorSetInteger(INDICATOR_LEVELS,1);
       IndicatorSetDouble(INDICATOR_LEVELVALUE,0);
    //--- Create a new historical data object
       history=new CHistoryPosition();
    //--- Return the result of creating a historical data object
       return(history!=NULL ? INIT_SUCCEEDED : INIT_FAILED);
      }
    
    

    Write the OnDeinit() handler, in which we will delete the created instance of the historical position list class and delete chart comments:

    //+------------------------------------------------------------------+
    //| Custom indicator deinitialization function                       |
    //+------------------------------------------------------------------+
    void OnDeinit(const int reason)
      {
    //--- Delete the object of historical positions and chart comments
       if(history!=NULL)
          delete history;
       Comment("");
      }
    
    

    In the OnCalculate() handler, namely in the first tick, create a list of all historical positions and access the created list, using the function for calculating profit on the current bar of the indicator loop, in the main loop:

    //+------------------------------------------------------------------+
    //| 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[])
      {
    //--- Set 'close' and 'time' array indexing as in timeseries
       ArraySetAsSeries(close,true);
       ArraySetAsSeries(time,true);
    //--- Flag of the position list successful creation
       static bool done=false;
    //--- If the position data object is created
       if(history!=NULL)
         {
          //--- If no position list was created yet
          if(!done)
            {
             //--- If the list of positions for the current symbol has been successfully created,
             if(history.CreatePositionList(Symbol()))
               {
                //--- print positions in the journal and set the flag for successful creation of a list of positions 
                history.Print();
                done=true;
               }
            }
         }
    //--- Number of bars required to calculate the indicator
       int limit=rates_total-prev_calculated;
    //--- If 'limit' exceeds 1, this means this is the first launch or a change in historical data
       if(limit>1)
         {
          //--- Set the number of bars for calculation equal to the entire available history and initialize the buffers with "empty" values 
          limit=rates_total-1;
          ArrayInitialize(BufferFilling1,EMPTY_VALUE);
          ArrayInitialize(BufferFilling2,EMPTY_VALUE);
         }
    //--- In the loop by symbol history bars
       for(int i=limit;i>=0;i--)
         {
          //--- get the profit of positions present on the bar with the i loop index and write the resulting value to the first buffer
          double profit=Profit(close[i],time[i]);
          BufferFilling1[i]=profit;
          //--- Always write zero to the second buffer. Depending on whether the value in the first buffer is greater or less than zero,
          //--- the fill color between arrays 1 and 2 of the indicator buffer will change
          BufferFilling2[i]=0;
         }
    //--- return value of prev_calculated for the next call
       return(rates_total);
      }
    
    

    A regular indicator loop is arranged here from the beginning of historical data to the current date. In the loop, for each bar, call the function to calculate the profit of historical positions on the current bar of the loop:

    //+-----------------------------------------------------------------------+
    //| Return the profit of all positions from the list at the specified time|
    //+-----------------------------------------------------------------------+
    double Profit(const double price,const datetime time)
      {
    //--- Variable for recording and returning the result of calculating profit on a bar
       double res=0;
    //--- In the loop by the list of historical positions
       for(int i=0;i<history.PositionsTotal();i++)
         {
          //--- get the pointer to the next position 
          CPosition *pos=history.GetPositionObjByIndex(i);
          if(pos==NULL)
             continue;
          //--- add the value of calculating the profit of the current position, relative to the 'price' on the bar with 'time', to the result
          res+=pos.ProfitRelativeClosePrice(price,time,InpProfitPoints);
         }
    //--- Return the calculated amount of profit of all positions relative to the 'price' on the bar with 'time'
       return res;
      }
    
    

    The function gets the price (bar's Close), relative to which the number of profit points of the position should be obtained, as well as the time, at which the existence of the position is checked (bar's Time open). Next, the profit of all positions received from each object of historical positions is summed up and returned.

    After compilation, we can run the indicator on the chart of a symbol where there were many open positions, and it will draw a profit chart of all historical positions:



    Conclusion

    We have considered the possibility of restoring a position from deals and compiling a list of all historical positions that have ever been present on an account. The example is simple and does not cover all aspects of accurately restoring a position from deals, but it is quite sufficient to independently supplement it to the required accuracy of position restoration.


    Translated from Russian by MetaQuotes Ltd.
    Original article: https://www.mql5.com/ru/articles/13911

    Attached files |
    Last comments | Go to discussion (10)
    Roman Kutemov
    Roman Kutemov | 3 Jan 2024 at 14:57

    Hello,

    how can I make the indicator show not every profit/loss, but an accumulative total ?

    and if possible - with a line ?!!

    Artyom Trishkin
    Artyom Trishkin | 3 Jan 2024 at 15:30
    Roman Kutemov #:

    Hello,

    how can I make the indicator show not every profit/loss, but the cumulative total ?

    and if possible - with a line ?!!

    On a hunch and without checking, you just need to add the profit to the already received one, and not to record it every time the one taken from the positions.

    That'show it is:

    //+------------------------------------------------------------------+
    //| 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);
      }
    

    Well, and with the buffer as a line - it's themselves. There you need to remove one extra buffer, because the fill always uses two buffers. And for a line you need one.

    Roman Kutemov
    Roman Kutemov | 3 Jan 2024 at 17:13

    I think it's working.

    I made your changes and just specified the type - line graphics.

    I didn't remove the buffers.

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

    Roman Kutemov
    Roman Kutemov | 4 Jan 2024 at 05:53
    Everything seems to be good on currencies, but on silver it is constantly redrawing for some reason.
    Artyom Trishkin
    Artyom Trishkin | 4 Jan 2024 at 06:29
    Roman Kutemov #:
    Everything seems to be fine on currencies, but on silver it is constantly redrawing for some reason.

    It is necessary to look at what data the indicator receives and why it is not calculated. The redrawing can be caused by the limit value greater than 1. This value is calculated as the difference between rates_total and prev_calculated. You need to look at these values to see what they contain on each tick

    Developing a Replay System (Part 33): Order System (II) Developing a Replay System (Part 33): Order System (II)
    Today we will continue to develop the order system. As you will see, we will be massively reusing what has already been shown in other articles. Nevertheless, you will receive a small reward in this article. First, we will develop a system that can be used with a real trading server, both from a demo account or from a real one. We will make extensive use of the MetaTrader 5 platform, which will provide us with all the necessary support from the beginning.
    Color buffers in multi-symbol multi-period indicators Color buffers in multi-symbol multi-period indicators
    In this article, we will review the structure of the indicator buffer in multi-symbol, multi-period indicators and organize the display of colored buffers of these indicators on the chart.
    Overcoming ONNX Integration Challenges Overcoming ONNX Integration Challenges
    ONNX is a great tool for integrating complex AI code between different platforms, it is a great tool that comes with some challenges that one must address to get the most out of it, In this article we discuss the common issues you might face and how to mitigate them.
    MQL5 Wizard Techniques you should know (Part 16): Principal Component Analysis with Eigen Vectors MQL5 Wizard Techniques you should know (Part 16): Principal Component Analysis with Eigen Vectors
    Principal Component Analysis, a dimensionality reducing technique in data analysis, is looked at in this article, with how it could be implemented with Eigen values and vectors. As always, we aim to develop a prototype expert-signal-class usable in the MQL5 wizard.