English Русский
preview
Entwicklung eines Roboters in Python und MQL5 (Teil 1): Vorverarbeitung der Daten

Entwicklung eines Roboters in Python und MQL5 (Teil 1): Vorverarbeitung der Daten

MetaTrader 5Handelssysteme | 19 August 2024, 12:12
14 0
Yevgeniy Koshtenko
Yevgeniy Koshtenko

Einführung

Der Markt wird immer komplexer. Es ist zu einer Schlacht der Algorithmen geworden. Über 95 % des Handelsumsatzes wird von Robotern generiert. 

Der nächste Schritt ist das maschinelle Lernen. Es handelt sich nicht um starke KI, aber auch nicht um einfache lineare Algorithmen. Das Modell des maschinellen Lernens ist in der Lage, auch unter schwierigen Bedingungen Gewinne zu erzielen. Es ist interessant, maschinelles Lernen für die Entwicklung von Handelssystemen einzusetzen. Dank neuronaler Netze wird der Handelsroboter große Datenmengen analysieren, Muster finden und Kursbewegungen vorhersagen.


Wir werden uns den Entwicklungszyklus eines Handelsroboters ansehen: Datenerfassung, -verarbeitung, Erweiterung der Stichprobe, Feature Engineering, Modellauswahl und -training, Erstellung eines Handelssystems mit Python und Überwachung der Handelsgeschäften.

Die Arbeit in Python hat ihre eigenen Vorteile: Geschwindigkeit im Bereich des maschinellen Lernens sowie die Fähigkeit, Merkmale auszuwählen und zu erzeugen. Der Export von Modellen nach ONNX erfordert genau die gleiche Logik zur Erzeugung von Merkmalen wie in Python, was nicht einfach ist. Aus diesem Grund habe ich mich für den Online-Handel über Python entschieden.

Festlegung der Aufgabe und Auswahl des Werkzeugs

Ziel des Projekts ist es, ein profitables und nachhaltiges maschinelles Lernmodell für den Handel in Python zu entwickeln. Etappen der Arbeit:

  • Sammeln von Daten aus MetaTrader 5, Laden der Hauptmerkmale.
  • Datenerweiterung zur Erweiterung der Stichprobe.  
  • Kennzeichnen von Daten.
  • Merkmalstechnik: Generierung, Clustering, Auswahl.
  • Auswahl und Training des ML-Modells. Möglicherweise die Zusammenlegung.
  • Bewertung von Modellen anhand von Metriken.
  • Entwicklung von Tests zur Bewertung der Rentabilität.  
  • Erzielung eines positiven Ergebnisses auf der Grundlage neuer Daten.
  • Implementierung eines Handelsalgorithmus mit Python MQL5.
  • Integration mit MetaTrader 5.
  • Verbesserung der Modelle.

Instrumente: Python MQL5, ML-Bibliotheken in Python für Geschwindigkeit und Funktionalität.

Wir haben also die Ziele und Aufgaben definiert. Im Rahmen des Artikels werden wir die folgenden Arbeiten durchführen:

  • Sammeln von Daten aus MetaTrader 5, Laden der Hauptmerkmale.
  • Datenerweiterung zur Erweiterung der Stichprobe.  
  • Kennzeichnen von Daten.
  • Merkmalstechnik: Generierung, Clustering, Auswahl.

Einrichten der Umgebung und Importe. Datenerhebung

Zunächst müssen wir historische Daten über MetaTrader 5 abrufen. Der Code stellt eine Verbindung zur Handelsplattform her, indem er den Pfad zur Terminaldatei an die Initialisierungsmethode übergibt.

Holen Sie sich in der Schleife Daten mit der Methode mt5.copy_rates_range() mit den folgenden Parametern: Instrument, Zeitrahmen, Start- und Enddatum. Es gibt einen Zähler für erfolglose Versuche und eine Verzögerung für eine stabile Verbindung.

Die Funktion wird beendet, indem die Verbindung zur Plattform mit der Methode mt5.shutdown() getrennt wird.

Dies ist eine eigene Funktion, die im Programm weiter aufgerufen werden kann. 

