English 日本語
preview
Selbstoptimierende Expert Advisors in MQL5 (Teil 17): Ensemble Intelligence

Selbstoptimierende Expert Advisors in MQL5 (Teil 17): Ensemble Intelligence

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

Alle algorithmischen Handelsstrategien sind, unabhängig von ihrer Komplexität, schwierig einzurichten und zu pflegen. Dieses universelle Problem wird von Anfängern und Experten gleichermaßen geteilt. Anfänger haben Mühe, die Periodenlänge für die Strategien des Kreuzens ihrer gleitenden Durchschnitte einzustellen, während Experten ebenso unermüdlich die Gewichte ihrer tiefen neuronalen Netze anpassen. Auf beiden Seiten des Zauns gibt es materielle Probleme.

Modelle des maschinellen Lernens sind anfällig und fallen im realen Handel oft auseinander. Ihr undurchsichtiges und komplexes Design erschwert die Fehlersuche und Diagnose von Leistungsengpässen zusätzlich. Andererseits können menschliche Strategien robuster sein, erfordern aber oft eine manuelle Konfiguration, um in Gang zu kommen – ein intensiver Prozess, je nach Ansatz. In diesem Artikel wird ein Ensemble-Rahmenwerk vorgeschlagen, in dem überwachte Modelle und menschliche Intuition aufeinander aufbauen, um ihre kollektiven Beschränkungen auf beschleunigte Weise zu überwinden.

Um dieses Ziel zu erreichen, haben wir unsere Strategie und unser statistisches Modell auf dieselben vier technischen Indikatoren ausgerichtet. Wir haben eine Kanalstrategie mit gleitendem Durchschnitt gewählt und ein Ridge-Regressionsmodell auf dieselben Indikatoren angewendet. Auf diese Weise konnten wir schnell eine rentable Konfiguration für das gesamte System ermitteln.

Die technischen Indikatoren geben uns eine zentrale Kontrolle sowohl über die menschliche Intuition als auch über das überwachte Modell. Die Anforderung an die Strategie, Positionen nur dann zu eröffnen, wenn sowohl die traditionelle als auch die statistische Komponente übereinstimmen, führte zu einem gewinnbringenden Ergebnis aus zwei unabhängig voneinander unrentablen Systemen. Dies ist die Motivation hinter unserem Ensemble-Rahmenwerk: Unsere Strategien scheinen sich gegenseitig schneller zu korrigieren, als wir eine von ihnen gleichwertig korrigieren können.

Unser Ansatz ermöglicht es dem statistischen Modell, von denselben technischen Indikatoren zu lernen, die auch von der Strategie verwendet werden, was das Ensemble-Stacking praktischer macht und uns dabei hilft, stabile Konfigurationen zu finden, die andernfalls zeitaufwändig zu ermitteln wären. Diese zentrale Steuerung bedeutet, dass wir nur einige wenige technische Indikatoren konfigurieren müssen, die sich auf beide Komponenten auswirken – so können wir schnell herausfinden, welche gleitenden Durchschnitte von Bedeutung sind, unabhängig von ihren zugrunde liegenden Zeiträumen. Eine Anpassung der Zeiträume für den gleitenden Durchschnitt war nicht erforderlich, auch wenn der Artikel zeigen wird, dass die ursprüngliche Strategie mit den ausgewählten Zeiträumen, die wir zu Demonstrationszwecken beibehalten haben, unrentabel war. Das System lernt, sich selbst zu korrigieren.


Visualisierung der Handelsstrategie

Die Strategie stützt sich auf zwei gleitende Durchschnittsindikatoren, von denen jeweils einer auf dem Höchst- und dem Tiefstkurs liegt. Die Indikatoren bilden einen Kanal, wobei der Abstand zwischen ihnen von der gegebenen Volatilität des Marktes abhängt. Die nachstehende Abbildung 1 veranschaulicht diese Strategie. Die Strategie liefert Kaufsignale, wenn die Kurse über den obersten Kanal ausbrechen, und umgekehrt Verkaufssignale. Im weißen Kasten oben rechts in Abbildung 1 ist zu erkennen, dass die Strategie in kurzer Zeit zwei gegensätzliche Signale erzeugt hat; die Strategie weist ein sichtbares Maß an Rauschen auf.

