English Русский 中文 Español Deutsch 日本語
preview
Reimaginando Estratégias Clássicas em Python: MA Crossovers

Reimaginando Estratégias Clássicas em Python: MA Crossovers

MetaTrader 5Sistemas de negociação |
375 3
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

Introdução

Muitas das estratégias de negociação atuais foram concebidas em cenários de mercado muito diferentes. Avaliar sua relevância em mercados contemporâneos dominados por algoritmos é crucial. Este artigo examina a estratégia de cruzamento de médias móveis para avaliar sua eficácia no ambiente financeiro atual.

Este artigo abordará os seguintes pontos:

  • Há evidências quantitativas que apoiam o uso contínuo da estratégia?
  • Quais vantagens a estratégia oferece em comparação à análise direta de preços?
  • A estratégia ainda funciona de maneira eficaz em meio ao trading algorítmico moderno?
  • Existem outros indicadores que podem melhorar a precisão da estratégia?
  • A IA pode ser utilizada de maneira eficaz para prever cruzamentos de médias móveis antes que eles aconteçam?

A técnica de usar cruzamentos de médias móveis tem sido amplamente estudada ao longo de décadas. O conceito fundamental de usar essas médias para detectar tendências e sinais de negociação tem sido um pilar na análise técnica, embora sua origem exata seja incerta.

A estratégia de cruzamento de médias móveis geralmente envolve duas médias móveis com períodos diferentes, mas a condição crucial é que um período seja mais longo que o outro. Quando a média móvel de período mais curto cruza acima da média móvel de período mais longo, sinaliza uma tendência de alta em potencial, e o contrário para uma tendência de baixa.

Analistas técnicos têm utilizado essa estratégia por décadas para identificar pontos de entrada e saída, avaliar o sentimento do mercado e para várias outras aplicações. Para determinar sua eficácia atual, submeteremos a estratégia a um teste quantitativo moderno. Nossa abordagem é detalhada a seguir.


Cruzamentos de médias móveis

Fig 1: Exemplo de cruzamentos de médias móveis aplicados ao par CADJPY.

Visão Geral

Estamos prestes a embarcar em uma jornada emocionante, onde vamos conectar nosso terminal MetaTrader5 com nosso ambiente Python. Primeiro, solicitaremos dados M15 para o par EURUSD de 1º de janeiro de 2020 a 25 de junho de 2024. Esse extenso conjunto de dados nos dará uma visão abrangente dos comportamentos recentes do mercado.

Nosso próximo passo é estabelecer dois alvos. O primeiro medirá nossa precisão em prever mudanças diretas de preço, servindo como nossa linha de base. Essa referência nos ajudará a comparar nosso desempenho ao prever cruzamentos de médias móveis. Ao longo do caminho, procuraremos indicadores técnicos adicionais para aumentar nossa precisão. Finalmente, pediremos aos nossos modelos computacionais que identifiquem as variáveis-chave para prever cruzamentos de médias móveis. Se o modelo não priorizar as duas médias móveis que usamos, isso pode indicar que nossas suposições iniciais estavam incorretas.

Antes de mergulhar nos números, vamos considerar os possíveis resultados:

  1. Superação na Previsão Direta de Preço: Se prever mudanças de preço diretamente resulta em maior ou igual precisão em comparação com os cruzamentos de médias móveis, isso sugere que prever cruzamentos pode não oferecer nenhuma vantagem extra, questionando a validade da estratégia.

  2. Superação na Previsão de Cruzamentos: Se alcançarmos melhor precisão ao prever cruzamentos de médias móveis, isso nos motivaria a buscar mais dados para aprimorar nossas previsões, destacando o valor potencial da estratégia.

  3. Irrelevância das Médias Móveis: Se nossos modelos não identificarem nenhuma das médias móveis como crucial para prever cruzamentos, isso implica que outras variáveis podem ser mais significativas, sugerindo que a relação assumida entre as duas médias móveis não se sustenta.

  4. Relevância das Médias Móveis: Se uma ou ambas as médias móveis forem identificadas como importantes para prever cruzamentos, isso confirma uma relação substancial entre elas, permitindo-nos construir modelos confiáveis para previsões informadas.

Esta análise nos ajudará a entender os pontos fortes e fracos do uso de cruzamentos de médias móveis em nossa estratégia de negociação, orientando-nos em direção a métodos de previsão mais eficazes.


O Experimento: Os Cruzamentos de Médias Móveis Ainda São Confiáveis?

Vamos começar importando as bibliotecas padrão do Python de que precisamos.

import pandas as pd
import pandas_ta as ta
import numpy as np
import MetaTrader5 as mt5
from   datetime import datetime
import seaborn as sns
import time

Em seguida, inserimos nossos dados de login.

account = 123436536
password = "Enter Your Password"
server = "Enter Your Broker"

Prosseguindo, tentaremos agora fazer login em nossa conta de negociação.

if(mt5.initialize(login=account,password=password,server=server)):
    print("Logged in succesfully")
else:
    print("Failed to login")

Login realizado com sucesso.

A seguir, definiremos algumas variáveis globais.

timeframe = mt5.TIMEFRAME_M15
deviation = 1000
volume = 0
lot_multiple = 10
symbol = "EURUSD"

Em seguida, buscaremos dados de mercado sobre o símbolo que desejamos negociar.

