English Русский Deutsch 日本語 Português
preview
Desarrollo de un robot en Python y MQL5 (Parte 1): Preprocesamiento de datos

Desarrollo de un robot en Python y MQL5 (Parte 1): Preprocesamiento de datos

MetaTrader 5Sistemas comerciales | 9 septiembre 2024, 11:14
259 0
Yevgeniy Koshtenko
Yevgeniy Koshtenko

Introducción

El mercado es cada vez más complejo. Hoy se está convirtiendo en una batalla de algoritmos. Más del 95% del volumen de negocio lo generan los robots. 

El siguiente paso es el aprendizaje automático. No son IA fuertes, pero tampoco son simples algoritmos lineales. El modelo de aprendizaje automático es capaz de obtener beneficios en condiciones difíciles. Es interesante aplicar el aprendizaje automático para crear sistemas de negociación. Gracias a las redes neuronales, el robot de trading analizará big data, encontrará patrones y predecirá los movimientos de los precios.


Examinaremos el ciclo de desarrollo de un robot de negociación: recopilación de datos, procesamiento, ampliación de muestras, ingeniería de características, selección y formación de modelos, creación de un sistema de negociación mediante Python y supervisión de las operaciones.

Trabajar en Python tiene sus propias ventajas: la velocidad en el campo del aprendizaje automático, así como la capacidad de seleccionar y generar características. La exportación de modelos a ONNX requiere exactamente la misma lógica de generación de características que en Python, lo que no es fácil. Por eso he elegido el comercio en línea a través de Python.

Definir la tarea y elegir una herramienta

El objetivo del proyecto es crear un modelo de aprendizaje automático rentable y sostenible para el comercio en Python. Etapas del trabajo:

  • Recopilación de datos de MetaTrader 5, cargando las características principales.
  • Aumento de datos para ampliar la muestra.  
  • Marcado de datos con etiquetas.
  • Ingeniería de características: generación, agrupación y selección.
  • Selección y entrenamiento del modelo ML. Posiblemente, ensamblar.
  • Evaluación de modelos mediante métricas.
  • Desarrollo de pruebas para evaluar la rentabilidad.  
  • Obtener un resultado positivo a partir de nuevos datos.
  • Implementación de un algoritmo de negociación mediante Python MQL5.
  • Integración con MetaTrader 5.
  • Mejora de los modelos.

Herramientas: Python MQL5, librerías ML en Python para mayor velocidad y funcionalidad.

Así pues, hemos definido las metas y los objetivos. En el marco del artículo realizaremos los siguientes trabajos:

  • Recopilación de datos de MetaTrader 5, cargando las características principales.
  • Aumento de datos para ampliar la muestra.  
  • Marcado de datos con etiquetas.
  • Ingeniería de características: generación, agrupación y selección.

Configuración del entorno y las importaciones. Recogida de datos

En primer lugar, tenemos que obtener datos históricos a través de MetaTrader 5. El código establece una conexión con la plataforma de negociación pasando la ruta al archivo del terminal al método de inicialización.

En el bucle, obtenga los datos utilizando el método mt5.copy_rates_range() con los siguientes parámetros: instrumento, marco temporal, fechas de inicio y fin. Hay un contador de intentos fallidos y un retardo para una conexión estable.

La función termina desconectándose de la plataforma mediante el método mt5.shutdown().

Se trata de una función independiente para su posterior llamada en el programa. 

