English Русский 中文 Español Português
preview
Untersuchung von Regressionsmodellen für kausale Inferenz und algorithmischen Handel

Untersuchung von Regressionsmodellen für kausale Inferenz und algorithmischen Handel

MetaTrader 5Integration |
30 15
[Gelöscht]

Einführung

Im Laufe mehrerer Artikel haben wir verschiedene Methoden zur Klassifizierung von Zeitreihen untersucht, aber wir sind nicht auf Regressionsmodelle eingegangen. Regressionsmodelle ermöglichen es uns im Gegensatz zur binären Klassifizierung, nicht die Wahrscheinlichkeit vorherzusagen, mit der eine Beobachtung zu einer bestimmten Klasse gehört, sondern kontinuierliche Werte, was die Möglichkeiten ihrer Anwendung zur Erstellung automatisierter Handelssysteme erweitert.

Die binäre Klassifizierung ist eine grundlegende Aufgabe des maschinellen Lernens, bei der das Ziel darin besteht, Eingabedaten in eine von zwei verschiedenen Kategorien oder Klassen zu klassifizieren. Im Kontext eines Forex-Handelsbots bedeutet dies normalerweise die Vorhersage eines „Kauf“- (dargestellt als 0) oder „Verkauf“-Signals (dargestellt als 1). Dieser Ansatz reduziert die komplexe Marktdynamik auf eine einfache Richtungsentscheidung.

Die bedeutendste inhärente Einschränkung der binären Klassifizierung für den quantitativen Handel ist ihre Unfähigkeit, das Ausmaß oder die Intensität einer vorhergesagten Kursbewegung zu quantifizieren. Ein binärer Klassifikator gibt nur an, ob sich der Kurs voraussichtlich nach oben oder unten bewegen wird, ohne Informationen darüber zu liefern, wie stark er sich voraussichtlich ändern wird. Dieser Mangel an Granularität schränkt die Entscheidungsfindung im Handel grundlegend ein.

Die Genauigkeit der Vorhersagen des Klassifikators allein berücksichtigt nicht das Ausmaß der Änderung und ist daher für den Handel nicht sehr nützlich. Dieser Aspekt ist entscheidend, da er verdeutlicht, dass eine hohe Richtungsgenauigkeit (z. B. die korrekte Vorhersage der Richtung in 70 % der Fälle) nicht automatisch zu profitablem Handel führt. 

Wichtig ist die Beobachtung, dass eine hohe Richtungsgenauigkeit keine Rentabilität garantiert. Sie können zum Beispiel in 30 % der Fälle richtig liegen und profitabel sein, oder Sie können in 70 % der Fälle richtig liegen und unprofitabel sein. Dies zeigt, dass das Nettoergebnis einer Handelsstrategie durch die Höhe des Gewinns bei gewinnbringenden Trades im Vergleich zur Höhe des Verlusts bei verlustbringenden Trades bestimmt wird und nicht einfach durch den Prozentsatz der Gewinne.

Indem das binäre Klassifikationsmodell alle korrekten Richtungsprognosen unabhängig von der tatsächlichen Kursbewegung gleich behandelt, kann es nicht zwischen einer kleinen, unbedeutenden Kursbewegung und einer großen, hochprofitablen unterscheiden. Dies kann zu einem Szenario führen, in dem viele kleine gewinnbringende Trades durch wenige große verlustbringende Trades ausgeglichen werden oder umgekehrt, was trotz scheinbar hoher Genauigkeit zu einem insgesamt negativen PnL führt.

Das Fehlen einer Quantifizierung der Kursbewegung bedeutet, dass der Handelsbot keine Trades mit höheren erwarteten Gewinnen priorisieren oder Trades vermeiden kann, bei denen der potenzielle Verlust den potenziellen Gewinn deutlich überwiegt, selbst wenn die Richtung korrekt vorhergesagt wurde. Ohne Informationen über das Ausmaß arbeitet der Bot mit einem blinden Fleck bezüglich der tatsächlichen finanziellen Auswirkungen seiner Entscheidungen, was trotz eines hohen Prozentsatzes an richtungsbasierten Gewinnen zu suboptimalen oder sogar negativen kumulativen Renditen führt.


