English 日本語
preview
Die Grenzen des maschinellen Lernens überwinden (Teil 4): Überwindung des irreduziblen Fehlers durch mehrere Prognosehorizonte

Die Grenzen des maschinellen Lernens überwinden (Teil 4): Überwindung des irreduziblen Fehlers durch mehrere Prognosehorizonte

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

Maschinelles Lernen ist ein sehr weites Feld, das aus vielen verschiedenen Perspektiven untersucht und interpretiert werden kann. Gerade diese Breite macht es für jeden von uns zu einer großen Herausforderung, sie zu meistern. In unserer Artikelserie haben wir einiges zum maschinellen Lernen aus statistischer Sicht oder aus der Perspektive der linearen Algebra behandelt. Der geometrischen Interpretation von Modellen des maschinellen Lernens wird jedoch nur selten Aufmerksamkeit geschenkt. Traditionell werden Modelle des maschinellen Lernens als Approximation einer Funktion beschrieben, die Eingaben auf Ausgaben abbildet. Aus geometrischer Sicht ist dies jedoch unvollständig.

Was Modelle tatsächlich tun, ist, Bilder des Ziels in den durch die Eingaben definierten Raum einzubetten, sodass sie in Zukunft versuchen können, das Ziel nur mit diesen Eingaben zu beschreiben. Dabei definiert das Modell einen neuen Verteiler aus den Eingabedaten und macht Vorhersagen für diesen neuen Verteiler. Aber das eigentliche Ziel lebt von seiner eigenen Vielfältigkeit. Diese Fehlausrichtung führt zu einer subtilen, aber unvermeidlichen Form des irreduziblen Fehlers: Das Modell ist nie wirklich auf das Ziel ausgerichtet, sondern kann nur auf eine bestimmte Kombination der Eingaben zeigen.

Ein Gedankenexperiment kann helfen. Stellen Sie sich vor, Sie erhalten Aufzeichnungen der Geschwindigkeiten von zwei Autos und sollen beurteilen, welches schneller ist. Das ist einfach – bis Sie feststellen, dass die Geschwindigkeit des einen Autos in Meilen pro Minute und die des anderen in Kilometern pro Stunde gemessen wird. Ihre Urteile werden unzuverlässig, weil die Messungen in unterschiedlichen Einheiten erfolgen. Ebenso werden die Vorhersagen des Modells in einem anderen Koordinatensystem ausgedrückt als dem, in dem das tatsächliche Ziel lebt. Das heißt, dass das Modell seine eigenen Einheiten im laufenden Betrieb erstellen kann.

In Fällen, in denen das Ziel wirklich innerhalb der Spanne der Eingänge liegt, kann der Ausrichtungsfehler fast gleich 0 sein. Im Handel kann es jedoch teuer werden, sie zu ignorieren. Modelle des maschinellen Lernens führen hinter den Kulissen Koordinatentransformationen durch und versetzen uns in ein anderes Koordinatensystem als das des Ziels. Anders als in den Naturwissenschaften haben wir auf den Finanzmärkten keine Garantie, dass unsere Inputs das Ziel perfekt erklären. Hier arbeiten wir teilweise blind.

In unserer Serie über selbstoptimierende Expertenberater haben wir erörtert, wie lineare Regressionsmodelle mithilfe der Matrixfaktorisierung konstruiert werden können, die OpenBLAS-Bibliothek vorgestellt und die Singulärwertzerlegung (SVD) erläutert. Leser, die mit dieser Diskussion nicht vertraut sind, sollten sie nachlesen, da der vorliegende Artikel auf dieser Grundlage aufbaut; ein Link ist hier angegeben.

Die SVD faktorisiert eine Matrix in drei kleinere Matrizen, wie Sie wissen: U, S und VT. Jede hat besondere geometrische Eigenschaften. U und VT sind orthogonale Matrizen, d. h. sie stellen Drehungen oder Spiegelungen der ursprünglichen Daten dar – und entscheidend ist, dass sie Vektoren nicht strecken, sondern nur die Richtung ändern. S, die mittlere Matrix, ist diagonal und skaliert die Datenwerte. 

Zusammengenommen kann SVD als eine Abfolge von Rotation, Skalierung und Drehung der Daten verstanden werden. Auf diese Weise betten lineare Regressionsmodelle Bilder des Ziels in den Raum der Eingaben ein. Wenn wir also die lineare Regression auf ihr geometrisches Wesen reduzieren, ist sie nichts anderes als Rotation, Skalierung und nochmals Rotation. Mehr nicht. Das war's. Drehen, Skalieren, Drehen. Wenn man Geometrie studiert, lernt man, sie so zu sehen, aber wenn man das tut, stellt sich eine provokante Frage: Wo findet das ganze „Lernen“ wirklich statt?

Die Antwort ist beunruhigend. Was die Praktiker als „Lernen“ bezeichnen, ist in Wirklichkeit nichts anderes als das Ausrichten von Koordinatensystemen und die Neuskalierung von Achsen, damit das Ziel in den Bereich der Eingaben projiziert werden kann. Wir decken keine versteckten Wahrheiten in den Daten auf. Wir wenden eine Reihe von geometrischen Transformationen an, bis zwei Mannigfaltigkeiten gerade so weit übereinstimmen, dass die Vorhersagen vernünftig aussehen.

Die SVD ist der Prozess, durch den das neue Koordinatensystem erzeugt wird. Bei der linearen Regression werden die Eingabedaten auf eine Reihe von orthogonalen Achsen projiziert, skaliert und zurückgedreht, sodass ein transformierter Raum entsteht, in dem das Ziel so genau wie möglich angenähert werden kann. Das „Lernen“ des Modells besteht eigentlich nur in der Ausrichtung des Ziels auf dieses neue Koordinatensystem. 

Auf der Grundlage dieses geometrischen Rahmens können wir Maßnahmen und bereichsgebundene bewährte Praktiken motivieren, die sonst unbegründet erscheinen würden. Die wichtigste Erkenntnis ist, dass wir aufhören müssen, direkte Vergleiche zwischen den Vorhersagen eines Modells und dem tatsächlichen Wert des Ziels anzustellen. Stattdessen sollten wir die Vorhersagen des Modells miteinander vergleichen, und zwar für verschiedene Zeithorizonte.

Nehmen wir zum Beispiel an, das Modell sagt voraus, dass der Schlusskurs einen Schritt voraus bei 5 $ und zehn Schritte voraus bei 15 $ liegen wird. Die Steigung zwischen diesen Prognosen ist positiv, also kaufen wir. Wenn die Steigung negativ ist, verkaufen wir. Wir erwarten nicht mehr, dass die Vorhersagen perfekt mit der Realität übereinstimmen – da dies aufgrund vielfältiger Abweichungen für immer unmöglich sein kann – und handeln stattdessen aufgrund der relativen Steigung der Vorhersagen. Dieses mehrstufige Vorhersageformat ist für den algorithmischen Handel nicht neu. Vielmehr möchte dieser Artikel dem Leser vermitteln, dass mehrstufige Vorhersagen der DeFacto-Goldstandard beim Einsatz von maschinellen Lernmodellen sein sollten. 