Abbildung 1

Abbildung 1: Identifizierung einer Handelsgelegenheit nach der Strategie des gleitenden Durchschnittskanals

Die Strategie ist zwar verrauscht, aber sie dient als zuverlässiger Wegweiser für den allgemeinen Markttrend.

Abbildung 2

Abbildung 2: Obwohl die Strategie mit Rauschen behaftet ist, erscheint sie insgesamt solide


Festlegung eines grundlegenden Leistungsniveaus

Zu Beginn werden wir zunächst Systemkonstanten definieren, die wir während des gesamten Tests beibehalten werden. 

//+------------------------------------------------------------------+
//|                                                           EI.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 constants                                                 |
//+------------------------------------------------------------------+
#define SYSTEM_TF PERIOD_D1
#define MA_SHIFT 0
#define MA_TYPE MODE_EMA
#define ATR_PERIOD 14
#define PADDING 2

Als Nächstes werden wir einige Hilfsbibliotheken laden, die wir für unsere Übung benötigen.

//+------------------------------------------------------------------+
//| Libraries                                                        |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
#include <VolatilityDoctor\Trade\TradeInfo.mqh>

CTrade Trade;
TradeInfo *TradeHelper;

Globale Variablen werden in fast allen Anwendungen benötigt. Wir brauchen sie, um die Messwerte unserer technischen Indikatoren und die Zeit zu verfolgen.

//+------------------------------------------------------------------+
//| Define global variables                                          |
//+------------------------------------------------------------------+
int    ma_h_handler,ma_l_handler,atr_handler;
double ma_h[],ma_l[],atr[];
MqlDateTime tc,ts;

Wir werden einen Eingabewert von 20 festlegen. Wenn Sie diesen Wert ändern, denken Sie daran, die Änderung im Skript konsistent zu halten.

//+------------------------------------------------------------------+
//| Input varaibles                                                  |
//+------------------------------------------------------------------+
input group "Technical Indicators"
input int MA_PERIOD = 20;//Moving average period

Wenn unsere Anwendung initialisiert ist, laden wir die benötigten technischen Indikatoren und erstellen neue Instanzen der benötigten Klasseninstanzen.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Setup our technical indicators
   ma_h_handler = iMA(Symbol(),SYSTEM_TF,MA_PERIOD,MA_SHIFT,MA_TYPE,PRICE_HIGH);
   ma_l_handler = iMA(Symbol(),SYSTEM_TF,MA_PERIOD,MA_SHIFT,MA_TYPE,PRICE_LOW);
   atr_handler = iATR(Symbol(),SYSTEM_TF,ATR_PERIOD);
   TradeHelper = new TradeInfo(Symbol(),SYSTEM_TF);

//--- Mark the time
   TimeLocal(tc);
   TimeLocal(ts);
//---
   return(INIT_SUCCEEDED);
  }

Wenn unsere Anwendung nicht mehr genutzt wird, werden wir die technischen Indikatoren, die wir nicht mehr verwenden, freigeben.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   delete TradeHelper;
   IndicatorRelease(ma_h_handler);
   IndicatorRelease(ma_l_handler);
   IndicatorRelease(atr_handler);
  }

Unsere Anwendung führt jede Stunde Routineaufgaben aus. Dies geschieht durch die Überprüfung des Zeitablaufs mit dem spezialisierten Objekt MqlDateTime. Anschließend aktualisieren wir die Puffer für die technischen Indikatoren und speichern den aktuellen Schlusskurs. Schließlich überprüfen wir das Handelssignal: Wenn der Schlusskurs aus dem Kanal des gleitenden Durchschnitts ausgebrochen ist, gehen wir Positionen ein, die das Vertrauen in die Entwicklung widerspiegeln.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   TimeLocal(ts);

   if(ts.hour != tc.hour)
     {
      if(PositionsTotal()==0)
        {
         //--- Update the time
         TimeLocal(tc);

         //--- Update the indicator buffer
         CopyBuffer(ma_h_handler,0,0,1,ma_h);
         CopyBuffer(ma_l_handler,0,0,1,ma_l);
         CopyBuffer(atr_handler,0,0,1,atr);

         //--- Check if the current price is above or below the channel
         double c = iClose(Symbol(),SYSTEM_TF,0);

         if(c > ma_h[0])
            Trade.Buy(TradeHelper.MinVolume(),Symbol(),TradeHelper.GetAsk(),TradeHelper.GetBid()-(atr[0]*PADDING),TradeHelper.GetBid()+(atr[0]*PADDING));

         else
            if(c < ma_l[0])
               Trade.Sell(TradeHelper.MinVolume(),Symbol(),TradeHelper.GetBid(),TradeHelper.GetAsk()+(atr[0]*PADDING),TradeHelper.GetAsk()-(atr[0]*PADDING));
        }
     }
  }