Änderung der Labeling-Funktion

Betrachten Sie das folgende Szenario: Es gibt eine finanzielle Zeitreihe, die auf Basis einer Reihe von Merkmalen vorhergesagt werden muss. Im Falle einer binären Klassifizierung kann die Richtung eines zukünftigen Trades (Kauf oder Verkauf) bestimmt werden, und diese Labels sind immer festgelegt. Wir können sie nicht anders labeln, um eine genauere Schätzung des Ausmaßes zukünftiger Kursabweichungen zu erhalten. Alle Trades gelten als gleichwertig, unabhängig davon, wie stark sich der Kurs tatsächlich geändert hat.

Stellen Sie sich nun vor, dass wir nicht nur die Richtung eines Handels vorhersagen können, sondern auch das Ausmaß der zukünftigen Änderung. Damit können wir unser Handelssystem feiner abstimmen, indem wir zusätzliche Filter erstellen, die dabei helfen, nur die vorhergesagten Kursschwankungen zu identifizieren, die für den Handel signifikant sind, und unbedeutende auszuschließen.

Um ein Regressionsmodell zu trainieren, ist es notwendig, Merkmale und Ziele für dessen Training vorzubereiten. Die Merkmale können vom Klassifikator und dem Regressor gemeinsam genutzt werden, während sich die Zielvariablen unterscheiden werden.

Schreiben wir eine einfache Funktion, die beispielhaft Labels für ein Regressionsmodell erzeugt.

@njit
def calculate_labels_r(close_data, min_val, max_val):
    labels = []
    for i in range(len(close_data) - max_val):
        rand = random.randint(min_val, max_val)
        labels.append(close_data[i + rand] - close_data[i])
    return labels

def get_labels_r(dataset, min = 1, max = 15) -> pd.DataFrame:
    # Extract closing prices from the dataset
    close_data = dataset['close'].values
    labels = calculate_labels_r(close_data, min, max)
    # Trim the dataset to match the length of calculated labels
    dataset = dataset.iloc[:len(labels)].copy() 
    # Add the calculated labels as a new column
    dataset['labels'] = labels
    # Remove rows with NaN values (potentially introduced in 'calculate_labels')
    dataset = dataset.dropna()
    return dataset

Der Hauptunterschied zur Label-Erzeugung bei der binären Klassifizierung besteht darin, dass wir nun Kursänderungen bestimmen (Subtraktion des aktuellen Kurses vom zukünftigen Kurs), anstatt einfach die Richtung (Kauf oder Verkauf) zu bestimmen. Der Code wird durch Numba beschleunigt, sodass das Ziel-Labeling sehr schnell ist.

Die obige Funktion berücksichtigt nur die Differenz zwischen einem zufällig ausgewählten zukünftigen Kurs im Bereich {min_val; max_val} und dem aktuellen Kurs. Dies ist möglicherweise nicht ganz korrekt, da zwischenzeitliche Abweichungen, die signifikant sein könnten, nicht berücksichtigt werden. Ich schlage eine weitere Modifikation der Abweichungsberechnungsfunktion vor, die unten dargestellt ist.

@njit
def calculate_labels_mean_r(close_data, min_val, max_val):
    labels = []
    for i in range(len(close_data) - max_val):
        # Calculate the average price value in the window from min_val to max_val
        future_prices = close_data[i + min_val : i + max_val + 1]
        mean_future_price = np.mean(future_prices)
        # Calculate the difference between the average future value and the current price
        labels.append(mean_future_price - close_data[i])
    return labels

def get_labels_r(dataset, min = 1, max = 15) -> pd.DataFrame:
    # Extract closing prices from the dataset
    close_data = dataset['close'].values
    # Calculate buy/hold labels based on future price movements
    labels = calculate_labels_mean_r(close_data, min, max)
    # Trim the dataset to match the length of calculated labels
    dataset = dataset.iloc[:len(labels)].copy() 
    # Add the calculated labels as a new column
    dataset['labels'] = labels
    # Remove rows with NaN values (potentially introduced in 'calculate_labels')
    dataset = dataset.dropna()
    return dataset