#Setup trading volume
symbols = mt5.symbols_get()
for index,symbol in enumerate(symbols):
    if symbol.name == "EURUSD":
        print(f"{symbol.name} has minimum volume: {symbol.volume_min}")
        volume = symbol.volume_min * lot_multiple

EURUSD tem volume mínimo: 0,01

Agora nos prepararemos para buscar dados de treinamento.

#Specify date range of data to be modelled
date_start = datetime(2020,1,1)
date_end = datetime.now()

Em seguida, definiremos o quão distante no futuro desejamos prever.

#Define how far ahead we are looking
look_ahead = 20

Podemos então proceder para buscar dados de mercado em nosso terminal MetaTrader5 e, em seguida, rotular os dados. Nosso esquema de rotulagem usa "1" para codificar um movimento de alta e "0" para movimentos de baixa. 

#Fetch market data
market_data = pd.DataFrame(mt5.copy_rates_range("EURUSD",timeframe,date_start,date_end))
market_data["time"] = pd.to_datetime(market_data["time"],unit='s')
#Add simple moving average technical indicator
market_data.ta.sma(length=5,append=True)
#Add simple moving average technical indicator
market_data.ta.sma(length=50,append=True)
#Delete missing rows
market_data.dropna(inplace=True)

#Add a column for the target
market_data["target"] = 0
market_data["close_target"] = 0

#Encoding the target
ma_cross_conditions = [
    (market_data["SMA_5"].shift(-look_ahead) > market_data["SMA_50"].shift(-look_ahead)),
    (market_data["SMA_5"].shift(-look_ahead) < market_data["SMA_50"].shift(-look_ahead))
]
#Encoding pattern
ma_cross_choices = [
    #Fast MA above Slow MA
    1,
    #Fast MA below Slow MA
    0
]

price_conditions = [
    (market_data["close"] > market_data["close"].shift(-look_ahead)),
    (market_data["close"] < market_data["close"].shift(-look_ahead))
]

#Encoding pattern
price_choices = [
    #Price fell
    0,
    #Price rose
    1
]

market_data["target"] = np.select(ma_cross_conditions,ma_cross_choices)
market_data["close_target"] = np.select(price_conditions,price_choices)

#The last rows do not have answers
market_data = market_data[:-look_ahead]
market_data


Nosso dataframe com dados de mercado.

Fig 2: Nosso dataframe com nossos dados de mercado em sua forma atual.

Agora importaremos as bibliotecas de machine learning de que precisamos.

#XGBoost
from xgboost import XGBClassifier
#Catboost
from catboost import CatBoostClassifier
#Random forest
from sklearn.ensemble import RandomForestClassifier
#LDA and QDA
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis , QuadraticDiscriminantAnalysis
#Logistic regression
from sklearn.linear_model import LogisticRegression
#Neural network
from sklearn.neural_network import MLPClassifier
#Time series split
from sklearn.model_selection import TimeSeriesSplit
#Accuracy metrics
from sklearn.metrics import accuracy_score
#Visualising performance
from sklearn.metrics import confusion_matrix
import seaborn as sns
import matplotlib.pyplot as plt
from sklearn.model_selection import learning_curve

Preparando-se para realizar uma divisão de série temporal no conjunto de dados.

#Time series split
splits = 10
gap = look_ahead
models = ["Logistic Regression","Linear Discriminant Analysis","Quadratic Discriminant Analysis","Random Forest Classifier","XGB Classifier","Cat Boost Classifier","Neural Network Small","Neural Network Large"]

Vamos avaliar a precisão de vários modelos diferentes, e armazenaremos a precisão obtida de cada modelo em um data frame. Um data frame armazenará nossa precisão ao prever cruzamentos de médias móveis, e o segundo medirá nossa precisão ao prever mudanças diretas de preço.

error_ma_crossover = pd.DataFrame(index=np.arange(0,splits),columns=models)
error_price = pd.DataFrame(index=np.arange(0,splits),columns=models)

Agora vamos proceder para medir a precisão de cada modelo. Mas primeiro devemos definir as entradas que nossos modelos usarão.

predictors = ["open","high","low","close","tick_volume","spread","SMA_5","SMA_50"]

Para medir a precisão de cada modelo, treinaremos nossos modelos em uma fração do conjunto de dados e depois os testaremos no restante do conjunto que não foi visto durante o treinamento. A biblioteca TimeSeriesSplit particiona nosso data frame para nós e facilita esse processo.

tscv = TimeSeriesSplit(n_splits=splits,gap=gap)
#Training each model to predict changes in the moving average cross over
for i,(train,test) in enumerate(tscv.split(market_data)):
    model = MLPClassifier(solver='lbfgs',alpha=1e-5,hidden_layer_sizes=(20, 10), random_state=1,early_stopping=True)
    model.fit( market_data.loc[train[0]:train[-1],predictors] , market_data.loc[train[0]:train[-1],"target"] ) 
    error_ma_crossover.iloc[i,7] = accuracy_score(market_data.loc[test[0]:test[-1],"target"],model.predict(market_data.loc[test[0]:test[-1],predictors]))

#Training each model to predict changes in the close price
for i,(train,test) in enumerate(tscv.split(market_data)):
    model = MLPClassifier(solver='lbfgs',alpha=1e-5,hidden_layer_sizes=(20, 10), random_state=1,early_stopping=True)
    model.fit( market_data.loc[train[0]:train[-1],predictors] , market_data.loc[train[0]:train[-1],"close_target"] ) 
    error_price.iloc[i,7] = accuracy_score(market_data.loc[test[0]:test[-1],"close_target"],model.predict(market_data.loc[test[0]:test[-1],predictors]))