Dieser Artikel erhebt keinen Anspruch darauf, den geometrischen Fehler zu verringern oder zu beseitigen. Stattdessen lehrt sie uns, wie wir unsere Interaktion mit ihm minimieren können, indem wir uns außerhalb des Bereichs aufhalten, in dem der Fehler dominiert.

Bei unserer Methodik begannen wir mit einem Modell für maschinelles Lernen, das auf neun verschiedene Ziele trainiert wurde. Diese Ziele setzten sich aus dem gleitenden Durchschnitt des Schlusskurses, des Hochs und des Tiefs bei 1, 5 und 10 Tageskerzen in der Zukunft zusammen. Das Kontrollsystem folgte dem klassischen Ansatz: Vorhersage des realen Wertes des Ziels 1 Kerze in der Zukunft, Vergleich mit dem aktuellen Wert des Ziels und entsprechender Handel. Wie der Leser sehen wird, haben wir diese klassische Methode wiederholt übertroffen, indem wir auf direkte Vergleiche verzichtet und stattdessen die eigenen Vorhersagen des Modells über mehrere Zeithorizonte hinweg verglichen haben. Der Grundgedanke ist einfach, aber wirkungsvoll: Die Vorhersagen unseres Modells können für uns profitabler sein, wenn wir sie mit sich selbst vergleichen, als wenn wir sie mit dem tatsächlichen Ziel vergleichen.

Wir haben das Kontrollsystem anhand von 3 Jahren historischer Daten getestet, die von März 2022 bis Mai 2025 reichen. Das Kontrollsystem erzielte in diesem Zeitraum einen Nettogewinn von 71 $. Indem wir lediglich die Art und Weise, wie wir die Vorhersagen des Modells interpretieren, geändert haben, konnten wir den Nettogewinn auf 180 $ steigern, was einer Verbesserung des Gewinns um 153 % entspricht. Unsere Sharpe Ratio stieg von 0,45 auf 2,16 und der Prozentsatz profitabler Handelsgeschäfte von 46% auf 65%, was einer Verbesserung der Handelsgenauigkeit um 41% entspricht. 

Das Wichtigste ist, dass alle Verbesserungen, die wir dem Leser jetzt zeigen werden, durchgeführt werden können, ohne dass das Modell, von dem wir abhängig sind, ausgetauscht werden muss, und dass sie auf jedes andere maschinelle Lernmodell, das der Leser bereits kennt, ausgedehnt werden können.



Abrufen der Marktdaten

Wir beginnen mit dem Schreiben eines MQL5-Skripts, um die benötigten historischen Marktdaten abzurufen. Die historischer Marktdaten von Ihrem MetaTrader 5-Terminal abzurufen, ist die beste Vorgehensweise, da sie sicherstellt, dass unsere ONNX-Modelle auf historischen Daten trainiert werden, die mit der endgültigen Einsatzumgebung übereinstimmen. Unser MQL5-Skript holt detaillierte Aufzeichnungen über die vier vorherrschenden Preisniveaus und ihre gleitenden Durchschnitte ab. Wir achten auch auf das Wachstum jedes dieser Preisniveaus im Vergleich zu ihren früheren Niveaus 5 Schritte in der Vergangenheit. Alle diese Daten werden im CSV-Format auf Ihrer Festplatte gespeichert.

//+------------------------------------------------------------------+
//|                                                      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 5                 //--- Moving Average Period
#define MA_TYPE   MODE_SMA          //--- Type of moving average we have
#define HORIZON   5                 //--- Forecast horizon

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

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

//--- File name
string file_name = Symbol() + " Detailed Market Data As Series Moving Average.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);
   ma_h_handle = iMA(_Symbol,PERIOD_CURRENT,MA_PERIOD,0,MA_TYPE,PRICE_HIGH);
   ma_l_handle = iMA(_Symbol,PERIOD_CURRENT,MA_PERIOD,0,MA_TYPE,PRICE_LOW);

//---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);
   CopyBuffer(ma_h_handle,0,0,fetch,ma_h_reading);
   ArraySetAsSeries(ma_h_reading,true);
   CopyBuffer(ma_l_handle,0,0,fetch,ma_l_reading);
   ArraySetAsSeries(ma_l_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",
                   //--- OHLC
                   "True Open",
                   "True High",
                   "True Low",
                   "True Close",
                   //--- MA OHLC
                   "True MA C",
                   "True MA O",
                   "True MA H",
                   "True MA L",
                   //--- Growth in OHLC
                   "Diff Open",
                   "Diff High",
                   "Diff Low",
                   "Diff Close",
                   //--- Growth in MA OHLC
                   "Diff MA Close 2",
                   "Diff MA Open 2",
                   "Diff MA High 2",
                   "Diff MA Low 2"
                  );
        }

      else
        {
         FileWrite(file_handle,
                   iTime(_Symbol,PERIOD_CURRENT,i),
                   //--- OHLC
                   iClose(_Symbol,PERIOD_CURRENT,i),
                   iOpen(_Symbol,PERIOD_CURRENT,i),
                   iHigh(_Symbol,PERIOD_CURRENT,i),
                   iLow(_Symbol,PERIOD_CURRENT,i),
                   //--- MA OHLC
                   ma_reading[i],
                   ma_o_reading[i],
                   ma_h_reading[i],
                   ma_l_reading[i],
                   //--- Growth in OHLC
                   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)),
                   //--- Growth in MA OHLC
                   ma_reading[i] - ma_reading[(i + HORIZON)],
                   ma_o_reading[i] - ma_o_reading[(i + HORIZON)],
                   ma_h_reading[i] - ma_h_reading[(i + HORIZON)],
                   ma_l_reading[i] - ma_l_reading[(i + HORIZON)]
                  );
        }
     }
//--- Close the file
   FileClose(file_handle);
  }
//+------------------------------------------------------------------+

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


Analyse der Marktdaten

Wir können nun unsere historischen Marktdaten einlesen. Laden wir zunächst einige Python-Bibliotheken zur Datenmanipulation.

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

Nun werden wir 3 eindeutige Zeithorizonte definieren, die wir prognostizieren wollen.

#Define our different forecast horizons
H1 = 1
H2 = 5
H3 = 10

Wir werden nun die Marktdaten lesen, die wir von unserem Terminal exportiert haben, und Ziele für die gleitenden Durchschnitte für verschiedene Zeithorizonte erstellen.

#Read in the data
data = pd.read_csv('../EURUSD Detailed Market Data As Series Moving Average.csv')

#Label the data
data['Target 1'] = data['True MA C'].shift(-H1)
data['Target 2'] = data['True MA C'].shift(-H2)
data['Target 3'] = data['True MA C'].shift(-H3)
data['Target 4'] = data['True MA H'].shift(-H1)
data['Target 5'] = data['True MA H'].shift(-H2)
data['Target 6'] = data['True MA H'].shift(-H3)
data['Target 7'] = data['True MA L'].shift(-H1)
data['Target 8'] = data['True MA L'].shift(-H2)
data['Target 9'] = data['True MA L'].shift(-H3)

#Drop missing rows
data = data.iloc[:-H3,:]

Wir erstellen eine Trainingspartition

