English
preview
Klassische Strategien neu interpretiert (Teil 20): Moderne stochastische Oszillatoren

Klassische Strategien neu interpretiert (Teil 20): Moderne stochastische Oszillatoren

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

Der Stochastik-Oszillator ist ein bekannter technischer Indikator, der traditionell verwendet wird, um potenzielle Marktumkehrungen zu erkennen. In seiner klassischen Form misst er die Dynamik der Kursbewegungen innerhalb einer bestimmten Spanne. Wenn die Preisentwicklung in extreme Bereiche vordringt, wird der Markt im Allgemeinen als überkauft oder überverkauft betrachtet. Nach dieser konventionellen Interpretation suchen Händler bei überkauften Bedingungen nach Verkaufsgelegenheiten und bei überverkauften Bedingungen nach Kaufgelegenheiten, wobei sie davon ausgehen, dass sich die Preise schließlich wieder dem Mittelwert annähern werden.

Dieser Ansatz wird seit vielen Jahren erfolgreich angewandt. Dieser Artikel deutet jedoch darauf hin, dass der stochastische Oszillator möglicherweise übersehene Fähigkeiten hat – insbesondere, dass er effektiver als Trendfolgeindikator und nicht als reines Instrument der Rückkehr zum Mittelwert funktioniert. Wir zeigen, dass der Oszillator mit geringfügigen Anpassungen der Interpretationsregeln zu einer nützlichen Methode für die Identifizierung dominanter Markttrends umfunktioniert werden kann.

Um dies zu unterstützen, überprüfen wir die Signale des Indikators und stellen die klassischen Regeln infrage. Wir stellen einen alternativen Rahmen vor, der zum Kauf in überkauften Situationen und zum Verkauf in überverkauften Situationen ermutigt – eine Idee, die auf den ersten Blick kontraintuitiv erscheinen mag. Wie wir jedoch zeigen, ist der stochastische Oszillator vielseitiger als gemeinhin angenommen und nicht auf eine einzige Interpretationsweise beschränkt.

In früheren Analysen haben wir auch festgestellt, dass der Oszillator besser vorhersehbar ist als die reinen Kursbewegungen. Diese Erkenntnis hat uns dazu veranlasst, den Indikator auf der Suche nach zusätzlichen, bisher ungenutzten Werten genauer zu untersuchen.

Um dies umfassend zu untersuchen, stellen wir fünf verschiedene Versionen einer stochastischen Strategie vor. Obwohl unser letzter Versuch, den maximalen Wert aus dem Indikator zu extrahieren, nicht erfolgreich war, unterstreicht dieses Ergebnis einen wichtigen Punkt: Vier der fünf Strategien haben gut abgeschnitten. Dies veranlasst uns, zu überdenken, wie gut wir einen Indikator wirklich verstehen, von dem viele Händler glauben, dass sie ihn bereits kennen.

Da wir mehrere Versionen der Strategie bewerten, müssen mehrere Parameter in allen Versionen gleichbleiben. Jede Version führt einen Handel nach dem anderen aus, um sicherzustellen, dass Unterschiede in der Rentabilität nur auf Änderungen der Handelsregeln zurückzuführen sind. Alle Strategien verwenden die gleiche Positionsgröße, und jeder Backtest wird über das gleiche historische Fenster von 2021 bis 2025 laufen. 

Abbildung 1: Die Backtest-Daten, die wir für alle Versionen unserer Strategie verwenden werden

Schließlich wird jede Version unter zufälligen Ausführungsverzögerungen getestet, um sich der realen Handelslatenz anzunähern.

Abbildung 2: Die Testbedingungen, unter denen wir unsere Strategien testen werden, sind den realen Marktbedingungen nachempfunden


Festlegung einer Basislinie

Wir beginnen damit, dass wir zunächst wichtige globale Variablen definieren, die wir für unsere Diskussion benötigen.

//+------------------------------------------------------------------+
//|                                          Stochastic Strategy.mq5 |
//|                                  Copyright 2025, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
int      stoch_handler,atr_handler;
double   stoch_main_reading[],stoch_signal_reading[],atr_reading[];
double   bid,ask;

Als Nächstes importieren wir die Handelsbibliothek, die uns bei der Verwaltung unserer Positionen hilft.

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

Wenn die Handelsanwendung zum ersten Mal initialisiert wird, definieren wir die technischen Indikatoren, die für unsere Strategie erforderlich sind.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Setup our technical indicators
atr_handler    = iATR(Symbol(),PERIOD_D1,14);
stoch_handler  = iStochastic(Symbol(),PERIOD_D1,5,3,3,MODE_SMA,STO_LOWHIGH);
//---
   return(INIT_SUCCEEDED);
  }

