English 日本語
preview
Klassische Strategien neu interpretieren (Teil 14): Hochwahrscheinliche Setups

Klassische Strategien neu interpretieren (Teil 14): Hochwahrscheinliche Setups

MetaTrader 5Beispiele |
87 0
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

In unseren vorangegangenen Diskussionen haben wir analysiert, wie der Handel des Kreuzens von gleitenden Durchschnitten neu konzipiert werden kann, indem die Perioden der beiden fraglichen gleitenden Durchschnittsindikatoren festgelegt werden. Wir haben gezeigt, dass wir auf diese Weise ein angemessenes Maß an Kontrolle über den Umfang der Verzögerung in unserer Handelsstrategie ausüben können. Wir haben dann festgestellt, dass wir durch die Anwendung eines gleitenden Durchschnitts auf den Eröffnungskurs und des anderen auf den Schlusskurs eine sehr viel empfindlichere Form der alten Strategie des Kreuzens von gleitendem Durchschnitt erhalten haben.  Unser neuer Rahmen bietet einige Garantien, die wir mit der traditionellen Strategie nicht erreichen konnten. Leser, die die frühere Diskussion noch nicht gelesen haben, können den Artikel hier nachlesen.

Heute gehen wir der Frage nach, ob es sinnvoll ist, mit unserer neu konzipierten Version des gleitenden Durchschnitts-Kreuzes mehr Produktivität zu erzielen. Indem wir die Beziehung zwischen unserer Kreuzstrategie der gleitenden Durchschnitte und dem EURUSD-Markt sorgfältig modellieren, können wir hoffentlich herausfinden, worin der Unterschied zwischen den Marktbedingungen besteht, unter denen sich unsere Strategie auszeichnet, und den Marktbedingungen, die sich für unsere Strategie als zu schwierig erweisen. Unser Ziel ist es dann, eine Handelsstrategie zu entwickeln, die lernt, den Handel zu stoppen, wenn sie ungünstige Marktbedingungen feststellt.


Überblick über die Handelsstrategie

Die meisten Mitglieder unserer Gemeinschaft sind sich einig, dass Händler aktiv versuchen sollten, Setups mit hoher Wahrscheinlichkeit zu handeln. Es gibt jedoch nur wenige formale Definitionen dafür, was genau ein hochwahrscheinliches Handels-Setup ist. Wie lässt sich die Wahrscheinlichkeit eines bestimmten Handels-Setups empirisch messen? Je nachdem, wen Sie fragen, werden Sie unterschiedliche Definitionen dafür erhalten, wie Sie solche Chancen erkennen und verantwortungsvoll nutzen können.

Dieser Artikel versucht, diese Probleme anzugehen, indem er einen algorithmischen Rahmen vorschlägt, der es uns ermöglicht, von den alten Definitionen abzuweichen und uns auf numerische Definitionen zu stützen, die evidenzbasiert sind, sodass unsere Handelsstrategien in der Lage sein können, sie zu identifizieren und gewinnbringend zu handeln, und zwar auf konsistente Art und Weise ganz von selbst. 

Wir möchten die Beziehung zwischen unserer speziellen Handelsstrategie und jedem Symbol, das wir für den Handel ausgewählt haben, modellieren. Wir können dies erreichen, indem wir zunächst Marktdaten, die den Markt vollständig beschreiben, und alle Parameter, aus denen unsere Handelsstrategie besteht, vom MetaTrader 5-Terminal abrufen.
Danach werden wir ein statistisches Modell anpassen, um zu klassifizieren, ob die Strategie Signale erzeugt, die profitabel sind, oder ob das von unserer Strategie erzeugte Signal höchstwahrscheinlich unprofitabel sein wird. 

Die von unserem Modell geschätzten Wahrscheinlichkeiten werden zu den Wahrscheinlichkeiten, die wir mit diesem bestimmten Signal verbinden. Daher können wir jetzt anfangen, über „Hochwahrscheinlichkeits-Setups“ auf eine wissenschaftlichere und empirischere Weise zu sprechen, die sich auf Beweise und relevante Marktdaten stützt.

Dieser Rahmen ermöglicht es uns im Wesentlichen, Handelsstrategien zu entwickeln, die „zielbewusst“ sind und ausdrücklich angewiesen werden, nur Aktionen durchzuführen, von denen sie erwarten, dass sie vorteilhaft sind. Wir beginnen damit, die notwendigen Komponenten zu formalisieren, die für die Entwicklung von algorithmischen Handelsstrategien erforderlich sind, die versuchen, die wahrscheinlichsten Folgen ihrer Handlungen abzuschätzen. Dies lässt sich korrekt als Ideologie des Verstärkungslernens bezeichnen, die auf überwachte Weise angegangen wird.


Erste Schritte in MQL5

Unsere heutige Aufgabe besteht darin, die Beziehung zwischen unserer Handelsstrategie und dem Symbol, mit dem wir handeln wollen, zu verstehen. Um dieses Ziel zu erreichen, werden wir das Wachstum der 4 primären Kursdaten (Open, High, Low und Close) sowie die Veränderungen unserer beiden gleitenden Durchschnittsindikatoren abrufen. 