Um das Skript auszuführen, müssen Sie die folgenden Bibliotheken installieren:

  1. NumPy: Die Bibliothek für die Arbeit mit mehrdimensionalen Arrays und mathematischen Funktionen.

  2. Pandas: Ein Datenanalysetool, das praktische Datenstrukturen für die Arbeit mit Tabellen und Zeitreihen bietet.

  3. Random: Modul zur Erzeugung von Zufallszahlen und zur Auswahl von Zufallselementen aus Sequenzen.

  4. Datetime: Bietet Klassen und Funktionen für die Arbeit mit Datum und Uhrzeit.

  5. MetaTrader5: Bibliothek für die Interaktion mit dem MetaTrader 5-Handelsterminal.

  6. Time: Modul für die Arbeit mit Zeit- und Programmausführungsverzögerungen.

  7. Concurrent.futures: Das Werkzeug zur Ausführung paralleler und asynchroner Aufgaben. Wir werden dies in Zukunft für die parallele Arbeit in mehreren Währungen benötigen.

  8. Tqdm: Die Bibliothek zur Anzeige von Fortschrittsindikatoren bei der Durchführung iterativer Operationen. Wir werden dies in Zukunft für die parallele Arbeit in mehreren Währungen benötigen.

  9. Train_test_split: Funktion zur Aufteilung eines Datensatzes in einen Trainings- und einen Testsatz beim Training von Modellen für maschinelles Lernen.

Sie können sie mit „pip“ installieren, indem Sie die folgenden Befehle ausführen:

pip install numpy pandas MetaTrader5 concurrent-futures tqdm sklearn matplotlib imblearn
import numpy as np
import pandas as pd
import random
from datetime import datetime
import MetaTrader5 as mt5
import time
import concurrent.futures
from tqdm import tqdm
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
from sklearn.utils import class_weight
from imblearn.under_sampling import RandomUnderSampler

# GLOBALS
MARKUP = 0.00001
BACKWARD = datetime(2000, 1, 1)
FORWARD = datetime(2010, 1, 1)
EXAMWARD = datetime(2024, 1, 1)
MAX_OPEN_TRADES = 3
symbol = "EURUSD"

def retrieve_data(symbol, retries_limit=300):
    terminal_path = "C:/Program Files/MetaTrader 5/Arima/terminal64.exe"

    attempt = 0
    raw_data = None

    while attempt < retries_limit:
        if not mt5.initialize(path=terminal_path):
            print("MetaTrader initialization failed")
            return None

        instrument_count = mt5.symbols_total()
        if instrument_count > 0:
            print(f"Number of instruments in the terminal: {instrument_count}")
        else:
            print("No instruments in the terminal")

        rates = mt5.copy_rates_range(symbol, mt5.TIMEFRAME_H1, BACKWARD, EXAMWARD)
        mt5.shutdown()

        if rates is None or len(rates) == 0:
            print(f"Data for {symbol} not available (attempt {attempt+1})")
            attempt += 1
            time.sleep(1)
        else:
            raw_data = pd.DataFrame(rates[:-1], columns=['time', 'open', 'high', 'low', 'close', 'tick_volume'])
            raw_data['time'] = pd.to_datetime(raw_data['time'], unit='s')
            raw_data.set_index('time', inplace=True)
            break

    if raw_data is None:
        print(f"Data retrieval failed after {retries_limit} attempts")
        return None

    # Add simple features
    raw_data['raw_SMA_10'] = raw_data['close'].rolling(window=10).mean()
    raw_data['raw_SMA_20'] = raw_data['close'].rolling(window=20).mean()
    raw_data['Price_Change'] = raw_data['close'].pct_change() * 100

    # Additional features
    raw_data['raw_Std_Dev_Close'] = raw_data['close'].rolling(window=20).std()
    raw_data['raw_Volume_Change'] = raw_data['tick_volume'].pct_change() * 100

    raw_data['raw_Prev_Day_Price_Change'] = raw_data['close'] - raw_data['close'].shift(1)
    raw_data['raw_Prev_Week_Price_Change'] = raw_data['close'] - raw_data['close'].shift(7)
    raw_data['raw_Prev_Month_Price_Change'] = raw_data['close'] - raw_data['close'].shift(30)

    raw_data['Consecutive_Positive_Changes'] = (raw_data['Price_Change'] > 0).astype(int).groupby((raw_data['Price_Change'] > 0).astype(int).diff().ne(0).cumsum()).cumsum()
    raw_data['Consecutive_Negative_Changes'] = (raw_data['Price_Change'] < 0).astype(int).groupby((raw_data['Price_Change'] < 0).astype(int).diff().ne(0).cumsum()).cumsum()
    raw_data['Price_Density'] = raw_data['close'].rolling(window=10).apply(lambda x: len(set(x)))
    raw_data['Fractal_Analysis'] = raw_data['close'].rolling(window=10).apply(lambda x: 1 if x.idxmax() else (-1 if x.idxmin() else 0))
    raw_data['Price_Volume_Ratio'] = raw_data['close'] / raw_data['tick_volume']
    raw_data['Median_Close_7'] = raw_data['close'].rolling(window=7).median()
    raw_data['Median_Close_30'] = raw_data['close'].rolling(window=30).median()
    raw_data['Price_Volatility'] = raw_data['close'].rolling(window=20).std() / raw_data['close'].rolling(window=20).mean()

    print("\nOriginal columns:")
    print(raw_data[['close', 'high', 'low', 'open', 'tick_volume']].tail(100))

    print("\nList of features:")
    print(raw_data.columns.tolist())

    print("\nLast 100 features:")
    print(raw_data.tail(100))

    # Replace NaN values with the mean
    raw_data.fillna(raw_data.mean(), inplace=True)

    return raw_data