Nun berücksichtigt die Funktion alle Kursabweichungen im angegebenen Intervall und berechnet dafür den Durchschnitt der Kurse. Danach wird die Differenz zwischen dem Durchschnittswert der zukünftigen Kurse und dem aktuellen Kurs berechnet. Dementsprechend ruft die Funktion get_labels_r() nun die Labeling-Funktion calculate_labels_mean_r() auf und nicht mehr calculate_labels_r() wie zuvor. Wir können experimentieren, indem wir verschiedene Labeling-Funktionen aufrufen.


Hinzufügen eines Systems zur kausalen Inferenz

Für genauere Vorhersagen verwenden wir einen Algorithmus, der dem im Artikel über kausale Inferenz beschriebenen ähnelt. Der Hauptunterschied besteht in der Verwendung eines Regressors anstelle eines Klassifikators.

def meta_learners(data, models_number: int, iterations: int, depth: int):
    data = data.copy()
    data = data[(data.index < hyper_params['forward']) & (data.index > hyper_params['backward'])].copy()

    X = data[data.columns[1:-1]]
    y = data['labels']
    data['meta_labels'] = 0

    for i in range(models_number):
        X_train, X_val, y_train, y_val = train_test_split(
            X, y, train_size = 0.5, test_size = 0.5, shuffle = True)
        
        # fit debias model with train and validation subsets
        meta_m = CatBoostRegressor(iterations = iterations,
                                depth = depth,
                                verbose = False,
                                use_best_model = True)
        
        meta_m.fit(X_train, y_train, eval_set = (X_val, y_val), plot = False)
        
        coreset = X.copy()
        coreset['labels'] = y
        coreset['labels_pred'] = meta_m.predict(X)
        data['meta_labels'] += abs(coreset['labels'] - coreset['labels_pred'])

    data['meta_labels'] = data['meta_labels'] / models_number
    return data

Die Funktion trainiert mehrere Regressoren auf zufälligen Teilmengen von Daten aus dem ursprünglichen Datensatz und vergleicht dann die tatsächlichen Zielwerte mit den vorhergesagten. Daher wird das Metamodell nicht 0 oder 1 (Trade eröffnen oder nicht) vorhersagen, sondern die Durchschnittswerte der Abweichungen der Prognosen von den tatsächlichen Werten. Auf diese Weise können wir Prognosen herausfiltern, die signifikant von den erwarteten Werten abweichen.


Training und Test trainierter Modelle

Zum Testen von Regressionsmodellen wurde der Tester modifiziert. Jetzt verfügt er über das Suffix ‚r‘. Es ist an der Zeit, mehrere Modelle zu trainieren. In diesem Artikel werde ich 10 Modelle trainieren und dasjenige auswählen, das sich aus meiner Sicht am besten eignet.

hyper_params = {
    'symbol': 'EURUSD_H1',
    'export_path': '/Users/dmitrievsky/drive_c/Program Files/MetaTrader 5/MQL5/Include/Trend following/',
    'model_number': 0,
    'markup': 0.00010,
    'stop_loss':  0.00500,
    'take_profit': 0.00200,
    'periods': [i for i in range(5, 100, 30)],
    'backward': datetime(2010, 1, 1),
    'forward': datetime(2024, 1, 1),
}

models = []
for i in range(10):
    print('Learn ' + str(i) + ' model')
    data = get_labels_r(get_features(get_prices()), min=1, max=15)
    dataset = meta_learners(data=data, models_number=5, iterations=15, depth=3)
    models.append(fit_final_models(dataset, tol=3e-2))

Hier sollten wir auf den Parameter tol achten, der an die Funktion zum Training des endgültigen Modells übergeben wird. Da wir das Hauptmodell so robust wie möglich machen wollen, ist es nicht sinnvoll, es mit allen Beispielen zu trainieren. Wir werden es nur mit denjenigen Beispielen trainieren, deren Vorhersagen um weniger als den tol-Wert von den tatsächlichen Werten abweichen.

