English Русский 中文 Español Deutsch 日本語
preview
Repensando estratégias clássicas (Parte X): A IA pode operar o MACD?

Repensando estratégias clássicas (Parte X): A IA pode operar o MACD?

MetaTrader 5Experts |
142 0
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

O cruzamento de médias móveis é provavelmente uma das estratégias de trading mais antigas que existem. O indicador de Convergência e Divergência de Médias Móveis (Moving Average Convergence Divergence (MACD)) é um indicador muito popular baseado no conceito de cruzamento de médias móveis. Nossa comunidade tem crescido com a chegada de muitos novos participantes, que talvez se interessem em saber sobre o poder preditivo do indicador MACD em sua busca por desenvolver a melhor estratégia de trading possível. Além disso, há analistas técnicos experientes que utilizam o MACD em suas estratégias e que podem ter a mesma dúvida em mente. Neste artigo, apresentaremos uma análise empírica da capacidade de previsão do indicador para o par EURUSD. Além disso, ensinaremos técnicas de modelagem que você poderá usar para aprimorar a análise técnica com ajuda da IA.


Visão geral da estratégia de trading

O indicador MACD é utilizado principalmente para identificar tendências de mercado e medir a força do movimento de uma tendência. Esse indicador foi criado na década de 1970 pelo falecido Gerald Appel (Gerrald Appel). Appel era gestor financeiro de clientes privados, e seu sucesso se devia à sua abordagem de trading baseada em análise técnica. Ele inventou o indicador MACD há cerca de 50 anos.

Figura 1: Gerald Appel, o criador do indicador MACD

Analistas técnicos utilizam o indicador para identificar pontos de entrada e saída de diversas maneiras. A Fig. 2 abaixo mostra uma captura de tela do indicador MACD aplicado ao par GBPUSD com as configurações padrão. O indicador já vem incluso na sua instalação padrão do MetaTrader 5. A linha vermelha, chamada de linha principal do MACD, é calculada como a diferença entre duas médias móveis, uma rápida e outra lenta. Sempre que a linha principal cruza para baixo da marca de 0, o mercado provavelmente está em tendência de baixa, e o contrário também é válido quando a linha cruza acima de 0.

De maneira semelhante, a própria linha principal também pode ser usada para avaliar a força do mercado. Somente o aumento nos níveis de preço levará a um aumento no valor da linha principal e, inversamente, uma queda nos níveis de preço fará com que a linha principal diminua. Assim, pontos de inflexão, quando a linha principal assume a forma semelhante a uma tigela, surgem como resultado de uma mudança na dinâmica do mercado. Diversas estratégias de trading foram desenvolvidas em torno do MACD. Estratégias mais sofisticadas e bem elaboradas são voltadas para a detecção de divergência no MACD.

A divergência do MACD ocorre quando os níveis de preço estão em um forte movimento de tendência, rompendo para novos extremos. Enquanto isso, por outro lado, o indicador MACD segue uma tendência que apenas enfraquece e começa a cair, o que contrasta fortemente com o movimento forte dos preços observado no gráfico. Em geral, a divergência do MACD é interpretada como um sinal de alerta precoce de reversão da tendência, permitindo que os traders fechem suas posições abertas antes que os mercados se tornem mais voláteis.

Figura 2: Indicador MACD com as configurações padrão no gráfico GBPUSD M1

Existem muitos céticos que questionam o uso do indicador MACD como um todo. Vamos começar abordando o elefante na sala. Todos os indicadores técnicos são agrupados como indicadores atrasados. Isso significa que os indicadores técnicos só mudam depois que os níveis de preço mudaram; eles não podem se alterar antes disso. Indicadores macroeconômicos, como o nível de inflação global, e notícias geopolíticas, como o início de uma guerra ou um desastre natural, podem impactar a oferta e a demanda. Esses são considerados indicadores antecedentes, pois podem se alterar rapidamente antes que os níveis de preço reflitam essa mudança.

Muitos traders acreditam que esses sinais atrasados provavelmente levam os traders a abrir posições quando o movimento do mercado já se esgotou. Além disso, normalmente ocorrem reversões de tendência que não foram precedidas por divergências no MACD. E, da mesma forma, pode-se observar uma divergência no MACD que não resulta em reversão de tendência.

Esses fatos nos levam a questionar a confiabilidade desse indicador e se ele realmente possui algum poder preditivo digno de atenção. Queremos avaliar se é possível superar a defasagem inerente ao indicador com o uso de IA. Se o indicador MACD mostrar ser robusto, implementaremos um modelo de IA que:

  1. Use os valores do indicador para prever níveis futuros de preço.
  2. Faça a previsão do próprio indicador MACD.

Dependendo de qual abordagem de modelagem apresentasse menor margem de erro. Caso contrário, se nossa análise indicar que o MACD pode não ter poder preditivo dentro da estratégia atual, escolheremos, em vez disso, o modelo mais eficaz para previsão dos níveis de preço.


Visão geral da metodologia

Nossa análise começou com a criação de um script específico escrito em MQL5 para extrair exatamente 100.000 linhas de cotações de mercado M1 do par EURUSD, juntamente com os respectivos sinais e valores principais do indicador MACD, em um arquivo CSV. A julgar por nossas visualizações dos dados, o indicador MACD aparentemente não define bem os níveis futuros de preço. As variações nos níveis de preço provavelmente são independentes dos valores do indicador, e além disso, o cálculo do indicador adicionou uma estrutura não linear e complexa aos dados, o que pode dificultar a modelagem.

Os dados que obtivemos do nosso terminal MetaTrader 5 foram divididos em 2 metades. Usamos a primeira metade para avaliar a precisão do nosso modelo por meio de uma validação cruzada com 5 divisões. Em seguida, criamos 3 modelos idênticos de redes neurais profundas e os treinamos em 3 subconjuntos diferentes dos nossos dados:

  1. Modelo de preço:  Prevê os níveis de preço usando cotações OHLC do MetaTrader 5
  2. Modelo MACD: Prevê os valores do indicador MACD usando cotações OHLC e leituras do MACD
  3. Modelo completo: Prevê os níveis de preço usando cotações OHLC e o indicador MACD

A segunda metade da divisão foi usada para testar os modelos. O primeiro modelo apresentou a maior precisão no teste – 69%. Nossos algoritmos de seleção de características mostraram que as cotações de mercado obtidas no MetaTrader 5 eram mais informativas do que os valores do MACD.

Com isso, passamos a otimizar o melhor modelo que tínhamos – um modelo de regressão para prever o preço futuro do par EURUSD. No entanto, logo encontramos dificuldades, pois detectamos ruído em nossos dados de treinamento. Infelizmente, não conseguimos superar os resultados de uma simples regressão linear no conjunto de teste. Portanto, substituímos o modelo superajustado pelo Método de Vetores de Suporte (SVM).