Wenn die Handelsanwendung nicht mehr verwendet wird, geben wir die technischen Indikatoren frei, auf die man sich verlassen hat.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   IndicatorRelease(atr_handler);
   IndicatorRelease(stoch_handler);
  }

Jedes Mal, wenn neue Preisniveaus empfangen werden, werden die entsprechenden Indikatorpuffer und globalen Variablen aktualisiert. Wir setzen zunächst die traditionelle Interpretation des Stochastik-Oszillators um: Kauf bei überverkauften Bedingungen und Verkauf bei überkauften Bedingungen.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Keep track of the time
datetime        current_time   = iTime(Symbol(),PERIOD_D1,0);
static datetime time_stamp;
   
   if(current_time != time_stamp)
      {
         //--- Update the time
         time_stamp = current_time;
         
         //--- Update our technical indicators
         CopyBuffer(stoch_handler,0,0,1,stoch_main_reading);
         CopyBuffer(stoch_handler,1,0,1,stoch_signal_reading);
         CopyBuffer(atr_handler,0,0,1,atr_reading);
         ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);
         bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);
         
         //--- Trading rules
         if(PositionsTotal() == 0)
            {
               if(stoch_main_reading[0] < 20) Trade.Buy(0.01,Symbol(),ask,(ask - (atr_reading[0]*2)),(ask + (atr_reading[0]*2)));
               
               if(stoch_main_reading[0] > 80) Trade.Sell(0.01,Symbol(),bid,(bid + (atr_reading[0]*2)),(bid - (atr_reading[0]*2)));
            }
      }
   
  }
//+------------------------------------------------------------------+

Die von dieser Benchmark erzeugte Kapitalkurve ist volatil und bietet wenig Vertrauen in die Integrität der vorgeschlagenen Strategie. Normalerweise würde man eine solche Strategie aufgeben, aber wir schlagen vor, dass noch nicht alle Hoffnung verloren ist.

Abbildung 3: Die Kapitalkurve, die sich aus der traditionellen Interpretation des stochastischen Oszillators ergibt, erscheint unzuverlässig

Darüber gingen 49 % der Handelsgeschäfte dieser Strategie verloren, was zu einem Kapitalverlust während des Backtest-Zeitraums führte.

Abbildung 4: Die detaillierten Statistiken der klassischen Strategie lassen erheblichen Raum für Verbesserungen


Verbesserung über die Basislinie hinaus

In unserem Lösungsvorschlag interpretieren wir den Indikator als Instrument zur Trenderkennung neu. Der vorherrschende Trend, den wir in einem Markt identifizieren, wird dadurch definiert, wohin sich die aktuellen Marktpreise in Bezug auf die beobachtete Spanne des Marktes entwickeln: Wir beginnen mit der Erstellung von Vektoren, um zuvor beobachtete Höchst- und Tiefstwerte aufzuzeichnen.

vector   high,low;

Anschließend berechnen wir den Mittelpunkt der Handelsspanne über die letzten 90 Tageskerzen. Der Wert von 90 wurde willkürlich gewählt, da er den typischen Geschäftszyklen der institutionellen Marktteilnehmer entspricht, die die Devisenmärkte beherrschen.

//--- Calculate the middle of the trading range
high.CopyRates(Symbol(),PERIOD_D1,COPY_RATES_HIGH,0,90);
low.CopyRates(Symbol(),PERIOD_D1,COPY_RATES_LOW,0,90);
double mid = ((high.Mean() + low.Mean())/2);

Wenn keine Positionen offen sind, versuchen wir zunächst, bei überkauften Kursniveaus Kaufpositionen einzugehen. Ferner hinaus benötigen wir eine weitere Bestätigung durch die Beobachtung des Schlusskurses über dem Mittelpunkt der beobachteten Hoch-Tief-Spanne.

//--- Trading rules
if(PositionsTotal() == 0)
   {
      if((stoch_main_reading[0] > 80) && (iClose(Symbol(),PERIOD_D1,0) > mid)) Trade.Buy(0.01,Symbol(),ask,(ask - (atr_reading[0]*2)),(ask + (atr_reading[0]*2)));
      
      if((stoch_main_reading[0] < 20) && (iClose(Symbol(),PERIOD_D1,0) < mid)) Trade.Sell(0.01,Symbol(),bid,(bid + (atr_reading[0]*2)),(bid - (atr_reading[0]*2)));
   }

Die unter diesen Bedingungen realisierte Kapitalkurve zeigt nun einen deutlichen Aufwärtstrend im Vergleich zu der volatilen und unterdurchschnittlichen Benchmark, mit der wir begonnen haben.

Abbildung 5: Die durch unsere verfeinerte Anwendung des stochastischen Oszillators erzeugte Kapitalkurve