Para ejecutar el script, necesitará instalar las siguientes librerías:

  1. NumPy: La librería para trabajar con matrices multidimensionales y funciones matemáticas.

  2. Pandas: Herramienta de análisis de datos que proporciona cómodas estructuras de datos para trabajar con tablas y series temporales.

  3. Random: Módulo para generar números aleatorios y seleccionar elementos aleatorios de secuencias.

  4. Datetime: Proporciona clases y funciones para trabajar con fechas y horas.

  5. MetaTrader5: Librería para la interacción con el terminal de comercio MetaTrader 5.

  6. Time: Módulo para trabajar con retardos temporales y de ejecución de programas.

  7. Concurrent.futures: La herramienta para ejecutar tareas paralelas y asíncronas. Lo necesitaremos en el futuro para el trabajo paralelo multidivisa.

  8. Tqdm: Librería para mostrar indicadores de progreso al realizar operaciones iterativas. Lo necesitaremos en el futuro para el trabajo paralelo multidivisa.

  9. Train_test_split: Función para dividir un conjunto de datos en conjuntos de entrenamiento y de prueba cuando se entrenan modelos de aprendizaje automático.

Puede instalarlos con 'pip' ejecutando los siguientes comandos:

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)

Define variables globales: costos de spread y comisiones del bróker, fechas para las muestras de entrenamiento y prueba, número máximo de operaciones, símbolo.

Carga de cotizaciones de MetaTrader 5 en bucle. Los datos se convierten en un DataFrame y se enriquecen con características:

  • Medias móviles SMA de 10 y 20 periodos
  • Cambio de precio 
  • Desviación típica de los precios
  • Cambio de volumen
  • Variación diaria/mensual de los precios
  • Medias de precios

Estas características ayudan a tener en cuenta los factores que influyen en el precio.

Se muestra información sobre las columnas del DataFrame y las últimas entradas.

Veamos cómo se ejecuta nuestra primera función:

Primera función

Todo funciona. El código ha cargado correctamente las cotizaciones, preparado y cargado las funciones. Pasemos a la siguiente parte del código.


Aumento de datos para ampliar la muestra

El aumento de datos consiste en generar nuevos ejemplos de entrenamiento a partir de los existentes para aumentar el tamaño de la muestra y mejorar la calidad del modelo. Es pertinente para la previsión de series temporales con datos limitados. Además, reduce los errores del modelo y aumenta su robustez.

Métodos de aumento de datos financieros:

  • Añadir ruido (desviaciones aleatorias) para aumentar la robustez frente al ruido
  • Desplazamiento temporal para modelizar distintos escenarios de desarrollo
  • Escalado para modelizar las subidas y bajadas de precios
  • Inversión de datos de origen

He implementado la función de aumento de entrada que acepta DataFrame y el número de nuevos ejemplos para cada método. Genera nuevos datos utilizando diferentes enfoques y los concatena con el 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

Llama al código y obtén los siguientes resultados:

2

Tras aplicar métodos de aumento de datos, nuestro conjunto original de 150.000 barras de precios H1 se amplió a unas impresionantes 747.000 cadenas. Muchos estudios autorizados sobre aprendizaje automático demuestran que un aumento tan significativo del volumen de datos de entrenamiento debido a la generación de ejemplos sintéticos tiene un efecto positivo en las métricas de calidad de los modelos entrenados. Esperamos que en nuestro caso esta técnica también produzca el efecto deseado.


Datos de la etiqueta

