Post-Factum-Handelsanalyse: Auswahl von Trailing-Stops und neuen Stoppstufen im Strategietester
Inhalt
- Einführung
- Auswahl und Verfeinerung einer Klasse von Positions-Trailing-Stops
- Testen verschiedener Arten von Trailing-Stop
- Schlussfolgerung
Einführung
Im vorigen Artikel haben wir einen EA erstellt, der im Strategietester des Client-Terminals auf der Grundlage der Ergebnisse des Handels auf einem echten Konto handelt. Wir haben die Möglichkeit hinzugefügt, neue StopLoss- und TakeProfit-Größen zu setzen, um unseren Handel im Tester mit verschiedenen Stop-Order-Größen zu testen. Das Ergebnis war unerwartet: Anstelle eines Verlustes erhielten wir einen Gewinn, der dem Verlust im realen Handel entsprach. Das heißt, wenn wir die StopLoss- und TakeProfit-Levels in unserem Handelskonto verwendet hätten, die uns im Test einen Gewinn ermöglicht haben, dann wäre der Handel auf einem echten Konto ebenfalls profitabel. Und das ist nur eine einfache Änderung der Stop-Größen. Ich frage mich, was passieren würde, wenn wir einen Trailing StopLoss hinzufügen würden? Wie würde dies die Dinge verändern?
Heute werden wir mehrere verschiedene Trailing Stops mit dem EA verbinden und unser Trading im Strategietester testen. Wir werden sehen, welches Ergebnis wir erhalten werden.
Auswahl und Verfeinerung einer Klasse von Positions-Trailing-Stops
Versuchen wir, eine Verbindung zwischen dem Parabolic SAR und dem gleitender Durchschnitt herzustellen. Der Indikator Parabolic SAR sollte nach seiner Beschreibung und seinem Namen zu urteilen (SAR = Stop And Reverse) perfekt geeignet sein, um die Stopp-Linie entsprechend seiner Werte zu verschieben:
Der technische Indicator Parabolic SAR wurde zur Trend-Analyse entwickelt. Der Indikator wird direkt auf dem Chart gezeichnet. Dieser Indikator ist dem gleitenden Durchschnitt ähnlich, mit dem einzigen Unterschied, dass sich der Parabolic SAR mit höherer Beschleunigung bewegt und seine Position in Bezug auf den Preis ändern kann. Der Indikator verläuft unter dem Preis in einem Aufwärtstrend und über dem Preis in einem Abwärtstrend.
Wenn der Kurs die Parabolic SAR-Linie kreuzt, kehrt sich der Indikator um, und seine weiteren Werte befinden sich auf der anderen Seite des Kurses. Wenn diese Änderung eintritt, sollte das Hoch oder Tief der vorherigen Periode als Startwert genutzt werden. Wenn der Indikator dreht, ist dies das Signal für ein Ende des Trends, eine Korrektur oder einem nicht vorhandenen Trend.
Der Parabolic SAR ist ein sehr guter Indikator für Punkte zum Ausstieg aus einer Position. Kaufpositionen sollten geschlossen werden, wenn der Preis unter den SAR Wert fällt und umgekehrt. Daher ist es notwendig, Richtung des Parabolic SAR zu berücksichtigen und Positionen nur in seiner Richtung zu eröffnen. Der Indikator wird häufig für als Trailing-Stop verwendet.
Wenn es eine offene Kaufposition gibt, der Preis also über der SAR-Linie liegt, wird die SAR-Linie weiter nach oben gehen, egal welche Richtung der Preis nimmt. Das Ausmaß der Veränderung der Linie des Parabolic SAR hängt von der Größe der Preisbewegung ab.
Der gleitende Durchschnitt eignet sich aufgrund seiner natürlichen Verzögerung auch zum Nachziehen der dem Kurs folgenden Stopplinie. Liegt die Indikatorlinie bei einer Kaufposition unter dem Kurs, so kann der Stopp anhand des gleitenden Durchschnittswerts dem Kurs nachgezogen werden. Der Kurs wird den gleitenden Durchschnitt auf natürliche Weise überschreiten und der Stopp wird ausgelöst. Bei einer Verkaufsposition sollte der Stopp der gleitenden Durchschnittslinie folgen, wenn der Kurs darunter liegt.
Ich habe bereits einen Artikel über die Verbindung von Trailing mit EAs geschrieben: „Wie man jede Art von Trailing-Stop entwickelt und mit einem EA verbindet“. In diesem Artikel schlage ich vor, eine Klasse für das Trailing zu erstellen und sie einfach mit dem EA zu verbinden.
Lassen Sie uns diese Gelegenheit wahrnehmen. Das Einzige, was uns nicht so gut gefällt, ist, dass jedes Symbol oder jede magische Zahl eine eigene Schleife durch die offenen Positionen durchläuft. Das bedeutet, dass wir die Klasse aus dem Artikel verfeinern müssen. Zum Glück ist das nicht schwierig.
Laden wir die Datei Trailings.mqh aus dem Anhang des Artikels herunter. Dies ist die einzige Datei, die wir derzeit benötigen. Im vorigen Artikel haben wir einen Ordner erstellt, der Klassen- und EA-Dateien für den Handel nach Geschäftsverlauf enthält: \MQL5\Experts\TradingByHistoryDeals. Speichern Sie die heruntergeladene Datei Trailings.mqh und öffnen Sie sie in MetaEditor.
Die wichtigste und wichtigste Methode, die vom Programm aufgerufen wird, ist Run(). Ein Blick auf den Code zeigt, dass er die Iteration der offenen Positionen implementiert:
//+------------------------------------------------------------------+ //| Launch simple trailing with StopLoss offset from the price | //+------------------------------------------------------------------+ bool CSimpleTrailing::Run(void) { //--- if disabled, leave if(!this.m_active) return false; //--- trailing variables MqlTick tick = {}; // price structure bool res = true; // result of modification of all positions //--- check the correctness of the data by symbol if(this.m_point==0) { //--- let's try to get the data again ::ResetLastError(); this.SetSymbol(this.m_symbol); if(this.m_point==0) { ::PrintFormat("%s: Correct data was not received for the %s symbol. Error %d",__FUNCTION__, this.m_symbol, ::GetLastError()); return false; } } //--- in a loop by the total number of open positions int total =::PositionsTotal(); for(int i = total - 1; i >= 0; i--) { //--- get the ticket of the next position ulong pos_ticket =::PositionGetTicket(i); if(pos_ticket == 0) continue; //--- get the symbol and position magic string pos_symbol = ::PositionGetString(POSITION_SYMBOL); long pos_magic = ::PositionGetInteger(POSITION_MAGIC); //--- if the position does not match the filter by symbol and magic number, leave if((this.m_magic != -1 && pos_magic != this.m_magic) || (pos_symbol != this.m_symbol)) continue; //--- if failed to get the prices, move on if(!::SymbolInfoTick(this.m_symbol, tick)) continue; //--- get the position type, its opening price and StopLoss level ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)::PositionGetInteger(POSITION_TYPE); double pos_open =::PositionGetDouble(POSITION_PRICE_OPEN); double pos_sl =::PositionGetDouble(POSITION_SL); //--- get the calculated StopLoss level double value_sl = this.GetStopLossValue(pos_type, tick); //--- if StopLoss modification conditions are suitable, modify the position stop level and add the result to the res variable if(this.CheckCriterion(pos_type, pos_open, pos_sl, value_sl, tick)) res &=this.ModifySL(pos_ticket, value_sl); } //--- at the end of the loop, return the result of modifying each position that matches the "symbol/magic" filter return res; }
Wenn wir die Methode für jedes Symbol-Handelsobjekt aufrufen (siehe den ersten Artikel), entspricht die Anzahl der Iterationsschleifen für offene Positionen der Anzahl der im Handel verwendeten Symbole. Dies ist nicht optimal. Das Programm selbst wird eine Schleife haben, in der alle offenen Positionen durchlaufen werden, und diese Methoden werden von dieser Schleife aus aufgerufen. Aber die Methode hat auch eine eingebaute Schleife... Das bedeutet, dass wir eine weitere Methode Run() in der Trailing-Klasse erstellen müssen, die jedoch das Ticket der Position angibt, deren Stop verschoben werden muss, und diese von der Hauptschleife aus aufrufen.
In der einfachen Trailing-Klasse deklarieren wir eine neue Methode, um das Ticket an der erforderlichen Position zu erhalten:
//+------------------------------------------------------------------+ //| Class of the position StopLoss simple trailing | //+------------------------------------------------------------------+ class CSimpleTrailing : public CObject { private: //--- check the criteria for modifying the StopLoss position and return the flag bool CheckCriterion(ENUM_POSITION_TYPE pos_type, double pos_open, double pos_sl, double value_sl, MqlTick &tick); //--- modify StopLoss of a position by its ticket bool ModifySL(const ulong ticket, const double stop_loss); //--- return StopLevel in points int StopLevel(void); protected: string m_symbol; // trading symbol long m_magic; // EA ID double m_point; // Symbol Point int m_digits; // Symbol digits int m_offset; // stop distance from price int m_trail_start; // profit in points for launching trailing uint m_trail_step; // trailing step uint m_spread_mlt; // spread multiplier for returning StopLevel value bool m_active; // trailing activity flag //--- calculate and return the StopLoss level of the selected position virtual double GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick); public: //--- set trailing parameters void SetSymbol(const string symbol) { this.m_symbol = (symbol==NULL || symbol=="" ? ::Symbol() : symbol); this.m_point =::SymbolInfoDouble(this.m_symbol, SYMBOL_POINT); this.m_digits = (int)::SymbolInfoInteger(this.m_symbol, SYMBOL_DIGITS); } void SetMagicNumber(const long magic) { this.m_magic = magic; } void SetStopLossOffset(const int offset) { this.m_offset = offset; } void SetTrailingStart(const int start) { this.m_trail_start = start; } void SetTrailingStep(const uint step) { this.m_trail_step = step; } void SetSpreadMultiplier(const uint value) { this.m_spread_mlt = value; } void SetActive(const bool flag) { this.m_active = flag; } //--- return trailing parameters string Symbol(void) const { return this.m_symbol; } long MagicNumber(void) const { return this.m_magic; } int StopLossOffset(void) const { return this.m_offset; } int TrailingStart(void) const { return this.m_trail_start; } uint TrailingStep(void) const { return this.m_trail_step; } uint SpreadMultiplier(void) const { return this.m_spread_mlt; } bool IsActive(void) const { return this.m_active; } //--- launch trailing with StopLoss offset from the price bool Run(void); bool Run(const ulong pos_ticket); //--- constructors CSimpleTrailing() : m_symbol(::Symbol()), m_point(::Point()), m_digits(::Digits()), m_magic(-1), m_trail_start(0), m_trail_step(0), m_offset(0), m_spread_mlt(2) {} CSimpleTrailing(const string symbol, const long magic, const int trailing_start, const uint trailing_step, const int offset); //--- destructor ~CSimpleTrailing() {} };
Nun schreiben wir die Implementierung außerhalb des Hauptteils der Klasse:
//+------------------------------------------------------------------+ //| Launch simple trailing with StopLoss offset from the price | //+------------------------------------------------------------------+ bool CSimpleTrailing::Run(const ulong pos_ticket) { //--- if trailing is disabled, or the ticket is invalid, we leave if(!this.m_active || pos_ticket==0) return false; //--- trailing variables MqlTick tick = {}; // price structure //--- check the correctness of the data by symbol ::ResetLastError(); if(this.m_point==0) { //--- let's try to get the data again this.SetSymbol(this.m_symbol); if(this.m_point==0) { ::PrintFormat("%s: Correct data was not received for the %s symbol. Error %d",__FUNCTION__, this.m_symbol, ::GetLastError()); return false; } } //--- select a position by ticket if(!::PositionSelectByTicket(pos_ticket)) { ::PrintFormat("%s: PositionSelectByTicket(%I64u) failed. Error %d",__FUNCTION__, pos_ticket, ::GetLastError()); return false; } //--- if prices could not be obtained, return 'false' if(!::SymbolInfoTick(this.m_symbol, tick)) return false; //--- get the position type, its opening price and StopLoss level ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)::PositionGetInteger(POSITION_TYPE); double pos_open =::PositionGetDouble(POSITION_PRICE_OPEN); double pos_sl =::PositionGetDouble(POSITION_SL); //--- get the calculated StopLoss level double value_sl = this.GetStopLossValue(pos_type, tick); //--- if the conditions for modifying StopLoss are suitable, return the result of modifying the position stop if(this.CheckCriterion(pos_type, pos_open, pos_sl, value_sl, tick)) return(this.ModifySL(pos_ticket, value_sl)); //--- conditions for modification are not suitable return false; }
Diese Methode wird nun entweder von der Methode Run() aufgerufen, die keine formalen Parameter hat, sondern in der eine Schleife durch alle offenen Positionen organisiert wird, oder direkt vom Programm, dem das erforderliche Ticket übergeben wird.
Verfeinern wir die Methode Run() ohne formale Parameter. Wir entfernen den Codeblock aus der Schleife:
for(int i = total - 1; i >= 0; i--) { //--- get the ticket of the next position ulong pos_ticket =::PositionGetTicket(i); if(pos_ticket == 0) continue; //--- get the symbol and position magic string pos_symbol = ::PositionGetString(POSITION_SYMBOL); long pos_magic = ::PositionGetInteger(POSITION_MAGIC); //--- if the position does not match the filter by symbol and magic number, leave if((this.m_magic != -1 && pos_magic != this.m_magic) || (pos_symbol != this.m_symbol)) continue; //--- if failed to get the prices, move on if(!::SymbolInfoTick(this.m_symbol, tick)) continue; //--- get the position type, its opening price and StopLoss level ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)::PositionGetInteger(POSITION_TYPE); double pos_open =::PositionGetDouble(POSITION_PRICE_OPEN); double pos_sl =::PositionGetDouble(POSITION_SL); //--- get the calculated StopLoss level double value_sl = this.GetStopLossValue(pos_type, tick); //--- if StopLoss modification conditions are suitable, modify the position stop level and add the result to the res variable if(this.CheckCriterion(pos_type, pos_open, pos_sl, value_sl, tick)) res &=this.ModifySL(pos_ticket, value_sl); }
und fügen stattdessen den Aufruf der neuen Methode hinzu:
//+------------------------------------------------------------------+ //| Launch simple trailing with StopLoss offset from the price | //+------------------------------------------------------------------+ bool CSimpleTrailing::Run(void) { //--- if disabled, leave if(!this.m_active) return false; //--- trailing variables bool res = true; // result of modification of all positions //--- check the correctness of the data by symbol if(this.m_point==0) { //--- let's try to get the data again ::ResetLastError(); this.SetSymbol(this.m_symbol); if(this.m_point==0) { ::PrintFormat("%s: Correct data was not received for the %s symbol. Error %d",__FUNCTION__, this.m_symbol, ::GetLastError()); return false; } } //--- in a loop by the total number of open positions int total =::PositionsTotal(); for(int i = total - 1; i >= 0; i--) { //--- get the ticket of the next position ulong pos_ticket =::PositionGetTicket(i); if(pos_ticket == 0) continue; //--- get the symbol and position magic string pos_symbol = ::PositionGetString(POSITION_SYMBOL); long pos_magic = ::PositionGetInteger(POSITION_MAGIC); //--- if the position does not match the filter by symbol and magic number, leave if((this.m_magic != -1 && pos_magic != this.m_magic) || (pos_symbol != this.m_symbol)) continue; res &=this.Run(pos_ticket); } //--- at the end of the loop, return the result of modifying each position that matches the "symbol/magic" filter return res; }
In der Trailing-Klasse deklarieren wir die neue Methode Run(), die das Positionsticket angibt:
//+------------------------------------------------------------------+ //| Trailing class based on a specified value | //+------------------------------------------------------------------+ class CTrailingByValue : public CSimpleTrailing { protected: double m_value_sl_long; // StopLoss level for long positions double m_value_sl_short; // StopLoss level for short positions //--- calculate and return the StopLoss level of the selected position virtual double GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick); public: //--- return StopLoss level for (2) long and (2) short positions double StopLossValueLong(void) const { return this.m_value_sl_long; } double StopLossValueShort(void) const { return this.m_value_sl_short; } //--- launch trailing with the specified StopLoss offset from the price bool Run(const double value_sl_long, double value_sl_short); bool Run(const ulong pos_ticket, const double value_sl_long, double value_sl_short); //--- constructors CTrailingByValue(void) : CSimpleTrailing(::Symbol(), -1, 0, 0, 0), m_value_sl_long(0), m_value_sl_short(0) {} CTrailingByValue(const string symbol, const long magic, const int trail_start, const uint trail_step, const int trail_offset) : CSimpleTrailing(symbol, magic, trail_start, trail_step, trail_offset), m_value_sl_long(0), m_value_sl_short(0) {} //--- destructor ~CTrailingByValue(void){} };
Die Implementierung schreiben wir außerhalb des Hauptteils der Klasse:
//+------------------------------------------------------------------+ //| Launch trailing with the specified StopLoss offset from the price| //+------------------------------------------------------------------+ bool CTrailingByValue::Run(const ulong pos_ticket,const double value_sl_long,double value_sl_short) { this.m_value_sl_long =value_sl_long; this.m_value_sl_short=value_sl_short; return CSimpleTrailing::Run(pos_ticket); }
Hier rufen wir die Methode Run() auf, die der einfachen Trailing-Klasse zusammen mit dem angegebenen Positionsticket hinzugefügt wurde.
Einzelheiten zu den Trailing-Klassen finden Sie in dem Artikel, dem wir die Datei mit den Trailing-Stopps entnommen haben.
Die im vorherigen Artikel erstellte Handelsklasse „Symbol“ speichert die Listen der Handelsgeschäfte und die Handelsklasse CTrade der Standardbibliothek. Um zu vermeiden, dass sie so verändert wird, dass sie nachgestellte Klassen enthält, werden wir eine neue Klasse erstellen, die auf ihr basiert und der wir die Trailing-Klassen hinzufügen werden.
Im Terminalordner \MQL5\Experts\TradingByHistoryDeals\ erstellen wir die neue Datei SymbolTradeExt.mqh der Klasse CSymbolTradeExt. Die Datei sollte die Datei mit den Trailing-Klassen und die CSymbolTrade-Klassendatei enthalten. Die neu erstellte Klasse sollte von der letzteren abgeleitet werden:
//+------------------------------------------------------------------+ //| SymbolTradeExt.mqh | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #include "Trailings.mqh" #include "SymbolTrade.mqh" class CSymbolTradeExt : public CSymbolTrade { }
Bei Verwendung der Trailing-Stop-Klasse aus dem Artikel stehen uns Trailing-Stops für parabolische und alle Arten von gleitenden Standarddurchschnitten zur Verfügung. Die Klasse verfügt auch über eine Trailing-Funktion, die auf den angegebenen Werten basiert, aber wir werden sie hier nicht verwenden, da sie die Berechnung von Stopp-Levels in einem eigenen Programm erfordert und diese in den Parametern der aufgerufenen Methode Run() der Trailing-Klasse übergeben werden müssen. Dies könnte z.B. die Berechnung des ATR-Indikators und die Übergabe der berechneten Werte an die Trailing-Klasse sein.
Implementieren wir die Enumeration der Trailing-Modi:
//+------------------------------------------------------------------+ //| SymbolTradeExt.mqh | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #include "Trailings.mqh" #include "SymbolTrade.mqh" enum ENUM_TRAILING_MODE // Enumeration of trailing modes { TRAILING_MODE_SIMPLE=2, // Simple trailing TRAILING_MODE_SAR, // Trailing by Parabolic SAR TRAILING_MODE_AMA, // Trailing by adjustable moving average TRAILING_MODE_DEMA, // Trailing by double exponential moving average TRAILING_MODE_FRAMA, // Trailing by fractal adaptive moving average TRAILING_MODE_MA, // Trailing by simple moving average TRAILING_MODE_TEMA, // Trailing by triple exponential moving average TRAILING_MODE_VIDYA, // Trailing by moving average with dynamic averaging period }; class CSymbolTradeExt : public CSymbolTrade { }
Warum beginnen die Werte der Enumerationskonstanten mit 2 statt mit Null? Der EA, den wir für die Erstellung des neuen EAs verwenden werden, führt auch Testmodi auf:
enum ENUM_TESTING_MODE { TESTING_MODE_ORIGIN, /* Original trading */ TESTING_MODE_SLTP, /* Specified StopLoss and TakeProfit values */ };
Hier gibt es zwei Konstanten: den ursprünglichen Handel (0) und den Handel mit den angegebenen Stop-Order-Werten (1). Später werden wir hier neue Konstanten hinzufügen, die der Trailing-Enumeration entsprechen. Aus diesem Grund beginnen die Werte der Trailing-Enumeration mit dem Wert 2.
Im privaten Abschnitt der Klasse deklarieren wir einen Zeiger auf das Objekt der Klasse Trailing Stops und eine Variable zum Speichern der Chartperiode für die Berechnung der Indikatoren, die in Trailing Stops verwendet werden. Im öffentlichen Abschnitt deklarieren wir eine Methode zum Setzen von Trailing-Parametern, eine Methode zum Starten des Positions-Trailings, Konstruktoren und einen Destruktor:
class CSymbolTradeExt : public CSymbolTrade { private: CSimpleTrailing *m_trailing; // Trailing class object ENUM_TIMEFRAMES m_timeframe; // Timeframe for calculating the indicator for trailing public: //--- Set trailing and its parameters bool SetTrailing(const ENUM_TRAILING_MODE trailing_mode, const int data_index, const long magic, const int start, const int step, const int offset, const MqlParam ¶m[]); //--- Start a trail of the position specified by the ticket void Trailing(const ulong pos_ticket); //--- Constructor/destructor CSymbolTradeExt() : m_trailing(NULL), m_timeframe(::Period()) { this.SetSymbol(::Symbol()); } CSymbolTradeExt(const string symbol, const ENUM_TIMEFRAMES timeframe); ~CSymbolTradeExt(); };
Im Klassenkonstruktor, d. h. in der Initialisierungszeichenfolge, wird ein Symbol an den Konstruktor der übergeordneten Klasse übergeben. Der in den Formalparametern übergebene Wert wird als Zeitrahmen für die Berechnung des Indikators festgelegt, während der Zeiger auf das Objekt der Trailing-Klasse mit NULL initialisiert wird:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CSymbolTradeExt::CSymbolTradeExt(const string symbol, const ENUM_TIMEFRAMES timeframe) : CSymbolTrade(symbol) { this.m_trailing=NULL; this.m_timeframe=timeframe; }
Es wird im Destruktor der Klasse gelöscht, wenn das nachlaufende Objekt erstellt wurde:
//+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CSymbolTradeExt::~CSymbolTradeExt() { //--- delete the created trailing object if(this.m_trailing!=NULL) delete this.m_trailing; }
Da verschiedene Arten von Trailing-Stops, die auf der Grundlage verschiedener Indikatoren arbeiten, unterschiedliche Setup-Parameter haben, werden wir alle diese Parameter an die Methode zur Einstellung der Parameter für den Trailing Stop über die Struktur MqlParam übergeben. Es ist nur so, dass für jedes Trailing der Satz von Parametern individuell sein wird, und jedes Feld der Struktur wird den Wert der Indikator-Parameter, die nur für sie zu tragen.
//+------------------------------------------------------------------+ //| Set trailing parameters | //+------------------------------------------------------------------+ bool CSymbolTradeExt::SetTrailing(const ENUM_TRAILING_MODE trailing_mode, const int data_index, const long magic, const int start, const int step, const int offset, const MqlParam ¶m[]) { //--- Set trailing parameters (only necessary structure fields are used for each indicator type) int ma_period = (int)param[0].integer_value; int ma_shift = (int)param[1].integer_value; ENUM_APPLIED_PRICE ma_price = (ENUM_APPLIED_PRICE)param[2].integer_value; ENUM_MA_METHOD ma_method = (ENUM_MA_METHOD)param[3].integer_value; int fast_ema = (int)param[4].integer_value; int slow_ema = (int)param[5].integer_value; int period_cmo = (int)param[6].integer_value; double sar_step = param[0].double_value; double sar_max = param[1].double_value; //--- depending on the trailing type, we create a trailing object //--- if the value passed as the calculation period is less than the allowed value, then each indicator is assigned its own default value switch(trailing_mode) { case TRAILING_MODE_SIMPLE : this.m_trailing=new CSimpleTrailing(this.Symbol(), magic, start, step, offset); break; case TRAILING_MODE_AMA : this.m_trailing=new CTrailingByAMA(this.Symbol(), this.m_timeframe, magic, (ma_period<1 ? 9 : ma_period), (fast_ema<1 ? 2 : fast_ema), (slow_ema<1 ? 30 : slow_ema), ma_shift, ma_price, start, step, offset); break; case TRAILING_MODE_DEMA : this.m_trailing=new CTrailingByDEMA(this.Symbol(), this.m_timeframe, magic, (ma_period==0 ? 14 : ma_period), ma_shift, ma_price, start, step, offset); break; case TRAILING_MODE_FRAMA : this.m_trailing=new CTrailingByFRAMA(this.Symbol(), this.m_timeframe, magic, (ma_period==0 ? 14 : ma_period), ma_shift, ma_price, start, step, offset); break; case TRAILING_MODE_MA : this.m_trailing=new CTrailingByMA(this.Symbol(), this.m_timeframe, magic, ma_period, (ma_period==0 ? 10 : ma_period), ma_method, ma_price, start, step, offset); break; case TRAILING_MODE_TEMA : this.m_trailing=new CTrailingByTEMA(this.Symbol(), this.m_timeframe, magic, (ma_period==0 ? 14 : ma_period), ma_shift, ma_price, start, step, offset); break; case TRAILING_MODE_VIDYA : this.m_trailing=new CTrailingByVIDYA(this.Symbol(), this.m_timeframe, magic, (period_cmo<1 ? 9 : period_cmo), (ma_period==0 ? 12 : ma_period), ma_shift, ma_price, start, step, offset); break; case TRAILING_MODE_SAR : this.m_trailing=new CTrailingBySAR(this.Symbol(), this.m_timeframe, magic, (sar_step<0.0001 ? 0.02 : sar_step), (sar_max<0.02 ? 0.2 : sar_max), start, step, offset); break; default : break; } //--- something went wrong - return 'false' if(this.m_trailing==NULL) return false; //--- all is well - make the trail active and return 'true' this.m_trailing.SetActive(true); return true; }
Beim Aufruf dieser Methode für verschiedene Trailing-Typen ist es wichtig, die MqlParam-Struktur korrekt zu füllen. Wir werden dies überprüfen, wenn wir den EA so ändern, dass er Trailing verwendet.
Die Methode, die das Trailing an der durch das Ticket angegebenen Position beginnt:
//+------------------------------------------------------------------+ //| Start trailing of the position specified by the ticket | //+------------------------------------------------------------------+ void CSymbolTradeExt::Trailing(const ulong pos_ticket) { if(this.m_trailing!=NULL) this.m_trailing.Run(pos_ticket); }
Aus der EA-Schleife für offene Positionen (abhängig vom Positionssymbol) holen wir das Symbol-Handelsobjekt aus der Liste. Von diesem Objekt aus rufen wir die Methode auf, um den Positionsstopp zu verfolgen, dessen Ticket wir an die Methode übergeben. Wenn das Trailing-Objekt existiert, wird seine neue Methode Run() mit dem angegebenen Positionsticket aufgerufen.
Testen verschiedener Arten von Trailing-Stop
Um den Test durchzuführen, verwenden wir den EA TradingByHistoryDeals_SLTP.mq5 aus dem vorherigen Artikel und speichern ihn im gleichen Ordner \MQL5\Experts\TradingByHistoryDeals\ als TradingByHistoryDeals_Ext.mq5.
Das Einbinden der Klassendatei CSymbolTrade wird durch das Einbinden von CSymbolTradeExt ersetzt, das Einbinden der Datei mit den Trailing-Klassen und das Erweitern der Liste der Konstanten der Testmodi-Enumeration durch das Hinzufügen der Auswahl der notwendigen nachgestellten Klassen:
//+------------------------------------------------------------------+ //| TradingByHistoryDeals_Ext.mq5 | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #include "SymbolTradeExt.mqh" #include "Trailings.mqh" enum ENUM_TESTING_MODE { TESTING_MODE_ORIGIN, /* Original trading */ TESTING_MODE_SLTP, /* Specified StopLoss and TakeProfit values */ TESTING_MODE_TRAIL_SIMPLE, /* Simple Trailing */ TESTING_MODE_TRAIL_SAR, /* Trailing by Parabolic SAR indicator */ TESTING_MODE_TRAIL_AMA, /* Trailing by AMA indicator */ TESTING_MODE_TRAIL_DEMA, /* Trailing by DEMA indicator */ TESTING_MODE_TRAIL_FRAMA, /* Trailing by FRAMA indicator */ TESTING_MODE_TRAIL_MA, /* Trailing by MA indicator */ TESTING_MODE_TRAIL_TEMA, /* Trailing by TEMA indicator */ TESTING_MODE_TRAIL_VIDYA, /* Trailing by VIDYA indicator */ }; //+------------------------------------------------------------------+ //| Expert | //+------------------------------------------------------------------+
In den EA-Eingaben fügen wir die Variablen für die Auswahl der Nachlaufparameter hinzu:
//+------------------------------------------------------------------+ //| Expert | //+------------------------------------------------------------------+ //--- input parameters input group " - Strategy parameters - " input string InpTestedSymbol = ""; /* The symbol being tested in the tester */ input long InpTestedMagic = -1; /* The magic number being tested in the tester */ sinput bool InpShowDataInLog = false; /* Show collected data in the log */ input group " - Stops parameters - " input ENUM_TESTING_MODE InpTestingMode = TESTING_MODE_ORIGIN; /* Testing Mode */ input int InpStopLoss = 300; /* StopLoss in points */ input int InpTakeProfit = 500; /* TakeProfit in points */ input group " - Trailing Parameters -" input bool InpSetStopLoss = true; /* Set Initial StopLoss */ input bool InpSetTakeProfit = true; /* Set Initial TakeProfit */ input int InpTrailingStart = 150; /* Trailing start */ // Profit in points to start trailing input int InpTrailingStep = 50; /* Trailing step in points */ input int InpTrailingOffset = 0; /* Trailing offset in points */ input group " - Indicator Parameters -" input ENUM_TIMEFRAMES InpIndTimeframe = PERIOD_CURRENT; /* Indicator's timeframe */ // Timeframe of the indicator used in trailing calculation input int InpMAPeriod = 0; /* MA Period */ input int InpMAShift = 0; /* MA Shift */ input int InpFastEMAPeriod = 2; /* AMA Fast EMA Period */ input int InpSlowEMAPeriod = 30; /* AMA Slow EMA Period */ input int InpCMOPeriod = 9; /* VIDYA CMO Period */ input double InpSARStep = 0.02; /* Parabolic SAR Step */ input double InpSARMax = 0.2; /* Parabolic SAR Max */ input ENUM_APPLIED_PRICE InpAppliedPrice = PRICE_CLOSE; /* MA Applied Price */ input ENUM_MA_METHOD InpMAMethod = MODE_SMA; /* MA Smoothing Method */ input int InpDataIndex = 1; /* Indicator data index */ // Bar of data received frrom the indicator
InpMAPeriod ist hier standardmäßig auf Null gesetzt. Der Grund dafür ist, dass jeder Typ von gleitendem Durchschnitt seinen eigenen Standardperiodenwert hat. Wenn dieser Parameter auf Null gesetzt ist, wird der für den Indikator festgelegte Standardwert an die Trailing-Klasse übergeben. Alle Daten sind korrekt, wenn für den im Nachlauf verwendeten Indikator einfache Standardwerte erforderlich sind.
Im gesamten Code ersetzen wir alle Stellen, an denen CSymbolTrade vorkommt, durch CSymbolTradeExt. Jetzt haben wir eine neue Symbolhandelsobjektklasse CSymbolTradeExt, die von der vorherigen CSymbolTrade-Klasse abgeleitet wurde, die im vorherigen Artikel implementiert wurde. Dies ist die Klasse, in der das Objekt der Trailing-Klasse deklariert wird. Deshalb haben wir die bisherige Klassenart durch eine neue ersetzt. Tatsächlich ist dies nicht überall erforderlich, sondern nur dort, wo ein Nachsatz erforderlich ist. Wir wollen uns hier nicht in die Details der Klassenvererbung vertiefen, sondern einfach die alte Klasse durch die neue ersetzen.
Für jede Position werden entsprechend ihres Tickets das Trailing aufgerufen. Um auf das Positions-Trailing zugreifen zu können, müssen wir das Handelsobjekt des Symbols, für das die Position offen ist, aus der Liste holen. Zu diesem Zweck implementieren wir die Funktion, die den Zeiger auf das Handelsobjekt aus der Liste nach Symbolname zurückgibt:
//+------------------------------------------------------------------+ //| Return the pointer to the symbol trading object by name | //+------------------------------------------------------------------+ CSymbolTrade *GetSymbolTrade(const string symbol, CArrayObj *list) { SymbTradeTmp.SetSymbol(symbol); list.Sort(); int index=list.Search(&SymbTradeTmp); return list.At(index); }
Die Funktion erhält den Namen des Symbols, für das ein Handelsobjekt zurückgegeben werden muss, und den Zeiger auf die Liste, in der die Zeiger auf die Handelsobjekte der Symbole gespeichert sind. Der an die Funktion übergebene Symbolname wird auf das temporäre Handelsobjekt gesetzt und der Index des Objekts mit diesem Symbolnamen wird in der Liste gesucht. Als Ergebnis wird der Zeiger auf das gewünschte Objekt aus der Liste nach Index zurückgegeben. Befindet sich ein solches Objekt nicht in der Liste, ist der Index gleich -1 und NULL wird aus der Liste zurückgegeben.
Bei der Erstellung eines Arrays der verwendeten Symbole werden Symbolhandelsobjekte erstellt, in denen die in den Eingaben angegebenen Trailing-Stops initialisiert werden sollten. Verfeinern wir die Funktion, die ein Array der verwendeten Symbole erstellt:
//+------------------------------------------------------------------+ //| Creates an array of used symbols | //+------------------------------------------------------------------+ bool CreateListSymbolTrades(SDeal &array_deals[], CArrayObj *list_symbols) { bool res=true; // result MqlParam param[7]={}; // trailing parameters int total=(int)array_deals.Size(); // total number of deals in the array //--- if the deal array is empty, return 'false' if(total==0) { PrintFormat("%s: Error! Empty deals array passed",__FUNCTION__); return false; } //--- in a loop through the deal array CSymbolTradeExt *SymbolTrade=NULL; for(int i=0; i<total; i++) { //--- get the next deal and, if it is neither buy nor sell, move on to the next one SDeal deal_str=array_deals[i]; if(deal_str.type!=DEAL_TYPE_BUY && deal_str.type!=DEAL_TYPE_SELL) continue; //--- find a trading object in the list whose symbol is equal to the deal symbol string symbol=deal_str.Symbol(); SymbTradeTmp.SetSymbol(symbol); list_symbols.Sort(); int index=list_symbols.Search(&SymbTradeTmp); //--- if the index of the desired object in the list is -1, there is no such object in the list if(index==WRONG_VALUE) { //--- we create a new trading symbol object and, if creation fails, //--- add 'false' to the result and move on to the next deal SymbolTrade=new CSymbolTradeExt(symbol, InpIndTimeframe); if(SymbolTrade==NULL) { res &=false; continue; } //--- if failed to add a symbol trading object to the list, //--- delete the newly created object, add 'false' to the result //--- and we move on to the next deal if(!list_symbols.Add(SymbolTrade)) { delete SymbolTrade; res &=false; continue; } //--- initialize trailing specified in the settings in the trading object ENUM_TRAILING_MODE mode=(ENUM_TRAILING_MODE)InpTestingMode; SetTrailingParams(mode, param); SymbolTrade.SetTrailing(mode, InpDataIndex, InpTestedMagic, InpTrailingStart, InpTrailingStep, InpTrailingOffset, param); } //--- otherwise, if the trading object already exists in the list, we get it by index else { SymbolTrade=list_symbols.At(index); if(SymbolTrade==NULL) continue; } //--- if the current deal is not yet in the list of deals of the symbol trading object if(SymbolTrade.GetDealByTime(deal_str.time)==NULL) { //--- create a deal object according to its sample structure CDeal *deal=CreateDeal(deal_str); if(deal==NULL) { res &=false; continue; } //--- add the result of adding the deal object to the list of deals of a symbol trading object to the result value res &=SymbolTrade.AddDeal(deal); } } //--- return the final result of creating trading objects and adding deals to their lists return res; }
Hier haben wir die Deklaration der Input-Parameterstruktur des Indikators und einen kleinen Codeblock hinzugefügt, in dem das Trailing im Handelsobjekt initialisiert wird.
Implementieren wir die spezielle Funktion zur Einstellung der Parameter des angegebenen Trailing:
//+------------------------------------------------------------------+ //| Set the trailing parameters according to its selected type | //+------------------------------------------------------------------+ void SetTrailingParams(const ENUM_TRAILING_MODE mode, MqlParam ¶m[]) { //--- reset all parameters ZeroMemory(param); //--- depending on the selected trailing type, we set the indicator parameters switch(mode) { case TRAILING_MODE_SAR : param[0].type=TYPE_DOUBLE; param[0].double_value=InpSARStep; param[1].type=TYPE_DOUBLE; param[1].double_value=InpSARMax; break; case TRAILING_MODE_AMA : param[0].type=TYPE_INT; param[0].integer_value=InpMAPeriod; param[1].type=TYPE_INT; param[1].integer_value=InpMAShift; param[2].type=TYPE_INT; param[2].integer_value=InpAppliedPrice; param[4].type=TYPE_INT; param[4].integer_value=InpFastEMAPeriod; param[5].type=TYPE_INT; param[5].integer_value=InpSlowEMAPeriod; break; case TRAILING_MODE_DEMA : param[0].type=TYPE_INT; param[0].integer_value=InpMAPeriod; param[1].type=TYPE_INT; param[1].integer_value=InpMAShift; param[2].type=TYPE_INT; param[2].integer_value=InpAppliedPrice; break; case TRAILING_MODE_FRAMA : param[0].type=TYPE_INT; param[0].integer_value=InpMAPeriod; param[1].type=TYPE_INT; param[1].integer_value=InpMAShift; param[2].type=TYPE_INT; param[2].integer_value=InpAppliedPrice; break; case TRAILING_MODE_MA : param[0].type=TYPE_INT; param[0].integer_value=InpMAPeriod; param[1].type=TYPE_INT; param[1].integer_value=InpMAShift; param[2].type=TYPE_INT; param[2].integer_value=InpAppliedPrice; param[3].type=TYPE_INT; param[3].integer_value=InpMAMethod; break; case TRAILING_MODE_TEMA : param[0].type=TYPE_INT; param[0].integer_value=InpMAPeriod; param[1].type=TYPE_INT; param[1].integer_value=InpMAShift; param[2].type=TYPE_INT; param[2].integer_value=InpAppliedPrice; break; case TRAILING_MODE_VIDYA : param[0].type=TYPE_INT; param[0].integer_value=InpMAPeriod; param[1].type=TYPE_INT; param[1].integer_value=InpMAShift; param[2].type=TYPE_INT; param[2].integer_value=InpAppliedPrice; param[6].type=TYPE_INT; param[6].integer_value=InpCMOPeriod; break; case TRAILING_MODE_SIMPLE : break; default: break; } }
Hier werden für jeden der Trailing-Typen für die im Trailing verwendeten Indikatoren die in den EA-Eingaben angegebenen Werte in der Struktur gesetzt. Die den Strukturfeldern zugewiesenen Werte werden in der neuen Klasse CSymbolTradeExt des oben beschriebenen Symbolhandelsobjekts auf ihre Gültigkeit geprüft und angepasst.
Verbessern wir die Funktion für den Handel auf der Grundlage der Historie der Deals. Jetzt müssen wir klar unterscheiden, welche Art von Tests verwendet wird. Um den ursprünglichen Handel zu testen, ist es nicht erforderlich, Stop-Orders zu setzen – alle Positionen werden auf der Grundlage der abgeschlossenen historischen Handelsgeschäfte geschlossen.
Wenn Sie den Handel mit verschiedenen Stop-Order-Größen testen, müssen Sie die richtigen Größen ermitteln und sie für die zu eröffnende Position festlegen. Beim Testen von Trailing Stops ist es nicht notwendig, Stops zu setzen. Um jedoch große Drawdowns und ein „Überziehen“ zu vermeiden, wird empfohlen, anfängliche Stops zu setzen, die dann von Trailing Stops nach der festgelegten Logik verschoben werden. Die EA-Einstellungen enthalten Parameter, die bestimmen, ob anfängliche Stop-Orders platziert werden sollen:
input bool InpSetStopLoss = true; /* Set Initial StopLoss */ input bool InpSetTakeProfit = true; /* Set Initial TakeProfit */
Wenn Sie sie verwenden, müssen Sie auch die richtigen Stop-Größen wählen und sie für die zu eröffnende Position einstellen:
//+------------------------------------------------------------------+ //| Trading by history | //+------------------------------------------------------------------+ void TradeByHistory(const string symbol="", const long magic=-1) { datetime time=0; int total=ExtListSymbols.Total(); // number of trading objects in the list //--- in a loop by all symbol trading objects for(int i=0; i<total; i++) { //--- get another trading object CSymbolTradeExt *obj=ExtListSymbols.At(i); if(obj==NULL) continue; //--- get the current deal pointed to by the deal list index CDeal *deal=obj.GetDealCurrent(); if(deal==NULL) continue; //--- sort the deal by magic number and symbol if((magic>-1 && deal.Magic()!=magic) || (symbol!="" && deal.Symbol()!=symbol)) continue; //--- sort the deal by type (only buy/sell deals) ENUM_DEAL_TYPE type=deal.TypeDeal(); if(type!=DEAL_TYPE_BUY && type!=DEAL_TYPE_SELL) continue; //--- if this is a deal already handled in the tester, move on to the next one if(deal.TicketTester()>0) continue; //--- if the deal time has not yet arrived, move to the next trading object of the next symbol if(!obj.CheckTime(deal.Time())) continue; //--- in case of a market entry deal ENUM_DEAL_ENTRY entry=deal.Entry(); if(entry==DEAL_ENTRY_IN) { //--- set the sizes of stop orders depending on the stop setting method //--- stop orders are not used initially (for original trading) ENUM_ORDER_TYPE order_type=(deal.TypeDeal()==DEAL_TYPE_BUY ? ORDER_TYPE_BUY : ORDER_TYPE_SELL); double sl=0; double tp=0; //--- in case of the mode for setting the specified stop order values if(InpTestingMode==TESTING_MODE_SLTP) { //--- get correct values for StopLoss and TakeProfit sl=CorrectStopLoss(deal.Symbol(), order_type, ExtStopLoss); tp=CorrectTakeProfit(deal.Symbol(), order_type, ExtTakeProfit); } //--- otherwise, if testing with trailing stops else { if(InpTestingMode!=TESTING_MODE_ORIGIN) { //--- if allowed in the settings, we set correct stop orders for the positions being opened if(InpSetStopLoss) sl=CorrectStopLoss(deal.Symbol(), order_type, ExtStopLoss); if(InpSetTakeProfit) tp=CorrectTakeProfit(deal.Symbol(), order_type, ExtTakeProfit); } } //--- open a position by deal type ulong ticket=(type==DEAL_TYPE_BUY ? obj.Buy(deal.Volume(), deal.Magic(), sl, tp, deal.Comment()) : type==DEAL_TYPE_SELL ? obj.Sell(deal.Volume(),deal.Magic(), sl, tp, deal.Comment()) : 0); //--- if a position is opened (we received its ticket) if(ticket>0) { //--- increase the number of deals handled by the tester and write the deal ticket in the tester to the properties of the deal object obj.SetNumProcessedDeals(obj.NumProcessedDeals()+1); deal.SetTicketTester(ticket); //--- get the position ID in the tester and write it to the properties of the deal object long pos_id_tester=0; if(HistoryDealSelect(ticket)) { pos_id_tester=HistoryDealGetInteger(ticket, DEAL_POSITION_ID); deal.SetPosIDTester(pos_id_tester); } } } //--- in case of a market exit deal if(entry==DEAL_ENTRY_OUT || entry==DEAL_ENTRY_INOUT || entry==DEAL_ENTRY_OUT_BY) { //--- get a deal a newly opened position is based on CDeal *deal_in=obj.GetDealInByPosID(deal.PositionID()); if(deal_in==NULL) continue; //--- get the position ticket in the tester from the properties of the opening deal //--- if the ticket is zero, then most likely the position in the tester is already closed ulong ticket_tester=deal_in.TicketTester(); if(ticket_tester==0) { PrintFormat("Could not get position ticket, apparently position #%I64d (#%I64d) is already closed \n", deal.PositionID(), deal_in.PosIDTester()); obj.SetNextDealIndex(); continue; } //--- if we reproduce the original trading history in the tester, if(InpTestingMode==TESTING_MODE_ORIGIN) { //--- if the position is closed by ticket if(obj.ClosePos(ticket_tester)) { //--- increase the number of deals handled by the tester and write the deal ticket in the tester to the properties of the deal object obj.SetNumProcessedDeals(obj.NumProcessedDeals()+1); deal.SetTicketTester(ticket_tester); } } //--- otherwise, in the tester we work with stop orders placed according to different algorithms, and closing deals are skipped; //--- accordingly, for the closing deal, we simply increase the number of deals handled by the tester and //--- write the deal ticket in the tester to the properties of the deal object else { obj.SetNumProcessedDeals(obj.NumProcessedDeals()+1); deal.SetTicketTester(ticket_tester); } } //--- if a ticket is now set in the deal object, then the deal has been successfully handled - //--- set the deal index in the list to the next deal if(deal.TicketTester()>0) { obj.SetNextDealIndex(); } } }
Nun wollen wir die Funktion implementieren, die das Trailing der Positionen durchführt:
//+------------------------------------------------------------------+ //| Trail positions | //+------------------------------------------------------------------+ void Trailing(void) { //--- variables for getting position properties long magic=-1; string symbol=""; //--- in a loop through all positions int total=PositionsTotal(); for(int i=total-1; i>=0; i--) { //--- get the ticket of the next position ulong ticket=PositionGetTicket(i); if(ticket==0) continue; //--- get the magic number and position symbol ResetLastError(); if(!PositionGetInteger(POSITION_MAGIC, magic)) { Print("PositionGetInteger() failed. Error ", GetLastError()); continue; } if(!PositionGetString(POSITION_SYMBOL, symbol)) { Print("PositionGetString() failed. Error ", GetLastError()); continue; } //--- if the position does not meet the specified conditions of the magic number and symbol, we move to the next one if((InpTestedMagic>-1 && magic!=InpTestedMagic) || (InpTestedSymbol!="" && symbol!=InpTestedSymbol)) continue; //--- get a trading object by a symbol name and call its method for trailing a position by ticket CSymbolTradeExt *obj=GetSymbolTrade(symbol, &ExtListSymbols); if(obj!=NULL) obj.Trailing(ticket); } }
Hier ist alles ganz einfach: Wir wählen jede Position in der Schleife aus, prüfen, ob ihre magische Zahl und ihr Symbol mit denen übereinstimmen, die in den EA-Einstellungen eingegeben wurden, und wenn es sich um die gewünschte Position handelt, holen wir das Handelsobjekt des Symbols dieser Position und rufen das Trailing davon auf, das das Positionsticket angibt.
Implementieren wir den Funktionsaufruf von OnTick() im EA:
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- work only in the strategy tester if(!MQLInfoInteger(MQL_TESTER)) return; //--- Trail open positions Trailing(); //--- Handle the list of deals from the file TradeByHistory(InpTestedSymbol, InpTestedMagic); }
Alles ist bereit. Der EA ist bereit. Den vollständigen EA-Code finden Sie in den Dateien im Anhang zu diesem Artikel.
Testen wir verschiedene Arten von Trailing-Stops und vergleichen wir die Ergebnisse des ursprünglichen Handels und des Handels mit Trailing-Stops unter Verwendung verschiedener Algorithmen.
Wenn der EA auf einem Symbolchart gestartet wird, sammelt er den Handelsverlauf, schreibt ihn in die Datei und zeigt eine Warnung an, die die gewünschten Testereinstellungen angibt – das anfängliche Testdatum, die anfängliche Einzahlung und den Hebel:

Das Journal zeigt die im Handel verwendeten Symbole und die Anzahl der für jedes Symbol getätigten Handelsgeschäfte an:
Alert: Now you can run testing Interval: 2024.09.13 - current date Initial deposit: 3000.00, leverage 1:500 Symbols used in trading: 1. AUDUSD trade object. Total deals: 222 2. EURJPY trade object. Total deals: 120 3. EURUSD trade object. Total deals: 526 4. GBPUSD trade object. Total deals: 352 5. NZDUSD trade object. Total deals: 182 6. USDCAD trade object. Total deals: 22 7. USDCHF trade object. Total deals: 250 8. USDJPY trade object. Total deals: 150 9. XAUUSD trade object. Total deals: 118
Starten wir nun den EA im Tester mit den empfohlenen Tester-Parametern, die im Alert angegeben und im Journal dupliziert wurden.
Ursprünglicher Handel:

Wir sehen, dass der ursprüngliche Handel zu einem Verlust von 658 USD führte.
Probieren wir verschiedene Trailing-Stops aus. Lassen Sie den anfänglichen Stop auf 100 Punkte setzen und führen Sie jede Trailing-Order im Tester aus.
Dies geschieht abwechselnd ohne Änderung anderer Parameter. Wir werden sehen, wie sich der Handel verändert.
Einfaches Trailing:

Wir haben einen Verlust von 746,1 USD. Noch mehr als im ursprünglichen Handel.
Überprüfen wir das Trailing mit Hilfe von Indikatoren.
Trailing durch Parabolic SAR:

Gewinn – 541,8 USD.
Betrachten wir nun die Trailing-Stops, die auf verschiedenen Arten von gleitenden Durchschnitten basieren.
Trailing durch АМА:

Gewinn – 806,5 USD.
Trailing durch DEMA:

Gewinn – 1397,1 USD.
Trailing durch FRAMA:

Gewinn – 1291,6 USD.
Trailing durch MA:

Gewinn – 563,1 USD.
Trailing durch TEMA:

Gewinn – 1355,1 USD.
Trailing durch VIDYA:

Gewinn – 283,3 USD.
Die abschließende Tabelle zeigt also die Ergebnisse bei der Verwendung verschiedener Arten von StopLoss-Trailing-Stops im Vergleich zum ursprünglichen Handel:
| # | Trailing-Typ | StopLoss Größe | TakeProfit-Größe | Gesamtgewinn |
|---|---|---|---|---|
| 1 | Ursprünglicher Handel | 100 | 500 | - 658.0 |
| 2 | Einfacher TrailingStop | Zunächst 100, dann Stopp bei 100 Punkten vom Preis | 0 | - 746.1 |
| 3 | TrailingStop durch Parabolic SAR | Zunächst 100, dann Stopp auf der Grundlage des Indikatorwerts | 0 | + 541.8 |
| 4 | TrailingStop durch VIDYA | Zunächst 100, dann Stopp auf der Grundlage des Indikatorwerts | 0 | -283.3 |
| 5 | TrailingStop durch MA | Zunächst 100, dann Stopp auf der Grundlage des Indikatorwerts | 0 | + 563.1 |
| 6 | TrailingStop durch АМА | Zunächst 100, dann Stopp auf der Grundlage des Indikatorwerts | 0 | + 806.5 |
| 7 | TrailingStop durch FRAMA | Zunächst 100, dann Stopp auf der Grundlage des Indikatorwerts | 0 | + 1291.6 |
| 8 | TrailingStop durch TEMA | Zunächst 100, dann Stopp auf der Grundlage des Indikatorwerts | 0 | + 1355.1 |
| 9 | TrailingStop durch DEMA | Zunächst 100, dann Stopp auf der Grundlage des Indikatorwerts | 0 | + 1397.1 |
Wenn also der ursprüngliche Handel unrentabel ist, führt ein regelmäßiges einfaches Trailing, das in etwa dem Trailing im Kundenterminal entspricht, zu einem noch größeren Verlust. Schauen wir uns einen Trailing-Indikator an, der auf dem Parabolic SAR basiert und als Indikator für Umkehrungen und Stop-Levels (Stop And Reverse) dient. Indem wir dem StopLoss-Level hinter der Indikatorlinie des ersten Balkens folgten, erzielten wir einen Gewinn von 540 USD.
Schauen wir uns nun die Ergebnisse der gleitenden Stopp-Positionen an, die auf den Werten der verschiedenen Arten von gleitenden Durchschnitten auf dem ersten Balken basieren. Das Trailing des VIDYA-Indikators führte zu einem Verlust von 280 USD, während alle anderen Indikatoren Gewinne erzielten. Darüber hinaus brachte das Trailing des Stopps entlang des einfachen gleitenden Durchschnitts einen größeren Gewinn als das Nachziehen entlang des parabolischen SAR. Der absolute „Champion“ bei der Verbesserung der Handelsergebnisse war der doppelte, exponentiell gleitende Durchschnitt – + 1397 USD Gewinn.
Schlussfolgerung
Wir haben einen EA entwickelt, mit dem wir unseren Handel im Strategietester testen und den für unseren Handelsstil am besten geeigneten Trailing-Stop auswählen können. Bitte beachten Sie, dass alle in den Trailing-Tests verwendeten Indikatorparameter Standardwerte hatten und die Trailing-Einstellungen zufällig gewählt wurden.
Die korrekteste Lösung wäre die Durchführung von Tests für jedes einzelne Symbol und die Auswahl akzeptabler Trailing-Einstellungen unter Berücksichtigung der Merkmale der Bewegung und der Volatilität des Währungspaares. Aber dieser EA hat keine separaten Einstellungen für die Einstellung nutzerdefinierter Parameter für jedes einzelne gehandelte Symbol. Aber das Wichtigste war, dass die Möglichkeit des Testens, Analysierens und Verbesserns des Handels mit dem Strategietester demonstriert wurde. Das bedeutet, dass der EA so weiterentwickelt werden kann, dass wir unsere eigenen Parameter für jedes Symbol festlegen können – das ist nicht schwer.
Alle überprüften Klassendateien und der EA sind dem Artikel beigefügt. Außerdem gibt es ein Archiv. Nach dem Entpacken können Sie die installierten Dateien sofort zum Testen in den gewünschten Terminalordnern abrufen.
Die Programme dieses Artikels:
| # | Name | Typ | Beschreibung |
|---|---|---|---|
| 1 | Trailings.mqh | Klassenbibliothek | Bibliothek der Trailing-Klasse |
| 2 | SymbolTrade.mqh | Klassenbibliothek | Struktur der Deals und Klassenbibliothek, Symbolhandelsklasse |
| 3 | SymbolTradeExt.mqh | Klassenbibliothek | Symbol und Handelsklasse für das Trailing |
| 4 | TradingByHistoryDeals_Ext.mq5 | Expert Advisor | Der EA zum Anzeigen und Ändern von StopLoss, TakeProfit und Trailing Stops im Strategietester für Geschäfte und Transaktionen, die auf dem Konto ausgeführt werden. |
| 5 | MQL5.zip | ZIP-Archiv | Das Archiv mit den oben genannten Dateien kann in das MQL5-Verzeichnis des Client-Terminals entpackt werden |
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/16991
Warnung: Alle Rechte sind von MetaQuotes Ltd. vorbehalten. Kopieren oder Vervielfältigen untersagt.
Dieser Artikel wurde von einem Nutzer der Website verfasst und gibt dessen persönliche Meinung wieder. MetaQuotes Ltd übernimmt keine Verantwortung für die Richtigkeit der dargestellten Informationen oder für Folgen, die sich aus der Anwendung der beschriebenen Lösungen, Strategien oder Empfehlungen ergeben.
Analyse des Binärcodes der Börsenkurse (Teil II): Umwandlung in BIP39 und Schreiben des GPT-Modells
Algorithmus für zyklische Parthenogenese (CPA)
Time Evolution Travel Algorithm (TETA)
Evolutionärer Handelsalgorithmus mit Verstärkungslernen und Auslöschung von schwachen Individuen (ETARE)
- 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.
Трал по VIDYA:
Gewinn - 283,3 Dollar.Fehler: Verlust - 283,3 Dollar.
Fehler: Verlust - 283,3 $.
In dem Artikel wird der negative Wert des Gewinns geschrieben.
Das Leerzeichen nach dem Minus wurde jedoch versehentlich hinzugefügt.