Filtern von Signalen auf Basis statistischer Daten von Preiskorrelationen

Михаил Тарачков | 7 April, 2016


Wie alles begann

Die Idee zum Schreiben dieses Beitrags kam mir, nachdem ich Larry Williams' Buch "Die Erfolgsgeheimnisse des Kurzfrist-Tradings" gelesen hatte, in dem der Weltrekordhalter in Sachen Investitionen (über das Jahr 1987 konnte er sein Kapital um 11.000 % steigern) die durch "... Hochschulprofessoren und sonstige Akademiker, die reich an Theorie und arm an Marktkenntnis sind..." geschaffenen Mythen über das Fehlen jeglicher Korrelation zwischen dem Verhalten von Preisen in der Vergangenheit und ihren zukünftigen Trends vollständig widerlegt.

Wenn Sie eine Münze 100 Mal werfen, fällt sie 50 Mal mit dem Kopf und 50 Mal mit der Zahl nach oben. Mit jedem Wurf liegt die Wahrscheinlichkeit für Kopf bei 50 %, genauso wie für Zahl. Die Wahrscheinlichkeit ändert sich nicht von Wurf zu Wurf, weil dieses Spiel auf dem Zufall basiert und über kein Gedächtnis verfügt. Nehmen wir an, dass die Märkte sich ebenso chaotisch verhalten wie eine Münze.

Folglich hat ein Preis mit jedem neuen Balken eine gleich hohe Chance, nach oben oder nach unten zu gehen, und die vorhergehenden Balken beeinflussen den aktuellen nicht auf die geringste Weise. Welch ein Idyll! Erstellen Sie ein Handelssystem, legen Sie Take Profit höher als Stop Loss fest (d. h. legen Sie eine positive mathematische Erwartung fest) und das ist auch der ganze Trick. Einfach atemberaubend. Doch das Problem ist, dass unsere Annahme über das Verhalten des Marktes nicht ganz richtig ist. Genauer gesagt, ist sie vollkommen absurd! Und ich werde es beweisen.

Erstellen wir eine Vorlage für einen Expert Advisor mithilfe des MQL5 Wizard und bringen ihn durch einfache alphanumerische Eingriffe in einen Zustand, der geeignet ist, die Aufgabe auszuführen. Wir programmieren einen Expert Advisor, der einen Kauf nach einem, zwei und drei Balken, die mit Preissenkung schließen, simuliert. Simulieren bedeutet in diesem Fall, dass das Programm sich einfach an die Parameter analysierter Balken erinnern wird. Das Senden von Ordern (die gebräuchlichere Variante) wird in diesem Fall nicht funktionieren, da die Spreads und Swaps die Zuverlässigkeit der erhaltenen Informationen in Frage stellen können.

Und das ist der Code:

//+------------------------------------------------------------------+
//|                                                     explorer.mq5 |
//|                        Copyright 2011, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2011, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
//---Variables---
double profit_percent,open_cur,close_cur;
double profit_trades=0,loss_trades=0,day_cur,hour_cur,min_cur,count;
double open[],close[];
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
/* Calculate percent of closures with increase from the total number */
   profit_percent=NormalizeDouble(profit_trades*100/(profit_trades+loss_trades),2);
   Print("Percent of closures with increase ",profit_percent,"%");   // Enter data to the Journal
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---find out the time---
   MqlDateTime time;                        // Create a structure to store time
   TimeToStruct(TimeCurrent(),time);         // Structuring the data
   day_cur=time.day_of_week;              // Receive the value of the current day
   hour_cur=time.hour;                    // Receive the current hour
   min_cur=time.min;                      // Receive the current minute
//---Find out the prices---
   CopyOpen(NULL,0,0,4,open);ArraySetAsSeries(open,true);
   CopyClose(NULL,0,0,4,close);ArraySetAsSeries(close,true);

   if(close[1]<open[1]/*&&close[2]<open[2]&&close[3]<open[3]*/ && count==0) // If it closed with a loss
     {
      open_cur=open[0];                   // Remember open price of the current bar
      count=1;
     }
   if(open_cur!=open[0] && count==1)      // The current bar has closed
     {
      close_cur=close[1];                 // Remember the close price of the formed bar
      count=0;
      if(close_cur>=open_cur)profit_trades+=1;  // If the close price is higher than open,
      else loss_trades+=1;                      // +1 to closures with profit, otherwise +1 to closures with loss
     }
  }
