English Русский Español Deutsch 日本語 Português
preview
图表上的历史仓位及其盈利/亏损图指标

图表上的历史仓位及其盈利/亏损图指标

MetaTrader 5示例 | 24 七月 2024, 09:44
543 0
Artyom Trishkin
Artyom Trishkin

目录


概述

仓位...仓位是执行发送到服务器的交易订单的结果:订单-->交易-->仓位。

在净额账户中,只要指出未平仓仓位的交易品种的名称,我们就可以使用 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()HistoryDe​​alsTotal()HistoryDe​​alGetTicket()(HistoryDe​​alSelect()) 获得交易列表(包括交易和交易参与的仓位的 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 的现有仓位。将当前交易(如果尚未在列表中)添加到找到的仓位的交易列表中。因此,在遍历了整个历史成交列表后,我们将找到成交参与的所有仓位,创建所有历史仓位的列表,并将参与此仓位的所有成交添加到历史仓位的每个对象中(即其成交列表)。

我们需要三个类:

  1. 交易类。包含标识仓位及其属性所需的交易属性。
  2. 仓位类。包含参与持仓的交易列表以及仓位固有的属性。
  3. 历史仓位列表类。找到的历史仓位列表,能够根据指定的属性选择仓位。

通过这三个小类,可以轻松找到所有历史交易,将它们保存在列表中,然后在指标中使用这些仓位的数据来绘制账户上所选交易品种的仓位盈亏图。

在 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 部分 种群优化算法:模拟各向同性退火(SIA)算法。第 II 部分
    第一部分专注于众所周知、且流行的算法 — 模拟退火。我们已经通盘研究了它的利弊。本文的第二部分专注于算法的彻底变换,将其转变为一种新的优化算法 — 模拟各向同性退火(SIA)。
    多交易品种多周期指标中的颜色缓冲区 多交易品种多周期指标中的颜色缓冲区
    在本文中,我们将回顾多交易品种、多周期指标中指标缓冲区的结构,并在图表上组织这些指标的彩色缓冲区的显示。
    掌握 MQL5:从入门到精通(第二部分)基本数据类型和变量的使用 掌握 MQL5:从入门到精通(第二部分)基本数据类型和变量的使用
    这是初学者系列的延续。本文将介绍如何创建常量和变量、写入日期、颜色和其他有用的数据。我们将学习如何创建枚举,如一周中的天数或线条样式(实线、虚线等)。变量和表达式是编程的基础。它们肯定存在于99%以上的程序中,因此理解它们至关重要。因此,如果你是编程新手,这篇文章会对你非常有用。所需的编程知识水平:非常基础,在我上一篇文章(见开头的链接)的范围内。
    开发具有 RestAPI 集成的 MQL5 强化学习代理(第 3 部分):在 MQL5 中创建自动移动和测试脚本 开发具有 RestAPI 集成的 MQL5 强化学习代理(第 3 部分):在 MQL5 中创建自动移动和测试脚本
    本文讨论在 Python 中实现井字游戏中的自动移动,并与 MQL5 函数和单元测试集成。目标是通过在 MQL5 中进行测试,提高游戏的互动性并确保系统的可靠性。本文内容包括游戏逻辑开发、集成和实际测试,最后将介绍动态游戏环境和强大集成系统的创建。