Vamos primeiro ver o data frame que mede nossa precisão ao prever mudanças diretas de preço.

error_price

Erro ao prever preço

Fig 3: Nossa precisão ao prever mudanças diretas de preço.

Vamos interpretar os resultados antes de prosseguir. O primeiro comentário que podemos fazer é que nenhum dos modelos teve um bom desempenho na tarefa; alguns modelos demonstraram menos de 50% de precisão ao prever o preço diretamente. Esse desempenho é decepcionante, o que implica que poderíamos ter um desempenho semelhante a esses modelos apenas adivinhando aleatoriamente. Nossos modelos estão organizados em ordem de complexidade crescente, com regressão logística simples à esquerda e redes neurais profundas à direita. Nossos modelos estão organizados em ordem de complexidade crescente, com regressão logística simples à esquerda e redes neurais profundas à direita. Vamos agora ver se há alguma melhoria ao prever cruzamentos de médias móveis.

error_ma_crossover

Erro ao prever cruzamentos de médias móveis

Fig 4: Nossa precisão ao prever cruzamentos de médias móveis.

Como podemos ver no data frame acima, a Análise Discriminante Linear (LDA) teve um desempenho excepcional nessa tarefa. Foi o modelo com melhor desempenho por uma ampla margem. Além disso, ao contrastar o desempenho aprimorado do LDA com o quão mal ele se saiu na primeira tarefa, podemos ver claramente que cruzamentos de médias móveis podem ser mais confiáveis para prever do que mudanças diretas de preço. Os benefícios de prever cruzamentos de médias móveis são indiscutíveis nesse caso.

Visualizando os Resultados

Vamos visualizar os resultados obtidos acima. 

Visualizando os Resultados

Fig 5: Visualização dos resultados obtidos.

A melhora no algoritmo LDA é visivelmente notável em boxplots, indicando um aprendizado significativo do modelo. Adicionalmente, houve uma leve, mas perceptível, melhora no desempenho da Regressão Logística. Notavelmente, a Análise Discriminante Linear (LDA) consistentemente produziu pontuações agrupadas de forma bem próxima nos boxplots ao prever cruzamentos de médias móveis, demonstrando precisão e consistência desejáveis. Esse agrupamento sugere que as previsões do modelo eram estáveis, com resíduos provavelmente estacionários, indicando uma relação confiável aprendida pelo modelo.

Agora, vamos analisar os tipos de erros cometidos pelo modelo. Nosso objetivo é determinar se ele apresenta melhor desempenho em identificar movimentos de alta, movimentos de baixa ou se o desempenho é equilibrado entre ambas as tarefas.

Matriz de confusão LDA

Fig 6: Uma matriz de confusão do desempenho do nosso modelo LDA.

A matriz de confusão acima exibe a classificação verdadeira à esquerda e a previsão do modelo na parte inferior. Os dados mostram que o modelo cometeu mais erros ao prever movimentos de alta, classificando incorretamente um movimento de alta como baixa em 47% das vezes. Por outro lado, o modelo teve um bom desempenho ao prever movimentos de baixa, confundindo um movimento de baixa verdadeiro como alta apenas 25% das vezes. Portanto, podemos observar claramente que o modelo é melhor em prever movimentos de baixa do que movimentos de alta. 

Podemos visualizar o progresso de aprendizado do modelo à medida que ele encontra quantidades crescentes de dados de treinamento. O gráfico abaixo serve para avaliar se o modelo está superajustando ou subajustando os dados de treinamento. O superajuste ocorre quando o modelo aprende o ruído dos dados, falhando em capturar relações significativas. O subajuste, por outro lado, é indicado por uma diferença significativa entre a precisão de treinamento (representada pela linha azul) e a precisão de validação (a linha laranja) no gráfico. No gráfico atual, observamos uma diferença perceptível, mas não extensa, entre as pontuações de treinamento e validação, sugerindo que o modelo LDA está de fato superajustando os dados de treinamento. No entanto, a escala no lado esquerdo indica que esse superajuste não é severo.

Curva de aprendizado para análise discriminante linear

Fig 7: A curva de aprendizado do nosso classificador LDA.

Por outro lado, o subajuste é caracterizado por baixa precisão de treinamento e validação. Como exemplo, incluímos a curva de aprendizado de um de nossos modelos de baixo desempenho, a pequena rede neural. No gráfico abaixo, observamos uma relação instável entre o desempenho do modelo e a quantidade de dados de treinamento a que foi exposto. Inicialmente, o desempenho de validação do modelo se deteriora com o aumento dos dados, até atingir um ponto de inflexão e começar a melhorar conforme o tamanho do treinamento se aproxima de 10.000 amostras. Subsequentemente, a melhoria atinge um platô, com apenas incrementos marginais, apesar do contínuo aumento na quantidade de dados de treinamento disponíveis.

Curva de aprendizado pequena rede neural

Fig 8: A curva de aprendizado da nossa pequena rede neural.

Eliminação de Recursos

Na maioria dos projetos de aprendizado de máquina, é incomum que todas as entradas estejam diretamente relacionadas à variável alvo. Tipicamente, apenas um subconjunto das entradas disponíveis é relevante para prever o alvo. Eliminar entradas irrelevantes oferece várias vantagens, tais como:

  1. Melhoria na eficiência computacional durante o treinamento do modelo e a engenharia de características.
  2. Maior precisão do modelo, especialmente se as características removidas eram ruidosas.