Die detaillierten Statistiken zeigen eine deutliche Verbesserung. Der Gesamtnettogewinn stieg drastisch auf 184,35 $, verglichen mit einer Benchmarkleistung von 26,22 $. Ferner stieg der Anteil der Handelsgeschäfte mit Gewinn von 49 % im ersten Versuch auf derzeit 55 %.

Abbildung 6: Die detaillierten Statistiken, die wir aus unserer überarbeiteten Strategie gewonnen haben, zeigen, dass die von uns vorgenommenen Änderungen sinnvoll waren


Höhere Leistungsniveaus anstreben

Durch eine sorgfältige Kerzenanalyse auf niedrigeren Zeitrahmen können wir die Strategie noch sinnvoll verbessern. Der Grund dafür ist, dass sich ein auf dem täglichen Zeitrahmen beobachteter Trend auch in der Spanne zwischen dem Eröffnungs- und dem Schlusskurs auf einem niedrigeren Zeitrahmen widerspiegeln sollte, was uns hilft, sinnvolle Einstiegspunkte zu identifizieren.

vector  open,close;

Auf dem 1-Stunden-Zeitrahmen werden wir die letzten 12 Kerzen kopieren, um den vorherrschenden Trend des Tages zu bestimmen. 

//--- Calculate the current trend on the lower time frame
 open.CopyRates(Symbol(),PERIOD_H1,COPY_RATES_OPEN,0,12);
 close.CopyRates(Symbol(),PERIOD_H1,COPY_RATES_CLOSE,0,12);
Unsere überarbeiteten Handelsregeln bilden zusammen einen Filter, der aus drei Anforderungen besteht. Jede Anforderung verstärkt die Vorstellung, dass ein einziger dominanter Trend auf dem Markt aktiv sein kann.
//--- Trading rules
if(PositionsTotal() == 0)
   {
      if((stoch_main_reading[0] > 80) && (iClose(Symbol(),PERIOD_D1,0) > mid) && (open.Mean() < close.Mean())) Trade.Buy(0.01,Symbol(),ask,(ask - (atr_reading[0]*2)),(ask + (atr_reading[0]*2)));
               
      if((stoch_main_reading[0] < 20) && (iClose(Symbol(),PERIOD_D1,0) < mid) && (open.Mean() > close.Mean())) Trade.Sell(0.01,Symbol(),bid,(bid + (atr_reading[0]*2)),(bid - (atr_reading[0]*2)));
   }

Die Aktienkurve, die sich aus dieser verfeinerten Strategie ergibt, wächst nun gleichmäßiger, im Gegensatz zu der zerklüfteten und volatilen Kapitalkurve, die sich aus unserem ersten Versuch ergab.

Abbildung 7: Unsere überarbeitete Strategie schneidet deutlich besser ab als unsere bisherige Bestmarke

Der Gesamtnettogewinn ist weiter auf 223 $ gestiegen, während sich die Sharpe Ratio von einem vorherigen Wert von 0,69 auf 0,88 verbesserte. Die Gesamtzahl der Abschlüsse ging von 123 auf 118 zurück. Ein deutlicher Hinweis auf eine verbesserte Effizienz ist die Fähigkeit, das gleiche Ziel mit weniger Aufwand zu erreichen. Mit den durchgeführten Änderungen scheint dieses Ziel erreicht worden zu sein. Außerdem stieg der Prozentsatz der gewinnbringenden Handelsgeschäfte auf einen neuen Höchststand von 56 %.

Abbildung 8: Die detaillierten Ergebnisse der dritten Iteration unserer Stochastik-Oszillator-Strategie


Algorithmische Entdeckung von Handelsregeln anhand des stochastischen Oszillators

Bislang haben wir uns mit der manuellen Festlegung von Handelsregeln und Marktfiltern beschäftigt, um die Ausführung von Handelsgeschäften zu steuern. Dieser Prozess ist zwar eine wertvolle Übung in kreativem Denken und Marktüberlegungen, doch gibt es eine natürliche Grenze dafür, wie weit uns die menschliche Intuition allein bringen kann.

In den Marktdaten können aussagekräftige Muster, Regeln und Entscheidungslogiken vorhanden sein, die nicht sofort intuitiv sind oder vom menschlichen Verstand leicht erkannt werden. Um diese Möglichkeit zu erforschen, wenden wir uns nun algorithmischen Methoden zur Entdeckung zusätzlicher Regeln zu – insbesondere Regeln zur Interpretation des stochastischen Oszillators.

Dazu schreiben wir ein MQL5-Skript, das historische EUR/USD-Marktdaten zusammen mit den Werten des Stochastik-Oszillator-Indikators abruft. Diese Daten werden dann im CSV-Format exportiert. Der Datensatz enthält die standardmäßigen OHLC-Kurse (Open, High, Low und Close), gefolgt von den Puffern der stochastischen Indikatoren: der %K-Linie (Hauptlinie) und der %D-Linie (Signallinie).