Posteriormente, exportamos nosso modelo SVM para o formato ONNX e criamos um EA usando uma abordagem combinada para prever tanto os níveis futuros de preço do EURUSD quanto o indicador MACD.


Obtendo os dados de que precisamos

Para dar o pontapé inicial, começamos utilizando o ambiente de desenvolvimento integrado (IDE) MetaEditor. Criamos o script descrito abaixo para extrair nossos dados de mercado diretamente do terminal MetaTrader 5. Solicitamos 100.000 linhas de dados históricos M1 e os exportamos em formato CSV. O script abaixo preencherá nosso arquivo CSV com os valores de Time, Open, High, Low, Close e dois valores do MACD. Basta arrastar o script para qualquer par que você queira analisar, caso deseje seguir conosco.

//+------------------------------------------------------------------+
//|                                                      ProjectName |
//|                                      Copyright 2020, CompanyName |
//|                                       http://www.companyname.net |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Zororo Ndawana"
#property link      "https://www.mql5.com/en/users/gamuchiraindawa"
#property version   "1.00"
#property script_show_inputs

//+------------------------------------------------------------------+
//| Script Inputs                                                    |
//+------------------------------------------------------------------+
input int size = 100000; //How much data should we fetch?

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
int indicator_handler;
double indicator_buffer[];
double indicator_buffer_2[];

//+------------------------------------------------------------------+
//| On start function                                                |
//+------------------------------------------------------------------+
void OnStart()
  {

//--- Load indicator
   indicator_handler = iMACD(Symbol(),PERIOD_CURRENT,12,26,9,PRICE_CLOSE);
   CopyBuffer(indicator_handler,0,0,size,indicator_buffer);
   CopyBuffer(indicator_handler,1,0,size,indicator_buffer_2);
   ArraySetAsSeries(indicator_buffer,true);
   ArraySetAsSeries(indicator_buffer_2,true);

//--- File name
   string file_name = "Market Data " + Symbol() +" MACD " +  ".csv";

//--- Write to file
   int file_handle=FileOpen(file_name,FILE_WRITE|FILE_ANSI|FILE_CSV,",");

   for(int i= size;i>=0;i--)
     {
      if(i == size)
        {
         FileWrite(file_handle,"Time","Open","High","Low","Close","MACD Main","MACD Signal");
        }

      else
        {
         FileWrite(file_handle,iTime(Symbol(),PERIOD_CURRENT,i),
                   iOpen(Symbol(),PERIOD_CURRENT,i),
                   iHigh(Symbol(),PERIOD_CURRENT,i),
                   iLow(Symbol(),PERIOD_CURRENT,i),
                   iClose(Symbol(),PERIOD_CURRENT,i),
                   indicator_buffer[i],
                   indicator_buffer_2[i]
                  );
        }
     }
//--- Close the file
   FileClose(file_handle);
  }
//+------------------------------------------------------------------+


Pré-processamento de dados

Agora que exportamos nossos dados em formato CSV, vamos carregar os dados no nosso ambiente de trabalho Python. Primeiro, carregaremos as bibliotecas necessárias.

#Load libraries
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt

Lendo os dados.

#Read in the data
data = pd.read_csv("Market Data EURUSD MACD .csv")

Vamos definir quão longe no futuro devemos fazer as previsões.

#Forecast horizon
look_ahead = 20

Adicionaremos alvos binários para indicar se o valor atual é maior do que nos 20 casos anteriores, tanto para o preço de fechamento do EURUSD quanto para a linha principal do MACD.

#Let's add labels
data["Bull Bear"] = np.where(data["Close"] < data["Close"].shift(look_ahead),0,1)
data["MACD Bull"] = np.where(data["MACD Main"] < data["MACD Main"].shift(look_ahead),0,1)

data = data.loc[20:,:]

data

Figura 3: Algumas das colunas em nosso dataframe

Além disso, precisamos definir nossos indicadores-alvo.

data["MACD Target"] = data["MACD Main"].shift(-look_ahead)
data["Price Target"] = data["Close"].shift(-look_ahead)

data["MACD Binary Target"] = np.where(data["MACD Main"] < data["MACD Target"],1,0)
data["Price Binary Target"] = np.where(data["Close"] < data["Price Target"],1,0)

data = data.iloc[:-20,:]


Análise exploratória de dados

Os diagramas de dispersão nos ajudam a visualizar a relação entre variáveis dependentes e independentes. O gráfico abaixo mostra que definitivamente existe uma relação entre os níveis futuros de preço e o valor atual do MACD. O problema é que essa relação é não linear e, ao que parece, possui uma estrutura complexa. Não fica imediatamente claro quais mudanças no indicador MACD levam a um movimento de preço de alta ou de baixa.

sns.scatterplot(data=data,x="MACD Main",y="MACD Signal",hue="Price Binary Target")

Figura 4: Visualização da relação entre o indicador MACD e os níveis de preço

Executar um gráfico 3D apenas reforça o quão confusa essa relação realmente é. Não há limites claros, por isso esperamos que os dados sejam difíceis de classificar. A única conclusão razoável que podemos tirar do nosso gráfico é que os mercados parecem retornar rapidamente ao centro após atingirem níveis extremos no MACD.

#Define the 3D Plot
fig = plt.figure(figsize=(7,7))
ax = plt.axes(projection="3d")
ax.scatter(data["MACD Main"],data["MACD Signal"],data["Close"],c=data["Price Binary Target"])
ax.set_xlabel("MACD Main")
ax.set_ylabel("MACD Signal")
ax.set_zlabel("EURUSD Close")

Figura 5: Visualização da interação entre o indicador MACD e o mercado EURUSD

Os gráficos Violin nos permitem visualizar simultaneamente a distribuição dos dados e comparar duas distribuições. A linha azul representa um resumo da distribuição observada dos níveis futuros de preço após uma alta ou baixa no MACD. Na figura 6 abaixo, precisávamos entender se o aumento ou a queda no indicador MACD estavam relacionados a distribuições diferentes em relação aos movimentos futuros de preço. Como se pode ver, essas duas distribuições parecem praticamente idênticas. Além disso, o núcleo de cada distribuição possui um gráfico de caixas. As médias nos dois gráficos de caixas parecem quase idênticas, independentemente de o indicador estar em uma condição de alta ou de baixa.

sns.violinplot(data=data,x="MACD Bull",y="Close",hue="Price Binary Target",split=True,fill=False)

Figura 6: Visualização do impacto do indicador MACD nos níveis futuros de preço



Preparação para modelagem dos dados

Vamos agora iniciar a modelagem dos nossos dados; primeiro, precisamos importar nossas bibliotecas.

#Perform train test splits
from sklearn.model_selection import train_test_split,TimeSeriesSplit
from sklearn.metrics import accuracy_score
train,test = train_test_split(data,test_size=0.5,shuffle=False)

Agora vamos definir os preditores e o alvo.