Em seguida, precisamos determinar se há uma relação significativa entre as médias móveis. Utilizaremos algoritmos de eliminação de recursos para validar a relação assumida. Se esses algoritmos não eliminarem as médias móveis da lista de entradas, isso indica que existe uma relação significativa. Por outro lado, se eles conseguirem remover essas características, isso sugere que não há uma relação significativa entre as médias móveis e o cruzamento de médias móveis.

Empregaremos uma técnica de seleção de características conhecida como seleção regressiva. Este método começa ajustando um modelo linear usando todas as entradas disponíveis e, em seguida, mede a precisão do modelo. Posteriormente, uma característica é removida por vez, e o impacto na precisão do modelo é observado. A característica que causa a menor redução na precisão é eliminada em cada etapa até que nenhuma característica reste. Nesta fase, o algoritmo seleciona automaticamente os recursos mais importantes que identificou e os recomenda para uso.

Uma desvantagem significativa da eliminação de características que vale a pena mencionar é que, quando colunas ruidosas e sem importância estão presentes no conjunto de dados, colunas importantes podem parecer não informativas. Consequentemente, o algoritmo de seleção regressiva pode, inadvertidamente, eliminar uma característica importante porque ela parece não informativa devido ao ruído no sistema.

Agora, vamos ver quais colunas o computador considera importantes. Começamos importando uma biblioteca chamada mlxtend que contém implementações do algoritmo de seleção regressiva.

from mlxtend.feature_selection import SequentialFeatureSelector

Aplicamos então o algoritmo ao nosso conjunto de dados. Vamos prestar atenção especial a 3 dos parâmetros que passamos:

  1. "k_features=" instrui o algoritmo sobre quantas colunas selecionar. Podemos instruir o algoritmo a selecionar apenas as colunas que ele acredita serem necessárias passando um intervalo que começa em 1 e vai até o número total de colunas no conjunto de dados.
  2. "forward=" instrui o algoritmo se ele deve usar seleção progressiva ou regressiva; queremos usar seleção regressiva, portanto definimos este parâmetro como "False".
  3. "n_jobs=" instrui o algoritmo se ele deve realizar cálculos em paralelo; passamos "-1" para dar ao algoritmo permissão para usar todos os núcleos disponíveis, reduzindo significativamente o tempo gasto. 

backward_feature_selector = SequentialFeatureSelector(LinearDiscriminantAnalysis(),
                                                      k_features=(1,market_data.loc[:,predictors].shape[1]),
                                                      forward=False,
                                                      verbose=2,
                                                      scoring="accuracy",
                                                      cv=5,
						      n_jobs=-1
                                                     ).fit(market_data.loc[:,predictors],market_data.loc[:,"target"])

[Parallel(n_jobs=-1)]: Usando o backend LokyBackend com 8 workers simultâneos.

[Parallel(n_jobs=-1)]: Concluído 3 de 8 | tempo decorrido: 8,0s tempo restante: 13,3s

[Parallel(n_jobs=-1)]: Concluído 8 de 8 | tempo decorrido: 8,0s tempo restante: 0.0s

[Parallel(n_jobs=-1)]: Concluído 8 de 8 | tempo decorrido: 8,0s finalizado

Após a conclusão do processo, podemos obter uma lista das entradas que o algoritmo considera importantes usando o seguinte comando:

backward_feature_selector.k_feature_names_

('open', 'high', 'close', 'SMA_5', 'SMA_50')

E como podemos ver, o algoritmo de seleção regressiva incluiu nossas 2 médias móveis na lista de características importantes. Isso é uma ótima notícia para nós, pois valida que nossa estratégia de negociação não é apenas resultado de uma regressão espúria.

Engenharia de Características

Agora que estabelecemos uma relação significativa entre nossas duas médias móveis que justifica esforços adicionais de melhoria, vamos explorar se indicadores técnicos adicionais podem melhorar nossa precisão na previsão de cruzamentos de médias móveis. Aqui é onde o aprendizado de máquina se inclina mais para a arte do que para a ciência, já que prever quais entradas serão benéficas antecipadamente é desafiador. Nossa abordagem envolverá adicionar várias características que acreditamos serem úteis e avaliar seu impacto real.

Coletaremos dados de mercado do mesmo mercado utilizado anteriormente, mas desta vez incorporaremos indicadores adicionais:

  1. Moving Average Convergence Divergence (MACD): Um indicador técnico poderoso de confirmação de tendências, que pode ajudar a observar mudanças nos regimes subjacentes do mercado.
  2. Awesome Oscillator: Renomado por fornecer sinais de saída muito confiáveis, além de mostrar claramente mudanças no momento de uma tendência.
  3. Aroon: Usado para identificar o início de novas tendências.
  4. Chaikin's Commodity Index: Atua como um barômetro para medir se um ativo financeiro está sobrecomprado ou sobrevendido.
  5. Percent Return: Ajuda a observar o crescimento do preço e se ele está aumentando positivamente ou negativamente. 
Vamos prosseguir adicionando os indicadores mencionados acima, juntamente com nossas médias móveis originais.

