Sviluppo di un robot in Python e MQL5 (Parte 1): Preelaborazione dei dati
Introduzione
Il mercato sta diventando sempre più complesso. Oggi si sta trasformando in una battaglia di algoritmi. Oltre il 95% del fatturato del trading è generato dai robot.
Il passo successivo è l'apprendimento automatico. Non si tratta di IA forti, ma non sono nemmeno semplici algoritmi lineari. Il modello di apprendimento automatico è in grado di realizzare profitti in condizioni difficili. È interessante applicare l'apprendimento automatico per creare sistemi di trading. Grazie alle reti neurali, il robot di trading analizzerà grandi dati, troverà pattern e prevederà i movimenti dei prezzi.
Esamineremo il ciclo di sviluppo di un robot di trading: la raccolta dei dati, l'elaborazione, l'espansione del campione, l'ingegnerizzazione delle caratteristiche, la selezione e l'addestramento del modello, la creazione di un sistema di trading tramite Python e il monitoraggio delle operazioni.
Lavorare in Python ha i suoi vantaggi: la velocità nel campo dell'apprendimento automatico come la capacità di selezionare e generare caratteristiche. L'esportazione dei modelli in ONNX richiede esattamente la stessa logica di generazione delle caratteristiche di Python, il che non è facile. Ecco perché ho scelto il trading online tramite Python.
Impostazione del compito e scelta dello strumento
L'obiettivo del progetto è creare un modello di apprendimento automatico profittevole e sostenibile per il trading in Python. Fasi di lavoro:
- Raccolta dei dati da MetaTrader 5, caricamento delle funzionalità primarie.
- Aumento dei dati per ampliare il campione.
- Contrassegnare i dati con le etichette.
- Ingegneria delle caratteristiche: generazione, raggruppamento, selezione.
- Selezione e addestramento del modello ML. Eventualmente, ensembling.
- Valutazione dei modelli mediante metriche.
- Sviluppo di test per la valutazione della profittabilità.
- Ottenere un risultato positivo sulla base di nuovi dati.
- Implementazione di un algoritmo di trading tramite Python MQL5.
- Integrazione con MetaTrader 5.
- Migliorare i modelli.
Strumenti: Python MQL5, librerie ML in Python per velocità e funzionalità.
Abbiamo quindi definito gli obiettivi e le finalità. Nell'ambito dell'articolo, realizzeremo i seguenti lavori:
- Raccolta dei dati da MetaTrader 5, caricamento delle funzionalità primarie.
- Aumento dei dati per ampliare il campione.
- Contrassegnare i dati con le etichette.
- Ingegneria delle caratteristiche: generazione, raggruppamento, selezione.
Impostazione dell'ambiente e delle importazioni. Raccolta dei dati
Per prima cosa, è necessario ottenere i dati storici tramite MetaTrader 5. Il codice stabilisce una connessione con la piattaforma di trading passando il percorso del file del terminale al metodo di inizializzazione.
Nel ciclo, ottenere i dati utilizzando il metodo mt5.copy_rates_range() con i seguenti parametri: strumento, timeframe, date di inizio e fine. C'è un contatore di tentativi non riusciti e un ritardo per una connessione stabile.
La funzione termina disconnettendosi dalla piattaforma con il metodo mt5.shutdown().
Si tratta di una funzione separata per ulteriori chiamate nel programma.
Per eseguire lo script, è necessario installare le seguenti librerie:
-
NumPy: La libreria per lavorare con gli array multidimensionali e le funzioni matematiche.
-
Pandas: Uno strumento di analisi dei dati che fornisce comode strutture dati per lavorare con tabelle e serie temporali.
-
Random: Modulo per la generazione di numeri casuali e la selezione di elementi casuali da sequenze.
-
Datetime: Fornisce classi e funzioni per lavorare con date e orari.
-
MetaTrader5: Libreria per l'interazione con il terminale di trading MetaTrader 5.
-
Time: Modulo per lavorare con i ritardi di tempo e di esecuzione dei programmi.
-
Concurrent.futures: Lo strumento per eseguire compiti paralleli e asincroni. Ne avremo bisogno in futuro per lavorare in parallelo con più valute.
-
Tqdm: La libreria per la visualizzazione degli indicatori di avanzamento durante l'esecuzione di operazioni iterative. Ne avremo bisogno in futuro per lavorare in parallelo con più valute.
-
Train_test_split: Funzione che consente di suddividere un set di dati in set di addestramento e test quando si addestrano modelli di apprendimento automatico.
È possibile installarli utilizzando 'pip' eseguendo i seguenti comandi:
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)
Definire le variabili globali: costi di spread e commissioni di intermediazione, date per i campioni di addestramento e di prova, numero massimo di trade, simbolo.
Caricamento delle quotazioni da MetaTrader 5 in un ciclo. I dati vengono convertiti in un DataFrame e arricchiti di caratteristiche:
- Medie mobili SMA a 10 e 20 periodi
- Variazione di prezzo
- Deviazione standard dei prezzi
- Variazione dei volumi
- Variazione di prezzo giornaliero/mensile
- Mediane dei prezzi
Queste caratteristiche aiutano a tenere conto dei fattori che influenzano il prezzo.
Vengono visualizzate le informazioni sulle colonne del DataFrame e sugli ultimi dati.
Vediamo come viene eseguita la nostra prima funzione:

Tutto funziona. Il codice ha caricato con successo le quotazioni, ha preparato e caricato le caratteristiche. Passiamo alla parte successiva del codice.
Aumento dei dati per ampliare il campione
L'incremento dei dati è la generazione di nuovi esempi di addestramento basati su quelli esistenti per aumentare la dimensione del campione e migliorare la qualità del modello. È rilevante per la previsione di serie temporali con dati limitati. Inoltre, riduce gli errori del modello e aumenta la robustezza.
Metodi di incremento dei dati finanziari:
- Aggiunta di rumore (deviazioni casuali) per la robustezza contro il rumore
- Spostamento temporale per la modellazione di diversi scenari di sviluppo
- Scalatura per modellare i picchi e i cali di prezzo
- Inversione dei dati sorgente
Ho implementato la funzione di inserimento dell’incremento che accetta il DataFrame e il numero di nuovi esempi per ogni metodo. Genera nuovi dati utilizzando diversi approcci e li concatena con il DataFrame originale.
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
Chiamate il codice e otterrete i seguenti risultati:

Dopo aver applicato i metodi di incremento dei dati, il nostro set originale di 150.000 barre di prezzo H1 è stato ampliato a ben 747.000 stringhe. Molti studi autorevoli nel campo dell'apprendimento automatico dimostrano che un aumento così significativo del volume dei dati di addestramento, dovuto alla generazione di esempi sintetici, ha un effetto positivo sulle metriche della qualità dei modelli addestrati. Ci aspettiamo che anche nel nostro caso questa tecnica produca l'effetto desiderato.
Etichettatura dei dati
L'etichettatura dei dati è fondamentale per il successo degli algoritmi di apprendimento supervisionato. Ci permette di fornire ai dati di partenza le etichette che il modello apprende. I dati etichettati aumentano l'accuratezza, migliorano la generalizzazione, velocizzano l'addestramento e facilitano la valutazione dei modelli. Nel problema di previsione di EURUSD, abbiamo aggiunto la colonna binaria "etichette" che indica se la prossima variazione di prezzo è superiore allo spread e alla commissione. Ciò consente al modello di apprendere i pattern di spread replay e le tendenze di non-rollback.
L'etichettatura gioca un ruolo fondamentale consentendo agli algoritmi di apprendimento automatico di trovare pattern complessi nei dati che non sono visibili nella loro forma grezza. Passiamo alla revisione del codice.
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
Eseguire questo codice e ottenere il numero di etichette nei dati. La funzione restituisce un frame. Tutto funziona. Tra l'altro, su oltre 700.000 punti dati, il prezzo è cambiato di più dello spread solo in 70.000 casi.

