English Русский Español Deutsch 日本語
preview
Indicador de posições históricas no gráfico em forma de diagrama de lucro/prejuízo

Indicador de posições históricas no gráfico em forma de diagrama de lucro/prejuízo

MetaTrader 5Exemplos | 30 maio 2024, 16:55
205 0
Artyom Trishkin
Artyom Trishkin

Conteúdo


Introdução

Posição... A posição é o resultado de uma ordem de negociação executada no servidor: Order --> Deal --> Position.

Sempre podemos obter a lista de posições abertas no nosso programa usando a função PositionSelect() para contas de netting, especificando o nome do símbolo da posição aberta.

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

Para contas com posições independentes (hedge), primeiro obtemos o número de posições usando PositionsTotal(), depois, num laço, pegamos o ticket da posição pelo índice na lista de posições abertas usando PositionGetTicket(), e então selecionamos a posição pelo ticket usando 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();
      */
     }

Isso é simples e direto. Mas, quando queremos informações sobre uma posição já fechada, não há funções específicas para isso...

Precisamos lembrar que cada posição tem um identificador único. Esse identificador está nas negociações que abriram, modificaram ou fecharam a posição. Podemos pegar a lista de negociações e, a partir dela, usar HistorySelect(), HistoryDealsTotal() e HistoryDealGetTicket() (HistoryDealSelect()) para encontrar o identificador da posição.

Em geral, o identificador da posição na qual a transação estava envolvida pode ser obtido da seguinte forma:

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

Assim, sabemos a qual posição cada negociação pertence. Por exemplo, podemos pegar o identificador da posição e, pelas negociações com esse identificador, encontrar a negociação de abertura e fechamento. Com as propriedades dessas negociações, podemos obter o tempo, o lote e outras informações necessárias sobre a posição histórica.

Para encontrar posições fechadas, obtemos a lista de negociações históricas. Num laço, pegamos cada negociação e verificamos o identificador da posição. Se essa posição ainda não estiver na lista, criamos um objeto de posição; se já estiver, usamos a posição existente com esse identificador. A negociação atual (se ainda não estiver na lista) é adicionada à lista de negociações da posição encontrada. Percorrendo toda a lista de negociações históricas, encontraremos todas as posições das que participaram negociações, criando uma lista de todas as posições históricas e, em cada posição, adicionaremos todas as negociações que participaram dela.

Precisaremos de três classes:

  1. Classe de negociação: Contém as propriedades da negociação necessárias para identificar a posição e suas propriedades.
  2. Classe de posição: Contém a lista de negociações e as propriedades associadas às posições.
  3. Classe da lista de posições históricas: Lista de posições históricas encontradas, permitindo selecionar uma posição pelas propriedades especificadas.

Essas três classes nos ajudarão a encontrar todas as negociações históricas, salvá-las em uma lista e, depois, usar esses dados no indicador para desenhar o diagrama de lucro/prejuízo das posições por símbolo selecionado na conta.

Na pasta Indicators, criaremos um novo arquivo chamado PositionInfoIndicator. Especificaremos a renderização do indicador com um buffer de estilo Filling em uma janela separada do gráfico, com cores de preenchimento Green e Red.

Será criado um template de indicador com o seguinte cabeçalho:

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

Abaixo do código, escreveremos as classes criadas.

Para criar listas de negociações e posições, usaremos uma classe de array dinâmico de ponteiros para instâncias da classe CObject e seus derivados da Biblioteca Padrão.

Incluiremos o arquivo dessa classe no arquivo criado:

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

A classe contém o método Search() para buscar elementos iguais ao padrão em um array ordenado:

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

O array deve ser ordenado por alguma propriedade dos objetos cujos ponteiros estão contidos nele. O modo de ordenação é definido passando um valor inteiro para a variável m_sort_mode . Por padrão, usa-se o valor 0. O valor -1 significa que o array não está ordenado. Para ordenar por diferentes propriedades, é necessário definir vários modos de ordenação configurando valores de zero em diante na variável m_sort_mode. Para isso, é conveniente usar enumerações que definem diferentes modos de ordenação de listas. Esses modos são usados no método virtual de comparação de dois objetos Compare(). Ele é definido no arquivo da classe CObject e retorna o valor 0, que indica a identidade dos objetos comparados:

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

Este método deve ser sobrescrito em suas próprias classes, onde se pode gerar a comparação de dois objetos do mesmo tipo por diferentes propriedades (com m_sort_mode igual a 0, os objetos são comparados por uma propriedade, com m_sort_mode igual a 1 — por outra propriedade, e assim por diante).