Da Abweichungen von Vorhersagen tatsächlich in Punkten gezählt werden, bedeutet tol=3e-2 eine maximale Differenz von 0.03 oder 300 vierstelligen Punkten. Es mag als Filter wie ein großer Unterschied erscheinen, aber es ist zu bedenken, dass dies eine Differenz in absoluten Werten ist, da Vorhersagen sowohl positiv als auch negativ sein können. Sie können mit diesem Parameter experimentieren. Nachfolgend finden Sie die Funktion selbst.

def fit_final_models(dataset, tol=1e-2) -> list:
    # features for model\meta models. We learn main model only on filtered labels
    X = dataset[dataset['meta_labels'] < tol]
    X, X_meta = X[X.columns[1:-2]], dataset[dataset.columns[1:-2]]
    # labels for model\meta models
    y = dataset[dataset['meta_labels'] < tol]
    y, y_meta = y[y.columns[-2]], dataset[dataset.columns[-1]]
    
    # fit main model with train and validation subsets
    model = RandomForestRegressor(n_estimators=50, max_depth=10)
    model.fit(X, y)
    # fit meta model with train and validation subsets
    meta_model = RandomForestRegressor(n_estimators=50, max_depth=10)
    meta_model.fit(X_meta, y_meta)

    data = get_features(get_prices())
    R2 = test_model_r(data, 
                    [model, meta_model], 
                    hyper_params['stop_loss'], 
                    hyper_params['take_profit'],
                    hyper_params['forward'],
                    hyper_params['backward'],
                    hyper_params['markup'],
                    plt=False)
    
    if math.isnan(R2):
        R2 = -1.0
        print('R2 is fixed to -1.0')
    print('R2: ' + str(R2))
    result = [R2, model, meta_model]
    return result

Sortieren wir nun die Modelle und rufen die benutzerdefinierte Tester-Funktion auf:

models.sort(key=lambda x: x[0])
data = get_features(get_prices())
test_model_r(data, 
        models[-1][1:], 
        hyper_params['stop_loss'], 
        hyper_params['take_profit'],
        hyper_params['forward'],
        hyper_params['backward'],
        hyper_params['markup'],
        plt=True)

Das Modell ist an die Trainingsdaten überfittet und schneidet bei neuen Daten schlecht ab:

Abb. 1. Testen des Modells mit grundlegendem Labeling

Wir werden genau die gleichen Manipulationen durchführen, aber wir verwenden die Trade-Labeling-Funktion calculate_labels_mean_r(), die zukünftige Durchschnittskurse berechnet.

Abb. 2. Testen des Modells mit gemitteltem Labeling

Die zweite Trade-Labeling-Funktion zeigt im Durchschnitt stabilere Ergebnisse bei neuen Daten. Dies liegt anscheinend daran, dass der Durchschnittswert zukünftiger Kurse berücksichtigt wird.

Der benutzerdefinierte Tester kann keine Schwellenwerte für das Hauptregressionsmodell festlegen, daher unterteilt er die Modellvorhersagen einfach in positive und negative, was bedeutet, dass die Signale immer noch recht grob sind. Aber wir werden dies direkt im Terminal des MetaTrader 5 beheben.


Exportieren von Modellen in das Terminal des MetaTrader 5

Jetzt müssen wir die Modelle im ONNX-Format in das Terminal exportieren und das Handelssystem konfigurieren. Die Exportfunktion sieht vertraut aus:

export_model_to_ONNX(model = models[-1],
                     symbol = hyper_params['symbol'],
                     periods = hyper_params['periods'],
                     periods_meta = hyper_params['periods'],
                     model_number = hyper_params['model_number'],
                     export_path = hyper_params['export_path'])

Es ist anzumerken, dass ich CatBoost-Regressionsmodelle im ONNX-Format nicht mit dem Terminal verwenden konnte. Daher habe ich stattdessen Random Forest verwendet.

Die Dimension des Eingabetensors wird automatisch in Abhängigkeit von den Hyperparametern (Anzahl der Merkmale) angepasst, die vor Beginn des Trainings festgelegt werden. Anschließend werden die Modelle mithilfe der Funktion convert_sklearn() in das ONNX-Format konvertiert und in dem Verzeichnis auf der Festplatte gespeichert, das Sie in den Hyperparametern angegeben haben.

