Universal Expert Advisor: Das Event-Modell und der Trading-Strategie Prototyp (Part 2)
Inhalt
- Einleitung
- Das Event-Modell, welches auf einer zentralen Verarbeitung basiert, ENUM_MARKET_EVENT_TYPE
- Zugriff auf Events, die durch andere Finanzinstrumente ausgelöst werden, die MarketEvent-Struktur
- Das "Neue Bar" Event. Der "New Tick" und Bar-Detection-Algorithmus
- Die CPositionMT5 Klasse — Die Basis für plattformunabhängige Algorithmen
- Trading-Strategie Prototyp — die CStrategy Klasse
- Schlussfolgerung
Einleitung
Dieser Artikel beinhaltet weitere Beschreibungen der universellen Trading-Engine CStrategy. In dem ersten Artikel Universal Expert Advisor: Trading Modi von Strategien (Part 1), Haben wir ausführlich die Trading-Modi und deren Funktionen besprochen, die die Implementierung erlauben. Wir haben ein Schema eines universellen Expert-Advisors analysiert, welcher aus vier Methoden besteht: zwei von denen öffnen Positionen und die anderen beiden Methoden schließen Positionen. Die unterschiedlichen Kombinationen der Aufrufe dieser Methoden definieren spezielle Trading Modis. Zum Beispiel kann ein Expert Advisor nur für den Kauf oder für den Verkauf von Positionen eingesetzt werden, oder er kann schon bestehende Positionen verwalten oder einfach nur warten. Unter Verwendung dieser Modi kann der Expert Advisor auch sehr flexibel für bestimmte Tageszeiten oder Wochentage eingesetzt werden.
Aber solche Trading-Modis sind nicht das Einzige, was ein Expert Advisor braucht. In dem zweiten Abschnitt besprechen wir das Event-Modell des CStrategy Trading-Mode, basierend auf einem zentralisierten Event-Handling. Das Event-Handling-Schema unterscheidet sich von den System-Events dahingehend, dass hier alle Ereignisse an einer Stelle auftreten. Die Vorteile einer solchen Implementation werden wir später betrachten.
Zudem beschreibt dieser Artikel zwei wichtige Klassen der Trading Engine — CStrategy und CPosition. Die Erste ist der zentrale Kern der gesamten Expert Advisor Handlungslogik. Es vereint Ereignisse und Modi in einem einzigen flexiblen Framework, von dem der benutzerdefinierte EA direkt erbt. Die zweite Klasse bildet die Basis für universale Trading-Operationen. Es beinhaltet Aktionen die auf offene Positionen angewendet werden können (Wie zum Beispiel das Schließen von Positionen oder die Modifizierung von Stop Loss und Take Profit). Somit haben alle Handelsaktionen eine festgelegte Struktur und machen sie dadurch plattformunabhängig.
Bitte beachten Sie, dass alle Eigenschaften dieser Trading-Engine zu ihrem Vorteil geschaffen worden sind. Zum Beispiel gibt es die übliche Suche durch offene Positionen und den Zugriff auf System-Events in dieser Strategie nicht. Daher braucht man sich nicht um die Reihenfolge von Aktionen und die Frage, mit welchem Event-Handler man dieses Event behandelt, zu kümmern. Stattdessen liefert die Klasse CStrategy einen benutzerdefinierten EA, damit man sich auf die Handelsstrategie konzentrieren kann, während die Klasse alle anderen Implementationen und Operationen enthält und durchführt.
Eine Trading-Engine, wie sie hier beschrieben wird, wurden dazu entwickelt, um jedem User eine einfache Handhabung der gewünschten Funktionen ermöglichen zu können. Eine genauere Analyse der hier beschriebenen Algorithmen ist nicht notwendig. Sie müssen nur das grundlegende Prinzip und die Funktionsweise der Klasse CStrategy verstehen. Daher können Sie auch einige Teile dieses Artikels überspringen, falls Sie diese zu kompliziert finden.
Das Event-Modell, welches auf einer zentralen Verarbeitung basiert, ENUM_MARKET_EVENT_TYPE
MetaTrader 5 stellt eine große Menge an Events zur Verfügung. Diese Events beinhalten Benachrichtigungen über Veränderungen des Kurses (NewTick, BookEvent) Und System-Events wie Timer oder TradeTransaction. Es gibt für jedes Event eine entsprechende Systemfunktion mit dem prefix On*. Diese Funktion ist der Handler dieses Events. Zum Beispiel wenn sie auf den Eingang eines neuen Ticks reagieren wollen, dann schreiben Sie die gewünschten Prozeduren in die OnTick Funktion:
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { // Handhabung eines eingehenden Tick-Events // ... }
Wenn das OnBookEvent-Event auftritt, dann sollte ein anderer Teil im Programmcode für eine Veränderung im Orderbuch (Depth of Market) aufgerufen werden:
void OnBookEvent (const string& symbol) { // Hier reagieren wir auf Veränderungen im Orderbuch // ... }
Bei diesem Ansatz fragmentiert sich die Logik immer weiter, je mehr Events behandelt werden müssen. Die Bedingungen für das Öffnen und Schließen von Positionen kann in einem separaten Bereich untergebracht werden. Aus der Sicht einer effizient Programmierung kann die Separierung in verschiedene Bereiche eine gute Lösung darstellen, aber dieser Ansatz ist in unserem Falle nicht wünschenswert. Eine bessere Lösung ist es, die gesamte Logik des Handelns an einer bestimmten Stelle zu sammeln.
Zudem werden einige Events von MetaTrader 5 nicht unterstützt. In diesem Fall muss der Expert Advisor das Auftreten von bestimmten Bedingungen selbst erkennen. Zum Beispiel gibt es in MetaTrader 5 kein Event, welches das Öffnen einer neuer Bar behandelt. Dabei ist dieser Event einer der am häufigsten verwendeten Events in einem Expert Advisor. Daher unterstützt das Event Modell der beschriebenen Trading Engine nicht nur die System-Events, sondern auch benutzerdefinierte Events.(Diese Events bitte nicht verwechseln mit den Benutzer-Events die innerhalb eines Charts auftreten). Das erleichtert die Entwicklung von Expert Advisors sehr. Zum Beispiel ist ein solches Event die Erzeugung einer neuen Bar auf dem Chart.
Um das Ereignismodell besser verstehen zu können, lassen Sie uns die Ereignisse anhand von Preis- und Zeit-Änderungen im Zusammenhang mit der Enumeration ENUM_MARKET_EVENT_TYPE beschreiben.:
//+------------------------------------------------------------------+ //| Die Bestimmung eines Markt-Events. | //+------------------------------------------------------------------+ enum ENUM_MARKET_EVENT_TYPE { MARKET_EVENT_TICK, // Der Eingang eines neuen Ticks MARKET_EVENT_BAR_OPEN, // Das Öffnen einer neuen Bar MARKET_EVENT_TIMER, // Ein getriggerter Timer-Event MARKET_EVENT_BOOK_EVENT // Änderungen der Markttiefe (Inklusive des Eingangs eines neuen Ticks). };
Wie Sie sehen können, beinhaltet diese Enumeration eine Beschreibung von System-Events und Events, welche von dem System in MetaTrader 5 nicht direkt unterstützt werden. (MARKET_EVENT_BAR_OPEN — Das Öffnen einer neuen Bar des aktuellen Symbols)
Nehmen wir jetzt an, dass und unserer universeller Expert Advisor vier Methoden für die Handels-Logik besitzt: InitBuy, InitSell, SupportBuy, SupportSell. Diese Methoden haben wir in dem ersten Abschnitt unseres Artikels "Universal Expert Advisor" beschrieben. Wenn einer der Enumerationswerte als Parameter in den Methoden verwendet wird, dann kann die Logik des EAs zu jeder Zeit das Event herausfinden, welches für den Aufruf dieser Methode verantwortlich war. Wir beschreiben jetzt ein vereinfachtes Schema des Expert-Advisors:
//+------------------------------------------------------------------+ //| ExampExp.mq5 | //| Copyright 2015, Vasiliy Sokolov. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2015, Vasiliy Sokolov." #property link "https://www.mql5.com" #property version "1.00" #include <Strategy\Series.mqh> ulong ExpertMagic=12345; // Die "magic number" des Expert Advisors //+------------------------------------------------------------------+ //| Die Bestimmung eines Markt-Events. | //+------------------------------------------------------------------+ enum ENUM_MARKET_EVENT_TYPE { MARKET_EVENT_TICK, //Der Eingang eines neuen Ticks des aktuellen Symbols MARKET_EVENT_BAR_OPEN, // Das Öffnen einer neuen Bar des aktuellen Finanzinstruments MARKET_EVENT_TIMER, // Ein getriggerter Timer-Event MARKET_EVENT_BOOK_EVENT // Die Tiefe des Marktes verändert sich (Inklusive des Eingangs eines neuen Ticks). }; //+------------------------------------------------------------------+ //| Prototyp des Universal Expert Advisor. | //+------------------------------------------------------------------+ class CExpert { public: void InitBuy(ENUM_MARKET_EVENT_TYPE &event_id); void InitSell(ENUM_MARKET_EVENT_TYPE &event_id); void SupportBuy(ENUM_MARKET_EVENT_TYPE &event_id); void SupportSell(ENUM_MARKET_EVENT_TYPE &event_id); }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CExpert::InitBuy(ENUM_MARKET_EVENT_TYPE &event_id) { printf(__FUNCTION__+" EventID: "+EnumToString(event_id)); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CExpert::InitSell(ENUM_MARKET_EVENT_TYPE &event_id) { printf(__FUNCTION__+" EventID: "+EnumToString(event_id)); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CExpert::SupportBuy(ENUM_MARKET_EVENT_TYPE &event_id) { printf(__FUNCTION__+" EventID: "+EnumToString(event_id)); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CExpert::SupportSell(ENUM_MARKET_EVENT_TYPE &event_id) { printf(__FUNCTION__+" EventID: "+EnumToString(event_id)); } ENUM_MARKET_EVENT_TYPE event_type; CExpert Expert; datetime last_time; CTime Time; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int OnInit() { EventSetTimer(1); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { EventKillTimer(); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { event_type=MARKET_EVENT_TICK; CallExpertLogic(event_type); if(last_time!=Time[0]) { event_type=MARKET_EVENT_BAR_OPEN; CallExpertLogic(event_type); last_time=Time[0]; } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void OnTimer() { event_type=MARKET_EVENT_TIMER; CallExpertLogic(event_type); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void OnBookEvent(const string &symbol) { event_type=MARKET_EVENT_TIMER; CallExpertLogic(event_type); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CallExpertLogic(ENUM_MARKET_EVENT_TYPE &event) { Expert.InitBuy(event); Expert.InitSell(event); Expert.SupportBuy(event); Expert.SupportSell(event); } //+------------------------------------------------------------------+
Alle Events, die auf eine Veränderung des Kurses hindeuten, rufen die selbe CallExpertMagic Funktion auf und werden selbst als Parameter übergeben. Die Funktion selbst ruft dann die vier Methoden des CExpert auf. Sobald die Funktion aufgerufen wurde, schreibt jede Methode ihren Namen und die Identifikation des Events auf, die diesen Aufruf verursacht hat. Wenn Sie diesen Expert Advisor in einem Chart laufen lassen, dann erhalten Sie eine ähnliche Liste mit den bearbeiteten Events, wie sie wie folgt dargestellt ist:
2015.11.30 14:13:39.981 ExampExp2 (Si-12.15,M1) CExpert::SupportSell EventID: MARKET_EVENT_BAR_OPEN 2015.11.30 14:13:39.981 ExampExp2 (Si-12.15,M1) CExpert::SupportBuy EventID: MARKET_EVENT_BAR_OPEN 2015.11.30 14:13:39.981 ExampExp2 (Si-12.15,M1) CExpert::InitSell EventID: MARKET_EVENT_BAR_OPEN 2015.11.30 14:13:39.981 ExampExp2 (Si-12.15,M1) CExpert::InitBuy EventID: MARKET_EVENT_BAR_OPEN 2015.11.30 14:13:40.015 ExampExp2 (Si-12.15,M1) CExpert::InitSell EventID: MARKET_EVENT_TICK 2015.11.30 14:13:40.015 ExampExp2 (Si-12.15,M1) CExpert::InitBuy EventID: MARKET_EVENT_TICK 2015.11.30 14:13:39.981 ExampExp2 (Si-12.15,M1) CExpert::SupportSell EventID: MARKET_EVENT_BAR_OPEN 2015.11.30 14:13:39.981 ExampExp2 (Si-12.15,M1) CExpert::SupportBuy EventID: MARKET_EVENT_BAR_OPEN 2015.11.30 14:13:39.981 ExampExp2 (Si-12.15,M1) CExpert::InitSell EventID: MARKET_EVENT_BAR_OPEN 2015.11.30 14:13:39.981 ExampExp2 (Si-12.15,M1) CExpert::InitBuy EventID: MARKET_EVENT_BAR_OPEN 2015.11.30 14:13:39.981 ExampExp2 (Si-12.15,M1) CExpert::SupportSell EventID: MARKET_EVENT_TICK 2015.11.30 14:13:39.981 ExampExp2 (Si-12.15,M1) CExpert::SupportBuy EventID: MARKET_EVENT_TICK 2015.11.30 14:13:39.981 ExampExp2 (Si-12.15,M1) CExpert::InitSell EventID: MARKET_EVENT_TICK 2015.11.30 14:13:39.981 ExampExp2 (Si-12.15,M1) CExpert::InitBuy EventID: MARKET_EVENT_TICK
Zugriff auf Events, die durch andere Finanzinstrumente ausgelöst werden, die MarketEvent-Struktur
Wenn Sie ein Handelssystem entwickeln, welches mehrere Finanzinstrumente gleichzeitig analysiert, dann müssen sie einen Mechanismus erstellen, welcher alle Veränderungen in den Preisen der Finanzinstrumente überprüft. Aber die Standard OnTick Funktion wird nur bei Kursveränderungen des Symbols zu welchem der Expert Advisor gestartet wurde aufgerufen . Andererseits können sie als Entwickler eines Trading-Systems die Funktion OnBookEvent verwenden, welche auf alle Veränderungen in dem Orderbuch reagiert. Entgegen der Funktion OnTick, reagiert die Funktion OnBookEvent auf jede Veränderung eines Symbols, welches Sie mithilfe der Funktion MarketBookAdd abonniert haben.
Da Veränderungen in einem Orderbuch sehr häufig auftreten, ist dieses ein sehr stark Ressourcenfressender Prozess. Normalerweise ist die Protokollierung von Veränderungen in dem aktuellen Symbol für einen Expert Advisor ausreichend. Aber ein Event in dem Orderbuch beinhaltet auch den Eingang eines neuen Ticks. Außer OnBookEvent, können Sie auch noch mit Hilfe der OnTimer Funktion innerhalb eines angegebenen Zeitintervalls die Kurse von verschiedenen Symbolen abfragen.
Also können sie in den System-Funktionen, welche auf die Events NewTick, BookEvent und Timer reagieren, weitere Funktionen aufrufen intermediate module (lassen Sie sie uns EventProcessor nennen), welche gleichzeitig die Kurse von verschiedenen Symbolen analysieren und entsprechende Events ausgeben können. Jedes Event sollte eine eindeutige Struktur besitzen und durch die Kontrollmethoden der Strategie versendet werden. Sobald ein entsprechendes Event als Struktur empfangen wurde, sollte die Strategie darauf reagieren oder es ignorieren. In diesem Fall ist für den Expert Advisor der ursprüngliche Event, der diesen Aufruf verursacht hat, unbekannt.
In der Tat ist es so, dass wenn ein Expert Advisor ein Event über einen neu eingehenden Tick erhalten hat, es keine Rolle spielt, ob die Information über OnTick, OnTimer oder OnBookEvent ausgelöst wurde. Das einzige was wichtig ist, ist, dass jetzt ein neuer Tick für das angegebene Symbol eingegangen ist. Ein Eventhandler kann für mehrere Strategien gleichzeitig verwendet werden. Zum Beispiel wenn jede Strategie in einer eigenen Klasse repräsentiert wird, können mehrere Instanzen dieser Klasse in einer Liste von Strategien gespeichert werden. In diesem Falle ist es jeder Klasse möglich ein neues Event durch den Event-Prozessor zu erhalten und zu verarbeiten. Die folgende Abbildung zeigt wie Events generiert und versendet werden:
Fig. 1. Abbildung für die Generierung und das Versenden von Events
Lassen Sie uns nun die Struktur betrachten, die einem Expert-Advisor als Event übergeben wird. Die Struktur wird MarketEvent genannt, ihre Definition sieht wie folgt aus:
//+------------------------------------------------------------------+ //| Die Struktur beschreibt die Art des Events und das zugehörige Finanzinstrument | //| und die Timeframe (für das BarOpen Event) |# //+------------------------------------------------------------------+ struct MarketEvent { ENUM_MARKET_EVENT_TYPE type; // Event-Typ. ENUM_TIMEFRAMES period; // Die Timeframe von dem Chart zu welchem der Event gehört (nur für MARKET_EVENT_BAR_OPEN). string symbol; // Der Name des Symbols zu dem aktuellen Event. };
Nachdem die Methode, die eine Trading-Entscheidung fällen soll, eine Instanz der Struktur als Referenz erhalten hat, kann diese die Struktur analysieren und eine Entscheidung aufgrund der Folgenden Information Fällen:
- Der Eventtyp
- Die Timeframe des Charts, auf welchem der Event aufgetreten ist.
- Der Name des Finanzinstrumentes, zu welchem der Event gehört
Bei der Analyse der Timeframe von einem Event (zum Beispiel bei NewTick or Timer), wir der Parameter period immer mit der Variablen PERIOD_CURRENT gefüllt.
Das "Neue Bar" Event. Der "New Tick" und Bar-Detection-Algorithmus
Um neue Ticks und Informationen über die Generierung von neuen Bars zu mehreren Symbolen zu protokollieren, benötigen wir eine entsprechende (Modul-)Klasse die diese Aufgabe erfüllt. Diese Module sind interne Abschnitte der CStrategy Klasse, mit denen der Anwender nicht direkt interagiert. Wir betrachten nun das erste Modul, die CTickDetector Klasse. Der Quellcode dieser Klasse ist der Folgende:
//+------------------------------------------------------------------+ //| NewTickDetector.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2015, MetaQuotes Software Corp." #property link "https://www.mql5.com" #include <Object.mqh> //+------------------------------------------------------------------+ //| Neuer Tick detector | //+------------------------------------------------------------------+ class CTickDetector : public CObject { private: string m_symbol; // Das Symbol welches protokolliert werden soll. MqlTick m_last_tick; // Der letzte gespeicherte Tick. public: CTickDetector(void); CTickDetector(string symbol); string Symbol(void); void Symbol(string symbol); bool IsNewTick(void); }; //+------------------------------------------------------------------+ //| Der Konstruktor setzt die aktuelle Timeframe | //| und das Symbol. | //+------------------------------------------------------------------+ CTickDetector::CTickDetector(void) { m_symbol=_Symbol; } //+------------------------------------------------------------------+ //| Erzeugt ein Objekt mit einem vordefinierten Symbol und einer Timeframe. | //+------------------------------------------------------------------+ CTickDetector::CTickDetector(string symbol) { m_symbol=symbol; } //+------------------------------------------------------------------+ //| Legt den Namen des Symbols fest, zu welchem Informationen über neuen Ticks | //| . | //+------------------------------------------------------------------+ void CTickDetector::Symbol(string symbol) { m_symbol=symbol; } //+------------------------------------------------------------------+ //| Gibt den Namen des Symbols zurück zu welchem ein neuer Tick | //| verfolgt wurde. | //+------------------------------------------------------------------+ string CTickDetector::Symbol(void) { return m_symbol; } //+------------------------------------------------------------------+ //| Gibt 'true' zurück, falls es für das angegebene Symbol und die Timeframe eine neue | //| Tick gibt. | //+------------------------------------------------------------------+ bool CTickDetector::IsNewTick(void) { MqlTick tick; SymbolInfoTick(m_symbol,tick); if(tick.last!=m_last_tick.last || tick.time!=m_last_tick.time) { m_last_tick=tick; return true; } return false; }
Die wichtigste Methode ist hier: IsNewTick. Sie gibt true zurück, wenn ein neuer Tick des zu beobachtenden Symbols empfangen wurde. Die Bestimmung der zu protokollierenden Finanzinstrumente wird mit der Methode "Symbol" durchgeführt. Die CTickDetector Klass wurde abgeleitet von CObject. Daher kann es als ein Element der CArrayObj-Collection hinzugefügt werden. Das ist es, was wir brauchen. Zum Beispiel können wir jetzt zehn Kopien von CTickDetect machen und jede Kopie wird ihr eigenes Symbol protokollieren, bzw. beobachten. Durch die konsequente Abfrage der Klassen des Typs CArrayObj, können Sie schnell das Symbol herausfinden, welches das neue Event/Tick erzeugt hat und dann das entsprechende Ereignis an die Expert Advisor Klassen weitergeben.
Wie vorhin schon beschrieben, ist es zu den Ticks auch oft notwendig, die Erzeugung einer neuen Bar als Event zu bestimmen. Diese Aufgabe vergeben wir an die CBarDetector Klasse. Sie arbeitet ähnlich wie die CTickDetector-Klasse. Die Hauptmethode der Klasse CBarDetector ist: IsNewBar — die Methode gibt 'true' zurück, falls es zu dem zuvor über die Methode 'Symbol' angegebenen Finanzinstrument eine neue Bar gibt. Der Quellcode sieht wie folgt aus:
//+------------------------------------------------------------------+ //| NewBarDetecter.mqh | //| Copyright 2015, Vasiliy Sokolov. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2015, Vasiliy Sokolov." #property link "https://www.mql5.com" #include <Object.mqh> //+------------------------------------------------------------------+ //| Die Klasse erkennt die Entstehung einer neuen Bar zu dem angegebenen | //| Symbol und der Periode. | //+------------------------------------------------------------------+ class CBarDetector : public CObject { private: ENUM_TIMEFRAMES m_timeframe; // Die Timeframe für die die Entstehung einer neuen Bar protokolliert werden soll string m_symbol; // Das Symbol für welches die Entstehung einer neuen Bar protokolliert werden soll datetime m_last_time; // Die Zeit der letzten bekannten Bar public: CBarDetector(void); CBarDetector(string symbol,ENUM_TIMEFRAMES timeframe); void Timeframe(ENUM_TIMEFRAMES tf); ENUM_TIMEFRAMES Timeframe(void); void Symbol(string symbol); string Symbol(void); bool IsNewBar(void); }; //+------------------------------------------------------------------+ //| Der Konstruktor setzt die aktuelle Timeframe | //| und das Symbol. | //+------------------------------------------------------------------+ CBarDetector::CBarDetector(void) { m_symbol=_Symbol; m_timeframe=Period(); } //+------------------------------------------------------------------+ //| Erzeugt ein Objekt mit einem vordefinierten Symbol und einer Timeframe. | //+------------------------------------------------------------------+ CBarDetector::CBarDetector(string symbol,ENUM_TIMEFRAMES tf) { m_symbol=symbol; m_timeframe=tf; } //+------------------------------------------------------------------+ //| Liegt die time frame fest zu welcher die Erzeugung einer neuen bar | //| erkannt werden soll. | //+------------------------------------------------------------------+ void CBarDetector::Timeframe(ENUM_TIMEFRAMES tf) { m_timeframe=tf; } //+------------------------------------------------------------------+ //| Gib die Timeframe zurück zu der die Erkennung von neuen Bars | //| Bar gibt. | //+------------------------------------------------------------------+ ENUM_TIMEFRAMES CBarDetector::Timeframe(void) { return m_timeframe; } //+------------------------------------------------------------------+ //| Legt den Symbolnamen für die Erkennung einer neuen Bar fest | //| . | //+------------------------------------------------------------------+ void CBarDetector::Symbol(string symbol) { m_symbol=symbol; } //+------------------------------------------------------------------+ //| Gib den Namen des Symbols zurück zu welchen die Generierung einer neuen Bar | //| verfolgt wurde. | //+------------------------------------------------------------------+ string CBarDetector::Symbol(void) { return m_symbol; } //+------------------------------------------------------------------+ //| Gibt 'true' zurück, falls es für das angegebene Symbol und die Timeframe eine neue | //| Bar gibt. | //+------------------------------------------------------------------+ bool CBarDetector::IsNewBar(void) { datetime time[]; if(CopyTime(m_symbol, m_timeframe, 0, 1, time) < 1)return false; if(time[0] == m_last_time)return false; return m_last_time = time[0]; }
Die CPositionMT5 Klasse — die Basis für den Platformunabhängigen Algorithmus
Nun ist es an der Zeit eine der wichtigsten Klassen zu analysieren, die den Betrieb des universellen Expert Advisors möglich macht. Diese Klasse beinhaltet Methoden für das Arbeiten mit Positionen im MetaTrader 5. Technisch gesehen ist sie sehr einfach. Es ist eine Wrapper-Klasse, die zwischen einem Expert Advisor und Systemfunktionen im Zusammenhang mit Handels-Operationen in MetaTrader 5 als Vermittler fungiert — PositionSelect und PositionGet. Sie implementiert eine wichtige Funktion der angebotenen Klassen — Platform unabhängig.
Nach sorgfältiger Analyse der oben beschriebenen Module, können Sie erkennen, dass diese keine Funktionen verwenden, die nur auf eine Plattform zutreffen (MetaTrader 4 oder MetaTrader 5). Dieses ist so, weil die modernen Versionen von MQL4 und MQL5 eigentlich eine und dieselbe Programmiersprache haben, aber unterschiedliche Gruppen von Funktionen besitzen. Bestimmte Gruppen von Funktionen der beiden Plattformen sind hauptsächlich für das Management von Positionen verantwortlich.
Es liegt in der Natur der Sache, dass alle Expert-Advisors diese Managementfunktionen verwenden. Wenn man aber jetzt einem Expert Advisor anstelle der Funktionen ein abstraktes Interface in Form von einer Positions-Klasse zur Verfügung stellt, wäre es möglich (zumindest theoretisch) einen Handelsroboter zu entwickeln, der auf beiden Plattformen kompiliert werden kann ohne Änderungen an dem Quellcode vornehmen zu müssen. Wir müssen lediglich alternative Klassen programmieren, die das Arbeiten mit Positionen implementieren. Eine Klasse verwendet die MetaTrader 4 Funktionen, die andere verwendet die Funktionen von MetaTrader 5. Beide Klassen bieten dann "plattformunabhängig" die gleiche Gruppe von Methoden für den finalen Expert Advisor an.
In der Praxis ist die Entwicklung eines plattformunabhängigen Expert-Advisors um einiges komplizierter und würde einen extra Artikel erfordern. Dieses Thema würde sehr umfangreich werden und daher werden wir das in diesem Artikel nicht besprechen. Wir werden es in einem anderen Teil berücksichtigen.
Das zweite Argument für eine spezielle Klasse, die eine offene Position einer Strategie repräsentiert ist, dass jede Position individuell gemanagt wird. Die Trading-Engine durchläuft die Liste der Positionen und schickt jede Einzelne zu der Logik des Expert-Advisors. So erreichen wir ein Höchstmaß an Flexibilität: Da der Expert Advisor jede Position individuell behandelt, wird es möglich, Strategieregeln zu schreiben, welche sich mit mehr als einer offenen Position beschäftigen können. Es kann sich natürlich auch nur um eine einzige Position in MetaTrader 5 handeln. Aber wenn wir die Trading Engine auf die Plattform MetaTrader 4 anwenden, wird diese Eigenschaft sehr wichtig.
Hier ist der Quellcode der Klasse: Sie ist einfach und unkompliziert:
//+------------------------------------------------------------------+ //| PositionMT5.mqh | //| Copyright 2015, Vasiliy Sokolov. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2015, Vasiliy Sokolov." #property link "https://www.mql5.com" #include <Object.mqh> #include "Logs.mqh" #include <Trade\Trade.mqh> //+------------------------------------------------------------------+ //| Active position class for classical strategies | //+------------------------------------------------------------------+ class CPositionMT5 : public CObject { private: ulong m_id; // Eine eindeutige ID für eine Position uint m_magic; // Eine eindeutige ID des Expert-Advisors. ENUM_POSITION_TYPE m_direction; // Die Richtung der Position double m_entry_price; // Der Einstiegspreis der Position string m_symbol; // Das Symbol der Position datetime m_time_open; // Die Eröffnungszeit string m_entry_comment; // Ein Kommentar bool m_is_closed; // Ist true, wenn die Position geschlossen wurde CLog* Log; // Logging CTrade m_trade; // Trading-Modul public: CPositionMT5(void); uint ExpertMagic(void); ulong ID(void); ENUM_POSITION_TYPE Direction(void); double EntryPrice(void); string EntryComment(void); double Profit(void); double Volume(void); string Symbol(void); datetime TimeOpen(void); bool CloseAtMarket(string comment=""); double StopLossValue(void); bool StopLossValue(double sl); double TakeProfitValue(void); bool TakeProfitValue(double tp); }; //+------------------------------------------------------------------+ //| Initialisierung der Haupteigenschaften einer Position | //+------------------------------------------------------------------+ void CPositionMT5::CPositionMT5(void) : m_id(0), m_entry_price(0.0), m_symbol(""), m_time_open(0) { m_id=PositionGetInteger(POSITION_IDENTIFIER); m_magic=(uint)PositionGetInteger(POSITION_MAGIC); m_direction=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); m_entry_price=PositionGetDouble(POSITION_PRICE_OPEN); m_symbol=PositionGetString(POSITION_SYMBOL); m_time_open=(datetime)PositionGetInteger(POSITION_TIME); m_entry_comment=PositionGetString(POSITION_COMMENT); m_trade.SetExpertMagicNumber(m_magic); } //+------------------------------------------------------------------+ //| Gib die Handelsrichtung der Position zurück. | //+------------------------------------------------------------------+ ENUM_POSITION_TYPE CPositionMT5::Direction(void) { return m_direction; } //+------------------------------------------------------------------+ //| Gibt die eindeutige ID des Expert Advisor zurück | //| Der zu der aktuellen Position gehört to. | //+------------------------------------------------------------------+ uint CPositionMT5::ExpertMagic(void) { return m_magic; } //+------------------------------------------------------------------+ //| Gibt die eindeutige ID der Position zurück. | //+------------------------------------------------------------------+ ulong CPositionMT5::ID(void) { return m_id; } //+------------------------------------------------------------------+ //| Gibt den Einstiegspreis der Position zurück. | //+------------------------------------------------------------------+ double CPositionMT5::EntryPrice(void) { return m_entry_price; } //+------------------------------------------------------------------+ //| Gibt den Kommentar der aktiven Position zurück. | //+------------------------------------------------------------------+ string CPositionMT5::EntryComment(void) { return m_entry_comment; } //+------------------------------------------------------------------+ //| Gibt das Symbol der aktuellen offenen Position zurück | //| | //+------------------------------------------------------------------+ string CPositionMT5::Symbol(void) { return m_symbol; } //+------------------------------------------------------------------+ //| Gibt den Einstiegszeitpunkt einer Position zurück. | //+------------------------------------------------------------------+ datetime CPositionMT5::TimeOpen(void) { return m_time_open; } //+------------------------------------------------------------------+ //| Gibt den absoluten Stop Loss Level der aktuellen Position zurück. | //| Wenn kein Stop Loss Level gesetzt wurde, gibt die Funktion 0.0 zurück | //+------------------------------------------------------------------+ double CPositionMT5::StopLossValue(void) { if(!PositionSelect(m_symbol)) return 0.0; return PositionGetDouble(POSITION_SL); } //+------------------------------------------------------------------+ //| Legt einen absoluten Stop Loss Level fest | //+------------------------------------------------------------------+ bool CPositionMT5::StopLossValue(double sl) { if(!PositionSelect(m_symbol)) return false; return m_trade.Buy(0.0, m_symbol, 0.0, sl, TakeProfitValue(), NULL); } //+------------------------------------------------------------------+ //| Gibt den absoluten Stop Loss Level der aktuellen Position zurück. | //| Wenn kein Stop Loss Level gesetzt wurde, gibt die Funktion 0.0 zurück | //+------------------------------------------------------------------+ double CPositionMT5::TakeProfitValue(void) { if(!PositionSelect(m_symbol)) return 0.0; return PositionGetDouble(POSITION_TP); } //+------------------------------------------------------------------+ //| Legt einen absoluten Stop Loss Level fest | //+------------------------------------------------------------------+ bool CPositionMT5::TakeProfitValue(double tp) { if(!PositionSelect(m_symbol)) return false; return m_trade.Buy(0.0, m_symbol, 0.0, StopLossValue(), tp, NULL); } //+------------------------------------------------------------------+ //| Schließt die aktuelle Position mit dem Marktpreis und legt einen | //| Kommentar gleich 'comment' fest | //+------------------------------------------------------------------+ bool CPositionMT5::CloseAtMarket(string comment="") { if(!PositionSelect(m_symbol)) return false; return m_trade.PositionClose(m_symbol); } //+------------------------------------------------------------------+ //| Gibt die Größe der aktuellen Position zurück. | //+------------------------------------------------------------------+ double CPositionMT5::Volume(void) { if(!PositionSelect(m_symbol)) return false; return PositionGetDouble(POSITION_VOLUME); } //+------------------------------------------------------------------+ //| Gibt den aktuellen Profit einer Position in der Kontowährung zurück. | //+------------------------------------------------------------------+ double CPositionMT5::Profit(void) { if(!PositionSelect(m_symbol)) return false; return PositionGetDouble(POSITION_PROFIT); }
Wie Sie sehen können, ist die Hauptaufgabe dieser Klasse die Rückgabe/Abfrage von speziellen Eigenschaften der aktuellen offenen Position. Zudem bietet die Klasse noch einige Handelsoperationen für die aktuelle Position: Schließen und Ändern von Take Profit und Stop Loss.
Trading-Strategie Prototyp — die CStrategy Klasse
Wir haben nun einige Module betrachtet, die Grundfunktionen ausführen können. Wir haben zudem das Event-Modell analysiert, das Modell, welches die Entscheidungen trifft (decision-making model), und die Ausführungsalgorithmen eines typischen Expert Advisors. Nun müssen wir die Informationen in einem einzigen Modul zusammenfassen — der CStrategy Klasse. Sie muss verschiedene Aufgaben erfüllen. Hier sind ein paar von denen:
- Anordnung einer Reihe von Handelsaktionen für alle Strategien, die auf der Klasse CStrategy beruhen.
- Identifizierung der Events Timer, NewTick, BookEvent, NewBar und deren Weitergabe an eine benutzerdefinierte Strategie.
- Implementierung eines einfachen Zugriffs auf die Kurse eines Symbols, basierend auf dem MetaTrader 4-Modell.
- Die Verwaltung der Trading-Modes.
- Zugriff auf Informationen über offene Positionen und deren Statistiken.
Die Klasse CStrategy ist wirklich eine sehr große Klasse und wir werden den Quellcode hier nicht veröffentlichen. Stattdessen werden wir unseren Fokus auf die wichtigsten Algorithmen dieser Klasse richten. Das erste, was wir betrachten werden, ist das Event-Modell. Wir haben dieses schon kennengelernt. Nun betrachten wir uns nur noch den Prozess, wie ein Event empfangen und an die einzelnen Strategien weitergeleitet wird. CStrategy ist eine übergeordnete Klasse unserer zukünftigen Strategie. Daher sind wir mit dem Methoden über die Benachrichtigung eines neuen Events vertraut:
//+------------------------------------------------------------------+ //| Die Hauptklasse der Strategie-Ebene. | //+------------------------------------------------------------------+ class CStrategy : public CObject { //... void OnTick(void); void OnTimer(void); void OnBookEvent(string symbol); virtual void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result); //... };
Bitte beachten Sie, dass alle Eventhandler außer OnTradeTransaction nicht virtuell sind. Das bedeutet, dass die Events Tick, Timer oder BookEvent nicht direkt über die Ebene der Strategie behandelt werden können. Stattdessen werden sie über die Klasse CStrategy behandelt. Das Event TradeTransaction wird in dieser Klasse nicht verwendet, daher ist es virtuell.
Hier sind die Inhalte von OnTick, OnTimer und OnBookEvent:
//+------------------------------------------------------------------+ //| Wird von dem Strategie-Manager über das System-Event | //| 'new tick' aufgerufen. | //+------------------------------------------------------------------+ void CStrategy::OnTick(void) { NewTickDetect(); NewBarsDetect(); } //+------------------------------------------------------------------+ //| Wird von dem Strategie-Manager über das System-Event | //| 'OnTimer' aufgerufen. | //+------------------------------------------------------------------+ void CStrategy::OnTimer(void) { m_event.symbol=Symbol(); m_event.type=MARKET_EVENT_TIMER; m_event.period=(ENUM_TIMEFRAMES)Period(); CallSupport(m_event); CallInit(m_event); NewTickDetect(); NewBarsDetect(); } //+------------------------------------------------------------------+ //| Wird von dem Strategie-Manager über das System-Event | //| 'OnBookEvent' aufgerufen. | //+------------------------------------------------------------------+ void CStrategy::OnBookEvent(string symbol) { m_event.symbol=symbol; m_event.type=MARKET_EVENT_BOOK_EVENT; m_event.period=PERIOD_CURRENT; CallSupport(m_event); CallInit(m_event); NewTickDetect(); NewBarsDetect(); }
Wie schon oben aufgeführt, wird das NewTick-System-Event nur durch das Finanzinstrument aufgerufen, zu welchem der Expert Advisor hinzugefügt worden ist. Wenn wir das NewTick-Event von mehreren Symbolen erhalten wollen, müssen wir einen speziellen Handler für neue Ticks verwenden und das Auftreten von neuen Bars protokollieren. Diese Aufgabe wird auf die folgenden geschlossenen Methoden übertragen: NewBarDetect und NewTickDetect. Hier ist der Quellcode:
//+------------------------------------------------------------------+ //|Erkennt das Auftreten einer neuen Bar und generiert ein entsprechendes | //| Event für den Expert Advisor. | //+------------------------------------------------------------------+ void CStrategy::NewBarsDetect(void) { if(m_bars_detecors.Total()==0) AddBarOpenEvent(ExpertSymbol(),Timeframe()); for(int i=0; i<m_bars_detecors.Total(); i++) { CBarDetector *bar=m_bars_detecors.At(i); if(bar.IsNewBar()) { m_event.period = bar.Timeframe(); m_event.symbol = bar.Symbol(); m_event.type=MARKET_EVENT_BAR_OPEN; CallSupport(m_event); CallInit(m_event); } } } //+------------------------------------------------------------------+ //| Erkennt neue eingehende Ticks von mehreren Finanzinstrumenten. | //+------------------------------------------------------------------+ void CStrategy::NewTickDetect(void) { if(m_ticks_detectors.Total()==0) AddTickEvent(ExpertSymbol()); for(int i=0; i<m_ticks_detectors.Total(); i++) { CTickDetector *tick=m_ticks_detectors.At(i); if(tick.IsNewTick()) { m_event.period=PERIOD_CURRENT; m_event.type=MARKET_EVENT_TICK; m_event.symbol=tick.Symbol(); CallSupport(m_event); CallInit(m_event); } } }
Diese Methoden erkennen neue Ticks und Bars(). Die CTickDetector und die CBarDetector Klassen wurden oben beschrieben. Jeder dieser Detektoren wird zunächst für das Finanzinstrument konfiguriert, zu welchem der Expert Advisor hinzugefügt worden ist. In dem Moment, wo ein neuer Tick eingeht oder eine neue Bar Auftritt, werden spezielle CallSupport und CallInit Methoden aufgerufen, welche dann die Handels-Methoden der zugehörigen Strategie aufrufen.
Der grundlegende Algorithmus für die Eventhandler, inklusive NewBarsDetect und NewTickDetect, ist der Folgende:
- Erkennung des Auftretens eines neuen Events;
- Erstellen der MarketEvent-Struktur mit den Spezifikationen der zugehörige Event-ID und seiner Eigenschaften;
- Aufruf der CallSupport Methode, welche wiederum die virtuellen SupportBuy und SupportSell Methoden mit dem der Methode zugewiesenem Event aufrufen;
- In ähnlicher Weise wird die CallInit Methode aufgerufen, die dann die virtuellen Methoden InitBuy und InitSell aufruft und dabei das Event übergibt.
Um zu verstehen wie die Kontrolle an die verschiedenen Strategien übergeben wird, betrachten wir uns die CallInit und CallSupport Methoden. Hier ist der Quellcode von CallInit:
//+------------------------------------------------------------------+ //| Ruft die Logik für das Öffnen von Positionen auf, sofern der Handelsstatus | //| dieses nicht explizit verbietet. | //+------------------------------------------------------------------+ void CStrategy::CallInit(const MarketEvent &event) { m_trade_state=m_state.GetTradeState(); if(m_trade_state == TRADE_STOP)return; if(m_trade_state == TRADE_WAIT)return; if(m_trade_state == TRADE_NO_NEW_ENTRY)return; SpyEnvironment(); if(m_trade_state==TRADE_BUY_AND_SELL || m_trade_state==TRADE_BUY_ONLY) InitBuy(event); if(m_trade_state==TRADE_BUY_AND_SELL || m_trade_state==TRADE_SELL_ONLY) InitSell(event); }
Die Methode erhält den Trading-Status von dem m_state Modul (der CTradeState Klasse, welche in dem ersten Artikel beschrieben wurde) und entscheidet dann ob es möglich ist, neue Positions-Initialisierungsmethoden für den gegebenen Trading-Status aufzurufen. Wenn der Trading-Status dieses verbietet, werden diese Methoden nicht aufgerufen. Andernfalls werden sie mit dem Hinweis auf das Ereignis aufgerufen, welches den Aufruf der CallInit-Methode verursacht hat.
Der Aufruf der CallSupport-Methode funktioniert ähnlich, aber die dahinterstehende Logik ist ein wenig unterschiedlich. Hier ist der Quellcode:
//+------------------------------------------------------------------+ //| Ruft die Position Verwaltungs Logik auf sofern der Handels | //| -Status nicht gleich TRADE_WAIT ist. | //+------------------------------------------------------------------+ void CStrategy::CallSupport(const MarketEvent &event) { m_trade_state=m_state.GetTradeState(); if(m_trade_state == TRADE_WAIT)return; SpyEnvironment(); for(int i=ActivePositions.Total()-1; i>=0; i--) { CPosition *pos=ActivePositions.At(i); if(pos.ExpertMagic()!=m_expert_magic)continue; if(pos.Direction()==POSITION_TYPE_BUY) SupportBuy(event,pos); else SupportSell(event,pos); if(m_trade_state==TRADE_STOP && pos.IsActive()) ExitByStopRegim(pos); } }
In ähnlicher Weise empfängt die Methode den aktuellen Handelszustand des Expert Advisors. Wenn es erlaubt ist, mit dem Positionsmanagement fortzufahren, geht die Methode alle zur Zeit offenen Positionen durch. Diese Positionen werden durch die CPosition Klasse repräsentiert. Nach jedem erfolgreichen Zugriff auf eine Position, überprüft die Methode, ob die "Magic Number" mit der "Magic Nubmber" des aktuellen Expert Advisors uebereinstimmt. Sobald eine Übereinstimmung gefunden wurde, werden die Positionsmanagement-Methoden aufgerufen: SupportBuy für eine Long-Position und SupportSell für eine Short-Position.
Schlussfolgerung
Wir haben nun alle wichtigen Module der CStrategy Klasse untersucht, welche viele Funktionalitäten für eine benutzerdefinierte Strategie anbietet. Eine Strategie, welche von dieser Klasse abgeleitet wurde, kann mit Kursen und Handels-Events arbeiten. Zudem bekommt die Strategie eine vereinheitlichte Sequenz für Handelsaktionen, welche von der CStrategy Klasse geerbt werden. In diesem Fall kann die Strategie als ein separates Modul entwickelt werden, welche nur die Handelsoperationen und die zugehörigen Regeln beschreibt. Diese Lösung trägt maßgeblich dazu bei, den Aufwand für die Entwicklung eines Trading-Algorithmus zu minimieren.
In dem nächsten Artikel "Universal Expert Advisor: Individuelle Strategien und Hilfshandelsklassen (Teil 3)", Werden wir den Prozess der Entwicklung einer Strategie, basierend auf dem beschriebenem Algorithmus, diskutieren.
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/2169
- 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.