#Let's scale the data
ohlc_predictors = ["Open","High","Low","Close","Bull Bear"]
macd_predictors = ["MACD Main","MACD Signal","MACD Bull"]
all_predictors  = ohlc_predictors + macd_predictors
cv_predictors   = [ohlc_predictors,macd_predictors,all_predictors]

#Define the targets
cv_targets = ["MACD Binary Target","Price Binary Target","All"]

Escalonando os dados.

#Scaling the data
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
scaler.fit(train[all_predictors])
train_scaled = pd.DataFrame(scaler.transform(train[all_predictors]),columns=all_predictors)
test_scaled = pd.DataFrame(scaler.transform(test[all_predictors]),columns=all_predictors)

Vamos carregar as bibliotecas necessárias.

#Import the models we will evaluate
from sklearn.neural_network import MLPClassifier,MLPRegressor
from sklearn.linear_model import LinearRegression

Criamos um objeto de separação para séries temporais.

tscv = TimeSeriesSplit(n_splits=5,gap=look_ahead)

Os índices do nosso dataframe serão mapeados para o conjunto de dados de entrada que estávamos avaliando.

err_indexes = ["MACD Train","Price Train","All Train","MACD Test","Price Test","All Test"]

Agora criaremos um dataframe que registrará nossas métricas de precisão do modelo à medida que modificamos nossos dados de entrada.

#Now let us define a table to store our error levels
columns = ["Model Accuracy"]

cv_err = pd.DataFrame(columns=columns,index=err_indexes)

Resetamos todos os nossos índices.

#Reset index
train = train.reset_index(drop=True)
test = test.reset_index(drop=True)

Executamos a validação cruzada do modelo. Faremos a validação cruzada no conjunto de treinamento e, em seguida, registraremos sua precisão no conjunto de teste, sem aplicar a validação ao conjunto de teste.

#Initailize the model
price_model = MLPClassifier(hidden_layer_sizes=(10,6))
macd_model  = MLPClassifier(hidden_layer_sizes=(10,6))
all_model   = MLPClassifier(hidden_layer_sizes=(10,6))

price_acc = []
macd_acc = []
all_acc = []

#Cross validate each model twice
for j,(train_index,test_index) in enumerate(tscv.split(train_scaled)):
  #Fit the models
  price_model.fit(train_scaled.loc[train_index,ohlc_predictors],train.loc[train_index,"Price Binary Target"])
  macd_model.fit(train_scaled.loc[train_index,all_predictors],train.loc[train_index,"MACD Binary Target"])
  all_model.fit(train_scaled.loc[train_index,all_predictors],train.loc[train_index,"Price Binary Target"])
  #Store the accuracy
  price_acc.append(accuracy_score(train.loc[test_index,"Price Binary Target"],price_model.predict(train_scaled.loc[test_index,ohlc_predictors])))
  macd_acc.append(accuracy_score(train.loc[test_index,cv_targets[0]],macd_model.predict(train_scaled.loc[test_index,all_predictors])))
  all_acc.append(accuracy_score(train.loc[test_index,cv_targets[1]],all_model.predict(train_scaled.loc[test_index,all_predictors])))

#Now we can store our estimates of the model's error
cv_err.iloc[0,0] = np.mean(price_acc)
cv_err.iloc[1,0] = np.mean(macd_acc)
cv_err.iloc[2,0] = np.mean(all_acc)
#Estimating test error
cv_err.iloc[3,0] = accuracy_score(test[cv_targets[1]],price_model.predict(test_scaled[ohlc_predictors]))
cv_err.iloc[4,0] = accuracy_score(test[cv_targets[0]],macd_model.predict(test_scaled[all_predictors]))
cv_err.iloc[5,0] = accuracy_score(test[cv_targets[1]],all_model.predict(test_scaled[all_predictors]))
 Grupo de entrada
 Precisão do modelo
MACD Train
0.507129 
OHLC Train
0.690267
All Train
0.504577
MACD Test
0.48669
OHLC Test
0.684069
All Test
0.487442


Importância das características

Agora tentaremos avaliar os níveis de importância das variáveis para nossa rede neural profunda. Vamos escolher a importância por permutação para interpretar nosso modelo. A importância por permutação determina a relevância de cada parâmetro de entrada, embaralhando os valores dessa coluna de entrada e, em seguida, avaliando a mudança na precisão do modelo. A ideia é que características importantes provoquem uma queda significativa na precisão, enquanto características irrelevantes resultem em alterações muito próximas de 0.

No entanto, alguns pontos precisam ser considerados. Primeiro, o algoritmo de importância por permutação embaralha aleatoriamente todos os dados de entrada do modelo. Isso significa que o algoritmo pode embaralhar aleatoriamente o preço de abertura e colocá-lo acima do preço máximo. Obviamente, isso não é possível no mundo real. Por isso, devemos interpretar os resultados do algoritmo com cautela. Pode-se argumentar que o algoritmo é tendencioso, já que ele avalia a importância das variáveis em condições simuladas que potencialmente nunca aconteceriam, penalizando o modelo desnecessariamente. Além disso, devido à natureza estocástica dos algoritmos de otimização usados para treinar redes neurais modernas, o treinamento das mesmas redes neurais com os mesmos dados pode gerar explicações completamente diferentes a cada execução.

#Let us try assess feature importance
from sklearn.inspection import permutation_importance
from sklearn.linear_model import RidgeClassifier

Agora vamos aplicar nosso objeto de importância por permutação ao nosso modelo treinado de rede neural profunda. Umas temos a opção de passar os dados de treinamento ou de teste para o embaralhamento. Optamos pelos dados de teste. Depois disso, organizamos os dados por ordem decrescente de precisão e plotamos os resultados. Na figura 7 abaixo mostra os resultados observados da importância por permutação. Podemos ver que os resultados do embaralhamento das variáveis relacionadas ao MACD ficam muito próximos de 0, o que significa que as colunas do MACD não são tão importantes para nosso modelo.

#Let us fit the model
model   = MLPClassifier(hidden_layer_sizes=(10,6))
model.fit(train_scaled.loc[:,all_predictors],train.loc[:,"Price Binary Target"])

#Calculate permutation importance scores
pi = permutation_importance(
    model, test_scaled.loc[:,all_predictors], test.loc[:,"Price Binary Target"], n_repeats=10, random_state=42, n_jobs=-1
)

#Sort the importance scores
sorted_importances_idx = pi.importances_mean.argsort()
importances = pd.DataFrame(
    pi.importances[sorted_importances_idx].T,
    columns=test_scaled.columns[sorted_importances_idx],
)

#Create the plot
ax = importances.plot.box(vert=False, whis=10)
ax.set_title("Permutation Importances (test set)")
ax.axvline(x=0, color="k", linestyle="--")
ax.set_xlabel("Decrease in accuracy score")
ax.figure.tight_layout()

Figura 7: Nas nossas avaliações de importância por permutação, o preço de fechamento foi identificado como o recurso mais importante