//+------------------------------------------------------------------+

Schließlich heben wir alle zuvor definierten Systemkonstanten auf.

//+------------------------------------------------------------------+
//| Undefine system constants                                        |
//+------------------------------------------------------------------+
#undef SYSTEM_TF
#undef MA_SHIFT
#undef MA_TYPE
#undef ATR_PERIOD
#undef PADDING

Laden wir die Benchmark-Anwendung und legen unsere Testdaten entsprechend fest. Wir haben für diese Übung mehr als drei Jahre täglicher EURUSD-Daten ausgewählt, die sich von Januar 2022 bis Januar 2025 erstrecken.

Abbildung 3: Auswahl der Daten unserer Anwendung, um eine Basisleistung zu ermitteln

Eine Kombination aus zufälliger Verzögerung und Modellierung auf der Grundlage von Real-Tick-Einstellungen gewährleistet die besten Ergebnisse und entspricht dem Live-Handel.

Abbildung 4: Auswahl der Testbedingungen, unter denen wir unsere Anwendung testen wollen

Wir haben es dem Nutzer auch ermöglicht, seinen eigenen Zeitraum festzulegen und eine aktive Rolle beim Lesen zu spielen. Ansonsten wurde der Wert 20 willkürlich gewählt.

Abbildung 5: Unsere derzeitige Übung erlaubt es uns, beliebige Zeiträume für die Bewertung zu wählen

Wie in der Einleitung erläutert, war der Wert von 20 nicht rentabel. Wie wir jedoch sehen werden, kann unsere statistische Strategie daraus lernen und uns dabei helfen, das Rauschen richtig herauszufiltern, ohne dass wir immer nach verschiedenen Zeiträumen für unsere technischen Indikatoren fegen müssen.

Abbildung 6: Die von unserer Handelsanwendung erzeugte Kapitalkurve hat keine Stabilität und gibt uns wenig Vertrauen

Die von uns ermittelten Benchmark-Ergebnisse stimmen mit der zuvor beobachteten Kapitalkurve überein – negativ, was normalerweise ein Zeichen dafür ist, dass wir unsere Idee aufgeben sollten -, aber für heute werden wir mit dem intakten System fortfahren.

Abbildung 7: Die detaillierten Statistiken, die uns mit unserer Benchmark-Anwendung vorliegen, zeigen uns, dass wir uns noch verbessern können


Historische Marktdaten abrufen

Lassen Sie uns nun ein Skript erstellen, das historische Marktdaten abruft und in eine CSV-Datei schreibt. Wir werden diese Daten nutzen, um eine statistische Strategie für den EURUSD-Markt zu entwickeln.

//+------------------------------------------------------------------+
//|                                                      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 O",
                   "True MA H",
                   "True MA L",
                   "True MA C"
                  );
        }

      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_o_reading[i],
                   ma_h_reading[i],
                   ma_l_reading[i],
                   ma_reading[i]
                  );
        }
     }
//--- Close the file
   FileClose(file_handle);
  }
//+------------------------------------------------------------------+

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


Analysieren historischer Marktdaten in Python

Laden wir unsere Standard-Python-Bibliotheken.
#Load the libraries we need
import pandas as pd
import numpy as np

Lesen wir die Marktdaten, die wir zuvor in CSV geschrieben haben.

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

Wir definieren unseren Prognosehorizont.

#Define the forecast horizon
HORIZON = 20