retrieve_data(symbol)

Wir definieren die globalen Variablen: Spreadkosten und Maklerprovisionen, Daten für Trainings- und Testmuster, maximale Anzahl von Handelsgeschäften, Symbol.

Laden von Kursen aus MetaTrader 5 in einer Schleife. Die Daten werden in einen DataFrame umgewandelt und mit Merkmalen erweitert:

  • Gleitende Durchschnitte des SMA mit den Periodenlängen 10 und 20
  • Preisänderung 
  • Standardpreisabweichung
  • Volumenänderung
  • Tägliche/monatliche Preisänderung
  • Preis-Mediane

Diese Merkmale tragen dazu bei, dass die Faktoren, die den Preis beeinflussen, berücksichtigt werden.

Es werden Informationen über die DataFrame-Spalten und die letzten Einträge angezeigt.

Schauen wir uns an, wie unsere erste Funktion ausgeführt wurde:

Erste Funktion

Alles funktioniert. Der Code hat erfolgreich Angebote geladen, vorbereitet und Merkmale geladen. Fahren wir mit dem nächsten Teil des Codes fort.


Datenerweiterung zur Erweiterung der Stichprobe

Die Datenerweiterung ist die Generierung neuer Trainingsbeispiele auf der Grundlage bereits vorhandener Beispiele, um die Stichprobengröße zu erhöhen und die Modellqualität zu verbessern. Sie ist relevant für die Prognose von Zeitreihen mit begrenzten Daten. Außerdem werden dadurch Modellfehler reduziert und die Robustheit erhöht.

Methoden der Finanzdatenerweiterung:

  • Hinzufügen von Rauschen (Zufallsabweichungen) für eine Robustheit gegen das Rauschen
  • Zeitverschiebung für die Modellierung verschiedener Entwicklungsszenarien
  • Skalierung zur Modellierung von Preisspitzen/-einbrüchen
  • Inversion der Quelldaten

Ich habe die Eingabeerweiterungsfunktion implementiert, die DataFrame und die Anzahl der neuen Beispiele für jede Methode akzeptiert. Es generiert neue Daten mit verschiedenen Ansätzen und verkettet sie mit dem ursprünglichen DataFrame.

def augment_data(raw_data, noise_level=0.01, time_shift=1, scale_range=(0.9, 1.1)):
    print(f"Number of rows before augmentation: {len(raw_data)}")

    # Copy raw_data into augmented_data
    augmented_data = raw_data.copy()

    # Add noise
    noisy_data = raw_data.copy()
    noisy_data += np.random.normal(0, noise_level, noisy_data.shape)

    # Replace NaN values with the mean
    noisy_data.fillna(noisy_data.mean(), inplace=True)

    augmented_data = pd.concat([augmented_data, noisy_data])
    print(f"Added {len(noisy_data)} rows after adding noise")

    # Time shift
    shifted_data = raw_data.copy()
    shifted_data.index += pd.DateOffset(hours=time_shift)

    # Replace NaN values with the mean
    shifted_data.fillna(shifted_data.mean(), inplace=True)

    augmented_data = pd.concat([augmented_data, shifted_data])
    print(f"Added {len(shifted_data)} rows after time shift")

    # Scaling
    scale = np.random.uniform(scale_range[0], scale_range[1])
    scaled_data = raw_data.copy()
    scaled_data *= scale

    # Replace NaN values with the mean
    scaled_data.fillna(scaled_data.mean(), inplace=True)

    augmented_data = pd.concat([augmented_data, scaled_data])
    print(f"Added {len(scaled_data)} rows after scaling")

    # Inversion
    inverted_data = raw_data.copy()
    inverted_data *= -1

    # Replace NaN values with the mean
    inverted_data.fillna(inverted_data.mean(), inplace=True)

    augmented_data = pd.concat([augmented_data, inverted_data])
    print(f"Added {len(inverted_data)} rows after inversion")

    print(f"Number of rows after augmentation: {len(augmented_data)}")

    # Print dates by years
    print("Print dates by years:")
    for year, group in augmented_data.groupby(augmented_data.index.year):
        print(f"Year {year}: {group.index}")

    return augmented_data