Treinar um modelo mais simples também poderia nos fornecer uma noção dos níveis de importância dos dados brutos. O ridge classifier é um modelo linear que ajusta seus coeficientes cada vez mais próximos de 0 na direção que minimiza seu erro. Consequentemente, assumindo que seus dados foram padronizados e escalados, as variáveis irrelevantes terão os menores coeficientes. Caso você esteja curioso, o ridge classifier consegue isso expandindo o modelo linear tradicional e incluindo um termo de penalidade proporcional à soma dos quadrados dos coeficientes do modelo. Esse fenômeno é amplamente conhecido como regularização L2.

#Let us fit the model
model   = RidgeClassifier()
model.fit(train_scaled.loc[:,all_predictors],train.loc[:,"Price Binary Target"])

Agora vamos construir um gráfico com os coeficientes do modelo.

ridge_importance = pd.DataFrame(model.coef_.tolist(),columns=all_predictors)

#Prepare the plot
fig,ax = plt.subplots(figsize=(10,5))
sns.barplot(ridge_importance,ax=ax)

Figura 8: Os nossos coeficientes de ridge indicam que os preços alto e baixo são os recursos mais informativos que temos



Ajuste de parâmetros

Agora vamos tentar otimizar nosso modelo mais eficaz. No entanto, como já mencionamos anteriormente, nosso processo de otimização neste ponto não teve sucesso. Infelizmente, isso faz parte da natureza dos algoritmos de otimização, e não há garantia de que encontraremos soluções. Realizar a otimização de parâmetros não significa necessariamente que o modelo final será melhor. Estamos apenas tentando nos aproximar dos parâmetros ideais do modelo. Vamos carregar as bibliotecas necessárias.

#Let's tune our model further
from sklearn.model_selection import RandomizedSearchCV

Definição do modelo.

#Reinitialize the model
model  = MLPRegressor(max_iter=200)

Agora vamos definir o objeto tuner. O objeto avaliará nosso modelo com diferentes configurações de inicialização e retornará um objeto contendo os dados de entrada mais eficazes encontrados.

#Define the tuner
tuner = RandomizedSearchCV(
        model,
        {
        "activation" : ["relu","logistic","tanh","identity"],
        "solver":["adam","sgd","lbfgs"],
        "alpha":[0.1,0.01,0.001,0.0001,0.00001,0.00001,0.0000001],
        "tol":[0.1,0.01,0.001,0.0001,0.00001,0.000001,0.0000001],
        "learning_rate":['constant','adaptive','invscaling'],
        "learning_rate_init":[0.1,0.01,0.001,0.0001,0.00001,0.000001,0.0000001],
        "hidden_layer_sizes":[(2,4,8,2),(10,20),(5,10),(2,20),(6,8,10),(1,5),(20,10),(8,4),(2,4,8),(10,5)],
        "early_stopping":[True,False],
        "warm_start":[True,False],
        "shuffle": [True,False]
        },
        n_iter=100,
        cv=5,
        n_jobs=-1,
        scoring="neg_mean_squared_error"
)

Treinamento do objeto tuner.

tuner.fit(train.loc[:,ohlc_predictors],train.loc[:,"Price Target"])

Os melhores parâmetros encontrados por nós:

tuner.best_params_
{'warm_start': False,
 'tol': 0.01,
 'solver': 'sgd',
 'shuffle': False,
 'learning_rate_init': 0.01,
 'learning_rate': 'constant',
 'hidden_layer_sizes': (20, 10),
 'early_stopping': True,
 'alpha': 1e-07,
 'activation': 'identity'}


Otimização mais profunda

Podemos fazer uma busca ainda mais refinada por melhores configurações de entrada usando a biblioteca SciPy.  Usaremos a biblioteca para avaliar os resultados de uma otimização global sobre parâmetros contínuos do modelo.
#Deeper optimization
from scipy.optimize import minimize
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import TimeSeriesSplit

Vamos definir o objeto de separação para séries temporais.

#Define the time series split object
tscv = TimeSeriesSplit(n_splits=5,gap=look_ahead)

Criamos estruturas de dados para armazenar nossos níveis de precisão.

#Create a dataframe to store our accuracy
current_error_rate = pd.DataFrame(index = np.arange(0,5),columns=["Current Error"])
algorithm_progress = []

Nossa função de custo, que precisa ser minimizada, será o nível de erro do modelo sobre os dados de treinamento.

#Define the objective function
def objective(x):
    #The parameter x represents a new value for our neural network's settings
    model = MLPRegressor(hidden_layer_sizes=tuner.best_params_["hidden_layer_sizes"],
                         early_stopping=tuner.best_params_["early_stopping"],
                         warm_start=tuner.best_params_["warm_start"],
                         max_iter=500,
                         activation=tuner.best_params_["activation"],
                         learning_rate=tuner.best_params_["learning_rate"],
                         solver=tuner.best_params_["solver"],
                         shuffle=tuner.best_params_["shuffle"],
                         alpha=x[0],
                         tol=x[1],
                         learning_rate_init=x[2]
                         )
    #Now we will cross validate the model
    for i,(train_index,test_index) in enumerate(tscv.split(train)):
        #Train the model
        model.fit(train.loc[train_index,ohlc_predictors],train.loc[train_index,"Price Target"])
        #Measure the RMSE
        current_error_rate.iloc[i,0] = mean_squared_error(train.loc[test_index,"Price Target"],model.predict(train.loc[test_index,ohlc_predictors]))
    #Store the algorithm's progress
    algorithm_progress.append(current_error_rate.iloc[:,0].mean())
    #Return the Mean CV RMSE
    return(current_error_rate.iloc[:,0].mean())

O SciPy espera que forneçamos valores iniciais para iniciar o processo de otimização.

#Define the starting point
pt = [tuner.best_params_["alpha"],tuner.best_params_["tol"],tuner.best_params_["learning_rate_init"]]
bnds = ((10.00 ** -100,10.00 ** 100),
        (10.00 ** -100,10.00 ** 100),
        (10.00 ** -100,10.00 ** 100))

Agora vamos tentar otimizar o modelo.

#Searching deeper for parameters
result = minimize(objective,pt,method="L-BFGS-B",bounds=bnds)

Parece que o algoritmo conseguiu convergir. Isso significa que ele encontrou dados de entrada estáveis com baixa variância. Assim, ele concluiu que não existem soluções melhores, já que as variações no nível de erro estavam se aproximando de 0.

#The result of our optimization
result

 message: CONVERGENCE: REL_REDUCTION_OF_F_<=_FACTR*EPSMCH
  success: True
   status: 0
      fun: 3.730365831424036e-06
        x: [ 9.939e-08  9.999e-03  9.999e-03]
      nit: 3
      jac: [-7.896e+01 -1.133e+02  1.439e+03]
     nfev: 100
     njev: 25
 hess_inv: <3x3 LbfgsInvHessProduct with dtype=float64>

Vamos visualizar o procedimento.