Wir löschen alle historischen Daten, die sich mit dem Backtest-Zeitraum überschneiden.

#Drop the dates that overlap with the back test
data = data.iloc[:-(365*3),:]
_ = data.iloc[-(365*3):,:]

Wir kennzeichnen die Marktdaten, damit wir modellieren können, auf welcher Seite des gleitenden Durchschnitts der Schlusskurs voraussichtlich liegen wird. Das ist es, was unsere Strategie aus der Perspektive der menschlichen Intuition antreibt.

#Label the data
data['Target H'] = data['Close'].shift(-HORIZON) - data['MA H'].shift(-HORIZON)
data['Target L'] = data['Close'].shift(-HORIZON) - data['MA L'].shift(-HORIZON)

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

Laden wir unsere ONNX-Bibliotheken. ONNX, die Abkürzung für Open Neural Network Exchange, ist eine Open-Source-Bibliothek, die es uns ermöglicht, Modelle für maschinelles Lernen zu erstellen und einzusetzen, ohne Abhängigkeiten von ihren Trainingsumgebungen zu übernehmen.

from sklearn.linear_model import Ridge
import onnx
from skl2onnx import convert_sklearn 
from skl2onnx.common.data_types import FloatTensorType

Passen wir das Modell auf alle Trainingsdaten an.

model = Ridge(alpha=1e-3)
model.fit(data.iloc[:,1:-2],data.loc[:,['Target H','Target L']])

Definieren wir die Eingangs- und Ausgangsformen unseres ONNX-Modells.

initial_types = [('float_input',FloatTensorType([1,8]))]
final_types = [('float_output',FloatTensorType([1,2]))]

Speichern wir das ONNX-Modell als ONNX-Prototyp.

onnx_proto = convert_sklearn(model=model,initial_types=initial_types,final_types=final_types,target_opset=12)

Speichern wir den ONNX-Prototyp als Datei auf Ihrem Laufwerk. 

onnx.save(onnx_proto,'EURUSD MA R.onnx')


Die Basislinie übertreffen

Wir werden uns nun auf die Teile unserer Codebasis konzentrieren, die sich ändern werden, und alle anderen Teile, die sich nicht geändert haben, weglassen. Die erste Änderung, die wir vornehmen werden, ist die Einführung von zwei neuen Systemkonstanten, die mit unserem ONNX-Modell verbunden sind.

//+------------------------------------------------------------------+
//|                                                           EI.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 constants                                                 |
//+------------------------------------------------------------------+
#define ONNX_FEATURES 8
#define ONNX_TARGETS 2

Jetzt laden wir unser ONNX-Modell.

//+------------------------------------------------------------------+
//| Dependencies                                                     |
//+------------------------------------------------------------------+
#resource "\\Files\\EURUSD MA R.onnx" as const uchar onnx_buffer[];

Nun erstellen wir unser ONNX-Modell aus dem zuvor definierten Puffer und konfigurieren die Eingabe- und Ausgabeformen unseres Modells.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   onnx_model = OnnxCreateFromBuffer(onnx_buffer,ONNX_DATA_TYPE_FLOAT);

   if(onnx_model == INVALID_HANDLE)
     {
      Print("Failed to create ONNX model: ",GetLastError());
      return(INIT_FAILED);
     }

   ulong input_shape[]  = {1,ONNX_FEATURES};
   ulong output_shape[] = {1,ONNX_TARGETS};

   onnx_inputs = vectorf::Zeros(ONNX_FEATURES);
   onnx_output = vectorf::Zeros(ONNX_TARGETS);

   if(!OnnxSetInputShape(onnx_model,0,input_shape))
     {
      Print("Failed to define ONNX input shape: ",GetLastError());
      return(INIT_FAILED);
     }

   if(!OnnxSetOutputShape(onnx_model,0,output_shape))
     {
      Print("Failed to define ONNX output shape: ",GetLastError());
      return(INIT_FAILED);
     }

//--- Mark the time
   TimeLocal(tc);
   TimeLocal(ts);
//---
   return(INIT_SUCCEEDED);
  }