Beachten Sie, dass wir für die Kennzeichnung der Daten auch die realen Originalwerte der beiden Indikatoren und den Schlusskurs benötigen. Alles in allem werden wir unsere Daten in eine CSV-Datei mit 10 Spalten schreiben und dann damit fortfahren, die Beziehung zwischen unserer Strategie und diesem bestimmten Symbol zu erlernen, und bei jedem Schritt werden wir dieses Ergebnis mit der Leistung eines identischen Modells vergleichen, das versucht, den Marktpreis direkt vorherzusagen.

Dies gibt uns Aufschluss darüber, welches Ziel leichter zu erlernen ist, und das Schöne an unserem Ansatz ist die einfache Tatsache, dass unabhängig davon, welches Ziel leichter zu prognostizieren ist, beide Aufschluss darüber geben, wohin sich der Preis entwickelt.

/+-------------------------------------------------------------------+
//|                                                      ProjectName |
//|                                      Copyright 2020, CompanyName |
//|                                       http://www.companyname.net |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property script_show_inputs

//--- Define our moving average indicator
#define MA_PERIOD 3                 //--- Moving Average Period
#define MA_TYPE   MODE_SMA          //--- Type of moving average we have
#define  HORIZON 10

//--- Our handlers for our indicators
int ma_handle,ma_o_handle;

//--- Data structures to store the readings from our indicators
double ma_reading[],ma_o_reading[];

//--- File name
string file_name = Symbol() + " Reward Modelling.csv";

//--- Amount of data requested
input int size = 3000;

//+------------------------------------------------------------------+
//| Our script execution                                             |
//+------------------------------------------------------------------+
void OnStart()
  {
   int fetch = size + (HORIZON * 2);
//---Setup our technical indicators
   ma_handle = iMA(_Symbol,PERIOD_CURRENT,MA_PERIOD,0,MA_TYPE,PRICE_CLOSE);
   ma_o_handle = iMA(_Symbol,PERIOD_CURRENT,MA_PERIOD,0,MA_TYPE,PRICE_OPEN);

//---Set the values as series
   CopyBuffer(ma_handle,0,0,fetch,ma_reading);
   ArraySetAsSeries(ma_reading,true);
   CopyBuffer(ma_o_handle,0,0,fetch,ma_o_reading);
   ArraySetAsSeries(ma_o_reading,true);

//---Write to file
   int file_handle=FileOpen(file_name,FILE_WRITE|FILE_ANSI|FILE_CSV,",");

   for(int i=size;i>=1;i--)
     {
      if(i == size)
        {
         FileWrite(file_handle,"Time","True Close","True MA C","True MA O","Open","High","Low","Close","MA Close 2","MA Open 2");
        }

      else
        {
         FileWrite(file_handle,
                   iTime(_Symbol,PERIOD_CURRENT,i),
                   iClose(_Symbol,PERIOD_CURRENT,i),
                   ma_reading[i],
                   ma_o_reading[i],
                   iOpen(_Symbol,PERIOD_CURRENT,i)   - iOpen(_Symbol,PERIOD_CURRENT,(i + HORIZON)),
                   iHigh(_Symbol,PERIOD_CURRENT,i)   - iHigh(_Symbol,PERIOD_CURRENT,(i + HORIZON)),
                   iLow(_Symbol,PERIOD_CURRENT,i)    - iLow(_Symbol,PERIOD_CURRENT,(i + HORIZON)),
                   iClose(_Symbol,PERIOD_CURRENT,i)  - iClose(_Symbol,PERIOD_CURRENT,(i + HORIZON)),
                   ma_reading[i] - ma_reading[(i + HORIZON)],
                   ma_o_reading[i] - ma_o_reading[(i + HORIZON)]
                  );
        }
     }
//--- Close the file
   FileClose(file_handle);
  }
//+------------------------------------------------------------------+


Analyse der Daten

Lesen wir zunächst unsere Marktdaten in ein Jupyter-Notebook ein, damit wir die numerische Analyse der Performance der Strategie durchführen können.

import pandas as pd

Legen wir fest, wie weit in die Zukunft wir unsere Gewinne oder Verluste prognostizieren möchten.

HORIZON = 10

Wir lesen die Daten ein und fügen die benötigten Spalten hinzu. Die erste Spalte, „Ziel“, ist die traditionelle Marktrendite, in diesem Fall die 10-Tage-Rendite des EURUSD-Marktes. Die Spalte „Class“ ist entweder 1 für Aufwärts-Tage oder 0.  Drittens gibt die Spalte „Action“ an, welche Aktion unsere Handelsstrategie ausgelöst hätte, wobei 1 für Kauf und -1 für Verkauf steht. Die Spalte „Reward“ wird als elementweises Produkt aus der Spalte „Ziel“ und der Spalte „Action“ berechnet; diese Multiplikation führt nur zu positiven Belohnungen, wenn unsere Strategie funktioniert:

  • Wir wählen die Aktion -1 und das klassische Ziel ist kleiner als 0 (dies bedeutet, dass unsere Strategie verkauft und zukünftige Preisniveaus fallen werden)
  • Wir wählen die Aktion 1 und das klassische Ziel ist größer 0 (dies bedeutet, dass unsere Strategie kauft und zukünftige Preisniveaus steigen werden)
