English Русский Deutsch 日本語
preview
Desenvolvimento de robô em Python e MQL5 (Parte 1): Pré-processamento de dados

Desenvolvimento de robô em Python e MQL5 (Parte 1): Pré-processamento de dados

MetaTrader 5Sistemas de negociação | 29 agosto 2024, 13:04
27 0
Yevgeniy Koshtenko
Yevgeniy Koshtenko

Introdução

O mercado está ficando cada vez mais complexo. Hoje em dia, é praticamente uma batalha de algoritmos. Mais de 95% das negociações são realizadas por robôs. 

Portanto, o próximo passo é usar aprendizado de máquina. Não se trata apenas de IA avançada, nem de algoritmos simples. Um modelo de aprendizado de máquina pode lucrar em situações complexas. Aplicar aprendizado de máquina para desenvolver sistemas de trading é uma ideia interessante. Com redes neurais, o robô de trading pode analisar muitos dados, identificar padrões e prever movimentos de preços.


Vamos agora estudar o ciclo de desenvolvimento de um robô de negociação: coletar dados, processá-los, expandir a amostra, engenharia de características, selecionar e treinar o modelo, criar um sistema de trading em Python e monitorar as operações de trading.

Usar Python oferece vantagens, como acelerar o aprendizado de máquina e selecionar e criar características de maneira eficiente. No entanto, exportar modelos para ONNX exige replicar exatamente a mesma lógica de criação de características usada em Python, o que é complicado. Por isso, optamos pelo trading online via Python.

Definição do problema e escolha da ferramenta

O projeto busca criar um modelo de aprendizado de máquina que seja lucrativo e sustentável para trading usando Python. As etapas incluem:

  • Coletar dados do MetaTrader 5 e carregar as características iniciais.
  • Aumentar os dados para expandir a amostra.  
  • Rotular os dados.
  • Realizar engenharia de características, gerando, agrupando e selecionando-as.
  • Selecionar e treinar o modelo de aprendizado de máquina, possivelmente com ensemble.
  • Avaliar os modelos com métricas .
  • Desenvolver um testador para verificar a lucratividade.  
  • Alcançar resultados positivos em novos dados.
  • Implementar o algoritmo de trading com Python e MQL5.
  • Integrar com MetaTrader 5.
  • Melhorar os modelos.

Ferramentas: Python MQL5, bibliotecas de ML em Python para rapidez e funcionalidade.

Então, com os objetivos e tarefas definidos, neste artigo serão realizadas as seguintes atividades:

  • Coletar dados do MetaTrader 5 e carregar as características iniciais.
  • Aumentar os dados para expandir a amostra.  
  • Rotular os dados.
  • Realizar engenharia de características, gerando, agrupando e selecionando-as.

Configuração do ambiente, importações, coleta de dados

Primeiro, é necessário obter dados históricos através do MetaTrader 5. Um código estabelece a conexão com a plataforma de trading, passando o caminho para o arquivo do terminal no método de inicialização.

Em um loop, obtemos os dados com o método mt5.copy_rates_range() com os parâmetros: instrumento, timeframe, datas de início e fim. Aqui existe um contador de tentativas fracassadas e um delay para uma conexão estável.

Uma função encerra a operação, desconectando-a da plataforma com o método mt5.shutdown().

Esta é uma função separada para chamadas futuras no programa. 

Para executar o script, você precisará instalar as seguintes bibliotecas:

  1. NumPy: Biblioteca para trabalhar com arrays multidimensionais e funções matemáticas.

  2. Pandas: Ferramenta para análise de dados, fornece estruturas de dados convenientes para trabalhar com tabelas e séries temporais.

  3. Random: Módulo para gerar números aleatórios e selecionar elementos aleatórios de sequências.

  4. Datetime: Fornece classes e funções para manipulação de datas e horários.

  5. MetaTrader5: Biblioteca para interagir com o terminal de trading MetaTrader 5.

  6. Time: Módulo para manipulação de tempo e introdução de atrasos na execução do programa.

  7. Concurrent.futures: Ferramenta para execução de tarefas paralelas e assíncronas. Isso será necessário no futuro para trabalho multimoedas paralelo.

  8. Tqdm: Biblioteca para exibição de indicadores de progresso durante a execução de operações iterativas. Isso será necessário no futuro para trabalho multimoedas paralelo.

  9. Train_test_split: Função para dividir o conjunto de dados em amostras de treino e teste ao treinar modelos de aprendizado de máquina.

