![图表上的历史仓位及其盈利/亏损图指标](https://c.mql5.com/2/62/midjourney_image_13911_52_463_3_600x314.jpg)
图表上的历史仓位及其盈利/亏损图指标
目录
概述
仓位...仓位是执行发送到服务器的交易订单的结果:订单-->交易-->仓位。
在净额账户中,只要指出未平仓仓位的交易品种的名称,我们就可以使用 PositionSelect() 函数在我们的程序中获取未平仓仓位的列表:
if(PositionSelect(symbol_name)) { /* Work with selected position data PositionGetDouble(); PositionGetInteger(); PositionGetString(); */ }
对于可以显示独立仓位(对冲)的账户,我们首先需要使用 PositionsTotal()获取仓位数量。然后在按仓位数量的循环中,我们需要使用 PositionGetTicket()根据持仓单在未平仓位列表中的索引获取仓位编号。之后,使用 PositionSelectByTicket()根据收到的编号选择一个仓位:
int total=PositionsTotal(); for(int i=total-1;i>=0;i--) { ulong ticket=PositionGetTicket(i); if(ticket==0 || !PositionSelectByTicket(ticket)) continue; /* Work with selected position data PositionGetDouble(); PositionGetInteger(); PositionGetString(); */ }
这里的一切都简单而清晰。但当我们需要找出已平仓位的情况时,情况就完全不同了 - 没有可以处理历史仓位的函数......
在这种情况下,我们需要记住并知道每个仓位都有其唯一的ID。这个 ID 登记在影响仓位的交易中 - 引导仓位的开仓、修改或平仓。我们可以使用函数 HistorySelect()、HistoryDealsTotal() 和 HistoryDealGetTicket()(HistoryDealSelect()) 获得交易列表(包括交易和交易参与的仓位的 ID)。
一般而言,交易所涉及的仓位 ID 可以通过以下方式获取:
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(); */ } }
换句话说,通过交易列表,我们总能找出哪个仓位属于某笔交易。例如,我们可以获取一个仓位 ID,并查看具有相同 ID 的交易来找到进入和退出市场的交易。利用这些交易的属性,我们可以找出所需历史仓位的时间、手数和其他必要属性。
为了搜索已平仓的历史仓位,我们将要获取历史交易列表。在此列表的循环中,我们将获取每一笔下单,并检查该笔交易参与的仓位 ID。如果这样的仓位尚未在列表中,那么我们将创建一个仓位对象;如果已经存在,那么我们将使用具有相同 ID 的现有仓位。将当前交易(如果尚未在列表中)添加到找到的仓位的交易列表中。因此,在遍历了整个历史成交列表后,我们将找到成交参与的所有仓位,创建所有历史仓位的列表,并将参与此仓位的所有成交添加到历史仓位的每个对象中(即其成交列表)。
我们需要三个类:
- 交易类。包含标识仓位及其属性所需的交易属性。
- 仓位类。包含参与持仓的交易列表以及仓位固有的属性。
- 历史仓位列表类。找到的历史仓位列表,能够根据指定的属性选择仓位。
通过这三个小类,可以轻松找到所有历史交易,将它们保存在列表中,然后在指标中使用这些仓位的数据来绘制账户上所选交易品种的仓位盈亏图。
在 Indicators 文件夹中,创建一个名为 PositionInfoIndicator 的新指标文件。指定在单独的图表窗口中使用一个可绘制缓冲区、以填充样式绘制指标,并使用绿色和红色填充。
这将会创建具有以下标题的指标模板:
//+------------------------------------------------------------------+ //| PositionInfoIndicator.mq5 | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property indicator_separate_window #property indicator_buffers 2 #property indicator_plots 1 //--- plot Fill #property indicator_label1 "Profit;ZeroLine" #property indicator_type1 DRAW_FILLING #property indicator_color1 clrGreen,clrRed #property indicator_style1 STYLE_SOLID #property indicator_width1 1
输入下面创建中的类。
为了创建交易和持仓列表,我们将使用标准库中的指向 CObject 类及其派生类实例的动态指针数组类 。
将类的文件包含到创建的文件中:
//+------------------------------------------------------------------+ //| PositionInfoIndicator.mq5 | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property indicator_separate_window #property indicator_buffers 2 #property indicator_plots 1 //--- plot Fill #property indicator_label1 "Profit;ZeroLine" #property indicator_type1 DRAW_FILLING #property indicator_color1 clrGreen,clrRed #property indicator_style1 STYLE_SOLID #property indicator_width1 1 //--- includes #include <Arrays\ArrayObj.mqh>
该类包含 Search() 方法,用于 在排好序的数组中搜索与示例相等的元素:
//+------------------------------------------------------------------+ //| Search of position of element in a sorted array | //+------------------------------------------------------------------+ int CArrayObj::Search(const CObject *element) const { int pos; //--- check if(m_data_total==0 || !CheckPointer(element) || m_sort_mode==-1) return(-1); //--- search pos=QuickSearch(element); if(m_data[pos].Compare(element,m_sort_mode)==0) return(pos); //--- not found return(-1); }
该数组应该按照其包含指针的对象的某些属性进行排序。通过将整数传递给 m_sort_mode 变量来设置排序模式。默认情况下使用 0 值。值 -1 表示数组未排序。要按不同的属性排序,我们需要通过将 m_sort_mode 变量设置为零及以上的值来设置不同的排序模式。为此,使用定义列表不同排序模式的枚举会很方便。这些模式用于比较两个 Compare() 对象的虚拟方法。它定义在 CObject 类中,返回 0 值,表示被比较的对象相同:
//--- method of comparing the objects virtual int Compare(const CObject *node,const int mode=0) const { return(0); }
此方法应在自定义类中被重载,您可以根据它们的各种属性安排两个相同类型的对象的比较(如果 m_sort_mode 的值等于 0,则通过一个属性比较对象,如果等于 1 - 通过另一个属性进行比较,等于 2 - 通过第三个属性进行比较,等等)。
此处创建的每个类对象都有其自己的一组属性。应该根据这些属性对对象进行排序才能执行搜索。因此,我们需要首先创建对象属性的枚举 :
//+------------------------------------------------------------------+ //| PositionInfoIndicator.mq5 | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property indicator_separate_window #property indicator_buffers 2 #property indicator_plots 1 //--- plot Fill #property indicator_label1 "Profit;ZeroLine" #property indicator_type1 DRAW_FILLING #property indicator_color1 clrGreen,clrRed #property indicator_style1 STYLE_SOLID #property indicator_width1 1 //--- includes #include <Arrays\ArrayObj.mqh> //--- enums //--- 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
这两个枚举常量对应的属性将包含在交易和仓位对象类中。可以根据这些属性对列表进行排序,以找到对象的相等性。
下面我们将输入创建的类的代码。
交易类
让我们从整体上看一下交易类://+------------------------------------------------------------------+ //| 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){} };
这里的一切都非常简单:用于存储交易属性的类成员变量在私有部分中声明。公有部分实现设置和返回交易属性的方法。Compare() 虚拟方法对每个交易属性执行比较,具体取决于将哪个属性作为比较模式传递给该方法。如果当前对象正在检查的属性值大于被比较对象的相同属性值,则返回 1,如果小于,则返回 -1。如果值相等,则为 0。在类构造函数中,认为该交易已被选中,并将其属性写入相应的私有类变量。这已经足以创建一个交易对象,该对象存储了从终端历史交易列表中选择的交易的所有必要属性。将为每笔交易创建类对象。将从对象属性中提取仓位ID,并创建历史仓位类的对象。所有属于该仓位的交易都将列在仓位类的交易列表中。因此,历史仓位对象将包含其所有相关交易的列表。可以从中提取仓位历史记录。
让我们探讨一下历史仓位类。
仓位类
仓位类将包含此仓位的交易列表,以及用于计算值、获取值和返回仓位生命周期信息的辅助方法:
//+------------------------------------------------------------------+ //| 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); } };
在私有部分中,声明了用于存储仓位属性的类成员变量并编写了辅助方法。
BarOpenTime() 方法根据传递给该方法的特定时间计算并返回给定时间框架内某个柱的开启时间:
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 flag of the existence of a position at the specified time bool IsPresentInTime(const datetime time) const { return(time>=this.BarTimeOpenPosition() && time<=this.BarTimeClosePosition()); }
或者我们可以简单地获取开仓或平仓柱:
//--- 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()); }
为了计算当前图表柱上的仓位的盈利/亏损,我们将使用一个简化的近似计算原则 - 在一种情况下,我们将考虑相对于仓位的开盘价和当前柱的收盘价的盈利点数。在第二种情况下,我们将计算这些盈利/亏损点的成本:
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(); }
这两种方法都无法完整地反映仓位存续期间的浮动盈利,但在这里这不是必需的。这里我们更重要的是展示如何获取已平仓头寸的数据。为了进行准确的计算,我们将需要更多关于仓位生命周期中在每根柱的市场状态的不同数据 - 我们需要获取从开盘时间到收盘时间的分时,并使用它们来模拟这段时间内的仓位状态。然后我们应该对每一根柱形都做同样的事情。目前没有这样的任务。
将要创建仓位对象的 ID 被传递给类构造函数:
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); }
在创建类对象时,该 ID 就已经已经知道了,我们可以从交易属性中获取它。创建仓位类对象后,我们将从中获取仓位 ID 的选定交易添加到其交易列表中。所有这些都是在历史仓位列表类中完成的,我们将进一步探讨。
历史仓位列表类
//+------------------------------------------------------------------+ //| 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); } };
在类的私有部分,声明一个列表来存储指向历史仓位对象的指针。公有方法在某种程度上就是被设计用于此列表的。
让我们更详细地探讨它们。
CreatePositionList() 方法 创建历史仓位列表:
//+------------------------------------------------------------------+ //| 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; }
循环遍历终端中的历史成交列表,获取下一笔成交,从中检索仓位 ID,并检查历史仓位列表中是否存在具有此类 ID 的仓位对象。如果列表中还没有这样的仓位,则创建一个新的仓位对象并将其添加到列表中。如果这样的仓位已经存在,则从列表中获取指向该对象的指针。接下来,将交易添加到历史仓位的交易列表中。然后,根据交易类型,我们将仓位固有的、记录在交易属性中的属性输入到仓位对象中。
因此,在循环遍历所有历史交易时,我们创建一个历史仓位列表,其中属于每个仓位的交易都存储在列表中。当根据交易列表创建仓位列表时,不会考虑部分平仓仓位数量的变化。如果有必要,我们应该添加这样的功能。目前,这超出了本文的范围,因为在这里我们正在研究创建历史仓位列表的示例,而不是准确再现仓位在其生命周期内属性的变化。
我们需要在用颜色标记的代码块中添加所描述的功能。或者,如果我们遵循 OOP 原则,那么我们可以将 CreatePositionList() 方法设为虚拟方法,从历史仓位列表类继承,并在我们自己的类中实现此方法。或者,我们可以将交易属性(用颜色标记的代码块 )的变更仓位属性移至受保护的虚拟方法中,并在继承的类中覆盖此受保护的方法,而无需完全重写 CreatePositionList()。
GetPositionObjByIndex() 方法 根据传递给该方法的索引从列表中返回指向历史仓位对象的指针。
我们通过指定的索引获取指向列表中对象的指针,如果未找到该对象,则 得到 NULL。
GetPositionObjByID() 方法 根据传递给该方法的仓位 ID 从列表中返回指向历史仓位对象的指针:
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); }
在列表中搜索仓位 ID 与传递给该方法的仓位 ID 相同的对象。结果是指向找到的对象的指针,如果未找到该对象则为 NULL。
DealAdd() 方法将交易添加到交易列表中:
bool DealAdd(const long position_id,CDeal *deal) { CPosition *pos=this.GetPositionObjByID(position_id); return(pos!=NULL ? pos.DealAdd(deal) : false); }
该方法接收要添加交易的仓位的 ID。指向交易的指针也被传递给该方法。如果成功将交易添加到仓位交易列表中,该方法返回 true,否则返回 false。
IsPresentInTime() 方法 返回指定时间指定仓位的位置标志:
bool IsPresentInTime(CPosition *pos,const datetime time) const { return pos.IsPresentInTime(time); }
该方法接收需要检查的仓位的指针,以及需要检查该仓位的时间段。如果该仓位在指定时间存在,则该方法返回 true,否则返回 false。
ProfitRelativeClosePrice() 方法 返回相对于收盘价的持仓利润。
double ProfitRelativeClosePrice(CPosition *pos,const double price_close,const datetime time,const bool points) const { return pos.ProfitRelativeClosePrice(price_close,time,points); }
该方法接收指向需要获取数据的仓位的指针、柱的收盘价和时间、相对于该价格应获取仓位利润的价格,以及指示数据单位的标志 — 点数或点数成本。
PositionsTotal() 方法 返回列表中的历史仓位数量:
int PositionsTotal(void) const { return this.m_list_pos.Total(); }
Print() 方法 在日志中打印出仓位的属性及其交易:
void Print(void) { //--- 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(); } }
在类构造函数中将按时间标志(以毫秒为单位)排序设置为历史仓位列表:
//--- Constructor CHistoryPosition(void) { this.m_list_pos.Sort(POS_SORT_MODE_TIME_IN_MSC); }
我们已经创建了实施该计划所需的所有类。
现在让我们编写一个指标代码,它将创建帐户中曾经存在过的当前交易品种的所有仓位的列表,并在每个历史数据柱形上绘制其盈亏图表。
持仓利润图表指标
最适合将其显示为图表的指标缓冲区 绘制样式 — 两个指标缓冲区之间的彩色区域,DRAW_FILLING。
DRAW_FILLING 样式在两个指标缓冲区的值之间绘制一个彩色区域。实际上,这种样式绘制两条线,并用两种指定的颜色之一填充它们之间的空间。它是用于创建绘制通道的指标。任何缓冲区都不能只包含空值,因为在这种情况下没有绘制任何内容。
您可以设置两种填充颜色:
- 第一种颜色用于第一个缓冲区中的值大于第二个指标缓冲区中的值的区域;
- 第二种颜色用于第二个缓冲区中的值大于第一个指标缓冲区中的值的区域。
可以使用 编译器指令 或动态设置填充颜色 - 使用 PlotIndexSetInteger() 函数。绘图属性的动态变化可以使指标“活跃起来”,使其外观根据当前情况而变化。
此指标针对所有柱形进行计算,其两个指标缓冲区的值既不等于 0,也不等于空值。要指定什么值应被视为“空”,请在 PLOT_EMPTY_VALUE 属性中设置该值:
#define INDICATOR_EMPTY_VALUE -1.0 ... //--- the INDICATOR_EMPTY_VALUE (empty) value will not be used in the calculation PlotIndexSetDouble(plot_index_DRAW_FILLING,PLOT_EMPTY_VALUE,INDICATOR_EMPTY_VALUE);
不参与指标计算的柱形的绘制将取决于 在指标缓冲区中的值:
- 柱形 在两个指标缓冲区的都等于0,则不参与绘制指标。这意味着零值区域不会被填充。
- 柱形 在指标缓冲区的等于“空值”,参与绘制指标。将填充具有空值的区域,以便连接具有有效值的区域。
需要注意的是,如果“空值”等于零,则不参与指标计算的柱形也会被填充。
绘制 DRAW_FILLING 所需的缓冲区数量为 2。
添加一个输入参数 来选择柱形位置利润测量单位。声明指向历史仓位列表类实例的指针 ,并 稍微补充一下 OnInit() 处理程序:
//+------------------------------------------------------------------+ //| 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); }
编写 OnDeinit() 处理程序,在其中 我们将删除创建的历史仓位列表类的实例并删除图表注释:
//+------------------------------------------------------------------+ //| Custom indicator deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Delete the object of historical positions and chart comments if(history!=NULL) delete history; Comment(""); }
在 OnCalculate() 处理程序中,即在第一个价格变动中, 创建所有历史仓位的列表 ,并在主循环中使用计算指标循环当前柱的利润的函数访问创建的列表 :
//+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { //--- 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); }
这里会安排从历史数据开始到当前日期的常规指标循环。在循环中,对于每个柱,调用函数来计算循环当前柱上的历史仓位的盈利:
//+-----------------------------------------------------------------------+ //| 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; }
此函数获取价格(柱形的收盘价),相对于此价格应获得仓位的盈利点数,以及检查仓位存续的时间(柱形的开盘时间)。接下来将从每个历史仓位对象收到的所有仓位的盈利相加并返回。
编译完成后,我们可以在有大量持仓的交易品种图表上运行该指标,它会绘制所有历史仓位的盈利图表:
结论
我们已经探讨了从交易中恢复仓位并编制账户中曾经存在的所有历史仓位列表的可能性。该示例很简单,并未涵盖从成交中准确恢复持仓的所有方面,但独立补充到持仓恢复所需的准确度就足够了。
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/13911
![种群优化算法:模拟各向同性退火(SIA)算法。第 II 部分](https://c.mql5.com/2/62/midjourney_image_13870_45_399__3-logo.png)
![掌握 MQL5:从入门到精通(第二部分)基本数据类型和变量的使用](https://c.mql5.com/2/64/Learning_MQL5_-_from_beginner_to_pro_xPart_IIv_LOGO.png)
![开发具有 RestAPI 集成的 MQL5 强化学习代理(第 3 部分):在 MQL5 中创建自动移动和测试脚本](https://c.mql5.com/2/61/RestAPI_Parte_3_-_Criando_jogadas_automuticas_e_Scripts_de_Teste_em_MQL5__LOGO.png)