Jede andere Kombination von Aktionen und Zielen würde zu negativen Erträgen führen, weil unsere Handelsstrategie unangemessen gehandelt hat. Wenn unser Klassifikator lernen kann, vorherzusagen, ob unsere Belohnung positiv ausfallen wird, hätten wir ein sehr zuverlässiges Instrument gefunden, das uns vor ungünstigen Marktbedingungen bewahrt und uns hilft, unsere Geschäfte zu platzieren, wenn sie sich am ehesten auszahlen. Die letzte Spalte „Trade Signal“ ist auf 0 gesetzt und wird nur dann auf 1 gesetzt, wenn unsere Belohnung positiv ist. Zu Beginn werden alle 5 Spalten auf 0 gesetzt.

data = pd.read_csv("..\EURUSD Reward Modelling.csv")

data['Target'] = 0
data['Class']  = 0
data['Action'] = 0
data['Reward'] = 0
data['Trade Signal'] = 0

Jetzt geben wir unser klassisches Ziel ein, nämlich die 10-Tage-Veränderung des EURUSD-Schlusskurses.

data['Target'] = data['True Close'].shift(-HORIZON) - data['True Close']
data.dropna(inplace=True)

Wir müssen die Klassen kennzeichnen, damit wir in unseren Zeichnungen schnell zwischen Auf- und Abwärts-Tagen unterscheiden können.

data.loc[data['Target'] > 0,'Class'] = 1

Wir wollen nun herausfinden, welche Maßnahmen unsere Strategie ergriffen hätte.

data.loc[data['True MA C'] > data['True MA O'],'Action'] = 1
data.loc[data['True MA C'] < data['True MA O'],'Action'] = -1

Und der Gewinn oder Verlust, den unsere Strategie bei diesen Maßnahmen erzielt hätte.

data['Reward'] = data['Target'] * data['Action']

Geben wir nun das Handelssignal ein, das uns sagt, ob wir handeln oder abwarten sollten.

data.loc[((data['Target'] < 0) & (data['Action'] == -1)),'Trade Signal'] = 1
data.loc[((data['Target'] > 0) & (data['Action'] == 1)),'Trade Signal'] = 1

Wir können einen Blick auf die Daten werfen. Wir können schnell erkennen, dass es schwierig ist, das Marktgeschehen gut zu trennen. Die orangefarbenen Punkte stehen für Handelssignale, die wir hätten ergreifen sollen, während die blauen Punkte für Signale stehen, die wir hätten ignorieren sollen. Die Tatsache, dass die orangefarbenen und blauen Punkte in diesem Streudiagramm übereinander zu sehen sind, informiert den Leser mathematisch darüber, dass sich profitable und unprofitable Handelssignale unter fast identischen Bedingungen bilden können. Unsere statistischen Modelle können möglicherweise Ähnlichkeiten und Unterschiede erkennen, für die wir als Menschen nicht sensibel genug sind oder die nur mit erheblichem Aufwand zu erkennen wären.

import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt

sns.scatterplot(data=data,x='MA Close 2',y='MA Open 2',hue='Trade Signal')
plt.title('Analyzing How Well Moving Average Cross Overs Separate The Market')
plt.grid()

Abb. 1: Das Kreuzen unserer gleitenden Durchschnitte scheinen Probleme zu haben, die Preisbewegung zu trennen

Schätzen wir schnell ab, ob unser neues Ziel leichter zu prognostizieren ist als das traditionelle Ziel. 

from sklearn.linear_model import RidgeClassifier
from sklearn.model_selection import TimeSeriesSplit,cross_val_score

Lineare Modelle sind besonders nützlich, um zuverlässige und kostengünstige Näherungen zu erhalten. Erstellen wir ein geteiltes Zeitserienobjekt, um einen linearen Klassifikator kreuzvalidieren zu können.

tscv = TimeSeriesSplit(n_splits=5,gap=HORIZON)
model = RidgeClassifier()
scores = []

Wir führen eine Kreuzvalidierung des Modells durch, zunächst mit dem alten Ziel, dann mit unserem neuen Ziel, das den Gewinn/Verlust unserer Strategie zu schätzen versucht. Da es sich um eine Klassifizierungsaufgabe handelt, sollten wir die Bewertungsmethode auf „accuracy“ (Genauigkeit) einstellen.

scores.append(np.mean(np.abs(cross_val_score(model,data.iloc[:,4:-5],data.loc[:,'Class'],cv=tscv,scoring='accuracy'))))
scores.append(np.mean(np.abs(cross_val_score(model,data.iloc[:,4:-5],data.loc[:,'Trade Signal'],cv=tscv,scoring='accuracy'))))

Die Erstellung eines Balkendiagramms der Ergebnisse zeigt schnell, dass das Modell, das den Gewinn/Verlust der Strategie vorhersagt, besser abschneidet als das Modell, das versucht, den Markt direkt vorherzusagen. Das Modell, das versuchte, das Modell direkt zu prognostizieren, fiel unter die 50 %-Schwelle, während unser neues Ziel uns hilft, die 50 %-Benchmark zu überschreiten, wenn auch ehrlich gesagt nur knapp über dieser Schwelle.