Você pode instalá-los usando pip, executando os seguintes comandos: Você pode instalá-los usando pip, executando os seguintes 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)

São definidas variáveis globais: custos de spread e comissões das corretoras, datas para as amostras de treino e teste, número máximo de trades, símbolo.

A cotação é carregada do MetaTrader5 em um loop. Os dados são convertidos em DataFrame e enriquecidos com características:

  • Médias móveis SMA de 10 e 20 períodos
  • Variação de preço 
  • Desvio padrão do preço
  • Variação de volume
  • Variação diária/mensal do preço
  • Medianas de preço

Essas características ajudam a considerar fatores que influenciam o preço.

É exibida informação sobre as colunas e os últimos registros do DataFrame.

Vamos verificar como nossa primeira função foi executada:

Primeira função

Tudo funciona. O código carregou as cotações com sucesso, realizou a preparação e o carregamento das características. Vamos para a próxima parte do código.


Aumento de dados para expansão da amostra

Aumentar os dados significa criar novos exemplos de treinamento a partir dos que já existem para ampliar a amostra e melhorar a qualidade do modelo. Isso é especialmente útil na previsão de séries temporais com poucos dados disponíveis. Essa prática ajuda a reduzir o erro dos modelos e a torná-los mais robustos.

Maneiras de aumentar dados financeiros:

  • Adicionar ruído (pequenos desvios aleatórios) para tornar o modelo mais resistente a variações.
  • Deslocar no tempo para simular diferentes cenários de evolução.
  • Escalonar para capturar saltos ou quedas nos preços.
  • Inverter os dados brutos.

Criamos uma função que aumenta os dados de entrada, aceitando um DataFrame e o número de novos exemplos para cada método. Ela gera novos dados usando várias abordagens e depois os combina com o 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

Chamamos o código e obtemos os seguintes resultados:

2

Após aplicar os métodos de aumento de dados, nosso conjunto inicial de 150.000 barras horárias de preços foi expandido para impressionantes 747.000 linhas. Muitos estudos respeitáveis na área de aprendizado de máquina mostram que esse aumento significativo no volume de dados de treinamento através da geração de exemplos sintéticos tem um efeito positivo nas métricas de qualidade dos modelos treinados. Esperamos que, no nosso caso, essa abordagem também produza o efeito desejado.


Rotulagem de dados