def export_model_to_ONNX(**kwargs):
    model = kwargs.get('model')
    symbol = kwargs.get('symbol')
    periods = kwargs.get('periods')
    periods_meta = kwargs.get('periods_meta')
    model_number = kwargs.get('model_number')
    export_path = kwargs.get('export_path')

    initial_type = [('float_input', FloatTensorType([None, len(hyper_params['periods'])]))]
    onnx_model = convert_sklearn(model[1], initial_types=initial_type)
    # save main model to ONNX
    with open(export_path +'catmodel ' + symbol + ' ' + str(model_number) +'.onnx', "wb") as f:
        f.write(onnx_model.SerializeToString())
    onnx_model_meta = convert_sklearn(model[2], initial_types=initial_type)
    # save meta model to ONNX
    with open(export_path +'catmodel_m ' + symbol + ' ' + str(model_number) +'.onnx', "wb") as f:
        f.write(onnx_model_meta.SerializeToString())
    
    code = '#include <Math\Stat\Math.mqh>'
    code += '\n'
    code += '#resource "catmodel '+ symbol + ' '+str(model_number)+'.onnx" as uchar ExtModel_' + symbol + '_' + str(model_number) + '[]'
    code += '\n'
    code += '#resource "catmodel_m '+ symbol + ' '+str(model_number)+'.onnx" as uchar ExtModel2_' + symbol + '_' + str(model_number) + '[]'
    code += '\n\n'
    code += 'int Periods' + symbol + '_' + str(model_number) + '[' + str(len(periods)) + \
        '] = {' + ','.join(map(str, periods)) + '};'
    code += '\n'
    code += 'int Periods_m' + symbol + '_' + str(model_number) + '[' + str(len(periods_meta)) + \
        '] = {' + ','.join(map(str, periods_meta)) + '};'
    code += '\n\n'

    # get features
    code += 'void fill_arays' + symbol + '_' + str(model_number) + '( double &features[]) {\n'
    code += '   double pr[], ret[];\n'
    code += '   ArrayResize(ret, 1);\n'
    code += '   for(int i=ArraySize(Periods'+ symbol + '_' + str(model_number) + ')-1; i>=0; i--) {\n'
    code += '       CopyClose(NULL,PERIOD_H1,1,Periods' + symbol + '_' + str(model_number) + '[i],pr);\n'
    code += '       ret[0] = MathStandardDeviation(pr);\n'
    code += '       ArrayInsert(features, ret, ArraySize(features), 0, WHOLE_ARRAY); }\n'
    code += '   ArraySetAsSeries(features, true);\n'
    code += '}\n\n'

    # get features
    code += 'void fill_arays_m' + symbol + '_' + str(model_number) + '( double &features[]) {\n'
    code += '   double pr[], ret[];\n'
    code += '   ArrayResize(ret, 1);\n'
    code += '   for(int i=ArraySize(Periods_m' + symbol + '_' + str(model_number) + ')-1; i>=0; i--) {\n'
    code += '       CopyClose(NULL,PERIOD_H1,1,Periods_m' + symbol + '_' + str(model_number) + '[i],pr);\n'
    code += '       ret[0] = MathStandardDeviation(pr);\n'
    code += '       ArrayInsert(features, ret, ArraySize(features), 0, WHOLE_ARRAY); }\n'
    code += '   ArraySetAsSeries(features, true);\n'
    code += '}\n\n'

    file = open(export_path + str(symbol) + ' ONNX include' + ' ' + str(model_number) + '.mqh', "w")
    file.write(code)

    file.close()
    print('The file ' + 'ONNX include' + '.mqh ' + 'has been written to disk')


Festlegen von Schwellenwerten im MetaTrader 5-Terminal

Da wir nun zwei Regressionsmodelle anstelle von zwei Klassifikatoren haben, können wir spezifische numerische Schwellenwerte festlegen.