sns.barplot(scores,color='black')
plt.axhline(np.max(scores),linestyle=':',color='red')
plt.title('Forcasting Market Returns vs Forecasting Strategy Reward')
plt.ylabel('Percentage Accuracy Levels %')
plt.xlabel('0: Market Return Forecast | 1: Strategy Profit/Loss Forecast')

Abb. 2: Unsere linearen Modelle deuten darauf hin, dass wir den Gewinn oder Verlust, der durch unsere Strategie entsteht, besser vorhersagen sollten

Wir können die genaue prozentuale Leistungssteigerung berechnen, und es zeigt sich, dass wir durch die Vorhersage des Verhältnisses zwischen der Strategie und dem Markt eine um 7,6 % höhere Genauigkeit erzielen als durch die direkte Vorhersage des Marktes.

scores = (((scores / scores[0]) - 1) * 100)

scores[1]

7.595993322203687

Lassen wir alle Daten weg, die sich mit unserem Backtest-Zeitraum überschneiden, damit unser Backtest eine echte Simulation der realen Marktbedingungen darstellt.

#Drop all the data that overlaps with your backtest period
data = data.iloc[:-((365 * 4) + (30 * 5) + 17),:]
data

Abb. 3: Vergewissern wir uns, dass sich die Daten in unserem Datenrahmen nicht mit den Daten überschneiden, die wir für den Backtest verwenden.

Das lineare Modell gab uns die Gewissheit, dass die Vorhersage von Gewinnen/Verlusten, die durch die Strategie generiert werden, für uns besser sein könnte als die direkte Vorhersage von Preisen. In unseren Backtests für den Handel werden wir jedoch einen flexibleren Lerner verwenden, um sicherzustellen, dass das Modell so viele nützliche Informationen wie möglich aufnimmt.

from sklearn.ensemble import GradientBoostingRegressor

model = GradientBoostingRegressor()

Kennzeichnung der Eingänge und des Ziels.

X = ['Open','High','Low','Close','MA Close 2','MA Open 2']
y = 'Trade Signal'

Passen wir das Modell an.

model.fit(data.loc[:,X],data.loc[:,y])

Bereiten wir den Export des Modells nach ONNX vor.

import onnx
from skl2onnx import convert_sklearn
from skl2onnx.common.data_types import FloatTensorType

Definieren wir die Eingabegröße des Modells.

initial_types = [("float input",FloatTensorType([1,6]))]

Speichern wir das Modell.

onnx_proto = convert_sklearn(model,initial_types=initial_types,target_opset=12)
onnx.save(onnx_proto,"EURUSD Reward Model.onnx")


Erste Schritte in MQL5

Wir sind nun bereit, mit der Entwicklung unserer Handelsanwendung zu beginnen. Wir werden zwei Versionen der Strategie entwickeln, um die Wirksamkeit unserer Änderungen am Trainingsverfahren für unser statistisches Modell zu bewerten. Beide Versionen des Handelsalgorithmus werden unter identischen Bedingungen einem Backtest unterzogen. Machen wir uns mit diesen besonderen Bedingungen vertraut, damit wir die gleichen Informationen nicht unnötig wiederholen müssen. 

Die erste wichtige Einstellung ist das Symbol, wie wir bereits besprochen haben, handeln wir in diesem Beispiel das Paar EURUSD. Wir werden das Symbol über einen Zeitraum von 5 Jahren, vom 1. Januar 2020 bis zum 1. April 2025, im täglichen Zeitrahmen handeln. Damit haben wir einen langen Zeitraum, um die Begründetheit unseres Antrags zu prüfen. Der Leser sollte sich daran erinnern, dass wir in Abbildung 3 alle Marktdaten, die uns über den 29. Dezember 2019 hinaus vorlagen, gelöscht haben.

Abb. 4: Die für unseren Backtest benötigten Daten für beide Versionen unserer Handelsstrategie

Und schließlich werden die Bedingungen, unter denen wir den Markt modellieren, so festgelegt, dass sie die Unvorhersehbarkeit des realen Handels nachahmen. Daher haben wir uns dafür entschieden, unsere Verzögerung auf Random Delay zu setzen und jeden Tick auf echte Ticks zu beziehen, um eine realistische Wiedergabe des Marktes zu erhalten. 

Abb. 5: Die Einstellungen, die wir zur Nachahmung der Marktbedingungen verwenden, sind von entscheidender Bedeutung

Beginnen wir nun damit, eine Anwendung zu erstellen, die wir einem Backtest unterziehen können, um den Nutzen der von uns vorgeschlagenen Änderungen an unserem Schulungsverfahren zu bewerten. Wir beginnen damit, die Leistung unserer Strategie zu messen, ohne den neuen Modellierungsansatz zu verwenden, den wir formuliert haben, um Setups mit hoher Wahrscheinlichkeit zu identifizieren. Die erste Version unserer Handelsstrategie besteht im Wesentlichen aus der Anwendung der diskretionären Handelsstrategie, bei der die Überkreuzungen des gleitenden Durchschnitts gehandelt werden, wenn sie auftreten. Dadurch erhalten wir eine Benchmark, mit der wir unsere vorgeschlagene Belohnungsmodellierungsstrategie vergleichen können. Für den Anfang importieren wir zunächst die Handelsbibliothek.