//+------------------------------------------------------------------+

Der Test wird auf EUR/USD im Intervall von 1. Januar 2000 bis 31. Dezember 2010 ausgeführt:

Abbildung 1. Anteil von Schließungen mit Erhöhung.

Abbildung 1. Anteil von Schließungen mit Erhöhung

(Die erste Säule zeigt die Daten für den gesamten Zeitraum, die zweite dritte und vierte die Daten nach einfacher, zweifacher und dreifacher Schließung)

Das ist es! Die vorherigen Balken wirken sich deutlich auf den aktuellen aus, weil der Preis immer versucht, Verluste zurückzugewinnen.



Ein weiterer Schritt nach vorne

Großartig! Da wir nun überzeugt sind, dass das Verhalten von Preisen nicht zufällig ist, sollten wir diese erstaunliche Tatsache so bald wie möglich einsetzen. Natürlich reicht das nicht für ein unabhängiges Handelssystem, doch es wird ein tolles Werkzeug sein, das Sie von anstrengenden und oftmals falschen Signalen befreien kann. Setzen wir es um!

Was wir brauchen:

  1. Ein eigenständiges Handelssystem, das mindestens für das letzte Jahr positive Ergebnisse aufweist.
  2. Irgendein lustiges Beispiel, das das Vorhandensein von Korrelationen im Verhalten von Preisen bestätigt.

Ich habe in L. Williams' Buch etliche nützliche Ideen gefunden. Eine davon werde ich mit Ihnen teilen.

Die Strategie des Handelstages der Woche (Trade Day Of Week, TDW). Diese Idee ermöglicht es uns, zu sehen, was passieren wird, wenn wir an einigen Tagen der Woche nur kaufen und an den anderen nur kurze Positionen öffnen. Schließlich können wir annehmen, dass der Preis innerhalb des einen Tages in mehr Fällen wächst als innerhalb des anderen. Woran liegt das? Der geopolitischen Situation? Makroökonomischen Statistiken? Oder, wie es in A. Elders Buch steht, weil Montage und Dienstage die Tage der Laien sind und Donnerstage und Freitage die Tage der Experten? Versuchen wir, dies nachzuvollziehen.

Wir werden erst an jedem Wochentag kaufen und dann nur verkaufen. Am Ende der Untersuchung vergleichen wir die besten Ergebnisse und erstellen daraus einen Filter für unser Handelssystem. Dazu möchte ich noch ein paar Worte sagen. Es ist ein absoluter Klassiker!

Das System basiert auf zwei MAs und MACDake. Signale:                                                            

  1. BUY, wenn der schnelle gleitende Mittelwert den langsamen von unten nach oben überkreuzt und das MACD-Histogramm unter der Nulllinie liegt.
  2. SELL, wenn der schnelle gleitende Mittelwert den langsamen von oben nach unten überkreuzt und MACD über Null liegt. 

Verlassen Sie die Position mithilfe eines Trailing Stops von einem Punkt. Das Los ist fix: 0,1. 

 Aus Bequemlichkeitsgründen habe ich die Klasse des Expert Advisors in einer separaten Header-Datei platziert:

//+------------------------------------------------------------------+
//|                                                       moving.mqh |
//|                        Copyright 2011, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2011, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
//+------------------------------------------------------------------+
//| Класс my_expert                                                  |
//+------------------------------------------------------------------+
class my_expert
  {                                                  // Creating a class
   // Closed class members
private:
   int               ma_red_per,ma_yel_per;          // Periods of MAs
   int               ma_red_han,ma_yel_han,macd_han; // Handles
   double            sl,ts;                          // Stop orders
   double            lots;                           // Lot
   double            MA_RED[],MA_YEL[],MACD[];       // Arrays for the indicator values
   MqlTradeRequest   request;                         // Structure of a trade request
   MqlTradeResult    result;                          // Structure of a server response
                                                    // Open class members   
public:
   void              ma_expert();                                   // Constructor
   void get_lot(double lot){lots=lot;}                               // Receiving a lot  
   void get_periods(int red,int yel){ma_red_per=red;ma_yel_per=yel;} // Receiving the periods of MAs
   void get_stops(double SL,double TS){sl=SL;ts=TS;}                  // Receiving the values of stops
   void              init();                                         // Receiving the indicator values
   bool              check_for_buy();                                // Checking for buy
   bool              check_for_sell();                               // Checking for sell
   void              open_buy();                                     // Open buy
   void              open_sell();                                    // Open sell
   void              position_modify();                              // Position modification
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
/* Function definition */
//---Constructor---
void my_expert::ma_expert(void)
  {
//--- Reset the values of variables
   ZeroMemory(ma_red_han);
   ZeroMemory(ma_yel_han);
   ZeroMemory(macd_han);
  }
//---The function for receiving the indicator values---
void  my_expert::init(void)
  {
   ma_red_han=iMA(_Symbol,_Period,ma_red_per,0,MODE_EMA,PRICE_CLOSE); // Handle of the slow MA
   ma_yel_han=iMA(_Symbol,_Period,ma_yel_per,0,MODE_EMA,PRICE_CLOSE); // Handle of the fast MA
   macd_han=iMACD(_Symbol,_Period,12,26,9,PRICE_CLOSE);               // Handle of MACDaka
//---Copy data into arrays and set indexing like in a time-series---
   CopyBuffer(ma_red_han,0,0,4,MA_RED);
   CopyBuffer(ma_yel_han,0,0,4,MA_YEL);
   CopyBuffer(macd_han,0,0,2,MACD);
   ArraySetAsSeries(MA_RED,true);
   ArraySetAsSeries(MA_YEL,true);
   ArraySetAsSeries(MACD,true);
  }
//---Function to check conditions to open buy---   
bool my_expert::check_for_buy(void)
  {
   init();  //Receive values of indicator buffers
/* If the fast MA has crossed the slow MA from bottom up between 2nd and 3rd bars, 
   and there was no crossing back. MACD-hist is below zero */
   if(MA_RED[3]>MA_YEL[3] && MA_RED[1]<MA_YEL[1] && MA_RED[0]<MA_YEL[0] && MACD[1]<0)
     {
      return(true);
     }
   return(false);
  }
//----Function to check conditions to open sell---
bool my_expert::check_for_sell(void)
  {
   init();  //Receive values of indicator buffers
/* If the fast MA has crossed the slow MA from up downwards between 2nd and 3rd bars,
  and there was no crossing back. MACD-hist is above zero */
   if(MA_RED[3]<MA_YEL[3] && MA_RED[1]>MA_YEL[1] && MA_RED[0]>MA_YEL[0] && MACD[1]>0)
     {
      return(true);
     }
   return(false);
  }
//---Open buy---
/* Form a standard trade request to buy */
void my_expert::open_buy(void)
  {
   request.action=TRADE_ACTION_DEAL;
   request.symbol=_Symbol;
   request.volume=lots;
   request.price=SymbolInfoDouble(Symbol(),SYMBOL_ASK);
   request.sl=request.price-sl*_Point;
   request.tp=0;
   request.deviation=10;
   request.type=ORDER_TYPE_BUY;
   request.type_filling=ORDER_FILLING_FOK;
   OrderSend(request,result);
   return;
  }
//---Open sell---
/* Form a standard trade request to sell */
void my_expert::open_sell(void)
  {
   request.action=TRADE_ACTION_DEAL;
   request.symbol=_Symbol;
   request.volume=lots;
   request.price=SymbolInfoDouble(Symbol(),SYMBOL_BID);
   request.sl=request.price+sl*_Point;
   request.tp=0;
   request.deviation=10;
   request.type=ORDER_TYPE_SELL;
   request.type_filling=ORDER_FILLING_FOK;
   OrderSend(request,result);
   return;
  }
//---Position modification---
void my_expert::position_modify(void)
  {
   if(PositionGetSymbol(0)==_Symbol)
     {     //If a position is for our symbol
      request.action=TRADE_ACTION_SLTP;
      request.symbol=_Symbol;
      request.deviation=10;
      //---If a buy position---
      if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
        {
/* if distance from price to stop loss is more than trailing stop
   and the new stop loss is not less than the previous one */
         if(SymbolInfoDouble(Symbol(),SYMBOL_BID)-PositionGetDouble(POSITION_SL)>_Point*ts)
           {
            if(PositionGetDouble(POSITION_SL)<SymbolInfoDouble(Symbol(),SYMBOL_BID)-_Point*ts)
              {
               request.sl=SymbolInfoDouble(Symbol(),SYMBOL_BID)-_Point*ts;
               request.tp=PositionGetDouble(POSITION_TP);
               OrderSend(request,result);
              }
           }
        }
      //---If it is a sell position---                
      else if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
        {
/*  if distance from price to stop loss is more than the trailing stop value
   and the new stop loss is not above the previous one. Or the stop loss from the moment of opening is equal to zero */
         if((PositionGetDouble(POSITION_SL)-SymbolInfoDouble(Symbol(),SYMBOL_ASK))>(_Point*ts))
           {
            if((PositionGetDouble(POSITION_SL)>(SymbolInfoDouble(Symbol(),SYMBOL_ASK)+_Point*ts)) || 
               (PositionGetDouble(POSITION_SL)==0))
              {
               request.sl=SymbolInfoDouble(Symbol(),SYMBOL_ASK)+_Point*ts;
               request.tp=PositionGetDouble(POSITION_TP);
               OrderSend(request,result);
              }
           }
        }
     }
  }
//+------------------------------------------------------------------

Ich verneige mich vor dem Verfasser des Beitrags "Einen Expert Advisor mit Hilfe des MQL5 Objekt-orientierten Programmieransatzes schreiben". Was würde ich nur ohne diesen Beitrag tun! Ich empfehle diesen Beitrag jedem, der in dieser bösartigen, doch äußerst funktionalen objektorientierten Programmierung nicht allzu versiert ist.

Fügen Sie die Datei mit der Klasse zum Hauptcode des Expert Advisors hinzu, erstellen Sie ein Objekt und initialisieren Sie die Funktionen:

//+------------------------------------------------------------------+
//|                                                       Moving.mq5 |
//|                        Copyright 2011, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2011, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
//---Include a file with the class---
#include <moving.mqh>
//---External Variables---
input int MA_RED_PERIOD=7; // The period of a slow MA
input int MA_YEL_PERIOD=2; // The period of a fast MA
input int STOP_LOSS=800;   // Stop loss
input int TRAL_STOP=800;   // Trailing stop
input double LOTS=0.1;     // Lot
//---Create an object---
my_expert expert;
//---Initialize the MqlDataTime structure---
MqlDateTime time;
int day_of_week;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---Initialize the EA
   expert.get_periods(MA_RED_PERIOD,MA_YEL_PERIOD);   // Set the MA periods
   expert.get_lot(LOTS);                              // Set the lot
   expert.get_stops(STOP_LOSS,TRAL_STOP);             // Set stop orders  
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   TimeToStruct(TimeCurrent(),time);
   day_of_week=time.day_of_week;
   if(PositionsTotal()<1)
     {
      if(day_of_week==5 && expert.check_for_buy()==true){expert.open_buy();}
      else if(day_of_week==1 && expert.check_for_sell()==true){expert.open_sell();}
     }
   else expert.position_modify();
  }