Schließlich werden die stochastischen Daten manuell bearbeitet, um den Datensatz zu erweitern. Dazu gehören die Berechnung des Mittelpunkts zwischen der %K- und der %D-Linie, die Messung des Abstands des Hauptmesswerts von den Schwellenwerten 80 und 20 und die Ableitung zusätzlicher Beobachtungen, die helfen, das Verhalten des Indikators zu erfassen. Zusammen bilden diese Merkmale eine hochdimensionale Darstellung des stochastischen Oszillators, die eine anspruchsvollere algorithmische Analyse ermöglicht.

//+------------------------------------------------------------------+
//|                                          Fetch Data Stochastic 2 |
//|                                      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

//--- File name
string file_name = Symbol() + " Stochastic Strategy.csv";

int stoch_handler = iStochastic(Symbol(),PERIOD_CURRENT,5,3,3,MODE_EMA,STO_LOWHIGH);
double stoch_main[],stoch_signal[];
double stoch_o,stoch_h,stoch_l;

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

//+------------------------------------------------------------------+
//| Our script execution                                             |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Write to file
   int file_handle=FileOpen(file_name,FILE_WRITE|FILE_ANSI|FILE_CSV,",");

//--- 
   CopyBuffer(stoch_handler,0,0,size,stoch_main);
   stoch_o = stoch_main[0];
   stoch_h = stoch_main[0];
   stoch_l = stoch_main[0];
   ArraySetAsSeries(stoch_main,true);
   CopyBuffer(stoch_handler,1,0,size,stoch_signal);
   ArraySetAsSeries(stoch_signal,true);
   
   for(int i=size;i>=1;i--)
     {
      if(i == size)
        {
        
         FileWrite(file_handle,
                  //--- Time
                  "Time",
                   //--- OHLC
                   "Open",
                   "High",
                   "Low",
                   "Close",
                   //--- Stochastic Readings
                   "Stochastic Main",
                   "Stochastic Signal",
                   //--- Feature Engineering Stochastic Oscilator
                   "Stoch Main - Signal",
                   "Stoch M-S Mid",
                   "Stoch - 80",
                   "Stoch - 20",
                   "Stoch O",
                   "Stoch H",
                   "Stoch L",
                   "Stoch O-C",
                   "Stoch H-C",
                   "Stoch L-C"
                  );
        }

      else
        {
        
        //--- Set features
        stoch_h = (stoch_h < stoch_main[i]) ? stoch_main[i] : stoch_h;
        stoch_l = (stoch_l > stoch_main[i]) ? stoch_main[i] : stoch_l;
        
         FileWrite(file_handle,
                   iTime(_Symbol,PERIOD_CURRENT,i),
                   //--- OHLC
                   iOpen(_Symbol,PERIOD_CURRENT,i),
                   iHigh(_Symbol,PERIOD_CURRENT,i),
                   iLow(_Symbol,PERIOD_CURRENT,i),
                   iClose(_Symbol,PERIOD_CURRENT,i),
                   //--- Stochastic Readings
                   stoch_main[i],
                   stoch_signal[i],
                   //--- Stochastic Feature Engineering
                   stoch_main[i] - stoch_signal[i],
                   ((stoch_main[i] + stoch_signal[i])/2),
                   (stoch_main[i] - 80),
                   (stoch_main[i] - 20),
                   stoch_o,
                   stoch_h,
                   stoch_l,
                   stoch_o - stoch_main[i],
                   stoch_h - stoch_main[i],
                   stoch_l - stoch_main[i]
                   );
        }
     }
//--- Close the file
   FileClose(file_handle);
  }
//+------------------------------------------------------------------+


Analyse unserer Marktdaten in Python

Nachdem wir nun unsere CSV-Datei mit historischen Marktdaten erstellt haben, können wir die Daten in Python analysieren. Importieren Sie zunächst die benötigten Python-Bibliotheken.

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

Als Nächstes lesen wir die CSV-Datei ein, die wir geschrieben haben.

data = pd.read_csv("./EURUSD Stochastic Strategy.csv")
data

Wir trennen die Trainingsdaten von den Testdaten, die für die Backtests in MetaTrader 5 reserviert ist.

train = data.iloc[:-(365 * 5),:]
test = data.iloc[-(365 * 5):,:]

Wir laden die benötigten Bibliotheken für maschinelles Lernen 

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

und legen den Prognosehorizont fest.

HORIZON = 5

Wir erstellen ein Zeitreihen-Kreuzvalidierungsobjekt, das uns helfen wird, die Genauigkeit der einzelnen Modelle zu bewerten, die wir in Betracht ziehen.