#Fetch market data
market_data = pd.DataFrame(mt5.copy_rates_range("EURUSD",timeframe,date_start,date_end))
market_data["time"] = pd.to_datetime(market_data["time"],unit='s')
#Add simple moving average technical indicator
market_data.ta.sma(length=5,append=True)
#Add simple moving average technical indicator
market_data.ta.sma(length=50,append=True)
#Add macd
market_data.ta.macd(append=True)
#Add awesome oscilator
market_data.ta.ao(append=True)
#Add aroon
market_data.ta.aroon(append=True)
#Add chaikins comodity index
market_data.ta.cci(append=True)
#Add percent return
market_data.ta.percent_return(append=True)
#Delete missing rows
market_data.dropna(inplace=True)
#Add the target
market_data["target"] = 0
market_data.loc[market_data["SMA_5"].shift(-look_ahead) > market_data["SMA_50"].shift(-look_ahead),"target"] = 1
market_data.loc[market_data["SMA_5"].shift(-look_ahead) < market_data["SMA_50"].shift(-look_ahead),"target"] = 0
#The last rows do not have answers
market_data = market_data[:-look_ahead]
market_data

Nosso novo DataFrame.

Fig 9: Algumas das novas linhas adicionais adicionadas ao nosso DataFrame.


Após realizar a seleção de características, nosso algoritmo de seleção regressiva identificou as seguintes variáveis como importantes:

backward_feature_selector = SequentialFeatureSelector(LinearDiscriminantAnalysis(),
                                                      k_features=(1,market_data.loc[:,predictors].shape[1]),
                                                      forward=False,
                                                      verbose=2,
                                                      scoring="accuracy",
                                                      cv=5
                                                     ).fit(market_data.iloc[:,1:-1],market_data.loc[:,"target"])
backward_feature_selector.k_feature_names_

('close', 'tick_volume', 'spread', 'SMA_5', 'SMA_50', 'MACDh_12_26_9', 'AO_5_34')

Construindo Nossa Estratégia de Negociação

Agora estamos prontos para consolidar tudo que aprendemos em uma estratégia de negociação. 

Primeiro, ajustamos nosso modelo com todos os dados de treinamento disponíveis, usando apenas as colunas que identificamos como úteis.

predictors = ['close','tick_volume','spread','SMA_5','SMA_50','MACDh_12_26_9','AO_5_34']
model = LinearDiscriminantAnalysis()
model.fit(market_data.loc[:,predictors],market_data.loc[:,"target"])

Em seguida, definimos funções para buscar dados de mercado no terminal MetaTrader 5.

def get_prices():
    start = datetime(2024,6,1)
    end   = datetime.now()
    data  = pd.DataFrame(mt5.copy_rates_range("EURUSD",timeframe,start,end))
    #Add simple moving average technical indicator
    data.ta.sma(length=5,append=True)
    data.ta.sma(length=50,append=True)
    #Add awesome oscilator
    data.ta.ao(append=True)
    #Add macd
    data.ta.macd(append=True)
    #Delete missing rows
    data.dropna(inplace=True)
    data['time'] = pd.to_datetime(data['time'],unit='s')
    data.set_index('time',inplace=True)
    data = data.loc[:,['close','tick_volume','spread','SMA_5','SMA_50','MACDh_12_26_9','AO_5_34']]
    data = data.iloc[-2:,:]
    return(data)

Subsequentemente, precisamos de outro método para obter previsões do nosso modelo LDA.

#Get signals LDA model
def ai_signal(input_data,_model):
    #Get a forecast
    forecast = _model.predict(input_data)
    return forecast[1]

Agora podemos construir nossa estratégia de negociação.

#Now we define the main body of our Python Moving Average Crossover Trading Bot
if __name__ == '__main__':
    #We'll use an infinite loop to keep the program running
    while True:
        #Fetching model prediction
        signal = ai_signal(get_prices(),model)
        
        #Decoding model prediction into an action
        if signal == 1:
            direction = 'buy'
        elif signal == 0:
            direction = 'sell'
        
        print(f'AI Forecast: {direction}')
        
        #Opening A Buy Trade
        #But first we need to ensure there are no opposite trades open on the same symbol
        if direction == 'buy':
            #Close any sell positions
            for pos in mt5.positions_get():
                if pos.type == 1:
                    #This is an open sell order, and we need to close it
                    close_order(pos.ticket)
            
            if not mt5.positions_totoal():
                #We have no open positions
                mt5.Buy(symbol,volume)
        
        #Opening A Sell Trade
        elif direction == 'sell':
            #Close any buy positions
            for pos in mt5.positions_get():
                if pos.type == 0:
                    #This is an open buy order, and we need to close it
                    close_order(pos.ticket)
            
            if not mt5.positions_get():
                #We have no open positions
                mt5.sell(symbol,volume)
        
        print('time: ', datetime.now())
        print('-------\n')
        time.sleep(60)

Previsão de IA: venda

time:  2024-06-25 14:35:37.954923

-------


Nossa estratégia de negociação em ação

Fig 10: Nossa estratégia de negociação em ação.


Implementação no MQL5

Seguindo em frente, vamos utilizar a API do MQL5 para desenvolver nosso próprio classificador do zero. Há inúmeras vantagens em criar um classificador personalizado no MQL5. Como autor, acredito firmemente que as soluções nativas em MQL5 oferecem flexibilidade incomparável.