//+------------------------------------------------------------------+

Fertig! Ich möchte Sie auf einige Besonderheiten aufmerksam machen. Um die Wochentage auf Softwareebene zu identifizieren, nutze ich die Struktur MqlDateTime. Als Erstes wandeln wir die aktuelle Serverzeit in ein strukturiertes Format um. Wir erhalten einen Index des aktuellen Tages (1-Montag, ..., 5-Freitag) und vergleichen ihn mit dem von uns festgelegten Wert.

Probieren wir es aus! Um Sie nicht mit mühseligen Recherchen und unnötigen Zahlen zu belasten, führe ich alle Ergebnisse in einer Tabelle auf.

Hier ist sie: 

Tabelle 1. Zusammenfassung der Käufe an jedem Wochentag

Tabelle 1. Zusammenfassung der Käufe an jedem Wochentag

Tabelle 2. Zusammenfassung der Verkäufe an jedem Wochentag

Tabelle 2. Zusammenfassung der Verkäufe an jedem Wochentag

Die besten Ergebnisse sind grün markiert, die schlechtesten orange.

Ich möchte anmerken, dass das System nach den oben beschriebenen Aktionen den Gewinn in Kombination mit einem geringen relativen Wertverlust, einem guten Anteil rentabler Abschlüsse (hier: je weniger Abschlüsse, desto besser) und einem vergleichsweise hohen Gewinn pro Abschluss gewährleisten muss.

