
Indicator of historical positions on the chart as their profit/loss diagram
Contents
- Introduction
- Deal class
- Position class
- Historical position list class
- Position profit graph indicator
- Conclusion
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:
- Deal class. Contains deal properties necessary to identify a position and its properties.
- Position class. Contains a list of deals that participated in a position and the properties inherent to positions.
- 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





- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use
Hello,
how can I make the indicator show not every profit/loss, but an accumulative total ?
and if possible - with a line ?!!
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:
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.
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
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