data      = data.iloc[:-(365*3),:]
data_test = data.iloc[-(365*3):,:]

und trennen die Eingaben und die Ziele.

X = data.iloc[:,1:-9]
y = data.iloc[:,-9:]

Wir laden ein beliebiges maschinelles Lernmodell Ihrer Wahl. Für unsere Diskussion werden wir die Sklearn-Bibliothek verwenden und die Prinzipien anhand eines linearen Modells demonstrieren.

from sklearn.linear_model import LinearRegression

Initialisieren des Modells.

model = LinearRegression()

Passen wir das Modell an.

model.fit(X,y)

Ermitteln wir die Vorhersagen des Modells auf der Testmenge, aber passen das Modell nicht auf die Testmenge an. Wie wir sehen können, scheinen die Vorhersagen des Modells gut mit dem Ziel übereinzustimmen, aber wie wir jetzt sehen werden, kann unser Modell noch besser abschneiden.

preds = pd.DataFrame(model.predict(data_test.iloc[:,1:-9]))
plt.plot(data_test.iloc[:,-9].reset_index(drop=True),color='black')
plt.plot(preds.iloc[:,0],color='red',linestyle=':')
plt.grid()
plt.title("Out Of Sample Forecasting")
plt.ylabel('EUR/USD Exchange Rate')
plt.xlabel('Time')
plt.legend(['Actual Price','Forecasted Price'])

Abbildung 1: Die Vorhersagen des Modells außerhalb der Stichprobe scheinen mit der realen Zielvorgabe übereinzustimmen, aber dieses Leistungsniveau kann noch übertroffen werden

ONNX steht für Open Neural Network Exchange und ermöglicht es uns, Modelle für maschinelles Lernen in einer standardisierten Bibliothek zu erstellen und einzusetzen, die von einer ständig wachsenden Zahl von Programmiersprachen übernommen wird. Wir werden die ONNX-Bibliothek verwenden, um unser maschinelles Lernmodell aus Python zu exportieren und es anschließend in MQL5 zu importieren. ONNX ermöglicht es uns, Modelle für maschinelles Lernen schnell zu entwickeln und einzusetzen.

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

Wir müssen die Eingangs- und Ausgangsform unserer ONNX-Modelle definieren. Dies ist leicht möglich, da wir zuvor Ein- und Ausgänge getrennt haben. Holen Sie einfach die Anzahl der Spalten in jeder Partition und speichern Sie sie. Pandas macht das Abrufen dieser Informationen durch die Verwendung der shape-Eigenschaft trivial.

initial_types = [("FLOAT INPUT",FloatTensorType([1,X.shape[1]]))]
final_types = [("FLOAT OUTPUT",FloatTensorType([y.shape[1],1]))]

Erstellen Sie einen ONNX-Prototyp des maschinellen Lernmodells. Wir werden die Anzahl der Eingänge und Ausgänge angeben, die wir für unser Modell benötigen.

model_proto = convert_sklearn(model,initial_types=initial_types,target_opset=12)

Speichern Sie das ONNX-Modell auf der Festplatte.

onnx.save(model_proto,"EURUSD MFH LR D1.onnx")



Festlegung eines grundlegenden Leistungsniveaus

Wir sind nun in der Lage, ein Basisniveau der Leistung festzulegen. Wir beginnen damit, so viele Parameter der Strategie wie möglich festzulegen, um konsistente Tests zu gewährleisten.

//+------------------------------------------------------------------+
//|                                                          MFH.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"

//+------------------------------------------------------------------+
//| System definitions                                               |
//+------------------------------------------------------------------+
#define SYSTEM_INPUTS  16
#define SYSTEM_OUTPUTS 9
#define ATR_PERIOD     14
#define ATR_PADDING    1
#define TF_1           PERIOD_D1
#define TF_2           PERIOD_M15
#define MA_PERIOD      5
#define MA_TYPE        MODE_SMA
#define HORIZON        5

Laden Sie den ONNX-Puffer, den wir aus Python exportiert haben.

//+------------------------------------------------------------------+
//| System resources                                                 |
//+------------------------------------------------------------------+
#resource "\\Files\\EURUSD MFH LR D1.onnx" as const uchar onnx_buffer[];

Wir werden auch auf einige Bibliotheken zurückgreifen, um Routineaufgaben für unseren algorithmischen Handel zu erledigen, wie z. B. die Handelsausführung, die Kerzenbildung und die Handhabung des ONNX-Puffers.

//+------------------------------------------------------------------+
//| System libraries                                                 |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
#include <VolatilityDoctor\ONNX\ONNXFloat.mqh>
#include <VolatilityDoctor\Time\Time.mqh>
#include <VolatilityDoctor\Trade\TradeInfo.mqh>

ONNXFloat      *ONNXHandler;
Time           *TimeHandler;
Time           *LowerTimeHandler;
TradeInfo      *TradeHandler;
CTrade         Trade;

Eine Handvoll globaler Variablen sind notwendig, hauptsächlich für die Handhabung der technischen Indikatoren und die Speicherung der Vorhersagen des ONNX-Modells.

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
int ma_handle,ma_o_handle,ma_h_handle,ma_l_handle,fetch,atr_handler;
double ma_reading[],ma_o_reading[],ma_h_reading[],ma_l_reading[],atr[];
double padding;
vector model_prediction;

Wenn unsere Anwendung zum ersten Mal geladen wird, werden wir unsere globalen Variablen und nutzerdefinierten Klassen initialisieren und auch Handler für die von uns erstellten technischen Indikatoren speichern.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   fetch            = HORIZON * 2;
   ONNXHandler      = new ONNXFloat(onnx_buffer);
   LowerTimeHandler = new Time(Symbol(),TF_2);
   TimeHandler      = new Time(Symbol(),TF_1);
   TradeHandler     = new TradeInfo(Symbol(),TF_1);

   ONNXHandler.DefineOnnxInputShape(0,1,SYSTEM_INPUTS);
   ONNXHandler.DefineOnnxOutputShape(0,1,SYSTEM_OUTPUTS);

   ma_handle        = iMA(Symbol(),TF_1,MA_PERIOD,0,MA_TYPE,PRICE_CLOSE);
   ma_o_handle      = iMA(Symbol(),TF_1,MA_PERIOD,0,MA_TYPE,PRICE_OPEN);
   ma_h_handle      = iMA(Symbol(),TF_1,MA_PERIOD,0,MA_TYPE,PRICE_HIGH);
   ma_l_handle      = iMA(Symbol(),TF_1,MA_PERIOD,0,MA_TYPE,PRICE_LOW);
   atr_handler      = iATR(Symbol(),TF_1,MA_PERIOD);

   model_prediction = vector::Zeros(SYSTEM_OUTPUTS);
//---
   return(INIT_SUCCEEDED);
  }

Es ist eine gängige Praxis in MQL5, eine gute Speicherverwaltung zu praktizieren und Ressourcen, die nicht mehr benötigt werden, freizugeben.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   delete ONNXHandler;
   IndicatorRelease(ma_h_handle);
   IndicatorRelease(ma_o_handle);
   IndicatorRelease(ma_l_handle);
   IndicatorRelease(ma_handle);
   IndicatorRelease(atr_handler);
  }