tscv = TimeSeriesSplit(n_splits=10,gap=HORIZON)

Wir kennzeichnen unseren Datensatz. Damit können wir den Wert des stochastischen Hauptoszillators prognostizieren.

data['Target'] = data['Stochastic Main'].shift(-HORIZON)
data = data.iloc[:-HORIZON,:]

Vergleichen wir die Genauigkeit, die mit den klassischen OHLC-Spalten, den neuen stochastischen Merkmalen, die wir entwickelt haben, und schließlich mit einer Kombination aus beiden erzielt wird.

X_classic = data.iloc[:,1:7].columns
X_new     = data.iloc[:,7:-1].columns
X_all     = data.iloc[:,1:-1].columns
y         = 'Target'

Wir werden die beobachteten Genauigkeitsniveaus aufzeichnen, wenn wir die Eingaben, die wir unserem Modell zuführen, ändern.

scores = []

Wir behalten das zugrunde liegende Modell bei, um sicherzustellen, dass die Fehleränderungen auf die von uns gewählten Eingaben zurückzuführen sind.

model = LinearRegression()

Erfassen wir die Genauigkeit, die mit jedem möglichen Satz von Eingaben verbunden ist, die uns zur Verfügung stehen.

scores.append(np.mean(np.abs(cross_val_score(model,data.loc[:,X_classic],data.loc[:,y],cv=tscv,scoring='neg_mean_squared_error'))))
scores.append(np.mean(np.abs(cross_val_score(model,data.loc[:,X_new],data.loc[:,y],cv=tscv,scoring='neg_mean_squared_error'))))
scores.append(np.mean(np.abs(cross_val_score(model,data.loc[:,X_all],data.loc[:,y],cv=tscv,scoring='neg_mean_squared_error'))))

Wenn wir die mit den verschiedenen möglichen Eingabesätzen erzielten Genauigkeitsniveaus aufzeichnen, können wir eindeutig feststellen, dass die von uns entwickelten nutzerdefinierten stochastischen Oszillatorfunktionen die niedrigsten Fehlerwerte ergeben, die wir für möglich gehalten haben.

sns.barplot(np.abs(scores),color='black')
plt.axhline(np.min(scores),linestyle=':',color='red')
plt.xticks([0,1,2],['OHLC Features','Custom Features','All Features'])


Abbildung 9: Die nutzerdefinierten Funktionen, die wir entwickelt haben, haben uns geholfen, den Hauptpuffer des stochastischen Oszillators besser vorherzusagen

Bei den meisten dynamischen Prozessen sind nicht alle aufgezeichneten Variablen gleichermaßen informativ. Wenn wir herausfinden, welche Variablen die meisten Informationen enthalten, können wir uns in Zukunft auf die Entwicklung von Merkmalen konzentrieren, um reichhaltigere und vielfältigere Variationen der einflussreichsten Merkmale zu erzeugen. Um dies zu quantifizieren, verwenden wir die Regression der gegenseitigen Information. Die gegenseitige Information (Mutual Information, MI) ist ein Maß für die statistische Abhängigkeit, das sowohl lineare als auch nichtlineare Beziehungen erfasst und sich daher gut für die Bewertung jeder Form der Abhängigkeit zwischen zwei Variablen eignet.

from sklearn.feature_selection import mutual_info_regression

Führen wir den statistischen Test durch. Für den Test werden die neuen Merkmale des stochastischen Oszillators, die wir generiert haben, und das aktuelle Ziel, das wir ausgewählt haben, benötigt.

scores = mutual_info_regression(data.loc[:,X_new],data.loc[:,'Target'])

Es hat den Anschein, dass von den 10 nutzerdefinierten Merkmalen, die wir erstellt haben, nur 3 keine sinnvolle Beziehung zum Ziel haben. Dies geht aus den Spalten der MI-Werte hervor, die nahezu 0 sind. Es scheint also eine sinnvolle Beziehung zu geben, die wir algorithmisch aus den Daten, die wir in unserem MQL5-Skript erzeugt haben, entdeckt haben.

sns.barplot(scores,color='black')
plt.axhline(np.mean(scores),color='red',linestyle=':')


Abbildung 10: Die von uns beobachteten MI-Werte deuten darauf hin, dass der von uns erstellte Datensatz aussagekräftig ist und eine Beziehung aufweist, aus der wir lernen können


Exportieren nach ONNX

Wir sind nun bereit, unser trainiertes statistisches Modell in das Open Neural Network Exchange (ONNX)-Format zu exportieren. ONNX ermöglicht die Bereitstellung von Modellen für maschinelles Lernen, ohne dass das ursprüngliche Trainingsframework oder die Programmiersprache erforderlich ist, sodass sie plattform- und umgebungsübergreifend eingesetzt werden können. Zunächst laden wir die für diesen Prozess erforderlichen ONNX-Bibliotheken.

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