#Store the optimal coefficients
optimal_weights = result.x
optima_y = min(algorithm_progress)
optima_x = algorithm_progress.index(optima_y)
inputs = np.arange(0,len(algorithm_progress))

#Plot the performance of our optimization procedure
plt.scatter(inputs,algorithm_progress)
plt.plot(optima_x,optima_y,'ro',color='r')
plt.axvline(x=optima_x,ls='--',color='red')
plt.axhline(y=optima_y,ls='--',color='red')
plt.xlabel("Iterations")
plt.ylabel("Training MSE")
plt.title("Minimizing Training Error")

Figura 9: Visualização da otimização da rede neural profunda


Teste de sobreajuste

Sobreajuste é um efeito indesejado em que nosso modelo aprende padrões sem sentido a partir dos dados disponíveis. Isso é problemático, pois o modelo, nesse estado, apresentará baixos níveis de precisão. Podemos identificar se nosso modelo está sobreajustado ao compará-lo com modelos mais fracos, os chamados "alunos fracos", e com versões padrão de redes neurais semelhantes. Se nosso modelo estiver aprendendo apenas o ruído e não conseguir captar o sinal dos dados, os "alunos fracos" irão superá-lo. No entanto, mesmo que nosso modelo supere esses "alunos fracos", ainda há a possibilidade de sobreajuste.

#Testing for overfitting
#Benchmark
benchmark = LinearRegression()

#Default
default_nn = MLPRegressor(max_iter=500)

#Randomized NN
random_search_nn = MLPRegressor(hidden_layer_sizes=tuner.best_params_["hidden_layer_sizes"],
                         early_stopping=tuner.best_params_["early_stopping"],
                         warm_start=tuner.best_params_["warm_start"],
                         max_iter=500,
                         activation=tuner.best_params_["activation"],
                         learning_rate=tuner.best_params_["learning_rate"],
                         solver=tuner.best_params_["solver"],
                         shuffle=tuner.best_params_["shuffle"],
                         alpha=tuner.best_params_["alpha"],
                         tol=tuner.best_params_["tol"],
                         learning_rate_init=tuner.best_params_["learning_rate_init"]
                         )

#LBFGS NN
lbfgs_nn = MLPRegressor(hidden_layer_sizes=tuner.best_params_["hidden_layer_sizes"],
                         early_stopping=tuner.best_params_["early_stopping"],
                         warm_start=tuner.best_params_["warm_start"],
                         max_iter=500,
                         activation=tuner.best_params_["activation"],
                         learning_rate=tuner.best_params_["learning_rate"],
                         solver=tuner.best_params_["solver"],
                         shuffle=tuner.best_params_["shuffle"],
                         alpha=result.x[0],
                         tol=result.x[1],
                         learning_rate_init=result.x[2]
                         )

Treinamos os modelos e avaliamos sua precisão. Podemos ver claramente a diferença de desempenho: o modelo de regressão linear superou todas as nossas redes neurais profundas. Em seguida, decidi tentar treinar uma SVM linear. Ela teve desempenho superior ao das redes neurais, mas não conseguiu superar a regressão linear.

#Fit the models on the training sets
benchmark = LinearRegression()
benchmark.fit(((train.loc[:,ohlc_predictors])),train.loc[:,"Price Target"])
mean_squared_error(test.loc[:,"Price Target"],benchmark.predict(((test.loc[:,ohlc_predictors]))))

#Test the default
default_nn.fit(train.loc[:,ohlc_predictors],train.loc[:,"Price Target"])
mean_squared_error(test.loc[:,"Price Target"],default_nn.predict(test.loc[:,ohlc_predictors]))

#Test the random search
random_search_nn.fit(train.loc[:,ohlc_predictors],train.loc[:,"Price Target"])
mean_squared_error(test.loc[:,"Price Target"],random_search_nn.predict(test.loc[:,ohlc_predictors]))

#Test the lbfgs nn
lbfgs_nn.fit(train.loc[:,ohlc_predictors],train.loc[:,"Price Target"])
mean_squared_error(test.loc[:,"Price Target"],lbfgs_nn.predict(test.loc[:,ohlc_predictors])
Regressão linear
Rede neural padrão
Busca aleatória
Rede neural LBFGS
2.609826e-07
1.996431e-05
0.00051
0.000398

Vamos treinar nossa LinearSVR, que tem mais probabilidade de capturar interações não lineares nos nossos dados.

#From experience, I'll try LSVR
from sklearn.svm import LinearSVR

Inicializamos o modelo e o ajustamos com todos os dados disponíveis. Observe que os níveis de erro da SVR são melhores que os da rede neural, mas ainda não superam a regressão linear.

#Initialize the model
lsvr = LinearSVR()

#Fit the Linear Support Vector
lsvr.fit(train.loc[:,["Open","High","Low","Close"]],train.loc[:,"Price Target"])
mean_squared_error(test.loc[:,"Price Target"],lsvr.predict(test.loc[:,["Open","High","Low","Close"]]))

5.291875e-06


Exportação para ONNX

O Open Neural Network Exchange (ONNX) nos permite criar modelos de aprendizado de máquina em uma linguagem e depois transferi-los para qualquer outra linguagem que ofereça suporte à API ONNX. O protocolo ONNX está rapidamente ampliando o número de ambientes nos quais é possível usar aprendizado de máquina. O ONNX nos permite integrar IA facilmente ao nosso EA no MQL5.

#Let's export the LSVR to ONNX
import onnx
from skl2onnx import convert_sklearn
from skl2onnx.common.data_types import FloatTensorType

Criamos uma nova instância do modelo.

model = LinearSVR()

Ajustamos o modelo com todos os dados disponíveis.

model.fit(data.loc[:,["Open","High","Low","Close"]],data.loc[:,"Price Target"])

Definimos a forma de entrada do modelo.

#Define the input type
initial_types = [("float_input",FloatTensorType([1,4]))]

Criamos a representação ONNX do modelo.

#Create the ONNX representation
onnx_model = convert_sklearn(model,initial_types=initial_types,target_opset=12)

Salvamos o modelo ONNX.

# Save the ONNX model
onnx.save_model(onnx_model,"EURUSD SVR M1.onnx")

nossa LinearSVR

Figura 10: Visualização da nossa modelo ONNX



Implementação com MQL5

Agora podemos começar a implementar nossa estratégia em MQL5. Precisamos criar uma aplicação que compre sempre que o preço estiver acima da média móvel e a inteligência artificial prever que os preços vão subir.

Para começar a trabalhar com nosso aplicativo, primeiro incluiremos o arquivo ONNX recém-criado no nosso EA.

//+--------------------------------------------------------------+
//| EURUSD AI                                                    |
//+--------------------------------------------------------------+
#property copyright "Gamuchirai Ndawana"
#property link "https://metaquotes.com/en/users/gamuchiraindawa"
#property version "2.1"
#property description "Supports M1"

//+--------------------------------------------------------------+
//| Resources we need                                            |
//+--------------------------------------------------------------+
#resource "\\Files\\EURUSD SVR M1.onnx" as const uchar onnx_buffer[];

Agora vamos carregar a biblioteca de trading.

//+--------------------------------------------------------------+
//| Libraries                                                    |
//+--------------------------------------------------------------+
#include  <Trade\Trade.mqh>
CTrade trade;

Definiremos algumas constantes, que agora poderão ser modificadas.

//+--------------------------------------------------------------+
//| Constants                                                    |
//+--------------------------------------------------------------+
const double  stop_percent = 1;
const int     ma_period_shift = 0;

Permitiremos que o usuário controle os parâmetros dos indicadores técnicos e o comportamento geral do programa.

//+--------------------------------------------------------------+
//| User inputs                                                  |
//+--------------------------------------------------------------+
input group "TAs"
input double atr_multiple =2.5;             //How wide should the stop loss be?
input int    atr_period = 200;              //ATR Period
input int    ma_period = 1000;              //Moving average period

input group "Risk"
input double risk_percentage= 0.02;         //Risk percentage (0.01 - 1)
input double profit_target = 1.0;           //Profit target

Agora definiremos todas as variáveis globais necessárias.

//+--------------------------------------------------------------+
//| Global variables                                             |
//+--------------------------------------------------------------+
double position_size = 2;
int lot_multiplier = 1;
bool  buy_break_even_setup = false;
bool  sell_break_even_setup = false;
double up_level = 0.03;
double down_level = -0.03;
double min_volume,max_volume_increase, volume_step, buy_stop_loss, sell_stop_loss,ask, bid,atr_stop,mid_point,risk_equity;
double take_profit = 0;
double close_price[3];
double moving_average_low_array[],close_average_reading[],moving_average_high_array[],atr_reading[];
long   min_distance,login;
int    ma_high,ma_low,atr,close_average;
bool   authorized = false;
double tick_value,average_market_move,margin,mid_point_height,channel_width,lot_step;
string currency,server;
bool all_closed =true;
long onnx_model;
vectorf onnx_output = vectorf::Zeros(1);
ENUM_ACCOUNT_TRADE_MODE account_type;

Nosso EA primeiro verificará se o usuário permitiu que EAs operem na conta; em seguida, tentará carregar o modelo ONNX e, caso tenha sucesso, carregaremos nossos indicadores técnicos.

//+------------------------------------------------------------------+
//| On initialization                                                |
//+------------------------------------------------------------------+
int OnInit()
  {

//--- Authorization
   if(!auth())
     {
      return(INIT_FAILED);
     }
     
//--- Load the ONNX model
if(!load_onnx())
   {
      return(INIT_FAILED);
   }

//--- Everything went fine
   else
     {
      load();      
      return(INIT_SUCCEEDED);
     }
  }

Se nosso EA não estiver em uso, liberaremos a memória alocada para o modelo ONNX.

//+------------------------------------------------------------------+
//| On deinitialization                                              |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
      OnnxRelease(onnx_model);
  }