//+------------------------------------------------------------------+
//|                                             Reward Modelling.mq5 |
//|                                               Gamuchirai Ndawana |
//|                    https://www.mql5.com/en/users/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Ndawana"
#property link      "https://www.mql5.com/en/users/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| Libraries we need                                                |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
CTrade Trade;

Definieren wir die nützlichen Systemkonstanten. Diese Konstanten entsprechen den anderen Konstanten, die wir sowohl im MQL5-Skript als auch im Python-Skript verwendet haben.

//+------------------------------------------------------------------+
//| System constants                                                 |
//+------------------------------------------------------------------+
#define MA_PERIOD 3                //--- Moving Average Period
#define MA_TYPE   MODE_SMA         //--- Type of moving average we have
#define HORIZON 10                 //--- How far into the future we should forecast

Wir richten unsere globalen Variablen und technischen Indikatoren ein.

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
int fetch = HORIZON + 1;

//+------------------------------------------------------------------+
//| Technical indicators                                             |
//+------------------------------------------------------------------+
int ma_handle,ma_o_handle;
double ma_reading[],ma_o_reading[];
int position_timer;

Jedem Ereignis wurde eine entsprechende Methode zugeordnet, die aufgerufen wird, wenn die Ereignisbehandlung ausgelöst wird. Mit diesem Designstil lässt sich Ihre Codebasis leichter pflegen und in Zukunft erweitern, wenn Ihnen neue Ideen einfallen. Wenn Sie Ihren Code direkt in die Ereignisbehandlung schreiben, muss der Entwickler zahlreiche Codezeilen sorgfältig analysieren, bevor er Änderungen vornimmt, um sicher zu sein, dass nichts unbrauchbar wird, während der Entwickler bei unserem Entwurfsmuster nur einen Aufruf der gewünschten Funktion über die von uns bereitgestellten Funktionen legen muss, wenn er die Funktionalität der Anwendung erweitern möchte.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   if(!setup())
      return(INIT_FAILED);
//---
   return(INIT_SUCCEEDED);
  }
  
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   release();
  }
  
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   update();
  }
//+------------------------------------------------------------------+

Wir werden nun jede Methode der Reihe nach betrachten. Die erste Funktion, die wir einrichten werden, ist die Funktion, die für das Laden unserer technischen Indikatoren und das Zurücksetzen unseres Positionstimers verantwortlich ist. Der Positionstimer wird benötigt, um sicherzustellen, dass wir jeden Handel so lange halten, wie unsere Horizont-Systemkonstante eingestellt ist.

//+------------------------------------------------------------------+
//| Setup the system                                                 |
//+------------------------------------------------------------------+
bool setup(void)
  {
//---Setup our technical indicators
   ma_handle = iMA(_Symbol,PERIOD_CURRENT,MA_PERIOD,0,MA_TYPE,PRICE_CLOSE);
   ma_o_handle = iMA(_Symbol,PERIOD_CURRENT,MA_PERIOD,0,MA_TYPE,PRICE_OPEN);
   position_timer = 0;

   return(true);
  }

Die Freigabemethode gibt einfach die technischen Indikatoren frei, die wir nicht verwenden. Es ist eine gute Praxis in MQL5, hinter sich selbst aufzuräumen.

//+------------------------------------------------------------------+
//| Release system variables we are no longer using                  |
//+------------------------------------------------------------------+
void release(void)
  {
   IndicatorRelease(ma_handle);
   IndicatorRelease(ma_o_handle);
   return;
  }

Die Aktualisierungsmethode holt die aktuellen Preisniveaus und kopiert sie in unsere Indikatorpuffer. Darüber hinaus wird es auch verfolgen, wie lange unsere aktuelle Position geöffnet war, um sie rechtzeitig zu schließen.

//+------------------------------------------------------------------+
//| Update system parameters                                         |
//+------------------------------------------------------------------+
void update(void)
  {
   //--- Time stamps
   static datetime time_stamp;
   datetime current_time = iTime(Symbol(),PERIOD_D1,0);

   //--- We are on a new day
   if(time_stamp != current_time)
     {
      time_stamp = current_time;
      if(PositionsTotal() == 0)
        {
         //--- Copy indicator values
         CopyBuffer(ma_handle,0,0,fetch,ma_reading);
         CopyBuffer(ma_o_handle,0,0,fetch,ma_o_reading);
         //---Set the values as series
         ArraySetAsSeries(ma_reading,true);
         ArraySetAsSeries(ma_o_reading,true);
         find_setup();
         position_timer = 0;
        }

      //--- Forecasts are only valid for HORIZON days
      if(PositionsTotal() > 0)
        {
         position_timer += 1;
        }

      //--- Otherwise close the position
      if(position_timer == HORIZON)
         Trade.PositionClose(Symbol());
     }
     return;
  }

Und schließlich die Funktion zur Einrichtung der Suche. Unsere Setups werden immer dann identifiziert, wenn sich die gleitenden Durchschnitte überschneiden. Wenn der Eröffnungskurs den Schlusskurs übersteigt, wird dies als Verkaufssignal registriert. Ansonsten haben wir ein Kaufsignal.