Wir geben die Eingabeform unseres Modells an,

initial_types = [('float input',FloatTensorType([1,len(X_new)]))]

passen das Modell an die Trainingsdaten an

model = GradientBoostingRegressor()

model.fit(train.loc[:,X_new],train.iloc[:,-1])

und speichern das Modell als ONNX-Prototyp. 

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

Den ONNX-Prototyp speichern wir auf der Festplatte als ONNX-Datei.

onnx.save(onnx_proto,'EURUSD Stochastic GBR AI.onnx')


Implantierung der Verbesserungen

Ändern wir nun diese Handelsstrategie, um unsere ONNX-Datei einzubeziehen.

//+------------------------------------------------------------------+
//|                                               Stochastic AI.mq5  |
//|                                  Copyright 2025, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#resource "\\Files\\EURUSD Stochastic GBR AI.onnx" as const uchar onnx_proto[];

Wir definieren globale Variablen, die wir für die Handhabung unseres ONNX-Modells in unserer Anwendung verwenden werden

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
vectorf  model_inputs,model_outputs;
long     model;

und geben die Systemkonstanten an. Diese Konstanten geben die Anzahl der Eingänge und Ausgänge an, die unser Modell hat.

//+------------------------------------------------------------------+
//| System Definitions                                               |
//+------------------------------------------------------------------+
#define MODEL_INPUT_SHAPE  10
#define MODEL_OUTPUT_SHAPE 1

Wenn unsere Anwendung initialisiert wird, laden wir unsere technischen Indikatoren und behalten auch die nutzerdefinierten Stochastik-Oszillator-Merkmale im Auge, die wir im Trainingsset generiert haben, wie z. B. die Höchst- und Tiefstwerte aller Zeiten. Dann werden wir unser ONNX-Modell konfigurieren und sicherstellen, dass es angemessen eingerichtet ist und funktioniert. 

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Setup our indicators
   atr_handler     = iATR("EURUSD",PERIOD_D1,14);
   stoch_handler   = iStochastic(Symbol(),PERIOD_CURRENT,5,3,3,MODE_EMA,STO_LOWHIGH);
   stoch_o = 22.69153;
   stoch_h = 98.551023;
   stoch_l = 1.372058;

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

//--- Define the model parameter shape
   ulong input_shape[] = {1,MODEL_INPUT_SHAPE};
   ulong output_shape[] = {1,MODEL_OUTPUT_SHAPE};

   if(!OnnxSetInputShape(model,0,input_shape))
     {
      Print("ONNX Model Error: Incorrect Input Shape ",GetLastError());
      return(INIT_FAILED);
     }

   if(!OnnxSetOutputShape(model,0,output_shape))
     {
      Print("ONNX Model Error: Incorrect Output Shape ",GetLastError());
      return(INIT_FAILED);
     }

   model_inputs = vectorf::Zeros(MODEL_INPUT_SHAPE);
   model_outputs = vectorf::Zeros(MODEL_OUTPUT_SHAPE);

   if(model != INVALID_HANDLE)
     {
      return(INIT_SUCCEEDED);
     }

//---
   return(INIT_FAILED);
  }

Wenn unsere Handelsanwendung nicht mehr läuft, werden wir die technischen Indikatoren und das ONNX-Modell, das wir nicht mehr verwenden, freigeben.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Free up memory we are no longer using when the application is off
   IndicatorRelease(atr_handler);
   IndicatorRelease(stoch_handler);
   OnnxRelease(model);
  }

Wenn wir neue Preisniveaus erhalten, führen wir unsere manuellen Handelsregeln aus und ergänzen sie mit den von unserem statistischen Modell gelernten Handelssignalen.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- When price levels change

   datetime current_time = iTime("EURUSD",PERIOD_D1,0);
   static datetime  time_stamp;