Wenn unsere Anwendung nicht mehr verwendet wird, müssen wir auch das ONNX-Modell freigeben, das wir nicht mehr verwenden.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   OnnxRelease(onnx_model);
  }

Zusätzlich zu den zuvor definierten Indikatoraktualisierungen benötigen wir auch die aktuellen Marktinputs für unser ONNX-Modell. Wir erinnern daran, dass unser Modell vorhersagt, wie weit der Schlusskurs von den Grenzen des gleitenden Durchschnitts abweichen wird. Positive Abweichungen sind nach oben gerichtet, negative Abweichungen sind nach unten gerichtet.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   if(ts.hour != tc.hour)
     {
      if(PositionsTotal()==0)
        {
         onnx_inputs[0] = (float) iOpen(Symbol(),SYSTEM_TF,0);
         onnx_inputs[1] = (float) iHigh(Symbol(),SYSTEM_TF,0);
         onnx_inputs[2] = (float) iLow(Symbol(),SYSTEM_TF,0);
         onnx_inputs[3] = (float) iClose(Symbol(),SYSTEM_TF,0);
         onnx_inputs[4] = (float) ma_o[0];
         onnx_inputs[5] = (float) ma_h[0];
         onnx_inputs[6] = (float) ma_l[0];
         onnx_inputs[7] = (float) ma_c[0];

         if(OnnxRun(onnx_model,ONNX_DATA_TYPE_FLOAT,onnx_inputs,onnx_output))
           {
            //--- Check if the current price is above or below the channel
            Print("Forecast: ",onnx_output);
            double c = iClose(Symbol(),SYSTEM_TF,0);
	    
            if((c > ma_h[0]) && (onnx_output[0]>0) && (onnx_output[1]>0))
               Trade.Buy(TradeHelper.MinVolume(),Symbol(),TradeHelper.GetAsk(),TradeHelper.GetBid()-(atr[0]*PADDING),TradeHelper.GetBid()+(atr[0]*PADDING));

            else
               if((c < ma_l[0]) && (onnx_output[0]<0) && (onnx_output[1]<0))
                  Trade.Sell(TradeHelper.MinVolume(),Symbol(),TradeHelper.GetBid(),TradeHelper.GetAsk()+(atr[0]*PADDING),TradeHelper.GetAsk()-(atr[0]*PADDING));
           }

         else
           {
            Print("Failed to obtain a prediction from our ONNX model: ",GetLastError());
           }
        }
     }
  }
//+------------------------------------------------------------------+

Dann deaktivieren wie die neuen Definitionen, die wir vorgenommen haben, um unser ONNX-Modell zu berücksichtigen.

//+------------------------------------------------------------------+
//| Undefine system constants                                        |
//+------------------------------------------------------------------+
#undef ONNX_FEATURES
#undef ONNX_TARGETS
//+------------------------------------------------------------------+

Nun werden wir die neue Version der Anwendung, die wir gerade gemeinsam erstellt haben, in denselben Testzeiträumen testen.

Abbildung 8: Dann wählen wir die gleichen Testdaten aus, die wir in unserem ersten Test für die Konsistenz ausgewählt haben

Wie wir sehen können, ist die Anwendung immer noch unrentabel. Schauen wir uns jedoch die detaillierten Statistiken genauer an.

Abbildung 9: Unsere Anwendung ist noch nicht rentabel geworden

Der Gesamtnettogewinn ist von -$96 auf -$62 gestiegen, aber es gibt noch viel Spielraum für Verbesserungen.

Abbildung 10: Die detaillierten Ergebnisse des Antrags, den wir gerade erstellt haben, geben uns noch wenig Vertrauen in die Integrität unserer bisherigen Strategie


Zusätzliche Verbesserungen

Stützen wir uns auf unsere vorherige Diskussion über Kerzenmuster, um einen alternativen Lernpartner für unsere statistische Strategie anzubieten. Diese Höchst- und Tiefstkurse sind die technischen Inputs für unsere gleitenden Durchschnittsindikatoren. Wir haben die „Engulfing Aufwärtskerze“ bereits ausführlich besprochen und beobachtet, wie man unabhängig von unseren statistischen Modellen eine gute Performance aus ihr herausholen kann.

