English Русский 日本語
preview
Anzeige historischer Positionen auf dem Chart als deren Gewinn/Verlust-Diagramm

Anzeige historischer Positionen auf dem Chart als deren Gewinn/Verlust-Diagramm

MetaTrader 5Beispiele | 24 April 2024, 13:37
239 0
Artyom Trishkin
Artyom Trishkin

Inhalt


Einführung

Position... Eine Position ist eine Konsequenz der Ausführung eines an den Server gesendeten Handelsauftrags: Auftrag --> Deal --> Position.

Wir können immer eine Liste der offenen Positionen in unserem Programm erhalten, indem wir die Funktionen PositionSelect() bei Netting-Positionen verwenden, während wir den Namen eines Symbols angeben, für das es eine offene Position gibt:

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

Für Konten mit unabhängiger Positionsdarstellung (Hedge) müssen wir zunächst die Anzahl der Positionen mit PositionsTotal() ermitteln. Dann müssen wir in der Schleife nach der Anzahl der Positionen das Positionsticket nach seinem Index in der Liste der offenen Positionen mit PositionGetTicket() holen. Danach wählen wir eine Position anhand des erhaltenen Tickets mit PositionSelectByTicket() aus:

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

Hier ist alles einfach und klar. Ganz anders sieht es aus, wenn wir etwas über eine bereits geschlossene Position herausfinden wollen - es gibt keine Funktionen für die Arbeit mit historischen Positionen...

In diesem Fall müssen wir uns daran erinnern und wissen, dass jede Position ihre eigene eindeutige ID hat. Diese ID wird durch die Deals festgelegt, die die Position beeinflusst haben - die zu ihrer Eröffnung, zu Änderungen oder Schließung geführt haben. Mit den Funktionen HistorySelect(), HistoryDealsTotal() und HistoryDealGetTicket()(HistoryDealSelect()) können wir die Liste der Handelsgeschäfte (Deals, einschließlich des Handelsgeschäfts und der ID der Position, an der der Deal beteiligt war) abrufen.

Im Allgemeinen kann die ID der Position, an der der Deal beteiligt war, wie folgt ermittelt werden:

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

Mit anderen Worten, wenn wir eine Liste von Deals haben, können wir immer herausfinden, welche Position zu einem bestimmten Deal gehörte. Zum Beispiel können wir eine Positions-ID ermitteln und die Deals mit derselben ID betrachten, um Markteintritts- und -austrittsDeals zu finden. Anhand der Eigenschaften dieser Deals können wir die Zeit, das Los und andere notwendige Eigenschaften der gewünschten historischen Position herausfinden.

Um nach geschlossenen historischen Positionen zu suchen, erhalten wir eine Liste der historischen Deals. In der Schleife dieser Liste werden wir jeden nächsten Deal abrufen und die Positions-ID prüfen, an der der Deal teilgenommen hat. Wenn eine solche Position noch nicht in der Liste vorhanden ist, erstellen wir ein Positionsobjekt, und wenn sie bereits existiert, verwenden wir eine vorhandene Position mit derselben ID. Fügt den aktuellen Deal (sofern es nicht bereits in der Liste enthalten ist) zur Liste der Deals der gefundenen Position hinzu. Nachdem wir also die gesamte Liste der historischen Deals durchgegangen sind, finden wir alle Positionen, an denen Deals teilgenommen haben, erstellen eine Liste aller historischen Positionen und fügen alle Deals, die an dieser Position teilgenommen haben, zu jedem Objekt der historischen Position hinzu (d.h. zu ihrer Liste der Deals).

Wir brauchen drei Klassen:

  1. Die Klasse Deal. Sie verfügt über die zur Identifizierung einer Position und ihrer Eigenschaften erforderlichen Eigenschaften.
  2. Die Klasse Position. Sie verfügt über eine Liste der Deals, die an einer Position teilgenommen haben, sowie die Eigenschaften, die den Positionen eigen sind.
  3. Die Klasse der Liste mit den historische Positionen. Die Liste der ermittelten historischen Positionen mit der Möglichkeit, eine Position auf der Grundlage bestimmter Eigenschaften auszuwählen.