Sempre que novas informações de preços forem recebidas, atualizamos nossas variáveis globais de mercado e, em seguida, verificamos se há sinais de entrada, caso não haja posições abertas. Caso contrário, atualizamos nosso trailing stop-loss.
//+------------------------------------------------------------------+
//| On every tick                                                    |
//+------------------------------------------------------------------+
void OnTick()
  {
//On Every Function Call
   update();
   static datetime time_stamp;
   datetime time = iTime(_Symbol,PERIOD_CURRENT,0);
   Comment("AI Forecast: ",onnx_output[0]);

//On Every Candle
   if(time_stamp != time)
     {

      //Mark the candle
      time_stamp = time;

      OrderCalcMargin(ORDER_TYPE_BUY,_Symbol,min_volume,ask,margin);
      calculate_lot_size();
      if(PositionsTotal() == 0)
        {
         check_signal();
        }
     }

//--- If we have positions, manage them.
   if(PositionsTotal() > 0)
     {
      check_atr_stop();
      check_profit();
     }
  }


//+------------------------------------------------------------------+
//| Check if we have any valid setups, and execute them              |
//+------------------------------------------------------------------+
void check_signal(void)
  {
  //--- Get a prediction from our model
  model_predict();
     if(onnx_output[0] > iClose(Symbol(),PERIOD_CURRENT,0))
      {
         if(above_channel())
           {
               check_buy();
           }
      }
      
      else
         if(below_channel())
           {
             if(onnx_output[0] < iClose(Symbol(),PERIOD_CURRENT,0))
               {
                  check_sell();
                }
           }
  }

Essa função é responsável por atualizar todas as nossas variáveis globais de mercado.

//+------------------------------------------------------------------+
//| Update our global variables                                      |
//+------------------------------------------------------------------+
void update(void)
  {
//--- Important details that need to be updated everytick
   ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
   bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
   buy_stop_loss = 0;
   sell_stop_loss = 0;
   check_price(3);
   CopyBuffer(ma_high,0,0,1,moving_average_high_array);
   CopyBuffer(ma_low,0,0,1,moving_average_low_array);
   CopyBuffer(atr,0,0,1,atr_reading);
   ArraySetAsSeries(moving_average_high_array,true);
   ArraySetAsSeries(moving_average_low_array,true);
   ArraySetAsSeries(atr_reading,true);
   risk_equity = AccountInfoDouble(ACCOUNT_BALANCE) * risk_percentage;
   atr_stop = (((min_distance + (atr_reading[0]* 1e5) * atr_multiple) * _Point));
   mid_point = (moving_average_high_array[0] + moving_average_low_array[0]) / 2;
   mid_point_height = close_price[0] - mid_point;
   channel_width = moving_average_high_array[0] - moving_average_low_array[0];
  }

Agora devemos definir uma função que garanta a autorização para iniciar nosso aplicativo. Se ele não for iniciado, a função fornecerá instruções ao usuário sobre o que fazer e retornará o valor false, o que impedirá a inicialização.

//+------------------------------------------------------------------+
//| Check if the EA is allowed to be run                             |
//+------------------------------------------------------------------+
bool auth(void)
  {
   if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
     {
      Comment("Press Ctrl + E To Give The Robot Permission To Trade And Reload The Program");
      return(false);
     }

   else
      if(!MQLInfoInteger(MQL_TRADE_ALLOWED))
        {
         Comment("Reload The Program And Make Sure You Clicked Allow Algo Trading");
         return(false);
        }

   return(true);
  }

Durante a inicialização, precisaremos de uma função responsável por carregar todos os nossos indicadores técnicos e obter informações importantes do mercado. A função `load` fará exatamente isso para nós, e como ela faz referência a variáveis globais, seu tipo de retorno será `void`.