A rotulagem de dados é essencial para o sucesso dos algoritmos de aprendizado supervisionado. Ela permite atribuir rótulos aos dados brutos, que a modelo então aprende. Dados rotulados aumentam a precisão, melhoram a generalização, aceleram o treinamento e facilitam a avaliação dos modelos. Na tarefa de previsão do EURUSD, adicionamos uma coluna binária "labels", indicando se a próxima variação de preço superou o spread e a comissão. Isso permite que a modelo aprenda padrões de superação do spread e tendências sem reversão.

    A rotulagem desempenha um papel essencial, permitindo que os algoritmos de aprendizado de máquina identifiquem padrões complexos nos dados, inacessíveis à percepção em sua forma bruta e não processada. Agora, revisemos o 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

    Executamos este código e obtemos a contagem de rótulos nos dados. A função retorna um DataFrame. Tudo funciona. Aliás, de mais de 700.000 unidades de dados, o preço mudou mais que o spread em apenas 70.000 casos.

    3

    Continuamos trabalhando. Agora temos outra função de rotulagem de dados, desta vez mais focada diretamente no lucro.


    Função de rótulos-alvo

    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

    A função obtém os rótulos-alvo para treinar modelos de aprendizado de máquina com base no lucro de trading. Ela se conecta ao MetaTrader 5, extrai informações sobre o símbolo e calcula os níveis de stop/take. Em seguida, para cada ponto de entrada, determina-se o preço futuro em um período aleatório. Se a variação do preço exceder o take e não atingir o stop, e satisfizer as condições de alta/queda, adiciona-se o rótulo 1.0/0.0 respectivamente. Caso contrário, None. É criado um novo DataFrame apenas com os dados rotulados. Os valores None são substituídos pelas médias.

    Exibe-se a contagem de rótulos de alta/queda.

    Assim, tudo está funcionando. Obtivemos os dados e seus derivados, os dados foram aumentados, substancialmente complementados, rotulados por duas funções diferentes. Vamos para o próximo passo - balanceamento de classes.

    Aliás, o leitor experiente em aprendizado de máquina já percebeu há muito tempo que, no final, desenvolveremos um modelo de classificação, não de regressão. Prefiro modelos de regressão, vejo neles um pouco mais de lógica para previsão do que nos modelos de classificação. 

    Portanto, nosso próximo movimento é balancear classes.


    Balanceamento de classes. O que é classificação?

    A classificação é um método fundamental de análise, aproveitando a habilidade natural do ser humano de organizar informações com base em características comuns. Um dos primeiros usos da classificação foi na exploração de jazidas, identificando áreas promissoras a partir de características geológicas.

    Com os computadores, a classificação atingiu um novo nível, mas a essência permanece, que é descobrir padrões no aparente caos de detalhes. Para os mercados financeiros, é importante classificar a dinâmica dos preços em alta/baixa. No entanto, as classes podem estar desbalanceadas, podendo haver poucas tendências e muitos períodos de consolidação.

    Por isso, usamos métodos de balanceamento de classes, como remover exemplos dominantes ou gerar exemplos raros. Isso ajuda os modelos a reconhecer de maneira confiável padrões importantes, mas menos frequentes, na dinâmica dos preços.

    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)

    Agora, as classes estão balanceadas, temos uma quantidade igual de rótulos para cada classe (queda e alta de preços).

    Agora vamos ao que realmente importa na previsão de dados: as características.


    O que são características em aprendizado de máquina?

    Características e atributos são conceitos simples que aprendemos desde cedo. Quando descrevemos um objeto, mencionamos suas propriedades; essas são as características. Um conjunto dessas características permite que descrevamos um objeto de forma completa.

    O mesmo vale para a análise de dados, onde cada observação é descrita por um conjunto de características numéricas e categóricas. Escolher as características certas é crítico para o sucesso.

    Em um nível mais elevado, temos a engenharia de características, onde novos atributos derivados são construídos a partir de parâmetros brutos para uma melhor descrição do objeto de estudo.

    Dessa forma, a experiência humana de entender o mundo descrevendo objetos com suas características é levada para a ciência, onde é concretizada e automatizada.


    Extração automática de características

    A engenharia de características é o processo de transformar dados brutos em um conjunto de características para treinar modelos de aprendizado de máquina. O objetivo é identificar as características mais relevantes. Isso pode ser feito manualmente, onde uma pessoa escolhe as características, ou automaticamente, usando algoritmos.

    Vamos seguir com a abordagem automática. Vamos usar um método que cria novas características para extrair automaticamente as melhores dos nossos dados. Depois, escolheremos as mais relevantes entre as geradas.

    Método de geração de novas características: 

    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

    Preste atenção a este parâmetro:

    random_seed=42

    O parâmetro random_seed é necessário para que a geração de novas características possa ser reproduzida. A função generate_new_características cria novas características a partir dos dados brutos. Entrada: dados, número de características, seed.

    O gerador aleatório é inicializado com o seed fornecido. No loop: um nome é gerado, 2 características existentes são escolhidas aleatoriamente, e uma operação (adição, subtração, etc.) é selecionada. Uma nova característica é calculada com base na operação escolhida.

    Após a geração, as novas características são adicionadas aos dados brutos. Os dados atualizados com as características geradas automaticamente são retornados.

    O código permite criar automaticamente novas características para melhorar a qualidade do aprendizado de máquina.

    Executamos o código, observamos o resultado:

    5

    Foram geradas 100 novas características. Vamos para a próxima etapa, o agrupamento de características.


    Agrupamento de características

    O agrupamento de características junta características semelhantes em grupos para reduzir a sua quantidade. Isso ajuda a eliminar dados redundantes, reduzir a correlação e simplificar o modelo, evitando o overfitting.

    Algoritmos populares: k-means (o número de clusters é definido, as características são agrupadas em torno dos centróides) e agrupamento hierárquica (estrutura hierárquica em forma de árvore).

    O agrupamento de características ajuda a lidar com o excesso de características inúteis e melhora a eficiência do modelo.

    Vamos analisar o código de agrupamento 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

    Usamos o algoritmo GMM (Gaussian Mixture Model) para o agrupamento de características. A ideia principal é que os dados são gerados como se fossem uma mistura de distribuições normais, onde cada distribuição é um cluster.

    Primeiro, definimos o número de clusters. Em seguida, definimos os parâmetros iniciais do modelo: médias, matrizes de covariância e probabilidades dos clusters. O algoritmo recalcula ciclicamente esses parâmetros pelo método de máxima verossimilhança até que eles parem de mudar.

    No final, obtemos os parâmetros finais para cada cluster, que podem ser usados para determinar a qual cluster pertence uma nova característica.

    O GMM é uma ferramenta poderosa, aplicada em diversas tarefas. Ela é ideal para dados cujos clusters têm formas complexas e fronteiras difusas.

    Este código utiliza o GMM para agrupar características em clusters. Os dados brutos são usados, com as etiquetas de classe removidas. O GMM é aplicado para dividir os dados em um número definido de clusters. Os números dos clusters são adicionados como uma nova coluna. Por fim, a tabela dos clusters gerados é exibida.

    Executamos o código de agrupamento e observamos os resultados:

    6

    Vamos para a função de seleção das melhores características.


    Seleção das melhores 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 função identifica as características mais importantes e úteis (características) nos nossos dados. Os dados brutos com características e etiquetas de classe são inseridos, junto com o número necessário de características a serem selecionadas.

    Primeiro, as etiquetas de classe são removidas, pois elas não ajudam na escolha das características. Em seguida, o algoritmo Random Forest é executado — um modelo que constrói uma série de árvores de decisão em conjuntos aleatórios de características.

    Após o treinamento de todas as árvores, o Random Forest avalia o quanto cada feature é importante e influencia a classificação. Com base nessas avaliações de importância, a função seleciona as características mais relevantes.

    No final, as características selecionadas são adicionadas aos dados, e as etiquetas de classe são reintegradas. A função imprime uma tabela com as características escolhidas e retorna os dados atualizados.

    Por que tudo isso? Bem, isso nos permite eliminar as características inúteis que apenas retardam o processo. Mantendo apenas as características mais relevantes, a modelo apresentará resultados melhores ao treinar com esses dados.

    Executamos a função e verificamos o resultado:

    8

    A melhor característica para previsão de preços foi o próprio preço de abertura. No topo da lista estavam características baseadas em médias móveis, incrementos de preços, desvio padrão, e variações de preço diárias e mensais. As características geradas automaticamente não se mostraram informativas.

    O código permite selecionar automaticamente as características importantes, o que pode melhorar o desempenho e a capacidade de generalização da modelo.


    Conclusão

    Assim, criamos um código de processamento de dados que prepara o terreno para o desenvolvimento do nosso futuro modelo de aprendizado de máquina. Vamos recapitular brevemente os passos que realizamos:

    • Os dados brutos do par de moedas euro/dólar foram extraídos da plataforma MetaTrader 5. Em seguida, foram realizadas transformações aplicando operações aleatórias — adição de ruído, deslocamentos, escalonamento — para aumentar significativamente o tamanho da amostra.
    • O próximo passo foi a rotulagem dos valores de preço com etiquetas especiais de tendência — alta e baixa, considerando os níveis de stop-loss e take-profit. Para balancear as classes, exemplos redundantes foram removidos.
    • Depois, realizamos um procedimento complexo de engenharia de características. Primeiro, centenas de novos atributos derivados foram gerados automaticamente a partir das séries temporais brutas. Em seguida, foi feita o agrupamento usando o método de misturas gaussianas para identificar redundâncias. Finalmente, o algoritmo Random Forest foi utilizado para ranquear e selecionar o subconjunto mais informativo de variáveis.
    • Como resultado, formou-se um conjunto de dados enriquecido e de alta qualidade, com características otimizadas. Ele servirá como base para o treinamento futuro de modelos de aprendizado de máquina e o desenvolvimento de uma estratégia de trading em Python com integração ao MetaTrader 5.


    No próximo artigo, abordaremos a escolha do modelo de classificação ideal, sua melhoria, implementação de validação cruzada e a criação de uma função de testador para a integração Python/MQL5.

    Traduzido do russo pela MetaQuotes Ltd.
    Artigo original: https://www.mql5.com/ru/articles/14350

    Arquivos anexados |
    synergy_ml_bot.py (11.74 KB)
    Caminhe em novos trilhos: Personalize indicadores no MQL5 Caminhe em novos trilhos: Personalize indicadores no MQL5
    Vou agora listar todas as possibilidades novas e recursos do novo terminal e linguagem. Elas são várias, e algumas novidades valem a discussão em um artigo separado. Além disso, não há códigos aqui escritos com programação orientada ao objeto, é um tópico muito importante para ser simplesmente mencionado em um contexto como vantagens adicionais para os desenvolvedores. Neste artigo vamos considerar os indicadores, sua estrutura, desenho, tipos e seus detalhes de programação em comparação com o MQL4. Espero que este artigo seja útil tanto para desenvolvedores iniciantes quanto para experientes, talvez alguns deles encontrem algo novo.
    Negociação de Notícias Simplificada (Parte 1): Criando um Banco de Dados Negociação de Notícias Simplificada (Parte 1): Criando um Banco de Dados
    A negociação de notícias pode ser complicada e esmagadora. Neste artigo, passaremos pelos passos para obter dados de notícias. Além disso, aprenderemos sobre o Calendário Econômico do MQL5 e o que ele tem a oferecer.
    Está chegando o novo MetaTrader 5 e MQL5 Está chegando o novo MetaTrader 5 e MQL5
    Esta é apenas uma breve resenha do MetaTrader 5. Eu não posso descrever todos os novos recursos do sistema por um período tão curto de tempo - os testes começaram em 09.09.2009. Esta é uma data simbólica, e tenho certeza que será um número de sorte. Alguns dias passaram-se desde que eu obtive a versão beta do terminal MetaTrader 5 e MQL5. Eu ainda não consegui testar todos os seus recursos, mas já estou impressionado.
    Agrupamento de séries temporais na inferência causal Agrupamento de séries temporais na inferência causal
    Os algoritmos de agrupamento em aprendizado de máquina são ferramentas importantes de aprendizado não supervisionado que permitem dividir os dados brutos em grupos com características semelhantes. Com esses grupos, é possível, por exemplo, realizar análise de mercado para um cluster específico, identificar os clusters mais resilientes em novos conjuntos de dados e também realizar inferências causais. Este artigo apresenta um método original para o agrupamento de séries temporais, utilizando a linguagem Python.