Universal Expert Advisor: Einbindung der Standard MetaTrader Module für Signale (Teil 7)
Inhaltsverzeichnis
- Einführung
- Eine Übersicht über die Klassen des Strategiegenerators
- Eine Übersicht über die Signal-Module, das Konzept der Musters
- Signalmodule. Erste Verwendung
- Die erste Strategie auf Basis von CSignalMACD
- Signaladapter
- Der Programmcode einer Kombination mehrerer Signale für eine Handelsstrategie
- Schlussfolgerung
Einführung
In vorherigen Artikel diskutierten wir die Mechanismen für einen einfachen und effizienten Prozess zur Erstellung eines Handelsalgorithmus' — insbesondere erstellten wir den CStartegy-Algorithmus. Das Projekt wird seit mehr als sechs Monaten ständig weiterentwickelt. Während dieser Zeit wurden neue Module der CStrategy hinzugefügt, um die Handelsprozeduren effizienter und sicherer in Bezug auf die technische Ausführung der Handelsgeschäfte zu gestalten. Dennoch fehlt noch ein wichtiges Merkmal. Trotz der Tatsache, dass CStrategy eine streng objektorientierte Anwendung ist, blieb es immer ein "Ding an sich". Der objektorientierte Ansatz verlangt die Offenheit und Modularität des Codes. In der Tat sollte der Basiscode aus gemeinsamen, allgemeinen Klassen bestehen. Vor allem geht es um das Handelsmodul und die Bildung Signalen. Die Handelslogik von CStrategy basiert auf dem Standardhandelsmodul von CTrade, aber die Anbindung an die Datenbasis der Signale ist nicht so gut. Einfach ausgedrückt enthält CStrategy keine Module für die Erzeugung von Handelssignalen. Mit der vorherigen Versionen mussten alle Nutzer den Expert Advisor komplett neu schreiben, selbst wenn die benötigten Signale Teil der MetaTrader-5-Standardpakete sind. Daher wurde beschlossen, eine neue Version der Mechanismen von CStrategy zu ergänzen, die mit der Datenbank der Standard-MetaTrader-5-Signale arbeitet. In diesem Artikel werde ich erklären, wie man CStrategy mit einem der Signal-Standardmodule verbindet, und Ihnen zeigen, wie Sie Ihre eigene Strategie nur mittels der vorgefertigten Algorithmen erstellen.
Eine Übersicht über die Klassen des Strategiegenerators
Eine Reihe verschiedener Klassen, die Teil des Standardpakets von MetaTrader 5 sind, werden für die Erzeugung automatisierter Strategien durch den MQL-Assistenten verwendet. Die Klassen sind Teil der mqh-Dateien in den entsprechenden Unterordnern des MQL5\Include-Verzeichnisses. Diese Klassen (oder Module) werden üblicherweise in mehrere Kategorien unterteilt. Das sind sie.
- Grundlegende Klassen für die Organisation von Daten (CObject, CArrayDouble, CArrayObj und andere). Alle anderen Handelsmodule werden auf Basis dieser Klassen erstellt.
- Klassen für den Zugriff auf Indikator-Puffer (CDoubleBuffer, CIndicatorBuffer). Folglich werden sie für die Arbeit mit Indikatoren verwendet.
- Klassen für Indikatoren und Zeitreihen auf Basis der normalen Klasse der CSeries.
- Die Basisklasse des Experten CBaseExpert und die aus ihr abgeleitete Klasse CExpert. Alle Hilfsmodule basieren auf CBaseExpert — zum Beispiel das Modul für die Berechnung des Kapitals und die Module zur Kontrolle der Trailing-Stopps. CExpert ist die Grundlage aller nutzerdefinierten Expert Advisors.
- Signalmodule aus CExpertSignal, die wiederum auf CEpertBase basieren. Die Signalmodule erzeugen die Handelssignale zum Kaufen und Verkaufen. Sie verwenden die Klassen der Indikatoren, auf denen die Signale basieren.
- Das Handelsmodul aus CExpertTrade. Es basiert auf der Klasse CTrade und liefert die Handelsoperationen.
Das untenstehende Diagramm zeigt das allgemeine Schema der vertikalen Vererbung von Klassen des Prozesses für die automatische Erzeugung von Strategien:
Fig. 1. Schema der Vererbungen der Standardklassen des Strategiegenerators
Die Abbildung zeigt nur die grundlegenden und einige abgeleitete Klassen. Das Schema zeigt nicht alle von CIndicators ererbten Indikatoren. Auch nicht im Schema sind das Trailing, Geldmanagement und Signalmodule. Stattdessen werden nur die grundlegenden Beziehungen dargestellt. Eine der vorgestellten Gruppen ist für uns von Interesse: die Signalklassen des CExpertSignal und Ihrer Unterklassen. In Abbildung 1 ist die Gruppe durch eine grüne, gestrichelte Linie gekennzeichnet.
Zusätzlich zu den vertikalen Verbindungen bilden Klassen ein komplexes System von Inklusionen (horizontale Verbindungen). Zum Beispiel verwenden Signalmodule aktiv Indikatorklassen, die wiederum Indikator-Puffer verwenden. Verschiedene Gruppen sind Teil von einander. Zum Beispiel sind die Money-Management-Module gleichzeitig Trading-Experten (zumindest auf der Ebene des CExpertBase), obwohl es ist nicht ersichtlich ist, was diese Module mit einem Expert Advisor gemeinsam haben.
Um die Objekte dieser Klassen zu erstellen, sind in der Regel komplexe Initialisierungsketten erforderlich. Um zum Beispiel ein Signal-Objekt wie CSignalMacd zu erstellen, müssen wir das Signal und den Indikator, auf dem es basiert, initialisieren, ebenso wie die für die Signalberechnung benötigten Zeitreihen (Unterklassen von CPriceSeries). Da auch das Objekt durch komplexe Objekte initialisiert werden kann, verlangen auch die eine Initialisierung (wie Zeitreihen). Daher gehört die Initialisierung zu den kompliziertesten Teilen der beschriebenen Bibliothek.
Analysieren wir das folgende Beispiel. Angenommen, wir müssen das Modul CMacdSignal in seiner Klasse zum Zeitpunkt der Erstellung des entsprechenden Objekts dieser Klasse initialisieren. Dann wäre der Code der Initialisierung des Signal-Moduls wie folgt:
//+------------------------------------------------------------------+ //| Initialisierung des CSignalMacd Signal-Moduls | //+------------------------------------------------------------------+ CStrategyMACD::CStrategyMACD(void) { CSymbolInfo* info = new CSymbolInfo(); // Erstellen eines Objektes des Handelssymbols dieser Strategie info.Name(Symbol()); // Initialisierung des Objektes des Handelssymbols dieser Strategie m_signal_ma.Init(info, Period(), 10); // Initialisierung des Signal-Moduls mit dem Handelssymbol und Zeitrahmen m_signal_ma.InitIndicators(GetPointer(m_indicators)); // Erstellen der erforderlichen Indikatoren im Signal-Modul auf Basis der leeren Liste m_indicators von Indikatoren m_signal_ma.EveryTick(true); // Testmodus m_signal_ma.Magic(ExpertMagic()); // Magic-Nummer m_signal_ma.PatternsUsage(8); // Maskenmuster m_open.Create(Symbol(), Period()); // Initialisierung der Zeitreihe der "Open"-Preise m_high.Create(Symbol(), Period()); // Initialisierung der Zeitreihe der "High"-Preise m_low.Create(Symbol(), Period()); // Initialisierung der Zeitreihe der "Low"-Preise m_close.Create(Symbol(), Period()); // Initialisierung der Zeitreihe der "Close"-Preise m_signal_ma.SetPriceSeries(GetPointer(m_open), // Initialisierung des Signalmoduls mit den Zeitreihen-Objekten GetPointer(m_high), GetPointer(m_low), GetPointer(m_close)); }
Es sollte angemerkt werden, dass die Probleme der Initialisierung für den Nutzer des automatische Strategiegenerators nicht relevant sind. Die gesamte Kette der Initialisierungen wird automatisch im Strategiegenerator erstellt, alles, was der Nutzer machen muss, ist den Expert Advisor verwenden. Anders ist die Situation für die, die mit dieser Reihe von Klassen Ihre eigenen Lösungen erzeugen wollen. In diesem Fall wird es notwendig sein, die gesamte Initialisierungskette auszuführen.
Übersicht über die Signalmodule, das Konzept der Muster
Wie bereits erwähnt, basiert das Signalmodul auf der gemeinsamen CExpertSignal-Klasse, die ihrerseits auf CExpertBase basiert. Jedes Signalmodul von Signalen ist in der Tat eine Klasse mit Funktionen, die nach einem oder mehreren Mustern suchen — speziellen, logischen Bedingungen, die den Moment für Kauf oder Verkauf bestimmen. Zum Beispiel, wenn der schnelle gleitende Durchschnitt den langsam nach oben kreuzt, bildet sich so ein Kauf-Muster. Jeder Indikator kann eine Reihe von Bedingungen für Kauf und Verkauf bilden. Ein einfaches Beispiel ist der MACD-Indikator. Es können sowohl die Divergenz des Indikators wie die einfache Kreuzung der Signallinie mit dem Histogramms des Indikators als Signal interpretiert werden. Beide Handelsbedingungen sind unterschiedliche Muster. Ein Markteintritt ist möglich beim Erscheinen eines oder mehrerer dieser Ereignisse. Es ist wichtig zu beachten, dass sich die die Muster von Kauf und Verkauf unterscheiden und in der Regel entgegengesetzten sein sollten. Aus der Sicht des automatischen Strategiegenerators ist ein Signal ein Zustand von einem oder mehrerer Muster eines Indikators. Zum Beispiel kann ein Signal auf Basis des MACD das Vorhandensein mehrerer Mustern des Marktes bedeuten. Das Signalmodul der Signale auf Basis des MACD enthält fünf Kauf- und fünf Verkauf-Muster:
- Umkehr — Der Oszillator dreht nach oben (Kauf) oder nach unten (Verkauf)
- Der Schnittpunkt der Haupt- und Signallinie
- Das Kreuzen der Nulllinie
- Divergenz
- Doppelte Divergenz
Die detaillierte Beschreibung der Muster ist in den Terminal-Hilfe-Dateien, wir werden sie daher hier nicht im Detail diskutieren. Andere Signalmodule enthalten verschiedene Muster in unterschiedlicher Zahl. In der Regel enthält jedes Signal durchschnittlich drei Kauf- und drei Verkauf-Muster. Ein Signal kann maximal 32 Muster in eine Richtung und 32 in die entgegengesetzte enthalten (das ist die Länge des Bit-Feldes einer Integer-Variablen, die die Maske des verwendeten Muster speichert).
Das Signal kann Muster erkennen, es kann aber auch Empfehlungen als ganze Zahl geben. Es wird erwartet, dass diese Zahl die Signalstärke zeigen sollte: je größer die Zahl, desto stärker ist das Signal. Es ist auch möglich, mit Hilfe der speziellen Gruppe von Methoden Pattern_x(int value) für jedes der Muster die Signalstärke einzeln zu setzen, wobei x den Index des Musters kennzeichnet. Beispielsweise könnte der Code des Signals-Konfigurators wie folgt aussehen:
//+------------------------------------------------------------------+ //| Initialisierung des CSignalMacd Signal-Moduls | //+------------------------------------------------------------------+ CStrategyMACD::CStrategyMACD(void) { m_signal_ma.Pattern_0(0); m_signal_ma.Pattern_1(0); m_signal_ma.Pattern_2(0); m_signal_ma.Pattern_3(100); m_signal_ma.Pattern_4(0); m_signal_ma.Pattern_5(0); }
In diesem Fall gibt das Signalmodul CSignalMacd nur einen Wert zurück, wenn sich eine Divergenz gebildet hat, es ist jenes Muster des MACD, in dem das erste analysierte Tal des Oszillators weniger tief ist als das vorherige und das entsprechende Tal der Preise hingegen tiefer als das vorherige ist (dies ist Definition für einen Kauf). Alle anderen Muster werden ignoriert.
Die Handelsempfehlung werden durch zwei unabhängigen Methoden LongCondition und ShortCondition geliefert. Die Erste liefert die Empfehlung zu kaufen, die Zweite zu verkaufen. Werfen wir einen Blick auf das Innere einer der beiden, um die Arbeitsweise zu verstehen. Hier ist LongCondition:
//+------------------------------------------------------------------+ //| Ein Hinweis, dass der Preis steigen wird. | //+------------------------------------------------------------------+ int CSignalMACD::LongCondition(void) { int result=0; int idx =StartIndex(); //--- Prüfung der Richtung der Hauptlinie double diff = DiffMain(idx); if(diff>0.0) { //--- die Hauptlinie läuft aufwärts, die Bestätigung für einen möglichen Preisanstieg if(IS_PATTERN_USAGE(0)) result=m_pattern_0; // signal confirmation number 0 //--- Wird das Muster 1 verwendet, wird die Umkehr der Hauptlinie gesucht if(IS_PATTERN_USAGE(1) && DiffMain(idx+1)<0.0) result=m_pattern_1; // Signalnummer 1 //--- Wird das Muster 2 verwendet, wird das Kreuzen der Haupt- mit der Signallinie gesucht if(IS_PATTERN_USAGE(2) && State(idx)>0.0 && State(idx+1)<0.0) result=m_pattern_2; // Signal Nr. 2 //--- Wird das Muster 3 verwendet, wird das Kreuzen der Haupt- mit der Nulllinie gesucht if(IS_PATTERN_USAGE(3) && Main(idx)>0.0 && Main(idx+1)<0.0) result=m_pattern_3; // Signal No 3 //--- Werden Muster 4 und 5 verwendet und die Hauptlinie ist kleiner als Null und steigt, wird nach einer Divergenz gesucht if((IS_PATTERN_USAGE(4) || IS_PATTERN_USAGE(5)) && Main(idx)<0.0) { //--- Eine erweiterte Analyse des Zustands des Oszillators ExtState(idx); //--- Wird das Muster 4 verwendet, wird ein Divergenzsignal erwartet if(IS_PATTERN_USAGE(4) && CompareMaps(1,1)) // 0000 0001b result=m_pattern_4; // Signal No 4 //--- Wird das Muster 5 verwendet, wird ein doppeltes Divergenzsignal erwartet if(IS_PATTERN_USAGE(5) && CompareMaps(0x11,2)) // 0001 0001b return(m_pattern_5); //Signal No 5 } } //--- Ergebnisrückgabe return(result); }
Die Methode arbeitet mit dem Makro IS_PATTERN_USAGE zur Mustererkennung, die auf der Bitmaske basiert:
//--- Prüfen, ob das Markt-Muster verwendet wird #define IS_PATTERN_USAGE(p) ((m_patterns_usage&(((int)1)<<p))!=0)
Wenn das Muster verwendet wird und die entsprechenden Bedingungen erfüllt sind, wird das Ergebnis der Empfehlung gleich dem entsprechenden Muster der Wichtungen, die ihrerseits vom Nutzer durch die Methodengruppe Pattern_x definiert ist. Wenn Sie jedoch mehrere Muster verwenden, ist es nicht möglich herauszufinden, welches Muster ausgelöst wurde, da die Prüfung nicht zerlegt werden kann. Die Stärke der Empfehlungen ist in diesem Fall gleich dem Gewicht des zuletzt gefundenen Musters.
Die Nummer des Muster, das Sie verwenden wollen, sollte in einer speziellen Bitmaske übergeben werden. Wollen Sie zum Beispiel das Muster №3 verwenden, muss das 4. Bit der 32-Bitmaske 1 sein (beachten Sie der Index beginnt mit Null, es bestimmt daher die vierte Ziffer das dritte Muster und die erste Ziffer das Muster 0). Wenn wir die binäre Zahl 1000 in eine Dezimalzahl umwandeln, erhalten wir die Zahl 8. Diese Zahl müssen Sie der Methode PatternsUsage übergeben. Die Muster können kombiniert werden. Um zum Beispiel Muster 2 zusammen mit 3 zu verwenden, müssen Sie ein Bitmaske erstellen, deren dritte und vierte Stelle 1 ist: 1100. Der gleichen Wert als Dezimalzahl ist 12.
Ein Signalmodul. Ersten Verwendung
Wir haben jetzt genug gelernt und können beginnen die Signalmodule zu verwenden. Experimentieren wir auf Basis von CStrategy. Zu diesem Zweck erstellen wir eine spezielle, experimentelle Klasse, die CSignalSamples Strategie.
//+------------------------------------------------------------------+ //| EventListener.mqh | //| Copyright 2016, Vasiliy Sokolov, St-Petersburg, Russia | //| https://www.mql5.com/de/users/c-4 | //+------------------------------------------------------------------+ #property copyright "Copyright 2016, Vasiliy Sokolov." #property link "https://www.mql5.com/de/users/c-4" #include <Strategy\Strategy.mqh> #include <Expert\Signal\SignalMACD.mqh> //+------------------------------------------------------------------+ //| Die Strategie erhält Ereignisse und zeigt sie im Terminal. | //+------------------------------------------------------------------+ class CSignalSamples : public CStrategy { private: CSignalMACD m_signal_macd; CSymbolInfo m_info; CiOpen m_open; CiHigh m_high; CiLow m_low; CiClose m_close; CIndicators m_indicators; public: CSignalSamples(void); virtual void OnEvent(const MarketEvent& event); }; //+------------------------------------------------------------------+ //| Initialisierung des CSignalMacd Signal-Moduls | //+------------------------------------------------------------------+ CSignalSamples::CSignalSamples(void) { m_signal_macd.Pattern_0(0); m_signal_macd.Pattern_1(0); m_signal_macd.Pattern_2(0); m_signal_macd.Pattern_3(100); m_signal_macd.Pattern_4(0); m_signal_macd.Pattern_5(0); m_info.Name(Symbol()); // Initialisierung des Objektes des Handelssymbols dieser Strategie m_signal_macd.Init(GetPointer(m_info), Period(), 10); // Initialisierung des Signalmoduls mit dem Handelssymbol und Zeitrahmen m_signal_macd.InitIndicators(GetPointer(m_indicators)); // Erstellen der erforderlichen Indikatoren im Signalmodul auf Basis der leeren Liste m_indicators von Indikatoren m_signal_macd.EveryTick(true); // Testmodus m_signal_macd.Magic(ExpertMagic()); // Magic-Nummer m_signal_macd.PatternsUsage(8); // Maskenmuster m_open.Create(Symbol(), Period()); // Initialisierung der Zeitreihe der "Open"-Preise m_high.Create(Symbol(), Period()); // Initialisierung der Zeitreihe der "High"-Preise m_low.Create(Symbol(), Period()); // Initialisierung der Zeitreihe der "Low"-Preise m_close.Create(Symbol(), Period()); // Initialisierung der Zeitreihe der "Close"-Preise m_signal_macd.SetPriceSeries(GetPointer(m_open), // Initialisierung des Signalmoduls mit den Zeitreihen-Objekten GetPointer(m_high), GetPointer(m_low), GetPointer(m_close)); } //+------------------------------------------------------------------+ //| Kaufen. | //+------------------------------------------------------------------+ void CSignalSamples::OnEvent(const MarketEvent &event) { if(event.type != MARKET_EVENT_BAR_OPEN) return; m_indicators.Refresh(); m_signal_macd.SetDirection(); int power_sell = m_signal_macd.ShortCondition(); int power_buy = m_signal_macd.LongCondition(); if(power_buy != 0 || power_sell != 0) printf("PowerSell: " + (string)power_sell + " PowerBuy: " + (string)power_buy); } //+------------------------------------------------------------------+
Die neuesten Versionen von CStrategy enthalten ein neues Ereignis OnEvent, das durch die Methode OnEvent repräsentiert wird, die aufgerufen wird, wenn ein Ereignis auftritt. Anders als die bekannteren Methoden BuyInit, SellInit, BuySupport und SellSupport, wird OnEvent aufgerufen unabhängig von der Handelsstrategie oder dem Handelsplan. OnEvent erlaubt daher den Zugriff auf den Ablauf der Ereignisse der Strategie unter Beibehaltung des strengen Ereignismodells. OnEvent ist sehr einfach für allgemeine Berechnungen oder Aktionen zu verwenden, die nicht direkt mit den Kauf- oder Verkauf-Richtungen in Beziehung stehen.
Der größte Teil der Codes betrifft die Initialisierung des Moduls der Handelssignale. Als Beispiel verwenden wir ein Modul auf Basis des MACD-Indikators: CSignalMACD. Es wird nur das Muster Nr. 3 des Moduls verwendet. Das Gewicht 100 wird dem Modul übergeben, und die passende Methode PatternsUsage mit dem Wert 8 verwendet. Um jetzt diese Strategie zu fahren, muss ein Strategie-Lader oder ein ausführbares mq5-Modul vorbereitet werden. Sein Inhalt:
//+------------------------------------------------------------------+ //| Agent.mq5 | //| Copyright 2016, Vasiliy Sokolov, St-Petersburg, Russia | //| https://www.mql5.com/de/users/c-4 | //+------------------------------------------------------------------+ #property copyright "Copyright 2016, Vasiliy Sokolov." #property link "https://www.mql5.com/de/users/c-4" #property version "1.00" #include <Strategy\StrategiesList.mqh> #include <Strategy\Samples\SignalSamples.mqh> CStrategyList Manager; //+------------------------------------------------------------------+ //| Initialisierungs-Funktion des Experten | //+------------------------------------------------------------------+ int OnInit() { CSignalSamples* signal = new CSignalSamples(); signal.ExpertMagic(2918); signal.ExpertName("MQL Signal Samples"); signal.Timeframe(Period()); if(!Manager.AddStrategy(signal)) delete signal; return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { Manager.OnTick(); } //+------------------------------------------------------------------+ //| OnChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { Manager.OnChartEvent(id, lparam, dparam, sparam); }
Es verfügt über die Initialisierung der Strategie in der Funktion OnInit und den Handles der neuen Ticks in OnTick. Nach der Ausführung des resultierenden Codes im Strategie Tester im "Visuellen Modus" sehen Sie die Hinweise über die erhaltenen Signale:
2016.06.20 16:34:31.697 tester agent shutdown finished 2016.06.20 16:34:31.642 shutdown tester machine 2016.06.20 16:34:31.599 tester agent shutdown started 2016.06.20 16:34:31.381 log file "Z:\MetaTrader 5\Tester\Agent-127.0.0.1-3000\logs\20160620.log" written 2016.06.20 16:34:31.381 325 Mb memory used including 28 Mb of history data, 64 Mb of tick data 2016.06.20 16:34:31.381 EURUSD,M1: 51350 ticks (12935 bars) generated in 0:00:00.780 (total bars in history 476937, total time 0:00:00.843) 2016.06.20 16:34:31.376 final balance 100000.00 USD 2016.06.20 16:34:31.373 2016.04.14 22:12:00 PowerSell: 100 PowerBuy: 0 2016.06.20 16:34:31.373 2016.04.14 22:01:00 PowerSell: 0 PowerBuy: 100 2016.06.20 16:34:31.373 2016.04.14 21:24:00 PowerSell: 100 PowerBuy: 0 2016.06.20 16:34:31.373 2016.04.14 20:54:00 PowerSell: 0 PowerBuy: 100 2016.06.20 16:34:31.373 2016.04.14 20:50:00 PowerSell: 100 PowerBuy: 0 2016.06.20 16:34:31.373 2016.04.14 20:18:00 PowerSell: 0 PowerBuy: 100 2016.06.20 16:34:31.373 2016.04.14 20:14:00 PowerSell: 100 PowerBuy: 0 2016.06.20 16:34:31.373 2016.04.14 20:13:00 PowerSell: 100 PowerBuy: 0 2016.06.20 16:34:31.373 2016.04.14 20:07:00 PowerSell: 0 PowerBuy: 100 2016.06.20 16:34:31.372 2016.04.14 19:48:00 PowerSell: 100 PowerBuy: 0 2016.06.20 16:34:31.372 2016.04.14 18:48:00 PowerSell: 0 PowerBuy: 100 ... ...
Diese Benachrichtigung zeigt an, dass die angeforderten Signale erfolgreich empfangen wurden, und wir fortfahren können, die Signalmodule in unsere Strategie zu integrieren.
Schreiben wir die erste Strategie auf Basis von CSignalMACD
Jetzt ist es Zeit, eine voll funktionsfähige Strategie auf der Grundlage des Moduls CSignalMACD zu schreiben. Das erste Muster, das wir verwenden, ist das Kreuzen der Signallinie mit dem Oszillator. Verwenden wir die Dokumentation. Öffnen Sie Nachschlagewerk MQL5 -> Standardbibliothek -> Handelsstrategien-Klassen -> Module der Handelssignale -> Signale des Oszillators MACD. Suchen Sie das zweite Muster "Die Haupt- und Signallinie schneiden". Hier ist seine Beschreibung:
- Kaufen: "Die Haupt- und Signallinie schneiden" — die Hauptlinie ist über der Signallinie zur Zeit der analysierten Bar und unter der Signallinie eine Bar vorher.
- Verkaufen: "Die Haupt- und Signallinie schneiden" — die Hauptlinie ist unter der Signallinie zur Zeit der analysierten Bar und über der Signallinie eine Bar vorher.
Fig 2. Der Oszillator kreuzt die Signallinie aufwärts
Fig. 3. Der Oszillator kreuzt die Signallinie abwärts
Wir müssen die Nummer der Muster entsprechend dieser Beschreibung festlegen. Der Header der CSignalMACD-Klasse wird uns helfen:
//+------------------------------------------------------------------+ //| Class CSignalMACD. | //| Purpose: Class of generator of trade signals based on | //| the 'Moving Average Convergence/Divergence' oscillator. | //| Is derived from the CExpertSignal class. | //+------------------------------------------------------------------+ class CSignalMACD : public CExpertSignal { protected: CiMACD m_MACD; // Oszillator Objekt //--- angepasste Parameter int m_period_fast; // Oszillator Parameter "fast EMA period" (Glättunglänge des schnellen EMAs) int m_period_slow; //Oszillator Parameter "slow EMA period" (Glättunglänge des langsamen EMAs) int m_period_signal; // Oszillator Parameter "difference averaging period" (Glättunglänge der Signallinie) ENUM_APPLIED_PRICE m_applied; // Oszillator Parameter "price series" (Kalkulationspreis) //--- "weights" of market models (0-100) (Wichtungen) int m_pattern_0; // Muster 0 "Oszillator bewegt sich in die gewünschte Richtung" int m_pattern_1; // Muster 1 "Oszillator dreht sich in die gewünschte Richtung" int m_pattern_2; // Muster 2 "Haupt- und Signallinie kreuzen sich" int m_pattern_3; // Muster 3 "Hauptlinie kreuzt die Nulllinie" int m_pattern_4; // Muster 4 "Divergenz des Oszillators mit den Preisen" int m_pattern_5; // Muster 5 "doppelte Divergenz des Oszillators mit den Preisen" //--- Die Variablen double m_extr_osc[10]; // Ein Array der Extrema des Oszillator double m_extr_pr[10]; // Ein Array mit den entsprechenden Preisextrema int m_extr_pos[10]; // Versatz-Array der Extrema (in Bars) uint m_extr_map; // Resultierendes Bitmap der Extrema des Oszillators und der Preisextrema ... }
Aus den Kommentaren im Code sehen wir, unser Mustertyp ist Nr. 2.
Da wir jetzt die Nummer des Muster kennen, müssen wir jetzt das Signal sauber konfigurieren. Erstens, um Verwirrungen zu vermeiden, verwenden wir keine anderen Muster. Daher setzen wir die Maske der Muster auf 4 (100 in binärer Form). Da wir nur ein Muster verwenden, müssen wir die Signalstärke nicht kennen (keine Konfiguration der Stärke des Musters) - entweder es gibt ein Signal oder es gibt kein Signal. Wir werden das Signal beim Eröffnen einer neuen Bar überprüfen, wir müssen daher der entsprechende Signalmethode EveryTick aufrufen und "false" übergeben. Die Konfiguration sollte im Strategie-Konstruktor implementiert werden. Danach programmieren wir die Handelslogik. Wir überschreiben dafür die Methoden InitBuy, SupportBuy, InitSell, SupportSell. Wir benennen die Strategie COnSignalMACD: der On Präfix zeigt, dass die Strategie auf dem Signalmodul des Signals basiert. Der Code der Strategie ist unten angegeben:
//+------------------------------------------------------------------+ //| EventListener.mqh | //| Copyright 2016, Vasiliy Sokolov, St-Petersburg, Russia | //| https://www.mql5.com/de/users/c-4 | //+------------------------------------------------------------------+ #property copyright "Copyright 2016, Vasiliy Sokolov." #property link "https://www.mql5.com/de/users/c-4" #include <Strategy\Strategy.mqh> #include <Expert\Signal\SignalMACD.mqh> //+------------------------------------------------------------------+ //| Die Strategie erhält Ereignisse und zeigt sie im Terminal. | //+------------------------------------------------------------------+ class COnSignalMACD : public CStrategy { private: CSignalMACD m_signal_macd; CSymbolInfo m_info; CiOpen m_open; CiHigh m_high; CiLow m_low; CiClose m_close; CIndicators m_indicators; public: COnSignalMACD(void); virtual void InitBuy(const MarketEvent &event); virtual void InitSell(const MarketEvent &event); virtual void SupportBuy(const MarketEvent& event, CPosition* pos); virtual void SupportSell(const MarketEvent& event, CPosition* pos); }; //+------------------------------------------------------------------+ //| Initialisierung des CSignalMacd Signal-Moduls | //+------------------------------------------------------------------+ COnSignalMACD::COnSignalMACD(void) { m_info.Name(Symbol()); // Initialisierung des Objektes des Handelssymbols dieser Strategie m_signal_macd.Init(GetPointer(m_info), Period(), 10); // Initialisierung des Signalmoduls mit dem Handelssymbol und Zeitrahmen m_signal_macd.InitIndicators(GetPointer(m_indicators)); // Erstellen der erforderlichen Indikatoren im Signalmodul auf Basis der leeren Liste m_indicators von Indikatoren m_signal_macd.EveryTick(false); // Testmodus m_signal_macd.Magic(ExpertMagic()); // Magic-Nummer m_signal_macd.PatternsUsage(4); // Maskenmuster m_open.Create(Symbol(), Period()); // Initialisierung der Zeitreihe der "Open"-Preise m_high.Create(Symbol(), Period()); // Initialisierung der Zeitreihe der "High"-Preise m_low.Create(Symbol(), Period()); // Initialisierung der Zeitreihe der "Low"-Preise m_close.Create(Symbol(), Period()); // Initialisierung der Zeitreihe der "Close"-Preise m_signal_macd.SetPriceSeries(GetPointer(m_open), // Initialisierung des Signalmoduls mit den Zeitreihen-Objekten GetPointer(m_high), GetPointer(m_low), GetPointer(m_close)); } //+------------------------------------------------------------------+ //| Kaufen. | //+------------------------------------------------------------------+ void COnSignalMACD::InitBuy(const MarketEvent &event) { if(event.type != MARKET_EVENT_BAR_OPEN) return; m_indicators.Refresh(); m_signal_macd.SetDirection(); int power_buy = m_signal_macd.LongCondition(); if(power_buy != 0) Trade.Buy(1.0); } //+------------------------------------------------------------------+ //| Kaufposition schließen | //+------------------------------------------------------------------+ void COnSignalMACD::SupportBuy(const MarketEvent &event, CPosition* pos) { if(event.type != MARKET_EVENT_BAR_OPEN) return; m_indicators.Refresh(); m_signal_macd.SetDirection(); int power_sell = m_signal_macd.ShortCondition(); //printf("Power sell: " + (string)power_sell); if(power_sell != 0) pos.CloseAtMarket(); } //+------------------------------------------------------------------+ //| Verkaufen. | //+------------------------------------------------------------------+ void COnSignalMACD::InitSell(const MarketEvent &event) { if(event.type != MARKET_EVENT_BAR_OPEN) return; m_indicators.Refresh(); m_signal_macd.SetDirection(); int power_sell = m_signal_macd.ShortCondition(); if(power_sell != 0) Trade.Sell(1.0); } //+------------------------------------------------------------------+ //| Verkaufposition schließen | //+------------------------------------------------------------------+ void COnSignalMACD::SupportSell(const MarketEvent &event, CPosition* pos) { if(event.type != MARKET_EVENT_BAR_OPEN) return; m_indicators.Refresh(); m_signal_macd.SetDirection(); int power_buy = m_signal_macd.LongCondition(); if(power_buy != 0) pos.CloseAtMarket(); } //+------------------------------------------------------------------+
Kauf- und Verkauf-Positionen werden auf Basis der in der Referenz beschriebenen Signale eröffnet. Offene Positionen werden durch entgegengesetzte Signale geschlossen. Stimmen daher die Bedingungen für die Eröffnung einer Kaufposition, wird einer zuvor geöffnete Verkaufsposition geschlossen und umgekehrt.
Das Handelsergebnis kann im Strategietester betrachtet werden. Ein Teil der Testhistorie ist im Bild unten gezeigt:
Fig. 4. Positionseröffnung im Falle einer Kreuzung des MACD Histogramms mit der Signallinie
Gemäß des Testmodus' werden die Positionen nach einem Signal der vorherigen Bar eröffnet. Die Abbildung zeigt, dass, wenn auf der vorherige Bar das MACD-Histogramm die Signallinie kreuzt, die noch offene Position geschlossen und dann eine neue Kauf- oder Verkauf-Position eröffnet wird.
Ein Adapter für die Signale
Wir haben herausgefunden, dass wir zuerst die Signal konfigurieren müssen, bevor wir mit ihnen arbeiten können. Signale werden durch komplexe Signale konfiguriert, die ihrerseits konfiguriert werden müssen, bevor sie weitergegeben werden können. Unterschiedliche Signale bedingen unterschiedliche Objekte zum Funktionieren. Zum Beispiel benötigen einige Signale Spezifikationen der Basiszeitreihen, während andere Signale Container von Indikatoren und zusätzliche Preisdaten, wie Ticks oder das Volumen, benötigen. All dies erschwert die Verwendung von Signalen durch einen Anwender, da er das interne System der Signale und die Daten, die für seine ordnungsgemäße Funktion benötigt werden, kennen muss.
Um diese Probleme zu vermeiden, wurde eine spezielle Adapter-Klasse eingeführt. Die Klasse heißt CSignalAdapter und befindet sich im Verzeichnis des CStrategy Projektes. Der Adapter verfügt über eine einfache Schnittstelle. Er erlaubt das Erstellen von Signalen und den Erhalt von Hinweisen von Mustern für Kauf und Verkauf. Um eine Signal zu erstellen, müssen wir die Signalparameter einer speziellen Methode CSignalAdapter::CreateSignal übergeben. Die Signalparameter sind Teil der speziellen Struktur MqlSignalParams. Hier ist die Definition dieser Struktur:
//+--------------------------------------------------------------------+ //| Signal parameters | //+--------------------------------------------------------------------+ struct MqlSignalParams { public: string symbol; // Symbol ENUM_TIMEFRAMES period; // Zeitrahmen ENUM_SIGNAL_TYPE signal_type; // Signaltyp int index_pattern; // Musterindex int magic; // EAs Magic-Number double point; // Anzahl von "Points" bool every_tick; // Testmethode "Jeder Tick" void operator=(MqlSignalParams& params); }; //+--------------------------------------------------------------------+ //| Verwendung des Kopieroperators, da Zeichenketten verwendet werden | //+--------------------------------------------------------------------+ void MqlSignalParams::operator=(MqlSignalParams& params) { symbol = params.symbol; period = params.period; signal_type = params.signal_type; usage_pattern = params.usage_pattern; magic = params.magic; point = params.point; every_tick = params.every_tick; }
Die Struktur verfügt über die grundlegenden Typen, die die nachfolgenden Signaleigenschaften definieren:
- Symbol;
- Zeitrahmen;
- Signaltyp;
- Die Magic-Nummer des Expert Advisors;
- Ein Merker für den Testmodus von "Every tick";
- Preisfilter;
- Verwendetes Signalmuster.
Hier noch ein paar Details zum index_pattern. Anders als bei den Signalmodule werden keine die Maskenmuster akzeptiert, sondern nur der Index von einem von ihnen. Daher kann jeder Signaladapter nur ein Muster der gewählten Signale verwenden.. Der Indexwert des Musters sollte zwischen 1 und 31 sein und gleich der wirklichen Nummer des verwendeten Signals.
Zusätzlich zu den Basisparametern verfügt die Struktur über den Kopieroperator, denn es werden Zeichenketten verwendet. Das ist der Grund, weshalb es unmöglich ist, automatisch eine Struktur auf eine andere zu kopieren. Nach der Bestimmung der notwendigen Parameter und dem Ausfüllen der entsprechenden Struktur kann der Nutzer die Methode CSignalAdapter::CreateSignal aufrufen und erhält von dieser Methode eine Instanz des erzeugten Signals. Die empfangene Instanz kann dann unter Berücksichtigung der spezifischen Merkmale des entsprechenden Signals weiter konfiguriert werden.
Die Auflistung unten zeigt eine Methode zum Konfigurieren des Signals CSignalMACD mit dem Adapter CSignalAdapter:
//+------------------------------------------------------------------+ //| EventListener.mqh | //| Copyright 2016, Vasiliy Sokolov, St-Petersburg, Russia | //| https://www.mql5.com/de/users/c-4 | //+------------------------------------------------------------------+ #property copyright "Copyright 2016, Vasiliy Sokolov." #property link "https://www.mql5.com/de/users/c-4" #include <Strategy\Strategy.mqh> #include <Strategy\SignalAdapter.mqh> //+------------------------------------------------------------------+ //| Die Strategie erhält Ereignisse und zeigt sie im Terminal. | //+------------------------------------------------------------------+ class CAdapterMACD : public CStrategy { private: CSignalAdapter m_signal; MqlSignalParams m_params; public: CAdapterMACD(void); virtual void InitBuy(const MarketEvent &event); virtual void InitSell(const MarketEvent &event); virtual void SupportBuy(const MarketEvent& event, CPosition* pos); virtual void SupportSell(const MarketEvent& event, CPosition* pos); }; //+------------------------------------------------------------------+ //| Konfiguration des Adapters | //+------------------------------------------------------------------+ CAdapterMACD::CAdapterMACD(void) { m_params.symbol = Symbol(); m_params.period = Period(); m_params.every_tick = false; m_params.signal_type = SIGNAL_MACD; m_params.magic = 1234; m_params.point = 1.0; m_params.usage_pattern = 2; CSignalMACD* macd = m_signal.CreateSignal(m_params); macd.PeriodFast(15); macd.PeriodSlow(32); macd.PeriodSignal(6); }
Die Parameter des Adapters müssen auch konfiguriert werden. Jedoch, im Gegensatz zur ersten Version des Expert Advisors, sind alle Parameter trivial bzw. Basistypen. Darüber hinaus muss man keine weiteren, komplexe Objekte erstellen und kontrollieren, wie Zeitreihen und Indikatoren. All das erledigt der Adapter. Darum vereinfacht er so stark die Arbeit mit Signalen.
Beachten Sie, dass nach dem Erstellen des Signals die Konfiguration durch das Setzen der eigenen Periodenlänge des MACD-Indikators (15, 32, 6) erfolgt. Das ist so einfach, weil die Methode CreateSignal das entsprechende Objekt liefert.
Ist das Signal sauber konfiguriert, können Sie es sofort verwenden. Die einfachen Methoden BuySignal und ShortSignal werden zu diesem Zweck verwendet. Hier ist die Fortsetzung des Strategie-Klasse:
//+------------------------------------------------------------------+ //| Kaufen. | //+------------------------------------------------------------------+ void CAdapterMACD::InitBuy(const MarketEvent &event) { if(event.type != MARKET_EVENT_BAR_OPEN) return; if(m_signal.LongSignal()) Trade.Buy(1.0); } //+------------------------------------------------------------------+ //| Kaufposition schließen | //+------------------------------------------------------------------+ void CAdapterMACD::SupportBuy(const MarketEvent &event, CPosition* pos) { if(event.type != MARKET_EVENT_BAR_OPEN) return; if(m_signal.ShortSignal()) pos.CloseAtMarket(); } //+------------------------------------------------------------------+ //| Verkaufen | //+------------------------------------------------------------------+ void CAdapterMACD::InitSell(const MarketEvent &event) { if(event.type != MARKET_EVENT_BAR_OPEN) return; if(m_signal.ShortSignal()) Trade.Sell(1.0); } //+------------------------------------------------------------------+ //| Verkaufposition schließen | //+------------------------------------------------------------------+ void CAdapterMACD::SupportSell(const MarketEvent &event, CPosition* pos) { if(event.type != MARKET_EVENT_BAR_OPEN) return; if(m_signal.LongSignal()) pos.CloseAtMarket(); }
Die obige Logik ist gleich der des vorherigen Beispiels: Kauf oder Verkauf-Positionen werden eröffnet, wenn das MACD-Histogramm die Signallinie kreuzt. Allerdings, wie man sieht, ist der Code jetzt noch kürzer. Anders als die erste Version des Codes des Signals muss jetzt nicht mehr jedes Mal die Richtung definiert oder die Werte des Indikators aktualisiert werden. Es müssen dem Code der Strategie auch keine Hilfsobjekte hinzugefügt werden. All das leistet der Adapter.
Kombination mehrerer Signale im Code einer Handelsstrategie
Können wir verschiedene Muster und auch verschiedene Signale für den Markteintritt und -Austritt verwenden? Die Antwort ist ja. Wir haben vollen Zugriff auf das Signalsystem, ergo können wir mehr als ein Muster verwenden. Um zu verhindern, dass Muster sich gegenseitig beeinflussen, erlaubt der Adapter immer nur ein Muster. Aber die Zahl der Adapter ist nicht limitiert. In diesem Fall gibt es für jedes Muster einen separaten Adapter und Signal, selbst wenn alle Muster auf nur einem Signal basieren. Natürlich ist das bezüglich der Ressourcen weniger effizient als die Version aus der Standardbibliothek, aber es gibt dennoch Vorteile.
Schreiben wir nun ein Beispiel einer Strategie mit verschiedenen Signalen für das Eröffnen und das Schließen von Positionen. Die Strategie verwendet den RSI Indikator mit seinen Überkauft- und Überverkauft-Zonen für den Markteintritt. Als zweites Muster den "Accelerator Oscillator" (AC) entwickelt von Bill Williams für das Schließen der Positionen. Hier die Regeln der Strategie im Detail.
Kaufen: Rückkehr aus der Überverkauft-Zone — der Oszillator dreht nach oben und sein Wert der analysierten Bar ist über dem Überverkauft-Level (Standardwert ist 30).
Fig. 5. Bedingungen für das Eröffnen einer Kaufposition
Verkauf: Rückkehr aus der Überkauft-Zone — der Oszillator dreht nach unten und sein Wert der analysierten Bar ist unter dem Überverkauft-Level (Standardwert ist 70).
Fig. 6. Bedingungen für das Eröffnen einer Verkaufposition
Kaufposition schließen: Der Wert des AC Indikators ist über 0 und fällt während dieser und der beiden vorherigen Bars:
Fig. 7. Bedingungen für das Schließen der Kaufposition
Verkaufposition schließen: Der Wert des AC Indikators ist unter 0 und steigt während dieser und der beiden vorherigen Bars:
Fig. 8. Bedingungen für das Schließen der Verkaufposition
Das Schließen einer Kauf-Position wird auf Basis eines Musters des AC-Signals ausgeführt, das die Eröffnung einer Verkauf-Position bedeuten würde und umgekehrt, man schließt eine Verkauf-Position durch das Signal einer Kauf-Position.
Der Expert Advisor mit diese Logik steht unten:
//+------------------------------------------------------------------+ //| EventListener.mqh | //| Copyright 2016, Vasiliy Sokolov, St-Petersburg, Russia | //| https://www.mql5.com/de/users/c-4 | //+------------------------------------------------------------------+ #property copyright "Copyright 2016, Vasiliy Sokolov." #property link "https://www.mql5.com/ru/users/c-4" #include <Strategy\Strategy.mqh> #include <Strategy\SignalAdapter.mqh> input int RSI_Period = 14; // RSI Period //+------------------------------------------------------------------+ //| Die Strategie erhält Ereignisse und zeigt sie im Terminal. | //+------------------------------------------------------------------+ class COnSignal_RSI_AC : public CStrategy { private: CSignalAdapter m_adapter_rsi; CSignalAdapter m_adapter_ac; public: COnSignal_RSI_AC(void); virtual void InitBuy(const MarketEvent &event); virtual void InitSell(const MarketEvent &event); virtual void SupportBuy(const MarketEvent& event, CPosition* pos); virtual void SupportSell(const MarketEvent& event, CPosition* pos); }; //+------------------------------------------------------------------+ //| Initialisierung des CSignalMacd Signal-Moduls | //+------------------------------------------------------------------+ COnSignal_RSI_AC::COnSignal_RSI_AC(void) { MqlSignalParams params; params.every_tick = false; params.magic = 32910; params.point = 10.0; params.symbol = Symbol(); params.period = Period(); params.usage_pattern = 2; params.signal_type = SIGNAL_AC; CSignalAC* ac = m_adapter_ac.CreateSignal(params); params.usage_pattern = 1; params.magic = 32911; params.signal_type = SIGNAL_RSI; CSignalRSI* rsi = m_adapter_rsi.CreateSignal(params); rsi.PeriodRSI(RSI_Period); } //+------------------------------------------------------------------+ //| Kaufen. | //+------------------------------------------------------------------+ void COnSignal_RSI_AC::InitBuy(const MarketEvent &event) { if(event.type != MARKET_EVENT_BAR_OPEN) return; if(positions.open_buy > 0) return; if(m_adapter_rsi.LongSignal()) Trade.Buy(1.0); } //+------------------------------------------------------------------+ //| Kaufposition schließen | //+------------------------------------------------------------------+ void COnSignal_RSI_AC::SupportBuy(const MarketEvent &event, CPosition* pos) { if(event.type != MARKET_EVENT_BAR_OPEN) return; if(m_adapter_ac.ShortSignal()) pos.CloseAtMarket(); } //+------------------------------------------------------------------+ //| Verkaufen. | //+------------------------------------------------------------------+ void COnSignal_RSI_AC::InitSell(const MarketEvent &event) { if(event.type != MARKET_EVENT_BAR_OPEN) return; if(positions.open_sell > 0) return; if(m_adapter_rsi.ShortSignal()) Trade.Sell(1.0); } //+------------------------------------------------------------------+ //| Kaufposition schließen | //+------------------------------------------------------------------+ void COnSignal_RSI_AC::SupportSell(const MarketEvent &event, CPosition* pos) { if(event.type != MARKET_EVENT_BAR_OPEN) return; if(m_adapter_ac.LongSignal()) pos.CloseAtMarket(); } //+------------------------------------------------------------------+
Beachten Sie, der Expert Advisor hat einen externen Parameter, über den die Periodenlänge des RSI bestimmt werden kann. Dies erfolgt im Strategie-Konstruktor durch direkten Zugriff auf das Signal.
Das Ergebnis dieser Strategie ist unten im Diagramm gezeigt:
Fig. 8. Das Ergebnis der Strategie
Wie der Chart zeigt, verwendet der EA zwei Indikatoren — RSI und AC. Der EA eröffnet eine Position, wenn der RSI sich steigend oder fallend aus den Überkauft- bzw. Überverkauft-Zonen bewegt. Diese Momente sind rot gekennzeichnet. Der EA schließt, wenn der der AC drei Linien mit derselben Farbe bildet. Für das Schließen einer Kauf-Position müssen es rote Linien über Null sein. Für das Schließen einer Verkauf-Position müssen es grüne Linien unter Null sein. Das ist durch blaue Rechtecke hervorgehoben.
Die Grafik zeigt, dass die Logik des EA korrekt arbeitet. Die Handelsregeln dieses Expert Advisor sind nicht trivial. Dennoch ist die Strategie selbst nicht lang. Dies sind die Vorteile der Wiederverwendung von Code.
Schlussfolgerung
Wir haben eine Möglichkeit kennen gelernt, die Standardbibliothek der Signale mit denen der Handelsausführung CStrategy zu verbinden. Durch diese Verbindung wird CStrategy zu einem sehr handlichem Werkzeug für den Nutzer, um eigene Strategien auf Basis der Muster der Standardsignale zu entwickeln. Darüber hinaus steht jedes Signal, das für den MetaTrader Strategiegenerator geschrieben wurde, automatisch den Klassen von CStrategy zur Verfügung.
Die Möglichkeit die Standard-Handelssignale von CStrategy zu verwenden, kann die Entwicklungszeit einer Strategie erheblich reduzieren. Jetzt brauchen Sie Ihre eigenen Indikatoren oder Algorithmen zur Mustererkennung nicht selber schreiben, wenn sie bereits im Standard-Modul der Signale existieren. Weiters reduziert dieses neue Merkmal deutlich die Komplexität der Entwicklung einer Strategie, weil die Bestimmung komplexer Muster, wie die Divergenz oder die doppelte Divergenz, mit Fertiglösungen erreicht werden kann.
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/2540
- 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.