//+---------------------------------------------------------------------+
//| Load our needed variables                                           |
//+---------------------------------------------------------------------+
void load(void)
  {
//Account Info
   currency = AccountInfoString(ACCOUNT_CURRENCY);
   server = AccountInfoString(ACCOUNT_SERVER);
   login = AccountInfoInteger(ACCOUNT_LOGIN);

//Indicators
   atr = iATR(_Symbol,PERIOD_CURRENT,atr_period);
   ma_high = iMA(_Symbol,PERIOD_CURRENT,ma_period,ma_period_shift,MODE_EMA,PRICE_HIGH);
   ma_low = iMA(_Symbol,PERIOD_CURRENT,ma_period,ma_period_shift,MODE_EMA,PRICE_LOW);

//Market Information
   min_volume = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
   max_volume_increase = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX) / SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
   min_distance = SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL);
   tick_value = SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_VALUE_PROFIT) * min_volume;
   lot_step = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP);
   average_market_move = NormalizeDouble(10000 * tick_value,_Digits);
  }

Por outro lado, nosso modelo ONNX será carregado por meio de uma chamada de função separada. A função criará nosso modelo ONNX a partir do buffer que definimos anteriormente e verificará a forma de entrada e saída.

//+------------------------------------------------------------------+
//| Load our ONNX model                                              |
//+------------------------------------------------------------------+
bool load_onnx(void)
   {
      onnx_model = OnnxCreateFromBuffer(onnx_buffer,ONNX_DEFAULT);
      ulong onnx_input [] = {1,4};
      ulong onnx_output[] = {1,1};
      if(!OnnxSetInputShape(onnx_model,0,onnx_input))
         {
            Comment("[INTERNAL ERROR] Failed to load AI modules. Relode the EA.");
            return(false);
         }
         
      if(!OnnxSetOutputShape(onnx_model,0,onnx_output))
         {
            Comment("[INTERNAL ERROR] Failed to load AI modules. Relode the EA.");
            return(false);
         }
         
     return(true);
   }

Agora vamos definir a função que fará previsões a partir do nosso modelo.

//+------------------------------------------------------------------+
//| Get a prediction from our model                                  |
//+------------------------------------------------------------------+
void model_predict(void)
   {
      vectorf onnx_inputs = {iOpen(Symbol(),PERIOD_CURRENT,0),iHigh(Symbol(),PERIOD_CURRENT,0),iLow(Symbol(),PERIOD_CURRENT,0),iClose(Symbol(),PERIOD_CURRENT,0)};
      OnnxRun(onnx_model,ONNX_DEFAULT,onnx_inputs,onnx_output);
   }

Nosso stop-loss será ajustado com base no valor do ATR. Dependendo se a negociação atual for uma compra ou venda, isso será o principal fator determinante para nos ajudar a decidir se devemos aumentar nosso stop-loss adicionando o valor atual do ATR ou reduzi-lo subtraindo o ATR. Também podemos usar um valor multiplicado pelo ATR atual para oferecer ao usuário um controle mais preciso sobre o nível de risco.

//+------------------------------------------------------------------+
//| Update the ATR stop loss                                         |
//+------------------------------------------------------------------+
void check_atr_stop()
  {

   for(int i = PositionsTotal() -1; i >= 0; i--)
     {

      string symbol = PositionGetSymbol(i);
      if(_Symbol == symbol)
        {

         ulong ticket = PositionGetInteger(POSITION_TICKET);
         double position_price = PositionGetDouble(POSITION_PRICE_OPEN);
         double type = PositionGetInteger(POSITION_TYPE);
         double current_stop_loss = PositionGetDouble(POSITION_SL);

         if(type == POSITION_TYPE_BUY)
           {
            double atr_stop_loss = (ask - (atr_stop));
            double atr_take_profit = (ask + (atr_stop));

            if((current_stop_loss < atr_stop_loss) || (current_stop_loss == 0))
              {
               trade.PositionModify(ticket,atr_stop_loss,atr_take_profit);
              }
           }

         else
            if(type == POSITION_TYPE_SELL)
              {
               double atr_stop_loss = (bid + (atr_stop));
               double atr_take_profit = (bid - (atr_stop));
               if((current_stop_loss > atr_stop_loss) || (current_stop_loss == 0))
                 {
                  trade.PositionModify(ticket,atr_stop_loss,atr_take_profit);
                 }
              }
        }
     }
  }

Por fim, precisamos definir 2 funções responsáveis por abrir posições de compra e venda, além de suas respectivas funções complementares para fechamento das posições.

//+------------------------------------------------------------------+
//| Open buy positions                                               |
//+------------------------------------------------------------------+
void check_buy()
  {
   if(PositionsTotal() == 0)
     {
      for(int i=0; i < position_size;i++)
        {
         trade.Buy(min_volume * lot_multiplier,_Symbol,ask,buy_stop_loss,0,"BUY");
         Print("Position: ",i," has been setup");
        }
     }
  }

//+------------------------------------------------------------------+
//| Open sell positions                                              |
//+------------------------------------------------------------------+
void check_sell()
  {
   if(PositionsTotal() == 0)
     {
      for(int i=0; i < position_size;i++)
        {
         trade.Sell(min_volume * lot_multiplier,_Symbol,bid,sell_stop_loss,0,"SELL");
         Print("Position: ",i," has been setup");
        }
     }
  }

//+------------------------------------------------------------------+
//| Close all buy positions                                          |
//+------------------------------------------------------------------+
void close_buy()
  {
   ulong ticket;
   int type;
   if(PositionsTotal() > 0)
     {
      for(int i = 0; i < PositionsTotal();i++)
        {
         if(PositionGetSymbol(i) == _Symbol)
           {
            ticket = PositionGetTicket(i);
            type = (int)PositionGetInteger(POSITION_TYPE);
            if(type == POSITION_TYPE_BUY)
              {
               trade.PositionClose(ticket);
              }
           }
        }
     }
  }

//+------------------------------------------------------------------+
//| Close all sell positions                                         |
//+------------------------------------------------------------------+
void close_sell()
  {
   ulong ticket;
   int type;
   if(PositionsTotal() > 0)
     {
      for(int i = 0; i < PositionsTotal();i++)
        {
         if(PositionGetSymbol(i) == _Symbol)
           {
            ticket = PositionGetTicket(i);
            type = (int)PositionGetInteger(POSITION_TYPE);
            if(type == POSITION_TYPE_SELL)
              {
               trade.PositionClose(ticket);
              }
           }
        }
     }
  }

Vamos observar os últimos 3 níveis de preço.

//+------------------------------------------------------------------+
//| Get the last 3 quotes                                            |
//+------------------------------------------------------------------+
void check_price(int candles)
  {
   for(int i = 0; i < candles;i++)
     {
      close_price[i] = iClose(_Symbol,PERIOD_CURRENT,i);
     }
  }

Essa verificação lógica retornará true se estivermos acima da média móvel.