El etiquetado de datos es fundamental para el éxito de los algoritmos de aprendizaje supervisado. Nos permite proporcionar a los datos de origen etiquetas que el modelo aprende a continuación. Los datos etiquetados aumentan la precisión, mejoran la generalización, aceleran el entrenamiento y facilitan la evaluación de los modelos. En el problema de previsión del EURUSD, añadimos la columna binaria «etiquetas» que indica si el siguiente cambio de precio fue mayor que el diferencial y la comisión. Esto permite al modelo aprender patrones de repetición de spreads y tendencias de no retroceso.

    El etiquetado desempeña un papel fundamental para que los algoritmos de aprendizaje automático encuentren patrones complejos en los datos que no son visibles en su forma bruta. Pasemos a la revisión del código.

    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

    Ejecute este código y obtenga el número de etiquetas en los datos. La función devuelve un marco. Todo funciona. Por cierto, de más de 700.000 puntos de datos, el precio sólo varió más que el diferencial en 70.000 casos.

    3

    Ahora tenemos otra función de marcado de datos. Esta vez, se acerca más a los ingresos reales.


    Función de etiquetas de destino

    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 función obtiene etiquetas objetivo para entrenar modelos de aprendizaje automático sobre los beneficios de las operaciones. La función se conecta a MetaTrader 5, recupera la información del símbolo y calcula los niveles de stop/take. A continuación, se determina el precio futuro tras un periodo aleatorio para cada punto de entrada. Si el cambio de precio supera el take profit y no toca el stop, además de cumplir las condiciones de crecimiento/caída, se añade la marca 1,0/0,0 correspondiente. En caso contrario, ninguna. Se crea un nuevo marco de datos con sólo los datos etiquetados. Ninguno se sustituye por promedios.

    Se muestra el número de etiquetas de crecimiento/caída.

    Todo funciona según lo previsto. Hemos recibido datos y sus derivados. Los datos se han aumentado, completado significativamente y marcado con dos funciones diferentes. Pasemos al siguiente paso: el equilibrio de clases.

    Por cierto, creo que los lectores con experiencia en aprendizaje automático hace tiempo que han comprendido que, en última instancia, desarrollaremos un modelo de clasificación, no uno de regresión. Me gustan más los modelos de regresión, ya que veo un poco más de lógica predictiva en ellos que en los modelos de clasificación. 

    Así que nuestro siguiente paso es el equilibrio de clases.


    Clases de equilibrio. Clasificación

    La clasificación es un método fundamental de análisis basado en la capacidad humana natural de estructurar la información según características comunes. Una de las primeras aplicaciones de la clasificación fue la prospección, es decir, la identificación de zonas prometedoras a partir de sus características geológicas.

    Con la llegada de los ordenadores, la clasificación ha alcanzado un nuevo nivel, pero la esencia permanece: descubrir patrones en el aparente caos de los detalles. Para los mercados financieros, es importante clasificar la dinámica de los precios en crecimiento/caída. Sin embargo, las clases pueden estar desequilibradas: hay pocas tendencias y muchos pisos.

    Por lo tanto, se utilizan métodos de equilibrado de clases: eliminación de ejemplos dominantes o generación de ejemplos raros. Esto permite a los modelos reconocer con fiabilidad patrones de dinámica de precios importantes pero poco frecuentes.

    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)

    Por lo tanto, nuestras clases están ahora equilibradas. Tenemos el mismo número de etiquetas para cada clase (caída de precios y caída).

    Pasemos a lo más importante en la previsión de datos: las características.


    ¿Qué son las características en el aprendizaje automático?

    Los atributos y las características son conceptos básicos que todos conocemos desde la infancia. Cuando describimos un objeto, enumeramos sus propiedades: son los atributos. Un conjunto de estas características nos permite caracterizar completamente un objeto.

    Lo mismo ocurre en el análisis de datos: cada observación se describe mediante un conjunto de características numéricas y categóricas. La selección de elementos informativos es fundamental para el éxito.

    En un nivel superior, tenemos la ingeniería de características, cuando se construyen nuevas características derivadas a partir de los parámetros iniciales para describir mejor el objeto de estudio.

    Así, la experiencia humana de conocer el mundo a través de la descripción de los objetos por sus características se traslada a la ciencia en el nivel de la formalización y la automatización.


    Extracción automática de características

    La ingeniería de características es la transformación de los datos de origen en un conjunto de características para el entrenamiento de modelos de aprendizaje automático. El objetivo es encontrar las características más informativas. Existe un enfoque manual (una persona selecciona las características) y otro automático (mediante algoritmos).

    Utilizaremos el enfoque automático. Apliquemos el método de generación de características para extraer automáticamente las mejores características de nuestros datos. A continuación, seleccione las más informativas del conjunto resultante.

    Método de generación de nuevos elementos: 

    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

    Por favor, preste atención al siguiente parámetro:

    random_seed=42

    El parámetro random_seed es necesario para la reproducibilidad de los resultados de la generación de nuevas características. La función generate_new_features crea nuevas características a partir de los datos de origen. Input: datos, número de características, semilla.

    Random se inicializa con una semilla dada. En el bucle: se genera un nombre, se seleccionan aleatoriamente 2 características existentes y una operación (suma, resta, etc.). Se calcula una nueva característica para la operación seleccionada.

    Tras la generación, se añaden nuevas características a los datos originales. Se devuelven datos actualizados con características generadas automáticamente.

    El código nos permite crear automáticamente nuevas funciones para mejorar la calidad del aprendizaje automático.

    Lancemos el código y echemos un vistazo al resultado:

    5

    100 nuevas características generadas. Pasemos a la siguiente fase: la agrupación de características.


    Agrupamiento de características

    La agrupación de características agrupa características similares para reducir su número. Esto ayuda a eliminar datos redundantes, reducir la correlación y simplificar el modelo sin sobreajustarlo.

    Algoritmos populares: k-means (se especifica el número de clusters, las características se agrupan en torno a los centroides) y clustering jerárquico (estructura de árbol multinivel).

    La agrupación de características nos permite eliminar un montón de características inútiles y mejorar la eficacia del modelo.

    Veamos el código de agrupación de características:

    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

    Utilizamos el algoritmo GMM (Gaussian Mixture Model) para la agrupación de características. La idea básica es que los datos se generan como una mezcla de distribuciones normales, donde cada distribución es un conglomerado.

    En primer lugar, establezca el número de clusters. A continuación, fijamos los parámetros iniciales del modelo: medias, matrices de covarianza y probabilidades de agrupación. El algoritmo recalcula cíclicamente estos parámetros utilizando el método de máxima verosimilitud hasta que dejan de cambiar.

    Como resultado, obtenemos los parámetros finales de cada cluster, mediante los cuales podemos determinar a qué cluster pertenece la nueva característica.

    GMM es algo genial. Se utiliza en diferentes tareas. Es bueno para datos en los que los conglomerados tienen formas complejas y límites borrosos.

    Este código utiliza GMM para dividir las características en clusters. Se toman los datos originales y se eliminan las etiquetas de clase. GMM se utiliza para dividir en un número determinado de clusters. Los números de clúster se añaden como una nueva columna. Al final, se imprime una tabla con los clusters obtenidos.

    Ejecutemos el código de agrupación y veamos los resultados:

    6

    Pasemos a la función de seleccionar las mejores características.


    Seleccionar las mejores características

    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)

    Esta función extrae las características más interesantes y útiles de nuestros datos. La entrada son los datos originales con las características de clase y las etiquetas, más el número necesario de características seleccionadas.

    En primer lugar, las etiquetas de clase se anulan porque no ayudarán en la selección de características. A continuación, se pone en marcha el algoritmo Random Forest, un modelo que construye un grupo de árboles de decisión sobre conjuntos aleatorios de características.

    Después de entrenar todos los árboles, Random Forest evalúa la importancia de cada característica y en qué medida afecta a la clasificación. A partir de estas puntuaciones de importancia, la función selecciona las características más importantes.

    Por último, las características seleccionadas se añaden a los datos y se obtienen las etiquetas de clase. La función imprime una tabla con las características seleccionadas y devuelve los datos actualizados.

    ¿Por qué tanto alboroto? Así nos libramos de funciones basura que sólo ralentizan el proceso. Dejamos las características más frescas, para que el modelo muestre mejores resultados, entrenando con esos datos.

    Lancemos la función y veamos los resultados:

    8

    El mejor indicador para la previsión de precios resultó ser el propio precio de apertura. La parte superior incluye funciones basadas en medias móviles, incrementos de precios, desviación estándar y variaciones de precios diarias y mensuales. Las características generadas automáticamente resultaron ser poco informativas.

    El código permite la selección automática de características importantes, lo que puede mejorar el rendimiento del modelo y su capacidad de generalización.


    Conclusión

    Hemos creado un código de manipulación de datos que anticipa el desarrollo de nuestro futuro modelo de aprendizaje automático. Repasemos brevemente los pasos dados:

    • Los datos iniciales del EURUSD se extrajeron de la plataforma MetaTrader 5. A continuación, se realizaron transformaciones mediante operaciones aleatorias: imposición de ruido, desplazamientos y escalado para aumentar significativamente el tamaño de la muestra.
    • El siguiente paso consistió en marcar los valores de los precios con marcadores de tendencia especiales: crecimiento y caída, teniendo en cuenta los niveles de stop loss y take profit. Para equilibrar las clases, se eliminaron los ejemplos redundantes.
    • A continuación, se llevó a cabo un complejo procedimiento de ingeniería de rasgos. En primer lugar, se generaron automáticamente cientos de nuevas características derivadas a partir de las series temporales originales. A continuación, se realizó una agrupación de mezclas gaussianas para detectar la redundancia. Por último, se utilizó el algoritmo de random forest para clasificar y seleccionar el subconjunto de variables más informativo.
    • Como resultado, se generó un conjunto de datos enriquecidos de alta calidad con características óptimas. Servirá de base para el posterior entrenamiento de modelos de aprendizaje automático y el desarrollo de una estrategia de negociación en Python con integración en MetaTrader 5.


    En el próximo artículo, nos centraremos en elegir el modelo de clasificación óptimo, mejorarlo, implementar la validación cruzada y escribir una función de comprobación para el paquete Python/MQL5.

    Traducción del ruso hecha por MetaQuotes Ltd.
    Artículo original: https://www.mql5.com/ru/articles/14350

    Archivos adjuntos |
    synergy_ml_bot.py (11.74 KB)
    Cómo construir y optimizar un sistema de trading basado en la volatilidad (Chaikin Volatility - CHV) Cómo construir y optimizar un sistema de trading basado en la volatilidad (Chaikin Volatility - CHV)
    En este artículo, proporcionaremos otro indicador basado en la volatilidad llamado Chaikin Volatility. Entenderemos cómo construir un indicador personalizado después de identificar cómo se puede utilizar y construir. Compartiremos algunas estrategias sencillas que se pueden utilizar y luego las pondremos a prueba para entender cuál puede ser mejor.
    Redes neuronales: así de sencillo (Parte 80): Modelo generativo y adversarial del Transformador de grafos (GTGAN) Redes neuronales: así de sencillo (Parte 80): Modelo generativo y adversarial del Transformador de grafos (GTGAN)
    En este artículo, le presentamos el algoritmo GTGAN, introducido en enero de 2024 para resolver problemas complejos de disposición arquitectónica con restricciones gráficas.
    Introducción a MQL5 (Parte 5): Funciones de trabajo con arrays para principiantes Introducción a MQL5 (Parte 5): Funciones de trabajo con arrays para principiantes
    En el quinto artículo de nuestra serie, nos familiarizaremos con el mundo de los arrays en MQL5. Este artículo ha sido pensado para principiantes. En este artículo intentaremos repasar conceptos complejos de programación de manera simplificada para que el material resulte comprensible para todos. Asimismo, exploraremos conceptos básicos, discutiremos diferentes cuestiones y compartiremos conocimientos.
    Clústeres de series temporales en inferencia causal Clústeres de series temporales en inferencia causal
    Los algoritmos de agrupamiento en el aprendizaje automático son importantes algoritmos de aprendizaje no supervisado que pueden dividir los datos originales en grupos con observaciones similares. Utilizando estos grupos, puede analizar el mercado de un grupo específico, buscar los grupos más estables utilizando nuevos datos y hacer inferencias causales. El artículo propone un método original de agrupación de series temporales en Python.