Abb. 3. Konfiguration der Signalschwellenwerte im Terminal

  • buy_threshold und sell_threshold sind für die Filterung der Signale des Hauptregressionsmodells verantwortlich. Wenn das Signal unter diesem Schwellenwert liegt, werden keine Trades eröffnet. Wenn beispielsweise die vorhergesagte Kursänderung weniger als 10 Pips beträgt, ist die Eröffnung eines solchen Trades nicht sehr sinnvoll, da dies den Spread und die Provision nicht deckt.
  • Der meta_threshold filtert die Signale des Hauptmodells basierend auf der zuvor beschriebenen kausalen Inferenz. Es testet, wie stark die Prognose voraussichtlich von der tatsächlichen zukünftigen Änderung abweichen wird. Wenn die Differenz zu groß ist, werden die Trades ebenfalls nicht eröffnet.

Lassen Sie uns nun unser Modell im MetaTrader 5-Tester mit den vorgegebenen Schwellenwerten testen:

Abb. 4. Testen eines Modells mit vorgegebenen Schwellenwerten

Ich möchte Sie daran erinnern, dass der Forward-Zeitraum Anfang 2024 beginnt und das Modell diesen Forward-Zeitraum nun recht stabil handhabt. Dies unterstreicht die Bedeutung der korrekten Definition und Einstellung von Schwellenwerten. Sie können die Schwellenwerte selbst optimieren.

Die Vielfalt der Modelle kann groß sein, abhängig von der Art der Merkmale (in diesem Artikel werden beide Modelle mit Standardabweichungen trainiert) und von den Modellparametern selbst. Zum Beispiel wurde ein anderes Modell mit anderen Parametern trainiert, das auch ohne Anpassung der Schwellenwerte auf neuen Daten gut abschnitt.

Abb. 5. Training und Testen eines anderen Modells mit anderen Trainingsparametern

Nach der Anpassung der Schwellenwerte zeigte das Modell ab Anfang 2024 ein stetiges Wachstum.

Abb. 6. Testen des Modells im Terminal nach dem Festlegen der Schwellenwerte

Das Testen von Modellen auf Basis einer zweiten Trade-Labeling-Funktion und die Filterung nach Schwellenwerten liefern noch interessantere und genauere Ergebnisse:

Abb. 7. Testen des Modells auf Basis der durchschnittlichen Trade-Labeling-Funktion und tol = 1e-2

Wenn wir den Parameter tol beim Training von 1e-2 auf 1e-3 ändern, werden die Ergebnisse noch besser sein:

Abb. 8. Testen des Modells auf Basis der durchschnittlichen Trade-Labeling-Funktion und tol = 1e-3


Weitere Informationen

Um Modelle zu exportieren, müssen wir das Paket skl2onnx installieren und importieren:

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

Der ONNX-Aufrufcode im Trading-Bot-Code wurde ebenfalls modifiziert, um neue Regressionsmodelle korrekt zu verarbeiten:

vectorf y_main(1), y_meta(1);
   
OnnxRun(ExtHandle, ONNX_DEBUG_LOGS, f, y_main);
OnnxRun(ExtHandle2, ONNX_DEBUG_LOGS, f_m, y_meta);

float sig = y_main[0];
float meta_sig = y_meta[0];

Zusätzlich wurden neue Eingabeparameter zum Anpassen und Optimieren von Schwellenwerten hinzugefügt:

input double buy_threshold = 0.00001;
input double sell_threshold = -0.00001;
input double meta_threshold = 0.001;

 Die ONNX-Modellnamen werden nun über #define-Direktiven referenziert, was die Einbindung von Modellen mit unterschiedlichen Namen erleichtert.

#define model ExtModel_EURUSD_H1_0
#define model_m ExtModel2_EURUSD_H1_0
#define periods PeriodsEURUSD_H1_0
#define periods_m Periods_mEURUSD_H1_0
#define fill_arrays fill_araysEURUSD_H1_0
#define fill_arrays_m fill_arays_mEURUSD_H1_0

Handelssignale werden generiert, wenn Bedingungen in Abhängigkeit von den Schwellenwerten ausgelöst werden:

if((Ask-Bid < max_spread*_Point) && MathAbs(meta_sig) < meta_threshold &&
      AllowTrade(OrderMagic))
      if(countOrders(OrderMagic) < max_orders &&
         CheckMoneyForTrade(_Symbol, LotsOptimized(), ORDER_TYPE_BUY))
        {
         double l = LotsOptimized();
         if(sig > buy_threshold && Allow_Buy)
           {
            int res = -1;
            do
              {
               double stop = Bid - stoploss * _Point;
               double take = Ask + takeprofit * _Point;
               res = mytrade.PositionOpen(_Symbol, ORDER_TYPE_BUY, l, Ask, stop, take, bot_comment);
               Sleep(50);
              }
            while(res == -1);
           }
         else
           {
            if(sig < sell_threshold && Allow_Sell)
              {
               int res = -1;
               do
                 {
                  double stop = Ask + stoploss * _Point;
                  double take = Bid - takeprofit * _Point;
                  res = mytrade.PositionOpen(_Symbol, ORDER_TYPE_SELL, l, Bid, stop, take, bot_comment);
                  Sleep(50);
                 }
               while(res == -1);
              }
           }
        }

Der modifizierte Strategietester und die Modellexportfunktion wurden den entsprechenden Modulen hinzugefügt und dem Artikel beigefügt.


Schlussfolgerung

In diesem Artikel habe ich einen möglichen, aber nicht den einzigen Weg beschrieben, Handelssysteme auf der Grundlage von Regressionsmodellen aufzubauen. Dieser Ansatz ermöglicht eine feinere Abstimmung von auf maschinellem Lernen basierenden Bots. Er ermöglicht es auch, durch geeignete Schwellenwerte selbst klar unprofitable Modelle rentabler zu machen. Sie können beliebige Merkmale und/oder Handels-Labeling-Funktionen verwenden und diesen Algorithmus auch auf anderen Handelsinstrumenten und in anderen Zeitrahmen testen.

Das Archiv Python files.zip enthält die folgenden Dateien für die Entwicklung in der Python-Umgebung:

Dateiname Beschreibung
causal regression.py 
Das Hauptskript für das Training von Modellen
labeling_lib.py
Aktualisiertes Modul zum Trade-Labeling
tester_lib.py
Aktualisierter benutzerdefinierter Strategietester basierend auf maschinellem Lernen
export_lib.py Modul zum Exportieren von Modellen in das Terminal
EURUSD_H1.csv
Aus MetaTrader 5 exportierte Kursdaten

Das Archiv MQL5 files.zip enthält Dateien für das MetaTrader 5-Terminal:

Dateiname Beschreibung
regression trader.ex5
Der kompilierte Bot aus dem Artikel
regression trader.mq5
Der Quellcode des Bots aus dem Artikel
Include//Trend following folder
Die ONNX-Modelle und die Header-Datei für die Verbindung mit dem Bot

Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/18603

Beigefügte Dateien |
MQL5_files.zip (356.92 KB)
Python_files.zip (1092.44 KB)
Letzte Kommentare | Zur Diskussion im Händlerforum (15)
[Gelöscht] | 8 Aug. 2025 in 08:50
Guanlin Qi #:

Ich verwende „causal_regression_orig.py“, um die Header-Datei „ea“ zu erstellen, und kompiliere anschließend „ea“.

Das Ergebnis ist das unten abgebildete Bild „test_result“.

Es gibt so viel weniger Trades als in dem von dir geposteten Beispiel.

Was ist also der Unterschied zwischen diesen beiden?

Gibt es im Python-Tester viele Trades? Wenn ja, dann musst du die Schwellenwerte für die Eröffnung von Trades im MQL5-Programm neu einstellen – vielleicht sind sie zu hoch.
Guanlin Qi
Guanlin Qi | 14 Aug. 2025 in 07:00
Maxim Dmitrievsky #:
Gibt es im Python-Tester viele Trades? Wenn ja, dann müssen die Schwellenwerte für die Eröffnung von Trades im MQL5-Programm neu eingestellt werden – vielleicht sind sie zu hoch.


Das ist das Ergebnis des Python-Testers; ich verwende denselben Code, den du in diesem Beitrag angehängt hast. Ich habe im Konfigurationsfenster des MT5-Testers nachgesehen: Die Schwellenwerte „buy_threshold“ und „sell_threshold“ sind beide auf 0,0001 bzw. -0,0001 eingestellt, genau wie in diesem Beitrag.

