English Русский 中文 Español Deutsch 日本語 Português 한국어 Français Türkçe
preview
Sviluppo di un robot in Python e MQL5 (Parte 1): Preelaborazione dei dati

Sviluppo di un robot in Python e MQL5 (Parte 1): Preelaborazione dei dati

MetaTrader 5Sistemi di trading |
469 71
Yevgeniy Koshtenko
Yevgeniy Koshtenko

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:

  1. NumPy: La libreria per lavorare con gli array multidimensionali e le funzioni matematiche.

  2. Pandas: Uno strumento di analisi dei dati che fornisce comode strutture dati per lavorare con tabelle e serie temporali.

  3. Random: Modulo per la generazione di numeri casuali e la selezione di elementi casuali da sequenze.

  4. Datetime: Fornisce classi e funzioni per lavorare con date e orari.

  5. MetaTrader5: Libreria per l'interazione con il terminale di trading MetaTrader 5.

  6. Time: Modulo per lavorare con i ritardi di tempo e di esecuzione dei programmi.

  7. Concurrent.futures: Lo strumento per eseguire compiti paralleli e asincroni. Ne avremo bisogno in futuro per lavorare in parallelo con più valute.

  8. 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.

  9. 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:

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:

2

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.

    3

    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:

    5

    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:

    6

    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:

    8

    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

    File allegati |
    synergy_ml_bot.py (11.74 KB)
    Ultimi commenti | Vai alla discussione (71)
    Andreas Kress
    Andreas Kress | 10 ott 2024 a 07:58
    raw_Prev_Day_Price_Change
    raw_Prev_Week_Price_Change
    raw_Prev_Week_Price_Change
    Non capisco come dovrebbe funzionare, penso che abbiamo bisogno di un time-frame giornaliero prima di calcolare questo o no?
    Andreas Kress
    Andreas Kress | 10 ott 2024 a 08:03
    stenli21 #:

    Caro autore.

    Per favore, dimmi le versioni di python e i moduli utilizzati.


    Quando si esegue lo script, appare un errore




    Il percorso non contiene ansi. Степан.
    Provare a installare python per tutti gli utenti. Questo installerebbe python in c:\Program Files\python
    O qualcosa di simile

    Oppure installare python in C:\python
    Non dimenticate di modificare la variabile del percorso di sistema, se necessario.
    stenli21
    stenli21 | 16 ott 2024 a 19:16
    Andreas Kress #:
    Il percorso manca di ansi. Stepan.
    Provare a installare python per tutti gli utenti. Questo installerà python in c:\Program Files\python
    O qualcosa di simile a questo

    Oppure installare python in C:\python.
    Non dimenticate di modificare la variabile del percorso di sistema se necessario

    Grazie per la risposta.

    Sì, il problema era effettivamente dovuto a percorsi con lettere russe. L'ho risolto e tutto ha funzionato.....

    Andreas Kress
    Andreas Kress | 30 ott 2024 a 08:43
    Quando arriva la terza parte?
    Roberto Liguoro
    Roberto Liguoro | 22 lug 2025 a 08:48
    Grande articolo 
    Sviluppo di un robot in Python e MQL5 (parte 2): Selezione, creazione e addestramento del modello, tester personalizzato in Python Sviluppo di un robot in Python e MQL5 (parte 2): Selezione, creazione e addestramento del modello, tester personalizzato in Python
    Continuiamo la serie di articoli sullo sviluppo di un robot di trading in Python e MQL5. Oggi risolveremo il problema della selezione e dell'addestramento di un modello, del suo test, dell'implementazione della convalida incrociata, della ricerca a griglia, nonché il problema dell'ensemble di modelli.
    Approccio brute force per la ricerca di pattern (Parte VI): Ottimizzazione ciclica Approccio brute force per la ricerca di pattern (Parte VI): Ottimizzazione ciclica
    In questo articolo mostrerò la prima parte dei miglioramenti che mi hanno permesso non solo di chiudere l'intera catena di automazione per il trading su MetaTrader 4 e 5, ma anche di fare qualcosa di molto più interessante. D'ora in poi, questa soluzione mi consente di automatizzare completamente sia la creazione di EA che l'ottimizzazione, nonché di minimizzare i costi di manodopera per trovare configurazioni di trading efficaci.
    Sviluppo di un robot di trading in Python (parte 3): Implementazione di un algoritmo di trading basato su un modello Sviluppo di un robot di trading in Python (parte 3): Implementazione di un algoritmo di trading basato su un modello
    Continuiamo la serie di articoli sullo sviluppo di un robot di trading in Python e MQL5. In questo articolo creeremo un algoritmo di trading in Python.
    Misurazione delle informazioni degli indicatori Misurazione delle informazioni degli indicatori
    L'apprendimento automatico è diventato un metodo popolare per lo sviluppo di strategie. Sebbene sia stata posta maggiore enfasi sulla massimizzazione della redditività e dell'accuratezza delle previsioni, l'importanza dell'elaborazione dei dati utilizzati per costruire i modelli predittivi non ha ricevuto molta attenzione. In questo articolo consideriamo l'utilizzo del concetto di entropia per valutare l'adeguatezza degli indicatori da utilizzare nella costruzione di modelli predittivi, come documentato nel libro Testing and Tuning Market Trading Systems di Timothy Masters.