
Développement d'un robot en Python et MQL5 (Partie 1) : Pré-traitement des données
Introduction
Le marché devient de plus en plus complexe. Aujourd'hui, il s'agit d'une bataille d'algorithmes. Plus de 95% du chiffre d'affaires des transactions est généré par des robots.
L'étape suivante est l'apprentissage automatique. Il ne s'agit pas d'IA forte, mais ce ne sont pas non plus de simples algorithmes linéaires. Le modèle d'apprentissage automatique est capable de réaliser des bénéfices dans des conditions difficiles. Il est intéressant d'appliquer l'apprentissage automatique pour créer des systèmes de trading. Grâce aux réseaux neuronaux, le robot de trading analysera des données volumineuses, trouvera des modèles et prédira l'évolution des prix.
Nous examinerons le cycle de développement d'un robot de trading : collecte de données, traitement, expansion de l'échantillon, ingénierie des caractéristiques, sélection et formation de modèles, création d'un système de trading via Python et suivi des transactions.
Travailler en Python a ses propres avantages : la rapidité dans le domaine de l'apprentissage automatique, ainsi que la possibilité de sélectionner et de générer des caractéristiques. L'exportation de modèles vers ONNX nécessite exactement la même logique de génération de caractéristiques qu'en Python, ce qui n'est pas facile. C'est pourquoi j'ai choisi le trading en ligne via Python.
Définir la tâche et choisir un outil
L'objectif du projet est de créer un modèle d'apprentissage automatique rentable et durable pour le trading en Python. Voici les étapes du travail :
- Collecte des données de MetaTrader 5, chargement des caractéristiques principales.
- Augmentation des données pour élargir l'échantillon.
- Marquer les données avec des étiquettes.
- Ingénierie des caractéristiques : génération, regroupement, sélection.
- Sélection et entraînement du modèle ML. Éventuellement, assemblage.
- Évaluation des modèles à l'aide de mesures.
- Développement de testeurs pour l'évaluation de la rentabilité.
- Obtention d’un résultat positif sur la base des nouvelles données.
- Mise en œuvre d'un algorithme de trading via Python MQL5.
- Intégration avec MetaTrader 5.
- Améliorer les modèles.
Outils : Python MQL5, bibliothèques de ML en Python pour plus de rapidité et de fonctionnalité.
Nous avons donc défini les buts et les objectifs. Nous effectuerons les travaux suivants dans le cadre de l'article :
- Collecte des données de MetaTrader 5, chargement des caractéristiques principales.
- Augmentation des données pour élargir l'échantillon.
- Marquer les données avec des étiquettes.
- Ingénierie des caractéristiques : génération, regroupement, sélection.
Mise en place de l'environnement et des importations. Collecte des données
Tout d'abord, nous devons obtenir des données historiques via MetaTrader 5. Le code établit une connexion avec la plateforme de trading en transmettant le chemin d'accès au fichier du terminal à la méthode d'initialisation.
Dans la boucle, obtenez des données en utilisant la méthode mt5.copy_rates_range() avec les paramètres suivants : instrument, timeframe, dates de début et de fin. Il y a un compteur de tentatives infructueuses et un délai pour une connexion stable.
La fonction se termine par une déconnexion de la plateforme à l'aide de la méthode mt5.shutdown().
Il s'agit d'une fonction distincte qui sera appelée ultérieurement dans le programme.
Pour exécuter le script, vous devez installer les bibliothèques suivantes :
-
NumPy : bibliothèque permettant de travailler avec des tableaux multidimensionnels et des fonctions mathématiques.
-
Pandas : outil d'analyse de données qui fournit des structures de données pratiques pour travailler avec des tableaux et des séries chronologiques.
-
Random : module permettant de générer des nombres aléatoires et de sélectionner des éléments aléatoires dans des séquences.
-
Datetime : fournit des classes et des fonctions pour travailler avec les dates et l'heure.
-
MetaTrader5 : bibliothèque pour l'interaction avec le terminal de trading MetaTrader 5.
-
Time : module permettant de travailler avec l’heure et les délais d'exécution des programmes.
-
Concurrent.futures : outil permettant d'exécuter des tâches parallèles et asynchrones. Nous en aurons besoin à l'avenir pour les travaux parallèles multidevises.
-
Tqdm : bibliothèque permettant d'afficher des indicateurs de progression lors de l'exécution d'opérations itératives. Nous en aurons besoin à l'avenir pour les travaux parallèles multidevises.
-
Train_test_split : fonction permettant de diviser un ensemble de données en ensembles de formation et de test lors de la formation de modèles d'apprentissage automatique.
Vous pouvez les installer à l'aide de 'pip' en exécutant les commandes suivantes :
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)
Définir des variables globales : coûts de spread et commissions des courtiers, dates des échantillons de formation et de test, nombre maximum de transactions, symbole.
Chargement en boucle des cotations de MetaTrader 5. Les données sont converties en DataFrame et enrichies de caractéristiques :
- Moyennes mobiles SMA à 10 et 20 périodes
- Changement de prix
- Écart type des prix
- Changement de volume
- Variation quotidienne/mensuelle des prix
- Prix médians
Ces caractéristiques permettent de prendre en compte les facteurs qui influencent le prix.
Des informations sur les colonnes du DataFrame et les dernières entrées sont affichées.
Voyons comment notre première fonction a été exécutée :
Tout fonctionne. Le code a réussi à charger les cotations, à préparer et à charger les caractéristiques. Passons à la partie suivante du code.
Augmentation des données pour élargir l'échantillon
L'augmentation des données consiste à générer de nouveaux exemples d'apprentissage à partir des exemples existants afin d'augmenter la taille de l'échantillon et d'améliorer la qualité du modèle. Elle est utile pour prévoir des séries temporelles avec des données limitées. Elle réduit également les erreurs de modèle et augmente la robustesse.
Méthodes d'enrichissement des données financières :
- Ajout de bruit (déviations aléatoires) pour une meilleure résistance au bruit
- Décalage temporel pour la modélisation de différents scénarios de développement
- Modulation pour modéliser les hausses et les baisses de prix
- Inversion des données sources
J'ai implémenté la fonction d'augmentation de l'entrée qui accepte le DataFrame et le nombre de nouveaux exemples pour chaque méthode. Elle génère de nouvelles données en utilisant différentes approches et les concatène avec le DataFrame original.
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
Appelez le code et obtenez les résultats suivants :
Après avoir appliqué des méthodes d'augmentation des données, notre ensemble initial de 150 000 barres de prix H1 a été étendu à un nombre impressionnant de 747 000 chaînes. De nombreuses études faisant autorité dans le domaine de l'apprentissage automatique montrent qu'une augmentation aussi importante du volume des données d'apprentissage, due à la génération d'exemples synthétiques, a un effet positif sur les paramètres de qualité des modèles formés. Nous pensons que dans notre cas, cette technique produira également l'effet désiré.
Etiquetage des données
L'étiquetage des données est essentiel au succès des algorithmes d'apprentissage supervisé. Il nous permet de fournir aux données sources des étiquettes que le modèle apprend ensuite. Les données étiquetées améliorent la précision et la généralisation, accélèrent la formation et facilitent l'évaluation des modèles. Dans le problème de prévision EURUSD, nous avons ajouté la colonne binaire "labels" indiquant si la prochaine variation de prix était supérieure au spread et à la commission. Cela permet au modèle d'apprendre les schémas de relecture des écarts et les tendances de non-retour.
L'étiquetage joue un rôle clé en permettant aux algorithmes d'apprentissage automatique de trouver des modèles complexes dans les données qui ne sont pas visibles sous leur forme brute. Passons maintenant à l'examen du code.
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
Exécutez ce code et obtenez le nombre d'étiquettes dans les données. La fonction renvoie un cadre. Tout fonctionne. D'ailleurs, sur plus de 700 000 points de données, le prix n'a varié plus que l'écart que dans 70 000 cas.
Nous disposons à présent d'une autre fonction de marquage des données. Cette fois, elle est plus proche des revenus réels.
Fonction des étiquettes cibles
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
Cette fonction permet d'obtenir des étiquettes cibles pour former des modèles d'apprentissage automatique sur les bénéfices de trading. La fonction se connecte à MetaTrader 5, récupère les informations sur les symboles et calcule les niveaux de stop/take. Ensuite, le prix futur est déterminé après une période aléatoire pour chaque point d'entrée. Si le changement de prix dépasse le take profit et ne touche pas le stop, et s'il remplit les conditions de croissance/chute, l’étiquette 1,0/0,0 est ajoutée en conséquence. Sinon, aucune étiquette n’est mise. Un nouveau cadre de données contenant uniquement les données étiquetées est créé. Aucune n'est remplacée par des moyennes.
Le nombre d'étiquettes de croissance/chute est affiché.
Tout fonctionne comme prévu. Nous avons reçu des données et leurs dérivés. Les données ont été augmentées, complétées de manière significative et marquées par deux fonctions différentes. Passons à l'étape suivante : l'équilibrage des classes.
D'ailleurs, je crois que les lecteurs expérimentés dans l'apprentissage automatique ont compris depuis longtemps que nous développerons finalement un modèle de classification, et non de régression. Je préfère les modèles de régression, car j'y vois un peu plus de logique prédictive que dans les modèles de classification.
La prochaine étape est donc l'équilibrage des classes.
Equilibrer les classes. Classification
La classification est une méthode d'analyse fondamentale basée sur la capacité naturelle de l'homme à structurer l'information en fonction de caractéristiques communes. L'une des premières applications de la classification a été la prospection, c'est-à-dire l'identification de zones prometteuses sur la base de caractéristiques géologiques.
Avec l'avènement des ordinateurs, la classification a atteint un nouveau niveau, mais l'essentiel demeure : découvrir des modèles dans le chaos apparent des détails. Pour les marchés financiers, il est important de classer la dynamique des prix en croissance/chute. Mais les classes peuvent être déséquilibrées : peu de tendances et beaucoup de plats.
C'est pourquoi des méthodes d'équilibrage des classes sont utilisées : suppression des exemples dominants ou création d'exemples rares. Cela permet aux modèles de reconnaître de manière fiable des modèles importants mais rares de dynamique des prix.
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)
Nos classes sont donc désormais équilibrées. Nous avons un nombre égal d'étiquettes pour chaque classe (baisse des prix et chute).
Passons maintenant à l'élément le plus important de la prévision des données : les caractéristiques.
Qu’est-ce que les caractéristiques dans l'apprentissage automatique ?
Les attributs et les caractéristiques sont des concepts de base que tout le monde connaît depuis l'enfance. Lorsque nous décrivons un objet, nous énumérons ses propriétés - ce sont les attributs. Un ensemble de ces caractéristiques nous permet de caractériser complètement un objet.
Il en va de même pour l'analyse des données : chaque observation est décrite par un ensemble de caractéristiques numériques et catégorielles. La sélection de caractéristiques informatives est essentielle au succès de l'opération.
À un niveau plus élevé, nous avons l'ingénierie des caractéristiques, lorsque de nouvelles caractéristiques dérivées sont construites à partir des paramètres initiaux pour mieux décrire l'objet de l'étude.
Ainsi, l'expérience humaine de la connaissance du monde à travers la description des objets par leurs caractéristiques est transférée à la science au niveau de la formalisation et de l'automatisation.
Extraction automatique des caractéristiques
L'ingénierie des caractéristiques est la transformation de données sources en un ensemble de caractéristiques pour la formation de modèles d'apprentissage automatique. L'objectif est de trouver les caractéristiques les plus informatives. Il existe une approche manuelle (une personne sélectionne les caractéristiques) et une approche automatique (utilisant des algorithmes).
Nous utiliserons l'approche automatique. Appliquons la méthode de génération de caractéristiques pour extraire automatiquement les meilleures caractéristiques de nos données. Il faut ensuite sélectionner les plus informatives dans l'ensemble obtenu.
Méthode de génération de nouvelles caractéristiques :
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
Veuillez prêter attention au paramètre suivant :
random_seed=42
Le paramètre random_seed est nécessaire pour la reproductibilité des résultats de la génération de nouvelles caractéristiques. La fonction generate_new_features crée de nouvelles caractéristiques à partir des données sources. Entrée : données, nombre de caractéristiques, semences.
Random est initialisé avec une graine donnée. Dans la boucle : un nom est généré, 2 caractéristiques existantes et une opération (addition, soustraction, etc.) sont sélectionnées au hasard. Une nouvelle caractéristique est calculée pour l'opération sélectionnée.
Après la génération, de nouvelles caractéristiques sont ajoutées aux données d’origine. Des données actualisées avec des caractéristiques générées automatiquement sont renvoyées.
Le code nous permet de créer automatiquement de nouvelles fonctionnalités afin d'améliorer la qualité de l'apprentissage automatique.
Lançons le code et regardons le résultat :
100 nouvelles fonctionnalités générées. Passons à l'étape suivante : le regroupement des caractéristiques.
Regroupement des caractéristiques
Le regroupement des caractéristiques permet de regrouper les caractéristiques similaires afin d'en réduire le nombre. Cela permet d'éliminer les données redondantes, de réduire la corrélation et de simplifier le modèle sans sur-ajustement.
Algorithmes populaires : k-means (le nombre de groupes est spécifié, les caractéristiques sont regroupées autour des centroïdes) et clustering hiérarchique (structure arborescente à plusieurs niveaux).
Le regroupement des caractéristiques nous permet de trier un tas de caractéristiques inutiles et d'améliorer l'efficacité du modèle.
Examinons le code de regroupement des caractéristiques :
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
Nous utilisons l'algorithme GMM (Gaussian Mixture Model, ou modèle de mélange gaussien) pour le regroupement des caractéristiques. L'idée de base est que les données sont générées sous la forme d'un mélange de distributions normales, où chaque distribution correspond à un groupe.
Commencez par définir le nombre de groupes. Nous définissons ensuite les paramètres initiaux du modèle : moyennes, matrices de covariance et probabilités des groupes. L'algorithme recalcule cycliquement ces paramètres en utilisant la méthode du maximum de vraisemblance jusqu'à ce qu'ils cessent de changer.
Nous obtenons ainsi les paramètres finaux pour chaque groupe, ce qui nous permet de déterminer à quel groupe appartient la nouvelle caractéristique.
Le GMM est un intéressant. Il est utilisé pour différentes tâches. Il convient aux données dont les groupes ont des formes complexes et des limites floues.
Ce code utilise le GMM pour répartir les caractéristiques en groupes. Les données originales sont prises et les étiquettes de classe sont supprimées. Le GMM est utilisé pour répartir les données en un nombre donné de groupes. Les numéros de groupe sont ajoutés dans une nouvelle colonne. A la fin, un tableau des groupes obtenus est affiché.
Exécutons le code de regroupement et voyons les résultats :
Passons maintenant à la fonction de sélection des meilleures caractéristiques.
Sélectionner les meilleures caractéristiques
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)
Cette fonction extrait les caractéristiques les plus intéressantes et les plus utiles de nos données. Les données d'entrée sont les données originales avec les caractéristiques et les étiquettes des classes, plus le nombre requis de caractéristiques sélectionnées.
Tout d'abord, les étiquettes de classe sont réinitialisées parce qu'elles n'aideront pas à la sélection des caractéristiques. L'algorithme Random Forest est ensuite lancé - un modèle qui construit un ensemble d'arbres de décision sur des ensembles aléatoires de caractéristiques.
Après avoir formé tous les arbres, Random Forest évalue l'importance de chaque caractéristique et son influence sur la classification. Sur la base de ces scores d'importance, la fonction sélectionne les caractéristiques les plus importantes.
Enfin, les caractéristiques sélectionnées sont ajoutées aux données et les étiquettes de classe sont renvoyées. La fonction affiche un tableau avec les caractéristiques sélectionnées et renvoie les données mises à jour.
Pourquoi tout ce remue-ménage ? C'est ainsi que nous nous débarrassons des fonctions inutiles qui ne font que ralentir le processus. Nous laissons les caractéristiques les plus intéressantes, afin que le modèle obtienne de meilleurs résultats lors de l'entraînement sur ces données.
Lançons la fonction et voyons les résultats :
Le meilleur indicateur pour la prévision des prix s'est avéré être le prix d'ouverture lui-même. Le top comprend des fonctionnalités basées sur les moyennes mobiles, les incréments de prix, l'écart-type, les variations de prix quotidiennes et mensuelles. Les caractéristiques générées automatiquement se sont révélées peu informatives.
Le code permet une sélection automatique des caractéristiques importantes, ce qui peut améliorer la performance du modèle et la capacité de généralisation.
Conclusion
Nous avons créé un code de manipulation des données qui anticipe le développement de notre futur modèle d'apprentissage automatique. Passons brièvement en revue les mesures prises :
- Les données initiales pour l'EURUSD ont été extraites de la plateforme MetaTrader 5. Des transformations ont ensuite été effectuées à l'aide d'opérations aléatoires - ajout de bruit, décalage et mise à l'échelle - afin d'augmenter de manière significative la taille de l'échantillon.
- L'étape suivante a consisté à marquer les valeurs de prix avec des marqueurs de tendance spéciaux - croissance et chute, en tenant compte des niveaux de stop loss et de take profit. Pour équilibrer les classes, les exemples redondants ont été supprimés.
- Ensuite, une procédure complexe d'ingénierie des caractéristiques a été mise en œuvre. Tout d'abord, des centaines de nouvelles caractéristiques dérivées ont été automatiquement générées à partir des séries temporelles d’origine. Un regroupement de mélanges gaussiens a ensuite été effectué pour détecter les redondances. Enfin, l'algorithme Random Forest a été utilisé pour classer et sélectionner le sous-ensemble de variables le plus informatif.
- Il en résulte un ensemble de données enrichies de haute qualité avec des caractéristiques optimales. Il servira de base à la formation de modèles d'apprentissage automatique et au développement d'une stratégie de trading en Python avec intégration dans MetaTrader 5.
Dans le prochain article, nous nous concentrerons sur le choix du modèle de classification optimal, son amélioration, la mise en œuvre de la validation croisée et l'écriture d'une fonction de test pour le bundle Python/MQL5.
Traduit du russe par MetaQuotes Ltd.
Article original : https://www.mql5.com/ru/articles/14350





- Applications de trading gratuites
- Plus de 8 000 signaux à copier
- Actualités économiques pour explorer les marchés financiers
Vous acceptez la politique du site Web et les conditions d'utilisation
raw_Prev_Day_Price_Change
raw_Prev_Week_Price_Change
raw_Prev_Week_Price_Change
Je ne comprends pas comment cela fonctionne, je pense que nous avons besoin d'un cadre de temps quotidien avant de calculer cela ou non ?Cher auteur.
Veuillez m'indiquer les versions de python et les modules utilisés.
Lors de l'exécution du script, une erreur apparaît
Votre chemin d'accès ne contient pas d'ansi. Stepan.
Merci pour votre réponse.
Oui, le problème était effectivement dû à des chemins avec des lettres russes. Je l'ai corrigé et tout a fonctionné.....
Le nouvel article Développement d'un robot en Python et MQL5 (Partie 1) : Data preprocessing a été publié :
Auteur : Yevgeniy Koshtenko