Bibliothek für ein leichtes und schnelles Entwickeln vom Programmen für den MetaTrader (Teil X): Kompatibilität mit MQL4 - Ereignisse der Positionseröffnung und der Aktivierung von Pending-Orders
Inhalt
Test-EA
Im vorigen Artikel haben wir Fehler in den Bibliotheksdateien entfernt, die sich auf die Unterschiede zwischen MQL4 und MQL5 beziehen, und eine Kollektion von MQL4 historischen Orders und Positionen vorgestellt. In diesem Artikel werden wir die Zusammenführung von MQL4 und MQL5 in der Bibliothek fortsetzen und die Ereignisse beim Öffnen von Positionen und beim Aktivieren von offenen Orders definieren.
Die Reihenfolge der Verbesserungsschritte wird umgekehrt. Zuvor haben wir die Funktionsweise erklärt, gefolgt von dem Test EA. Nun, um zu verstehen, was verbessert werden muss, müssen wir den Test EA starten und sehen, wo es funktioniert und wo nicht. Die Dinge, die nicht funktionieren, sind diejenigen, die verbessert werden müssen.
Um dies zu erreichen, nehmen wir den Test-EA TestDoEasyPart08.mq5 aus dem achten Teil der Bibliotheksbeschreibung aus dem Ordner \MQL5\Experts\TestDoEasy\Part08 und speichern ihn unter dem Namen TestDoEasyPart10.mq4 im MetaTrader 4 Ordner \MQL4\Experts\TestDoEasy\Part10.
Versuchen wir, ihn zu kompilieren. Dies führt erst einmal zu 34 Kompilierungsfehlern. Fast alle von ihnen stehen im Zusammenhang mit dem Fehlen der Handelsklassen in der Standardbibliothek MQL4:
Kommen wir zum ersten Fehler, der das Fehlen der Include-Datei anzeigt
und beheben ihn — die Datei wird nur unter MQL5 eingebunden:
//+------------------------------------------------------------------+ //| TestDoEasyPart08.mq5 | //| Copyright 2018, MetaQuotes Software Corp. | //| https://mql5.com/de/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2018, MetaQuotes Software Corp." #property link "https://mql5.com/de/users/artmedia70" #property version "1.00" //--- includes #include <DoEasy\Engine.mqh> #ifdef __MQL5__ #include <Trade\Trade.mqh> #endif //--- Enumerationen
Das Kompilieren endet mit 33 Fehlern. Gehen wir erneut zum allerersten Fehler über, der bei der Deklaration des Objekts der Handelsklasse CTrade den fehlenden Typ anzeigt — er ist in MQL4 nicht vorhanden.
Verwenden wir das bedingte Kompilieren wie vorher:
//--- Globale Variablen CEngine engine; #ifdef __MQL5__ CTrade trade; #endif SDataButt butt_data[TOTAL_BUTT];
Kompilieren. Nun ist das 'Handelsobjekt' der Klasse CTrade für MQL4 unbekannt geworden. Beheben wir dies auf ähnliche Weise:
//--- Setzen der Handelsparameter #ifdef __MQL5__ trade.SetDeviationInPoints(slippage); trade.SetExpertMagicNumber(magic_number); trade.SetTypeFillingBySymbol(Symbol()); trade.SetMarginMode(); trade.LogLevel(LOG_LEVEL_NO); #endif //--- return(INIT_SUCCEEDED); }
Ummanteln wir alle trade Objektinstanzen mit der bedingte Kompilierung im gesamten EA-Code mit der Anweisung #else — der MQL4-Code soll dort platziert werden. Verwenden wir den allerersten Fehler des unbekannten Handelstyps aus der früheren Bearbeitungen und Zusammenstellungen:
//--- Wenn die Schaltfläche BUTT_BUY geklickt wurde: Eröffnen einer Kaufposition if(button==EnumToString(BUTT_BUY)) { //--- Abrufen der korrekten Preise von StopLoss und TakeProfit relativ zu StopLevel double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY,0,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY,0,takeprofit); //--- Eröffnen einer Kaufposition #ifdef __MQL5__ trade.Buy(lot,Symbol(),0,sl,tp); #else #endif }
Nachdem wir alle Objektinstanzen von 'trade' in die Direktive zur bedingten Kompilierung aufgenommen haben, erhalten wir einen weiteren Fehler, der angibt, dass der Compiler nicht genau definieren kann, welcher Aufruf einer überladenen Funktion mangels Parameter verwendet werden soll:
Wenn wir uns den Code genau ansehen, wird der Grund für die Verwirrung des Compilers deutlich:
//+------------------------------------------------------------------+ //| Return the flag of a prefixed object presence | //+------------------------------------------------------------------+ bool IsPresentObects(const string object_prefix) { for(int i=ObjectsTotal(0)-1;i>=0;i--) if(StringFind(ObjectName(0,i,0),object_prefix)>WRONG_VALUE) return true; return false; } //+------------------------------------------------------------------+
In MQL5, hat die Funktion nur eine Aufrufform:
int ObjectsTotal( long chart_id, // chart ID int sub_window=-1, // window index int type=-1 // object type );
wobei der erste Parameter eine Chart-ID (0 - der aktuelle) ist,
während in MQL4 die Funktion seit einiger Zeit zwei Aufrufformen hat. Die erste ist die gleiche wie in MQL5:
int ObjectsTotal( long chart_id, // chart ID int sub_window=-1, // window index int type=-1 // object type );
die zweite ist veraltet hat nur einen Parameter:
int ObjectsTotal( int type=EMPTY // object type );
In MQL5 verursacht die Übergabe von 0 als Chart ID an die Funktion (der aktuelle Chart) keine Widersprüche und Zweifel, aber in MQL4 sollte der Compiler die übergebenen Parameter verwenden, um den Aufruftyp zu definieren. In diesem Fall kann er nicht erkennen, ob wir die aktuelle Chart-ID (0) übergeben, wodurch das erste Aufrufformular verwendet werden soll (schließlich werden die beiden anderen Parameter auf ihre Standardwerte gesetzt, d.h. wir müssen sie nicht an die Funktion übergeben), oder wir einen Fensterindex (oder einen Objekttyp) übergeben haben, wodurch das zweite Aufrufformular verwendet werden sollte.
Die Lösung hier ist einfach — wir übergeben den Subfensterindex (0 = Hauptdiagrammfenster) als zweiten Parameter:
//+------------------------------------------------------------------+ //| Return the flag of a prefixed object presence | //+------------------------------------------------------------------+ bool IsPresentObects(const string object_prefix) { for(int i=ObjectsTotal(0,0)-1;i>=0;i--) if(StringFind(ObjectName(0,i,0),object_prefix)>WRONG_VALUE) return true; return false; } //+------------------------------------------------------------------+
und
//+------------------------------------------------------------------+ //| Manage button status | //+------------------------------------------------------------------+ void PressButtonsControl(void) { int total=ObjectsTotal(0,0); for(int i=0;i<total;i++) { string obj_name=ObjectName(0,i); if(StringFind(obj_name,prefix+"BUTT_")<0) continue; PressButtonEvents(obj_name); } } //+------------------------------------------------------------------+
Jetzt wird alles fehlerfrei kompiliert. Bevor wir den Test starten, müssen wir bedenken, dass die EA keine MQL4-Handelsfunktionen bietet, da wir sie durch die bedingte Kompilierung aus dem Code ausgeschlossen haben, was bedeutet, dass wir sie hinzufügen müssen.
Da wir den Code für den Tester schreiben, werden wir keine Kontrollen implementieren, die beim Handel auf einem Real/Demokonto erforderlich sind, sondern uns auf minimale Kontrollen beschränken.
Da in der Funktion Order- und Position-Tickets sowie berechnete Preisniveaus übergeben werden, sollten wir lediglich eine Order/Position über ihr Ticket auswählen und Art und Zeit des Schließens überprüfen. Wenn der Typ nicht mit einem Auftrags- oder Positionstyp übereinstimmt, zeigen wir die entsprechende Meldung an und verlassen die Funktion mit einem Fehler. Wenn ein Auftrag entfernt oder eine Position geschlossen wird, zeigen wir die Meldung an und verlassen die Funktion mit einem Fehler. Anschließend rufen wir die Funktion zum Öffnen/Schließen/Ändern auf und geben das Ausführungsergebnis zurück.
Am Ende der Datei DELib.mqh schreiben wir alle notwendigen Funktionen für den MQL4-Tester:
#ifdef __MQL4__ //+------------------------------------------------------------------+ //| MQL4 temporary functions for the tester | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Open Buy position | //+------------------------------------------------------------------+ bool Buy(const double volume,const string symbol=NULL,const ulong magic=0,const double sl=0,const double tp=0,const string comment=NULL,const int deviation=2) { string sym=(symbol==NULL ? Symbol() : symbol); double price=0; ResetLastError(); if(!SymbolInfoDouble(sym,SYMBOL_ASK,price)) { Print(DFUN,TextByLanguage("Не удалось получить цену Ask. Ошибка ","Could not get Ask price. Error "),(string)GetLastError()); return false; } if(!OrderSend(sym,ORDER_TYPE_BUY,volume,price,deviation,sl,tp,comment,(int)magic,0,clrBlue)) { Print(DFUN,TextByLanguage("Не удалось открыть позицию Buy. Ошибка ","Failed to open a Buy position. Error "),(string)GetLastError()); return false; } return true; } //+------------------------------------------------------------------+ //| Set pending BuyLimit order | //+------------------------------------------------------------------+ bool BuyLimit(const double volume,const double price_set,const string symbol=NULL,const ulong magic=0,const double sl=0,const double tp=0,const string comment=NULL,const int deviation=2) { string sym=(symbol==NULL ? Symbol() : symbol); ResetLastError(); if(!OrderSend(sym,ORDER_TYPE_BUY_LIMIT,volume,price_set,deviation,sl,tp,comment,(int)magic,0,clrBlue)) { Print(DFUN,TextByLanguage("Не удалось установить ордер BuyLimit. Ошибка ","Could not place order BuyLimit. Error "),(string)GetLastError()); return false; } return true; } //+------------------------------------------------------------------+ //| Set pending BuyStop order | //+------------------------------------------------------------------+ bool BuyStop(const double volume,const double price_set,const string symbol=NULL,const ulong magic=0,const double sl=0,const double tp=0,const string comment=NULL,const int deviation=2) { string sym=(symbol==NULL ? Symbol() : symbol); ResetLastError(); if(!OrderSend(sym,ORDER_TYPE_BUY_STOP,volume,price_set,deviation,sl,tp,comment,(int)magic,0,clrBlue)) { Print(DFUN,TextByLanguage("Не удалось установить ордер BuyStop. Ошибка ","Could not place order BuyStop. Error "),(string)GetLastError()); return false; } return true; } //+------------------------------------------------------------------+ //| Open Sell position | //+------------------------------------------------------------------+ bool Sell(const double volume,const string symbol=NULL,const ulong magic=0,const double sl=0,const double tp=0,const string comment=NULL,const int deviation=2) { string sym=(symbol==NULL ? Symbol() : symbol); double price=0; ResetLastError(); if(!SymbolInfoDouble(sym,SYMBOL_BID,price)) { Print(DFUN,TextByLanguage("Не удалось получить цену Bid. Ошибка ","Could not get Bid price. Error "),(string)GetLastError()); return false; } if(!OrderSend(sym,ORDER_TYPE_SELL,volume,price,deviation,sl,tp,comment,(int)magic,0,clrRed)) { Print(DFUN,TextByLanguage("Не удалось открыть позицию Sell. Ошибка ","Failed to open a Sell position. Error "),(string)GetLastError()); return false; } return true; } //+------------------------------------------------------------------+ //| Set pending SellLimit order | //+------------------------------------------------------------------+ bool SellLimit(const double volume,const double price_set,const string symbol=NULL,const ulong magic=0,const double sl=0,const double tp=0,const string comment=NULL,const int deviation=2) { string sym=(symbol==NULL ? Symbol() : symbol); ResetLastError(); if(!OrderSend(sym,ORDER_TYPE_SELL_LIMIT,volume,price_set,deviation,sl,tp,comment,(int)magic,0,clrRed)) { Print(DFUN,TextByLanguage("Не удалось установить ордер SellLimit. Ошибка ","Could not place order SellLimit. Error "),(string)GetLastError()); return false; } return true; } //+------------------------------------------------------------------+ //| Set pending SellStop order | //+------------------------------------------------------------------+ bool SellStop(const double volume,const double price_set,const string symbol=NULL,const ulong magic=0,const double sl=0,const double tp=0,const string comment=NULL,const int deviation=2) { string sym=(symbol==NULL ? Symbol() : symbol); ResetLastError(); if(!OrderSend(sym,ORDER_TYPE_SELL_STOP,volume,price_set,deviation,sl,tp,comment,(int)magic,0,clrRed)) { Print(DFUN,TextByLanguage("Не удалось установить ордер SellStop. Ошибка ","Could not place order SellStop. Error "),(string)GetLastError()); return false; } return true; } //+------------------------------------------------------------------+ //| Close position by ticket | //+------------------------------------------------------------------+ bool PositionClose(const ulong ticket,const double volume=0,const int deviation=2) { ResetLastError(); if(!OrderSelect((int)ticket,SELECT_BY_TICKET)) { Print(DFUN,TextByLanguage("Не удалось выбрать позицию. Ошибка ","Could not select position. Error "),(string)GetLastError()); return false; } if(OrderCloseTime()>0) { Print(DFUN,TextByLanguage("Позиция уже закрыта","Position already closed")); return false; } ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderType(); if(type>ORDER_TYPE_SELL) { Print(DFUN,TextByLanguage("Ошибка. Не позиция: ","Error. Not position: "),OrderTypeDescription(type)," #",ticket); return false; } double price=0; color clr=clrNONE; if(type==ORDER_TYPE_BUY) { price=SymbolInfoDouble(OrderSymbol(),SYMBOL_BID); clr=clrBlue; } else { price=SymbolInfoDouble(OrderSymbol(),SYMBOL_ASK); clr=clrRed; } double vol=(volume==0 || volume>OrderLots() ? OrderLots() : volume); ResetLastError(); if(!OrderClose((int)ticket,vol,price,deviation,clr)) { Print(DFUN,TextByLanguage("Не удалось закрыть позицию. Ошибка ","Could not close position. Error "),(string)GetLastError()); return false; } return true; } //+------------------------------------------------------------------+ //| Close position by an opposite one | //+------------------------------------------------------------------+ bool PositionCloseBy(const ulong ticket,const ulong ticket_by) { ResetLastError(); if(!OrderSelect((int)ticket,SELECT_BY_TICKET)) { Print(DFUN,TextByLanguage("Не удалось выбрать позицию. Ошибка ","Could not select position. Error "),(string)GetLastError()); return false; } if(OrderCloseTime()>0) { Print(DFUN,TextByLanguage("Позиция уже закрыта","Position already closed")); return false; } ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderType(); if(type>ORDER_TYPE_SELL) { Print(DFUN,TextByLanguage("Ошибка. Не позиция: ","Error. Not position: "),OrderTypeDescription(type)," #",ticket); return false; } ResetLastError(); if(!OrderSelect((int)ticket_by,SELECT_BY_TICKET)) { Print(DFUN,TextByLanguage("Не удалось выбрать встречную позицию. Ошибка ","Could not select the opposite position. Error "),(string)GetLastError()); return false; } if(OrderCloseTime()>0) { Print(DFUN,TextByLanguage("Встречная позиция уже закрыта","Opposite position already closed")); return false; } ENUM_ORDER_TYPE type_by=(ENUM_ORDER_TYPE)OrderType(); if(type_by>ORDER_TYPE_SELL) { Print(DFUN,TextByLanguage("Ошибка. Встречная позиция не является позицией: ","Error. Opposite position is not a position: "),OrderTypeDescription(type_by)," #",ticket_by); return false; } color clr=(type==ORDER_TYPE_BUY ? clrBlue : clrRed); ResetLastError(); if(!OrderCloseBy((int)ticket,(int)ticket_by,clr)) { Print(DFUN,TextByLanguage("Не удалось закрыть позицию встречной. Ошибка ","Could not close position by opposite position. Error "),(string)GetLastError()); return false; } return true; } //+------------------------------------------------------------------+ //| Remove a pending order by ticket | //+------------------------------------------------------------------+ bool PendingOrderDelete(const ulong ticket) { ResetLastError(); if(!OrderSelect((int)ticket,SELECT_BY_TICKET)) { Print(DFUN,TextByLanguage("Не удалось выбрать ордер. Ошибка ","Could not select order. Error "),(string)GetLastError()); return false; } if(OrderCloseTime()>0) { Print(DFUN,TextByLanguage("Ордер уже удалён","Order already deleted")); return false; } ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderType(); if(type<ORDER_TYPE_SELL || type>ORDER_TYPE_SELL_STOP) { Print(DFUN,TextByLanguage("Ошибка. Не ордер: ","Error. Not order: "),PositionTypeDescription((ENUM_POSITION_TYPE)type)," #",ticket); return false; } color clr=(type<ORDER_TYPE_SELL_LIMIT ? clrBlue : clrRed); ResetLastError(); if(!OrderDelete((int)ticket,clr)) { Print(DFUN,TextByLanguage("Не удалось удалить ордер. Ошибка ","Could not delete order. Error "),(string)GetLastError()); return false; } return true; } //+------------------------------------------------------------------+ //| Modify position by ticket | //+------------------------------------------------------------------+ bool PositionModify(const ulong ticket,const double sl,const double tp) { ResetLastError(); if(!OrderSelect((int)ticket,SELECT_BY_TICKET)) { Print(DFUN,TextByLanguage("Не удалось выбрать позицию. Ошибка ","Could not select position. Error "),(string)GetLastError()); return false; } ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderType(); if(type>ORDER_TYPE_SELL) { Print(DFUN,TextByLanguage("Ошибка. Не позиция: ","Error. Not position: "),OrderTypeDescription(type)," #",ticket); return false; } if(OrderCloseTime()>0) { Print(DFUN,TextByLanguage("Ошибка. Для модификации выбрана закрытая позиция: ","Error. Closed position selected for modification: "),PositionTypeDescription((ENUM_POSITION_TYPE)type)," #",ticket); return false; } color clr=(type==ORDER_TYPE_BUY ? clrBlue : clrRed); ResetLastError(); if(!OrderModify((int)ticket,OrderOpenPrice(),sl,tp,0,clr)) { Print(DFUN,TextByLanguage("Не удалось модифицировать позицию. Ошибка ","Failed to modify position. Error "),(string)GetLastError()); return false; } return true; } //+------------------------------------------------------------------+ //| Modify pending order by ticket | //+------------------------------------------------------------------+ bool PendingOrderModify(const ulong ticket,const double price_set,const double sl,const double tp) { ResetLastError(); if(!OrderSelect((int)ticket,SELECT_BY_TICKET)) { Print(DFUN,TextByLanguage("Не удалось выбрать ордер. Ошибка ","Could not select order. Error "),(string)GetLastError()); return false; } ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderType(); if(type<ORDER_TYPE_SELL || type>ORDER_TYPE_SELL_STOP) { Print(DFUN,TextByLanguage("Ошибка. Не ордер: ","Error. Not order: "),PositionTypeDescription((ENUM_POSITION_TYPE)type)," #",ticket); return false; } if(OrderCloseTime()>0) { Print(DFUN,TextByLanguage("Ошибка. Для модификации выбран удалённый ордер: ","Error. Deleted order selected for modification: "),OrderTypeDescription(type)," #",ticket); return false; } color clr=(type<ORDER_TYPE_SELL_LIMIT ? clrBlue : clrRed); ResetLastError(); if(!OrderModify((int)ticket,price_set,sl,tp,0,clr)) { Print(DFUN,TextByLanguage("Не удалось модифицировать ордер. Ошибка ","Failed to modify order. Error "),(string)GetLastError()); return false; } return true; } //+------------------------------------------------------------------+ #endif
Diese Funktionen sind vorläufig. In Kürze werden wir die vollwertigen Handelsklassen für MQL5 und MQL4 schreiben und diese Funktionen aus der Datei entfernen.
Jetzt müssen wir den Aufruf von neu geschriebenen Funktionen hinzufügen, wo immer wir im EA-Code einen Platz für den Aufruf der MQL4-Handelsfunktionen gelassen haben. Wir drücken Strg+F und geben trade in das Suchfeld ein. So finden wir schnell den Codeteil, in denen die Aufrufe von Trading-MQL4-Funktionen gesetzt werden sollen.
Implementieren wir den Aufruf von MQL4-Handelsfunktionen, wo es notwendig ist, beginnend mit der Funktion PressButtonEvents() zur Behandlung von Tastendruckereignissen bis hinunter zum Ende der Datei. Der Code ist recht umfangreich, während die Auswahl der notwendigen Funktion eindeutig ist. Daher werde ich den Code hier nicht anzeigen. Sie finden ihn in den Dateien, die dem Artikel beigefügt sind. Wir werden uns nur mit dem Drücken von zwei Schaltflächen befassen — die Schaltfläche zum Öffnen einer Kauf-Position und der Schaltfläche zum Platzieren einer BuyLimit Pending-Order:
//+------------------------------------------------------------------+ //| Bearbeiten des Klicks auf Schaltflächen | //+------------------------------------------------------------------+ void PressButtonEvents(const string button_name) { //--- Konvertieren der Namen der Schaltflächen in die Zeichenketten-ID string button=StringSubstr(button_name,StringLen(prefix)); //--- Falls eine Taste gedrückt wurde if(ButtonState(button_name)) { //--- Wenn die Schaltfläche BUTT_BUY geklickt wurde: Eröffnen einer Kaufposition if(button==EnumToString(BUTT_BUY)) { //--- Abrufen der korrekten Preise von StopLoss und TakeProfit relativ zu StopLevel double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY,0,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY,0,takeprofit); //--- Eröffnen einer Kaufposition #ifdef __MQL5__ trade.Buy(lot,Symbol(),0,sl,tp); #else Buy(lot,Symbol(),magic_number,sl,tp); #endif } //--- Falls die Schaltfläche BUTT_BUY_LIMIT geklickt wurde: Setzen von BuyLimit else if(button==EnumToString(BUTT_BUY_LIMIT)) { //--- Abrufen des Preises der korrekten Order-Platzierung relativ zu StopLevel double price_set=CorrectPricePending(Symbol(),ORDER_TYPE_BUY_LIMIT,distance_pending); //--- Abrufen der korrekten Preise von StopLoss und TakeProfit relativ zur Level der Order-Platzierung unter Berücksichtigung von StopLevel double sl=CorrectStopLoss(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,stoploss); double tp=CorrectTakeProfit(Symbol(),ORDER_TYPE_BUY_LIMIT,price_set,takeprofit); //--- Setzen einer BuyLimit-Order #ifdef __MQL5__ trade.BuyLimit(lot,price_set,Symbol(),sl,tp); #else BuyLimit(lot,price_set,Symbol(),magic_number,sl,tp); #endif } //--- Falls die Schaltfläche BUTT_BUY_STOP geklickt wurde: Platzieren von BuyStop
Beim Testen des Bibliothekscodes bemerkte ich etwas Seltsames: Ereignisse, die MQL4 sieht, ohne den Code zu verbessern, werden erst nach einiger Zeit im Journal angezeigt. Nach einigen Recherchen wurde mir klar, dass der Grund im Zähler des Kollektion-Timers liegt, der im Timer CEngine arbeitet. Für den von uns entwickelten Zähler des Kollektion-Timer im dritten Teil der Bibliotheksbeschreibung haben wir beim Erstellen des Bibliotheks-Basisobjekts die minimale Verzögerung von 16 Millisekunden eingestellt. Da wir jedoch nicht mit dem Timer im Tester arbeiten und OnTimer() direkt aus OnTick() aufrufen, um mit Ticks zu arbeiten, wird die Verzögerung von 16 Millisekunden in die Verzögerung von 16 Ticks umgewandelt. Um dies zu beheben, habe ich die Klasse CEngine leicht modifiziert und die Methode eingeführt, die das Tester-Flag zurückgibt und die Arbeit im Tester in OnTimer() behandelt, der wiederum von OnTick() des EA aufgerufen wird, wenn er im Tester arbeitet.
Eine 'private' Klassenvariable und die Methode, die den Variablenwert zurückgibt, wurden erstellt, um Änderungen vorzunehmen:
//+------------------------------------------------------------------+ //| Bibliothek der Basisklasse | //+------------------------------------------------------------------+ class CEngine : public CObject { private: CHistoryCollection m_history; // Kollektion der historischen Aufträge und Deals CMarketCollection m_market; // Kollektion der Marktorder und Deals CEventsCollection m_events; // Kollektion der Ereignisse CArrayObj m_list_counters; // Liste der Timerzähler bool m_first_start; // Flag des Erststarts bool m_is_hedge; // Flag des Hedging-Kontos bool m_is_tester; // Flag of working in the tester bool m_is_market_trade_event; // Flag of an account trading event bool m_is_history_trade_event; // Flag of an account history trading event ENUM_TRADE_EVENT m_acc_trade_event; // Handelsereignis auf dem Konto //--- Return counter index by id public: //--- Rückgabe der Liste aller (1) Positionen, (2) Pending-Order und (3) Marktorders CArrayObj* GetListMarketPosition(void); CArrayObj* GetListMarketPendings(void); CArrayObj* GetListMarketOrders(void); //--- Rückgabe der Liste aller historischen (1) Aufträge, (2) gelöschten Pending-Orders, (3) Deals, (4) Positionen nach deren ID CArrayObj* GetListHistoryOrders(void); CArrayObj* GetListHistoryPendings(void); CArrayObj* GetListDeals(void); CArrayObj* GetListAllOrdersByPosID(const ulong position_id); //--- Rücksetzen des letzten Handelsereignisses void ResetLastTradeEvent(void) { this.m_events.ResetLastTradeEvent(); } //--- Return the (1) last trading event, (2) hedge account flag, (3) flag of working in the tester ENUM_TRADE_EVENT LastTradeEvent(void) const { return this.m_acc_trade_event; } bool IsHedge(void) const { return this.m_is_hedge; } bool IsTester(void) const { return this.m_is_tester; } //--- Erstellen der Timerzählers void CreateCounter(const int id,const ulong frequency,const ulong pause); //--- Timer void OnTimer(void); //--- Constructor/Destructor CEngine(); ~CEngine(); }; //+------------------------------------------------------------------+
Der Wert dieser Flag-Variablen des Testers wird im Klassenkonstruktor gesetzt:
//+------------------------------------------------------------------+ //| CEngine Konstruktor | //+------------------------------------------------------------------+ CEngine::CEngine() : m_first_start(true),m_acc_trade_event(TRADE_EVENT_NO_EVENT) { this.m_list_counters.Sort(); this.m_list_counters.Clear(); this.CreateCounter(COLLECTION_COUNTER_ID,COLLECTION_COUNTER_STEP,COLLECTION_PAUSE); this.m_is_hedge=#ifdef __MQL4__ true #else bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) #endif; this.m_is_tester=::MQLInfoInteger(MQL_TESTER); ::ResetLastError(); #ifdef __MQL5__ if(!::EventSetMillisecondTimer(TIMER_FREQUENCY)) ::Print(DFUN,"Не удалось создать таймер. Ошибка: ","Could not create timer. Error: ",(string)::GetLastError()); //---__MQL4__ #else if(!this.IsTester() && !::EventSetMillisecondTimer(TIMER_FREQUENCY)) ::Print(DFUN,"Не удалось создать таймер. Ошибка: ","Could not create timer. Error: ",(string)::GetLastError()); #endif } //+------------------------------------------------------------------+
Überprüfen wir in OnTimer() der Klasse CEngine die Arbeit im Tester und arbeiten, je nachdem, ob die Arbeit im Tester oder nicht durchgeführt wird, entweder mit dem Timerzähler oder durch mit den Ticks:
//+------------------------------------------------------------------+ //| CEngine Timer | //+------------------------------------------------------------------+ void CEngine::OnTimer(void) { //--- Timer der Kollektion der historischen Aufträge, Deals Marktorders und Positionen int index=this.CounterIndex(COLLECTION_COUNTER_ID); if(index>WRONG_VALUE) { CTimerCounter* counter=this.m_list_counters.At(index); if(counter!=NULL) { //--- If this is not a tester if(!this.IsTester()) { //--- Wenn nicht 'in Pause', arbeite mit der Kollektion der Ereignisse if(counter.IsTimeDone()) this.TradeEventsControl(); } //--- If this is a tester, work with collection events by tick else { this.TradeEventsControl(); } } } } //+------------------------------------------------------------------+
Kompilieren wir den EA, starten ihn im Tester und versuchen die Schaltflächen:
Die Meldungen zeigen an, dass die Bibliothek einige Ereignisse sieht: das Setzen einer ausstehenden Reihenfolge und das Ändern von Auftrags- und Positionsparametern. Er kann augenblicklich noch keine anderen Ereignisse sehen.
Kommen wir zu den Fehlern.
Verbessern der Bibliothek
Das erste, was wir uns ansehen sollten, ist, warum die Bibliothek die Entfernung einer Pending-Order nicht sieht.Alle Ereignisse werden in der Methode der Klasse CEventsCollection::Refresh() Event Collection verfolgt. Wir sind an den Ereignissen der Kontohistorie interessiert. Gehen wir zur Methode über und werfen einen Blick auf den Code, der für Verfolgung von Änderungen in der Kollektion von MQL5 historischen Aufträgen und Geschäften verantwortlich ist:
} //--- Wenn das Ereignis in Kontohistorie existiert if(is_history_event) { //--- Wenn sich die Anzahl historischer Aufträge erhöht hat if(new_history_orders>0) { //--- Erhalt der Liste nur der entfernten Pending-Orders CArrayObj* list=this.GetListHistoryPendings(list_history); if(list!=NULL) { Print(DFUN); //--- Sortieren der Liste nach der Entfernungszeit der Orders list.Sort(SORT_BY_ORDER_TIME_CLOSE_MSC); //--- Nehmen der Order-Anzahl gleich der Anzahl neu entfernten vom Ende der Liste in einer Schleife (die letzten N Ereignisse) int total=list.Total(), n=new_history_orders; for(int i=total-1; i>=0 && n>0; i--,n--) { //--- Empfangen einer Order von der Liste. If this is a removed pending order without a position ID, //--- this is an order removal - set a trading event COrder* order=list.At(i); if(order!=NULL && order.Status()==ORDER_STATUS_HISTORY_PENDING && order.PositionID()==0) this.CreateNewEvent(order,list_history,list_market); } } } /--- Wenn sich die Anzahl der Deals sich erhöht hat
Die Auftragseigenschaft, die die Positions-ID angibt, ist nicht ausgefüllt (gleich Null). Nachdem wir den rechten Teil gefunden haben, können wir sehen, dass wir diese Funktion zur genauen Identifizierung einer ausstehenden Auftragslöschung (und nicht der Aktivierung) in MQL5 verwendet haben (in MQL5 wäre die Positions-ID gleich der ID der durch die Auftragsaktivierung geöffneten Position, wenn eine Order aktiviert wurde und zu einem Deal und einer Position führte). In MQL4 wird in dieses Feld sofort das Ticket des Auftrags eingetragen, was falsch ist.
Gehen wir zum Konstruktor der geschlossenen Klasse des abstrakten Auftrags und finden die Zeile mit der Auftragseigenschaft, die die Positions-ID enthält:
//+------------------------------------------------------------------+ //| Closed parametric constructor | //+------------------------------------------------------------------+ COrder::COrder(ENUM_ORDER_STATUS order_status,const ulong ticket) { //--- Sichern der ganzzahligen Eigenschaften this.m_ticket=ticket; this.m_long_prop[ORDER_PROP_STATUS] = order_status; this.m_long_prop[ORDER_PROP_MAGIC] = this.OrderMagicNumber(); this.m_long_prop[ORDER_PROP_TICKET] = this.OrderTicket(); this.m_long_prop[ORDER_PROP_TIME_OPEN] = (long)(ulong)this.OrderOpenTime(); this.m_long_prop[ORDER_PROP_TIME_CLOSE] = (long)(ulong)this.OrderCloseTime(); this.m_long_prop[ORDER_PROP_TIME_EXP] = (long)(ulong)this.OrderExpiration(); this.m_long_prop[ORDER_PROP_TYPE] = this.OrderType(); this.m_long_prop[ORDER_PROP_STATE] = this.OrderState(); this.m_long_prop[ORDER_PROP_DIRECTION] = this.OrderTypeByDirection(); this.m_long_prop[ORDER_PROP_POSITION_ID] = this.OrderPositionID(); this.m_long_prop[ORDER_PROP_REASON] = this.OrderReason(); this.m_long_prop[ORDER_PROP_DEAL_ORDER_TICKET] = this.DealOrderTicket(); this.m_long_prop[ORDER_PROP_DEAL_ENTRY] = this.DealEntry(); this.m_long_prop[ORDER_PROP_POSITION_BY_ID] = this.OrderPositionByID(); this.m_long_prop[ORDER_PROP_TIME_OPEN_MSC] = this.OrderOpenTimeMSC(); this.m_long_prop[ORDER_PROP_TIME_CLOSE_MSC] = this.OrderCloseTimeMSC(); this.m_long_prop[ORDER_PROP_TIME_UPDATE] = (long)(ulong)this.PositionTimeUpdate(); this.m_long_prop[ORDER_PROP_TIME_UPDATE_MSC] = (long)(ulong)this.PositionTimeUpdateMSC(); //--- Sichern der Double-Eigenschaften this.m_double_prop[this.IndexProp(ORDER_PROP_PRICE_OPEN)] = this.OrderOpenPrice(); this.m_double_prop[this.IndexProp(ORDER_PROP_PRICE_CLOSE)] = this.OrderClosePrice(); this.m_double_prop[this.IndexProp(ORDER_PROP_PROFIT)] = this.OrderProfit(); this.m_double_prop[this.IndexProp(ORDER_PROP_COMMISSION)] = this.OrderCommission(); this.m_double_prop[this.IndexProp(ORDER_PROP_SWAP)] = this.OrderSwap(); this.m_double_prop[this.IndexProp(ORDER_PROP_VOLUME)] = this.OrderVolume(); this.m_double_prop[this.IndexProp(ORDER_PROP_SL)] = this.OrderStopLoss(); this.m_double_prop[this.IndexProp(ORDER_PROP_TP)] = this.OrderTakeProfit(); this.m_double_prop[this.IndexProp(ORDER_PROP_VOLUME_CURRENT)] = this.OrderVolumeCurrent(); this.m_double_prop[this.IndexProp(ORDER_PROP_PRICE_STOP_LIMIT)] = this.OrderPriceStopLimit(); //--- Sichern der String-Eigenschaften this.m_string_prop[this.IndexProp(ORDER_PROP_SYMBOL)] = this.OrderSymbol(); this.m_string_prop[this.IndexProp(ORDER_PROP_COMMENT)] = this.OrderComment(); this.m_string_prop[this.IndexProp(ORDER_PROP_EXT_ID)] = this.OrderExternalID(); //--- Sichern weiterer ganzzahliger Eigenschaften this.m_long_prop[ORDER_PROP_PROFIT_PT] = this.ProfitInPoints(); this.m_long_prop[ORDER_PROP_TICKET_FROM] = this.OrderTicketFrom(); this.m_long_prop[ORDER_PROP_TICKET_TO] = this.OrderTicketTo(); this.m_long_prop[ORDER_PROP_CLOSE_BY_SL] = this.OrderCloseByStopLoss(); this.m_long_prop[ORDER_PROP_CLOSE_BY_TP] = this.OrderCloseByTakeProfit(); this.m_long_prop[ORDER_PROP_GROUP_ID] = 0; //--- Sichern weiterer Double-Eigenschaften this.m_double_prop[this.IndexProp(ORDER_PROP_PROFIT_FULL)] = this.ProfitFull(); } //+------------------------------------------------------------------+
Dies geschieht mit der Methode OrderPositionID(). Wie wir sehen können, wird in MQL4, das Ticket sofort als ID gesetzt:
//+------------------------------------------------------------------+ //| Rückgabe der Positions-ID | //+------------------------------------------------------------------+ long COrder::OrderPositionID(void) const { #ifdef __MQL4__ return ::OrderTicket(); #else long res=0; switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS)) { case ORDER_STATUS_MARKET_POSITION : res=::PositionGetInteger(POSITION_IDENTIFIER); break; case ORDER_STATUS_MARKET_ORDER : case ORDER_STATUS_MARKET_PENDING : res=::OrderGetInteger(ORDER_POSITION_ID); break; case ORDER_STATUS_HISTORY_PENDING : case ORDER_STATUS_HISTORY_ORDER : res=::HistoryOrderGetInteger(m_ticket,ORDER_POSITION_ID); break; case ORDER_STATUS_DEAL : res=::HistoryDealGetInteger(m_ticket,DEAL_POSITION_ID); break; default : res=0; break; } return res; #endif } //+------------------------------------------------------------------+
Dort sollte zunächst 0 eingetragen werden (keine offene Position beim Entfernen der Order). Wir machen das so:
//+------------------------------------------------------------------+ //| Rückgabe der Positions-ID | //+------------------------------------------------------------------+ long COrder::OrderPositionID(void) const { #ifdef __MQL4__ return 0; #else long res=0; switch((ENUM_ORDER_STATUS)this.GetProperty(ORDER_PROP_STATUS)) { case ORDER_STATUS_MARKET_POSITION : res=::PositionGetInteger(POSITION_IDENTIFIER); break; case ORDER_STATUS_MARKET_ORDER : case ORDER_STATUS_MARKET_PENDING : res=::OrderGetInteger(ORDER_POSITION_ID); break; case ORDER_STATUS_HISTORY_PENDING : case ORDER_STATUS_HISTORY_ORDER : res=::HistoryOrderGetInteger(m_ticket,ORDER_POSITION_ID); break; case ORDER_STATUS_DEAL : res=::HistoryDealGetInteger(m_ticket,DEAL_POSITION_ID); break; default : res=0; break; } return res; #endif } //+------------------------------------------------------------------+
Kompilieren wir den EA, starten ihn im Tester und setzen und entfernen dann eine Pending-Order:
Nun wird das Ereignis der Entfernung einer Pending-Order verfolgt.
Wenn wir auf die Aktivierung einer Pending-Order warten, werden wir wieder sehen, dass dieses Ereignis, genau wie eine einfache Positionsöffnung, für die Bibliothek nicht sichtbar ist. Definieren wir die Gründe.
Wie wir uns erinnern, begann alles mit OnTimer() der Klasse CEngine:
//+------------------------------------------------------------------+ //| CEngine Timer | //+------------------------------------------------------------------+ void CEngine::OnTimer(void) { //--- Timer der Kollektion der historischen Aufträge, Deals Marktorders und Positionen int index=this.CounterIndex(COLLECTION_COUNTER_ID); if(index>WRONG_VALUE) { CTimerCounter* counter=this.m_list_counters.At(index); if(counter!=NULL) { //--- If this is not a tester if(!this.IsTester()) { //--- Wenn nicht 'in Pause', arbeite mit der Kollektion der Ereignisse if(counter.IsTimeDone()) this.TradeEventsControl(); } //--- If this is a tester, work with collection events by tick else { this.TradeEventsControl(); } } } } //+------------------------------------------------------------------+
Gemäß dem Code werden die Ereignisse in der Methode TradeEventsControl() kontrolliert. Im Falle eines Ereignisses rufen wir die Methode zum Aktualisieren der Ereignisse der Klasse Event Collection CEventsCollection::Refresh() auf:
//+------------------------------------------------------------------+ //| Prüfen der Handelsereignisse | //+------------------------------------------------------------------+ void CEngine::TradeEventsControl(void) { //--- Initialize the trading events code and flags this.m_is_market_trade_event=false; this.m_is_history_trade_event=false; //--- Aktualisieren der Liste this.m_market.Refresh(); this.m_history.Refresh(); //--- Aktionen beim ersten Start if(this.IsFirstStart()) { this.m_acc_trade_event=TRADE_EVENT_NO_EVENT; return; } //--- Prüfen der Änderungen des Marktstatus' und der Kontohistorie this.m_is_market_trade_event=this.m_market.IsTradeEvent(); this.m_is_history_trade_event=this.m_history.IsTradeEvent(); //--- Im Falle irgendeines Ereignisses, werden die Listen, Flags und die Anzahl der neuen Aufträge und Deals an die Kollektion der Ereignisse gesendet und aktualisiert int change_total=0; CArrayObj* list_changes=this.m_market.GetListChanges(); if(list_changes!=NULL) change_total=list_changes.Total(); if(this.m_is_history_trade_event || this.m_is_market_trade_event || change_total>0) { this.m_events.Refresh(this.m_history.GetList(),this.m_market.GetList(),list_changes, this.m_is_history_trade_event,this.m_is_market_trade_event, this.m_history.NewOrders(),this.m_market.NewPendingOrders(), this.m_market.NewMarketOrders(),this.m_history.NewDeals()); //--- Get the account's last trading event this.m_acc_trade_event=this.m_events.GetLastTradeEvent(); } } //+------------------------------------------------------------------+
Hier senden wir die Listen der historischen und Marktkollektionen, die Flags der Änderungen in den Kollektionen, die Anzahl der neuen historischen Aufträge und aktiven Marktorders und Positionen, sowie die Anzahl der neuen Deals an die Methode. Aber ein genauerer Blick zeigt, dass das Verfahren anstelle der Anzahl der neuen Marktpositionen die Anzahl der neuen Marktorders erhält, die wir in der Bibliothek noch nicht verwendet haben. Das ist mein Fehler. Zunächst wurde alles für MQL5 entwickelt, während die Anzahl der neuen Positionen für die MQL4-Methode gesendet werden sollte. In MQL5 werden neue Positionen durch die Anzahl der Geschäfte definiert. Der Fehler trat auf, als ich die übergebenen Daten für die MQL4-Methode eingegeben habe. Jetzt ist klar, warum die Methode die neuen Marktpositionen nicht sehen kann.
Beheben wir das und lösen ein weiteres Problem auf dem Weg dorthin:
Im Gegensatz zu MQL5 bietet MQL4 keine Möglichkeit, eine Reihenfolge zu finden, die zum Öffnen einer Position führte. Wir haben jedoch bereits eine Liste von Kontrollaufträgen zur Verfolgung von Änderungen der Auftrags- und Positionseigenschaften. Wir haben aus dieser Liste die unnötigen Daten noch nicht entfernt. Diese Liste wird uns helfen, eine Order zu verfolgen, die zur Eröffnung einer Position führte, und das Ereignis zu identifizieren — eine Marktorder oder die Aktivierung einer Pending-Order.
Fügen wir die 'public' Methode, die die Liste der Kontrollaufträge zurückgibt, zur Kollektion von Marktorders und -positionen hinzu (Klasse CMarketCollection in der Datei MarketCollection.mqh):
public: //--- Return the list (1) of all pending orders and open positions, (2) control orders and positions CArrayObj* GetList(void) { return &this.m_list_all_orders; } CArrayObj* GetListChanges(void) { return &this.m_list_changed; } CArrayObj* GetListControl(void) { return &this.m_list_control; } //--- Rückgabe der Liste von Aufträgen und Positionen mit einer Eröffnungszeit vom begin_time bis end_time CArrayObj* GetListByTime(const datetime begin_time=0,const datetime end_time=0); //--- Rückgabe der Liste von Aufträgen und Positionen ausgewählt nach (1) Double-, (2) Integer- und (3) String-Eigenschaften, die eoinem Vergleichswert entsprechen CArrayObj* GetList(ENUM_ORDER_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByOrderProperty(this.GetList(),property,value,mode); } CArrayObj* GetList(ENUM_ORDER_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByOrderProperty(this.GetList(),property,value,mode); } CArrayObj* GetList(ENUM_ORDER_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByOrderProperty(this.GetList(),property,value,mode); } //--- Return the number of (1) new market orders, (2) new pending orders, (3) new positions, (4) occurred trading event flag, (5) changed volume int NewMarketOrders(void) const { return this.m_new_market_orders; } int NewPendingOrders(void) const { return this.m_new_pendings; } int NewPositions(void) const { return this.m_new_positions; } bool IsTradeEvent(void) const { return this.m_is_trade_event; } double ChangedVolumeValue(void) const { return this.m_change_volume_value; } //--- Konstructor CMarketCollection(void); //--- Aktualisieren der Liste der Pending-Orders und Positionen void Refresh(void); }; //+------------------------------------------------------------------+Um Daten aus der Liste zu verwenden, müssen wir sie an die Methode Refresh() der Klasse CEventsCollection übergeben.
Schreiben wir dazu alle notwendigen Änderungen wie oben beschrieben:
//+------------------------------------------------------------------+ //| Prüfen der Handelsereignisse | //+------------------------------------------------------------------+ void CEngine::TradeEventsControl(void) { //--- Initialize the trading events code and flags this.m_is_market_trade_event=false; this.m_is_history_trade_event=false; //--- Aktualisieren der Liste this.m_market.Refresh(); this.m_history.Refresh(); //--- Aktionen beim ersten Start if(this.IsFirstStart()) { this.m_acc_trade_event=TRADE_EVENT_NO_EVENT; return; } //--- Prüfen der Änderungen des Marktstatus' und der Kontohistorie this.m_is_market_trade_event=this.m_market.IsTradeEvent(); this.m_is_history_trade_event=this.m_history.IsTradeEvent(); //--- Im Falle irgendeines Ereignisses, werden die Listen, Flags und die Anzahl der neuen Aufträge und Deals an die Kollektion der Ereignisse gesendet und aktualisiert int change_total=0; CArrayObj* list_changes=this.m_market.GetListChanges(); if(list_changes!=NULL) change_total=list_changes.Total(); if(this.m_is_history_trade_event || this.m_is_market_trade_event || change_total>0) { this.m_events.Refresh(this.m_history.GetList(),this.m_market.GetList(),list_changes,this.m_market.GetListControl(), this.m_is_history_trade_event,this.m_is_market_trade_event, this.m_history.NewOrders(),this.m_market.NewPendingOrders(), this.m_market.NewPositions(),this.m_history.NewDeals()); //--- Get the account's last trading event this.m_acc_trade_event=this.m_events.GetLastTradeEvent(); } } //+------------------------------------------------------------------+
Hier in der Methode TradeEventsControl() der Klasse CEngine, haben wir die Weitergabe einer weiteren Liste — die Liste der Kontrollaufträge zur Methode Refresh() der Klasse CEventsCollection hinzugefügt und die fehlerhafte Weitergabe einer Reihe von neuen Marktaufträgen an die Methode durch die Weitergabe einer Reihe von neuen Positionen ersetzt.
Korrigieren wir noch die Definition der Methode Refresh() im Körper der Klasse CEventsCollection:
public: //--- Auswählen der Ereignisse aus der Kollektion mit einer Zeit im Bereich von begin_time bis end_time. CArrayObj *GetListByTime(const datetime begin_time=0,const datetime end_time=0); //--- Rückgabe des der gesamten Kollektionsliste des Ereignisses "wie besehen" CArrayObj *GetList(void) { return &this.m_list_events; } //--- Rückgabe der Auswahlliste von (1) Integer-, (2) Double- und (3) String-Eigenschaften, die dem Vergleichskriterium entsprechen CArrayObj *GetList(ENUM_EVENT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByEventProperty(this.GetList(),property,value,mode); } CArrayObj *GetList(ENUM_EVENT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByEventProperty(this.GetList(),property,value,mode); } CArrayObj *GetList(ENUM_EVENT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByEventProperty(this.GetList(),property,value,mode); } //--- Aktualisieren der Ereignisliste void Refresh(CArrayObj* list_history, CArrayObj* list_market, CArrayObj* list_changes, CArrayObj* list_control, const bool is_history_event, const bool is_market_event, const int new_history_orders, const int new_market_pendings, const int new_market_positions, const int new_deals); //--- Setzen der Chart-ID des Steuerprogramms void SetChartID(const long id) { this.m_chart_id=id; } //--- Rückgabe des letzten Handelsereignisses auf dem Konto ENUM_TRADE_EVENT GetLastTradeEvent(void) const { return this.m_trade_event; } //--- Rücksetzen des letzten Handelsereignisses void ResetLastTradeEvent(void) { this.m_trade_event=TRADE_EVENT_NO_EVENT; } //--- Konstructor CEventsCollection(void); }; //+------------------------------------------------------------------+
und in der Umsetzung außerhalb des Klassenkörpers:
//+------------------------------------------------------------------+ //| Aktualisieren der Ereignisliste | //+------------------------------------------------------------------+ void CEventsCollection::Refresh(CArrayObj* list_history, CArrayObj* list_market, CArrayObj* list_changes, CArrayObj* list_control, const bool is_history_event, const bool is_market_event, const int new_history_orders, const int new_market_pendings, const int new_market_positions, const int new_deals) {
Die Methode zum Aktualisieren der Ereignisliste der Kollektion der Ereignisklasse fehlt noch bei der Behandlung des Ereignisses zum Öffnen einer Position für MQL4. Wir werden dafür ein paar Methoden benötigen.
Um die Liste der offenen Stellen zu erhalten, sollten wir eine Methode haben, um sie abzurufen. Außerdem haben wir noch keine Methode, um mit Hilfe der Liste der Kontrollaufträge einen Typ der Order zu definieren, die zur Eröffnung einer Position führte.
Wir benötigen auch zwei 'private' Klassenmitglieder, um den Typ eines Eröffnungsauftrags in der Liste der Kontrollaufträge und eine Positions-ID zu speichern. Der Typ und die ID sind im Codeblock zur Behandlung von Marktpositionseröffnungsereignissen für MQL4 zu definieren.
Hinzufügen in den Teil 'private':
//+------------------------------------------------------------------+ //| Kollektion der Kontoereignisse | //+------------------------------------------------------------------+ class CEventsCollection : public CListObj { private: CListObj m_list_events; // Liste der Ereignisse bool m_is_hedge; // Flag des Hedging-Kontos long m_chart_id; // Chart-ID des Steuerprogramms int m_trade_event_code; // Trading event code ENUM_TRADE_EVENT m_trade_event; // Handelsereignis auf dem Konto CEvent m_event_instance; // Ereignisobjekt für die Suche nach einer Eigenschaft MqlTick m_tick; // Last tick structure ulong m_position_id; // Position ID (MQL4) ENUM_ORDER_TYPE m_type_first; // Opening order type (MQL4) //--- Create a trading event depending on the order (1) status and (2) change type void CreateNewEvent(COrder* order,CArrayObj* list_history,CArrayObj* list_market); void CreateNewEvent(COrderControl* order); //--- Create an event for a (1) hedging account, (2) netting account void NewDealEventHedge(COrder* deal,CArrayObj* list_history,CArrayObj* list_market); void NewDealEventNetto(COrder* deal,CArrayObj* list_history,CArrayObj* list_market); //--- Select from the list and return the list of (1) market pending orders, (2) open positions CArrayObj* GetListMarketPendings(CArrayObj* list); CArrayObj* GetListPositions(CArrayObj* list); //--- Select from the list and return the list of historical (1) removed pending orders, (2) deals, (3) all closing orders CArrayObj* GetListHistoryPendings(CArrayObj* list); CArrayObj* GetListDeals(CArrayObj* list); CArrayObj* GetListCloseByOrders(CArrayObj* list); //--- Return the list of (1) all position orders by its ID, (2) all deal positions by its ID //--- (3) all market entry deals by position ID, (4) all market exit deals by position ID, //--- (5) all position reversal deals by position ID CArrayObj* GetListAllOrdersByPosID(CArrayObj* list,const ulong position_id); CArrayObj* GetListAllDealsByPosID(CArrayObj* list,const ulong position_id); CArrayObj* GetListAllDealsInByPosID(CArrayObj* list,const ulong position_id); CArrayObj* GetListAllDealsOutByPosID(CArrayObj* list,const ulong position_id); CArrayObj* GetListAllDealsInOutByPosID(CArrayObj* list,const ulong position_id); //--- Rückgabe des Gesamtvolumens aller Deals (1) IN, (2) OUT der Position nach deren ID double SummaryVolumeDealsInByPosID(CArrayObj* list,const ulong position_id); double SummaryVolumeDealsOutByPosID(CArrayObj* list,const ulong position_id); //--- Return the (1) first, (2) last and (3) closing order from the list of all position orders, //--- (4) an order by ticket, (5) market position by ID, //--- (6) the last and (7) penultimate InOut deal by position ID COrder* GetFirstOrderFromList(CArrayObj* list,const ulong position_id); COrder* GetLastOrderFromList(CArrayObj* list,const ulong position_id); COrder* GetCloseByOrderFromList(CArrayObj* list,const ulong position_id); COrder* GetHistoryOrderByTicket(CArrayObj* list,const ulong order_ticket); COrder* GetPositionByID(CArrayObj* list,const ulong position_id); //--- Return the type of the opening order by the position ticket (MQL4) ENUM_ORDER_TYPE GetTypeFirst(CArrayObj* list,const ulong ticket); //--- Rückgabe des Flags des Ereignisobjekts in der Ereignisliste bool IsPresentEventInList(CEvent* compared_event); //--- Existing order/position change event handler void OnChangeEvent(CArrayObj* list_changes,const int index); public:
Implementieren wir das Verfahren zum Abrufen der Liste der offenen Positionen außerhalb des Klassenkörpers:.
//+------------------------------------------------------------------+ //| Select only market positions from the list | //+------------------------------------------------------------------+ CArrayObj* CEventsCollection::GetListPositions(CArrayObj *list) { if(list.Type()!=COLLECTION_MARKET_ID) { Print(DFUN,TextByLanguage("Ошибка. Список не является списком рыночной коллекции","Error. The list is not a list of the market collection")); return NULL; } CArrayObj* list_positions=CSelect::ByOrderProperty(list,ORDER_PROP_STATUS,ORDER_STATUS_MARKET_POSITION,EQUAL); return list_positions; } //+------------------------------------------------------------------+
Die vollständige Liste der Marktorders und Positionen wird an die Methode übergeben und sortiert nach dem Status "market position". Die resultierende Liste wird an das aufrufende Programm zurückgegeben.
Schreiben wir die Methode , die einen Typ der Order zurückgibt, die zum Öffnen einer Position führte:
//+------------------------------------------------------------------+ //| Return the type of an opening order by position ticket (MQL4) | //+------------------------------------------------------------------+ ENUM_ORDER_TYPE CEventsCollection::GetTypeFirst(CArrayObj* list,const ulong ticket) { if(list==NULL) return WRONG_VALUE; int total=list.Total(); for(int i=0;i<total;i++) { COrderControl* ctrl=list.At(i); if(ctrl==NULL) continue; if(ctrl.Ticket()==ticket) return (ENUM_ORDER_TYPE)ctrl.TypeOrder(); } return WRONG_VALUE; } //+------------------------------------------------------------------+
Die Liste der Kontrollaufträge und das Ticket einer neu geöffneten Position werden an die Methode übergeben. Als Nächstes holen wir uns in einer Schleife vom Anfang der Liste (vorausgesetzt, dass eine Pending-Order vor anderen offenen Positionen platziert wurde, so dass ihr Ticket schneller erscheint), die Kontrollorder aus der Liste und vergleichen ihr Ticket mit dem, das der Funktion übergeben wurde. Wird das Ticket gefunden, ist diese Order eine Eröffnungsorder für die Position, deren Ticket an die Methode übergeben wurde — Rückgabe des Typs der Order. Wenn keine Order mit einem solchen Ticket gefunden wird, wird -1 zurückgegeben.
Jetzt können wir die Behandlung von Ereignissen mit Positionen für MQL4 verbessern.
Hinzufügen der Behandlung einer Positionseröffnung für MQL4 zur Aktualisierungsmethode der Ereignisliste:
//+------------------------------------------------------------------+ //| Aktualisieren der Ereignisliste | //+------------------------------------------------------------------+ void CEventsCollection::Refresh(CArrayObj* list_history, CArrayObj* list_market, CArrayObj* list_changes, CArrayObj* list_control, const bool is_history_event, const bool is_market_event, const int new_history_orders, const int new_market_pendings, const int new_market_positions, const int new_deals) { //--- Rückkehren, wenn die Liste leer ist if(list_history==NULL || list_market==NULL) return; //--- Wenn das Ereignis in der Umgebung des Marktes existiert if(is_market_event) { //--- if the order properties were changed int total_changes=list_changes.Total(); if(total_changes>0) { for(int i=total_changes-1;i>=0;i--) { this.OnChangeEvent(list_changes,i); } } //--- wenn sich die Anzahl der platzierten Pending-Orders erhöht hat if(new_market_pendings>0) { //--- Empfangen der Liste der neuesten, platzierten Pending-Orders CArrayObj* list=this.GetListMarketPendings(list_market); if(list!=NULL) { //--- Sortieren der neuen Liste nach der Platzierungszeit der Orders list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC); //--- Nehmen der Order-Anzahl, die gleich der Anzahl neu platzierten vom Ende der Liste ist, in einer Schleife (die letzten N Ereignisse) int total=list.Total(), n=new_market_pendings; for(int i=total-1; i>=0 && n>0; i--,n--) { //--- Erhalt der Order von der Liste, wenn es eine Pending-Order ist, wird das Handelsereignis gesetzt COrder* order=list.At(i); if(order!=NULL && order.Status()==ORDER_STATUS_MARKET_PENDING) this.CreateNewEvent(order,list_history,list_market); } } } #ifdef __MQL4__ //--- If the number of positions increased if(new_market_positions>0) { //--- Get the list of open positions CArrayObj* list=this.GetListPositions(list_market); if(list!=NULL) { //--- Sort the new list by a position open time list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC); //--- Take the number of positions equal to the number of newly placed open positions from the end of the list in a loop (the last N events) int total=list.Total(), n=new_market_positions; for(int i=total-1; i>=0 && n>0; i--,n--) { //--- Receive a position from the list. If this is a position, search for opening order data and set a trading event COrder* position=list.At(i); if(position!=NULL && position.Status()==ORDER_STATUS_MARKET_POSITION) { //--- Find an order and set (1) a type of an order that led to opening a position and a (2) position ID this.m_type_first=this.GetTypeFirst(list_control,position.Ticket()); this.m_position_id=position.Ticket(); this.CreateNewEvent(position,list_history,list_market); } } } } #endif } //--- Wenn das Ereignis in Kontohistorie existiert if(is_history_event) { //--- Wenn sich die Anzahl historischer Aufträge erhöht hat if(new_history_orders>0) { //--- Erhalt der Liste nur der entfernten Pending-Orders CArrayObj* list=this.GetListHistoryPendings(list_history); if(list!=NULL) { //--- Sortieren der Liste nach der Entfernungszeit der Orders list.Sort(SORT_BY_ORDER_TIME_CLOSE_MSC); //--- Take the number of orders equal to the number of newly removed pending ones from the end of the list in a loop (the last N events) int total=list.Total(), n=new_history_orders; for(int i=total-1; i>=0 && n>0; i--,n--) { //--- Empfangen einer Order von der Liste. If this is a removed pending order without a position ID, //--- this is an order removal - set a trading event COrder* order=list.At(i); if(order!=NULL && order.Status()==ORDER_STATUS_HISTORY_PENDING && order.PositionID()==0) this.CreateNewEvent(order,list_history,list_market); } } } /--- Wenn sich die Anzahl der Deals sich erhöht hat if(new_deals>0) { //--- Empfangen der Liste nur der Deals CArrayObj* list=this.GetListDeals(list_history); if(list!=NULL) { //--- Sortieren der neuen Liste nach der Dealzeit list.Sort(SORT_BY_ORDER_TIME_OPEN_MSC); //--- Nehmen der Anzahl der Deals, die gleich der Anzahl der neuen vom Ende der Liste ist, in einer Schleife (die letzten N Ereignisse) int total=list.Total(), n=new_deals; for(int i=total-1; i>=0 && n>0; i--,n--) { //--- Erhalt eines Deals von der Liste und setzen des Handelsereignisses COrder* order=list.At(i); if(order!=NULL) this.CreateNewEvent(order,list_history,list_market); } } } } } //+------------------------------------------------------------------+
Alle Aktionen zum Öffnen einer neuen Position oder zum Auslösen einer ausstehenden Bestellung für MQL4 sind in den Codekommentaren beschrieben und bedürfen keiner zusätzlichen Erklärung.
Gehen wir nun zur Methode CEventsCollection::CreateNewEvent() zum Erzeugen eines neuen Ereignisses und dem Finden des Codeblocks, der für das Erzeugen eines Positionsöffnungsereignisses für MQL4 verantwortlich ist (der Anfang des Blocks ist in den Codekommentaren markiert) und ergänzen die Definition des Positionsöffnungsereignisses und die Ursache für dessen Öffnen, sowie Hinzufügen von Daten in der entsprechenden Reihenfolge und Position ID zu den Daten der offenen Position:
//--- Position eröffnet (__MQL4__) if(status==ORDER_STATUS_MARKET_POSITION) { //--- Set the "position opened" trading event code this.m_trade_event_code=TRADE_EVENT_FLAG_POSITION_OPENED; //--- Set the "request executed partially" reason ENUM_EVENT_REASON reason=EVENT_REASON_DONE; //--- If an opening order is a pending one if(this.m_type_first>ORDER_TYPE_SELL && this.m_type_first<ORDER_TYPE_BALANCE) { //--- set the "pending order activated" reason reason=EVENT_REASON_ACTIVATED_PENDING; //--- add a pending order activation flag to the event code this.m_trade_event_code+=TRADE_EVENT_FLAG_ORDER_ACTIVATED; } CEvent* event=new CEventPositionOpen(this.m_trade_event_code,order.Ticket()); if(event!=NULL) { event.SetProperty(EVENT_PROP_TIME_EVENT,order.TimeOpenMSC()); // Event time event.SetProperty(EVENT_PROP_REASON_EVENT,reason); // Event reason (from the ENUM_EVENT_REASON enumeration) event.SetProperty(EVENT_PROP_TYPE_DEAL_EVENT,this.m_type_first); // Event deal type event.SetProperty(EVENT_PROP_TICKET_DEAL_EVENT,order.Ticket()); // Event deal ticket event.SetProperty(EVENT_PROP_TYPE_ORDER_EVENT,this.m_type_first); // Type of the order that triggered an event deal (the last position order) event.SetProperty(EVENT_PROP_TYPE_ORDER_POSITION,this.m_type_first); // Type of an order that triggered a position deal (the first position order) event.SetProperty(EVENT_PROP_TICKET_ORDER_EVENT,order.Ticket()); // Ticket of an order, based on which a deal event is opened (the last position order) event.SetProperty(EVENT_PROP_TICKET_ORDER_POSITION,order.Ticket()); // Ticket of an order, based on which a position event is opened (the first position order) event.SetProperty(EVENT_PROP_POSITION_ID,this.m_position_id); // Position ID event.SetProperty(EVENT_PROP_POSITION_BY_ID,order.PositionByID()); // Opposite position ID event.SetProperty(EVENT_PROP_MAGIC_BY_ID,0); // Opposite position magic number event.SetProperty(EVENT_PROP_TYPE_ORD_POS_BEFORE,order.TypeOrder()); // Position order type before direction changed event.SetProperty(EVENT_PROP_TICKET_ORD_POS_BEFORE,order.Ticket()); // Position order ticket before direction changed event.SetProperty(EVENT_PROP_TYPE_ORD_POS_CURRENT,order.TypeOrder()); // Current position order type event.SetProperty(EVENT_PROP_TICKET_ORD_POS_CURRENT,order.Ticket()); // Current position order ticket event.SetProperty(EVENT_PROP_PRICE_OPEN_BEFORE,order.PriceOpen()); // Order price before modification event.SetProperty(EVENT_PROP_PRICE_SL_BEFORE,order.StopLoss()); // StopLoss before modification event.SetProperty(EVENT_PROP_PRICE_TP_BEFORE,order.TakeProfit()); // TakeProfit before modification event.SetProperty(EVENT_PROP_PRICE_EVENT_ASK,this.m_tick.ask); // Ask price during an event event.SetProperty(EVENT_PROP_PRICE_EVENT_BID,this.m_tick.bid); // Bid price during an event event.SetProperty(EVENT_PROP_MAGIC_ORDER,order.Magic()); // Order/deal/position magic number event.SetProperty(EVENT_PROP_TIME_ORDER_POSITION,order.TimeOpenMSC()); // Time of an order, based on which a position deal is opened (the first position order) event.SetProperty(EVENT_PROP_PRICE_EVENT,order.PriceOpen()); // Event price event.SetProperty(EVENT_PROP_PRICE_OPEN,order.PriceOpen()); // Order/deal/position open price event.SetProperty(EVENT_PROP_PRICE_CLOSE,order.PriceClose()); // Order/deal/position close price event.SetProperty(EVENT_PROP_PRICE_SL,order.StopLoss()); // StopLoss position price event.SetProperty(EVENT_PROP_PRICE_TP,order.TakeProfit()); // TakeProfit position price event.SetProperty(EVENT_PROP_VOLUME_ORDER_INITIAL,order.Volume()); // Requested order volume event.SetProperty(EVENT_PROP_VOLUME_ORDER_EXECUTED,order.Volume()-order.VolumeCurrent()); // Executed order volume event.SetProperty(EVENT_PROP_VOLUME_ORDER_CURRENT,order.VolumeCurrent()); // Remaining (unexecuted) order volume event.SetProperty(EVENT_PROP_VOLUME_POSITION_EXECUTED,order.Volume()); // Executed position volume event.SetProperty(EVENT_PROP_PROFIT,order.Profit()); // Profit event.SetProperty(EVENT_PROP_SYMBOL,order.Symbol()); // Order symbol event.SetProperty(EVENT_PROP_SYMBOL_BY_ID,order.Symbol()); // Opposite position symbol //--- Setzen der Chart-ID des Steuerprogramms, dekodieren des Ereigniscodes und setzen des Ereignis-Typs event.SetChartID(this.m_chart_id); event.SetTypeEvent(); //--- Hinzufügen einer Ereignisobjekts, wenn es nicht in der Liste ist if(!this.IsPresentEventInList(event)) { this.m_list_events.InsertSort(event); //--- Senden einer Nachricht über das Ereignis und setzen des Wertes des letzten Handelsereignisses event.SendEvent(); this.m_trade_event=event.TradeEvent(); } //--- Wenn es das Ereignis bereits in der Liste gibt, wird das neue Objekt entfernt und eine Debugging-Nachricht angezeigt else { ::Print(DFUN_ERR_LINE,TextByLanguage("Такое событие уже есть в списке","This event is already in the list.")); delete event; } } } //--- Neuer Deal (__MQL5__)
Nachdem alle Änderungen vorgenommen wurden, sollte die Bibliothek das Öffnen der Position und die Aktivierung der Pending-Orders in MQL4 "sehen" können.
Tests
Überprüfen wir die vorgenommenen Änderungen. Kompilieren wir TestDoEasyPart10.mq4, starten es im Tester, öffnen und schließen Positionen, platzieren Pending-Orders, warten, bis eine von ihnen aktiviert ist und prüfen, ob Stopp-Loss und das Trailing aktiviert wird (Ändern von Positionen und offenen Aufträgen). Alle Ereignisse, die die Bibliothek für MQL4 "sieht", sollen im Journal des Testers angezeigt werden:
Wenn wir das Journal des Testers genau beachten, können wir feststellen, dass die Bibliothek immer noch nicht das Schließen von Positionen erkennt. Wenn die Pending-Order BuyLimit #3 ausgelöst wird, erscheint im Journal, dass [BuyLimit #3] aktiviert ist, was zur Position Buy #3 führt. Nun sieht die Bibliothek die Ereignisse der anstehenden Auftragsaktivierung und kennt einen Quellauftrag, aus dem eine Position stammt. Außerdem sehen wir ein leichtes Versäumnis in der Modifikationsfunktion — das Label der Pending-Order BuyStop #1, die durch Trailing modifiziert wurde, wird rot. Aber die Bibliothek sieht alle Ereignisse der Auftrags- und Positionsänderung.
Fügen wir die Korrekturen an den Funktionen von Trading-MQL4 des Testers in der Datei DELib.mqh hinzu. Lassen Sie uns noch eine weitere Funktion erstellen, die den Positionstyp Kaufen/Verkaufen in Abhängigkeit vom Typ der Pending-Order zurückgibt, die ihm übergeben wurde und ersetzen die Überprüfung eines Auftragstyps durch die Überprüfung eines Auftragstyps nach der Richtung in der Zeile zur Auswahl der Pfeilfarbe:
//+------------------------------------------------------------------+ //| Modifying a pending order by ticket | //+------------------------------------------------------------------+ bool PendingOrderModify(const ulong ticket,const double price_set,const double sl,const double tp) { ResetLastError(); if(!OrderSelect((int)ticket,SELECT_BY_TICKET)) { Print(DFUN,TextByLanguage("Не удалось выбрать ордер. Ошибка ","Could not select order. Error "),(string)GetLastError()); return false; } ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderType(); if(type<ORDER_TYPE_BUY_LIMIT || type>ORDER_TYPE_SELL_STOP) { Print(DFUN,TextByLanguage("Ошибка. Не ордер: ","Error. Not order: "),PositionTypeDescription((ENUM_POSITION_TYPE)type)," #",ticket); return false; } if(OrderCloseTime()>0) { Print(DFUN,TextByLanguage("Ошибка. Для модификации выбран удалённый ордер: ","Error. Deleted order selected for modification: "),OrderTypeDescription(type)," #",ticket); return false; } color clr=(TypeByPendingDirection(type)==ORDER_TYPE_BUY ? clrBlue : clrRed); ResetLastError(); if(!OrderModify((int)ticket,price_set,sl,tp,0,clr)) { Print(DFUN,TextByLanguage("Не удалось модифицировать ордер. Ошибка ","Failed to modify order. Error "),(string)GetLastError()); return false; } return true; } //+------------------------------------------------------------------+ //| Return the type by a pending order direction | //+------------------------------------------------------------------+ ENUM_ORDER_TYPE TypeByPendingDirection(const ENUM_ORDER_TYPE type) { if(type==ORDER_TYPE_BUY_LIMIT || type==ORDER_TYPE_BUY_STOP) return ORDER_TYPE_BUY; if(type==ORDER_TYPE_SELL_LIMIT || type==ORDER_TYPE_SELL_STOP) return ORDER_TYPE_SELL; return WRONG_VALUE; } //+------------------------------------------------------------------+
Was kommt als Nächstes?
Im nächsten Artikel werden wir das Verfolgen des Schließens von Positionen implementieren und weitere Fehler beheben, die in der aktuellen Version der Verfolgungsereignisse für MQL4 auftreten könnten. Derzeit werdeb das Platzieren und Entfernen von Aufträgen durch den MQL5-Code verfolgt, und es kann einige Feinheiten geben, die bei der Arbeit unter MQL4 berücksichtigt werden sollten.
Alle Dateien der aktuellen Version der Bibliothek sind unten zusammen mit den Dateien der Test-EAs angehängt, die Sie herunterladen und testen können.
Stellen Sie Ihre Fragen, Kommentare und Vorschläge in den Kommentaren.
Frühere Artikel dieser Serie:
Teil 1. Konzept, Datenverwaltung.
Teil 2. Erhebung (Collection) historischer Aufträge und Deals.
Teil 3. Erhebung (Collection) von Marktorders und Positionen, Organisieren der Suche
Teil 4. Handelsereignisse. Konzept.
Teil 5. Klassen und Kollektionen von Handelsereignissen. Senden von Ereignissen an das Programm.
Teil 6. Ereignisse auf Netting-Konten.
Teil 7. Ereignis der Aktivierung einer StopLimit-Order, Vorbereiten der Funktionsweise bei Änderungen von Orders und Positionen.
Teil 8. Ereignisse von Änderungen von Orders und Positionen.
Teil 9. Kompatibilität mit MQL4 - Datenvorbereitung.
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/6767
- 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.