Se exportássemos nosso modelo para o formato ONNX, precisaríamos de um modelo separado para cada mercado que desejamos negociar. Além disso, negociar em diferentes períodos de tempo exigiria múltiplos modelos ONNX para cada mercado. Ao construir nosso classificador diretamente em MQL5, ganhamos a capacidade de negociar em qualquer mercado sem essas limitações.

Vamos criar um novo projeto.

MQL5 EA

Fig 11: Criando um EA para implementar nossa estratégia.


Nossa primeira tarefa é definir algumas variáveis globais que usaremos ao longo do programa.

//Global variables
int ma_5,ma_50;
double bid, ask;
double min_volume;
double ma_50_reading[],ma_5_reading[];
int size;
double current_prediction;
int state = -1;
matrix ohlc;
vector target;
double b_nort = 0;
double b_one = 0;
double b_two = 0;
long min_distance,atr_stop;

Também teremos entradas ajustáveis pelo usuário final.

//Inputs
int input lot_multiple = 20;
int input positions = 2;
double input sl_width = 0.4;

Por último, importaremos a biblioteca de negociação para nos ajudar a gerenciar nossas posições.

//Libraries
#include <Trade\Trade.mqh>
CTrade Trade;

Em seguida, precisamos definir funções auxiliares que nos ajudem a buscar dados, rotular os dados de treinamento, treinar nosso modelo e obter previsões. Vamos começar definindo uma função para buscar dados de treinamento e rotular o alvo para nosso classificador. 

//+----------------------------------------------------------------------+
//|This function is responsible for getting our training data ready      |
//+----------------------------------------------------------------------+
void get_training_data(void)
  {
//How much data are we going to use?
   size = 100;
//Copy price data
   ohlc.CopyRates(_Symbol,PERIOD_CURRENT,COPY_RATES_CLOSE,1,size);
//Get indicator data
   ma_50 = iMA(_Symbol,PERIOD_CURRENT,50,0,MODE_EMA,PRICE_CLOSE);
   ma_5 = iMA(_Symbol,PERIOD_CURRENT,5,0,MODE_EMA,PRICE_CLOSE);
   CopyBuffer(ma_50,0,0,size,ma_50_reading);
   CopyBuffer(ma_5,0,0,size,ma_5_reading);
   ArraySetAsSeries(ma_50_reading,true);
   ArraySetAsSeries(ma_5_reading,true);
//Label the target
   target = vector::Zeros(size);
   for(int i = 0; i < size; i++)
     {
      if(ma_5_reading[i] > ma_50_reading[i])
        {
         target[i] = 1;
        }

      else
         if(ma_5_reading[i] < ma_50_reading[i])
           {
            target[i] = 0;
           }
     }

//Feedback
   Print("Done getting training data.");
  }

Nosso modelo possui três coeficientes que utiliza para fazer previsões. Esses coeficientes precisam ser otimizados. Usaremos uma equação de atualização amigável para iniciantes para ajustar esses coeficientes. Medindo o erro nas previsões do modelo, modificaremos iterativamente os coeficientes para minimizar o erro e melhorar a precisão do sistema. Mas antes de começarmos a otimizar o modelo, precisamos primeiro definir como ele faz previsões. 

//+----------------------------------------------------------------------+
//|This function is responsible for making predictions using our model   |
//+----------------------------------------------------------------------+
double model_predict(double input_one,double input_two)
  {
//We simply return the probability that the shorter moving average will rise above the slower moving average
   double prediction = 1 / (1 + MathExp(-(b_nort + (b_one * input_one) + (b_two * input_two))));
   return prediction;
  }

Agora que nosso modelo pode fazer previsões, podemos medir o erro nas suas previsões e iniciar o processo de otimização. Inicialmente, todos os três coeficientes serão definidos como 0. Ajustaremos iterativamente os coeficientes em pequenos passos para minimizar o erro total no sistema. 

//+----------------------------------------------------------------------+
//|This function is responsible for  training our model                  |
//+----------------------------------------------------------------------+
bool train_model(void)
  {
//Update the coefficients
   double learning_rate = 0.3;
   for(int i = 0; i < size; i++)
     {
      //Get a prediction from the model
      current_prediction = model_predict(ma_5_reading[i],ma_50_reading[i]);
      //Update each coefficient
      b_nort = b_nort + learning_rate * (target[i] - current_prediction) * current_prediction * (1 - current_prediction) * 1;
      b_one = b_one + learning_rate * (target[i] - current_prediction) * current_prediction * (1-current_prediction) * ma_5_reading[i];
      b_two = b_two + learning_rate * (target[i] - current_prediction) * current_prediction * (1-current_prediction) * ma_50_reading[i];
      Print(current_prediction);
     }

//Show updated coefficient values
   Print("Updated coefficient values");
   Print(b_nort);
   Print(b_one);
   Print(b_two);
   return(true);
  }

Após treinar o modelo com sucesso, seria benéfico ter uma função que recupere previsões do modelo. Essas previsões servirão como nossos sinais de negociação. Lembre-se: uma previsão de 1 é um sinal de compra, indicando que o modelo espera que a média móvel de período curto suba acima da média móvel de período longo. Por outro lado, uma previsão de 0 é um sinal de venda, indicando que o modelo espera que a média móvel de período curto caia abaixo da média móvel de período longo.