Ich habe den Code überprüft und konnte keinen Unterschied feststellen.

[Gelöscht] | 18 Aug. 2025 in 21:05
Guanlin Qi #:


Dies ist das Ergebnis des Python-Testers. Ich verwende denselben Code, den Sie in diesem Beitrag angehängt haben. Ich habe im Konfigurationsfenster des MT5-Testers nachgesehen: Die Werte für „buy_threshold“ und „sell_threshold“ sind beide 0,0001 bzw. -0,0001, genau wie in den Einstellungen in diesem Beitrag.

Ich habe den Code überprüft und konnte keinen Unterschied feststellen.

Im Artikel ist der Meta-Schwellenwert 0,001 angegeben; das könnte ebenfalls der Grund für die geringe Anzahl an Trades sein. Dieser Schwellenwert muss erhöht werden – versuchen Sie es für Testzwecke mit 0,01 oder 0,1. Das sollte funktionieren. Alle Schwellenwerte können später optimiert werden, um eine feinere Abstimmung zu erreichen.
GOT.IT
GOT.IT | 12 Sept. 2025 in 14:37

Hallo Maxim, vielen Dank, dass du deine Erfahrungen mit uns teilst.

Eine Frage: Wie kann man sich vergewissern, dass der Expert Advisor nach der Berechnung des ONNX-Modells für den neuen Ticker und dem Ablegen der Datei im Ordner „Trend Following“ tatsächlich die trainierten Daten für den neuen Ticker verwendet? Bei anderen Tickers verhält sich der Expert Advisor identisch, selbst wenn man die ONNX_EURUSD-Datei löscht und eine andere einfügt. Muss man beim Wechsel des Tickers etwas am MQL5-Expert Advisor ändern?

[Gelöscht] | 21 Sept. 2025 in 10:30
GOT.IT #:

Hallo Maxim, vielen Dank, dass du das mit uns teilst.

Eine Frage: Wie kann man sich nach der Berechnung des ONNX für einen neuen Ticker und dem Ablegen der Datei im Ordner „Trend Following“ vergewissern, dass der Expert Advisor nun tatsächlich die trainierten Daten für den neuen Ticker verwendet? Bei anderen Tickers verhält sich der Expert Advisor identisch, selbst wenn man die ONNX_EURUSD-Datei löscht und eine andere einfügt. Muss man beim Wechsel des Tickers etwas am MQL5-Expert Advisor ändern?

Hallo, im Code des Expert Advisors muss die .mqh-Datei mit dem neuen Ticker eingebunden werden

#include <Trend following/EURUSD_H1 ONNX include 0.mqh>

Das heißt, die markierte Datei muss durch eine andere Datei mit einem anderen Namen ersetzt werden, die vom Python-Skript generiert wurde.

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.
Neuronale Netze im Trading: Anomalieerkennung im Frequenzbereich (letzter Teil) Neuronale Netze im Trading: Anomalieerkennung im Frequenzbereich (letzter Teil)
Wir arbeiten weiter an der Implementierung des Frameworks CATCH, das die Fourier-Transformation und Frequenz-Patching-Mechanismen kombiniert und so eine genaue Erkennung von Marktanomalien gewährleistet. In diesem Artikel schließen wir die Implementierung unserer eigenen Vision der vorgeschlagenen Ansätze ab und testen die neuen Modelle an realen historischen Daten.
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.
Analyse von Kurs-Zeit-Lücken in MQL5 (Teil II): Erstellung einer Heatmap der Liquiditätsverteilung über die Zeit Analyse von Kurs-Zeit-Lücken in MQL5 (Teil II): Erstellung einer Heatmap der Liquiditätsverteilung über die Zeit
Ein detaillierter Leitfaden zur Erstellung eines Heatmap-Indikators für MetaTrader 5, der die zeitliche Verteilung der Kurse visualisiert. Der Artikel erläutert die mathematischen Grundlagen der Zeitdichteanalyse, bei der jedes Kursniveau von Rot (minimale Verweildauer) bis Blau (maximale Verweildauer) eingefärbt wird.