Sobald wir neue Preisniveaus erhalten haben, werden wir unsere technischen Indikatorpuffer und Stop-Loss-Niveaus aktualisieren. Danach werden wir prüfen, ob wir noch offene Positionen haben. Wenn keine offen sind, prüfen wir, ob sich eine Handelsmöglichkeit ergibt, andernfalls verwalten wir die offene Position mit einem Trailing Stop Loss.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Has a new candle formed
   if(TimeHandler.NewCandle())
     {
      //---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);
      CopyBuffer(ma_h_handle,0,0,fetch,ma_h_reading);
      ArraySetAsSeries(ma_h_reading,true);
      CopyBuffer(ma_l_handle,0,0,fetch,ma_l_reading);
      ArraySetAsSeries(ma_l_reading,true);
      CopyBuffer(atr_handler,0,0,fetch,atr);
      ArraySetAsSeries(atr,true);
      padding = (atr[0] * ATR_PADDING);

      //--- Obtain a prediction from our model
      if(PositionsTotal() == 0)
        {
         model_predict();
        }

      //--- Manage open positions
      if(PositionsTotal() >0)
        {
         manage_setup();
        }
     }
  }
//+------------------------------------------------------------------+

Unser Trailing-Stop wird durch den ATR-Indikator (Average True Range) definiert. Die ATR misst die Marktvolatilität und hilft uns, unser Risikoniveau dynamisch anzupassen. Wenn der Stop-Loss sicher auf eine profitablere Position aktualisiert werden kann, werden wir dies tun, andernfalls warten wir.

//+------------------------------------------------------------------+
//| Manage our open positions                                        |
//+------------------------------------------------------------------+
void manage_setup(void)
  {
//--- Select the position by its ticket number
   if(PositionSelectByTicket(PositionGetTicket(0)))
     {
      //--- Store the current tp and sl levels
      double current_tp,current_sl;

      current_tp = PositionGetDouble(POSITION_TP);
      current_sl = PositionGetDouble(POSITION_SL);

      //--- Before we calculate the new stop loss or take profit
      double new_sl,new_tp;

      //--- We first check the position type
      if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
        {
         new_sl = TradeHandler.GetBid()-padding;
         new_tp = TradeHandler.GetBid()+padding;

         //--- Check if the new stops are more profitable
         if(new_sl>current_sl)
            Trade.PositionModify(Symbol(),new_sl,new_tp);
        }

      if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
        {
         new_sl = TradeHandler.GetAsk()+padding;
         new_tp = TradeHandler.GetAsk()-padding;

         //--- Check if the new stops are more profitable
         if(new_sl<current_sl)
            Trade.PositionModify(Symbol(),new_sl,new_tp);
        }
     }
  }

Wir werden unsere Anwendung zunächst ohne ein Modell des maschinellen Lernens testen, um ein Basisniveau für den Gewinn zu ermitteln. Wir werden eine einfache Ausbruchsstrategie für unsere maschinellen Lernmodelle anwenden, um eine bessere Leistung zu erzielen. Modelle, die unter dieses Leistungsniveau fallen, sind inakzeptabel. 

//+------------------------------------------------------------------+
//| Obtain a prediction from our model                               |
//+------------------------------------------------------------------+
void model_predict(void)
  {
   if(iHigh(Symbol(),TF_2,1)<iOpen(Symbol(),TF_2,0))
     {
      Trade.Buy(TradeHandler.MinVolume(),TradeHandler.GetSymbol(),TradeHandler.GetAsk(),TradeHandler.GetBid()-padding,TradeHandler.GetBid()+padding);
     }


   if(iLow(Symbol(),TF_2,1)>iOpen(Symbol(),TF_2,0))
     {
      Trade.Sell(TradeHandler.MinVolume(),TradeHandler.GetSymbol(),TradeHandler.GetBid(),TradeHandler.GetAsk()+padding,TradeHandler.GetAsk()-padding);
     }
  }
//+------------------------------------------------------------------+

Schließlich müssen Sie alle zuvor erstellten Systemkonstanten zurücksetzen.

//+------------------------------------------------------------------+
//| Undefine system constants                                        |
//+------------------------------------------------------------------+
#undef MA_PERIOD
#undef MA_TYPE
#undef HORIZON
#undef TF_1
#undef TF_2
#undef SYSTEM_INPUTS
#undef SYSTEM_OUTPUTS
#undef ATR_PADDING
#undef ATR_PERIOD

Alles in allem sieht unser System so aus.

//+------------------------------------------------------------------+
//|                                                          MFH.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"

//+------------------------------------------------------------------+
//| System definitions                                               |
//+------------------------------------------------------------------+
#define SYSTEM_INPUTS  16
#define SYSTEM_OUTPUTS 9
#define ATR_PERIOD     14
#define ATR_PADDING    1
#define TF_1           PERIOD_D1
#define TF_2           PERIOD_M15
#define MA_PERIOD      5
#define MA_TYPE        MODE_SMA
#define HORIZON        5

//+------------------------------------------------------------------+
//| System resources                                                 |
//+------------------------------------------------------------------+
#resource "\\Files\\EURUSD MFH LR D1.onnx" as const uchar onnx_buffer[];

//+------------------------------------------------------------------+
//| System libraries                                                 |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
#include <VolatilityDoctor\ONNX\ONNXFloat.mqh>
#include <VolatilityDoctor\Time\Time.mqh>
#include <VolatilityDoctor\Trade\TradeInfo.mqh>

ONNXFloat      *ONNXHandler;
Time           *TimeHandler;
Time           *LowerTimeHandler;
TradeInfo      *TradeHandler;
CTrade         Trade;

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
int ma_handle,ma_o_handle,ma_h_handle,ma_l_handle,fetch,atr_handler;
double ma_reading[],ma_o_reading[],ma_h_reading[],ma_l_reading[],atr[];
double padding;
vector model_prediction;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   fetch            = HORIZON * 2;
   ONNXHandler      = new ONNXFloat(onnx_buffer);
   LowerTimeHandler = new Time(Symbol(),TF_2);
   TimeHandler      = new Time(Symbol(),TF_1);
   TradeHandler     = new TradeInfo(Symbol(),TF_1);

   ONNXHandler.DefineOnnxInputShape(0,1,SYSTEM_INPUTS);
   ONNXHandler.DefineOnnxOutputShape(0,1,SYSTEM_OUTPUTS);

   ma_handle        = iMA(Symbol(),TF_1,MA_PERIOD,0,MA_TYPE,PRICE_CLOSE);
   ma_o_handle      = iMA(Symbol(),TF_1,MA_PERIOD,0,MA_TYPE,PRICE_OPEN);
   ma_h_handle      = iMA(Symbol(),TF_1,MA_PERIOD,0,MA_TYPE,PRICE_HIGH);
   ma_l_handle      = iMA(Symbol(),TF_1,MA_PERIOD,0,MA_TYPE,PRICE_LOW);
   atr_handler      = iATR(Symbol(),TF_1,MA_PERIOD);

   model_prediction = vector::Zeros(SYSTEM_OUTPUTS);