Diese drei kleinen Klassen ermöglichen es, auf einfache Weise alle historischen Deals zu finden, sie in einer Liste zu speichern und dann die Daten dieser Positionen im Indikator zu verwenden, um ein Diagramm der Gewinne/Verluste der Positionen für das ausgewählte Symbol auf dem Konto zu zeichnen.

Wir erstellen im Ordner Indicators eine neue Indikator-Datei mit dem Namen PositionInfoIndicator. Wir erstellen einen Indikator mit einem zeichenbaren Puffer mit einem Füllstil mit grünen und roten Füllfarben, der in einem separaten Chartfenster gezeichnet wird.

Die Indikatorvorlage mit der folgenden Kopfteil wird erstellt:

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

Dann geben wir (s. unten) die die zu erstellenden Klassen ein.

Um Listen von Deals und Positionen zu erstellen, werden wir die Klasse der dynamischen Arrays von Zeigern auf Instanzen der Klasse CObject und ihrer abgeleiteten Klassen der Standardbibliothek verwenden.

Wir binden die Datei der Klasse in die erstellte Datei ein:

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

Die Klasse enthält die Methode Search(), um in einem sortierten Array nach den Elementen zu suchen, die dem Beispiel entsprechen:

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

Das Array sollte nach einer Eigenschaft der Objekte sortiert sein, auf die es Zeiger enthält. Der Sortiermodus wird durch Übergabe einer Ganzzahl an die Variable m_sort_mode festgelegt. Standardmäßig wird der Wert 0 verwendet. Der Wert -1 bedeutet, dass das Array nicht sortiert ist. Um nach verschiedenen Eigenschaften zu sortieren, müssen wir verschiedene Sortiermodi festlegen, indem wir die Variable m_sort_mode auf Werte von Null und höher setzen. Zu diesem Zweck ist es sinnvoll, Enumerationen zu verwenden, die verschiedene Sortiermodi für Listen definieren. Diese Modi werden in der virtuellen Methode des Vergleichs zweier Compare()-Objekte verwendet. Sie ist in der Klasse CObject definiert und gibt den Wert 0 zurück, was bedeutet, dass die verglichenen Objekte identisch sind:

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

Diese Methode sollte in nutzerdefinierten Klassen überschrieben werden, in denen Sie einen Vergleich von zwei Objekten desselben Typs anhand ihrer verschiedenen Eigenschaften durchführen können (wenn der Wert von m_sort_mode gleich 0 ist, werden die Objekte anhand einer Eigenschaft verglichen, wenn er gleich 1 ist - anhand einer anderen Eigenschaft, gleich 2 - anhand einer dritten, usw.).

Jedes hier erstellte Klassenobjekt hat seinen eigenen Satz von Eigenschaften. Die Objekte sollten nach diesen Eigenschaften sortiert werden, um eine Suche durchführen zu können. Daher müssen wir zunächst Enumerationen von Objekteigenschaften erstellen:

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

Die Eigenschaften, die den Konstanten dieser beiden Enumerationen entsprechen, werden in den Objektklassen deal und position enthalten sein. Es wird möglich sein, Listen nach diesen Eigenschaften zu sortieren, um die Gleichheit von Objekten festzustellen.

