
Bibliothek für ein leichtes und schnelles Entwickeln vom Programmen für den MetaTrader (Teil XXIV): Handelsklassen - automatische Korrektur ungültiger Parametern
Inhalt
- Konzept
- Korrektur der Definition der Handelsereignisse
- Fehlerbehandlung der Parameter der Handelsaufträge
- Tests
- Was kommt als Nächstes?
Im vorherigen Artikel haben wir die Kontrolle ungültiger Parameter zur Handelsklasse hinzugefügt. Die Gültigkeit der an die Handelsmethoden übergebenen Werte wird geprüft. Wenn sich einer der Parameter als ungültig herausstellt, wird bei der Rückkehr von der Handelsmethode der entsprechenden Fehler gemeldet. Dieses Verhalten erlaubt es dem EA nicht, einen Handelsserver mit absichtlich ungültigen Aufträgen zu überlasten. Auf der anderen Seite gibt es keine volle Kontrolle über das Verhalten des EA. Stattdessen können wir prüfen, ob ungültige Werte korrigiert werden können. Wenn ja, dann wäre es vernünftig, sie zu korrigieren und die angepasste Handelsorder an den Server zu senden.
Im Allgemeinen sollte der EA in der Lage sein, den Umständen entsprechend zu handeln und gleichzeitig der benutzerdefinierten Logik der Behandlung von Fehlern in Handelsaufträgen zu folgen. Daher können wir dem EA die folgenden Anweisungen geben, wenn ein Handelsauftragsfehler entdeckt wird:
- Einfach die Handelsmethode beenden, um den Nutzer die Behandlung für ungültige Parameter eines fehlerhaften Auftrags selbst erstellen lassen.
- Wenn ein ungültiger Wert eines Handelsauftrags behoben werden kann, tun Sie dies sofort und senden Sie ihn danach an den Server.
- Wenn ein Fehler situationsbedingt ist, wiederholen Sie eine Handelsanfrage nach einer Pause oder wiederholen Sie einfach die Anfrage mit den gleichen Parametern.
Die Behandlung von Fehlern in den Parametern der Handelsaufträge kann zu einem von mehreren Ergebnissen führen:
- Die Unfähigkeit, den Handel mit dem EA fortzusetzen, bevor eine Fehlerquelle vom Nutzer beseitigt ist.
- Unfähigkeit, einen Handelsauftrag zu senden — Ausstieg aus der Handelsmethode.
- Korrigieren ungültiger Werte und Senden eines korrigierten Handelsauftrags.
- Sofortiges Senden eines Handelsauftrags mit den Ausgangsparametern (hier wird davon ausgegangen, dass sich die Handelsbedingungen verbessert haben).
- Warten, Aktualisieren der Kursdaten und Senden eines Handelsauftrags mit den Ausgangsparametern.
In diesem Artikel werden wir den Fehlerbehandler für Handelsaufträge entwickeln, der Fehler und ihre Quellen prüft und die Methode der Fehlerbehandlung zurückgibt:
- deaktivieren des Handels,
- unterbrechen des Handels,
- korrigieren ungültiger Parameter,
- Handelsanfrage mit den Ausgangsparametern,
- Handelsanfrage nach einer Pause (vorläufige Lösung),
- Erstellen einer schwebenden (pending) Handelsanfrage (in nachfolgenden Artikeln)
Konzept
Das Deaktivieren des Handels ist notwendig, wenn der Handel auf dem Server entweder vollständig deaktiviert ist oder wenn die EAs die Handelsanfragen nutzlos machen. In diesem Fall kann der EA nur als analytischer Assistent verwendet werden. Um dies zu erreichen, benötigen wir das globale Flag, das beim ersten Handelsversuch gesetzt werden soll und das die Unmöglichkeit des Handels bestimmt.
Unterbrechung eines Handelsvorgangs: Im Falle eines Fehlers ist die Handelsmethode zu beenden, um dem Nutzer die Möglichkeit zu geben, die Handelsversuche mit den gleichen Parametern fortzusetzen.
Die Korrektur ungültiger Parameter funktioniert wie folgt: Bei der Überprüfung der Gültigkeit der Handelsanfrage erstellen wir eine Liste aller festgestellten Fehler. Die Methode zur Überprüfung der Parameter sieht alle Fehler aus der Liste durch und gibt den Verhaltenscode der Handelsmethode zurück. Wenn ein Fehler den weiteren Handel unmöglich macht, gibt die Methode den Code zum Verlassen der Handelsmethode zurück, da das Senden eines Handelsauftrags immer noch kein positives Ergebnis liefert. Wenn der Fehler behoben werden kann, werden die Methoden zur Korrektur der entsprechenden Handelsauftragswerte aufgerufen und das Ergebnis der erfolgreichen Überprüfung zurückgegeben. Außerdem gibt die Methode die Verhaltenscodes "Warten und wiederholen", "Daten aktualisieren und wiederholen" und "Eine schwebende Anfrage erstellen" der Handelsmethode zurück.
Was bedeutet das?Das Verhalten von "Warten und wiederholen" kann notwendig sein, wenn sich der Markt nahe an einem der Stopp-Preise des Auftrags oder ihrem Aktivierungspreis befindet, während wir versuchen, den Stopp-Preis zu ändern oder einen Auftrag zu entfernen bzw. eine Position zu schließen. Wenn sich der Aktivierungspreis des Stopps innerhalb des Einfrierens der Handelsoperationen befindet, gibt der Server das Verbot zur Änderung des Auftragswertes zurück. In diesem Fall gibt es nur eine Lösung — einfach eine Weile warten und hoffen, dass der Marktpreis den Bereich verlässt. Danach senden Sie eine Handelsanfrage mit geänderten Parametern der Order/Position oder die Order wird nach dem Warten entfernt.
Das Verhalten "Daten aktualisieren und wiederholen" kann notwendig sein, wenn die Preise veraltet sind und wir während der Bearbeitung eines Handelsauftrags ein "Requote" erhalten haben.
Das Verhalten "Erstellen einer schwebenden Anfrage". Was bedeutet das?
Wenn wir uns die beiden vorhergehenden Abwicklungsmethoden genau ansehen, wird klar, dass wir bei der Handelsmethode beim Warten einfach warten, bis die Wartezeit vorbei ist. Ein solches Verhalten ist gerechtfertigt, wenn wir während des Wartens nicht das Handelsumfeld analysieren müssen. Um das Programm von der Notwendigkeit zu befreien, innerhalb der Handelsmethode "stehen" zu bleiben, erstellen wir einfach eine schwebende Handelsanfrage, die die notwendigen Parameter und die Wartezeit mit der Anzahl der Wiederholungen enthält.
Bevor wir anfangen, möchte ich Sie daran erinnern, dass wir begonnen haben, Änderungen in der Definition des Handelsereignisses im vorherigen Artikel vorzunehmen:
Ich habe mehrere Nutzerberichte über den Fehler erhalten, der beim Empfang des letzten Handelsereignisses festgestellt wurde. Der Test EA, der sich auf die Artikel bezieht und beschreibt, wie man Handelsereignisse empfängt, erhält Daten über das aufgetretene Handelsereignis, indem er den Wert des vorherigen Ereignisses mit dem aktuellen vergleicht. Dies wäre für den Zweck des Tests der Verfolgung von Handelsereignissen durch die Bibliothek ausreichend, da ich nicht beabsichtigte, die unfertige Version der Bibliothek in benutzerdefinierten Anwendungen zu verwenden, wenn ich Artikel über Handelsereignisse schreibe. Es stellte sich jedoch heraus, dass die Beschaffung von Informationen über Trading-Events sehr gefragt ist und es wichtig ist, das letzte aufgetretene Ereignis genau zu kennen.
Die implementierte Methode zum Erhalten eines Handelsereignisses kann einige Ereignisse überspringen. Zum Beispiel, wenn Sie eine Pending Order zweimal hintereinander setzen, wird die zweite nicht im Programm verfolgt (die Bibliothek verfolgt alle Ereignisse), da sie mit der vorletzten übereinstimmt ("Platzieren einer Pending-Order"), obwohl die Orders selbst sich tatsächlich unterscheiden können.
Deshalb werden wir dieses Verhalten korrigieren. Heute werden wir ein einfaches Flag implementieren, das das Programm über ein Ereignis informiert, so dass wir in der Lage sind, Daten über das Ereignis im Programm anzuzeigen. Im nächsten Artikel werden wir das Erhalten von Handelsereignissen im Programm vervollständigen, indem wir eine vollständige Liste aller gleichzeitig aufgetretenen Ereignisse erstellen und sie an das Programm senden. Auf diese Weise können wir nicht nur die Information über das eingetretene Handelsereignis erhalten, sondern auch alle gleichzeitig eingetretenen Ereignisse ansehen, wie es für Konto und Ergebnissee der Symbolkollektion geschieht.
Lassen Sie uns also die Arbeit an der Änderung dieser Funktionalität abschließen, bevor wir unsere Arbeit an der Handelsklasse wieder aufnehmen.
Korrektur der Definition der Handelsereignisse
Da alle unsere Objekte tatsächlich auf dem Basisobjekt aller Bibliotheksobjekte basieren, das die Liste der Ereignisse und die Methode, die das Ereignisflag des Objekts zurückgibt, enthält, fügen wir alle Handelsereignisse zur Ereignisliste des Basisobjekts hinzu, während das Ereignisflag von der Klasse mit der Methode IsEvent() erhalten werden kann. Ereignisflags werden automatisch von der Klasse gesetzt. Wir sollten jedoch in der Lage sein, das Flag eines eingetretenen Handelsereignisses von anderen Klassen und ihren Ereignisbehandlern zu setzen.
Um dies zu tun, fügen Sie der Klasse CEventBaseObj in der Datei BaseObj.mqh die Methode zum Setzen des Basisobjekt-Ereignisflags hinzu:
//--- Set/return the occurred event flag to the object data void SetEvent(const bool flag) { this.m_is_event=flag; } bool IsEvent(void) const { return this.m_is_event; }
Wenn nun ein neues Ereignis in der Klasse der Handelsereignisse CEventsCollection auftritt, müssen wir die Ereignisbeschreibung erstellen, sie in die Liste der neuen Ereignisse der Basisklasse aller Objekte aufnehmen und das neue Ereignis-Flag setzen.
Auf diese Weise werden die Beschreibungen aller neu aufgetretenen Ereignisse in die Liste der Handelsereignisse der Basisklasse aller Objekte der Symbolsammlung aufgenommen. Von dieser Liste aus können wir die Liste im Programm leicht lesen und jedes Ereignis in ihr behandeln.
Lassen Sie uns alle notwendigen Verbesserungen an der Datei der Handelsereignisse EventsCollection.mqh vornehmen.
Hinzufügen der Definition der beiden neuen Methoden zum 'public' Teil der Klasse —
die Methode zum Empfangen des Basis-Ereignisobjekts durch seinen Index in der Liste und
die Methode, die die Anzahl der neuen Ereignisse zurückgibt:
//--- Return (1) the last trading event on an account, (2) base event object by index and (3) number of new events ENUM_TRADE_EVENT GetLastTradeEvent(void) const { return this.m_trade_event; } CEventBaseObj *GetTradeEventByIndex(const int index) { return this.GetEvent(index,false); } int GetTradeEventsTotal(void) const { return this.m_list_events.Total(); } //--- Rücksetzen des letzten Handelsereignisses
Die Methode, die das Basis-Ereignisobjekt nach Index zurückgibt, ruft die Methode GetEvent() auf, die den erforderlichen Ereignisindex und das Reset-Flag (false) enthält und den über die Ereignisliste hinausgehenden Index prüft, um das zurückgegebene Ereignis nicht zu korrigieren, wenn der Index über die Ereignisliste hinausgeht. Mit anderen Worten, wenn wir einen nicht existierenden Index übergeben, gibt die Methode NULL zurück. Wenn wir das Flag auf true setzen würden, würde die Methode das letzte Ereignis zurückgeben, das wir hier nicht benötigen.
Die Methode, die die Anzahl der neuen Ereignisse zurück gibt, gibt einfach die Größe der Basisobjektliste zurück.
Da die Listen der historischen und Markt-Orders und -Positionen ständig in der Klasse der Kollektion für Handelsereignisse des Timers angezeigt werden, müssen wir die Liste der Basis-Handelsereignisse löschen und die Flagge für sortierte Listen in der Refresh()-Methode setzen:
//+------------------------------------------------------------------+ //| 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, const double changed_volume) { //--- Rückkehren, wenn die Liste leer ist if(list_history==NULL || list_market==NULL) return; //--- this.m_is_event=false; this.m_list_events.Clear(); this.m_list_events.Sort(); //--- Wenn das Ereignis in der Umgebung des Marktes existiert if(is_market_event) {
Nach dem Ereignis, das eine Zeichenkette sendet, müssen wir in allen Methoden zur Erstellung des neuen Ereignisses CreateNewEvent() zur Liste der Basisereignisse hinzufügen:
event.SendEvent(); CBaseObj::EventAdd(this.m_trade_event,order.Ticket(),order.Price(),order.Symbol());
Dies wurde bereits in den Methodenlisten festgelegt, so dass es keinen Sinn macht, hier darauf zu verweilen, so können wir für den Artikel Platz sparen. Alles ist in den angehängten Dateien zu finden.
Fügen Sie nun im 'public' Teil der Klasse des Basisobjekts der Bibliothek CEngine die Methoden hinzu, die das Basis-Ereignisobjekt über seinen Index in der Liste, und die die Anzahl der neuen Ereignisse zurückgibt:
//--- Return (1) the list of order, deal and position events, (2) base trading event object by index and the (3) number of new trading events CArrayObj *GetListAllOrdersEvents(void) { return this.m_events.GetList(); } CEventBaseObj *GetTradeEventByIndex(const int index) { return this.m_events.GetTradeEventByIndex(index); } int GetTradeEventsTotal(void) const { return this.m_events.GetTradeEventsTotal(); } //--- Rücksetzen des letzten Handelsereignisses
Diese Methoden rufen einfach die oben beschriebenen Methoden der Kollektionsklasse für gleichnamige Handelsereignisse auf.
Dies sind alle notwendigen Änderungen, die es Ihnen ermöglichen, alle gleichzeitig aufgetretenen Ereignisse zu verfolgen und alles auf einmal an das Programm zu senden. Dies wird später — beim Testen der im Artikel beschriebenen Funktionen — deutlich werden.
Jetzt können wir mit der weiteren Verfeinerung der Handelsklasse beginnen.
Fehlerbehandlung der Parameter der Handelsaufträge
Zuerst werden die Indizes der notwendigen Nachrichten zur Datei Datas.mqh hinzugefügt:
MSG_LIB_TEXT_REQUEST_REJECTED_DUE, // Request was rejected before sending to the server due to: MSG_LIB_TEXT_INVALID_REQUEST, // Invalid request: MSG_LIB_TEXT_NOT_ENOUTH_MONEY_FOR, // Insufficient funds for performing a trade MSG_LIB_TEXT_UNSUPPORTED_PRICE_TYPE_IN_REQ, // Unsupported price parameter type in a request MSG_LIB_TEXT_TRADING_DISABLE, // Trading disabled for the EA until the reason is eliminated MSG_LIB_TEXT_TRADING_OPERATION_ABORTED, // Trading operation is interrupted MSG_LIB_TEXT_CORRECTED_TRADE_REQUEST, // Correcting trading request parameters MSG_LIB_TEXT_CREATE_PENDING_REQUEST, // Creating a pending request MSG_LIB_TEXT_NOT_POSSIBILITY_CORRECT_LOT, // Unable to correct a lot };
und die den Indizes entsprechenden Texte:
{"Запрос отклонён до отправки на сервер по причине:","Request rejected before being sent to server due to:"}, {"Ошибочный запрос:","Invalid request:"}, {"Недостаточно средств для совершения торговой операции","Not enough money to perform trading operation"}, {"Неподдерживаемый тип параметра цены в запросе","Unsupported price parameter type in request"}, {"Торговля отключена для эксперта до устранения причины запрета","Trading for the expert is disabled until this ban is eliminated"}, {"Торговая операция прервана","Trading operation aborted"}, {"Корректировка параметров торгового запроса ...","Correction of trade request parameters ..."}, {"Создание отложенного запроса","Create pending request"}, {"Нет возможности скорректировать лот","Unable to correct the lot"}, };
Fügen Sie in der Datei Defines.mqh die Enumerationen hinzu, die wir benötigen, um die Art und Weise der Behandlung von Fehlern in Handelsanforderungen und vom Handelsserver zurückgegebenen Fehlern zu definieren und zurückzugeben.
Um ein Verhalten festzulegen, das aktiviert wird, wenn ein Fehler des Eas direkt empfangen wird, fügen Sie die Enumeration hinzu, die das mögliche Verhalten des EA beschreibt, wenn ein Fehler in der Handelsanfrage entdeckt wird oder wenn ein Fehler vom Handelsserver zurückgegeben wird:
//+------------------------------------------------------------------+ //| EA behavior when handling errors | //+------------------------------------------------------------------+ enum ENUM_ERROR_HANDLING_BEHAVIOR { ERROR_HANDLING_BEHAVIOR_BREAK, // Abort trading attempt ERROR_HANDLING_BEHAVIOR_CORRECT, // Correct invalid parameters ERROR_HANDLING_BEHAVIOR_PENDING_REQUEST, // Create a pending request }; //+------------------------------------------------------------------+
Es wird möglich sein, das bevorzugte Verhalten von EA bei der Behandlung von Fehlern in den EA-Einstellungen festzulegen, indem einer der Aufzählungsparameter angegeben wird.
Verschiedene Methoden zur Fehlerbehandlung sind möglich, wenn die Werte der Handelsauftragsparameter überprüft werden. Um herauszufinden, welche Fehler bei der Überprüfung der Parameter von Handelsaufträgen erkannt werden und welche Handelsbedingungen diese Fehler beeinflussen, müssen der Enumeration mit Flags der möglichen Fehlerbehandlungsmethoden hinzufügen:
//+------------------------------------------------------------------+ //| Flags indicating the trading request error handling methods | //+------------------------------------------------------------------+ enum ENUM_TRADE_REQUEST_ERR_FLAGS { TRADE_REQUEST_ERR_FLAG_NO_ERROR = 0, // No error TRADE_REQUEST_ERR_FLAG_FATAL_ERROR = 1, // Disable trading for an EA (critical error) - exit TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR = 2, // Library internal error - exit TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST = 4, // Error in the list - handle (ENUM_ERROR_CODE_PROCESSING_METHOD) }; //+------------------------------------------------------------------+
Wenn wir die Parameter von Handelsaufträgen und deren Ausführbarkeit überprüfen, fügen wir die Kennzeichen für das Fehlerbehandlungsverhalten hinzu:
- 0 — kein Fehler, der Handelsauftrag kann gesendet werden,
- 1 — kritischer Fehler — es macht keinen Sinn, Handelsversuche zu unternehmen, der EA sollte auf den analytischen Assistenten-Modus ohne Handelsaktivität umgeschaltet werden,
- 2 — etwas ist schief gelaufen, und es gab einen Fehler in der Bibliothek — einfach die weitere Ausführung der Handelsmethode unterbrechen, um die Fehlfunktion der Handelsklasse zu vermeiden,
- 4 — der Fehler kann behoben werden, und es wird in die Fehlerliste geschrieben, um die Methode zur Behebung des Fehlers aufzurufen.
Die Fehlerprüfmethode gibt die Möglichkeiten zur korrekten Behandlung der erkannten Fehler zurück.
Dazu fügen wir die Enumeration der möglichen Methoden zur Behandlung von Handelsauftragsfehlern sowie der vom Handelsserver zurückgegebenen Fehler hinzu:
//+------------------------------------------------------------------+ //| The methods of handling errors and server return codes | //+------------------------------------------------------------------+ enum ENUM_ERROR_CODE_PROCESSING_METHOD { ERROR_CODE_PROCESSING_METHOD_OK, // No errors ERROR_CODE_PROCESSING_METHOD_DISABLE, // Disable trading for the EA ERROR_CODE_PROCESSING_METHOD_EXIT, // Exit the trading method ERROR_CODE_PROCESSING_METHOD_REFRESH, // Update data and repeat ERROR_CODE_PROCESSING_METHOD_WAIT, // Wait and repeat ERROR_CODE_PROCESSING_METHOD_PENDING, // Create a pending request }; //+------------------------------------------------------------------+
Die Methoden der Fehlerbehandlung sind aus den Beschreibungen der Aufzählungskonstanten ersichtlich.
Verbessert wurde auch die Basis des Handelsobjekts.
Abhängig von der Art und Weise, wie die Balken auf dem Chart aufgebaut sind, wird der Handel entweder durch Ask- und Bid-Preise oder durch Ask- und Last-Preise durchgeführt. Derzeit wird in der Basis-Handelsklasse nur der Handel nach Ask- und Bid-Preisen angeordnet. Fügen wir die Möglichkeit hinzu, die Preise zu überprüfen, die zur Erstellung des Charts verwendet werden. Passen Sie auch die Preise an, die wir für den Handel verwenden werden. Außerdem bietet MQL5 die Struktur der Ergebnisse der Handelsanfrage MqlTradeResult sowie die Felder 'retcode' und 'comment', die den Fehlercode bzw. die Fehlercodebeschreibung enthalten. Dies ermöglicht die Überprüfung der Codes, die vom Handelsserver nach dem Senden einer Handelsorder an den Server zurückgegeben werden. MQL4 hat keine solche Funktion, daher sollte der Fehlercode von der Funktion GetLastError() gelesen werden, die den letzten Fehlercode zurückgibt. Da unsere Bibliothek eine Multiplattform-Bibliothek ist, müssen wir im Falle von MQL4 die Felder der Handelsanforderungsstruktur ausfüllen, nachdem wir sie an den Server gesendet haben.
Bei der Überprüfung des Abstands der Stop-Order im Verhältnis zum Preis berücksichtigen wir auch die Abstände der minimal akzeptablen Stop-Level (StopLevel), die für ein Symbol festgelegt wurden. Wenn der Wert des StopLevels, der von der Funktion SymbolInfoInteger() mit der Eigenschafts-ID SYMBOL_TRADE_STOPS_LEVEL zurückgegeben wird, gleich Null ist, bedeutet dies die Existenz eines minimalen Punktabstands für Stop-Order vom Preis. Es bedeutet nur, dass das Niveau veränderlich ist. Um also den Wert der Verschiebung des Stop-Levels vom Preis zu korrigieren, müssen wir das Level "in situ" auswählen oder den aktuellen Spread multipliziert mit einem bestimmten Wert als Verschiebungswert verwenden. Der doppelte Spread wird normalerweise für eine unproblematische Anpassung der Stop-Level verwendet. Fügen Sie den Multiplikator sowie seine Rückgabe- und Einstellungsmethoden zum Handelsobjekt hinzu, um den Multiplikator auf das Handelsobjekt jedes Symbols einstellen zu können.Fügen wir die erforderlichen Änderungen an der Basisklasse CTradeObj des Handelsobjekts in der Datei TradeObj.mqh hinzu.
Deklarieren Sie im privaten Abschnitt der Klasse zwei Klassenvariablen zur Speicherung des Preistyps für die Konstruktion von Bars und den Spread-Multiplikator zur Anpassung der Stop-Order-Level:
SActions m_datas; MqlTick m_tick; // Tick structure for receiving prices MqlTradeRequest m_request; // Trade request structure MqlTradeResult m_result; // trade request execution result ENUM_SYMBOL_CHART_MODE m_chart_mode; // Price type for constructing bars ENUM_ACCOUNT_MARGIN_MODE m_margin_mode; // Margin calculation mode ENUM_ORDER_TYPE_FILLING m_type_filling; // Filling policy ENUM_ORDER_TYPE_TIME m_type_expiration; // Order expiration type int m_symbol_expiration_flags; // Flags of order expiration modes for a trading object symbol ulong m_magic; // Magic number string m_symbol; // Symbol string m_comment; // Comment ulong m_deviation; // Slippage in points double m_volume; // Volume datetime m_expiration; // Order expiration time (for ORDER_TIME_SPECIFIED type order) bool m_async_mode; // Flag of asynchronous sending of a trade request ENUM_LOG_LEVEL m_log_level; // Logging level int m_stop_limit; // Distance of placing a StopLimit order in points bool m_use_sound; // The flag of using sounds of the object trading events uint m_multiplier; // The spread multiplier to adjust levels relative to StopLevel
Fügen wir im 'public' Teil der Klasse die Methode zur Einstellung des Spread-Multiplikators und die Methode zur Rückgabe des Multiplikatorwertes hinzu:
public: //--- Konstructor CTradeObj(); //--- Set/return the spread multiplier void SetSpreadMultiplier(const uint value) { this.m_multiplier=(value==0 ? 1 : value); } uint SpreadMultiplier(void) const { return this.m_multiplier; } //--- Set default values
Prüfen wir beim Einstellen des Spread-Multiplikators, ob der an die Methode übergebene Wert gleich Null ist. Wenn ja, weisen wir den Wert 1 zu.
Fügen wir außerdem im 'public' Teil der Klasse zwei Methoden hinzu — eine, die den Fehlercode der Handelsanfrage und eine, die die Fehlercodebeschreibung der Handelsanfrage festlegt:
//--- Set the error code in the last request result void SetResultRetcode(const uint retcode) { this.m_result.retcode=retcode; } void SetResultComment(const string comment) { this.m_result.comment=comment; } //--- Data on the last request result:
Im Klassenkonstruktor weisen wir dem Spread-Multiplikator den Standardwert 1 zu:
//+------------------------------------------------------------------+ //| Konstruktor | //+------------------------------------------------------------------+ CTradeObj::CTradeObj(void) : m_magic(0), m_deviation(5), m_stop_limit(0), m_expiration(0), m_async_mode(false), m_type_filling(ORDER_FILLING_FOK), m_type_expiration(ORDER_TIME_GTC), m_comment(::MQLInfoString(MQL_PROGRAM_NAME)+" by DoEasy"), m_log_level(LOG_LEVEL_ERROR_MSG) { //--- Margin calculation mode this.m_margin_mode= ( #ifdef __MQL5__ (ENUM_ACCOUNT_MARGIN_MODE)::AccountInfoInteger(ACCOUNT_MARGIN_MODE) #else /* MQL4 */ ACCOUNT_MARGIN_MODE_RETAIL_HEDGING #endif ); //--- Spread multiplier this.m_multiplier=1; //--- Set default sounds and flags of using sounds this.m_use_sound=false; this.InitSounds(); } //+------------------------------------------------------------------+
In der Methode Init(), die die Standardwerte der Parameter des Handelsobjekts definiert, setzen wir den Variablenwert m_chart_mode, der die Art der Preisdarstellung speichert:
//+------------------------------------------------------------------+ //| Set default values | //+------------------------------------------------------------------+ void CTradeObj::Init(const string symbol, const ulong magic, const double volume, const ulong deviation, const int stoplimit, const datetime expiration, const bool async_mode, const ENUM_ORDER_TYPE_FILLING type_filling, const ENUM_ORDER_TYPE_TIME type_expiration, ENUM_LOG_LEVEL log_level) { this.SetSymbol(symbol); this.SetMagic(magic); this.SetDeviation(deviation); this.SetVolume(volume); this.SetExpiration(expiration); this.SetTypeFilling(type_filling); this.SetTypeExpiration(type_expiration); this.SetAsyncMode(async_mode); this.SetLogLevel(log_level); this.m_symbol_expiration_flags=(int)::SymbolInfoInteger(this.m_symbol,SYMBOL_EXPIRATION_MODE); this.m_volume=::SymbolInfoDouble(this.m_symbol,SYMBOL_VOLUME_MIN); this.m_chart_mode=#ifdef __MQL5__ (ENUM_SYMBOL_CHART_MODE)::SymbolInfoInteger(this.m_symbol,SYMBOL_CHART_MODE) #else SYMBOL_CHART_MODE_BID #endif ; } //+------------------------------------------------------------------+
Hier erhalten wir für MQL5 die Daten mit der Funktion SymbolInfoInteger() mit der ID SYMBOL_CHART_MODE, während im Falle von MQL4 die Bars durch Bid-Preise konstruiert werden.
Nun sollten wir das Eintragen der Rückgabestruktur des Handelsserver zu jeder Handelsmethode hinzufügen.
Nutzen wir die Methode der Positionsöffnung als Beispiel:
//+------------------------------------------------------------------+ //| Open a position | //+------------------------------------------------------------------+ bool CTradeObj::OpenPosition(const ENUM_POSITION_TYPE type, const double volume, const double sl=0, const double tp=0, const ulong magic=ULONG_MAX, const string comment=NULL, const ulong deviation=ULONG_MAX) { ::ResetLastError(); //--- If failed to get the current prices, write the error code and description, send the message to the journal and return 'false' if(!::SymbolInfoTick(this.m_symbol,this.m_tick)) { this.m_result.retcode=::GetLastError(); this.m_result.comment=CMessage::Text(this.m_result.retcode); if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_NOT_GET_PRICE),CMessage::Text(this.m_result.retcode)); return false; } //--- Clear the structures ::ZeroMemory(this.m_request); ::ZeroMemory(this.m_result); //--- Fill in the request structure this.m_request.action = TRADE_ACTION_DEAL; this.m_request.symbol = this.m_symbol; this.m_request.magic = (magic==ULONG_MAX ? this.m_magic : magic); this.m_request.type = OrderTypeByPositionType(type); this.m_request.price = (type==POSITION_TYPE_BUY ? this.m_tick.ask : (this.m_chart_mode==SYMBOL_CHART_MODE_BID ? this.m_tick.bid : this.m_tick.last)); this.m_request.volume = volume; this.m_request.sl = sl; this.m_request.tp = tp; this.m_request.deviation= (deviation==ULONG_MAX ? this.m_deviation : deviation); this.m_request.comment = (comment==NULL ? this.m_comment : comment); //--- Return the result of sending a request to the server #ifdef __MQL5__ return(!this.m_async_mode ? ::OrderSend(this.m_request,this.m_result) : ::OrderSendAsync(this.m_request,this.m_result)); #else ::ResetLastError(); int ticket=::OrderSend(m_request.symbol,m_request.type,m_request.volume,m_request.price,(int)m_request.deviation,m_request.sl,m_request.tp,m_request.comment,(int)m_request.magic,m_request.expiration,clrNONE); if(ticket!=WRONG_VALUE) { ::SymbolInfoTick(this.m_symbol,this.m_tick); this.m_result.retcode=::GetLastError(); this.m_result.ask=this.m_tick.ask; this.m_result.bid=this.m_tick.bid; this.m_result.deal=ticket; this.m_result.price=(::OrderSelect(ticket,SELECT_BY_TICKET) ? ::OrderOpenPrice() : this.m_request.price); this.m_result.volume=(::OrderSelect(ticket,SELECT_BY_TICKET) ? ::OrderLots() : this.m_request.volume); this.m_result.comment=CMessage::Text(this.m_result.retcode); return true; } else { ::SymbolInfoTick(this.m_symbol,this.m_tick); this.m_result.retcode=::GetLastError(); this.m_result.ask=this.m_tick.ask; this.m_result.bid=this.m_tick.bid; this.m_result.comment=CMessage::Text(this.m_result.retcode); return false; } #endif } //+------------------------------------------------------------------+
Hier geben wir für MQL5 wie bisher das Ergebnis der Funktion OrderSend() zurück, während, im Falle von MQL4, die Ticketnummer überprüft wird, die von der MQL4-OrderSend-Funktion zurückgegeben wurde. Wenn eine Handelsauftrag erfolgreich ausgeführt wurde, gibt die Funktion das Ticket des Auftrags zurück. Ein Fehler erzeugt WRONG_VALUE. Stellen Sie daher sicher, dass die Funktion einen anderen Wert als -1 zurückgibt. Wenn ja, aktualisieren Sie die Symbolpreise, füllen Sie die Ergebnisstruktur der Handelsanforderung mit den entsprechenden Daten aus und geben Sie true zurück — die Funktion wurde erfolgreich ausgeführt.
Wenn die Funktion zum Senden von Aufträgen -1 zurückgibt, schreiben Sie den letzten Fehlercode , die aktuellen Preise und die letzte Fehlercodebeschreibung in die Ergebnisstruktur der Handelsanfrage. Die übrigen Strukturfelder werden gleich Null belassen. Als Ergebnis wird false für den gesendeten Handelsauftrag zurückgegeben.
Dank dieser Verfeinerung können wir das Ergebnis der Anfrage mit Hilfe der Klassenmethoden unabhängig vom Ergebnis des Sendens der Handelsorder sehen:
//--- Data on the last request result: //--- Return (1) operation result code, (2) performed deal ticket, (3) placed order ticket, //--- (4) deal volume confirmed by a broker, (5) deal price confirmed by a broker, //--- (6) current market Bid (requote) price, (7) current market Ask (requote) price //--- (8) broker comment to operation (by default, it is filled by the trade server return code description), //--- (9) request ID set by the terminal when sending, (10) external trading system return code uint GetResultRetcode(void) const { return this.m_result.retcode; } ulong GetResultDeal(void) const { return this.m_result.deal; } ulong GetResultOrder(void) const { return this.m_result.order; } double GetResultVolume(void) const { return this.m_result.volume; } double GetResultPrice(void) const { return this.m_result.price; } double GetResultBid(void) const { return this.m_result.bid; } double GetResultAsk(void) const { return this.m_result.ask; } string GetResultComment(void) const { return this.m_result.comment; } uint GetResultRequestID(void) const { return this.m_result.request_id; } uint GetResultRetcodeEXT(void) const { return this.m_result.retcode_external;}
Die übrigen Handelsmethoden werden auf ähnliche Weise abgeschlossen, daher ist es überflüssig, sie hier zu besprechen. Sie finden alles, was Sie brauchen, in den unten angehängten Dateien.
In der Kontoobjektklasse CAccount der Datei Account.mqh verbessern wir die Methode der Rückgabe der für die Eröffnung einer Position oder die Einstellung der erforderlichen Marge einer Pending-Order:
//+------------------------------------------------------------------+ //| Return the margin required for opening a position | //| or placing a pending order | //+------------------------------------------------------------------+ double CAccount::MarginForAction(const ENUM_ORDER_TYPE action,const string symbol,const double volume,const double price) const { double margin=EMPTY_VALUE; #ifdef __MQL5__ return(!::OrderCalcMargin(ENUM_ORDER_TYPE(action%2),symbol,volume,price,margin) ? EMPTY_VALUE : margin); #else return this.MarginFree()-::AccountFreeMarginCheck(symbol,ENUM_ORDER_TYPE(action%2),volume); #endif } //+------------------------------------------------------------------+
Hier muss lediglich eine Konvertierung des an die Methode übergebenen Auftragstyps in zwei mögliche Werte — ORDER_TYPE_BUY oder ORDER_TYPE_SELL — vorgenommen werden, da die Funktionen MQL5 und MQL4, mit denen die Methode arbeitet, nur diesen Auftragstyp erfordern.
Wie Sie sich vielleicht erinnern, ergibt der Rest der Division der Auftragsart Konstante durch 2 immer einen der beiden Werte:
- entweder 0 (ORDER_TYPE_BUY),
- oder 1 (ORDER_TYPE_SELL).
Genau dies ist notwendig, um eine Konvertierung in den richtigen Auftragstyp vorzunehmen.
Wir haben bereits die nutzerdefinierte Struktur für das Ausfüllen der Preisparameter der Handelsorder in der Klasse CTrading aus der Datei Trading.mqh erstellt:
struct SDataPrices { double open; // Open price double limit; // Limit order price double sl; // StopLoss price double tp; // TakeProfit price }; SDataPrices m_req_price; // Trade request prices
MQL verfügt jedoch die Struktur MqlTradeRequest. Um also eine redundante Struktur zu vermeiden,
ersetzen wir die nutzerdefinierte Struktur durch die Standardstruktur im 'private' Teil der Klasse , und wir
deklarieren die Klassenvariable zum Speichern des Flags der Fehlerquelle in der Handelsanfrage und
die Variable für die Speicherung des EA-Verhaltens bei Fehlern, die beim Senden von Handelsaufträgen auftreten:
//+------------------------------------------------------------------+ //| Trading class | //+------------------------------------------------------------------+ class CTrading { private: CAccount *m_account; // Pointer to the current account object CSymbolsCollection *m_symbols; // Pointer to the symbol collection list CMarketCollection *m_market; // Pointer to the list of the collection of market orders and positions CHistoryCollection *m_history; // Pointer to the list of the collection of historical orders and deals CArrayInt m_list_errors; // Error list bool m_is_trade_disable; // Flag disabling trading bool m_use_sound; // The flag of using sounds of the object trading events ENUM_LOG_LEVEL m_log_level; // Logging level MqlTradeRequest m_request; // Trade request prices ENUM_TRADE_REQUEST_ERR_FLAGS m_error_reason_flags; // Flags of error source in a trading method ENUM_ERROR_HANDLING_BEHAVIOR m_err_handling_behavior; // Behavior when handling error //--- Add the error code to the list bool AddErrorCodeToList(const int error_code);
Schreiben Sie außerdem im 'private' Teil der Klasse die Methode, die das Flag innerhalb der Variable zurückgibt, die die Fehlerquellen-Flags schreibt,
die Methode, die das Vorhandensein des Fehlercodes in der Fehlerliste zurückgibt, und
die Methoden der Platzierung und Rückgabemaßnahmen bei der Behandlung von Fehlern :
//--- Return the flag presence in the trading event error reason bool IsPresentErrorFlag(const int code) const { return (this.m_error_reason_flags & code)==code; } //--- Return the error code in the list bool IsPresentErorCode(const int code) { this.m_list_errors.Sort(); return this.m_list_errors.Search(code)>WRONG_VALUE; } //--- Set/return the error handling action void SetErrorHandlingBehavior(const ENUM_ERROR_HANDLING_BEHAVIOR behavior) { this.m_err_handling_behavior=behavior; } ENUM_ERROR_HANDLING_BEHAVIOR ErrorHandlingBehavior(void) const { return this.m_err_handling_behavior; } //--- Check trading limitations
Entfernen Sie den Code für die Anzeige der Meldung im Journal aus der Methode zur Prüfung der Mittelunterdeckung:
if(money_free<=0 #ifdef __MQL4__ || ::GetLastError()==134 #endif ) { if(this.m_log_level>LOG_LEVEL_NO_MSG) { //--- create a message text string message= ( symbol_obj.Name()+" "+::DoubleToString(volume,symbol_obj.DigitsLot())+" "+ ( order_type>ORDER_TYPE_SELL ? OrderTypeDescription(order_type,false,false) : PositionTypeDescription(PositionTypeByOrderType(order_type)) )+" ("+::DoubleToString(money_free,(int)this.m_account.CurrencyDigits())+")" ); //--- display a journal message if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(source_method,CMessage::Text(MSG_LIB_TEXT_NOT_ENOUTH_MONEY_FOR),": ",message); this.AddErrorCodeToList(MSG_LIB_TEXT_NOT_ENOUTH_MONEY_FOR); } return false; }
Jetzt wird die Meldung über die Mittelknappheit von einer anderen Methode angezeigt.
In der aktuellen Methode fügen Sie einfach das Flag hinzu, das die Suche nach dem Fehler in der Fehlerliste anweist, und fügen Sie den Fehlercode zur Fehlerliste hinzu:
//+------------------------------------------------------------------+ //| Check if the funds are sufficient | //+------------------------------------------------------------------+ bool CTrading::CheckMoneyFree(const double volume, const double price, const ENUM_ORDER_TYPE order_type, const CSymbol *symbol_obj, const string source_method, const bool mess=true) { ::ResetLastError(); //--- Get the type of a market order by a trading operation type ENUM_ORDER_TYPE action=this.DirectionByActionType((ENUM_ACTION_TYPE)order_type); //--- Get the value of free funds to be left after conducting a trading operation double money_free= ( //--- For MQL5, calculate the difference between free funds and the funds required to conduct a trading operation #ifdef __MQL5__ this.m_account.MarginFree()-this.m_account.MarginForAction(action,symbol_obj.Name(),volume,price) //--- For MQL4, use the operation result of the standard function returning the amount of funds left #else/*__MQL4__*/::AccountFreeMarginCheck(symbol_obj.Name(),action,volume) #endif ); //--- If no free funds are left, inform of that and return 'false' if(money_free<=0 #ifdef __MQL4__ || ::GetLastError()==134 #endif ) { this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_NOT_ENOUTH_MONEY_FOR); return false; } //--- Verification successful return true; } //+------------------------------------------------------------------+
Deklarieren Sie die Methoden zur Korrektur der Preise der Stop- und Pending-Orders, die Methode zur Korrektur des Volumens in der Handelsorder , die Methode, die angibt, wie mit dem Fehler umzugehen ist, und die Methode zur Korrektur von Fehlern im Handelsauftrag:
//--- Return the correct (1) StopLoss, (2) TakeProfit and (3) order placement price double CorrectStopLoss(const ENUM_ORDER_TYPE order_type, const double price_set, const double stop_loss, const CSymbol *symbol_obj, const uint spread_multiplier=1); double CorrectTakeProfit(const ENUM_ORDER_TYPE order_type, const double price_set, const double take_profit, const CSymbol *symbol_obj, const uint spread_multiplier=1); double CorrectPricePending(const ENUM_ORDER_TYPE order_type, const double price_set, const double price, const CSymbol *symbol_obj, const uint spread_multiplier=1); //--- Return the volume, at which it is possible to open a position double CorrectVolume(const double price, const ENUM_ORDER_TYPE order_type, const CSymbol *symbol_obj, const string source_method); //--- Return the error handling method ENUM_ERROR_CODE_PROCESSING_METHOD ResultProccessingMethod(void); //--- Correct errors ENUM_ERROR_CODE_PROCESSING_METHOD RequestErrorsCorrecting(MqlTradeRequest &request,const ENUM_ORDER_TYPE order_type,const uint spread_multiplier,CSymbol *symbol_obj); public:
Ergänzen Sie die Angabe der Methode zur Überprüfung der Einschränkungen und Fehler und ersetzen Sie den zurückgegebenen Typ von 'bool' durch ENUM_ERROR_CODE_PROCESSING_METHOD:
//--- Check limitations and errors ENUM_ERROR_CODE_PROCESSING_METHOD CheckErrors(const double volume, const double price, const ENUM_ACTION_TYPE action, const ENUM_ORDER_TYPE order_type, CSymbol *symbol_obj, const CTradeObj *trade_obj, const string source_method, const double limit=0, double sl=0, double tp=0);
Die Methode ist jetzt vollständiger geworden — sie prüft sofort auf mögliche Methoden zur Korrektur von Fehlern in einem Handelsauftrag, und jetzt gibt die Methode die Art und Weise zurück, wie man mit dem festgestellten Fehler umgehen kann. Zuvor gab sie lediglich das Flag für die erfolgreiche Prüfung zurück.
Deklarieren Sie die Methode zur Einstellung des Spread-Multiplikators:
//--- Set the following for symbol trading objects: //--- (1) correct filling policy, (2) filling policy, //--- (3) correct order expiration type, (4) order expiration type, //--- (5) magic number, (6) comment, (7) slippage, (8) volume, (9) order expiration date, //--- (10) the flag of asynchronous sending of a trading request, (11) logging level, (12) spread multiplier void SetCorrectTypeFilling(const ENUM_ORDER_TYPE_FILLING type=ORDER_FILLING_FOK,const string symbol=NULL); void SetTypeFilling(const ENUM_ORDER_TYPE_FILLING type=ORDER_FILLING_FOK,const string symbol=NULL); void SetCorrectTypeExpiration(const ENUM_ORDER_TYPE_TIME type=ORDER_TIME_GTC,const string symbol=NULL); void SetTypeExpiration(const ENUM_ORDER_TYPE_TIME type=ORDER_TIME_GTC,const string symbol=NULL); void SetMagic(const ulong magic,const string symbol=NULL); void SetComment(const string comment,const string symbol=NULL); void SetDeviation(const ulong deviation,const string symbol=NULL); void SetVolume(const double volume=0,const string symbol=NULL); void SetExpiration(const datetime expiration=0,const string symbol=NULL); void SetAsyncMode(const bool mode=false,const string symbol=NULL); void SetLogLevel(const ENUM_LOG_LEVEL log_level=LOG_LEVEL_ERROR_MSG,const string symbol=NULL); void SetSpreadMultiplier(const uint value=1,const string symbol=NULL);
Fügen Sie die Methoden zum Setzen und die Rückgabe des Flags hinzu, das den Handel für einen EA ermöglicht:
//--- Set/return the flag enabling sounds void SetUseSounds(const bool flag); bool IsUseSounds(void) const { return this.m_use_sound; } //--- Set/return the flag enabling trading void SetTradingDisableFlag(const bool flag) { this.m_is_trade_disable=flag; } bool IsTradingDisable(void) const { return this.m_is_trade_disable;} //--- Open (1) Buy, (2) Sell position
Es kann Fehler geben, die den weiteren Handel nach ihrer Entdeckung verhindern, z.B. das vollständige Verbot des Handels für ein Konto. Dieses Flag wird gesetzt, wenn solche Fehler entdeckt werden und verhindert, dass weitere nutzlose Handelsaufträge gesendet werden.
Im Klassenkonstruktor setzen Sie das Flag zurück, das den Handel deaktiviert, und setzen Sie das Standard-EA-Verhalten im Falle von Handelsanforderungen als "korrekte Parameter":
//+------------------------------------------------------------------+ //| Konstruktor | //+------------------------------------------------------------------+ CTrading::CTrading() { this.m_list_errors.Clear(); this.m_list_errors.Sort(); this.m_log_level=LOG_LEVEL_ALL_MSG; this.m_is_trade_disable=false; this.m_err_handling_behavior=ERROR_HANDLING_BEHAVIOR_CORRECT; ::ZeroMemory(this.m_request); } //+------------------------------------------------------------------+
Das EA-Verhalten bei Fehlern in den Handelsmethoden kann dann über die EA-Einstellungen eingestellt werden. Wir werden jedoch die Methode der automatischen Korrektur verwenden, bis alle Behandlungsformen fertiggestellt sind.
Die aktuelle Implementierung der Methoden, die die Rückgabecodes des Handelsservers behandeln, liefert nur das Erfolgsflag zurück:
//+------------------------------------------------------------------+ //| Return the error handling method | //+------------------------------------------------------------------+ ENUM_ERROR_CODE_PROCESSING_METHOD CTrading::ResultProccessingMethod(void) { return ERROR_CODE_PROCESSING_METHOD_OK; } //+------------------------------------------------------------------+
Warum wohl? Wir besprechen diese Methode im aktuellen Artikel nicht, da wir im nächsten Artikel die Behandlung der Rückgabecodes des Handelsservers implementieren werden. Die Methode wurde jedoch bereits beschrieben und implementiert, wenn auch in ihrer vereinfachten Form.
Implementierung der Methode zur Korrektur von Fehlern in einer Handelsordnung:
//+------------------------------------------------------------------+ //| Correct errors | //+------------------------------------------------------------------+ ENUM_ERROR_CODE_PROCESSING_METHOD CTrading::RequestErrorsCorrecting(MqlTradeRequest &request, const ENUM_ORDER_TYPE order_type, const uint spread_multiplier, CSymbol *symbol_obj) { //--- The empty error list means no errors are detected, return success int total=this.m_list_errors.Total(); if(total==0) return ERROR_CODE_PROCESSING_METHOD_OK; //--- In the current implementation, all these codes are temporarily handled by interrupting a trading request if( this.IsPresentErorCode(MSG_LIB_TEXT_ACCOUNT_NOT_TRADE_ENABLED) || // Trading is disabled for the current account this.IsPresentErorCode(MSG_LIB_TEXT_ACCOUNT_EA_NOT_TRADE_ENABLED) || // Trading on the trading server side is disabled for EAs on the current account this.IsPresentErorCode(MSG_LIB_TEXT_TERMINAL_NOT_TRADE_ENABLED) || // Trading operations are disabled in the terminal this.IsPresentErorCode(MSG_LIB_TEXT_EA_NOT_TRADE_ENABLED) || // Trading operations are disabled for the EA this.IsPresentErorCode(MSG_SYM_TRADE_MODE_DISABLED) || // Trading on a symbol is disabled this.IsPresentErorCode(MSG_SYM_TRADE_MODE_CLOSEONLY) || // Close only this.IsPresentErorCode(MSG_SYM_MARKET_ORDER_DISABLED) || // Market orders disabled this.IsPresentErorCode(MSG_SYM_LIMIT_ORDER_DISABLED) || // Limit orders are disabled this.IsPresentErorCode(MSG_SYM_STOP_ORDER_DISABLED) || // Stop orders are disabled this.IsPresentErorCode(MSG_SYM_STOP_LIMIT_ORDER_DISABLED) || // StopLimit orders are disabled this.IsPresentErorCode(MSG_SYM_TRADE_MODE_SHORTONLY) || // Only short positions are allowed this.IsPresentErorCode(MSG_SYM_TRADE_MODE_LONGONLY) || // Only long positions are allowed this.IsPresentErorCode(MSG_SYM_CLOSE_BY_ORDER_DISABLED) || // CloseBy orders are disabled this.IsPresentErorCode(MSG_LIB_TEXT_MAX_VOLUME_LIMIT_EXCEEDED) || // Exceeded maximum allowed aggregate volume of orders and positions in one direction this.IsPresentErorCode(MSG_LIB_TEXT_CLOSE_BY_ORDERS_DISABLED) || // Close by is disabled this.IsPresentErorCode(MSG_LIB_TEXT_CLOSE_BY_SYMBOLS_UNEQUAL) || // Symbols of opposite positions are not equal this.IsPresentErorCode(MSG_LIB_TEXT_UNSUPPORTED_PRICE_TYPE_IN_REQ) || // Unsupported price parameter type in a request this.IsPresentErorCode(MSG_LIB_TEXT_TRADING_DISABLE) || // Trading disabled for the EA until the reason is eliminated this.IsPresentErorCode(10006) || // Request rejected this.IsPresentErorCode(10011) || // Request handling error this.IsPresentErorCode(10012) || // Request rejected due to expiration this.IsPresentErorCode(10013) || // Invalid request this.IsPresentErorCode(10017) || // Trading disabled this.IsPresentErorCode(10018) || // Market closed this.IsPresentErorCode(10023) || // Order status changed this.IsPresentErorCode(10025) || // No changes in the request this.IsPresentErorCode(10026) || // Auto trading disabled by server this.IsPresentErorCode(10027) || // Auto trading disabled by client terminal this.IsPresentErorCode(10032) || // Transaction is allowed for live accounts only this.IsPresentErorCode(10033) || // The maximum number of pending orders is reached this.IsPresentErorCode(10034) // You have reached the maximum order and position volume for this symbol ) return ERROR_CODE_PROCESSING_METHOD_EXIT; //--- View the full list of errors and correct trading request parameters for(int i=0;i<total;i++) { int err=this.m_list_errors.At(i); if(err==NULL) continue; switch(err) { //--- Correct an invalid volume and stop levels in a trading request case MSG_LIB_TEXT_REQ_VOL_LESS_MIN_VOLUME : case MSG_LIB_TEXT_REQ_VOL_MORE_MAX_VOLUME : case MSG_LIB_TEXT_INVALID_VOLUME_STEP : request.volume=symbol_obj.NormalizedLot(request.volume); break; case MSG_SYM_SL_ORDER_DISABLED : request.sl=0; break; case MSG_SYM_TP_ORDER_DISABLED : request.tp=0; break; case MSG_LIB_TEXT_PR_LESS_STOP_LEVEL : request.price=this.CorrectPricePending(order_type,request.price,0,symbol_obj,spread_multiplier); break; case MSG_LIB_TEXT_SL_LESS_STOP_LEVEL : request.sl=this.CorrectStopLoss(order_type,request.price,request.sl,symbol_obj,spread_multiplier); break; case MSG_LIB_TEXT_TP_LESS_STOP_LEVEL : request.tp=this.CorrectTakeProfit(order_type,request.price,request.tp,symbol_obj,spread_multiplier); break; //--- If unable to select the position lot, return "abort trading attempt" since the funds are insufficient even for the minimum lot case MSG_LIB_TEXT_NOT_ENOUTH_MONEY_FOR : request.volume=this.CorrectVolume(request.volume,request.price,order_type,symbol_obj,DFUN); if(request.volume==0) return ERROR_CODE_PROCESSING_METHOD_EXIT; break; //--- Proximity to the order activation level is handled by five-second waiting - during this time, the price may go beyond the freeze level case MSG_LIB_TEXT_SL_LESS_FREEZE_LEVEL : case MSG_LIB_TEXT_TP_LESS_FREEZE_LEVEL : case MSG_LIB_TEXT_PR_LESS_FREEZE_LEVEL : return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000; // ERROR_CODE_PROCESSING_METHOD_WAIT - wait 5 seconds default: break; } } return ERROR_CODE_PROCESSING_METHOD_OK; } //+------------------------------------------------------------------+
Die Logik der Methode wird in den Code-Kommentaren beschrieben. Kurz gesagt: Wenn Fehlercodes entdeckt werden, die noch nicht behandelt werden können, geben wir die Behandlungsmethode "Abbruch des Handelsversuchs" zurück. Im Falle von Fehlern, die korrigiert werden können, korrigieren Sie die Parameterwerte und geben ОК zurück.
Verbesserung der Methode zur Überprüfung von Handelseinschränkungen und Fehlern von Handelsaufträgen:
//+------------------------------------------------------------------+ //| Check limitations and errors | //+------------------------------------------------------------------+ ENUM_ERROR_CODE_PROCESSING_METHOD CTrading::CheckErrors(const double volume, const double price, const ENUM_ACTION_TYPE action, const ENUM_ORDER_TYPE order_type, CSymbol *symbol_obj, const CTradeObj *trade_obj, const string source_method, const double limit=0, double sl=0, double tp=0) { //--- Check the previously set flag disabling trading for an EA if(this.IsTradingDisable()) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_FATAL_ERROR; if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(source_method,CMessage::Text(MSG_LIB_TEXT_TRADING_DISABLE)); return ERROR_CODE_PROCESSING_METHOD_DISABLE; } //--- result of all checks and error flags this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_NO_ERROR; bool res=true; //--- Clear the error list this.m_list_errors.Clear(); this.m_list_errors.Sort(); //--- Check trading limitations res &=this.CheckTradeConstraints(volume,action,symbol_obj,source_method,sl,tp); //--- Check the funds sufficiency for opening positions/placing orders if(action<ACTION_TYPE_CLOSE_BY) res &=this.CheckMoneyFree(volume,price,order_type,symbol_obj,source_method); //--- Check parameter values by StopLevel and FreezeLevel res &=this.CheckLevels(action,order_type,price,limit,sl,tp,symbol_obj,source_method); //--- If there are limitations, display the header and the error list if(!res) { //--- Request was rejected before sending to the server due to: int total=this.m_list_errors.Total(); if(this.m_log_level>LOG_LEVEL_NO_MSG) { //--- For MQL5, first display the list header followed by the error list #ifdef __MQL5__ ::Print(source_method,CMessage::Text(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_BREAK ? MSG_LIB_TEXT_REQUEST_REJECTED_DUE : MSG_LIB_TEXT_INVALID_REQUEST)); for(int i=0;i<total;i++) ::Print((total>1 ? string(i+1)+". " : ""),CMessage::Text(m_list_errors.At(i))); //--- For MQL4, the journal messages are displayed in the reverse order: the error list in the reverse loop is followed by the list header #else for(int i=total-1;i>WRONG_VALUE;i--) ::Print((total>1 ? string(i+1)+". " : ""),CMessage::Text(m_list_errors.At(i))); ::Print(source_method,CMessage::Text(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_BREAK ? MSG_LIB_TEXT_REQUEST_REJECTED_DUE : MSG_LIB_TEXT_INVALID_REQUEST)); #endif } //--- If the action is performed at the "abort trading operation" error if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_BREAK) return ERROR_CODE_PROCESSING_METHOD_EXIT; //--- If the action is performed at the "create a pending request" error if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_PENDING_REQUEST) return ERROR_CODE_PROCESSING_METHOD_PENDING; //--- If the action is performed at the "correct parameters" error if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_CORRECT) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(CMessage::Text(MSG_LIB_TEXT_CORRECTED_TRADE_REQUEST)); //--- Return the result of an attempt to correct the request parameters return this.RequestErrorsCorrecting(this.m_request,order_type,trade_obj.SpreadMultiplier(),symbol_obj); } } //--- No limitations return ERROR_CODE_PROCESSING_METHOD_OK; } //+------------------------------------------------------------------+
Der ergänzte Code ist gelb markiert. Nun wird in der Methode zunächst das Flag geprüft, das den Handel deaktiviert. Wenn es gesetzt ist, wird die Fehlerbehandlungsart "Handel für EA deaktivieren" zurückgegeben. Als Nächstes wird je nach dem spezifizierten EA-Verhalten bei Fehlern und entsprechend dem Fehlercode die erforderliche Fehlerbehandlungsmethode zurückgegeben. Wenn es keine Fehler gibt, wird der Code, der keine Fehlerbehandlung erfordert, zurückgegeben.
Die Methode zur Überprüfung von Handelsbeschränkungen wurde mehrfach ähnlich geändert, indem die notwendigen Flags hinzugefügt wurden, die das Vorhandensein verschiedener Fehlertypen und deren Behandlung anzeigen.
Alle in der Methode durchgeführten Aktionen sowie deren Logik werden in den Code-Kommentaren sehr detailliert beschrieben. Schauen wir uns daher die fertiggestellte Methode an:
//+------------------------------------------------------------------+ //| Check trading limitations | //+------------------------------------------------------------------+ bool CTrading::CheckTradeConstraints(const double volume, const ENUM_ACTION_TYPE action_type, const CSymbol *symbol_obj, const string source_method, double sl=0, double tp=0) { //--- the result of conducting all checks bool res=true; //--- Check connection with the trade server (not in the test mode) if(!::TerminalInfoInteger(TERMINAL_CONNECTED)) { if(!::MQLInfoInteger(MQL_TESTER)) { //--- Write the error code to the list and return 'false' - there is no point in further checks this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(10031); return false; } } //--- Check if trading is enabled for an account (if there is a connection with the trade server) else if(!this.m_account.TradeAllowed()) { //--- Write the error code to the list and return 'false' - there is no point in further checks this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_ACCOUNT_NOT_TRADE_ENABLED); return false; } //--- Check if trading is allowed for any EAs/scripts for the current account if(!this.m_account.TradeExpert()) { //--- Write the error code to the list and return 'false' - there is no point in further checks this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_ACCOUNT_EA_NOT_TRADE_ENABLED); return false; } //--- Check if auto trading is allowed in the terminal. //--- AutoTrading button (Options --> Expert Advisors --> "Allowed automated trading") if(!::TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) { //--- Write the error code to the list and return 'false' - there is no point in further checks this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_TERMINAL_NOT_TRADE_ENABLED); return false; } //--- Check if auto trading is allowed for the current EA. //--- (F7 --> Common --> Allow Automated Trading) if(!::MQLInfoInteger(MQL_TRADE_ALLOWED)) { //--- Write the error code to the list and return 'false' - there is no point in further checks this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_EA_NOT_TRADE_ENABLED); return false; } //--- Check if trading is enabled on a symbol. //--- If trading is disabled, write the error code to the list and return 'false' - there is no point in further checks if(symbol_obj.TradeMode()==SYMBOL_TRADE_MODE_DISABLED) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_SYM_TRADE_MODE_DISABLED); return false; } //--- If not closing/removal/modification if(action_type<ACTION_TYPE_CLOSE_BY) { //--- In case of close-only, write the error code to the list and return 'false' - there is no point in further checks if(symbol_obj.TradeMode()==SYMBOL_TRADE_MODE_CLOSEONLY) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_SYM_TRADE_MODE_CLOSEONLY); return false; } //--- Check the minimum volume if(volume<symbol_obj.LotsMin()) { //--- The volume in a request is less than the minimum allowed one. //--- add the error code to the list this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_REQ_VOL_LESS_MIN_VOLUME); //--- If the EA behavior during the trading error is set to "abort trading operation", //--- return 'false' - there is no point in further checks if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_BREAK) return false; //--- If the EA behavior during a trading error is set to //--- "correct parameters" or "create a pending request", //--- write 'false' to the result else res &=false; } //--- Check the maximum volume else if(volume>symbol_obj.LotsMax()) { //--- The volume in the request exceeds the maximum acceptable one. //--- add the error code to the list this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_REQ_VOL_MORE_MAX_VOLUME); //--- If the EA behavior during the trading error is set to "abort trading operation", //--- return 'false' - there is no point in further checks if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_BREAK) return false; //--- If the EA behavior during a trading error is set to //--- "correct parameters" or "create a pending request", //--- write 'false' to the result else res &=false; } //--- Check the minimum volume gradation double step=symbol_obj.LotsStep(); if(fabs((int)round(volume/step)*step-volume)>0.0000001) { //--- The volume in the request is not a multiple of the minimum gradation of the lot change step //--- add the error code to the list this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_INVALID_VOLUME_STEP); //--- If the EA behavior during the trading error is set to "abort trading operation", //--- return 'false' - there is no point in further checks if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_BREAK) return false; //--- If the EA behavior during a trading error is set to //--- "correct parameters" or "create a pending request", //--- write 'false' to the result else res &=false; } } //--- When opening a position if(action_type<ACTION_TYPE_BUY_LIMIT) { //--- Check if sending market orders is allowed on a symbol. //--- If using market orders is disabled, write the error code to the list and return 'false' - there is no point in further checks if(!symbol_obj.IsMarketOrdersAllowed()) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_SYM_MARKET_ORDER_DISABLED); return false; } } //--- When placing a pending order else if(action_type>ACTION_TYPE_SELL && action_type<ACTION_TYPE_CLOSE_BY) { //--- If there is a limitation on the number of pending orders on an account and placing a new order exceeds it if(this.m_account.LimitOrders()>0 && this.OrdersTotalAll()+1 > this.m_account.LimitOrders()) { //--- The limit on the number of pending orders is reached - write the error code to the list and return 'false' - there is no point in further checks this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(10033); return false; } //--- Check if placing limit orders is allowed on a symbol. if(action_type==ACTION_TYPE_BUY_LIMIT || action_type==ACTION_TYPE_SELL_LIMIT) { //--- If setting limit orders is disabled, write the error code to the list and return 'false' - there is no point in further checks if(!symbol_obj.IsLimitOrdersAllowed()) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_SYM_LIMIT_ORDER_DISABLED); return false; } } //--- Check if placing stop orders is allowed on a symbol. else if(action_type==ACTION_TYPE_BUY_STOP || action_type==ACTION_TYPE_SELL_STOP) { //--- If setting stop orders is disabled, write the error code to the list and return 'false' - there is no point in further checks if(!symbol_obj.IsStopOrdersAllowed()) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_SYM_STOP_ORDER_DISABLED); return false; } } //--- For MQL5, check if placing stop limit orders is allowed on a symbol. #ifdef __MQL5__ else if(action_type==ACTION_TYPE_BUY_STOP_LIMIT || action_type==ACTION_TYPE_SELL_STOP_LIMIT) { //--- If setting stop limit orders is disabled, write the error code to the list and return 'false' - there is no point in further checks if(!symbol_obj.IsStopLimitOrdersAllowed()) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_SYM_STOP_LIMIT_ORDER_DISABLED); return false; } } #endif } //--- In case of opening/placing/modification if(action_type!=ACTION_TYPE_CLOSE_BY) { //--- If not modification if(action_type!=ACTION_TYPE_MODIFY) { //--- When buying, check if long trading is enabled on a symbol if(this.DirectionByActionType(action_type)==ORDER_TYPE_BUY) { //--- If only short positions are enabled, write the error code to the list and return 'false' - there is no point in further checks if(symbol_obj.TradeMode()==SYMBOL_TRADE_MODE_SHORTONLY) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_SYM_TRADE_MODE_SHORTONLY); return false; } //--- If a symbol has the limitation on the total volume of an open position and pending orders in the same direction if(symbol_obj.VolumeLimit()>0) { //--- (If the total volume of placed long orders and open long positions)+open volume exceed the maximum one if(this.OrdersTotalVolumeLong()+this.PositionsTotalVolumeLong()+volume > symbol_obj.VolumeLimit()) { //--- Exceeded maximum allowed aggregate volume of orders and positions in one direction //--- write the error code to the list and return 'false' - there is no point in further checks this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_MAX_VOLUME_LIMIT_EXCEEDED); return false; } } } //--- When selling, check if short trading is enabled on a symbol else if(this.DirectionByActionType(action_type)==ORDER_TYPE_SELL) { //--- If only long positions are enabled, write the error code to the list and return 'false' - there is no point in further checks if(symbol_obj.TradeMode()==SYMBOL_TRADE_MODE_LONGONLY) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_SYM_TRADE_MODE_LONGONLY); return false; } //--- If a symbol has the limitation on the total volume of an open position and pending orders in the same direction if(symbol_obj.VolumeLimit()>0) { //--- (If the total volume of placed short orders and open short positions)+open volume exceed the maximum one if(this.OrdersTotalVolumeShort()+this.PositionsTotalVolumeShort()+volume > symbol_obj.VolumeLimit()) { //--- Exceeded maximum allowed aggregate volume of orders and positions in one direction //--- write the error code to the list and return 'false' - there is no point in further checks this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_MAX_VOLUME_LIMIT_EXCEEDED); return false; } } } } //--- If the request features StopLoss and its placing is not allowed if(sl>0 && !symbol_obj.IsStopLossOrdersAllowed()) { //--- add the error code to the list this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_SYM_SL_ORDER_DISABLED); //--- If the EA behavior during the trading error is set to "abort trading operation", //--- return 'false' - there is no point in further checks if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_BREAK) return false; //--- If the EA behavior during a trading error is set to //--- "correct parameters" or "create a pending request", //--- write 'false' to the result else res &=false; } //--- If the request features TakeProfit and its placing is not allowed if(tp>0 && !symbol_obj.IsTakeProfitOrdersAllowed()) { //--- add the error code to the list this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_SYM_TP_ORDER_DISABLED); //--- If the EA behavior during the trading error is set to "abort trading operation", //--- return 'false' - there is no point in further checks if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_BREAK) return false; //--- If the EA behavior during a trading error is set to //--- "correct parameters" or "create a pending request", //--- write 'false' to the result else res &=false; } } //--- Beim Schließen durch einen Gegenposition else if(action_type==ACTION_TYPE_CLOSE_BY) { //--- When closing by an opposite position is disabled if(!symbol_obj.IsCloseByOrdersAllowed()) { //--- write the error code to the list and return 'false' this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_CLOSE_BY_ORDERS_DISABLED); return false; } } return res; } //+------------------------------------------------------------------+
Bei der Methode, die die Parameterwerte nach StopLevel und FreezeLevel zurückgibt, fügen Sie zu jedem erkannten Fehler das Flag hinzu, das angibt, dass ein Fehler in der Fehlerliste angezeigt werden soll:
//+------------------------------------------------------------------+ //| Check parameter values by StopLevel and FreezeLevel | //+------------------------------------------------------------------+ bool CTrading::CheckLevels(const ENUM_ACTION_TYPE action, const ENUM_ORDER_TYPE order_type, double price, double limit, double sl, double tp, const CSymbol *symbol_obj, const string source_method) { //--- the result of conducting all checks bool res=true; //--- StopLevel //--- If this is not a position closure/order removal if(action!=ACTION_TYPE_CLOSE && action!=ACTION_TYPE_CLOSE_BY) { //--- When placing a pending order if(action>ACTION_TYPE_SELL) { //--- If the placement distance in points is less than StopLevel if(!this.CheckPriceByStopLevel(order_type,price,symbol_obj)) { //--- add the error code to the list and write 'false' to the result this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_PR_LESS_STOP_LEVEL); res &=false; } } //--- If StopLoss is present if(sl>0) { //--- If StopLoss distance in points from the open price is less than StopLevel double price_open=(action==ACTION_TYPE_BUY_STOP_LIMIT || action==ACTION_TYPE_SELL_STOP_LIMIT ? limit : price); if(!this.CheckStopLossByStopLevel(order_type,price_open,sl,symbol_obj)) { //--- add the error code to the list and write 'false' to the result this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_SL_LESS_STOP_LEVEL); res &=false; } } //--- If TakeProfit is present if(tp>0) { double price_open=(action==ACTION_TYPE_BUY_STOP_LIMIT || action==ACTION_TYPE_SELL_STOP_LIMIT ? limit : price); //--- If TakeProfit distance in points from the open price is less than StopLevel if(!this.CheckTakeProfitByStopLevel(order_type,price_open,tp,symbol_obj)) { //--- add the error code to the list and write 'false' to the result this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_TP_LESS_STOP_LEVEL); res &=false; } } } //--- FreezeLevel //--- If this is a position closure/order removal/modification if(action>ACTION_TYPE_SELL_STOP_LIMIT) { //--- If this is a position if(order_type<ORDER_TYPE_BUY_LIMIT) { //--- StopLoss modification if(sl>0) { //--- If the distance from the price to StopLoss is less than FreezeLevel if(!this.CheckStopLossByFreezeLevel(order_type,sl,symbol_obj)) { //--- add the error code to the list and write 'false' to the result this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_SL_LESS_FREEZE_LEVEL); res &=false; } } //--- TakeProfit modification if(tp>0) { //--- If the distance from the price to StopLoss is less than FreezeLevel if(!this.CheckTakeProfitByFreezeLevel(order_type,tp,symbol_obj)) { //--- add the error code to the list and write 'false' to the result this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_TP_LESS_FREEZE_LEVEL); res &=false; } } } //--- If this is a pending order else { //--- Placement price modification if(price>0) { //--- If the distance from the price to the order activation price is less than FreezeLevel if(!this.CheckPriceByFreezeLevel(order_type,price,symbol_obj)) { //--- add the error code to the list and write 'false' to the result this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_PR_LESS_FREEZE_LEVEL); res &=false; } } } } return res; } //+------------------------------------------------------------------+
Bei der Methode zur Festlegung der Preise für Handelsanfragen fügen Sie die Aktualisierung der Preise hinzu und, im Falle eines Aktualisierungsfehlers, geben Sie den entsprechenden Fehlercode zurück:
//+------------------------------------------------------------------+ //| Set trading request prices | //+------------------------------------------------------------------+ template <typename PR,typename SL,typename TP,typename PL> bool CTrading::SetPrices(const ENUM_ORDER_TYPE action,const PR price,const SL sl,const TP tp,const PL limit,const string source_method,CSymbol *symbol_obj) { //--- Reset prices ::ZeroMemory(this.m_request); //--- Update all data by symbol if(!symbol_obj.RefreshRates()) { this.AddErrorCodeToList(10021); return false; } //--- Open/close price
Auch die Preiskalkulation in der Methode zur Festlegung der Preise für Handelsaufträge wurde geändert:
//--- Calculate the order price switch((int)action) { //--- Pending order case ORDER_TYPE_BUY_LIMIT : this.m_request.price=::NormalizeDouble(symbol_obj.Ask()-price*symbol_obj.Point(),symbol_obj.Digits()); break; case ORDER_TYPE_BUY_STOP : case ORDER_TYPE_BUY_STOP_LIMIT : this.m_request.price=::NormalizeDouble(symbol_obj.Ask()+price*symbol_obj.Point(),symbol_obj.Digits()); break; case ORDER_TYPE_SELL_LIMIT : this.m_request.price=::NormalizeDouble(symbol_obj.BidLast()+price*symbol_obj.Point(),symbol_obj.Digits()); break; case ORDER_TYPE_SELL_STOP : case ORDER_TYPE_SELL_STOP_LIMIT : this.m_request.price=::NormalizeDouble(symbol_obj.BidLast()-price*symbol_obj.Point(),symbol_obj.Digits()); break; //--- Default - current position open prices default : this.m_request.price= ( this.DirectionByActionType((ENUM_ACTION_TYPE)action)==ORDER_TYPE_BUY ? ::NormalizeDouble(symbol_obj.Ask(),symbol_obj.Digits()) : ::NormalizeDouble(symbol_obj.BidLast(),symbol_obj.Digits()) ); break; }
Jetzt wurde die Klassenmethode Bid() durch die Methode BidLast() ersetzt, die je nach Diagrammkonstruktionsmodus entweder den Bid-Preis oder den letzten Preis zurückgibt.
Eine Methode, die den Spread-Multiplikator für Handelsobjekte aller Symbole festlegt:
//+------------------------------------------------------------------+ //| Set the spread multiplier | //| for trading objects of all symbols | //+------------------------------------------------------------------+ void CTrading::SetSpreadMultiplier(const uint value=1,const string symbol=NULL) { CSymbol *symbol_obj=NULL; if(symbol==NULL) { CArrayObj *list=this.m_symbols.GetList(); if(list==NULL || list.Total()==0) return; int total=list.Total(); for(int i=0;i<total;i++) { symbol_obj=list.At(i); if(symbol_obj==NULL) continue; CTradeObj *trade_obj=symbol_obj.GetTradeObj(); if(trade_obj==NULL) continue; trade_obj.SetSpreadMultiplier(value); } } else { CTradeObj *trade_obj=this.GetTradeObjBySymbol(symbol,DFUN); if(trade_obj==NULL) return; trade_obj.SetSpreadMultiplier(value); } } //+------------------------------------------------------------------+
Die Methode erhält den Multiplikator (Standard ist 1) und einen Symbolnamen (Standard ist NULL).
Wenn NULL für das Symbol übergeben wird, wird der Multiplikator für Handelsobjekte aller Symbole der bestehenden Symbolkollektion gesetzt.
Andernfalls wird der Wert einem Handelsobjekt eines Symbols zugewiesen, dessen Name an die Methode übergeben wurde.
Aufgrund der neuen Fehlerbehandlung wurden alle Handelsmethoden verfeinert.
Betrachten wir den Code der Methode zur Eröffnung einer Kaufposition:
//+------------------------------------------------------------------+ //| Open Buy position | //+------------------------------------------------------------------+ template<typename SL,typename TP> bool CTrading::OpenBuy(const double volume, const string symbol, const ulong magic=ULONG_MAX, const SL sl=0, const TP tp=0, const string comment=NULL, const ulong deviation=ULONG_MAX) { //--- Set the trading request result as 'true' and the error flag as "no errors" bool res=true; this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_NO_ERROR; ENUM_ACTION_TYPE action=ACTION_TYPE_BUY; ENUM_ORDER_TYPE order_type=ORDER_TYPE_BUY; //--- Get a symbol object by a symbol name. If failed to get CSymbol *symbol_obj=this.m_symbols.GetSymbolObjByName(symbol); //--- If failed to get - write the "internal error" flag, display the message in the journal and return 'false' if(symbol_obj==NULL) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR; if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_SYM_OBJ)); return false; } //--- get a trading object from a symbol object CTradeObj *trade_obj=symbol_obj.GetTradeObj(); //--- If failed to get - write the "internal error" flag, display the message in the journal and return 'false' if(trade_obj==NULL) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR; if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TRADE_OBJ)); return false; } //--- Set the prices //--- If failed to set - write the "internal error" flag, display the message in the journal and return 'false' if(!this.SetPrices(order_type,0,sl,tp,0,DFUN,symbol_obj)) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR; if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(10021)); return false; } //--- Write the volume to the request structure this.m_request.volume=volume; //--- Get the method of handling errors from the CheckErrors() method while checking for errors ENUM_ERROR_CODE_PROCESSING_METHOD method=this.CheckErrors(this.m_request.volume,symbol_obj.Ask(),action,order_type,symbol_obj,trade_obj,DFUN,0,this.m_request.sl,this.m_request.tp); //--- In case of trading limitations, funds insufficiency, //--- if there are limitations by StopLevel or FreezeLevel ... if(method!=ERROR_CODE_PROCESSING_METHOD_OK) { //--- If trading is disabled completely, display a journal message, play the error sound and exit if(method==ERROR_CODE_PROCESSING_METHOD_DISABLE) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(CMessage::Text(MSG_LIB_TEXT_TRADING_DISABLE)); if(this.IsUseSounds()) trade_obj.PlaySoundError(action,order_type); return false; } //--- If the check result is "abort trading operation" - display a journal message, play the error sound and exit if(method==ERROR_CODE_PROCESSING_METHOD_EXIT) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(CMessage::Text(MSG_LIB_TEXT_TRADING_OPERATION_ABORTED)); if(this.IsUseSounds()) trade_obj.PlaySoundError(action,order_type); return false; } //--- If the check result is "waiting", display the message in the journal if(method==ERROR_CODE_PROCESSING_METHOD_EXIT) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(CMessage::Text(MSG_LIB_TEXT_CREATE_PENDING_REQUEST)); //--- Instead of creating a pending request, we temporarily wait the required time period (the CheckErrors() method result is returned) ::Sleep(method); //--- after waiting, update all data symbol_obj.Refresh(); } //--- If the check result is "create a pending request", do nothing temporarily if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_PENDING_REQUEST) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(CMessage::Text(MSG_LIB_TEXT_CREATE_PENDING_REQUEST)); } } //--- Send the request res=trade_obj.OpenPosition(POSITION_TYPE_BUY,this.m_request.volume,this.m_request.sl,this.m_request.tp,magic,comment,deviation); //--- If the request is successful, play the success sound set for a symbol trading object for this type of trading operation if(res) { if(this.IsUseSounds()) trade_obj.PlaySoundSuccess(action,order_type); } //--- If the request is not successful, play the error sound set for a symbol trading object for this type of trading operation else { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(trade_obj.GetResultRetcode())); if(this.IsUseSounds()) trade_obj.PlaySoundError(action,order_type); } //--- Return the result of sending a trading request in a symbol trading object return res; } //+------------------------------------------------------------------+
Alle Klarstellungen werden in den Code-Kommentaren im Detail vorgenommen. Andere Handelsmethoden wurden in ähnlicher Weise verbessert. Ich hoffe, hier ist alles klar. In jedem Fall können Sie gerne den Abschnitt Kommentare verwenden.
Die Methoden, die die berechneten, gültigen Preise der Stop- und Pending-Orders zurückgeben:
//+------------------------------------------------------------------+ //| Rückgabe des korrekten StopLevels relativ zum StopLevel | //+------------------------------------------------------------------+ double CTrading::CorrectStopLoss(const ENUM_ORDER_TYPE order_type,const double price_set,const double stop_loss,const CSymbol *symbol_obj,const uint spread_multiplier=1) { if(stop_loss==0) return 0; uint lv=(symbol_obj.TradeStopLevel()==0 ? symbol_obj.Spread()*spread_multiplier : symbol_obj.TradeStopLevel()); double price=(order_type==ORDER_TYPE_BUY ? symbol_obj.BidLast() : order_type==ORDER_TYPE_SELL ? symbol_obj.Ask() : price_set); return (this.DirectionByActionType((ENUM_ACTION_TYPE)order_type)==ORDER_TYPE_BUY ? ::NormalizeDouble(fmin(price-lv*symbol_obj.Point(),stop_loss),symbol_obj.Digits()) : ::NormalizeDouble(fmax(price+lv*symbol_obj.Point(),stop_loss),symbol_obj.Digits()) ); } //+------------------------------------------------------------------+ //| Rückgabe des korrekten TakeProfit relativ zum StopLevel | //+------------------------------------------------------------------+ double CTrading::CorrectTakeProfit(const ENUM_ORDER_TYPE order_type,const double price_set,const double take_profit,const CSymbol *symbol_obj,const uint spread_multiplier=1) { if(take_profit==0) return 0; uint lv=(symbol_obj.TradeStopLevel()==0 ? symbol_obj.Spread()*spread_multiplier : symbol_obj.TradeStopLevel()); double price=(order_type==ORDER_TYPE_BUY ? symbol_obj.BidLast() : order_type==ORDER_TYPE_SELL ? symbol_obj.Ask() : price_set); return (this.DirectionByActionType((ENUM_ACTION_TYPE)order_type)==ORDER_TYPE_BUY ? ::NormalizeDouble(fmax(price+lv*symbol_obj.Point(),take_profit),symbol_obj.Digits()) : ::NormalizeDouble(fmin(price-lv*symbol_obj.Point(),take_profit),symbol_obj.Digits()) ); } //+------------------------------------------------------------------+ //| Rückgabe des Preises der korrekten Order-Platzierung | //| relative to StopLevel | //+------------------------------------------------------------------+ double CTrading::CorrectPricePending(const ENUM_ORDER_TYPE order_type,const double price_set,const double price,const CSymbol *symbol_obj,const uint spread_multiplier=1) { uint lv=(symbol_obj.TradeStopLevel()==0 ? symbol_obj.Spread()*spread_multiplier : symbol_obj.TradeStopLevel()); double pp=0; switch((int)order_type) { case ORDER_TYPE_BUY_LIMIT : pp=(price==0 ? symbol_obj.Ask() : price); return ::NormalizeDouble(fmin(pp-lv*symbol_obj.Point(),price_set),symbol_obj.Digits()); case ORDER_TYPE_BUY_STOP : case ORDER_TYPE_BUY_STOP_LIMIT : pp=(price==0 ? symbol_obj.Ask() : price); return ::NormalizeDouble(fmax(pp+lv*symbol_obj.Point(),price_set),symbol_obj.Digits()); case ORDER_TYPE_SELL_LIMIT : pp=(price==0 ? symbol_obj.BidLast() : price); return ::NormalizeDouble(fmax(pp+lv*symbol_obj.Point(),price_set),symbol_obj.Digits()); case ORDER_TYPE_SELL_STOP : case ORDER_TYPE_SELL_STOP_LIMIT : pp=(price==0 ? symbol_obj.BidLast() : price); return ::NormalizeDouble(fmin(pp-lv*symbol_obj.Point(),price_set),symbol_obj.Digits()); default : if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_INVALID_ORDER_TYPE),::EnumToString(order_type)); return 0; } } //+------------------------------------------------------------------+
Hier sollte auch ohne Code-Kommentare alles klar sein — die an die Methoden übergebenen Preise werden mit dem Preis verglichen, der als StopLevel-Abstand vom Eröffnungspreis erzielt wird. Der gültige (je nach Auftragsart höhere/niedrigere) Preis wird an das aufrufende Programm zurückgegeben.
Die Methode, die das Volumen zurückgibt, zu dem eine Position eröffnet werden kann:
//+------------------------------------------------------------------+ //| Return the volume, at which it is possible to open a position | //+------------------------------------------------------------------+ double CTrading::CorrectVolume(const double price,const ENUM_ORDER_TYPE order_type,CSymbol *symbol_obj,const string source_method) { //--- If funds are insufficient for the minimum lot, inform of that and return zero if(!this.CheckMoneyFree(symbol_obj.LotsMin(),price,order_type,symbol_obj,source_method)) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(CMessage::Text(MSG_LIB_TEXT_NOT_POSSIBILITY_CORRECT_LOT)); return 0; } //--- Update account and symbol data this.m_account.Refresh(); symbol_obj.RefreshRates(); //--- Calculate the lot, which is closest to the acceptable one double vol=symbol_obj.NormalizedLot(this.m_account.Equity()*this.m_account.Leverage()/symbol_obj.TradeContractSize()/(symbol_obj.CurrencyBase()=="USD" ? 1.0 : symbol_obj.BidLast())); //--- Calculate a sufficient lot double margin=this.m_account.MarginForAction(order_type,symbol_obj.Name(),1.0,price); if(margin!=EMPTY_VALUE) vol=symbol_obj.NormalizedLot(this.m_account.MarginFree()/margin); //--- If the calculated lot is invalid or the margin calculation returns an error if(!this.CheckMoneyFree(vol,price,order_type,symbol_obj,source_method)) { //--- In the do-while loop, while the calculated valid volume exceeds the minimum lot do { //--- Subtract the minimum lot from the valid lot value vol-=symbol_obj.LotsStep(); //--- If the calculated lot allows opening a position/setting an order, return the lot value if(this.CheckMoneyFree(symbol_obj.NormalizedLot(vol),price,order_type,symbol_obj,source_method)) return vol; } while(vol>symbol_obj.LotsMin() && !::IsStopped()); } //--- If the lot is calculated correctly, return the calculated lot else return vol; //--- If the current stage is reached, the funds are insufficient. Inform of that and return zero if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(CMessage::Text(MSG_LIB_TEXT_NOT_POSSIBILITY_CORRECT_LOT)); return 0; } //+------------------------------------------------------------------+
Der Code wird hier auch kommentiert.
Zuerst prüfen wir die Fähigkeit, mit einem Mindestlot zu öffnen. Ist dies nicht möglich, sind weitere Berechnungen sinnlos — geben Sie Null zurück.
Als Nächstes berechnen wir die ungefähr zulässige Losgröße (um im Falle einer möglichen Anpassung nicht die gewünschte Losgröße von ihrem Maximalwert "zuwählen").
Als Nächstes berechnen Sie die maximale Losgröße, das die Eröffnung einer Position unter Verwendung aller verfügbaren Funktionen erlaubt. Warum das? Wenn die Mittel für die Eröffnung einer Position nicht ausreichen, bedeutet dies, dass das erforderliche Volumen groß war, was bedeutet, dass wir das maximal mögliche Volumen berechnen müssen.
In dieser Berechnung verwenden wir die Funktion OrderCalcMargin(), die im Falle eines Fehlers false zurückgeben kann, während die Methode MarginForAction() der Klasse CAccount, die diese Funktion verwendet, EMPTY_VALUE entsprechend dem konstanten Wert DBL_MAX (der maximale Wert, der durch den Typ doppelt dargestellt werden kann) zurückgibt. Wenn wir diesen Wert erhalten, liegt ein Fehler vor, und das Los wurde nicht berechnet.
In diesem Fall (nicht nur im Falle eines Fehlers, sondern auch bei der Überprüfung der Berechnungsgültigkeit) verwenden wir die "Auswahl" eines erforderlichen maximalen Loses, indem wir einfach den Losschritt vom berechneten maximal möglichen Volumen in der Handelsorder subtrahieren. Dazu benötigen wir das zuvor berechnete ungefähres verfügbares Volumen. Wenn wir das exakte Volumen nicht berechnen konnten, beginnt die Lot-Abnahme-Schleife mit dem nächstgelegenen Lot (und nicht mit dem für das Symbol festgelegten Maximal-Lot), wodurch die Anzahl der Schleifen-Iterationen stark reduziert wird.
Übrigens habe ich bei der Prüfung nicht die Funktionsfehler OrderCalcMargin() bei der Berechnung des Loses erhalten. Die ungültigen Berechnungen sind aber trotzdem aufgetreten (ungefähr, um eine Schrittweite der Losgrößenänderung).
Damit sind die Änderungen und Verbesserungen der Handelsklasse abgeschlossen.
Tests
Um den Test durchzuführen, verwenden wir den EA aus dem vorherigen Artikel und speichern ihn unter \MQL5\Experts\TestDoEasy\ Part24\ unter dem Namen TestDoEasyPart24.mq5.
Fügen wir das Flag für die Arbeit im Strategietester zur Liste der globalen Variablen hinzufügen:
//--- Globale Variablen CEngine engine; SDataButt butt_data[TOTAL_BUTT]; string prefix; double lot; double withdrawal=(InpWithdrawal<0.1 ? 0.1 : InpWithdrawal); ulong magic_number; uint stoploss; uint takeprofit; uint distance_pending; uint distance_stoplimit; uint slippage; bool trailing_on; double trailing_stop; double trailing_step; uint trailing_start; uint stoploss_to_modify; uint takeprofit_to_modify; int used_symbols_mode; string used_symbols; string array_used_symbols[]; bool testing; //+------------------------------------------------------------------+
Setzen Sie in OnInit() den Wert des Flags für die Arbeit im Strategietester:
//+------------------------------------------------------------------+ //| Initialisierungsfunktion des Experten | //+------------------------------------------------------------------+ int OnInit() { //--- Calling the function displays the list of enumeration constants in the journal //--- (the list is set in the strings 22 and 25 of the DELib.mqh file) for checking the constants validity //EnumNumbersTest(); //--- Set EA global variables prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_"; testing=engine.IsTester(); for(int i=0;i<TOTAL_BUTT;i++) { butt_data[i].name=prefix+EnumToString((ENUM_BUTTONS)i); butt_data[i].text=EnumToButtText((ENUM_BUTTONS)i); }
Um während der Arbeit im Tester Ereignisse an die Ereignisbehandlung OnDoEasyEvent() der Bibliothek zu senden, benötigen wir die spezielle Funktion EventsHandling().
Sie wurde geringfügig verbessert:
//+------------------------------------------------------------------+ //| Working with events in the tester | //+------------------------------------------------------------------+ void EventsHandling(void) { //--- If a trading event is present if(engine.IsTradeEvent()) { //--- Number of trading events occurred simultaneously int total=engine.GetTradeEventsTotal(); for(int i=0;i<total;i++) { //--- Get the next event from the list of simultaneously occurred events by index CEventBaseObj *event=engine.GetTradeEventByIndex(i); if(event==NULL) continue; long lparam=i; double dparam=event.DParam(); string sparam=event.SParam(); OnDoEasyEvent(CHARTEVENT_CUSTOM+event.ID(),lparam,dparam,sparam); } } //--- If there is an account event if(engine.IsAccountsEvent()) { //--- Get the list of all account events occurred simultaneously CArrayObj* list=engine.GetListAccountEvents(); if(list!=NULL) { //--- Get the next event in a loop int total=list.Total(); for(int i=0;i<total;i++) { //--- take an event from the list CEventBaseObj *event=list.At(i); if(event==NULL) continue; //--- Send an event to the event handler long lparam=event.LParam(); double dparam=event.DParam(); string sparam=event.SParam(); OnDoEasyEvent(CHARTEVENT_CUSTOM+event.ID(),lparam,dparam,sparam); } } } //--- If there is a symbol collection event if(engine.IsSymbolsEvent()) { //--- Get the list of all symbol events occurred simultaneously CArrayObj* list=engine.GetListSymbolsEvents(); if(list!=NULL) { //--- Get the next event in a loop int total=list.Total(); for(int i=0;i<total;i++) { //--- take an event from the list CEventBaseObj *event=list.At(i); if(event==NULL) continue; //--- Send an event to the event handler long lparam=event.LParam(); double dparam=event.DParam(); string sparam=event.SParam(); OnDoEasyEvent(CHARTEVENT_CUSTOM+event.ID(),lparam,dparam,sparam); } } } } //+------------------------------------------------------------------+
Die Kommentare im Code sind hier ziemlich selbsterklärend.
Da wir nun die Liste der neuen Handelsereignisse erstellt haben, erhalten wir jedes Ereignis aus der Liste aller neuen Handelsereignisse im Ereignis-Handler der Bibliothek OnDoEasyEvent() durch einen Ereignisindex und zeigen einfach die Beschreibungen aller aus der Liste erhaltenen Ereignisse im Journal an:
//+------------------------------------------------------------------+ //| Handling DoEasy library events | //+------------------------------------------------------------------+ void OnDoEasyEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { int idx=id-CHARTEVENT_CUSTOM; //--- Retrieve (1) event time milliseconds, (2) reason and (3) source from lparam, as well as (4) set the exact event time ushort msc=engine.EventMSC(lparam); ushort reason=engine.EventReason(lparam); ushort source=engine.EventSource(lparam); long time=TimeCurrent()*1000+msc; //--- Handling symbol events if(source==COLLECTION_SYMBOLS_ID) { CSymbol *symbol=engine.GetSymbolObjByName(sparam); if(symbol==NULL) return; //--- Number of decimal places in the event value - in case of a 'long' event, it is 0, otherwise - Digits() of a symbol int digits=(idx<SYMBOL_PROP_INTEGER_TOTAL ? 0 : symbol.Digits()); //--- Event text description string id_descr=(idx<SYMBOL_PROP_INTEGER_TOTAL ? symbol.GetPropertyDescription((ENUM_SYMBOL_PROP_INTEGER)idx) : symbol.GetPropertyDescription((ENUM_SYMBOL_PROP_DOUBLE)idx)); //--- Property change text value string value=DoubleToString(dparam,digits); //--- Check event reasons and display its description in the journal if(reason==BASE_EVENT_REASON_INC) { Print(symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_DEC) { Print(symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_MORE_THEN) { Print(symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_LESS_THEN) { Print(symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_EQUALS) { Print(symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } } //--- Handling account events else if(source==COLLECTION_ACCOUNT_ID) { CAccount *account=engine.GetAccountCurrent(); if(account==NULL) return; //--- Number of decimal places in the event value - in case of a 'long' event, it is 0, otherwise - Digits() of a symbol int digits=int(idx<ACCOUNT_PROP_INTEGER_TOTAL ? 0 : account.CurrencyDigits()); //--- Event text description string id_descr=(idx<ACCOUNT_PROP_INTEGER_TOTAL ? account.GetPropertyDescription((ENUM_ACCOUNT_PROP_INTEGER)idx) : account.GetPropertyDescription((ENUM_ACCOUNT_PROP_DOUBLE)idx)); //--- Property change text value string value=DoubleToString(dparam,digits); //--- Checking event reasons and handling the increase of funds by a specified value, //--- In case of a property value increase if(reason==BASE_EVENT_REASON_INC) { //--- Display an event in the journal Print(account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); //--- if this is an equity increase if(idx==ACCOUNT_PROP_EQUITY) { //--- Abrufen der Liste aller offenen Positionen CArrayObj* list_positions=engine.GetListMarketPosition(); //--- Select positions with the profit exceeding zero list_positions=CSelect::ByOrderProperty(list_positions,ORDER_PROP_PROFIT_FULL,0,MORE); if(list_positions!=NULL) { //--- Sortieren der Liste nach Gewinn unter Berücksichtigung von Kommission und Swap list_positions.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Get the position index with the highest profit int index=CSelect::FindOrderMax(list_positions,ORDER_PROP_PROFIT_FULL); if(index>WRONG_VALUE) { COrder* position=list_positions.At(index); if(position!=NULL) { //--- Get a ticket of a position with the highest profit and close the position by a ticket engine.ClosePosition(position.Ticket()); } } } } } //--- Other events are simply displayed in the journal if(reason==BASE_EVENT_REASON_DEC) { Print(account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_MORE_THEN) { Print(account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_LESS_THEN) { Print(account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_EQUALS) { Print(account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } } //--- Handling market watch window events else if(idx>MARKET_WATCH_EVENT_NO_EVENT && idx<SYMBOL_EVENTS_NEXT_CODE) { //--- Market Watch window event string descr=engine.GetMWEventDescription((ENUM_MW_EVENT)idx); string name=(idx==MARKET_WATCH_EVENT_SYMBOL_SORT ? "" : ": "+sparam); Print(TimeMSCtoString(lparam)," ",descr,name); } //--- Handling trading events else if(idx>TRADE_EVENT_NO_EVENT && idx<TRADE_EVENTS_NEXT_CODE) { //--- Get the list of trading events CArrayObj *list=engine.GetListAllOrdersEvents(); if(list==NULL) return; //--- get the event index shift relative to the end of the list //--- in the tester, the shift is passed by the lparam parameter to the event handler //--- outside the tester, events are sent one by one and handled in OnChartEvent() int shift=(testing ? (int)lparam : 0); CEvent *event=list.At(list.Total()-1-shift); if(event==NULL) return; //--- Accrue the credit if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_CREDIT) { Print(DFUN,event.TypeEventDescription()); } //--- Additional charges if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_CHARGE) { Print(DFUN,event.TypeEventDescription()); } //--- Correction if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_CORRECTION) { Print(DFUN,event.TypeEventDescription()); } //--- Enumerate bonuses if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_BONUS) { Print(DFUN,event.TypeEventDescription()); } //--- Additional commissions if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION) { Print(DFUN,event.TypeEventDescription()); } //--- Daily commission if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION_DAILY) { Print(DFUN,event.TypeEventDescription()); } //--- Monthly commission if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION_MONTHLY) { Print(DFUN,event.TypeEventDescription()); } //--- Daily agent commission if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION_AGENT_DAILY) { Print(DFUN,event.TypeEventDescription()); } //--- Monthly agent commission if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION_AGENT_MONTHLY) { Print(DFUN,event.TypeEventDescription()); } //--- Interest rate if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_INTEREST) { Print(DFUN,event.TypeEventDescription()); } //--- Canceled buy deal if(event.TypeEvent()==TRADE_EVENT_BUY_CANCELLED) { Print(DFUN,event.TypeEventDescription()); } //--- Canceled sell deal if(event.TypeEvent()==TRADE_EVENT_SELL_CANCELLED) { Print(DFUN,event.TypeEventDescription()); } //--- Dividend operations if(event.TypeEvent()==TRADE_EVENT_DIVIDENT) { Print(DFUN,event.TypeEventDescription()); } //--- Accrual of franked dividend if(event.TypeEvent()==TRADE_EVENT_DIVIDENT_FRANKED) { Print(DFUN,event.TypeEventDescription()); } //--- Tax charges if(event.TypeEvent()==TRADE_EVENT_TAX) { Print(DFUN,event.TypeEventDescription()); } //--- Replenishing account balance if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_BALANCE_REFILL) { Print(DFUN,event.TypeEventDescription()); } //--- Withdrawing funds from balance if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL) { Print(DFUN,event.TypeEventDescription()); } //--- Pending-Order platziert if(event.TypeEvent()==TRADE_EVENT_PENDING_ORDER_PLASED) { Print(DFUN,event.TypeEventDescription()); } //--- Pending-Order entfernt if(event.TypeEvent()==TRADE_EVENT_PENDING_ORDER_REMOVED) { Print(DFUN,event.TypeEventDescription()); } //--- Pending order activated by price if(event.TypeEvent()==TRADE_EVENT_PENDING_ORDER_ACTIVATED) { Print(DFUN,event.TypeEventDescription()); } //--- Pending order partially activated by price if(event.TypeEvent()==TRADE_EVENT_PENDING_ORDER_ACTIVATED_PARTIAL) { Print(DFUN,event.TypeEventDescription()); } //--- Position opened if(event.TypeEvent()==TRADE_EVENT_POSITION_OPENED) { Print(DFUN,event.TypeEventDescription()); } //--- Position opened partially if(event.TypeEvent()==TRADE_EVENT_POSITION_OPENED_PARTIAL) { Print(DFUN,event.TypeEventDescription()); } //--- Position closed if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED) { Print(DFUN,event.TypeEventDescription()); } //--- Position closed by an opposite one if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_BY_POS) { Print(DFUN,event.TypeEventDescription()); } //--- Position closed by StopLoss if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_BY_SL) { Print(DFUN,event.TypeEventDescription()); } //--- Position closed by TakeProfit if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_BY_TP) { Print(DFUN,event.TypeEventDescription()); } //--- Position reversal by a new deal (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_REVERSED_BY_MARKET) { Print(DFUN,event.TypeEventDescription()); } //--- Position reversal by activating a pending order (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_REVERSED_BY_PENDING) { Print(DFUN,event.TypeEventDescription()); } //--- Position reversal by partial market order execution (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_REVERSED_BY_MARKET_PARTIAL) { Print(DFUN,event.TypeEventDescription()); } //--- Position reversal by activating a pending order (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_REVERSED_BY_PENDING_PARTIAL) { Print(DFUN,event.TypeEventDescription()); } //--- Added volume to a position by a new deal (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET) { Print(DFUN,event.TypeEventDescription()); } //--- Added volume to a position by partial execution of a market order (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET_PARTIAL) { Print(DFUN,event.TypeEventDescription()); } //--- Added volume to a position by activating a pending order (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING) { Print(DFUN,event.TypeEventDescription()); } //--- Added volume to a position by partial activation of a pending order (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING_PARTIAL) { Print(DFUN,event.TypeEventDescription()); } //--- Position closed partially if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL) { Print(DFUN,event.TypeEventDescription()); } //--- Position partially closed by an opposite one if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS) { Print(DFUN,event.TypeEventDescription()); } //--- Position closed partially by StopLoss if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_SL) { Print(DFUN,event.TypeEventDescription()); } //--- Position closed partially by TakeProfit if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_TP) { Print(DFUN,event.TypeEventDescription()); } //--- StopLimit order activation if(event.TypeEvent()==TRADE_EVENT_TRIGGERED_STOP_LIMIT_ORDER) { Print(DFUN,event.TypeEventDescription()); } //--- Changing order price if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE) { Print(DFUN,event.TypeEventDescription()); } //--- Changing order and StopLoss price if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE_STOP_LOSS) { Print(DFUN,event.TypeEventDescription()); } //--- Changing order and TakeProfit price if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE_TAKE_PROFIT) { Print(DFUN,event.TypeEventDescription()); } //--- Changing order, StopLoss and TakeProfit price if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE_STOP_LOSS_TAKE_PROFIT) { Print(DFUN,event.TypeEventDescription()); } //--- Changing order's StopLoss and TakeProfit price if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_STOP_LOSS_TAKE_PROFIT) { Print(DFUN,event.TypeEventDescription()); } //--- Changing order's StopLoss if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_STOP_LOSS) { Print(DFUN,event.TypeEventDescription()); } //--- Changing order's TakeProfit if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_TAKE_PROFIT) { Print(DFUN,event.TypeEventDescription()); } //--- Changing position's StopLoss and TakeProfit if(event.TypeEvent()==TRADE_EVENT_MODIFY_POSITION_STOP_LOSS_TAKE_PROFIT) { Print(DFUN,event.TypeEventDescription()); } //--- Changing position StopLoss if(event.TypeEvent()==TRADE_EVENT_MODIFY_POSITION_STOP_LOSS) { Print(DFUN,event.TypeEventDescription()); } //--- Changing position TakeProfit if(event.TypeEvent()==TRADE_EVENT_MODIFY_POSITION_TAKE_PROFIT) { Print(DFUN,event.TypeEventDescription()); } } } //+------------------------------------------------------------------+
Der Einfachheit halber erhalten wir ein Ereignis aus der Liste einfach durch seinen Index (für den Tester wird der Index im Parameter lparam durch die Funktion EventsHandling() übergeben, während bei einem Demo- und realen Konten der Index immer gleich Null ist, da jedes Ereignis als unabhängiges Ereignis an OnChartEvent() und nicht aus der Liste gesendet wird) und zeigen die Beschreibung eines erhaltenen Ereignisses im Journal an.
Sie müssen entscheiden, wie darauf zu regieren ist. Sie können die Behandlung direkt im gleichen Code implementieren oder die Liste der Ereignisflags deklarieren, indem Sie hier die Flags der aufgetretenen Ereignisse setzen, während die eigentliche Behandlung in separaten Funktionen durchgeführt wird.
Dies sind alle Änderungen und Verbesserungen, die notwendig sind, um alle gleichzeitig aufgetretenen Handelsereignisse zu kontrollieren. Die Bibliothek enthält bereits alle notwendigen Dinge für die automatische Korrektur von Fehlern der Handelsauftragsparameter. Im EA sind (vorerst) keine Änderungen erforderlich. Nachdem wir alle Möglichkeiten der Fehlerbehandlung geschaffen haben, werden wir im weiteren Verlauf eine zusätzliche Eingabe einführen, die das Verhalten des EA bei Fehlern angibt.
Kompilieren Sie den EA und starten Sie ihn im Tester. Platzieren Sie mehrere Pending-Orders und entfernen Sie alle in einer einzigen Schleife:
Der EA zeigt vier Ereignisse im Journal an. Diese Ereignisse traten auf, wenn vier Pending-Orders in einer einzigen Schleife entfernt wurden, nachdem Sie auf "Delete pending" geklickt hatten.
Lassen Sie uns nun in den EA-Einstellungen im Strategietester eine größere Losgröße einstellen (z.B. 100.0) und versuchen, einen Pending-Order zu platzieren oder eine Position zu eröffnen:
Nachdem wir versucht haben, eine Pending-Order zu platzieren und eine Position mit dem Volumen vom 100,0 Lots zu eröffnen, haben wir Journalmeldungen erhalten, die über unzureichende Mittel und die Anpassung des Volumens informieren. Die Order wurde gesetzt und die Position danach eröffnet.
Was kommt als Nächstes?
Im nächsten Artikel werden wir die Behandlung von Fehlern, die vom Handelsserver zurückgegeben werden, implementieren.
Alle Dateien der aktuellen Version der Bibliothek sind unten zusammen mit den Dateien der Test-EAs angehängt, die Sie testen und herunterladen 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
Teil 10. Kompatibilität mit MQL4 - Ereignisse der Positionseröffnung und Aktivierung von Pending-Orders
Teil 11. Kompatibilität mit MQL4 - Ereignisse des Schließens von Positionen
Teil 12. Objektklasse "Account" und die Kollektion von Konto-Objekten
Teil 13. Das Objekt der Kontoereignisse
Teil 14. Das Symbolobjekt
Teil 15. Die Kollektion der Symbolobjekte
Teil 16. Ereignisse der Kollektionssymbole
Teil 17. Interaktivität von Bibliotheksobjekten
Teil 18. Interaktivität des Kontos und aller anderen Bibliotheksobjekt
Teil 19. Klassenbibliothek für Nachrichten
Teil 20. Erstellen und Speichern von Programmressourcen
Teil 21. Handelsklassen - Plattformübergreifendes Basis-Handelsobjekt
Teil 22. Handelsklassen - Basisklasse des Handels, Verifikation der Einschränkungen
Teil 23. Handelsklasse - Basisklasse des Handels, Verifikation der Parameter
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/7326





- 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.