//---
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   delete ONNXHandler;
   IndicatorRelease(ma_h_handle);
   IndicatorRelease(ma_o_handle);
   IndicatorRelease(ma_l_handle);
   IndicatorRelease(ma_handle);
   IndicatorRelease(atr_handler);
  }

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Has a new candle formed
   if(TimeHandler.NewCandle())
     {
      //---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);
      CopyBuffer(ma_h_handle,0,0,fetch,ma_h_reading);
      ArraySetAsSeries(ma_h_reading,true);
      CopyBuffer(ma_l_handle,0,0,fetch,ma_l_reading);
      ArraySetAsSeries(ma_l_reading,true);
      CopyBuffer(atr_handler,0,0,fetch,atr);
      ArraySetAsSeries(atr,true);
      padding = (atr[0] * ATR_PADDING);
     }

   if(LowerTimeHandler.NewCandle())
     {
      //--- Obtain a prediction from our model
      if(PositionsTotal() == 0)
        {
         model_predict();
        }

      //--- Manage open positions
      if(PositionsTotal() >0)
        {
         manage_setup();
        }
     }
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Manage our open positions                                        |
//+------------------------------------------------------------------+
void manage_setup(void)
  {
//--- Select the position by its ticket number
   if(PositionSelectByTicket(PositionGetTicket(0)))
     {
      //--- Store the current tp and sl levels
      double current_tp,current_sl;

      current_tp = PositionGetDouble(POSITION_TP);
      current_sl = PositionGetDouble(POSITION_SL);

      //--- Before we calculate the new stop loss or take profit
      double new_sl,new_tp;

      //--- We first check the position type
      if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
        {
         new_sl = TradeHandler.GetBid()-padding;
         new_tp = TradeHandler.GetBid()+padding;

         //--- Check if the new stops are more profitable
         if(new_sl>current_sl)
            Trade.PositionModify(Symbol(),new_sl,new_tp);
        }

      if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
        {
         new_sl = TradeHandler.GetAsk()+padding;
         new_tp = TradeHandler.GetAsk()-padding;

         //--- Check if the new stops are more profitable
         if(new_sl<current_sl)
            Trade.PositionModify(Symbol(),new_sl,new_tp);
        }
     }
  }

