
Entwicklung eines Roboters in Python und MQL5 (Teil 1): Vorverarbeitung der Daten
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:
-
NumPy: Die Bibliothek für die Arbeit mit mehrdimensionalen Arrays und mathematischen Funktionen.
-
Pandas: Ein Datenanalysetool, das praktische Datenstrukturen für die Arbeit mit Tabellen und Zeitreihen bietet.
-
Random: Modul zur Erzeugung von Zufallszahlen und zur Auswahl von Zufallselementen aus Sequenzen.
-
Datetime: Bietet Klassen und Funktionen für die Arbeit mit Datum und Uhrzeit.
-
MetaTrader5: Bibliothek für die Interaktion mit dem MetaTrader 5-Handelsterminal.
-
Time: Modul für die Arbeit mit Zeit- und Programmausführungsverzögerungen.
-
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.
-
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.
-
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:
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:
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.
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:
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:
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:
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





- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.