//+------------------------------------------------------------------+
//| Are we completely above the MA?                                  |
//+------------------------------------------------------------------+
bool above_channel()
  {
   return (((close_price[0] - moving_average_high_array[0] > 0)) && ((close_price[0] - moving_average_low_array[0]) > 0));
  }

Vamos verificar se estamos abaixo da média móvel.

//+------------------------------------------------------------------+
//| Are we completely below the MA?                                  |
//+------------------------------------------------------------------+
bool below_channel()
  {
   return(((close_price[0] - moving_average_high_array[0]) < 0) && ((close_price[0] - moving_average_low_array[0]) < 0));
  }

Vamos fechar todas as posições que temos abertas.

//+------------------------------------------------------------------+
//| Close all positions we have                                      |
//+------------------------------------------------------------------+
void close_all()
  {
   if(PositionsTotal() > 0)
     {
      ulong ticket;
      for(int i =0;i < PositionsTotal();i++)
        {
         ticket = PositionGetTicket(i);
         trade.PositionClose(ticket);
        }
     }
  }

Calculamos o tamanho de lote ideal a ser utilizado, de modo que nossa margem corresponda ao valor de capital que estamos dispostos a arriscar.

//+------------------------------------------------------------------+
//| Calculate the lot size to be used                                |
//+------------------------------------------------------------------+
void calculate_lot_size()
  {
//--- This is the total percentage of the account we're willing to part with for margin, or to keep a position open in other words.
   Print("Risk Equity: ",risk_equity);

//--- Now that we're ready to part with a discrete amount for margin, how many positions can we afford under the current lot size?
//--- By default we always start from minimum lot
   position_size = risk_equity / margin;

//--- We need to keep the number of positions lower than 10
   if(position_size > 10)
     {
      //--- How many times is it greater than 10?
      int estimated_lot_size = (int)  MathFloor(position_size / 10);
      position_size = risk_equity / (margin * estimated_lot_size);
      Print("Position Size After Dividing By margin at new estimated lot size: ",position_size);
      int estimated_position_size = position_size;
      //--- Can we increase the lot size this many times?
      if(estimated_lot_size < max_volume_increase)
        {
         Print("Est Lot Size: ",estimated_lot_size," Position Size: ",estimated_position_size);
         lot_multiplier = estimated_lot_size;
         position_size = estimated_position_size;
        }
     }
  }

Fechamos as posições abertas e verificamos se podemos voltar a operar.

//--- This function will help us keep track of which side we need to enter the market
void close_all_and_enter()
  {

   if(PositionSelect(Symbol()))
     {
      // Determine the type of position
      check_signal();
     }
   else
     {
      Print("No open position found.");
     }
  }

Se atingirmos nossa meta de lucro, fechamos todas as posições que tivermos para realizar os ganhos e, em seguida, verificamos se podemos abrir novas posições.

//+------------------------------------------------------------------+
//| Chekc if we have reached our profit target                       |
//+------------------------------------------------------------------+
void check_profit()
  {
   double current_profit = (AccountInfoDouble(ACCOUNT_EQUITY) - AccountInfoDouble(ACCOUNT_BALANCE)) / PositionsTotal();
   if(current_profit > profit_target)
     {
      close_all_and_enter();
     }

   if((current_profit * PositionsTotal()) < (risk_equity * -1))
     {
      Comment("We've breached our risk equity, consider closing all positions");
     }
  }

Por fim, precisamos de uma função que feche todas as nossas operações com prejuízo.

//+------------------------------------------------------------------+
//| Close all losing trades                                          |
//+------------------------------------------------------------------+
void close_profitable_trades()
  {
   for(int i=PositionsTotal()-1; i>=0; i--)
     {
      if(PositionSelectByTicket(PositionGetTicket(i)))
        {
         if(PositionGetDouble(POSITION_PROFIT)>profit_target)
           {
            ulong ticket;
            ticket = PositionGetTicket(i);
            trade.PositionClose(ticket);
           }
        }
     }
  }
//+------------------------------------------------------------------+

Figura 11: Nosso EA

Figura 12: Parâmetros que utilizamos para testar o aplicativo

Figura 13: Nosso aplicativo em ação



Considerações finais

Embora nossos resultados não tenham sido encorajadores, eles estão longe de serem conclusivos. Existem outras formas de interpretar o indicador MACD que podem merecer uma avaliação. Por exemplo, durante uma tendência de alta, a linha de sinal do MACD cruza acima da linha principal, e durante uma tendência de baixa, ela cruza para baixo da linha principal. Observar o indicador sob essa perspectiva pode gerar métricas de erro diferentes. Não podemos simplesmente assumir que todas as estratégias de interpretação do MACD apresentarão os mesmos níveis de erro. Antes de formarmos uma opinião sobre a eficácia do indicador, seria sensato avaliar o desempenho de várias estratégias baseadas no MACD. 

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

Arquivos anexados |
EURUSD_MACD.ipynb (514.45 KB)
EURUSD_SVR_M1.onnx (0.28 KB)
EURUSD_AI.mq5 (18.01 KB)
Construindo um Modelo de Restrição de Tendência com Candlestick (Parte 9): Expert Advisor de Múltiplas Estratégias (I) Construindo um Modelo de Restrição de Tendência com Candlestick (Parte 9): Expert Advisor de Múltiplas Estratégias (I)
Hoje, vamos explorar as possibilidades de incorporar múltiplas estratégias em um Expert Advisor (EA) usando MQL5. Os Expert Advisors oferecem capacidades mais amplas do que apenas indicadores e scripts, permitindo abordagens de negociação mais sofisticadas que podem se adaptar às mudanças das condições do mercado. Confira mais na discussão deste artigo.
Criando um painel MQL5 interativo usando a classe Controls (Parte 1): Configurando o painel Criando um painel MQL5 interativo usando a classe Controls (Parte 1): Configurando o painel
Neste artigo, vamos criar um painel de negociação interativo utilizando a classe Controls no MQL5, voltado para otimizar as operações de trading. O painel conterá um cabeçalho, botões de navegação para trading, fechamento e informações, além de botões especializados para envio de ordens e gerenciamento de posições. Ao final do artigo, teremos um painel básico pronto para futuras melhorias.
Criando um Expert Advisor Integrado MQL5-Telegram (Parte 7): Análise de Comandos para Automação de Indicadores em Gráficos Criando um Expert Advisor Integrado MQL5-Telegram (Parte 7): Análise de Comandos para Automação de Indicadores em Gráficos
Neste artigo, exploramos como integrar comandos do Telegram com MQL5 para automatizar a adição de indicadores em gráficos de negociação. Cobrimos o processo de análise (parsing) dos comandos dos usuários, sua execução no MQL5 e o teste do sistema para garantir uma negociação baseada em indicadores de forma fluida.
Exemplo de novo Indicador e LSTM Condicional Exemplo de novo Indicador e LSTM Condicional
Este artigo explora o desenvolvimento de um Expert Advisor (EA) para trading automatizado que combina análise técnica com previsões de deep learning.