//--- Update the time
   if(current_time != time_stamp)
     {
     
      time_stamp = current_time;

      //--- Calculate the middle of the trading range produced over the last business cycle
      high.CopyRates(Symbol(),PERIOD_D1,COPY_RATES_HIGH,0,90);
      low.CopyRates(Symbol(),PERIOD_D1,COPY_RATES_LOW,0,90);
      double mid = ((high.Mean() + low.Mean())/2);

      //--- Calculate the current trend on the lower time frame
      open.CopyRates(Symbol(),PERIOD_H1,COPY_RATES_OPEN,0,12);
      close.CopyRates(Symbol(),PERIOD_H1,COPY_RATES_CLOSE,0,12);

      //--- Fetch indicator current readings
      CopyBuffer(atr_handler,0,0,1,atr_reading);
      CopyBuffer(stoch_handler,0,0,1,stoch_main);
      CopyBuffer(stoch_handler,1,0,1,stoch_signal);

      //--- Setting model inputs
      stoch_h = (stoch_h < stoch_main[0]) ? stoch_main[0] : stoch_h;
      stoch_l = (stoch_l > stoch_main[0]) ? stoch_main[0] : stoch_l;

      model_inputs[0] = (float)(stoch_main[0] - stoch_signal[0]);
      model_inputs[1] = (float)(((stoch_main[0] + stoch_signal[0])/2));
      model_inputs[2] = (float)((stoch_main[0] - 80));
      model_inputs[3] = (float)((stoch_main[0] - 20));
      model_inputs[4] = (float)(stoch_o);
      model_inputs[5] = (float)(stoch_h);
      model_inputs[6] = (float)(stoch_l);
      model_inputs[7] = (float)(stoch_o - stoch_main[0]);
      model_inputs[8] = (float)(stoch_h - stoch_main[0]);
      model_inputs[9] = (float)(stoch_l - stoch_main[0]);

      ask = SymbolInfoDouble("EURUSD",SYMBOL_ASK);
      bid = SymbolInfoDouble("EURUSD",SYMBOL_BID);

      //--- If we have no open positions
      if(PositionsTotal() == 0)
        {

         if(!(OnnxRun(model,ONNX_DATA_TYPE_FLOAT,model_inputs,model_outputs)))
           {
            Comment("Failed to obtain a forecast from our model: ",GetLastError());
           }

         else
           {
            Comment("Forecast: ",model_outputs);

            //--- Trading rules
            if((model_outputs[0] > stoch_main[0]) && (stoch_main[0] > 80) && (iClose(Symbol(),PERIOD_D1,0) > mid) && (open.Mean() < close.Mean()))
              {
               //--- Buy signal
               Trade.Buy(0.01,"EURUSD",ask,ask-(atr_reading[0] * 2),ask+(atr_reading[0] * 2),"");
              }

            else
               if((model_outputs[0] < stoch_main[0]) && (stoch_main[0] < 20) && (iClose(Symbol(),PERIOD_D1,0) < mid) && (open.Mean() > close.Mean()))
                 {
                  //--- Sell signal
                  Trade.Sell(0.01,"EURUSD",bid,bid+(atr_reading[0] * 2),bid-(atr_reading[0] * 2),"");
                 }
           }
        }
     }
  }
//+------------------------------------------------------------------+

Schließlich sollten Sie alle Systemdefinitionen, die Sie in MQL5 vornehmen, rückgängig machen; dies ist eine gute Praxis für Entwickler.

#undef MODEL_INPUT_SHAPE
#undef MODEL_OUTPUT_SHAPE

Betrachtet man die Kapitalkurve, die sich aus unserer überarbeiteten Strategie ergibt, so zeigt sich, dass wir zu viel Rauschen in das System eingebracht haben. Die Kurve hat ihren zuvor gleichmäßigen Aufwärtstrend verloren und sieht nun volatiler aus, als uns lieb ist. 

Abbildung 11: Die Kapitalkurve, die wir durch die letzte Iteration unserer Handelsstrategie erhalten haben, scheint verrauschte Signale zu enthalten

Obwohl die Strategie nach wie vor rentabel ist, ist die Performance gegenüber dem letzten Höchststand von 223 $ deutlich zurückgegangen. Dies bedeutet nicht, dass der stochastische Oszillator als Grundlage für statistische Handelsstrategien unbrauchbar ist, sondern unterstreicht vielmehr die Notwendigkeit einer sorgfältigeren und strengeren Methodik seitens der Praktiker. Ferner können wir beobachten, dass die Strategie nicht kauft und eine Vorliebe entwickelt hat, zu verkaufen.

Für neue Leser mögen diese Ergebnisse unerwartet erscheinen. Bei der Entwicklung unseres statistischen Modells haben wir deutliche Verbesserungen bei den Fehlermetriken festgestellt, wenn wir nutzerdefinierte stochastische Merkmale verwenden. Zurückgekehrte Leser werden dieses Muster jedoch erkennen.

Wie in unserer Schwester-Artikelserie „Die Grenzen des maschinellen Lernens überwinden (Teil 1)“ gezeigt: Mangels interoperabler Metriken spiegeln die statistischen Metriken, die zum Trainieren von Modellen verwendet werden, oft nicht die realen Ziele des Handels wider. Folglich lassen sich Verbesserungen des statistischen Fehlers nicht zuverlässig in eine bessere Handelsleistung umsetzen.