Offensichtlich ist das effektivste System eins, das freitags kauft und montags verkauft. Kombinieren Sie diese beiden Bedingungen:

if(PositionsTotal()<1){
      if(day_of_week==5&&expert.check_for_buy()==true){expert.open_buy();}
      else if(day_of_week==1&&expert.check_for_sell()==true){expert.open_sell();}}
   else expert.position_modify();

Nun öffnet der Expert Advisor Positionen in beiden Richtungen, allerdings nur an strikt festgelegten Tagen. Zur Erklärung zeichne ich die erhaltenen Diagramme mit und ohne Filter:

Abbildung 2. Testergebnisse des EAs ohne Filter (EURUSD, H1, 01.01.2010-31.12.2010,)

Abbildung 2. Testergebnisse des EAs ohne Filter (EURUSD, H1, 01.01.2010-31.12.2010,)

Abbildung 3. Testergebnisse des EAs mit Filter (EURUSD, H1, 01.01.2010-31.12.2010,)

Abbildung 3. Testergebnisse des EAs mit Filter (EURUSD, H1, 01.01.2010-31.12.2010,)

Wie gefällt Ihnen das Ergebnis? Mithilfe des Filters ist das Handelssystem stabiler geworden. Vor den Modifikationen erhöhte der Expert Advisor die Bilanz hauptsächlich während der ersten Hälfte des Testzeitraums. Nach dem "Upgrade" erhöht er sie während des gesamten Zeitraums.

Wir vergleichen die Berichte:

Tabelle 3. Testergebnisse vor und nach dem Einsatz des Filters

Tabelle 3. Testergebnisse vor und nach dem Einsatz des Filters

Die einzige Enttäuschung, die sich nicht ignorieren lässt, ist die Abnahme des Nettogewinns um fast 1000 USD (26 %). Doch wir senken die Anzahl der Abschlüsse um knapp das 3,5-Fache, d. h. wir vermindern erstens das Risiko eines negativen Abschlusses und zweitens die Ausgaben für den Spread (218*2-62*2=312 USD, und das gilt nur für EUR/USD). Der Gewinnanteil steigt auf 57 %, was schon beträchtlich ist. Und der Gewinn pro Abschluss steigt um 14 % auf 113 USD. Wie L. Williams sagen würde: "Das ist eine Summe, die es wert ist, gehandelt zu werden!"



Fazit

Das Verhalten von Preisen ist nicht zufällig – das ist Tatsache. Diese Tatsache kann und soll genutzt werden. Ich habe nur ein Beispiel aufgeführt, das nur einen winzigen Anteil der unzähligen Varianten und Techniken darstellt, die die Performance Ihres Handelssystems steigern können. Doch hinter all dieser Vielfalt verbirgt sich ein Mangel. Nicht jeder Filter lässt sich integrieren, also müssen Sie sorgfältig auswählen und alle möglichen Szenarien durchdenken.

Vergessen Sie nicht, dass ein Filter, so perfekt er auch sein mag, auch rentable Abschlüsse – also Ihren Gewinn – herausfiltert... Viel Erfolg!