//+------------------------------------------------------------------+
//| Find a trading oppurtunity                                       |
//+------------------------------------------------------------------+
void find_setup(void)
  {
         double bid = SymbolInfoDouble(Symbol(),SYMBOL_BID) , ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);
         double vol = SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN);

         vector ma_o,ma_c;

         ma_o.CopyIndicatorBuffer(ma_o_handle,0,0,1);
         ma_c.CopyIndicatorBuffer(ma_handle,0,0,1);

         if(ma_o[0] > ma_c[0])
           {
            Trade.Sell(vol,Symbol(),ask,0,0,"");
           }

         if(ma_o[0] < ma_c[0])
           {
            Trade.Buy(vol,Symbol(),bid,0,0,"");
           }
     return;
  }    

Vergessen wir nicht, die zuvor definierten Systemkonstanten zu deaktivieren.

//+------------------------------------------------------------------+
//| Undefine system constatns                                        |
//+------------------------------------------------------------------+
#undef HORIZON
#undef MA_PERIOD
#undef MA_TYPE

Wir haben die Einstellungen, die wir für unseren Backtest verwenden werden, bereits kurz zuvor behandelt. Unser Ziel ist es, zu beobachten, wie effektiv unsere Strategie ohne die von uns entwickelten, statistischen Modellierungstechniken ist. Wir laden einfach den Expert Advisor und starten den Backtest mit den Einstellungen, die wir in Abb. 4 und 5 besprochen haben.

Abb. 6: Festlegung einer Benchmark

Unsere diskretionäre Version der Handelsstrategie war profitabel, auch wenn sie nur geringfügig profitabel zu sein scheint. Der Unterschied zwischen dem durchschnittlichen Gewinn und Verlust beträgt nur 0,9 $ und nur 51 % aller von ihm getätigten Geschäfte sind profitabel, was nicht sehr ermutigend ist. Unsere Sharpe Ratio liegt bei 0,62 und könnte sich möglicherweise verbessern, wenn wir unser System überarbeiten würden.

Abb. 7: Eine detaillierte Analyse der Leistung unserer diskretionären Handelsstrategie

Wenn wir nun die Salden- und Kapitalkurve analysieren, die diese Version der Handelsstrategie erzeugt, können wir sofort offensichtliche Mängel erkennen. Die Strategie ist instabil und unbeständig. Tatsächlich hat die Strategie nach vier Jahren Backtest im Februar 2024 fast wieder ihren Anfangsbestand zu Beginn des Backtests erreicht. Das Unternehmen hatte Mühe, aus einer Reihe von unrentablen Handelsgeschäften auszubrechen, die Ende 2020 begannen und vier Jahre lang bis 2024 andauerten. Dies ist für uns als algorithmische Händler nicht attraktiv. 

Abb. 8: Visualisierung der Gewinn- und Verlustkurve, die durch unsere diskretionäre Version der Handelsstrategie erzeugt wird


Die Leistung unseres Expert Advisors verbessern

Verbessern wir nun unsere Handelsstrategie, indem wir unserer Anwendung die Fähigkeit geben, den menschlichen Denkprozess zu imitieren, bei dem man die Konsequenzen seiner Handlungen bedenkt, bevor man sich festlegt. 

Wir beginnen damit, dass wir unser ONNX-Modell als Ressource aus unserem Systemdateiverzeichnis in die Anwendung importieren. 

//+------------------------------------------------------------------+
//| System resources                                                 |
//+------------------------------------------------------------------+
#resource "\\Files\\EURUSD Reward Model.onnx" as uchar onnx_proto[];

Für die Anwendung unseres Modells werden einige zusätzliche globale Variablen benötigt. In erster Linie benötigen wir Variablen, die den Model-Handler und die Datenmenge darstellen, die wir abrufen sollen.

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
long onnx_model;
int fetch = HORIZON + 1;

Nun müssen wir das ONNX-Modell in der Setup-Funktion einrichten. Im folgenden Codebeispiel haben wir absichtlich Codesegmente weggelassen, die sich in beiden Versionen der Anwendung nicht geändert haben. Wir erstellen einfach das ONNX-Modell aus seinem Puffer, validieren das Modell und legen dann seine Eingabe- und Ausgabegröße entsprechend fest. Sollte ein Schritt auf diesem Weg fehlschlagen, brechen wir die Initialisierungsprozedur vollständig ab.

//+------------------------------------------------------------------+
//| Setup the system                                                 |
//+------------------------------------------------------------------+
bool setup(void)
  {
//---Omitted code that hasn't changed

//--- Setup the ONNX model
   onnx_model = OnnxCreateFromBuffer(onnx_proto,ONNX_DEFAULT);

//--- Validate the ONNX model
   if(onnx_model == INVALID_HANDLE)
     {
      Comment("Failed to create ONNX model");
      return(false);
     }

//--- Register the ONNX model I/O parameters
   ulong input_shape[] = {1,6};
   ulong output_shape[] = {1,1};

   if(!OnnxSetInputShape(onnx_model,0,input_shape))
     {
      Comment("Failed to set input shape");
      return(false);
     }

   if(!OnnxSetOutputShape(onnx_model,0,output_shape))
     {
      Comment("Failed to set output shape");
      return(false);
     }

   return(true);
  }