//Get the model's current forecast
void current_forecast()
  {
//Get indicator data
   ma_50 = iMA(_Symbol,PERIOD_CURRENT,50,0,MODE_EMA,PRICE_CLOSE);
   ma_5 = iMA(_Symbol,PERIOD_CURRENT,5,0,MODE_EMA,PRICE_CLOSE);
   CopyBuffer(ma_50,0,0,1,ma_50_reading);
   CopyBuffer(ma_5,0,0,1,ma_5_reading);
//Get model forecast
   model_predict(ma_5_reading[0],ma_50_reading[0]);
   interpret_forecast();
  }

Queremos que nosso Expert Advisor (EA) atue com base nas previsões do modelo. Portanto, escreveremos uma função para interpretar as previsões do modelo e tomar a ação apropriada: comprar quando o modelo prevê 1 e vender quando prevê 0. 

//+----------------------------------------------------------------------+
//|This function is responsible for taking action on our model's forecast|
//+----------------------------------------------------------------------+
void interpret_forecast(void)
  {
   if(current_prediction > 0.5)
     {
      state = 1;
      Trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,min_volume * lot_multiple,ask,0,0,"Volatitlity Doctor AI");
     }

   if(current_prediction < 0.5)
     {
      state = 0;
      Trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,min_volume * lot_multiple,bid,0,0,"Volatitlity Doctor AI");
     }
  }

Agora que nossa aplicação pode aprender com dados, fazer previsões e agir com base nessas previsões, precisamos criar funções adicionais para gerenciar posições abertas. Especificamente, queremos que nosso programa adicione trailing stop losses (stop móvel) e take profits (alvos) para cada posição, gerenciando nossos níveis de risco. Não queremos posições abertas sem um limite de risco definido. A maioria das estratégias de negociação recomenda um tamanho fixo de stop loss de 100 pips, mas queremos garantir que nossos níveis de stop loss e take profit sejam definidos dinamicamente com base na volatilidade atual do mercado. Portanto, usaremos o Average True Range (ATR) para calcular quão amplos ou estreitos nossos stops devem ser. Usaremos um múltiplo do ATR para determinar esses níveis.

//+----------------------------------------------------------------------+
//|This function is responsible for calculating our SL & TP values       |
//+----------------------------------------------------------------------+
void CheckAtrStop()
  {

//First we iterate over the total number of open positions
   for(int i = PositionsTotal() -1; i >= 0; i--)
     {

      //Then we fetch the name of the symbol of the open position
      string symbol = PositionGetSymbol(i);

      //Before going any furhter we need to ensure that the symbol of the position matches the symbol we're trading
      if(_Symbol == symbol)
        {
         //Now we get information about the position
         ulong ticket = PositionGetInteger(POSITION_TICKET); //Position Ticket
         double position_price = PositionGetDouble(POSITION_PRICE_OPEN); //Position Open Price
         long type = PositionGetInteger(POSITION_TYPE); //Position Type
         double current_stop_loss = PositionGetDouble(POSITION_SL); //Current Stop loss value

         //If the position is a buy
         if(type == POSITION_TYPE_BUY)
           {

            //The new stop loss value is just the ask price minus the ATR stop we calculated above
            double atr_stop_loss = NormalizeDouble(ask - ((min_distance * sl_width)/2),_Digits);
            //The new take profit is just the ask price plus the ATR stop we calculated above
            double atr_take_profit = NormalizeDouble(ask + (min_distance * sl_width),_Digits);

            //If our current stop loss is less than our calculated ATR stop loss
            //Or if our current stop loss is 0 then we will modify the stop loss and take profit
            if((current_stop_loss < atr_stop_loss) || (current_stop_loss == 0))
              {
               Trade.PositionModify(ticket,atr_stop_loss,atr_take_profit);
              }
           }

         //If the position is a sell
         else
            if(type == POSITION_TYPE_SELL)
              {
               //The new stop loss value is just the ask price minus the ATR stop we calculated above
               double atr_stop_loss = NormalizeDouble(bid + ((min_distance * sl_width)/2),_Digits);
               //The new take profit is just the ask price plus the ATR stop we calculated above
               double atr_take_profit = NormalizeDouble(bid - (min_distance * sl_width),_Digits);

               //If our current stop loss is greater than our calculated ATR stop loss
               //Or if our current stop loss is 0 then we will modify the stop loss and take profit
               if((current_stop_loss > atr_stop_loss) || (current_stop_loss == 0))
                 {
                  Trade.PositionModify(ticket,atr_stop_loss,atr_take_profit);
                 }
              }
        }
     }
  }

Em seguida, precisamos de uma função que será chamada sempre que quisermos calcular novos valores para stop loss e take profit.

//+------------------------------------------------------------------+
//|This function is responsible for updating our SL&TP values        |
//+------------------------------------------------------------------+
void ManageTrade()
  {
   CheckAtrStop();
  }

Agora que definimos nossas funções auxiliares, podemos começar a chamá-las dentro dos nossos manipuladores de eventos. Quando nosso programa carregar pela primeira vez, queremos iniciar o processo de treinamento. Portanto, chamaremos a função auxiliar responsável pelo treinamento dentro do manipulador de eventos OnInit.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   //Define important global variables
   min_volume = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
   min_distance = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL);
   //Train the model
   get_training_data();
   if(train_model())
     {
      interpret_forecast();
     }
   return(INIT_SUCCEEDED);
  }

Após treinar o modelo, podemos começar a negociação real.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//Get updates bid and ask prices
   bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
   ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);

   if(PositionsTotal() == 0)
     {
      current_forecast();
     }

   if(PositionsTotal() > 0)
     {
      ManageTrade();
     }
  }