In der Praxis ist die moderne statistische Modellierung oft ein Prozess, der an Versuch und Irrtum erinnert. Daher sollten Leser, die trotz sorgfältiger Analyse schlechte Handelsergebnisse erzielen, dies nicht als Ausdruck ihrer mangelnden Fähigkeiten interpretieren. Einen Link zu dem entsprechenden Artikel finden Sie hier. An diesem Punkt ist es vernünftig, zu dem Schluss zu kommen, dass übermäßiges Rauschen in das Handelssystem eingebracht wurde, und wir kehren daher zu Version 3 als der leistungsfähigsten Iteration der Anwendung zurück.

Abbildung 12: Die detaillierte statistische Analyse der letzten Iteration unserer stochastischen Handelsstrategie



Schlussfolgerung

Dieser Artikel zeigt, wie ein klassischer technischer Indikator über seine herkömmliche Verwendung hinaus umfunktioniert werden kann. Die Leser erhalten einen Einblick, wie vertraute Strategien neue Werte schaffen können, wenn sie durch eine andere analytische Linse betrachtet werden, und wie neue Paradigmen und Handelsregeln durch einen durchdachten Prozess von Versuch und Irrtum entstehen können. Letztlich birgt jeder technische Indikator im Terminal MetaTrader 5 ungenutztes Potenzial – die Herausforderung besteht darin, die sinnvollen Interpretationen, die dem Blick verborgen bleiben, automatisch aufzudecken.

Dateiname Beschreibung der Datei
Stochastic_Strategy.mq5  Die traditionelle Version der Stochastik-Oszillator-Strategie erwies sich beim Backtest als unrentabel.
Stochastic_Strategy_2.mq5 Unsere erste Iteration unserer Anwendung, die sich auf die tägliche Spanne stützte, um klare Trendrichtungen festzulegen.
Stochastic_Strategy_3.mq5 Die profitabelste Version unserer Handelsanwendung verwendet zusätzlich zu den vorherigen Änderungen eine Analyse mit niedrigerem Zeitrahmen.
Stochastic_AI.mq5 Die zweitprofitabelste Version unserer Handelsstrategie schien zu viel Rauschen in ihren Signalen zu haben.
Stochastic_Strategy.ipynb  Das Jupyter-Notebook, das wir geschrieben haben, um unsere historischen EURUSD-Marktdaten und nutzerdefinierten Funktionen zu analysieren.
Fetch_Data_Stochastic_2.mq5 Das MQL5-Skript, das wir geschrieben haben, um OHLC EURUSD-Daten und andere nutzerdefinierte Beobachtungen über den Stochastik-Oszillator zu holen.

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

Die Übertragung der Trading-Signale in einem universalen Expert Advisor. Die Übertragung der Trading-Signale in einem universalen Expert Advisor.
In diesem Artikel wurden die verschiedenen Möglichkeiten beschrieben, um die Trading-Signale von einem Signalmodul des universalen EAs zum Steuermodul der Positionen und Orders zu übertragen. Es wurden die seriellen und parallelen Interfaces betrachtet.
Codex-Pipelines: Von Python zu MQL5 für die Indikatorauswahl – eine Multi-Quartal-Analyse des FXI ETF Codex-Pipelines: Von Python zu MQL5 für die Indikatorauswahl – eine Multi-Quartal-Analyse des FXI ETF
Wir setzen unseren Blick darauf fort, wie MetaTrader außerhalb seiner „Komfortzone“ für den Devisenhandel eingesetzt werden kann, indem wir einen weiteren handelbaren Vermögenswert in Form des FXI ETFs betrachten. Im Gegensatz zum letzten Artikel, in dem wir versucht haben, „zu viel“ zu tun, indem wir uns nicht nur mit der Auswahl von Indikatoren, sondern auch mit der Kombination von Indikatormustern beschäftigt haben, werden wir in diesem Artikel etwas flussaufwärts schwimmen und uns mehr auf die Auswahl von Indikatoren konzentrieren. Unser Endprodukt ist als eine Art Pipeline gedacht, die dabei helfen kann, Indikatoren für verschiedene Vermögenswerte zu empfehlen, vorausgesetzt, wir verfügen über einen angemessenen Teil ihrer Kurshistorie.
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.
Automatisiertes Risikomanagement für das Bestehen der Herausforderungen von Prop-Firmen Automatisiertes Risikomanagement für das Bestehen der Herausforderungen von Prop-Firmen
Dieser Artikel erläutert den Aufbau eines Expert Advisors für GOLD, der für Prop-Firmen entwickelt wurde und sich durch Breakout-Filter, eine Analyse über mehrere Zeitrahmen, ein robustes Risikomanagement sowie einen strengen Schutz vor Drawdowns auszeichnet. Der EA hilft Händlern, die Herausforderungen von Prop-Firmen zu bestehen, indem er Regelverstöße vermeidet und die Handelsausführung unter volatilen Marktbedingungen stabilisiert.