Außerdem müssen wir, wenn die Anwendung freigegeben wird, die nicht mehr benötigten Systemressourcen freigeben

//+------------------------------------------------------------------+
//| Release system variables we are no longer using                  |
//+------------------------------------------------------------------+
void release(void)
  {
//--- Omitted code segments that haven't changed
   OnnxRelease(onnx_model);
   return;
  }

Wir werden die Anwendung anweisen, zunächst eine Prognose von unserem Handelsmodell einzuholen, bevor sie entscheidet, ob sie ein Geschäft tätigen soll. Wenn die Vorhersage unseres Modells über 0,5 liegt, bedeutet dies, dass unser Algorithmus davon ausgeht, dass das von unserer Strategie erzeugte Signal gewinnbringend ist, und uns die Erlaubnis zum Handeln erteilt. Wenn das nicht der Fall ist, werden wir abwarten, bis die ungünstigen Marktbedingungen ihren Lauf nehmen.

//+------------------------------------------------------------------+
//| Find a trading oppurtunity                                       |
//+------------------------------------------------------------------+
void find_setup(void)
  {
//--- Skipped parts of the code base that haven't changed

//--- Prepare the model's inputs
   vectorf model_input(6);
   model_input[0] = (float)(iOpen(_Symbol,PERIOD_CURRENT,0)   - iOpen(_Symbol,PERIOD_CURRENT,(HORIZON)));
   model_input[1] = (float)(iHigh(_Symbol,PERIOD_CURRENT,0)   - iHigh(_Symbol,PERIOD_CURRENT,(HORIZON)));
   model_input[2] = (float)(iLow(_Symbol,PERIOD_CURRENT,0)    - iLow(_Symbol,PERIOD_CURRENT,(HORIZON)));
   model_input[3] = (float)(iClose(_Symbol,PERIOD_CURRENT,0)  - iClose(_Symbol,PERIOD_CURRENT,(HORIZON)));
   model_input[4] = (float)(ma_reading[0] - ma_reading[(HORIZON)]);
   model_input[5] = (float)(ma_o_reading[0] - ma_o_reading[(HORIZON)]);

//--- Prepare the model's output
   vectorf model_output(1);

//--- We failed to run the model
   if(!OnnxRun(onnx_model,ONNX_DATA_TYPE_FLOAT,model_input,model_output))
      Comment("Failed to obtain a forecast");

//--- Everything went fine
   else
     {
      Comment("Forecast: ",model_output[0]);

      //--- Our model forecasts that our strategy is likely to be profitable
      if(model_output[0] > 0.5)
        {
         if(ma_o[0] > ma_c[0])
           {
            Trade.Sell(vol,Symbol(),ask,0,0,"");
           }

         if(ma_o[0] < ma_c[0])
           {
            Trade.Buy(vol,Symbol(),bid,0,0,"");
           }
        }
     }
   return;
  }

Führen wir die Anwendung in einem Backtest mit den in Abb. 4 und 5 angegebenen Einstellungen aus, um einen fairen Vergleich zwischen den beiden Strategien zu ermöglichen.

Backtest-Eingänge

Abb. 9: Durchführung unseres zweiten Backtests mit unserer überarbeiteten Version der Handelsstrategie, die Gewinne und Verluste modelliert

Unsere Sharpe Ratio stieg von 0,62 in der diskretionären Benchmark, die dieselbe Strategie anwendet, auf 1,07 in unserer aktuellen Iteration, was einer Steigerung von 72 % entspricht. Unser Gesamtnettogewinn stieg um 38 % von 117,13 $ auf 162,75 $, wenn wir die neue, algorithmisch definierte Strategie des „High Probability Setup“ anwenden. Der Anteil der Verlustgeschäfte sank um 17 % von 48,78 % auf 40,48 %. Und schließlich sank die Gesamtzahl der von der Strategie platzierten Handelsgeschäfte von 164 auf 126, was bedeutet, dass unser neues System mit nur 76 % der Gesamtzahl der vom alten System verwendeten Handelsgeschäfte 38 % mehr Gewinn erzielt hat, was effektiv bedeutet, dass wir effektiver sind als zuvor, weil wir höhere Renditen erzielen, während wir weniger Risiko eingehen.

Abb. 10: Eine detaillierte Zusammenfassung der Ergebnisse unserer neuen Handelsstrategie

Ich habe dem Leser die Salden- und Kapitalkurve der überarbeiteten Version unserer Handelsstrategie zur Verfügung gestellt und die Kurve der ursprünglichen Version der Strategie darunter gelegt, damit der Leser Vergleiche anstellen kann, ohne hin und her blättern zu müssen. Es ist deutlich zu erkennen, dass die ursprüngliche Version unserer Handelsstrategie noch vor Ablauf des ersten getesteten Jahres fast an den Breakeven-Punkt zurückfiel, während unsere neue Strategie diesen Zeitraum gut bewältigte. 

Interessant ist, dass beide Strategien in der Zeit von Mai bis Dezember 2022 nur mäßige Ergebnisse erzielten. Dies kann ein Anzeichen für besonders instabile Marktphasen sein, die mehr Anstrengungen erfordern, um wirksam angegangen zu werden.