Rufen Sie den Code auf und erhalten Sie die folgenden Ergebnisse:

2

Nach Anwendung von Methoden zur Datenerweiterung wurde unser ursprünglicher Satz von 150.000 H1-Kursbalken auf beeindruckende 747.000 Zeichenketten erweitert. Viele maßgebliche Studien im Bereich des maschinellen Lernens zeigen, dass sich eine signifikante Erhöhung des Volumens der Trainingsdaten durch die Erzeugung synthetischer Beispiele positiv auf die Qualitätsmetriken der trainierten Modelle auswirkt. Wir gehen davon aus, dass diese Technik auch in unserem Fall die gewünschte Wirkung erzielen wird.


Datenkennzeichnung

Die Kennzeichnung von Daten ist entscheidend für den Erfolg von Algorithmen des überwachten Lernens. Es ermöglicht uns, die Quelldaten mit Bezeichnungen zu versehen, die das Modell dann erlernt. Beschriftete Daten erhöhen die Genauigkeit, verbessern die Verallgemeinerung, beschleunigen das Training und erleichtern die Bewertung von Modellen. Beim EURUSD-Prognoseproblem haben wir die binäre Spalte „labels“ hinzugefügt, die angibt, ob die nächste Kursänderung größer als die Spanne und die Provision ist. Auf diese Weise kann das Modell Muster von Spread-Wiederholungen und Non-Rollback-Trends lernen.

    Die Kennzeichnung spielt eine Schlüsselrolle, da sie es den Algorithmen des maschinellen Lernens ermöglicht, komplexe Muster in Daten zu finden, die in ihrer Rohform nicht sichtbar sind. Lassen Sie uns mit der Codeüberprüfung fortfahren.

    def markup_data(data, target_column, label_column, markup_ratio=0.00002):
        data.loc[:, label_column] = np.where(data.loc[:, target_column].shift(-1) > data.loc[:, target_column] + markup_ratio, 1, 0)
    
        data.loc[data[label_column].isna(), label_column] = 0
    
        print(f"Number of markups on price change greater than markup: {data[label_column].sum()}")
    
        return data

    Wir führen diesen Code aus und ermitteln die Anzahl der „labels“ in den Daten. Die Funktion gibt einen Rahmen zurück. Alles funktioniert. Übrigens: Von über 700.000 Datenpunkten änderte sich der Preis nur in 70.000 Fällen um mehr als den Spread.

    3

    Jetzt haben wir eine weitere Datenauszeichnungsfunktion. Diesmal ist er näher an den tatsächlichen Einnahmen.


    Funktion für Ziel-Labels

    def label_data(data, symbol, min=2, max=48):
        terminal_path = "C:/Program Files/MetaTrader 5/Arima/terminal64.exe"
    
        if not mt5.initialize(path=terminal_path):
            print("Error connecting to MetaTrader 5 terminal")
            return
    
        symbol_info = mt5.symbol_info(symbol)
        stop_level = 100 * symbol_info.point
        take_level = 800 * symbol_info.point
    
        labels = []
    
        for i in range(data.shape[0] - max):
            rand = random.randint(min, max)
            curr_pr = data['close'].iloc[i]
            future_pr = data['close'].iloc[i + rand]
            min_pr = data['low'].iloc[i:i + rand].min()
            max_pr = data['high'].iloc[i:i + rand].max()
    
            price_change = abs(future_pr - curr_pr)
    
            if price_change > take_level and future_pr > curr_pr and min_pr > curr_pr - stop_level:
                labels.append(1)  # Growth
            elif price_change > take_level and future_pr < curr_pr and max_pr < curr_pr + stop_level:
                labels.append(0)  # Fall
            else:
                labels.append(None)
    
        data = data.iloc[:len(labels)].copy()
        data['labels'] = labels
    
        data.dropna(inplace=True)
    
        X = data.drop('labels', axis=1)
        y = data['labels']
    
        rus = RandomUnderSampler(random_state=2)
        X_balanced, y_balanced = rus.fit_resample(X, y)
    
        data_balanced = pd.concat([X_balanced, y_balanced], axis=1)
    
        return data

    Die Funktion ermittelt Ziel-Labels für das Training von Modellen des maschinellen Lernens auf Handelsgewinne. Die Funktion stellt eine Verbindung zum MetaTrader 5 her, ruft Symbolinformationen ab und berechnet Stop/Take-Levels. Dann wird der zukünftige Preis nach einer zufälligen Periode für jeden Einstiegspunkt ermittelt. Wenn die Kursveränderung den Take-Profit übersteigt und den Stop nicht berührt, sowie die Bedingungen für Wachstum/Fall erfüllt, wird die 1,0/0,0-Marke entsprechend hinzugefügt. Ansonsten - nichts. Es wird ein neuer Datenrahmen nur mit den Daten mit „labels“ erstellt. Keine wird durch Durchschnittswerte ersetzt.

    Die Anzahl der „labels“ für Steigen und Fallen wird angezeigt.

    Alles funktioniert wie vorgesehen. Wir haben Daten und ihre Ableitungen erhalten. Die Daten wurden vergrößert, erheblich ergänzt und mit zwei verschiedenen Funktionen versehen. Gehen wir zum nächsten Schritt über - dem Klassenausgleich.

    Übrigens glaube ich, dass Leser, die sich mit maschinellem Lernen auskennen, längst verstanden haben, dass wir letztendlich ein Klassifizierungsmodell und kein Regressionsmodell entwickeln werden. Mir gefallen Regressionsmodelle besser, da ich in ihnen ein wenig mehr Vorhersagelogik sehe als in Klassifikationsmodellen. 

    Unser nächster Schritt ist also der Ausgleich der Klassen.


    Ausgleich der Klassen. Klassifizierung

    Die Klassifizierung ist eine grundlegende Analysemethode, die auf der natürlichen menschlichen Fähigkeit beruht, Informationen nach gemeinsamen Merkmalen zu strukturieren. Eine der ersten Anwendungen der Klassifizierung war die Prospektion - die Identifizierung vielversprechender Gebiete auf der Grundlage geologischer Merkmale.

    Mit dem Aufkommen von Computern hat die Klassifizierung eine neue Ebene erreicht, aber das Wesentliche bleibt - die Entdeckung von Mustern im scheinbaren Chaos der Details. Für die Finanzmärkte ist es wichtig, die Preisdynamik in Wachstum/Fall zu unterteilen. Allerdings können die Klassen unausgewogen sein - es gibt wenige Trends und viele Seitwärtsbewegungen.

    Daher werden Methoden des Klassenausgleichs verwendet: Entfernen dominanter Beispiele oder Erzeugen seltener Beispiele. Dadurch können die Modelle wichtige, aber seltene Muster der Preisdynamik zuverlässig erkennen.

    pip install imblearn

        data = data.iloc[:len(labels)].copy()
        data['labels'] = labels
    
        data.dropna(inplace=True)
    
        X = data.drop('labels', axis=1)
        y = data['labels']
    
        rus = RandomUnderSampler(random_state=2)
        X_balanced, y_balanced = rus.fit_resample(X, y)
    
        data_balanced = pd.concat([X_balanced, y_balanced], axis=1)

    Unsere Klassen sind also jetzt ausgeglichen. Wir haben die gleiche Anzahl von „labels“ für jede Klasse (Preisverfall und Herbst).

    Kommen wir nun zum Wichtigsten bei der Datenprognose - den Merkmalen.


    Was sind Merkmale beim maschinellen Lernen?

    Attribute und Merkmale sind grundlegende Begriffe, die jeder von Kindheit an kennt. Wenn wir ein Objekt beschreiben, listen wir seine Eigenschaften auf - das sind die Attribute. Ein Satz solcher Merkmale ermöglicht es uns, ein Objekt vollständig zu charakterisieren.

    Dasselbe gilt für die Datenanalyse - jede Beobachtung wird durch eine Reihe numerischer und kategorischer Merkmale beschrieben. Die Auswahl der informativen Merkmale ist entscheidend für den Erfolg.

    Auf einer höheren Ebene gibt es das Feature-Engineering, bei dem neue abgeleitete Merkmale aus den ursprünglichen Parametern konstruiert werden, um den Untersuchungsgegenstand besser zu beschreiben.

    So wird die menschliche Erfahrung, die Welt durch die Beschreibung von Objekten anhand ihrer Merkmale zu kennen, auf der Ebene der Formalisierung und Automatisierung auf die Wissenschaft übertragen.


    Automatische Merkmalsextraktion

    Unter Feature-Engineering versteht man die Umwandlung von Quelldaten in einen Satz von Merkmalen für das Training von Modellen des maschinellen Lernens. Ziel ist es, die informativsten Merkmale zu finden. Es gibt einen manuellen Ansatz (eine Person wählt die Merkmale aus) und einen automatischen Ansatz (unter Verwendung von Algorithmen).

    Wir werden den automatischen Ansatz verwenden. Wenden wir die Methode der Merkmalsgenerierung an, um automatisch die besten Merkmale aus unseren Daten zu extrahieren. Wählen Sie dann aus der resultierenden Menge die informativsten aus.

    Methode zur Erzeugung neuer Merkmale: 

    def generate_new_features(data, num_features=200, random_seed=1):
        random.seed(random_seed)
        new_features = {}
    
        for _ in range(num_features):
            feature_name = f'feature_{len(new_features)}'
    
            col1_idx, col2_idx = random.sample(range(len(data.columns)), 2)
            col1, col2 = data.columns[col1_idx], data.columns[col2_idx]
    
            operation = random.choice(['add', 'subtract', 'multiply', 'divide', 'shift', 'rolling_mean', 'rolling_std', 'rolling_max', 'rolling_min', 'rolling_sum'])
    
            if operation == 'add':
                new_features[feature_name] = data[col1] + data[col2]
            elif operation == 'subtract':
                new_features[feature_name] = data[col1] - data[col2]
            elif operation == 'multiply':
                new_features[feature_name] = data[col1] * data[col2]
            elif operation == 'divide':
                new_features[feature_name] = data[col1] / data[col2]
            elif operation == 'shift':
                shift = random.randint(1, 10)
                new_features[feature_name] = data[col1].shift(shift)
            elif operation == 'rolling_mean':
                window = random.randint(2, 20)
                new_features[feature_name] = data[col1].rolling(window).mean()
            elif operation == 'rolling_std':
                window = random.randint(2, 20)
                new_features[feature_name] = data[col1].rolling(window).std()
            elif operation == 'rolling_max':
                window = random.randint(2, 20)
                new_features[feature_name] = data[col1].rolling(window).max()
            elif operation == 'rolling_min':
                window = random.randint(2, 20)
                new_features[feature_name] = data[col1].rolling(window).min()
            elif operation == 'rolling_sum':
                window = random.randint(2, 20)
                new_features[feature_name] = data[col1].rolling(window).sum()
    
        new_data = pd.concat([data, pd.DataFrame(new_features)], axis=1)
    
        print("\nGenerated features:")
        print(new_data[list(new_features.keys())].tail(100))
    
        return data

    Bitte beachten Sie den folgenden Parameter:

    random_seed=42

    Der Parameter random_seed ist für die Reproduzierbarkeit der Ergebnisse bei der Erzeugung neuer Merkmale erforderlich. Die Funktion generate_new_features erstellt neue Merkmale aus den Quelldaten. Eingabe: Daten, Anzahl der Merkmale, Seed.

    Random wird mit einem gegebenen Seed initialisiert. In der Schleife: Es wird ein Name wird generiert und 2 bestehende Merkmale und eine Operation (Addition, Subtraktion, etc.) werden zufällig ausgewählt. Es wird ein neues Merkmal für den ausgewählten Vorgang berechnet.

    Nach der Generierung werden neue Merkmale zu den Originaldaten hinzugefügt. Es werden aktualisierte Daten mit automatisch generierten Merkmalen zurückgegeben.

    Der Code ermöglicht es uns, automatisch neue Funktionen zu erstellen, um die Qualität des maschinellen Lernens zu verbessern.

    Starten wir den Code und schauen wir uns das Ergebnis an:

    5

    100 neue Funktionen erstellt. Gehen wir zum nächsten Schritt über - dem Clustering von Merkmalen.


    Clustering von Merkmalen

    Beim Clustering werden ähnliche Merkmale in Gruppen zusammengefasst, um ihre Anzahl zu verringern. Dies trägt dazu bei, redundante Daten zu entfernen, die Korrelation zu verringern und das Modell zu vereinfachen, ohne dass es zu einer Überanpassung kommt.

    Beliebte Algorithmen: k-means (die Anzahl der Cluster wird festgelegt, die Merkmale werden um die Zentren gruppiert) und hierarchisches Clustering (mehrstufige Baumstruktur).

    Das Clustering von Merkmalen ermöglicht es uns, eine Reihe von nutzlosen Merkmalen auszusortieren und die Effizienz des Modells zu verbessern.

    Schauen wir uns den Code für das Feature Clustering an:

    from sklearn.mixture import GaussianMixture
    
    def cluster_features_by_gmm(data, n_components=4):
        X = data.drop(['label', 'labels'], axis=1)
    
        X = X.replace([np.inf, -np.inf], np.nan)
        X = X.fillna(X.median())
    
        gmm = GaussianMixture(n_components=n_components, random_state=1)
    
        gmm.fit(X)
    
        data['cluster'] = gmm.predict(X)
    
        print("\nFeature clusters:")
        print(data[['cluster']].tail(100))
    
        return data

    Wir verwenden den GMM-Algorithmus (Gaussian Mixture Model) für das Clustering von Merkmalen. Die Grundidee ist, dass die Daten als eine Mischung von Normalverteilungen erzeugt werden, wobei jede Verteilung ein Cluster darstellt.

    Legen Sie zunächst die Anzahl der Cluster fest. Dann setzen wir die Anfangsparameter des Modells: Mittelwerte, Kovarianzmatrizen und Clusterwahrscheinlichkeiten. Der Algorithmus berechnet diese Parameter zyklisch nach der Maximum-Likelihood-Methode neu, bis sie sich nicht mehr ändern.

    Als Ergebnis erhalten wir die endgültigen Parameter für jedes Cluster, anhand derer wir bestimmen können, zu welchem Cluster das neue Merkmal gehört.

    GMM ist eine coole Sache. Es wird für verschiedene Aufgaben verwendet. Sie eignet sich gut für Daten, bei denen die Cluster komplexe Formen und unscharfe Grenzen aufweisen.

    Dieser Code verwendet GMM, um die Merkmale in Cluster zu unterteilen. Die Originaldaten werden übernommen und die Klassenbezeichnungen entfernt. GMM wird zur Aufteilung in eine bestimmte Anzahl von Clustern verwendet. Die Clusternummern werden als neue Spalte hinzugefügt. Am Ende wird eine Tabelle mit den erhaltenen Clustern gedruckt.

    Führen wir den Clustering-Code aus und sehen wir uns die Ergebnisse an:

    6

    Kommen wir nun zu der Funktion der Auswahl der besten Merkmale.


    Auswahl der besten Merkmale

    from sklearn.feature_selection import RFECV
    from sklearn.ensemble import RandomForestClassifier
    import pandas as pd
    
    def feature_engineering(data, n_features_to_select=10):
        # Remove the 'label' column as it is not a feature
        X = data.drop(['label', 'labels'], axis=1)
        y = data['labels']
    
        # Create a RandomForestClassifier model
        clf = RandomForestClassifier(n_estimators=100, random_state=1)
    
        # Use RFECV to select n_features_to_select best features
        rfecv = RFECV(estimator=clf, step=1, cv=5, scoring='accuracy', n_jobs=-1, verbose=1,
                      min_features_to_select=n_features_to_select)
        rfecv.fit(X, y)
    
        # Return a DataFrame with the best features, 'label' column, and 'labels' column
        selected_features = X.columns[rfecv.get_support(indices=True)]
        selected_data = data[selected_features.tolist() + ['label', 'labels']]  # Convert selected_features to a list
    
        # Print the table of best features
        print("\nBest features:")
        print(pd.DataFrame({'Feature': selected_features}))
    
        return selected_data
    
    labeled_data_engineered = feature_engineering(labeled_data_clustered, n_features_to_select=10)

    Diese Funktion extrahiert die coolsten und nützlichsten Merkmale aus unseren Daten. Die Eingabe sind die Originaldaten mit Klassenmerkmalen und Bezeichnungen sowie die erforderliche Anzahl ausgewählter Merkmale.

    Zunächst werden die Klassenbezeichnungen zurückgesetzt, da sie bei der Merkmalsauswahl nicht hilfreich sind. Dann wird der Algorithmus Random Forest gestartet - ein Modell, das eine Reihe von Entscheidungsbäumen auf zufälligen Merkmalssätzen aufbaut.

    Nachdem alle Bäume trainiert wurden, bewertet Random Forest, wie wichtig jedes Merkmal ist und wie sehr es die Klassifizierung beeinflusst. Auf der Grundlage dieser Wichtigkeitswerte wählt die Funktion die wichtigsten Merkmale aus.

    Schließlich werden die ausgewählten Merkmale zu den Daten hinzugefügt und die Klassenbezeichnungen zurückgegeben. Die Funktion druckt eine Tabelle mit den ausgewählten Merkmalen und gibt aktualisierte Daten zurück.

    Warum diese ganze Aufregung? Auf diese Weise werden wir überflüssige Funktionen los, die den Prozess nur verlangsamen. Wir lassen die coolsten Merkmale übrig, damit das Modell bessere Ergebnisse zeigt, wenn es mit solchen Daten trainiert wird.

    Starten wir die Funktion und sehen wir uns die Ergebnisse an:

    8

    Als bester Indikator für die Preisprognose erwies sich der Eröffnungskurs selbst. Die oben genannten Funktionen basieren auf gleitenden Durchschnitten, Preisinkrementen, Standardabweichung, täglichen und monatlichen Preisänderungen. Automatisch generierte Merkmale erwiesen sich als wenig informativ.

    Der Code ermöglicht die automatische Auswahl wichtiger Merkmale, was die Modellleistung und die Verallgemeinerungsfähigkeit verbessern kann.


    Schlussfolgerung

    Wir haben einen Code zur Datenmanipulation erstellt, der die Entwicklung unseres zukünftigen maschinellen Lernmodells vorwegnimmt. Lassen Sie uns kurz auf die unternommenen Schritte zurückblicken:

    • Die Ausgangsdaten für EURUSD wurden von der Plattform MetaTrader 5 extrahiert. Anschließend wurden Transformationen mit Hilfe von Zufallsoperationen - Rauschen, Verschiebungen und Skalierung - durchgeführt, um den Stichprobenumfang deutlich zu erhöhen.
    • Der nächste Schritt bestand darin, die Kurswerte mit speziellen Trendmarkern - Anstieg und Rückgang - zu kennzeichnen und dabei die Werte von Stop-Loss und Take-Profit zu berücksichtigen. Um ein Gleichgewicht zwischen den Klassen herzustellen, wurden redundante Beispiele entfernt.
    • Anschließend wurde ein komplexes Feature-Engineering-Verfahren durchgeführt. Zunächst wurden Hunderte von neuen abgeleiteten Merkmalen automatisch aus den ursprünglichen Zeitreihen generiert. Anschließend wurde ein Gauß'sches Mischclustering durchgeführt, um Redundanzen aufzudecken. Schließlich wurde der Random-Forest-Algorithmus verwendet, um die informativste Untergruppe von Variablen zu bewerten und auszuwählen.
    • Als Ergebnis wurde ein hochwertiger angereicherter Datensatz mit optimalen Merkmalen erstellt. Sie wird als Grundlage für das weitere Training von Machine-Learning-Modellen und die Entwicklung einer Handelsstrategie in Python mit Integration in MetaTrader 5 dienen.


    Im nächsten Artikel werden wir uns auf die Auswahl des optimalen Klassifizierungsmodells, dessen Verbesserung, die Implementierung der Kreuzvalidierung und das Schreiben einer Testerfunktion für das Python/MQL5-Bündel konzentrieren.

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

    Beigefügte Dateien |
    synergy_ml_bot.py (11.74 KB)
    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.
    Entwicklung eines Expertenberaters für mehrere Währungen (Teil 6): Automatisieren der Auswahl einer Instanzgruppe Entwicklung eines Expertenberaters für mehrere Währungen (Teil 6): Automatisieren der Auswahl einer Instanzgruppe
    Nach der Optimierung der Handelsstrategie erhalten wir eine Reihe von Parametern. Wir können sie verwenden, um mehrere Instanzen von Handelsstrategien zu erstellen, die in einem EA kombiniert werden. Früher haben wir das manuell gemacht. Hier werden wir versuchen, diesen Prozess zu automatisieren.
    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.
    Neuronale Netze leicht gemacht (Teil 81): Kontextgesteuerte Bewegungsanalyse (CCMR) Neuronale Netze leicht gemacht (Teil 81): Kontextgesteuerte Bewegungsanalyse (CCMR)
    In früheren Arbeiten haben wir immer den aktuellen Zustand der Umwelt bewertet. Gleichzeitig blieb die Dynamik der Veränderungen bei den Indikatoren immer „hinter den Kulissen“. In diesem Artikel möchte ich Ihnen einen Algorithmus vorstellen, mit dem Sie die direkte Veränderung der Daten zwischen 2 aufeinanderfolgenden Umweltzuständen bewerten können.