Ora abbiamo un'altra funzione di marcatura dei dati. Questa volta è più vicino ai guadagni effettivi.
Funzione etichette target
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
La funzione ottiene le etichette target per l'addestramento dei modelli di apprendimento automatico sui profitti di trading. La funzione si collega a MetaTrader 5, recupera le informazioni sul simbolo e calcola i livelli di stop/take. Quindi, il prezzo futuro viene determinato dopo un periodo casuale per ogni punto di ingresso. Se la variazione di prezzo supera il take profit e non tocca lo stop, oltre a soddisfare le condizioni di crescita/calo, il segno 1.0/0.0 viene aggiunto di conseguenza. Altrimenti - Nulla. Viene creato un nuovo dataframe con i soli dati etichettati. Nessuno viene sostituito con le medie.
Viene visualizzato il numero di etichette di crescita/calo.
Tutto funziona come previsto. Abbiamo ricevuto i dati e i loro derivati. I dati sono stati aumentati, integrati in modo significativo e contrassegnati da due differenti funzioni. Passiamo alla fase successiva: il bilanciamento delle classi.
Tra l'altro, credo che i lettori esperti di machine learning abbiano capito da tempo che alla fine svilupperemo un modello di classificazione, non di regressione. Mi piacciono di più i modelli di regressione, perché ci vedo un po' più di logica predittiva rispetto ai modelli di classificazione.
Quindi, la nostra prossima mossa è il bilanciamento delle classi.
Bilanciamento delle classi. Classificazione
La classificazione è un metodo fondamentale di analisi basato sulla naturale capacità umana di strutturare le informazioni secondo caratteristiche comuni. Una delle prime applicazioni della classificazione è stata la prospezione, ovvero l'identificazione di aree promettenti in base alle caratteristiche geologiche.
Con l'avvento dei computer, la classificazione ha raggiunto un nuovo livello, ma l'essenza rimane: scoprire pattern nell'apparente caos dei dettagli. Per i mercati finanziari è importante classificare la dinamica dei prezzi in crescita/calo. Tuttavia, le classi possono essere sbilanciate: ci sono pochi trend e molta lateralità.
Pertanto, si ricorre a metodi di bilanciamento delle classi: rimuovendo gli esempi dominanti o generando quelli rari. Ciò consente ai modelli di riconoscere in modo affidabile, pattern dinamici dei prezzi, importanti ma rari.
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) Quindi, le nostre classi sono ora equilibrate. Abbiamo un numero uguale di etichette per ogni classe (calo e caduta dei prezzi).
Passiamo alla cosa più importante nella previsione dei dati: le caratteristiche.
Cosa sono le caratteristiche nell'apprendimento automatico?
Gli attributi e le caratteristiche sono concetti di base familiari a tutti fin dall'infanzia. Quando descriviamo un oggetto, ne elenchiamo le sue proprietà: questi sono gli attributi. Un insieme di queste caratteristiche ci permette di caratterizzare completamente un oggetto.
Lo stesso vale per l'analisi dei dati - ogni osservazione è descritta da un insieme di caratteristiche numeriche e categoriali. La selezione delle caratteristiche informative è fondamentale per il successo.
A un livello superiore, abbiamo l'ingegneria delle caratteristiche, quando dai parametri iniziali vengono costruite nuove caratteristiche derivate per descrivere meglio l'oggetto di studio.
Così, l'esperienza umana di conoscere il mondo attraverso la descrizione degli oggetti in base alle loro caratteristiche viene trasferita alla scienza a livello di formalizzazione e automazione.
Estrazione automatica delle caratteristiche
L'ingegneria delle caratteristiche è la trasformazione dei dati di partenza in un insieme di caratteristiche per l'addestramento dei modelli di apprendimento automatico. L'obiettivo è trovare le caratteristiche più informative. Esiste un approccio manuale (una persona seleziona le caratteristiche) e un approccio automatico (utilizzando algoritmi).
Utilizzeremo l'approccio automatico. Applichiamo il metodo di generazione delle caratteristiche per estrarre automaticamente quelle migliori dai nostri dati. Quindi selezionare quelle più informative dall'insieme risultante.
Metodo di generazione di nuove caratteristiche:
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
Prestare attenzione al seguente parametro:
random_seed=42 Il parametro random_seed è necessario per la riproducibilità dei risultati della generazione di nuove caratteristiche. La funzione generate_new_features crea nuove caratteristiche dai dati di origine. Input: dati, numero di caratteristiche, seme.
Random viene inizializzato con il seme dato. Nel ciclo: viene generato un nome, vengono selezionate casualmente 2 caratteristiche esistenti e un'operazione (addizione, sottrazione, ecc.). Viene calcolata una nuova caratteristica per l'operazione selezionata.
Dopo la generazione, ai dati originali vengono aggiunte nuove caratteristiche. Vengono restituiti dati aggiornati con caratteristiche generate automaticamente.
Il codice ci permette di creare automaticamente nuove caratteristiche per migliorare la qualità dell'apprendimento automatico.
Lanciamo il codice e diamo un'occhiata al risultato:

100 nuove caratteristiche generate. Passiamo alla fase successiva - il raggruppamento (clustering) delle caratteristiche.
Raggruppamento delle caratteristiche
Il clustering delle caratteristiche raggruppa le caratteristiche simili in gruppi per ridurne il loro numero. Questo aiuta a rimuovere i dati ridondanti, a ridurre le correlazioni e a semplificare il modello senza overfitting.
Algoritmi popolari: k-means (il numero di cluster è specificato, le caratteristiche sono raggruppate intorno ai centroidi) e clustering gerarchico (struttura multilivello ad albero).
Il raggruppamento delle caratteristiche ci permette di eliminare un gruppo di caratteristiche inutili e di migliorare l'efficienza del modello.
Vediamo il codice del clustering delle caratteristiche:
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 Per il raggruppamento delle caratteristiche utilizziamo l'algoritmo GMM (Gaussian Mixture Model). L'idea di base è che i dati siano generati come una miscela di distribuzioni normali, dove ogni distribuzione rappresenta un cluster.
Per prima cosa, impostare il numero di cluster. Quindi si impostano i parametri iniziali del modello: medie, matrici di covarianza e probabilità dei cluster. L'algoritmo ricalcola ciclicamente questi parametri utilizzando il metodo della massima probabilità finché non smettono di cambiare.
Di conseguenza, si ottengono i parametri finali per ciascun cluster, in base ai quali è possibile determinare a quale cluster appartiene la nuova caratteristica.
Il GMM è una cosa fantastica. Viene utilizzato in diversi compiti. È ottimo per i dati in cui i cluster hanno forme complesse e confini sfumati.
Questo codice utilizza la GMM per suddividere le caratteristiche in cluster. I dati originali vengono presi e le etichette di classe vengono rimosse. GMM viene utilizzata per la suddivisione in un determinato numero di cluster. I numeri dei cluster vengono aggiunti come nuova colonna. Alla fine, viene stampata una tabella dei cluster ottenuti.
Eseguiamo il codice di clustering e vediamo i risultati:

Passiamo alla funzione di selezione delle caratteristiche migliori.
Selezione delle caratteristiche migliori
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)
Questa funzione estrae le caratteristiche più interessanti e utili dai nostri dati. L'input è costituito dai dati originali con le caratteristiche della classe e le etichette, più il numero richiesto di caratteristiche selezionate.
In primo luogo, le etichette della classe vengono azzerate perché non saranno più utili per la selezione delle caratteristiche. Viene quindi lanciato l'algoritmo Random Forest, un modello che costruisce un gruppo di alberi decisionali su serie casuali di caratteristiche.
Dopo aver addestrato tutti gli alberi, Random Forest valuta l'importanza di ogni caratteristica e la sua influenza sulla classificazione. In base a questi punteggi di importanza, la funzione seleziona le caratteristiche migliori.
Infine, le caratteristiche selezionate vengono aggiunte ai dati e le etichette della classe vengono restituite. La funzione stampa una tabella con le caratteristiche selezionate e restituisce i dati aggiornati.
Perché tutto questo polverone? In questo modo ci liberiamo delle caratteristiche spazzatura che non fanno altro che rallentare il processo. Lasciamo le caratteristiche più interessanti, in modo che il modello mostri risultati migliori, addestrandosi su tali dati.
Lanciamo la funzione e vediamo i risultati:

Il miglior indicatore per la previsione dei prezzi è risultato essere il prezzo di apertura stesso. Il top include caratteristiche basate su medie mobili, incrementi di prezzo, deviazione standard, variazioni di prezzo giornaliere e mensili. Le caratteristiche generate automaticamente si sono rivelate poco informative.
Il codice consente la selezione automatica di caratteristiche importanti, che possono migliorare le prestazioni del modello e la capacità di generalizzazione.
Conclusioni
Abbiamo creato un codice per la manipolazione dei dati che anticipa lo sviluppo del nostro futuro modello di apprendimento automatico. Rivediamo brevemente i passi compiuti:
- I dati iniziali di EURUSD sono stati estratti dalla piattaforma MetaTrader 5. Quindi sono state eseguite trasformazioni con operazioni casuali - imposizione di rumore, spostamenti e scalature per aumentare in modo significativo la dimensione del campione.
- Il passo successivo è stato quello di contrassegnare i valori dei prezzi con speciali indicatori di trend - crescita e calo, tenendo conto dei livelli di stop loss e take profit. Per bilanciare le classi, sono stati rimossi gli esempi ridondanti.
- Successivamente, è stata eseguita una complessa procedura di ingegnerizzazione delle caratteristiche. In primo luogo, centinaia di nuove caratteristiche derivate sono state generate automaticamente dalle serie temporali originali. È stato quindi eseguito un clustering a miscela gaussiana per rilevare la ridondanza. Infine, è stato utilizzato l'algoritmo random forest per classificare e selezionare il sottoinsieme di variabili più informativo.
- Di conseguenza, è stato generato un set di dati arricchito con caratteristiche ottimali di alta qualità. Servirà come base per un ulteriore addestramento dei modelli di apprendimento automatico e per lo sviluppo di una strategia di trading in Python con l'integrazione in MetaTrader 5.
Nel prossimo articolo ci concentreremo sulla scelta del modello di classificazione ottimale, sul suo miglioramento, sull'implementazione della convalida incrociata e sulla scrittura di una funzione tester per il pacchetto Python/MQL5.
Tradotto dal russo da MetaQuotes Ltd.
Articolo originale: https://www.mql5.com/ru/articles/14350
Avvertimento: Tutti i diritti su questi materiali sono riservati a MetaQuotes Ltd. La copia o la ristampa di questi materiali in tutto o in parte sono proibite.
Questo articolo è stato scritto da un utente del sito e riflette le sue opinioni personali. MetaQuotes Ltd non è responsabile dell'accuratezza delle informazioni presentate, né di eventuali conseguenze derivanti dall'utilizzo delle soluzioni, strategie o raccomandazioni descritte.
Sviluppo di un robot in Python e MQL5 (parte 2): Selezione, creazione e addestramento del modello, tester personalizzato in Python
Sviluppo di un robot di trading in Python (parte 3): Implementazione di un algoritmo di trading basato su un modello
- App di trading gratuite
- Oltre 8.000 segnali per il copy trading
- Notizie economiche per esplorare i mercati finanziari
Accetti la politica del sito e le condizioni d’uso
raw_Prev_Day_Price_Changeraw_Prev_Week_Price_Changeraw_Prev_Week_Price_ChangeNon capisco come dovrebbe funzionare, penso che abbiamo bisogno di un time-frame giornaliero prima di calcolare questo o no?Caro autore.
Per favore, dimmi le versioni di python e i moduli utilizzati.
Quando si esegue lo script, appare un errore
Il percorso manca di ansi. Stepan.
Grazie per la risposta.
Sì, il problema era effettivamente dovuto a percorsi con lettere russe. L'ho risolto e tutto ha funzionato.....
Il nuovo articolo Sviluppo di un robot in Python e MQL5 (Parte 1): Preelaborazione dei dati è stato pubblicato:
Autore: Yevgeniy Koshtenko