Abb. 11: Die Gewinn- und Verlustkurve unserer neuen Handelsstrategie, die in der Lage ist, die Konsequenzen ihres Handelns zu berücksichtigen

Abb. 12: Die Kapitalkurve, die von der ursprünglichen Version unserer Handelsstrategie erzeugt wurde, wurde zum leichteren Vergleich mit den neuen Ergebnissen, die wir erzielt haben, kopiert


Schlussfolgerung

Nach der Lektüre dieses Artikels hat der Leser eine neuartige Methode kennengelernt, um die Aufgabe des algorithmischen Handels mit überwachten statistischen Modellen anzugehen. Der Leser verfügt nun über das notwendige Wissen, um die Beziehungen zwischen seinen privaten Handelsstrategien und den von ihm gehandelten Märkten zu modellieren. Dies verschafft dem Leser einen Wettbewerbsvorteil gegenüber gelegentlichen Marktteilnehmern, die versuchen, den Markt direkt zu prognostizieren, was, wie wir gezeigt haben, nicht immer die beste Option für den Leser ist. 

Dateiname  Beschreibung der Datei
Reward Modelling Benchmark.mq5 Dies ist die traditionelle Version unserer Handelsstrategie, die nicht versucht, die Konsequenzen ihres Handelns zu berücksichtigen. Sie gewichtet alle Handelsmöglichkeiten gleich und geht davon aus, dass jeder Handel profitabel sein muss.
Reward Modelling.mq5 Dies ist die verfeinerte Version unserer Handelsstrategie, die explizit versucht, die Konsequenzen ihrer Handlungen abzuschätzen, bevor sie einen Handel tätigt.
EURUSD Reward Model.onnx Unser statistisches ONNX-Modell, das die Wahrscheinlichkeit schätzt, dass das von unserer Strategie erzeugte Signal profitabel sein wird.
Reward Modelling.ipynb Das Jupyter Notebook, mit dem wir unsere historischen Marktdaten analysiert und unser statistisches Modell angepasst haben.

Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/17756

Entwicklung des Price Action Analysis Toolkit (Teil 21): Das Tool Market Structure Flip Detector Entwicklung des Price Action Analysis Toolkit (Teil 21): Das Tool Market Structure Flip Detector
Der Market Structure Flip Detector Expert Advisor (EA) agiert als Ihr aufmerksamer Partner, der ständig die Veränderungen der Marktstimmung beobachtet. Durch die Verwendung von Average True Range (ATR)-basierten Schwellenwerten erkennt es effektiv Strukturumkehrungen und kennzeichnet jedes höhere Tief und niedrigere Hoch mit klaren Indikatoren. Dank der schnellen Ausführung und der flexiblen API von MQL5 bietet dieses Tool eine Echtzeitanalyse, die die Anzeige für eine optimale Lesbarkeit anpasst und ein Live-Dashboard zur Überwachung der Anzahl und des Timings von Flips bereitstellt. Darüber hinaus sorgen anpassbare Ton- und Push-Benachrichtigungen dafür, dass Sie über kritische Signale informiert bleiben, sodass Sie sehen können, wie einfache Eingaben und Hilfsroutinen Kursbewegungen in umsetzbare Strategien verwandeln können.
Manuelle Backtest leicht gemacht: Aufbau eines nutzerdefinierten Toolkits für Strategietester in MQL5 Manuelle Backtest leicht gemacht: Aufbau eines nutzerdefinierten Toolkits für Strategietester in MQL5
In diesem Artikel entwickeln wir ein nutzerdefiniertes MQL5-Toolkit für einfache manuelle Backtests im Strategy Tester. Wir erläutern den Aufbau und die Umsetzung des Systems und konzentrieren uns dabei auf interaktive Handelskontrollen. Wir zeigen dann, wie man damit Strategien effektiv testen kann
Formulierung eines dynamischen Multi-Pair EA (Teil 2): Portfolio-Diversifizierung und -Optimierung Formulierung eines dynamischen Multi-Pair EA (Teil 2): Portfolio-Diversifizierung und -Optimierung
Portfolio-Diversifizierung und -Optimierung sorgt für eine strategische Streuung der Anlagen auf mehrere Vermögenswerte, um das Risiko zu minimieren und gleichzeitig die ideale Mischung von Vermögenswerten auszuwählen, um die Renditen auf der Grundlage risikobereinigter Performance-Kennzahlen zu maximieren.
Handel mit dem MQL5 Wirtschaftskalender (Teil 7): Vorbereitung auf Strategietests mit der ressourcenbasierten Analyse von Nachrichtenereignissen Handel mit dem MQL5 Wirtschaftskalender (Teil 7): Vorbereitung auf Strategietests mit der ressourcenbasierten Analyse von Nachrichtenereignissen
In diesem Artikel bereiten wir unser MQL5-Handelssystem für Strategietests vor, indem wir Wirtschaftskalenderdaten als Ressource für nicht-live Analysen einbinden. Wir implementieren das Laden von Ereignissen und die Filterung nach Zeit, Währung und Auswirkung und validieren sie dann im Strategy Tester. Dies ermöglicht effektive Backtests von nachrichtengesteuerten Strategien.