Anzeige historischer Positionen auf dem Chart als deren Gewinn/Verlust-Diagramm
Inhalt
- Einführung
- Die Klasse Deal
- Die Klasse Position
- Die Klasse der historischen Positionen
- Indikator für die Position des Gewinndiagramms
- Schlussfolgerung
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:
- Die Klasse Deal. Sie verfügt über die zur Identifizierung einer Position und ihrer Eigenschaften erforderlichen Eigenschaften.
- 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.
- 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
- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.