//+------------------------------------------------------------------+
//| Obtain a prediction from our model                               |
//+------------------------------------------------------------------+
void model_predict(void)
  {
   if(iHigh(Symbol(),TF_2,1)<iOpen(Symbol(),TF_2,0))
     {
      Trade.Buy(TradeHandler.MinVolume(),TradeHandler.GetSymbol(),TradeHandler.GetAsk(),TradeHandler.GetBid()-padding,TradeHandler.GetBid()+padding);
     }


   if(iLow(Symbol(),TF_2,1)>iOpen(Symbol(),TF_2,0))
     {
      Trade.Sell(TradeHandler.MinVolume(),TradeHandler.GetSymbol(),TradeHandler.GetBid(),TradeHandler.GetAsk()+padding,TradeHandler.GetAsk()-padding);
     }
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Undefine system constants                                        |
//+------------------------------------------------------------------+
#undef MA_PERIOD
#undef MA_TYPE
#undef HORIZON
#undef TF_1
#undef TF_2
#undef SYSTEM_INPUTS
#undef SYSTEM_OUTPUTS
#undef ATR_PADDING
#undef ATR_PERIOD
//+------------------------------------------------------------------+

Wir beginnen damit, dass wir zunächst eine angemessene Gewinnerwartung aufstellen. Dieser Schritt ist von wesentlicher Bedeutung, um sicherzustellen, dass wir die Verbesserungen, die unsere Modelle des maschinellen Lernens tatsächlich bewirken, richtig einschätzen können.

Abbildung 2: Auswahl der Backtest-Tage für unser Kontrollsystem

Die detaillierten Ergebnisse des Kontrollsystems werden dem Leser im Folgenden vorgestellt. Wie wir sehen können, war die Mehrheit der von der Strategie platzierten Handelsgeschäfte unprofitabel, jedoch war der Durchschnitt der Handelsgeschäfte mit Gewinn größer als der mit Verlust. Diese asymmetrische Renditestruktur gab uns Vertrauen in das Kontrollsystem.

Abbildung 3: Analyse des Gewinns des Kontrollhandelsalgorithmus

Die von der Handelsstrategie erzeugte Kapitalkurve erscheint dagegen extrem volatil und gibt uns wenig Vertrauen, diese Handelsstrategie auch in Zukunft zu verfolgen. Daher werden wir nun versuchen, Modelle des maschinellen Lernens einzusetzen, um die volatilen Schwankungen in unserer derzeit einfachen Handelsstrategie auszugleichen.

Abbildung 4: Die ursprüngliche Version unserer Handelsstrategie weckt wenig Vertrauen in einen Entwickler


Klassischer Versuch, die Kontrolle zu umgehen

Wir werden nun versuchen, die Kontrollhandelsstrategie mit Hilfe des klassischen Handelskonzepts zu übertreffen. Normalerweise prognostizieren wir in der klassischen Einstellung das Ziel, 1 Kerze in die Zukunft und vergleichen dann den prognostizierten Preis mit dem realen Wert des Preises, um unsere Handelssignale zu erhalten. In diesem Artikel wird versucht, den Leser davon zu überzeugen, dass diese Praxis möglicherweise nicht die bestmögliche für unsere Gemeinschaft ist, und wir werden sehen, warum.  

Der größte Teil des Codes der Anwendung wird nicht absichtlich geändert, daher können wir uns ausschließlich auf den Teil des MQL5-Codes konzentrieren, der geändert werden muss, um unsere Ideen zu testen. Wie der Leser unten sehen kann, müssen wir nun die 16 Eingaben abrufen, die für unser ONNX-Modell erforderlich sind, um Vorhersagen zu treffen, und wir müssen sicherstellen, dass jede von ihnen in Float-Datentypen konvertiert wird, bevor wir irgendwelche Berechnungen durchführen. Anschließend erhalten wir eine Vorhersage von unserem ONNX-Modell und vergleichen sie mit dem tatsächlichen Wert des Ziels.

//+------------------------------------------------------------------+
//| Obtain a prediction from our model                               |
//+------------------------------------------------------------------+
void model_predict(void)
  {
   vectorf model_inputs(SYSTEM_INPUTS);

   model_inputs[0] = (float) iClose(_Symbol,PERIOD_CURRENT,0);
   model_inputs[1] = (float) iOpen(_Symbol,PERIOD_CURRENT,0);
   model_inputs[2] = (float) iHigh(_Symbol,PERIOD_CURRENT,0);
   model_inputs[3] = (float) iLow(_Symbol,PERIOD_CURRENT,0);
   model_inputs[4] = (float) ma_reading[0];
   model_inputs[5] = (float) ma_o_reading[0];
   model_inputs[6] = (float) ma_h_reading[0];
   model_inputs[7] = (float) ma_l_reading[0];
   model_inputs[8] = (float)(iOpen(_Symbol,PERIOD_CURRENT,0) - iOpen(_Symbol,PERIOD_CURRENT,(0 + HORIZON)));
   model_inputs[9] = (float)(iHigh(_Symbol,PERIOD_CURRENT,0)   - iHigh(_Symbol,PERIOD_CURRENT,(0 + HORIZON)));
   model_inputs[10] = (float)(iLow(_Symbol,PERIOD_CURRENT,0)    - iLow(_Symbol,PERIOD_CURRENT,(0 + HORIZON)));
   model_inputs[11] = (float)(iClose(_Symbol,PERIOD_CURRENT,0)  - iClose(_Symbol,PERIOD_CURRENT,(0 + HORIZON)));
   model_inputs[12] = (float)(ma_reading[0] - ma_reading[(0 + HORIZON)]);
   model_inputs[13] = (float)(ma_o_reading[0] - ma_o_reading[(0 + HORIZON)]);
   model_inputs[14] = (float)(ma_h_reading[0] - ma_h_reading[(0 + HORIZON)]);
   model_inputs[15] = (float)(ma_l_reading[0] - ma_l_reading[(0 + HORIZON)]);

//--- Obtain the prediction
   ONNXHandler.Predict(model_inputs);

   for(int i=0;i<SYSTEM_OUTPUTS;i++)
     {
      model_prediction[i] = ONNXHandler.GetPrediction(i);
     }

   if(iHigh(Symbol(),TF_2,1)<iOpen(Symbol(),TF_2,0))
     {
      if(model_prediction[0]>iClose(Symbol(),TF_2,0))
      Trade.Buy(TradeHandler.MinVolume(),TradeHandler.GetSymbol(),TradeHandler.GetAsk(),TradeHandler.GetBid()-padding,TradeHandler.GetBid()+padding);
     }


   if(iLow(Symbol(),TF_2,1)>iOpen(Symbol(),TF_2,0))
     {
      if(model_prediction[0]<iClose(Symbol(),TF_2,0)
      Trade.Sell(TradeHandler.MinVolume(),TradeHandler.GetSymbol(),TradeHandler.GetBid(),TradeHandler.GetAsk()+padding,TradeHandler.GetAsk()-padding);
     }
  }
//+------------------------------------------------------------------+

Wir haben den klassischen maschinellen Lernalgorithmus über denselben Testzeitraum laufen lassen, den wir zur Ermittlung unserer Kontrollrentabilitätswerte verwendet haben.

Abbildung 5: Durchführung des ersten Versuchs, das Kontrollsystem zu übertreffen

Das Rentabilitätsniveau unserer Handelsanwendung hat sich drastisch verschlechtert. Obwohl die Strategie eine hohe Genauigkeit zeigte, da 63 % aller von ihr platzierten Handelsgeschäfte profitabel waren, ist dies kaum beeindruckend, da sie das in dem Kontrollsystem festgelegte Gewinnniveau von 71 $ nicht übertraf.

Abbildung 6: Eine detaillierte Analyse unseres ersten Versuchs, das Kontrollsystem zu übertreffen

Die überarbeitete Anwendung, die wir entwickelt haben, erreicht nicht die gleiche Höhe wie die ursprüngliche Version der Handelsstrategie. Fairerweise muss man aber auch erwähnen, dass diese Version unserer Anwendung weit weniger volatil und zuverlässiger zu sein scheint als die ursprüngliche Strategie.

Abbildung 7: Die von der verbesserten Version unserer Handelsanwendung erzeugte Kapitalkurve ist weniger volatil als die ursprüngliche Strategie, erreicht aber auch nicht die gleichen Höhen


Den klassischen Versuch an seine Grenzen bringen

Erinnern Sie sich daran, dass wir das Ziel für 1, 5 und 10 Kerzen in die Zukunft prognostizieren. Wir wollen sehen, ob die Vorhersage für 10 Kerzen informativer ist als die einfache Vorhersage für einen Schritt, mit der wir begonnen haben. Daher werden wir uns wie zuvor nur auf die Teile der Handelsanwendung konzentrieren, die für einen fairen Vergleich geändert werden mussten.

   if(iHigh(Symbol(),TF_2,1)<iOpen(Symbol(),TF_2,0))
     {
      if(model_prediction[
    2 ]>iClose(Symbol(),TF_2,0))
         Trade.Buy(TradeHandler.MinVolume()*2,TradeHandler.GetSymbol(),TradeHandler.GetAsk(),TradeHandler.GetBid()-padding,TradeHandler.GetBid()+padding);

      Trade.Buy(TradeHandler.MinVolume(),TradeHandler.GetSymbol(),TradeHandler.GetAsk(),TradeHandler.GetBid()-padding,TradeHandler.GetBid()+padding);
     }

   if(iLow(Symbol(),TF_2,1)>iOpen(Symbol(),TF_2,0))
     {
     
      if(model_prediction[
    2 ]<iClose(Symbol(),TF_2,0))
         Trade.Sell(TradeHandler.MinVolume()*2,TradeHandler.GetSymbol(),TradeHandler.GetBid(),TradeHandler.GetAsk()+padding,TradeHandler.GetAsk()-padding);
         
      Trade.Sell(TradeHandler.MinVolume(),TradeHandler.GetSymbol(),TradeHandler.GetBid(),TradeHandler.GetAsk()+padding,TradeHandler.GetAsk()-padding);
     }

Wie bei dem Kontrollsystem der Handelsanwendung werden wir das gleiche 3-Jahres-Fenster wählen, um unsere Anwendung zu testen.

Abbildung 8: Denken Sie daran, dass alle Backtests über denselben Zeitraum durchgeführt werden müssen, um sicherzustellen, dass die Strategie die Zeit wirklich besser nutzt.

In den meisten Büchern, in denen Praktiker lernen, wie man Finanzmärkte mithilfe von maschinellem Lernen prognostiziert, wird die Vorhersage eines Schrittes in die Zukunft als Standardverfahren gelehrt. Gewinnbringende menschliche Händler versuchen jedoch selten, jeweils nur eine Kerze zu handeln, und wie wir in diesem Artikel zeigen, scheinen auch unsere Modelle profitabler zu sein, wenn sie über die unmittelbare Kerze hinausblicken dürfen. Der Leser sollte wissen, dass wir in diesem Beitrag zum ersten Mal die Ergebnisse des Kontrollsystems übertreffen.

Abbildung 9: Die Vorhersage von 10 Schritten in die Zukunft war für uns profitabler als die Vorhersage von nur einem Schritt in die Zukunft.

Die unerwünschte Volatilität unserer Kapitalkurve wurde natürlich korrigiert. Das ist auf jeden Fall ermutigend, aber wie der Leser bald sehen wird, können wir noch besser abschneiden als das.

Abbildung 10: Die Kapitalkurve, die durch die zweite Iteration unserer Handelsanwendung erzeugt wird, ist besser als die des Kontrollsystems, aber wir werden dem Leser nun zeigen, wie man dies zu neuen Höhen führen kann



Neuer Raum für Verbesserungen

Wir sind nun bereit, die Vorhersagen unseres Modells in mehreren Zeitschritten in die Zukunft zu vergleichen. Wir vergleichen, wo das Modell den Höchstpreis in 1 und 10 Schritten erwartet, und verwenden dann das erwartete Wachstum dieses Preisniveaus als unser Handelssignal. 
if(iHigh(Symbol(),TF_2,1)<iOpen(Symbol(),TF_2,0))
     {
      if(model_prediction[3]<model_prediction[5])
         Trade.Buy(TradeHandler.MinVolume()*2,TradeHandler.GetSymbol(),TradeHandler.GetAsk(),TradeHandler.GetBid()-padding,TradeHandler.GetBid()+padding);

      Trade.Buy(TradeHandler.MinVolume(),TradeHandler.GetSymbol(),TradeHandler.GetAsk(),TradeHandler.GetBid()-padding,TradeHandler.GetBid()+padding);
     }

if(iLow(Symbol(),TF_2,1)>iOpen(Symbol(),TF_2,0))
     {
      if(model_prediction[3]>model_prediction[5])
         Trade.Sell(TradeHandler.MinVolume()*2,TradeHandler.GetSymbol(),TradeHandler.GetBid(),TradeHandler.GetAsk()+padding,TradeHandler.GetAsk()-padding);
         
      Trade.Sell(TradeHandler.MinVolume(),TradeHandler.GetSymbol(),TradeHandler.GetBid(),TradeHandler.GetAsk()+padding,TradeHandler.GetAsk()-padding);
     }

Wir wollen prüfen, ob es sinnvoll ist, die Vorhersagen des Modells über mehrere Zeithorizonte hinweg mit einfacheren direkten Vergleichen zwischen den Vorhersagen des Modells und dem tatsächlichen Wert des Ziels zu vergleichen.

Abbildung 11: Ausführung der dritten Version unserer Handelsanwendung während des dreijährigen Backtestzeitraums

Wie der Leser sehen kann, sprechen die Ergebnisse, die wir erzielt haben, fast für sich selbst. Unsere Anwendung ist jetzt rentabler als jemals zuvor in unserem Entwicklungszyklus. Zur Erinnerung: Wir verwenden dasselbe ONNX-Modell, das wir zuvor exportiert haben. Und daran, dass sich die Grundlagen unserer Handelsbedingungen nicht geändert haben. Vielmehr scheinen wir durch eine sorgfältige Interpretation der Vorhersagen unseres Modells mehr Alpha aus der gleichen Handelsstrategie herauszuholen.

Abbildung 12: Die detaillierten Statistiken, die von der dritten Version unserer Handelsanwendung erstellt wurden, geben uns die Gewissheit, dass wir vernünftige Änderungen an der Anwendung vorgenommen haben

Unsere Kapitalkurve steigt kontinuierlich auf neue Höchststände, die weit über den Bereich des Kontrollsystems unserer Handelsanwendung oder eines der Rentabilitätsniveaus hinausgehen, die wir mit dem klassischen Ansatz des maschinellen Lernens im Finanzbereich ermitteln konnten.

Abbildung 13: Die von der aktuellen Iteration unserer Handelsanwendung erzeugte Kapitalkurve erreicht neue Höchststände, die wir in allen früheren Versionen der Anwendung nicht erreichen konnten.



Letzte Verbesserungen

Als Autor macht es mir Spaß, nach Marktstrukturen zu suchen, die sich leichter vorhersagen lassen als die Preise selbst, aber ebenso informativ sind wie die Kenntnis der künftigen Preisniveaus. Angesichts der Tatsache, dass wir es mit einem Kanal der gleitenden Durchschnitte der Hochs und Tiefs zu tun haben, führte mich meine Intuition zu der Frage, ob das Wachstum in der Mitte zwischen diesen beiden gleitenden Durchschnitten nicht einfacher zu prognostizieren sein könnte. Dies scheint bei dieser Diskussion definitiv der Fall gewesen zu sein.

if(iHigh(Symbol(),TF_2,1)<iOpen(Symbol(),TF_2,0))
     {
      if(((model_prediction[3]+model_prediction[6])/2)<((model_prediction[5]+model_prediction[8])/2))
         Trade.Buy(TradeHandler.MinVolume()*2,TradeHandler.GetSymbol(),TradeHandler.GetAsk(),TradeHandler.GetBid()-padding,TradeHandler.GetBid()+padding);

      Trade.Buy(TradeHandler.MinVolume(),TradeHandler.GetSymbol(),TradeHandler.GetAsk(),TradeHandler.GetBid()-padding,TradeHandler.GetBid()+padding);
     }

if(iLow(Symbol(),TF_2,1)>iOpen(Symbol(),TF_2,0))
     {
     
      if(((model_prediction[3]+model_prediction[6])/2)>((model_prediction[5]+model_prediction[8])/2))
         Trade.Sell(TradeHandler.MinVolume()*2,TradeHandler.GetSymbol(),TradeHandler.GetBid(),TradeHandler.GetAsk()+padding,TradeHandler.GetAsk()-padding);
         
      Trade.Sell(TradeHandler.MinVolume(),TradeHandler.GetSymbol(),TradeHandler.GetBid(),TradeHandler.GetAsk()+padding,TradeHandler.GetAsk()-padding);
     }

Führen Sie die endgültige Version unserer Anwendung über das gleiche 3-Jahres-Fenster aus, mit dem wir bisher gearbeitet haben.

Abbildung 14: Durchführung der endgültigen Version unserer Handelsanwendung, um zu versuchen, alle vorherigen Iterationen, die wir bisher erstellt haben, zu übertreffen

Wie wir unten sehen können, hat unsere Anwendung durchweg neue Leistungsstufen erreicht, die für uns unerreichbar waren, als wir diese Diskussion begannen. Es scheint, dass die endgültige Version der Anwendung, die wir bisher erstellt haben, die Mühe wert war, die wir in die Erstellung gesteckt haben.

Abbildung 15: Die endgültigen detaillierten Leistungswerte unserer Anwendung übertreffen alle anderen Leistungswerte, die wir zuvor in unserem Ausarbeitung ermittelt haben

Die Volatilität, die wir in unserer Kapitalkurve beobachten, ist fast vollständig unter unserer Kontrolle. Es kann bemerkenswert sein, wie viel Gewinn erzielt werden kann, ohne dass Ihre Handelsstrategien für maschinelles Lernen an Komplexität gewinnen.

Abbildung 16: Die Visualisierung der Kapitalkurve, die von der endgültigen Version unserer Handelsanwendung erzeugt wird, gibt uns Vertrauen in alle Änderungen, die wir bisher vorgenommen haben



Schlussfolgerung

Die Idee der Vorhersage mehrerer Zeitschritte ist in der algorithmischen Handelswelt nicht neu. Neu ist – und darauf beharrt dieser Artikel – die Perspektive, dass Vorhersagen von mehreren Zeitschritten nicht als eine alternative Technik, sondern vielmehr als ein möglicher Goldstandard für den algorithmischen Handel selbst angesehen werden sollten.

Weiter oben habe ich Ihnen, dem Leser, eine Frage gestellt. Ich habe gezeigt, dass die lineare Regression aus geometrischer Sicht nichts anderes ist als eine Folge von Rotationen und Skalierungen. Dann habe ich gefragt: Wo findet das Lernen wirklich statt?

Den Lesern, die sich immer noch an diese Frage erinnern, muss ich eine mögliche Hypothese anbieten – obwohl ich es stark bevorzuge, dass Sie unbeeinflusst bleiben, um Ihre eigenen zu entdecken.

Das Prinzip, das in diesem Artikel demonstriert wird, ist, dass mathematische Konzepte immer geometrische Entsprechungen haben. Jedes denkbare Modell für maschinelles Lernen kann als koordinierte Akrobatik aus Skalierung, Spiegelung, Projektion, Faltung und Drehung umgedeutet werden, die auf die durch die Daten selbst definierten Mannigfaltigkeiten angewendet wird. Selbst fortgeschrittene neuronale Netze sind nicht geheimnisvoll: Sie sind einfach ausgeklügelte Choreografien geometrischer Transformationen, die Daten immer wieder falten und umformen.

Die Antwort auf die Frage „Wo findet das Lernen statt?“ könnte daher lauten: Lernen findet statt, wenn Informationen in geometrischen Mustern kodiert werden. Der Zyklus von Rotation und Skalierung ist eines der elementarsten dieser Muster. Es ist eine kraftvolle geometrische Struktur in demselben Sinne, wie die drei Grundfarben die Grundlage für alle anderen Farben bilden. In der Geometrie gibt es primäre Transformationen, aus denen alle anderen zusammengesetzt sind.

Die Bildklassifizierungsbranche hat sich bereits auf diese Realität eingestellt. Ihr Erfolg beruht auf langen und detaillierten Vorverarbeitungspipelines – Pipelines, die im Wesentlichen aus einer sorgfältigen Orchestrierung geometrischer Transformationen bestehen. Sie mögen als routinemäßiges Feature-Engineering erscheinen, aber in Wahrheit sind sie die stille Anwendung eben dieser Prinzipien, oft ohne dass man sich ihrer tieferen Bedeutung bewusst ist.

Und so geht der Leser mit wertvollen Erkenntnissen nach Hause: Mehrstufige Prognosen im Handel sind vielleicht eine der am meisten unterschätzten Strategien in unserem Bereich, gerade weil sie weit mehr Arbeit leisten, als ihr wirklich zugetraut wird. Mehrstufige Vorhersagen stellen sicher, dass wir unsere Vergleiche im gleichen Koordinatensystem durchführen. Andernfalls ist der direkte Vergleich der Vorhersagen Ihres Modells mit dem tatsächlichen Wert des Ziels gleichbedeutend mit dem Vergleich zweier Größen, die garantiert nicht in denselben Einheiten vorliegen.

Dateiname Beschreibung der Datei
Fetch_Data_MA_2.1.mq5 Das MQL5-Skript, das wir verwendet haben, um historische Daten von unserem MetaTrader 5-Terminal abzurufen.
MFH_Baseline.mq5 Die Basisversion unserer Handelsstrategie, die keine maschinellen Lernmodelle verwendet.
MFH_1.1.mq5  Der erste Versuch, eine Handelsanwendung zu entwickeln, folgte ausschließlich den klassischen Paradigmen des maschinellen Lernens im Finanzbereich.
MFH_1.2.mq5 Der letzte Versuch, den wir unternommen haben, war der Aufbau einer Handelsanwendung unter Verwendung der klassischen Paradigmen des maschinellen Lernens im Finanzbereich.
MFH_1.3.mq5 Der erste Versuch, den wir unternommen haben, bestand darin, keine direkten Vergleiche zwischen dem aktuellen Preis und dem prognostizierten Preis anzustellen.
MFH_1.4.mq5  Die endgültige Version unserer Handelsstrategie, die zu den höchsten Rentabilitätsniveaus führte, haben wir in diesem Artikel vorgestellt.
Multiple_Forecast_Horizons.ipynb  Das Jupyter-Notebook, das wir für den Export unseres ONNX-Modells verwendet haben.

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

Beigefügte Dateien |
MFH_Baseline.mq5 (7.4 KB)
MFH_1.1.mq5 (8.85 KB)
MFH_1.2.mq5 (9.17 KB)
MFH_1.3.mq5 (9.17 KB)
MFH_1.4.mq5 (9.27 KB)
Pipelines in MQL5 Pipelines in MQL5
In diesem Beitrag befassen wir uns mit einem wichtigen Schritt der Datenaufbereitung für das maschinelle Lernen, der zunehmend an Bedeutung gewinnt. Pipelines für die Datenvorverarbeitung. Dabei handelt es sich im Wesentlichen um eine rationalisierte Abfolge von Datenumwandlungsschritten, mit denen Rohdaten aufbereitet werden, bevor sie in ein Modell eingespeist werden. So uninteressant dies für den Laien auch erscheinen mag, diese „Datenstandardisierung“ spart nicht nur Trainingszeit und Ausführungskosten, sondern trägt auch zu einer besseren Generalisierung bei. In diesem Artikel konzentrieren wir uns auf einige SCIKIT-LEARN Vorverarbeitungsfunktionen, und während wir den MQL5-Assistenten nicht ausnutzen, werden wir in späteren Artikeln darauf zurückkommen.
Entwicklung des Price Action Analysis Toolkit (Teil 41): Aufbau eines statistischen Preis-Level EA in MQL5 Entwicklung des Price Action Analysis Toolkit (Teil 41): Aufbau eines statistischen Preis-Level EA in MQL5
Die Statistik war schon immer das Herzstück der Finanzanalyse. Laut Definition ist Statistik die Disziplin, die sich mit dem Sammeln, Analysieren, Interpretieren und Darstellen von Daten auf sinnvolle Weise befasst. Stellen Sie sich nun vor, dasselbe Rahmenwerk auf Kerzen anzuwenden – und die rohe Preisbewegung in messbare Erkenntnisse zu verdichten. Wie hilfreich wäre es, für einen bestimmten Zeitraum die zentrale Tendenz, die Streuung und die Verteilung des Marktverhaltens zu kennen? In diesem Artikel stellen wir genau diesen Ansatz vor und zeigen, wie statistische Methoden Kerzendaten in klare, umsetzbare Signale verwandeln können.
Eine alternative Log-datei mit der Verwendung der HTML und CSS Eine alternative Log-datei mit der Verwendung der HTML und CSS
In diesem Artikel werden wir eine sehr einfache, aber leistungsfähige Bibliothek zur Erstellung der HTML-Dateien schreiben, dabei lernen wir auch, wie man eine ihre Darstellung einstellen kann (nach seinem Geschmack) und sehen wir, wie man es leicht in seinem Expert Advisor oder Skript hinzufügen oder verwenden kann.
Einführung in MQL5 (Teil 21): Automatisiertes Erkennen von harmonischen Mustern Einführung in MQL5 (Teil 21): Automatisiertes Erkennen von harmonischen Mustern
Lernen Sie, wie Sie das harmonische Muster von Gartley im MetaTrader 5 mit MQL5 erkennen und anzeigen können. In diesem Artikel wird jeder Schritt des Prozesses erläutert, von der Identifizierung der Umkehrpunkte über die Anwendung der Fibonacci-Ratios bis hin zur Darstellung des gesamten Musters auf dem Chart zur eindeutigen visuellen Bestätigung.