if(OnnxRun(onnx_model,ONNX_DATA_TYPE_FLOAT,onnx_inputs,onnx_output))
  {
   //--- Check if the current price is above or below the channel
   Print("Forecast: ",onnx_output);
   double c = iClose(Symbol(),SYSTEM_TF,0);

   //--- Check for any bullish engulfing candle sticks
   if((onnx_output[0]>0) && (onnx_output[1]>0) && (iHigh(Symbol(),PERIOD_CURRENT,1) > iHigh(Symbol(),PERIOD_CURRENT,2)) && (iLow(Symbol(),PERIOD_CURRENT,1) < iLow(Symbol(),PERIOD_CURRENT,2)))
      Trade.Buy(TradeHelper.MinVolume(),Symbol(),TradeHelper.GetAsk(),TradeHelper.GetBid()-(atr[0]*PADDING),TradeHelper.GetBid()+(atr[0]*PADDING));
            
   //--- Check for any bearish engulfing candle sticks
   else
      if((onnx_output[0]<0) && (onnx_output[1]<0) && (iHigh(Symbol(),PERIOD_CURRENT,1) > iHigh(Symbol(),PERIOD_CURRENT,2)) && (iLow(Symbol(),PERIOD_CURRENT,1) < iLow(Symbol(),PERIOD_CURRENT,2)))
         Trade.Sell(TradeHelper.MinVolume(),Symbol(),TradeHelper.GetBid(),TradeHelper.GetAsk()+(atr[0]*PADDING),TradeHelper.GetAsk()-(atr[0]*PADDING));
  }    

Wiederholen wir nun den Test für dieselben Zeiträume mit dieser überarbeiteten Version unserer Anwendung.

Abbildung 11: Durchführung unserer neuen Version der Handelsstrategie über dasselbe 3-Jahres-Testfenster

Wie wir sehen, erreichen wir jetzt neue Rentabilitätsniveaus, von denen wir früher nur träumen konnten. Unsere Bewerbung hat begonnen, sich positiv zu entwickeln. Aber lassen Sie uns einen genaueren Blick auf die detaillierten Ergebnisse dieses Backtests werfen.

Abbildung 12: Die von unserer neuen überarbeiteten Version der Handelsanwendung erstellte Kapitalkurve gibt uns messbares Vertrauen

Es ist ermutigend zu sehen, dass unsere Anwendung nun einen Gesamtnettogewinn von 126,58 $ erwirtschaftet, obwohl über einen dreijährigen Backtest nur neun Verkäufe platziert wurden. Dies ist nicht akzeptabel und könnte auf ein ungenutztes Potenzial hindeuten, das wir noch nicht entdeckt haben.

Abbildung 13: Die Analyse der detaillierten Ergebnisse unserer verbesserten Handelsstrategie zeigt, dass die Verteilung der Handelsgeschäfte weiter verfeinert werden muss


Letzter Versuch

Lassen Sie uns nun einen letzten Versuch zur Verbesserung des Modells unternehmen. Wir werden zunächst wichtige Anpassungen an unserem Modell vornehmen. Wir werden unsere technischen Indikatoren in mehreren Schritten in die Zukunft prognostizieren, wobei jeder Indikator mit einem und zwanzig Schritten in die Zukunft modelliert wird, sodass wir insgesamt vier Ziele haben.

#Label the data
data['Target H'] = data['MA H'].shift(-1)
data['Target L'] = data['MA L'].shift(-1)

data['Target H 2'] = data['MA H'].shift(-HORIZON)
data['Target L 2'] = data['MA L'].shift(-HORIZON)

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

Anschließend wird ein Ridge-Regressionsmodell mit einem Alpha-Wert von 0,001 angepasst. Damit wird festgelegt, wie schnell unwichtige Koeffizienten auf Null geschrumpft werden sollen, damit sich unser Modell auf die wichtigen Parameter konzentriert. Dann passen wir unser Modell an.

model = Ridge(alpha=1e-3)
model.fit(data.iloc[:,1:-4],data.loc[:,['Target H','Target L','Target H 2','Target L 2']])

Die Eingangs- und Ausgangsformen des Modells sind nun definiert.