Cada objeto da classe criada hoje terá seu próprio conjunto de propriedades. Essas propriedades serão usadas para ordenar as listas na busca de igualdade de objetos. Por isso, precisamos primeiro criar enumerações das propriedades desses objetos:

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

As propriedades correspondentes às constantes dessas duas enumerações estarão contidas nas classes de objetos de negociação e de posição. Essas propriedades serão usadas para ordenar as listas na busca de igualdade dos objetos.

Abaixo escreveremos os códigos das classes criadas.


    Classe de negociações

    Vamos ver a classe de negociação por completo:
    //+------------------------------------------------------------------+
    //| 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){}
      };
    
    

    Na seção privada, são declaradas as variáveis-membro da classe para armazenar as propriedades da negociação. Na seção pública, são implementados métodos para definir e retornar as propriedades da negociação. O método virtual Compare() implementa a comparação por cada propriedade da negociação dependendo da propriedade passada ao método como modo de comparação. Se o valor da propriedade verificada do objeto atual for maior que o valor da mesma propriedade do objeto comparado, retorna 1; se menor, retorna -1; caso contrário, retorna 0, indicando igualdade de valores. No construtor da classe, assume-se que a negociação já foi selecionada e suas propriedades são registradas nas variáveis privadas correspondentes da classe. Isso é suficiente para criar um objeto de negociação que armazene todas as propriedades necessárias da negociação selecionada na lista de negociações históricas do terminal. Para cada negociação, será criado um objeto dessa classe; das propriedades do objeto será extraído o identificador da posição, e será criado um objeto da classe de posição histórica. Todas as negociações dessa posição serão adicionadas à lista de negociações da classe de posição. Assim, o objeto de posição histórica conterá a lista de todas as suas negociações. E, a partir delas, será possível extrair o histórico da vida dessa posição.

    Vamos ver a classe de posição histórica.

    Classe de posições

    A classe de posição conterá a lista de negociações dessa posição e métodos auxiliares para calcular valores, obter e retornar informações sobre o tempo de existência da posição:

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

    Na seção privada, são declaradas variáveis-membro da classe para armazenar as propriedades da posição, bem como métodos auxiliares.

    O método BarOpenTime() calcula e retorna o tempo de abertura da barra no timeframe especificado com base em um tempo transmitido ao método:

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

    Por exemplo, sabemos um tempo específico e queremos agora saber o tempo de abertura da barra do período gráfico de interesse, dentro do qual está esse tempo. O método retornará o tempo de abertura da barra calculada. Assim, sempre podemos saber se a posição existia em determinado momento em uma barra específica do gráfico:

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

    ou simplesmente podemos obter a barra de abertura ou fechamento da posição:

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

    Para calcular o lucro/prejuízo da posição na barra atual do gráfico, usaremos um cálculo aproximado — contaremos o número de pontos de lucro em relação ao preço de abertura da posição e ao preço de fechamento da barra atual. No segundo caso, calcularemos o valor desses pontos de lucro/prejuízo:

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

    Ambos os métodos não fornecerão uma imagem completa do lucro flutuante durante a vida útil da posição, mas isso não é necessário aqui. O mais importante é mostrar como obter dados sobre uma posição fechada. Para um cálculo preciso, precisaríamos de mais dados sobre o estado do mercado em cada barra da vida útil da posição — precisaríamos dos ticks desde a abertura até o fechamento da barra e, a partir deles, modelar o estado da posição nesse intervalo de tempo. E assim para cada barra. Atualmente, isso não é necessário.

    O construtor da classe recebe o identificador da posição, cujo objeto será criado:

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

    O identificador, no momento da criação do objeto da classe, já é conhecido — é obtido das propriedades da negociação. A negociação selecionada, da qual foi obtido o identificador da posição, após a criação do objeto da classe posição, é adicionada à sua lista de negociações. Tudo isso é feito na classe da lista de posições históricas, que veremos a seguir.


    Classe da lista de posições históricas

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

    Na seção privada da classe, é declarada a lista onde serão armazenados os ponteiros para os objetos das posições históricas. Os métodos públicos são, de alguma forma, destinados a trabalhar com essa lista.

    Vamos analisá-los mais detalhadamente.

    O método CreatePositionList() cria a lista de posições históricas:

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

    Num laço pela lista de negociações históricas no terminal, obtemos a negociação atual, pegamos dela o identificador da posição e verificamos se existe na lista de posições históricas um objeto de posição com esse identificador. Se ainda não houver tal posição na lista, criamos um novo objeto de posição e o adicionamos à lista. Se já houver tal posição, obtemos o ponteiro para esse objeto da lista. Em seguida, adicionamos a negociação à lista de negociações da posição histórica. Depois, com base no tipo de negociação, inserimos no objeto posição as propriedades associadas à posição e registradas nas propriedades dessa negociação.

    Assim, percorrendo todas as negociações históricas, criamos uma lista de posições históricas, onde armazenamos, em uma lista, as negociações de cada posição. Ao criar a lista de posições pela lista de negociações, não levamos em conta a alteração no volume das posições em caso de fechamento parcial. Se necessário, tal funcionalidade deve ser adicionada. No momento, isso está fora do escopo deste artigo, pois estamos vendo um exemplo de criação de uma lista de posições históricas, não uma reprodução exata das mudanças nas propriedades das posições durante sua existência.

    Adicionar a funcionalidade descrita deve ser no bloco de código marcado com cor. Ou, seguindo os princípios da POO, fazer o método CreatePositionList() virtual, herdá-lo da classe da lista de posições históricas e, na própria classe, fazer sua própria implementação desse método. Ou pode-se mover a alteração das propriedades da posição pelas propriedades da negociação (bloco de código marcado com cor) para um método virtual protegido e, na classe herdada, sobrescrever esse método protegido, sem reescrever completamente o CreatePositionList().

    O método GetPositionObjByIndex() retorna um ponteiro para o objeto de posição histórica da lista pelo índice passado ao método.
    Retorna um ponteiro para o objeto localizado na lista no índice especificado ou NULL, se o objeto não for encontrado.

    O método GetPositionObjByID() retorna um ponteiro para o objeto de posição histórica da lista pelo identificador da posição passado ao método:

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

    Na lista, procura-se o objeto cujo identificador da posição é igual ao passado ao método, e retorna-se o ponteiro para o objeto encontrado, ou NULL, se o objeto não for encontrado.

    O método DealAdd() adiciona uma negociação à lista de negociações:

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

    O método recebe o identificador da posição à qual a negociação deve ser adicionada, e o ponteiro para a negociação também é passado ao método. Ao adicionar com sucesso a negociação à lista de negociações da posição, o método retorna true; em caso de erro, false.

    O método IsPresentInTime() retorna um indicador de que a posição especificada está presente no tempo especificado:

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

    O método recebe o ponteiro para a posição a ser verificada e o tempo no qual se quer saber se a posição estava presente ou não. Se a posição estava presente no tempo especificado, o método retorna true; caso contrário, false.

    O método ProfitRelativeClosePrice() retorna o lucro da posição em relação ao preço de fechamento.

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

    O método recebe o ponteiro para a posição cujos dados precisam ser obtidos, o preço de fechamento da barra e seu tempo, em relação aos quais o lucro da posição deve ser obtido, e um indicador que especifica em quais unidades os dados devem ser obtidos — em pontos ou no valor desses pontos.

    O método PositionsTotal() retorna o número de posições históricas na lista:

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

    O método Print() imprime no log as propriedades das posições e suas negociações:

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

    No construtor da classe, a lista de posições históricas é definida com o sinalizador de classificação por tempo em milissegundos:

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

    Todas as classes para a implementação do plano proposto foram criadas.

    Agora, escreveremos o código do indicador que criará a lista de todas as posições por símbolo atual, que já existiram na conta, e desenhará no gráfico um diagrama de seu lucro/prejuízo em cada barra dos dados históricos.


    Indicador de diagrama de lucro das posições

    O jeito mais adequado para desenhar o buffer do indicador em forma de diagramas é usar a área colorida entre dois buffers do indicador, DRAW_FILLING.

    O estilo DRAW_FILLING cria uma área colorida entre os valores de dois buffers do indicador. Basicamente, ele desenha duas linhas e preenche o espaço entre elas com uma das duas cores especificadas. É perfeito para criar indicadores que desenham canais. Nenhum dos buffers pode ter apenas valores vazios, pois, nesse caso, a renderização não acontece.

    Podem ser definidas duas cores de preenchimento:

    • a primeira cor para as áreas onde os valores do primeiro buffer do indicador são maiores que os valores do segundo buffer do indicador;
    • a segunda cor para as áreas onde os valores do segundo buffer do indicador são maiores que os valores do primeiro buffer do indicador.

    A cor de preenchimento pode ser definida por diretivas do compilador ou dinamicamente usando a função PlotIndexSetInteger(). A mudança dinâmica das propriedades gráficas permite "dar vida" aos indicadores, para que mudem sua aparência dependendo da situação atual.

    O indicador é calculado para todas as barras cujos valores de ambos os buffers do indicador não são iguais a 0 e não são valores vazios. Para indicar qual valor deve ser considerado "vazio", defina esse valor na propriedade 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);
    

    A renderização nas barras que não participam do cálculo do indicador dependerá dos valores nos buffers do indicador:

    • As barras cujos valores de ambos os buffers do indicador são iguais a 0 não participam da renderização do indicador. Ou seja, a área com valores zero não será preenchida.


    • As barras cujos valores dos buffers do indicador são iguais ao "valor vazio" participam da renderização do indicador. A área com valores vazios será preenchida de forma a conectar áreas com valores significativos.


    É importante notar que se o "valor vazio" for igual a zero, as barras que não participam do cálculo do indicador também serão preenchidas.

    O número de buffers necessários para construir DRAW_FILLING é 2.

    Adicionaremos um parâmetro de entrada para selecionar em quais unidades retornar o lucro da posição na barra, declararemos um ponteiro para a instância da classe da lista de posições históricas e adicionaremos um pouco no manipulador 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);
      }
    
    

    Escreveremos o manipulador OnDeinit(), no qual excluiremos a instância criada da classe da lista de posições históricas e limparemos os comentários no gráfico:

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

    No manipulador OnCalculate(), no primeiro tick criaremos a lista de todas as posições históricas e, no laço principal, acessaremos a lista criada usando a função de cálculo de lucro na barra atual do laço do indicador:

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

    Aqui é realizado um ciclo indicador comum desde o início dos dados históricos até a data atual. No ciclo, para cada barra, é chamada a função de cálculo de lucro das posições históricas na barra atual do ciclo:

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

    A função recebe o preço (fechamento da barra) em relação ao qual o número de pontos de lucro da posição deve ser obtido, e o tempo em que se verifica a existência da posição (tempo de abertura da barra). Em seguida, o lucro de todas as posições, obtido de cada objeto de posições históricas, é somado e retornado.

    Após a compilação, o indicador pode ser executado no gráfico de um símbolo onde houve muitas posições abertas, e ele desenhará um diagrama de lucro de todas as posições históricas:



    Considerações finais

    Vimos um exemplo de como restaurar uma posição a partir de negociações e compilar uma lista de todas as posições históricas que já existiram na conta. O exemplo é simples e não abrange todos os aspectos da restauração precisa de posições a partir de negociações, mas é suficiente para que você possa complementar por conta própria até a precisão necessária na restauração das posições.


    Traduzido do russo pela MetaQuotes Ltd.
    Artigo original: https://www.mql5.com/ru/articles/13911

    Arquivos anexados |
    Ciência de dados e aprendizado de máquina (Parte 17): O dinheiro cresce em árvores? Florestas aleatórias no trading de forex Ciência de dados e aprendizado de máquina (Parte 17): O dinheiro cresce em árvores? Florestas aleatórias no trading de forex
    Neste artigo, vamos desvendar os segredos da alquimia algorítmica, explorando a arte e precisão dos mercados financeiros. Você vai ver como as florestas aleatórias transformam dados em previsões e ajudam a navegar nas complexidades do mercado financeiro. Vamos entender o papel das florestas aleatórias com dados financeiros e ver se elas podem ajudar a aumentar os lucros.
    Funcionalidades do assistente MQL5 que você precisa conhecer (Parte 09): Combinação de agrupamento k-médias com ondas fractais Funcionalidades do assistente MQL5 que você precisa conhecer (Parte 09): Combinação de agrupamento k-médias com ondas fractais
    O agrupamento k-médias é uma abordagem para agrupar pontos de dados em um processo que inicialmente se concentra na representação macro do conjunto de dados, onde são aplicados centroides de cluster criados aleatoriamente. Com o tempo, esses centroides são ajustados e escalonados para representar melhor o conjunto de dados. Este artigo examina essa abordagem de agrupamento e algumas de suas aplicações.
    Padrões de projeto no MQL5 (Parte 4): Padrões comportamentais 2 Padrões de projeto no MQL5 (Parte 4): Padrões comportamentais 2
    Com este artigo concluímos a série sobre padrões de projeto na área de software. Já mencionei que existem três tipos de padrões de projeto: criacionais, estruturais e comportamentais. Finalizaremos os padrões comportamentais restantes, que ajudarão a definir a maneira de interação entre objetos, de modo a tornar nosso código mais limpo.
    Redes neurais de maneira fácil (Parte 68): Otimização off-line de políticas baseada em preferências Redes neurais de maneira fácil (Parte 68): Otimização off-line de políticas baseada em preferências
    Desde os primeiros artigos sobre aprendizado por reforço, a gente sempre falou de duas coisas: como explorar o ambiente e definir a função de recompensa. Os artigos mais recentes foram dedicados à exploração durante o aprendizado off-line. Neste aqui, quero apresentar a você um algoritmo em que os autores resolveram deixar de lado a função de recompensa.