Saída do Modelo

Fig 12: Um exemplo da saída do nosso Expert Advisor.

Nosso EA em ação

Fig 13: Nosso Expert Advisor em ação.


Conclusão

Neste artigo, demonstramos que é computacionalmente mais fácil para nosso modelo prever cruzamentos de médias móveis do que prever mudanças no preço diretamente.

Como em todos os meus artigos, prefiro fornecer explicações técnicas no final, enquanto demonstro o princípio primeiro. Existem várias possíveis razões para esta observação. Uma razão potencial é que, dependendo dos períodos escolhidos, médias móveis podem não cruzar tão frequentemente quanto os preços mudam de direção de forma errática. Em outras palavras, nas últimas duas horas, o preço pode ter subido, depois caído, ou mudado de direção duas vezes. No entanto, durante o mesmo período, as médias móveis podem não ter cruzado. Portanto, cruzamentos de médias móveis podem ser mais fáceis de prever porque não mudam de direção tão rapidamente quanto o próprio preço. Essa é apenas uma possível explicação. Sinta-se à vontade para pensar por conta própria, tirar suas próprias conclusões e compartilhá-las nos comentários abaixo.

Seguindo em frente, empregamos a seleção regressiva para eliminação de características, uma técnica onde um modelo linear é treinado iterativamente com uma característica removida a cada passo, com base no impacto sobre a precisão do modelo. Essa abordagem ajuda a identificar e reter as características mais informativas, embora seja suscetível a eliminar características importantes que possam parecer não informativas devido ao ruído.

Tendo validado uma relação significativa entre duas médias móveis, exploramos a integração de indicadores técnicos adicionais: MACD, Awesome Oscillator, Aroon, Chaikins Commodity Index e Percent Return. Esses indicadores visam aprimorar nossa capacidade de prever cruzamentos de médias móveis com precisão. No entanto, a seleção desses indicadores permanece algo artístico devido à natureza imprevisível de seu impacto no desempenho do modelo.

Em suma, nossa abordagem combina validação empírica com seleção estratégica de características para provar quantitativamente que cruzamentos de médias móveis podem ser previstos e que qualquer esforço para melhorar essa estratégia de negociação definitivamente não seria um desperdício de tempo.

Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/15160

Últimos Comentários | Ir para discussão (3)
Robert Mark Salmon
Robert Mark Salmon | 15 jul. 2024 em 09:10

Qualquer ajuda com este erro


O pacote PyPI 'sklearn' está obsoleto, use 'scikit-learn'

em vez de 'sklearn' para comandos pip.

Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana | 15 jul. 2024 em 13:08
Robert Mark Salmon #:

Alguma ajuda com esse erro?


O pacote PyPI 'sklearn' está obsoleto, use 'scikit-learn'

em vez de 'sklearn' para comandos pip.

É engraçado que eu estava instalando o scikit-learn em um ambiente virtual, o comando 'scikit-learn' é o caminho a seguir, executei o comando há poucos instantes:



Pip install scikit learn

Aliaksandr Kazunka
Aliaksandr Kazunka | 6 abr. 2025 em 07:09
Olá! Tenho uma pergunta novamente: como são escolhidas as configurações do indicador? O período ideal, por exemplo, será diferente para cada instrumento. O que não funciona com MA_5 e MA_50 em um instrumento pode funcionar perfeitamente em outro.
Ferramentas econométricas para previsão de volatilidade: Modelo GARCH Ferramentas econométricas para previsão de volatilidade: Modelo GARCH
O artigo descreve as propriedades do modelo não linear de heterocedasticidade condicional (GARCH). O indicador iGARCH para prever a volatilidade um passo à frente é construído com base nele. A biblioteca de análise numérica ALGLIB é usada para estimar os parâmetros do modelo.
Construindo um Modelo de Restrição de Tendência com Candlestick (Parte 5): Sistema de Notificação (Parte III) Construindo um Modelo de Restrição de Tendência com Candlestick (Parte 5): Sistema de Notificação (Parte III)
Esta parte da série de artigos é dedicada à integração do WhatsApp com o MetaTrader 5 para notificações. Incluímos um fluxograma para simplificar o entendimento e discutiremos a importância das medidas de segurança na integração. O principal objetivo dos indicadores é simplificar a análise por meio da automação, e eles devem incluir métodos de notificação para alertar os usuários quando condições específicas forem atendidas. Descubra mais neste artigo.
Negociação com spreads no mercado Forex usando o fator de sazonalidade Negociação com spreads no mercado Forex usando o fator de sazonalidade
Este artigo analisa as possibilidades de criação e fornecimento de dados de relatórios sobre o uso do fator de sazonalidade na negociação por meio de spreads no mercado Forex.
Data Science e Machine Learning (Parte 25): Previsão de Séries Temporais de Forex Usando uma Rede Neural Recorrente (RNN) Data Science e Machine Learning (Parte 25): Previsão de Séries Temporais de Forex Usando uma Rede Neural Recorrente (RNN)
Redes neurais recorrentes (RNNs) se destacam em utilizar informações passadas para prever eventos futuros. Suas notáveis capacidades preditivas foram aplicadas em diversos domínios com grande sucesso. Neste artigo, implementaremos modelos de RNN para prever tendências no mercado de forex, demonstrando seu potencial para aumentar a precisão das previsões no trading de forex.