initial_types = [('float_input',FloatTensorType([1,8]))]
final_types = [('float_output',FloatTensorType([1,4]))]

Schließlich speichern wir unser ONNX-Modell in einer Datei.

onnx.save(onnx_proto,'EURUSD MA MFH R.onnx')


Implementierung unserer Verbesserungen in MQL5

Jetzt sind wir bereit, unsere Verbesserungen in MQL5 umzusetzen. Wir beginnen damit, die Größe unserer ONNX-Modellausgaben zu ändern.

//+------------------------------------------------------------------+
//| System constants                                                 |
//+------------------------------------------------------------------+
#define ONNX_TARGETS 4

Dann laden wir unser neu aktualisiertes mehrstufiges Prognosemodell.

//+------------------------------------------------------------------+
//| Dependencies                                                     |
//+------------------------------------------------------------------+
#resource "\\Files\\EURUSD MA MFH R.onnx" as const uchar onnx_buffer[];

Jetzt können wir eine neue Vorhersage von unserem Modell erhalten und sie mit unserer menschlichen Intuition vergleichen. Wenn der gleitende Durchschnitt zum Schluss über dem Eröffnungskurs liegt, ist dies ein Zeichen für eine optimistische Marktstimmung. Wenn das ONNX-Modell hingegen davon ausgeht, dass sowohl der obere als auch der untere gleitende Durchschnitt im Laufe der Zeit ansteigen werden, bestätigt dies unser Vertrauen und ermöglicht es uns, Käufe zu platzieren. Das Gegenteil trifft auf unsere Verkaufsbedingungen zu.

 if(OnnxRun(onnx_model,ONNX_DATA_TYPE_FLOAT,onnx_inputs,onnx_output))
           {
            //--- Check if the current price is above or below the channel
            Print("Forecast: ",onnx_output);
            double c = iClose(Symbol(),SYSTEM_TF,0);

            if((ma_o[0]<ma_c[0]) && (onnx_output[0]<onnx_output[2]) && (onnx_output[1]<onnx_output[3]))
               Trade.Buy(TradeHelper.MinVolume(),Symbol(),TradeHelper.GetAsk(),TradeHelper.GetBid()-(atr[0]*PADDING),TradeHelper.GetBid()+(atr[0]*PADDING));

            else
               if((ma_o[0]>ma_c[0]) && (onnx_output[0]>onnx_output[2]) && (onnx_output[1]>onnx_output[3]))
                  Trade.Sell(TradeHelper.MinVolume(),Symbol(),TradeHelper.GetBid(),TradeHelper.GetAsk()+(atr[0]*PADDING),TradeHelper.GetAsk()-(atr[0]*PADDING));
           }

Testen wir nun die verbesserte Version unserer Handelsanwendung über denselben Testzeitraum.

Abbildung 14: Testen der endgültigen Konfiguration unserer Handelsanwendung über das gleiche 3-Jahres-Fenster, das wir in dieser Übung verwendet haben

Wie wir sehen können, ist die von unserer neuen Anwendung erstellte Kapitalkurve viel widerstandsfähiger und zeigt einen stärkeren Aufwärtstrend als alle früheren Versionen, die wir bisher erstellt haben.

Abbildung 15: Die Kapitalkurve, die von unserer endgültigen Version der Handelsanwendung erzeugt wird, erreicht neue Höchststände, die wir bisher nicht erreichen konnten

Außerdem ist unser neues Leistungsniveau gesund. Unsere Anwendung liefert nun einen Gesamtgewinn von 173,72 $, weitaus besser als der Gewinn von -96 $, mit dem wir gestartet sind, und unsere Verteilung der Käufe und Verkäufe ist endlich akzeptabel.

Abbildung 16: Die detaillierten Ergebnisse, die unsere verfeinerte Version der Handelsanwendung liefert, stimmen uns zuversichtlich in Bezug auf die von uns vorgenommenen Änderungen



Schlussfolgerung