Im Folgenden geben wir die Codes der erstellten Klassen ein.


    Die Klasse Deal

    Betrachten wir die Deal-Klasse als Ganzes:
    //+------------------------------------------------------------------+
    //| 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){}
      };
    
    

    Hier ist alles ganz einfach: Die Klassenvariablen zum Speichern von Dealeigenschaften werden im privaten Abschnitt deklariert. Der öffentliche Teil implementiert Methoden zum Setzen und Zurückgeben von Eigenschaften des Deals. Die virtuelle Methode Compare() führt den Vergleich für jede Dealeigenschaft durch, je nachdem, welche Eigenschaft als Vergleichsmodus an die Methode übergeben wird. Ist der Wert der zu prüfenden Eigenschaft des aktuellen Objekts größer als der Wert derselben Eigenschaft des zu vergleichenden Objekts, wird 1 zurückgegeben, ist er kleiner, -1. Wenn die Werte gleich sind, erhalten wir 0. Im Klassenkonstruktor wird davon ausgegangen, dass der Deal bereits ausgewählt wurde, und seine Eigenschaften werden in die entsprechenden privaten Klassenvariablen geschrieben. Dies reicht bereits aus, um ein Deal-Objekt zu erstellen, das alle erforderlichen Eigenschaften des ausgewählten Deals aus der Liste der historischen Deals des Terminals speichert. Das Klassenobjekt wird für jeden Deal erstellt. Die Positions-ID wird aus den Objekteigenschaften extrahiert und ein Objekt der historischen Positionsklasse wird erstellt. Alle Deals, die zu dieser Position gehören, werden in der Liste der Deals der Positionsklasse aufgeführt. Das Objekt der historischen Position wird also eine Liste aller Deals enthalten. Es wird möglich sein, die Positionshistorie aus ihnen zu extrahieren.

    Betrachten wir die historische Positionsklasse.

    Die Klasse Position

    Die Positionsklasse enthält eine Liste von Deals für diese Position und Hilfsmethoden zur Berechnung von Werten, die Informationen über die Lebensdauer der Position erhalten und zurückgeben:

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

    Im privaten Abschnitt werden Klassenvariablen zur Speicherung von Positionseigenschaften deklariert und Hilfsmethoden geschrieben.

    Die Methode BarOpenTime() berechnet und liefert die Eröffnungszeit eines Balkens in einem bestimmten Zeitrahmen auf der Grundlage einer bestimmten Zeit, die der Methode übergeben wird:

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

    Wir kennen z.B. eine Uhrzeit und wollen nun die Eröffnungszeit des Balkens in einer bestimmten Diagrammperiode herausfinden, in der sich diese Uhrzeit befindet. Die Methode gibt die Öffnungszeit des berechneten Balkens zurück. Auf diese Weise können wir immer herausfinden, ob eine Position zu einem bestimmten Zeitpunkt auf einem bestimmten Balken des Diagramms vorhanden war:

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

    oder wir können einfach den offenen oder geschlossenen Balken der Position abrufen:

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

    Um den Gewinn/Verlust einer Position auf dem aktuellen Chart-Balken zu berechnen, werden wir ein vereinfachtes, ungefähres Berechnungsprinzip anwenden - in einem Fall werden wir die Anzahl der Gewinnpunkte im Verhältnis zum Eröffnungskurs der Position und zum Schlusskurs des aktuellen Balkens berücksichtigen. Im zweiten Fall berechnen wir die Kosten für diese Gewinn-/Verlust in Punkten:

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

    Beide Methoden geben kein vollständiges Bild des gleitenden Gewinns während der Laufzeit der Position, aber das ist hier auch nicht notwendig. Hier ist es für uns wichtiger zu zeigen, wie man Daten über eine geschlossene Position erhält. Für eine genaue Berechnung benötigen wir sehr viel mehr unterschiedliche Daten über den Zustand des Marktes auf jedem Balken während der Laufzeit der Position - wir müssen die Ticks vom Eröffnungszeitpunkt bis zum Schließzeitpunkt des Balkens erhalten und diese verwenden, um den Zustand der Position in diesem Zeitraum zu modellieren. Dann sollten wir dasselbe für jeden Balken tun. Zurzeit gibt es keine solche Aufgabe.

    Die ID der Position, deren Objekt erstellt werden soll, wird an den Klassenkonstruktor übergeben:

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

    Die ID ist bereits zum Zeitpunkt der Erstellung des Klassenobjekts bekannt. Wir erhalten sie aus den Eigenschaften der Transaktion. Nachdem wir das Objekt der Positionsklasse erstellt haben, fügen wir den ausgewählten Deal, von dem wir die Positions-ID erhalten haben, zu seiner Liste der Deals hinzu. All dies geschieht in der Klasse der historischen Positionsliste, auf die wir noch näher eingehen werden.


    Die Klasse der historischen Positionen

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

    Wir deklarieren im privaten Abschnitt der Klasse eine Liste, in der die Zeiger auf die Objekte der historischen Positionen gespeichert werden. Öffentliche Methoden sind in gewissem Maße für die Arbeit mit dieser Liste ausgelegt.

    Betrachten wir das genauer.

    Die Methode CreatePositionList() erstellt die historische Positionsliste:

    //+------------------------------------------------------------------+
    //| CHistoryPosition::Create historical position list                |
    //+------------------------------------------------------------------+
    bool CHistoryPosition::CreatePositionList(const string symbol=NULL)
      {
    //--- If failed to request the history of deals and orders, return 'false'
       if(!::HistorySelect(0,::TimeCurrent()))
          return false;
    //--- Declare a result variable and a pointer to the position object
       bool res=true;
       CPosition *pos=NULL;
    //--- In a loop based on the number of history deals
       int total=::HistoryDealsTotal();
       for(int i=0;i<total;i++)
         {
          //--- get the ticket of the next deal in the list
          ulong ticket=::HistoryDealGetTicket(i);
          //--- If the deal ticket has not been received, or this is a balance deal, or if the deal symbol is specified, but the deal has been performed on another symbol, move on 
          if(ticket==0 || ::HistoryDealGetInteger(ticket,DEAL_TYPE)==DEAL_TYPE_BALANCE || (symbol!=NULL && symbol!="" && ::HistoryDealGetString(ticket,DEAL_SYMBOL)!=symbol))
             continue;
          //--- Create a deal object and, if the object could not be created, add 'false' to the 'res' variable and move on
          CDeal *deal=new CDeal(ticket);
          if(deal==NULL)
            {
             res &=false;
             continue;
            }
          //--- Get the value of the position ID from the deal 
          long pos_id=deal.PositionID();
          //--- Get the pointer to a position object from the list
          pos=this.GetPositionObjByID(pos_id);
          //--- If such a position is not yet in the list,
          if(pos==NULL)
            {
             //--- create a new position object. If failed to do that, add 'false' to the 'res' variable, remove the deal object and move on
             pos=new CPosition(pos_id);
             if(pos==NULL)
               {
                res &=false;
                delete deal;
                continue;
               }
             //--- Set the sorting by time flag in milliseconds to the list of positions and add the position object to the corresponding place in the list
             this.m_list_pos.Sort(POS_SORT_MODE_TIME_IN_MSC);
             //--- If failed to add the position object to the list, add 'false' to the 'res' variable, remove the deal and position objects and move on
             if(!this.m_list_pos.InsertSort(pos))
               {
                res &=false;
                delete deal;
                delete pos;
                continue;
               }
            }
          //--- If a position object is created and added to the list
          //--- If the deal object could not be added to the list of deals of the position object, add 'false' to the 'res' variable, remove the deal object and move on
          if(!pos.DealAdd(deal))
            {
             res &=false;
             delete deal;
             continue;
            }
          //--- All is successful.
          //--- Set position properties depending on the deal type
          if(deal.Entry()==DEAL_ENTRY_IN)
            {
             pos.SetSymbol(deal.Symbol());
             pos.SetDealIn(deal.Ticket());
             pos.SetTimeIn(deal.Time());
             pos.SetTimeInMsc(deal.TimeMsc());
             ENUM_POSITION_TYPE type=ENUM_POSITION_TYPE(deal.TypeDeal()==DEAL_TYPE_BUY ? POSITION_TYPE_BUY : deal.TypeDeal()==DEAL_TYPE_SELL ? POSITION_TYPE_SELL : WRONG_VALUE);
             pos.SetType(type);
             pos.SetPriceIn(deal.Price());
             pos.SetVolume(deal.Volume());
            }
          if(deal.Entry()==DEAL_ENTRY_OUT || deal.Entry()==DEAL_ENTRY_OUT_BY)
            {
             pos.SetDealOut(deal.Ticket());
             pos.SetTimeOut(deal.Time());
             pos.SetTimeOutMsc(deal.TimeMsc());
             pos.SetPriceOut(deal.Price());
            }
          if(deal.Entry()==DEAL_ENTRY_INOUT)
            {
             ENUM_POSITION_TYPE type=ENUM_POSITION_TYPE(deal.TypeDeal()==DEAL_TYPE_BUY ? POSITION_TYPE_BUY : deal.TypeDeal()==DEAL_TYPE_SELL ? POSITION_TYPE_SELL : WRONG_VALUE);
             pos.SetType(type);
             pos.SetVolume(deal.Volume()-pos.Volume());
            }
         }
    //--- Return the result of creating and adding a position to the list
       return res;
      }
    
    

    In einer Schleife durch die Liste der historischen Deals im Terminal den nächsten Deal holen, die Positions-ID daraus abrufen und prüfen, ob ein Positionsobjekt mit einer solchen ID in der historischen Positionsliste existiert. Wenn es noch keine solche Position in der Liste gibt, erstellen wir ein neues Positionsobjekt und fügen es der Liste hinzu. Wenn eine solche Position bereits existiert, holen wir uns einen Zeiger auf dieses Objekt aus der Liste. Als Nächstes fügen wir den Deal der Liste der Deals der historischen Position hinzu. Dann geben wir auf der Grundlage einer Deal-Typ die Eigenschaften, die der Position eigen sind und in den Deal-Eigenschaften festgehalten werden, in das Positionsobjekt ein.

    Während wir also in einer Schleife alle historischen Deals durchgehen, erstellen wir eine Liste historischer Positionen, in der die zu jeder Position gehörenden Deals gespeichert werden. Bei der Erstellung einer Liste von Positionen auf der Grundlage einer Liste von Deals wird die Änderung des Volumens von teilweise geschlossenen Positionen nicht berücksichtigt. Wir sollten diese Funktionsweise bei Bedarf hinzufügen. Dies würde den Rahmen dieses Artikels sprengen, da es sich hier um ein Beispiel für die Erstellung einer Liste historischer Positionen handelt und nicht um eine exakte Wiedergabe der Veränderungen der Eigenschaften von Positionen während ihrer Lebensdauer.

    Wir müssen die beschriebene Funktionsweise in den farbig markierten Codeblock einfügen. Wenn wir die Grundsätze der OOP befolgen, können wir alternativ die Methode CreatePositionList() virtuell machen, von der Klasse der Liste der historischen Positionen erben und unsere eigene Implementierung dieser Methode in unserer eigenen Klasse vornehmen. Oder wir können das Ändern der Positionseigenschaften durch die Deal-Eigenschaften (farblich markierter Codeblock) in eine geschützte virtuelle Methode verschieben und diese geschützte Methode in der geerbten Klasse überschreiben, ohne CreatePositionList() komplett neu zu schreiben.

    Die Methode GetPositionObjByIndex() gibt den Zeiger auf ein historisches Positionsobjekt aus der Liste mit dem der Methode übergebenen Index zurück.
    Wir erhalten den Zeiger auf das Objekt, das sich mit dem angegebenen Index in der Liste befindet, oder NULL, wenn das Objekt nicht gefunden wird.

    Die Methode GetPositionObjByID() gibt den Zeiger auf ein historisches Positionsobjekt aus der Liste anhand der der Methode übergebenen Positions-ID zurück:

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

    In der Liste wird nach dem Objekt gesucht, dessen Positions-ID mit der an die Methode übergebenen übereinstimmt. Das Ergebnis ist der Zeiger auf das gefundene Objekt oder NULL, wenn das Objekt nicht gefunden wird.

    Die Methode DealAdd() fügt einen Deal zur Liste der Deals hinzu:

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

    Die Methode erhält die ID der Position, zu der der Deal hinzugefügt werden soll. Der Zeiger auf den Deal wird ebenfalls an die Methode übergeben. Wenn ein Deal erfolgreich zur Liste der Deals einer Position hinzugefügt wurde, gibt die Methode true zurück, andernfalls false.

    Die Methode IsPresentInTime() gibt das Flag des Situation der angegebenen Position zum angegebenen Zeitpunkt zurück:

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

    Die Methode erhält den Zeiger auf die zu prüfende Position und die Zeitspanne, innerhalb derer die Position geprüft werden muss. Wenn die Position zum angegebenen Zeitpunkt existierte, gibt die Methode true zurück, andernfalls, false.

    Die Methode ProfitRelativeClosePrice() gibt den Positionsgewinn relativ zum Schlusskurs zurück.

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

    Die Methode erhält einen Zeiger auf die Position, deren Daten ermittelt werden sollen, den Schlusskurs des Balkens und den Zeitpunkt, zu der der Gewinn der Position ermittelt werden soll, sowie ein Flag, das angibt, in welchen Einheiten die Daten ermittelt werden sollen - in der Anzahl der Punkte oder im Wert dieser Anzahl von Punkten.

    Die Methode PositionsTotal() gibt die Anzahl der historischen Positionen in der Liste zurück:

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

    Die Methode Print() druckt die Eigenschaften der Positionen und ihre Deals im Journal aus:

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

    Wir setzen das Flag für die Sortierung nach Zeit in Millisekunden für die Liste der historischen Positionen im Klassenkonstruktor:

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

    Wir haben alle Klassen für die Umsetzung des Plans geschaffen.

    Schreiben wir nun einen Indikatorcode, der eine Liste aller Positionen für das aktuelle Symbol erstellt, die jemals auf dem Konto bestanden haben, und ein Diagramm ihrer Gewinne/Verluste für jedem Balken der historischen Daten zeichnet.


    Indikator für die Position des Gewinndiagramms

    Der am besten geeignete Zeichenstil der Indikatorpuffer für die Darstellung als Diagramm ist eine farbige Fläche zwischen zwei Indikatorpuffern, DRAW_FILLING.

    Der Stil DRAW_FILLING zeichnet einen farbigen Bereich zwischen den Werten von zwei Indikatorpuffern. Tatsächlich zeichnet dieser Stil zwei Linien und füllt den Raum zwischen ihnen mit einer der beiden angegebenen Farben. Er wird für die Erstellung von Indikatoren verwendet, die Kanäle zeichnen. Keiner der Puffer darf nur Nullwerte enthalten, da in diesem Fall nichts gezeichnet wird.

    Wir können zwei Füllfarben einstellen:

    • die erste Farbe wird für die Bereiche verwendet, in denen die Werte im ersten Puffer größer sind als die Werte im zweiten Indikatorpuffer;
    • die zweite Farbe wird für die Bereiche verwendet, in denen die Werte im zweiten Puffer größer sind als die Werte im ersten Indikatorpuffer.

    Die Füllfarbe kann mit Hilfe der Compiler-Direktiven oder dynamisch mit der Funktion PlotIndexSetInteger() festgelegt werden. Dynamische Änderungen der Plot-Eigenschaften ermöglichen es, Indikatoren zu „beleben“, sodass sich ihr Aussehen je nach aktueller Situation ändert.

    Der Indikator wird für alle Balken berechnet, bei denen die Werte der beiden Indikatorpuffer weder gleich 0 noch gleich dem Leerwert sind. Um festzulegen, welcher Wert als „leer“ betrachtet werden soll, legen wir diesen Wert in der Eigenschaft PLOT_EMPTY_VALUE fest:

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

    Die Zeichnung der Balken, die nicht an der Berechnung des Indikators teilnehmen, hängt von den Werten​ in den Indikatorpuffern ab:

    • Balken, für die die Werte ​der beiden Indikatorpuffer gleich 0 sind, nehmen nicht an der Zeichnung des Indikators teil. Das bedeutet, dass der Bereich mit Nullwerten nicht ausgefüllt ist.


    • Balken, für die die Werte ​des Indikatorpuffers gleich „dem leeren Wert“ (empty value) sind, an der Zeichnung des Indikators teilnehmen. Der Bereich mit leeren Werten wird ausgefüllt, um die Bereiche mit signifikanten Werten zu verbinden.


    Es ist zu beachten, dass, wenn der „leere Wert“ gleich Null ist, die Balken, die nicht an der Berechnung des Indikators teilnehmen, ebenfalls ausgefüllt werden.

    Die Anzahl der für das Plotten von DRAW_FILLING erforderlichen Puffer beträgt 2.

    Fügen wir eine Eingabe hinzu, um die Maßeinheiten für den Gewinn der Balkenposition auszuwählen. Wir deklarieren den Zeiger auf die Instanz der historischen Positionslistenklasse und ergänzen OnInit() ein wenig:

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

    In OnDeinit() löschen wir die erstellte Instanz der Klasse der Liste mit den historischen Positionen und die Kommentare auf dem Chart:

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

    In OnCalculate(), d.h. beim ersten Tick, erstellen wir eine Liste aller historischen Positionen und weiter in der Hauptschleife verwenden wir die erstellte Liste mittels der Funktion zur Berechnung des Gewinns für den aktuellen Balken der Indikatorschleife:

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

    Hier ist eine reguläre Indikatorschleife ab Beginn der historischen Daten bis zum aktuellen Datum angeordnet. In der Schleife über jeden Balken rufen wir die Funktion auf, um den Gewinn der historischen Positionen für den aktuellen Balken der Schleife zu berechnen:

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

    Die Funktion erhält den Preis (Schlusskurs des Balkens) in Bezug auf den die Anzahl der Gewinnpunkte der Position und den Zeitpunkt, zu dem das Vorhandensein der Position geprüft wird (Eröffnungszeit des Balkens). Anschließend wird der Gewinn aller Positionen, die von jedem Objekt der historischen Positionen erhalten wurden, summiert und zurückgegeben.

    Nach der Kompilierung können wir den Indikator auf dem Chart eines Symbols laufen lassen, auf dem es viele offene Positionen gab, und er wird ein Gewinndiagramm aller historischen Positionen zeichnen:



    Schlussfolgerung

    Wir haben die Möglichkeit besprochen, Positionen aus ihren Deals wiederherzustellen und eine Liste aller historischen Positionen, die jemals auf einem Konto vorhanden waren, zu erstellen. Das Beispiel ist einfach und deckt nicht alle Aspekte der genauen Wiederherstellung einer Position aus Deals ab, aber es ist völlig ausreichend, um es unabhängig auf die erforderliche Genauigkeit der Positionswiederherstellung zu ergänzen.


    Übersetzt aus dem Russischen von MetaQuotes Ltd.
    Originalartikel: https://www.mql5.com/ru/articles/13911

    Beigefügte Dateien |
    Datenwissenschaft und maschinelles Lernen (Teil 18): Der Kampf um die Beherrschung der Marktkomplexität, verkürzte SVD versus NMF Datenwissenschaft und maschinelles Lernen (Teil 18): Der Kampf um die Beherrschung der Marktkomplexität, verkürzte SVD versus NMF
    Die verkürzte Singulärwertzerlegung (Truncated Singular Value Decomposition, SVD) und die nicht-negative Matrixzerlegung (Non-Negative Matrix Factorization, NMF) sind Verfahren zur Dimensionsreduktion. Beide spielen eine wichtige Rolle bei der Entwicklung von datengesteuerten Handelsstrategien. Entdecken Sie die Kunst der Dimensionalitätsreduzierung, der Entschlüsselung von Erkenntnissen und der Optimierung quantitativer Analysen für einen fundierten Ansatz zur Navigation durch die Feinheiten der Finanzmärkte.
    Farbpuffer in Multi-Symbol-Multi-Perioden-Indikatoren Farbpuffer in Multi-Symbol-Multi-Perioden-Indikatoren
    In diesem Artikel werden wir die Struktur des Indikatorpuffers bei Multi-Symbol- und Multi-Perioden-Indikatoren untersuchen und die Darstellung farbiger Puffer dieser Indikatoren auf dem Chart organisieren.
    Algorithmischer Handel mit MetaTrader 5 und R für Einsteiger Algorithmischer Handel mit MetaTrader 5 und R für Einsteiger
    Begeben wir uns auf eine fesselnde Entdeckungsreise, bei der Finanzanalyse und algorithmischer Handel aufeinandertreffen, während wir die Kunst der nahtlosen Verbindung von R und MetaTrader 5 enträtseln. Dieser Artikel ist Ihr Leitfaden für den Brückenschlag zwischen den analytischen Finessen von R und den beeindruckenden Handelsmöglichkeiten von MetaTrader 5.
    Implementierung des Augmented Dickey Fuller-Tests in MQL5 Implementierung des Augmented Dickey Fuller-Tests in MQL5
    In diesem Artikel demonstrieren wir die Implementierung des Augmented Dickey-Fuller-Tests und wenden ihn zur Durchführung von Kointegrationstests mit der Engle-Granger-Methode an.