
Mean Reversion, eine einfache Handelsstrategie
Einführung
Mean Reversion (Rückkehr zum Mittelwert) ist eine Form des entgegengesetzten Handels, bei der der Händler erwartet, dass der Kurs zu einer Art Gleichgewicht zurückkehrt, das im Allgemeinen durch einen Mittelwert oder eine andere Statistik der zentralen Tendenz gemessen wird. In diesem Artikel wird eine wirklich einfache Mean-Reversion-Handelsstrategie erörtert.
Kurze Einführung in die Mean Reversion
Die Märkte bewegen sich im Allgemeinen in unregelmäßigen Zyklen. Das bedeutet, dass wir bei der Betrachtung der Charts in der Regel Auf- und Abschwünge sowie relativ flache Phasen sehen. Der Schlüssel zum Handeln und Investieren ist die Fähigkeit, die Veränderungen in diesen Phasen, die auch als Marktregime bezeichnet werden, zu erkennen.
Die mittlere Umkehrung kann in Form eines gleitenden Durchschnitts erfolgen, bei dem der Markt, wenn er sich zu weit von ihm entfernt, wahrscheinlich in seinen Bereich zurückkehrt.
Was ist Mean Reversion?
Mean Reversion ist ein Begriff aus der Finanzwelt, der die Annahme beschreibt, dass sich der Preis eines Vermögenswertes im Laufe der Zeit dem Durchschnittspreis annähert.
Die Verwendung der Mean Reversion als Timing-Strategie beinhaltet sowohl die Identifizierung der Handelsspanne für ein Wertpapier als auch die Berechnung des Durchschnittspreises mit Hilfe quantitativer Methoden. Mean Reversion ist ein Phänomen, das sich in einer Vielzahl von finanziellen Zeitreihendaten zeigen kann, von Preisdaten über Gewinndaten bis hin zum Buchwert.
Wenn der aktuelle Marktpreis unter dem Durchschnittspreis der Vergangenheit liegt, wird das Wertpapier als attraktiv für den Kauf angesehen, in der Erwartung, dass der Preis steigen wird. Wenn der aktuelle Marktpreis über dem durchschnittlichen Preis der Vergangenheit liegt, wird erwartet, dass der Marktpreis fallen wird. Mit anderen Worten: Es wird erwartet, dass sich Abweichungen vom Durchschnittspreis wieder dem Durchschnitt annähern. Diese Vorstellung ist der Grundstein für zahlreiche Handelsstrategien.
Aktienberichtsdienste bieten in der Regel gleitende Durchschnitte für Zeiträume wie 50 und 100 Tage an. Auch wenn die Berichte Durchschnittswerte enthalten, müssen die Höchst- und Tiefstkurse für den untersuchten Zeitraum ermittelt werden.
Die Mean Reversion scheint eine wissenschaftlichere Methode zur Auswahl von Kauf- und Verkaufspunkten für Aktien zu sein als die Charttechnik, da genaue Zahlenwerte aus historischen Daten abgeleitet werden, um die Kauf-/Verkaufswerte zu ermitteln, anstatt zu versuchen, Kursbewegungen anhand von Charts zu interpretieren (Charttechnik, auch bekannt als technische Analyse ), obwohl der RSI-Indikator und die Average True Range (ATR) erste Versuche sind, solche systematischen Muster zu erfassen.
Bei vielen Anlageklassen, auch bei den Wechselkursen, ist eine Rückkehr zum Mittelwert zu beobachten; dieser Prozess kann sich jedoch über Jahre hinziehen und ist daher für einen kurzfristigen Anleger nicht von Nutzen.
Mean Reversion sollte eine Form von Symmetrie aufweisen, da eine Aktie ungefähr genauso oft über wie unter ihrem historischen Durchschnitt liegen kann.
Ein historisches Mean-Reversion-Modell wird das tatsächliche Kursverhalten eines Wertpapiers nicht vollständig widerspiegeln. So können beispielsweise neue Informationen verfügbar werden, die die langfristige Bewertung einer zugrunde liegenden Aktie dauerhaft beeinflussen. Im Falle eines Konkurses kann es sein, dass es den Handel vollständig einstellt und sich nie wieder zu seinem früheren historischen Durchschnitt erholt.
Im Finanzwesen hat der Begriff „Mean Reversion“ eine etwas andere Bedeutung als „Rückkehr oder Regression zum Mittelwert“ in der Statistik. Jeremy Siegel verwendet den Begriff „Rückkehr zum Mittelwert“, um ein allgemeines Prinzip zu beschreiben, eine finanzielle Zeitreihe, bei der „die Renditen kurzfristig sehr instabil, langfristig aber sehr stabil sein können“. Quantitativ gesehen ist es die Standardabweichung der durchschnittlichen jährlichen Renditen, die schneller abnimmt als der Kehrwert der Haltedauer, was bedeutet, dass es sich nicht um einen Random Walk (Zufallsbewegung) handelt, sondern dass auf Perioden mit niedrigeren Renditen wieder Perioden mit höheren Renditen folgen, z. B. bei saisonalen Geschäften.
Die folgende Abbildung veranschaulicht das Beispiel.
Aber wie misst man „zu weit“? Wir werden eine sehr einfache Methode ausprobieren, die nur auf der Position des Preises im Verhältnis zum gleitenden Durchschnitt basiert.
Ausarbeitung der Strategie
Da wir nun einen auf 50 Perioden normierten Abstand zwischen dem Markt und seinem gleitenden 200-Perioden-Durchschnitt haben, sind wir bereit, die folgenden Handelssignale zu codieren:
- Ein Kaufsignal (long) wird generiert, wenn der normalisierte Index unter 100 fällt, nachdem er vorher 100 erreicht hatte und der aktuelle Schlusskurs niedriger ist als der Schlusskurs vor fünf Balken und unter dem gleitenden Durchschnitt aus 200 Balken liegt.
- Ein Verrkaufssignal (short) wird generiert, wenn der normalisierte Index über 100 steigt, nachdem er vorher 100 erreicht hatte und der aktuelle Schlusskurs höher ist als der Schlusskurs vor fünf Balken und über dem gleitenden Durchschnitt aus 200 Balken liegt.
Es handelt sich also vielleicht nicht um die einfachste Strategie unter den gegebenen Bedingungen, aber dennoch ist sie sehr intuitiv und unkompliziert. Die Signalfunktion ist wie folgt:
Nach dem letzten Abschnitt haben wir klare Ziele, um mit der Entwicklung der Strategie zu beginnen:
- Ein Kaufsignal wird generiert, wenn der Markt so weit unter seinen gleitenden Durchschnitt fällt, dass eine Rückkehr zum Mittelwert nach oben wahrscheinlich ist.
- Ein Verkaufssignal wird immer dann generiert, wenn der Markt so weit über seinen gleitenden Durchschnitt steigt, dass er wahrscheinlich nach unten zum Mittelwert zurückkehren wird.
Ich werde eine Änderung an der Strategie vornehmen, um zu versuchen, bessere Ergebnisse zu erzielen, wenn die Strategie nicht gut funktioniert (wenn der Aktienkurs sinkt). Ich werde diese Strategie ändern:
- Anstatt auf die letzten Schlusskurse zu achten, werde ich auf die Höchst- und Tiefstkurse schauen, um bessere Aufträge zu filtern.
Positionen werden so seltener eröffnet.
Dies ist der geänderte Code:
if(previousValue==100) { if(Normalizado<100 && array_ma[0]>tick.bid && rates[5].high < rates[1].low ) { Print("Open Order Buy"); Alert(" Buying"); Orden="Buy"; sl=NormalizeDouble(tick.ask - ptsl*_Point,_Digits); tp=NormalizeDouble(tick.bid + pttp*_Point,_Digits); //trade.Buy(get_lot(tick.bid),_Symbol,tick.bid,sl,tp); trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,get_lot(tick.bid),tick.bid,sl,tp,"Buy"); } if(Normalizado<100 && array_ma[0]<tick.ask && rates[5].low > rates[1].high ) { Print("Open Order Sell"); Alert(" Selling"); Orden="Sell"; sl=NormalizeDouble(tick.bid + ptsl*_Point,_Digits); tp=NormalizeDouble(tick.ask - pttp*_Point,_Digits); //trade.Sell(get_lot_s(tick.ask),_Symbol,tick.ask,sl,tp); trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,get_lot(tick.ask),tick.ask,sl,tp,"Sell"); } }
Mit diesen Änderungen ist der Gewinn der Strategie kleiner, sieht aber stabiler aus (sl = 4000 pts).
Dieser EA muss optimiert werden, um bessere Ergebnisse zu erzielen. Sie können den Code auch variieren. Dies ist ein Beispiel dafür, was Sie erhalten können, wenn die Strategie einen sl von 300 für 30 Minuten Zeitraum. Suchen Sie einfach nach dem Symbol und der Zeitspanne, die am besten passt.
Das Diagramm scheint stabiler mit sl von 300 pts aber profitabler für 4000 pts von sl. Wir sollten eine Optimierung für sl vornehmen, um ein Gleichgewicht für einen Zeitraum von 30 Minuten zu finden. Aber diese Aufgabe werde ich Ihnen überlassen.
Code
Dies sind die Eingaben
input ENUM_LOT_TYPE inp_lot_type = LOT_TYPE_FIX; // type of lot input double inp_lot_fix = 0.01; // fix lot input double inp_lot_risk = 0.01; input bool InpPrintLog = false; // Print log ulong Expert_MagicNumber =66777; // bool Expert_EveryTick =false; // input ENUM_TIMEFRAMES my_timeframe=PERIOD_CURRENT; // Timeframe int handle_iMA; input int Inp_MA_ma_period = 200; // MA: averaging period input int Inp_MA_ma_shift = 5; // MA: horizontal shift input ENUM_MA_METHOD Inp_MA_ma_method = MODE_SMA; // MA: smoothing type input ENUM_APPLIED_PRICE Inp_MA_applied_price = PRICE_CLOSE; // MA: type of price int shift = 49; // loockback normalization input int ptsl = 350; // points for stoploss input int pttp = 5000; // points for takeprofit
In der ersten Zeile wird die Art des zu verwendenden Losgröße festgelegt, d. h. eine feste Losgröße. In der nächsten Zeile wird die Größe der festen Losgröße festgelegt, gefolgt von der Größe des Risikolosgröße. In der nächsten Zeile wird ein boolescher Wert festgelegt, der bestimmt, ob ein Protokoll gedruckt werden soll oder nicht. In der folgenden Zeile wird die magische Zahl für den Experten festgelegt. In der nächsten Zeile wird ein boolescher Wert festgelegt, der bestimmt, ob der Experte bei jedem Tick ausgeführt werden soll oder nicht. In der nächsten Zeile wird der Zeitrahmen für den Experten festgelegt. In den nächsten Zeilen werden die Parameter für den Indikator des gleitenden Durchschnitts festgelegt, z. B. die Mittelungsperiode, die horizontale Verschiebung, die Art der Glättung und die Art des Preises. In der nächsten Zeile wird die Verschiebung für die Normalisierung des Rückblicks festgelegt. Die nächsten beiden Zeilen legen die Punkte für den Stop-Loss und den Take-Profit fest.
In OnInit():
int OnInit() { //--- handle_iMA=iMA(_Symbol,my_timeframe,Inp_MA_ma_period,Inp_MA_ma_shift, Inp_MA_ma_method,Inp_MA_applied_price); // Initialize the variable here if needed previousValue = 0.0; //--- return(INIT_SUCCEEDED); }
Dieser Code ist in der Sprache MQL5 geschrieben und wird zur Initialisierung einer Variablen verwendet. In der ersten Codezeile wird ein Handle für die Funktion iMA erstellt, mit der der gleitende Durchschnitt eines bestimmten Symbols über einen bestimmten Zeitrahmen berechnet wird. Die Parameter der Funktion iMA werden auf die Werte der Eingabevariablen Inp_MA_ma_period, Inp_MA_ma_shift, Inp_MA_ma_method und Inp_MA_applied_price gesetzt. Die zweite Codezeile initialisiert die Variable „previousValue“ mit 0,0. Die letzte Zeile des Codes gibt den Wert INIT_SUCCEEDED zurück, was bedeutet, dass die Initialisierung erfolgreich war.
In OnTick():
MqlTick tick; double last_price = tick.ask; SymbolInfoTick(_Symbol,tick);
und
if(SymbolInfoTick(_Symbol,tick)) last=tick.last; double Last = NormalizeDouble(last,_Digits);
Dieser Code ist in der Sprache MQL5 geschrieben und wird verwendet, um den aktuellen Briefkurs (bid) eines Symbols mit dem letzten Briefkurs zu vergleichen. In der ersten Zeile wird eine Variable namens ‚tick‘ vom Typ MqlTick erstellt. In der zweiten Zeile wird der letzte Briefkurs in der Variablen „last_price“ gespeichert. Die dritte Zeile ruft die Tick-Informationen für das in der Variablen „_Symbol“ angegebene Symbol ab und speichert sie in der Variablen „tick“. In der vierten Zeile wird geprüft, ob der aktuelle Briefkurs größer ist als der letzte Briefkurs, der in der Variablen „last_price“ gespeichert ist. Wenn ja, dann wird etwas unternommen.
Dieser Code wird verwendet, um den prozentualen Spread eines bestimmten Symbols zu berechnen. Zunächst wird mit der Funktion SymbolInfoTick() der letzte Preis des Symbols ermittelt. Der letzte Preis wird dann auf die durch den Parameter _Digits angegebene Anzahl von Ziffern normalisiert. Wenn der normalisierte letzte Kurs größer als 0 ist, werden die Geld- und Briefkurse (ask & bid) des Symbols abgerufen und normalisiert. Die Spanne wird durch Subtraktion des normalisierten Geldkurses vom normalisierten Briefkurs berechnet. Die Spanne wird dann durch den Punktwert des Symbols (berechnet mit der Funktion Pow()) geteilt, um die Spanne in Punkten zu erhalten. Schließlich wird der Spread in Punkten durch den normalisierten letzten Kurs geteilt und mit 100 multipliziert, um den Spread-Prozentsatz zu erhalten. Wenn der Prozentsatz der Streuung kleiner oder gleich dem Parameter Max_Spread ist, werden bestimmte Maßnahmen ergriffen.
Für die MA werden wir dies verwenden:
handle_iMA=iMA(_Symbol,my_timeframe,Inp_MA_ma_period,Inp_MA_ma_shift, Inp_MA_ma_method,Inp_MA_applied_price); //--- double array_ma[]; ArraySetAsSeries(array_ma,true); int start_pos=0,count=3; if(!iGetArray(handle_iMA,0,start_pos,count,array_ma)) return; string text=""; for(int i=0; i<count; i++) text=text+IntegerToString(i)+": "+DoubleToString(array_ma[i],Digits()+1)+"\n"; //--- Comment(text);
bool iGetArray(const int handle,const int buffer,const int start_pos, const int count,double &arr_buffer[]) { bool result=true; if(!ArrayIsDynamic(arr_buffer)) { //if(InpPrintLog) PrintFormat("ERROR! EA: %s, FUNCTION: %s, this a no dynamic array!",__FILE__,__FUNCTION__); return(false); } ArrayFree(arr_buffer); //--- reset error code ResetLastError(); //--- fill a part of the iBands array with values from the indicator buffer int copied=CopyBuffer(handle,buffer,start_pos,count,arr_buffer); if(copied!=count) { //--- if the copying fails, tell the error code //if(InpPrintLog) PrintFormat("ERROR! EA: %s, FUNCTION: %s, amount to copy: %d, copied: %d, error code %d", __FILE__,__FUNCTION__,count,copied,GetLastError()); //--- quit with zero result - it means that the indicator is considered as not calculated return(false); } return(result); }Dieser Code wird verwendet, um den gleitenden Durchschnitt (MA) eines bestimmten Symbols für einen bestimmten Zeitrahmen zu berechnen und anzuzeigen. Die Funktion iMA() wird zur Berechnung des MA verwendet, und die Funktion iGetArray() wird zum Abrufen der MA-Werte aus dem Indikatorpuffer verwendet. Die Funktion ArraySetAsSeries() wird verwendet, um das Array als Serie zu setzen, und die Funktionen IntegerToString() und DoubleToString() werden verwendet, um die Arraywerte in Strings umzuwandeln. Schließlich wird die Funktion Comment() verwendet, um die MA-Werte auf dem Chart anzuzeigen.
Um Volumenfehler bei der Eröffnung von Aufträgen zu vermeiden, werden wir dies verwenden:
//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< double get_lot(double price) { if(inp_lot_type==LOT_TYPE_FIX) return(normalize_lot(inp_lot_fix)); double one_lot_margin; if(!OrderCalcMargin(ORDER_TYPE_BUY,_Symbol,1.0,price,one_lot_margin)) return(inp_lot_fix); return(normalize_lot((AccountInfoDouble(ACCOUNT_BALANCE)*(inp_lot_risk/100))/ one_lot_margin)); } //+------------------------------------------------------------------+ //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< double normalize_lot(double lt) { double lot_step = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP); lt = MathFloor(lt / lot_step) * lot_step; double lot_minimum = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN); lt = MathMax(lt, lot_minimum); return(lt); }
Dieser Code wird zur Berechnung der Losgröße für einen Kaufauftrag verwendet. Die erste Funktion, get_lot(), nimmt einen Preis als Argument auf und gibt die Losgröße zurück. Die Losgröße wird durch den Losgrößentyp bestimmt, der entweder fest ist oder sich nach dem Risikoprozentsatz richtet. Ist die Art der Losgröße festgelegt, gibt die Funktion die normalisierte Losgröße zurück. Wenn der Losgrößentyp auf dem Risikoprozentsatz basiert, berechnet die Funktion die Marge für ein Lot, berechnet die Lotgröße auf der Grundlage des Saldos und des Risikoprozentsatzes und gibt die normalisierte Lotgröße zurück. Die zweite Funktion, normalize_lot(), nimmt eine Losgröße als Argument auf und gibt die normalisierte Losgröße zurück. Die normalisierte Losgröße wird berechnet, indem die Losgröße durch den Volumenschritt dividiert und dann mit dem Volumenschritt multipliziert wird. Die normalisierte Losgröße wird dann mit der minimalen Losgröße verglichen, und das Maximum der beiden Werte wird zurückgegeben.
Zum Eröffnen von Aufträgen verwenden wir Folgendes:
trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,get_lot(tick.bid),tick.bid,sl,tp,"Buy");
Dieser Code ist in MQL5 geschrieben und wird verwendet, um eine neue Position auf dem Markt zu eröffnen. Der erste Parameter ist das Symbol des Vermögenswerts, mit dem gehandelt werden soll. Der zweite Parameter ist die Art des Auftrags, in diesem Fall ein Kaufauftrag. Der dritte Parameter ist die Losgröße, die mit der Funktion get_lot() und dem aktuellen Geldkurs berechnet wird. Der vierte Parameter ist der aktuelle Briefkurs. Der fünfte und sechste Parameter sind Stop-Loss- und Take-Profit. Der letzte Parameter ist ein Kommentar, der dem Auftrag hinzugefügt wird.
Der Start erfolgt entsprechend der ursprünglichen Strategie, wobei nur die Abschlussbedingungen für Hoch-Tief geändert werden (um robustere Ergebnisse zu erhalten).
if(0<=Normalizado<=100 ) { //------------------------------------------------------------------------------ if(previousValue==100) { if(Normalizado<100 && array_ma[0]>tick.bid && rates[5].high < rates[1].low ) { Print("Open Order Buy"); Alert(" Buying"); Orden="Buy"; sl=NormalizeDouble(tick.ask - ptsl*_Point,_Digits); tp=NormalizeDouble(tick.bid + pttp*_Point,_Digits); //trade.Buy(get_lot(tick.bid),_Symbol,tick.bid,sl,tp); trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,get_lot(tick.bid),tick.bid,sl,tp,"Buy"); } if(Normalizado<100 && array_ma[0]<tick.ask && rates[5].low > rates[1].high ) { Print("Open Order Sell"); Alert(" Selling"); Orden="Sell"; sl=NormalizeDouble(tick.bid + ptsl*_Point,_Digits); tp=NormalizeDouble(tick.ask - pttp*_Point,_Digits); //trade.Sell(get_lot_s(tick.ask),_Symbol,tick.ask,sl,tp); trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,get_lot(tick.ask),tick.ask,sl,tp,"Sell"); } } } previousValue = Normalizado; if(Orden=="Sell" && rates[0].low <array_ma[0]) { trade.PositionClose(_Symbol,5); Print("cerro sell"); return; } if(Orden=="Buy" && rates[0].high >array_ma[0]) { trade.PositionClose(_Symbol,5); Print("cerro buy"); return; } }
Dieser Code ist in der Sprache MQL5 geschrieben und wird zum Öffnen und Schließen von Aufträgen auf dem Devisenmarkt verwendet. Der Code prüft zunächst, ob der Wert der Variablen „Normalizado“ zwischen 0 und 100 liegt. Ist dies der Fall, so wird geprüft, ob der vorherige Wert von „Normalizado“ 100 war. Wenn ja, wird geprüft, ob der Wert von „Normalizado“ kleiner als 100 ist und ob der Wert von array_ma[0] größer ist als der aktuelle Geldkurs und ob der Höchstwert der letzten 5 Kurse kleiner ist als der Tiefstwert des ersten Kurses. Wenn alle diese Bedingungen erfüllt sind, druckt der Code „Open Order Buy“, weist den Nutzer darauf hin, dass er kauft, setzt die Variable „Orden“ auf „Buy“, setzt Stop-Loss und Take-Profit und sendet einen Kaufauftrag mit den angegebenen Parametern.
Wenn der Wert von „Normalizado“ kleiner als 100 ist und der Wert von array_ma[0] kleiner als der aktuelle Briefkurs ist und der Tiefstwert der letzten 5 Kurse größer als der Höchstwert des ersten Kurses ist, druckt der Code „Open Order Sell“, weist den Nutzer darauf hin, dass er verkauf, setzt die Variable „Orden“ auf „Sell“, setzt die Stop-Loss und Take-Profit und öffnet eine Verkaufsorder mit den angegebenen Parametern. Danach setzt der Code den vorherigen Wert von „Normalizado“ auf den aktuellen Wert. Schließlich prüft der Code, ob die Variable „Orden“ auf „Verkaufen“ gesetzt ist und ob der Tiefstwert des aktuellen Kurses unter dem Wert von array_ma[0] liegt. Wenn diese Bedingungen erfüllt sind, schließt der Code den Verkaufsauftrag und druckt „cerro sell“. Ähnlich verhält es sich, wenn die Variable „Orden“ auf „Buy“ gesetzt ist und der Höchststand des aktuellen Kurses größer ist als der Wert von array_ma[0], dann schließt der Code den Kaufauftrag und druckt „cerro buy“.
Schlussfolgerung
Die Strategie muss optimiert werden, um auf den Märkten angewandt werden zu können, aber die Idee ist, eine Mean-Reversion-Denkweise für die Marktanalyse zu präsentieren.
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/12830





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