Wir sind nun am Ende unserer heutigen Diskussion angelangt. In diesem Artikel wurde untersucht, wie sich die Instabilität algorithmischer Handelsstrategien, die auf überwachten Modellen beruhen, kontrollieren lässt. Unsere Lösung bestand darin, statistische Modelle zu entwickeln, die von denselben Indikatoren und Marktdaten abhängen wie eine herkömmliche Handelsstrategie, und dann die beiden Strategien so zu kombinieren, dass sie wie eine einzige funktionieren. Durch dieses Verfahren war es nicht mehr notwendig, die Parameter der traditionellen Strategie, mit der wir begonnen hatten, zu optimieren, und die endgültige Strategie war robuster. Der Leser kann diese Lösung leicht erweitern, um seine Lieblingsindikatoren zu integrieren, nicht nur die in unserem Beispiel gezeigten.


Dateiname  Beschreibung der Datei
Fetch Data MA.mq5 Das MQL5-Skript, das wir verwendet haben, um unsere historischen Marktdaten aus dem MetaTrader 5-Terminal abzurufen.
EI Baseline.mq5  Diese Anwendung diente als Rentabilitätsbenchmark für die klassische Kanalstrategie mit gleitendem Durchschnitt.
EI.mq5 Unser anfänglicher Versuch, die Benchmark zu übertreffen, war in dieser Version der Anwendung nicht rentabel.
EI 2.mq5  Unser erster erfolgreicher Versuch, die Benchmark zu übertreffen. Beachten Sie, dass diese Version der Anwendung auf Käufe ausgerichtet war.
EI 3.mq5 Die beste Version der Moving-Average-Channel-Strategie, die wir entwickelt haben, übertraf die klassische Strategie und platzierte relativ unvoreingenommene Handelsgeschäfte.
MA Channel AI 3.ipynb  Das Jupyter-Notebook, das wir zusammen geschrieben haben, um die historischen Marktdaten zu analysieren, die wir mit unserem MQL5-Skript abgerufen haben.

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

Beigefügte Dateien |
Fetch_Data_MA.mq5 (3.69 KB)
EI_Baseline.mq5 (4.24 KB)
EI.mq5 (6.6 KB)
EI_2.mq5 (6.98 KB)
EI_3.mq5 (6.74 KB)
Integration von MQL5 mit Datenverarbeitungspaketen (Teil 6): Zusammenführung von Markt-Feedback und Modellanpassung Integration von MQL5 mit Datenverarbeitungspaketen (Teil 6): Zusammenführung von Markt-Feedback und Modellanpassung
In diesem Teil konzentrieren wir uns darauf, wie man Echtzeit-Markt-Feedback – z. B. Live-Handelsergebnisse, Volatilitätsänderungen und Liquiditätsverschiebungen – mit adaptivem Modelllernen zusammenführt, um ein reaktionsfähiges und selbstverbesserndes Handelssystem zu erhalten.
MQL5-Handelswerkzeuge (Teil 10): Aufbau eines Strategieverfolgungssystems mit visuellen Ebenen und Erfolgsmetriken MQL5-Handelswerkzeuge (Teil 10): Aufbau eines Strategieverfolgungssystems mit visuellen Ebenen und Erfolgsmetriken
In diesem Artikel entwickeln wir ein MQL5-Strategie-Trackersystem, das das Kreuzen von gleitenden Durchschnitte erkennt, die von einem langfristigen MA gefiltert werden, Handelsgeschäfte mit konfigurierbaren TP-Levels und SL in Punkten simuliert oder ausführt und Ergebnisse wie TP/SL-Treffer zur Leistungsanalyse überwacht.
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.
Entwicklung einer Handelsstrategie: Die Triple-Sinus-Mittelwertumkehrmethode Entwicklung einer Handelsstrategie: Die Triple-Sinus-Mittelwertumkehrmethode
In diesem Artikel wird die Methode der Triple Sine Mean Reversion vorgestellt, eine Handelsstrategie, die auf einem neuen mathematischen Indikator basiert – dem Triple Sine Oscillator (TSO). Der TSO ist von der kubischen Sinusfunktion abgeleitet, die zwischen -1 und +1 oszilliert und sich daher zur Erkennung überkaufter und überverkaufter Marktbedingungen eignet. Insgesamt zeigt die Studie, wie mathematische Funktionen in praktische Handelsinstrumente umgewandelt werden können.