English Русский 中文 Deutsch 日本語
preview
Superando as limitações do aprendizado de máquina (Parte 2): falta de reprodutibilidade

Superando as limitações do aprendizado de máquina (Parte 2): falta de reprodutibilidade

MetaTrader 5Exemplos |
30 0
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

Costumo receber comentários encorajadores dos nossos leitores, mas um tema recorrente nas mensagens privadas e nos comentários é a dificuldade que alguns encontram ao tentar reproduzir os resultados apresentados em nossos artigos. No início, isso me deixou intrigado, mas, depois de refletir um pouco, encontrei uma explicação provável.

O mercado financeiro global funciona como uma rede ampla e descentralizada. Existem muitas corretoras no mundo, e novas corretoras são registradas todos os dias, mas não há uma única entidade internacional que regule a atuação dessas corretoras ou coordene seus feeds de preços. Cada corretora é livre para obter preços a partir de suas próprias fontes preferenciais ou de serviços de dados, como a Reuters. 

Consequentemente, se compararmos a dinâmica do EURUSD em duas corretoras, que chamaremos de Corretora A e Corretora B, podemos descobrir que o mesmo par de moedas se move em direções opostas no mesmo momento. Por exemplo, a Corretora A pode informar uma alta de 0,12% no par EURUSD em um dia, enquanto a Corretora B registra uma queda de -0,65% no mesmo dia.


O cerne do problema: divergências nos dados entre corretoras

Para esta discussão, escolhi aleatoriamente duas corretoras que uso pessoalmente para trading por conta própria. De acordo com as regras da nossa comunidade, que proíbem publicidade de corretoras, seus nomes foram removidos e substituídos por "Corretora A" e "Corretora B".

Usando a biblioteca Python para MetaTrader 5, solicitei às duas corretoras dados históricos diários do par EURUSD ao longo de quatro anos. Ao verificar os dados, percebi que os timestamps não coincidiam: os dados de uma corretora cobriam o período a partir de setembro de 2019, enquanto os dados da outra começavam apenas em agosto de 2020. Ainda assim, ambos os serviços retornaram exatamente 1460 linhas de dados diários, atendendo corretamente à nossa solicitação.

Dado o caráter descentralizado do setor de corretoras, é perfeitamente esperado que seus fusos horários operacionais possam ser diferentes. No entanto, são menos óbvias as consequências do horário de verão, dos feriados nacionais reconhecidos e de outras pequenas divergências que podem distorcer ainda mais o alinhamento dos timestamps.

Em seguida, calculamos o retorno de 10 dias do par EURUSD nas duas corretoras e descobrimos que as características numéricas do símbolo EURUSD não coincidiam. O retorno médio de 10 dias do par EURUSD na Corretora A foi de 0,000267, enquanto na Corretora B o retorno médio foi de -0,000352. Isso representa uma diferença de aproximadamente 232% no retorno esperado do mesmo ativo subjacente.

A situação se agrava pelo fato de que o retorno esperado da Corretora A parece estar associado a um risco 21% maior do que o retorno esperado da Corretora B. Chegamos a essa conclusão porque a diferença nos retornos entre as corretoras aumentou em 21%. 

Nota para iniciantes: é importante deixar claro ao leitor que oscilações nos retornos são consideradas risco financeiro. Qualquer livro introdutório sobre teoria de portfólio financeiro pode demonstrar esse princípio aos leitores que talvez ainda não estejam familiarizados com ele.

Na estatística, podemos descobrir se duas variáveis se movem em sincronia ou de forma independente uma da outra medindo seu nível de correlação. As medidas padronizadas de correlação variam de 1 a -1. Um valor de 1 significa que as variáveis se movem estritamente na mesma direção, enquanto um valor de -1 significa que as variáveis se movem estritamente em direções opostas. Quando comparamos o coeficiente de correlação de Pearson entre as duas corretoras, eu, o autor do artigo, sinceramente esperava que os coeficientes de correlação ficassem próximos de 1. No entanto, os dados mostraram um nível de correlação de apenas 0,41. 

Isso indica que qualquer suposição de que os níveis de preço do símbolo EURUSD se moverão de forma sincronizada entre diferentes corretoras, ao que parece, não tem fundamento matemático. Pelo contrário, os resultados do nosso teste mostram que, em mais da metade dos casos, o par EURUSD se move em direções diferentes dependendo da corretora.

Outras características numéricas importantes das cotações das duas corretoras aprofundaram ainda mais os problemas para os quais chamamos a atenção do leitor neste artigo. Em nossa discussão anterior sobre as limitações da IA, mostramos ao leitor algumas armadilhas associadas às métricas normalmente usadas para construir modelos de regressão, como a RMSE. O leitor pode encontrar o artigo no link indicado aqui.

Em resumo, aconselhamos o leitor a não tratar a RMSE como uma métrica autônoma, mas a interpretá-la com cautela, comparando a razão entre o erro residual do modelo que você pretende usar (soma dos quadrados dos resíduos, RSS, RSS) e o erro produzido pelo uso de um modelo simples que sempre prevê o retorno médio do mercado (soma total dos quadrados, TSS). O ponto central é que os leitores podem se surpreender com a dificuldade de superar um modelo mais simples. A razão entre RSS e TSS mostra quão bem conseguimos superar o modelo simples.

Seria possível esperar que, para o mesmo símbolo, essa razão permanecesse praticamente constante, mesmo ao trabalhar com corretoras diferentes. No entanto, nossa capacidade de superar o modelo que prevê o retorno médio do mercado melhorou 7% simplesmente ao trocar de corretora. Isso significa que prever o retorno de 10 dias do EURUSD é aproximadamente 7% mais fácil com a Corretora B do que com a Corretora A!

Os estatísticos frequentemente comparam o centro de uma distribuição com seu desvio padrão para entender melhor as características da cauda dessa distribuição. Se reinterpretarmos essa operação e a aplicarmos ao retorno de 10 dias do par EURUSD, obteremos um método numérico para comparar qual corretora, em geral, proporciona retornos extraordinariamente altos. Seguindo essa lógica, o retorno de 10 dias do par EURUSD obtido com a Corretora B ficou superestimado em 147%.

A esta altura, o problema que enfrentamos deve estar claro: características numéricas importantes do mesmo símbolo não têm garantia de uniformidade entre corretoras. Como resultado, a lucratividade de qualquer estratégia de trading específica nem sempre pode ser reproduzida de forma confiável entre corretoras.

Estratégias de trading que integram modelos de inteligência artificial criados com a ONNX API, ou até mesmo nativamente em MQL5, podem deixar de atender repetidamente às expectativas dos investidores se o tempo adicional necessário para adaptar a inteligência artificial de forma específica à corretora pretendida não se tornar uma prática amplamente adotada. Embora essa adaptação tome muito tempo, ela sem dúvida é decisiva.

Enquanto você lê este artigo, vamos recriar passo a passo o ciclo de desenvolvimento que talvez seja seguido pela maioria dos desenvolvedores MQL5. Queremos usar este artigo para ilustrar que, quando um desenvolvedor cria e otimiza seu aplicativo usando a corretora que utiliza pessoalmente, no nosso caso a Corretora B, mas seu cliente implanta esse aplicativo em outra corretora, a Corretora A, o problema pode afetar diretamente o desenvolvedor e seu cliente. Qualquer desenvolvedor que siga esse ciclo de desenvolvimento provavelmente receberá avaliações mistas sobre seu produto.

Para evitar um desempenho insatisfatório desse tipo, os desenvolvedores MQL5 que desejam prestar serviços confiáveis talvez precisem reconhecer que seus clientes podem se beneficiar mais usando estratégias e aplicativos adaptados a corretoras específicas, a fim de proteger os usuários que queremos atender no Marketplace.


Começamos

Primeiro, precisamos importar nossas bibliotecas numéricas padrão.

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

Vamos definir em qual timeframe e em qual par de moedas vamos nos concentrar, bem como quantas linhas de dados serão necessárias.

#Let us define certain constants
TF = mt5.TIMEFRAME_D1
DATA = (365 * 4)
START = 1
PAIR = "EURUSD"

Inicie seu terminal.

#Log in to the terminal
if mt5.initialize():
   print('Logged in successfully')

else:
   print('Failed To Log In')

Login realizado com sucesso.

Vamos analisar a dificuldade de prever o par EURUSD na Corretora A.

EURUSD_BROKER_A = pd.DataFrame(mt5.copy_rates_from_pos(PAIR,TF,START,DATA))
#Store the data we retrieved from broker A
EURUSD_BROKER_A.to_csv("EURUSD BROKER A.csv")

Agora vamos repetir o mesmo procedimento para a Corretora B.

#I have manually changed brokers using the MT5 terminal, you should also do the same on your side
EURUSD_BROKER_B = pd.DataFrame(mt5.copy_rates_from_pos(PAIR,TF,START,DATA))
#Store the data we retrieved from broker B
EURUSD_BROKER_B.to_csv("EURUSD BROKER B.csv")

Excelente! Agora que coletamos os dados históricos do par EURUSD das duas corretoras, vamos começar a estudar as propriedades empíricas desses conjuntos de dados para ver se o símbolo EURUSD é consistente entre corretoras. Precisamos definir até que ponto no futuro vamos fazer previsões.

#Our forecasting horizon
HORIZON = 10

Vamos ler os dois conjuntos de dados.

EURUSD_BROKER_A = pd.read_csv("EURUSD BROKER A.csv")
EURUSD_BROKER_B = pd.read_csv("EURUSD BROKER B.csv")

Nos conjuntos de dados, a coluna de tempo atualmente está registrada em segundos. Preferiríamos ter colunas de data legíveis no formato dia-mês-ano. Vamos desenvolver um método que permita fazer isso.

def format_data(f_data):
    #First make a copy of the data, so we always preserve the original data
    f_data_copy = f_data.copy()
    #Format the time correctly, form seconds to human readable formats
    f_data_copy['time'] = pd.to_datetime(f_data_copy['time'],unit='s')
    return(f_data_copy)

Vamos formatar nossos conjuntos de dados.

A = format_data(EURUSD_BROKER_A)
B = format_data(EURUSD_BROKER_B)

Vamos renomear todas as colunas de forma adequada, para que a letra da corretora que nos forneceu os dados seja adicionada ao nome de cada coluna. Todas as colunas da Corretora A ou B terminarão com A ou B, respectivamente. Vamos agora examinar com atenção os dados históricos do par EURUSD que recebemos das duas corretoras. Observe que ambos os conjuntos contêm exatamente 1 460 linhas de dados diários, o que significa que cada corretora retornou corretamente dados diários de exatamente 4 anos. Que outras diferenças o leitor consegue notar? Você percebeu o volume de ticks?

# Rename all columns (except the join key)
B = B.rename(columns=lambda col: col + ' B' if col != 'id' else col)
A = A.rename(columns=lambda col: col + ' A' if col != 'id' else col)

Fig. 1: Dados históricos diários do par EURUSD que recebemos da Corretora A, com timestamps de setembro de 2019


Fig. 2: Dados históricos diários do par EURUSD que recebemos da Corretora B, não coincidem com os timestamps da Fig. 1, mas ambos correspondem exatamente a 4 anos

Agora vamos unir esses dois conjuntos de dados.

combined = pd.concat([A,B],axis=1)

Vamos criar uma coluna preenchida apenas com 0.

combined['Null'] = 0

Defina as entradas.

inputs = ['open A','high A','low A','close A','tick_volume A','spread A','open B','high B','low B','close B','tick_volume B','spread B']

Vamos calcular o retorno de 10 dias do par EURUSD.

#Label the data
combined['A Target'] = combined['close A'].shift(-HORIZON) - combined['close A']
combined['B Target'] = combined['close B'].shift(-HORIZON) - combined['close B']

#Drop the last HORIZON rows of data
combined = combined.iloc[:-HORIZON,:]

O volume de ticks nos informa quantas variações de preço observamos periodicamente. Períodos de trading intenso serão marcados por volume de ticks elevado, indicando alta atividade de mercado, enquanto um volume de ticks baixo indica que o mercado esteve relativamente calmo, com pouca atividade. Na Corretora A, a julgar pelos dados de volume de ticks, observa-se uma tendência de alta de longo prazo, o que sugere que o interesse dos investidores aparentemente aumenta com o tempo. No gráfico, às vezes aparecem grandes picos, que podem corresponder a períodos especialmente intensos, quando o interesse aberto no par EURUSD se aproxima do máximo.

plt.title('Broker A Daily EURUSD Tick Volume')
plt.plot(combined['tick_volume A'],color='black')
plt.ylabel('Tick Volume')
plt.xlabel('Historical Day')
plt.grid()

Fig. 3: Volume de ticks do par EURUSD obtido da Corretora A

Ao comparar o volume de ticks apresentado pela Corretora B na Fig. 4 com a Fig. 3, ficam claramente visíveis grandes diferenças nos níveis de atividade informados. Na Corretora B, praticamente não há tendência no volume de ticks em comparação com a Corretora A. A Fig. 4 é densa, com picos aleatórios que não parecem tão periódicos quanto os picos que observamos na Fig. 3. 

plt.title('Broker B Daily EURUSD Tick Volume')
plt.plot(combined['tick_volume B'],color='black')
plt.ylabel('Tick Volume')
plt.xlabel('Historical Day')
plt.grid()

Fig. 4: Volume diário de ticks do par EURUSD obtido da Corretora B

Quando analisamos o retorno médio com que um investidor pode contar ao manter EURUSD em cada corretora, descobrimos que as duas corretoras oferecem versões diferentes do mesmo símbolo. Caso contrário, se essas duas corretoras nos oferecessem versões idênticas do mesmo símbolo, não deveríamos ter os mesmos níveis de retorno esperado?

#What's the average 10-Day EURUSD return from both brokers
delta_return = str(((combined.iloc[:,-2:].mean()[0]-combined.iloc[:,-2:].mean()[1]) / combined.iloc[:,-2:].mean()[0]) * 100)

t = 'The Expected 10-Day EURUSD Return Differes by ' + delta_return[:5] + '% Between Our Brokers'

sns.barplot(combined.iloc[:,-2:].mean(),color='black')
plt.axhline(0,color='grey',linestyle='--')
plt.title(t)
plt.ylabel('Return')

Fig. 5: O retorno médio do mercado entre corretoras fica em lados opostos de 0

Vamos construir um gráfico dos retornos gerados pelas duas versões do símbolo, sobrepondo-os. Vou escalar cada série de retornos para que ambas as linhas fiquem centralizadas em 0, e o deslocamento em relação a 0 mostrará quantos desvios padrão nos afastamos do retorno médio do mercado. Vemos imediatamente que há muitos momentos em que as duas linhas estão em lados opostos da linha 0, e também há momentos em que as duas linhas se movem juntas. Lembremos que, em termos simples, tendemos a supor que essas duas linhas sempre se movem juntas, mas a Fig. 6 mostra que isso só é verdadeiro em alguns casos.

plt.plot(((combined.iloc[:,-1]-combined.iloc[:,-1].mean())/combined.iloc[:,-1].std()),color='red')
plt.plot(((combined.iloc[:,-2]-combined.iloc[:,-2].mean())/combined.iloc[:,-2].std()),color='black')
plt.grid()
plt.axhline(0,color='black',linestyle='--')
plt.ylabel('Std. Deviations From Expected 10-Day EURUSD Return')
plt.xlabel('Historical Days')
plt.title('EURUSD Returns from Different Brokers May Not Always Allign')
plt.legend(['Broker A','Broker B'])

Fig. 6: Visualização do retorno de 10 dias do par EURUSD gerado por duas corretoras diferentes

Comparar a magnitude da diferença entre os retornos oferecidos pelas corretoras permite avaliar qual corretora é mais arriscada e qual corretora nos oferece um retorno mais confiável. Segundo essa métrica, a versão do EURUSD usada pela Corretora A apresenta um risco maior associado ao seu retorno em comparação com a versão da Corretora B.

#The variance of returns is not the same across both brokers, broker A is riskier
delta_var = str(((combined.iloc[:,-2:].var()[0]-combined.iloc[:,-2:].var()[1]) / combined.iloc[:,-2:].var()[0]) * 100)

t = 'Broker A EURUSD Returns Appear to Carry '+ delta_var[:5]+'% Additional Risk.'

sns.barplot(combined.iloc[:,-2:].var(),color='black')
plt.axhline(np.min(combined.iloc[:,-2:].var()),color='red',linestyle=':')
plt.title(t)
plt.ylabel('Vriance of Returns')

Fig. 7: O retorno da Corretora A está associado a um risco 21% maior do que o retorno da Corretora B. Neste momento, você ainda considera esses instrumentos "iguais"?

Quando voltamos nossa atenção para o maior rebaixamento registrado pelas duas corretoras, ainda assim não conseguimos obter observações consistentes. O maior rebaixamento registrado nas duas versões do símbolo diferiu em aproximadamente 37% entre as nossas duas corretoras. Tudo isso parece sugerir que a Corretora B protege de forma prudente seus clientes da volatilidade do EURUSD ao oferecer uma visão mais limitada do mercado de câmbio.

#Broker A also demonstrated the largest drawdown ever in our 4 year sample window
delta = (((combined.iloc[:,-2:].min()[0]-combined.iloc[:,-2:].min()[1]) / combined.iloc[:,-2:].min()[0]) *100)
delta_s = str(delta)

t = 'The Largest Negative 10-Day EURUSD Return Grew By: ' + delta_s[:5] + ' %'

sns.barplot(combined.iloc[:,-2:].min(),color='black')
plt.axhline(np.max(combined.iloc[:,-2:].min()),color='red',linestyle=':')
plt.title(t)
plt.ylabel('Return')

Fig. 8: A Corretora A apresentou a maior queda de retorno, de 36,79%, superando significativamente o maior rebaixamento da Corretora B

A sobreposição da distribuição dos retornos de 10 dias do par EURUSD gerada pelas duas corretoras mostra que elas, na verdade, não oferecem a mesma visão do mercado. Como explicamos na introdução da nossa discussão, cada corretora é livre para obter seus dados de preço de qualquer fonte que escolher. Esse esquema descentralizado de precificação significa que cada corretora pode oferecer representações arbitrariamente diferentes de qualquer mercado específico.

sns.histplot(((combined.iloc[:,-2]-combined.iloc[:,-2].mean())/combined.iloc[:,-2].std()),color='black')
sns.histplot(((combined.iloc[:,-1]-combined.iloc[:,-1].mean())/combined.iloc[:,-1].std()),color='red')
plt.xlabel('Std. Deviations From The Expected Return')
plt.ylabel('Frequency')
plt.title('Comparing The Distribution of 10-Day EURUSD Returns Between 2 Brokers')
plt.grid()
plt.legend(['Broker A','Broker B'])

Fig. 9: Comparação da distribuição dos retornos gerados por dois mercados

Além disso, ao analisar os níveis de correlação entre as corretoras, descobrimos que os preços de mercado têm baixa correlação entre si. Isso significa que, como já dissemos anteriormente, em mais da metade dos casos as séries de preços dessas duas corretoras específicas podem se mover em direções opostas.

sns.heatmap(combined.loc[:,inputs].corr(),annot=True)

Fig. 10: A visualização dos níveis de correlação nos mostra que, na maior parte do tempo, os dois instrumentos da corretora se movem praticamente de forma independente um do outro

Vamos agora ver se nossas capacidades preditivas permanecem inalteradas entre corretoras.

from sklearn.model_selection import train_test_split,TimeSeriesSplit,cross_val_score
from sklearn.linear_model import Ridge

Vamos criar um objeto para validação de séries temporais.

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

Vamos implementar um método que retornará um novo modelo a ser usado.

def get_model():
    return(Ridge())

Vamos dividir os dados e garantir que eles não sejam embaralhados.
train , test = train_test_split(combined,shuffle=False,test_size=0.5)

Registraremos nossos níveis de erro ao usar a coluna que preenchemos intencionalmente apenas com zeros. Isso fará com que o modelo sempre preveja a média da variável-alvo. Lembremos que, quando todas as entradas são iguais a 0, um modelo linear prevê o intercepto. Em termos simples, esse modelo nos informa quão bem podemos operar nesse mercado se sempre previrmos o retorno médio do mercado. A incapacidade de superar esse modelo indica que não temos capacidade preditiva.

Esse benchmark é chamado de TSS. Apresentamos a definição de TSS na introdução da nossa discussão. Nosso objetivo aqui é medir a TSS nas duas corretoras e, em seguida, comparar nossa capacidade de superar essa referência entre corretoras.

broker_a_tss = np.mean(np.abs(cross_val_score(get_model(),train.loc[:,['Null']],train.loc[:,'A Target'],scoring='neg_mean_squared_error',n_jobs=-1,cv=tscv)))
broker_a_rss = np.mean(np.abs(cross_val_score(get_model(),train.loc[:,inputs[0:(len(inputs)//2)]],train.loc[:,'A Target'],scoring='neg_mean_squared_error',n_jobs=-1,cv=tscv)))

broker_b_tss = np.mean(np.abs(cross_val_score(get_model(),train.loc[:,['Null']],train.loc[:,'B Target'],scoring='neg_mean_squared_error',n_jobs=-1,cv=tscv)))
broker_b_rss = np.mean(np.abs(cross_val_score(get_model(),train.loc[:,inputs[(len(inputs)//2):]],train.loc[:,'B Target'],scoring='neg_mean_squared_error',n_jobs=-1,cv=tscv)))

Surpreendentemente, é mais fácil superar a TSS na Corretora B do que na Corretora A! Isso significa que a previsão do retorno de 10 dias do EURUSD nem sempre tem a mesma eficácia ao passar de uma corretora para outra.

res = [(broker_a_rss/broker_a_tss),(broker_b_rss/broker_b_tss)]

eff = str(((res[0] - res[1])/res[1]) * 100)

t = 'The EURUSD Appears ' + eff[0:4] + '% Easier To Forecast With Broker B'

sns.barplot(res,color='black')
plt.axhline(np.min(res),color='red',linestyle=':')
plt.ylabel('5-Fold Cross Valiated Ratio of RSS/TSS ')
plt.title(t)
plt.xticks([0,1],['Broker A','Broker B'])

Fig. 11: O retorno futuro de 10 dias do EURUSD é mais fácil de prever usando a Corretora B

Como definimos em qual corretora queremos nos concentrar, vamos selecionar as entradas que obtivemos da Corretora B.

b_inputs = inputs[len(inputs)//2:]

Agora vamos desenvolver um novo modelo por completo.

from sklearn.ensemble import GradientBoostingRegressor

model = GradientBoostingRegressor()

Vamos ajustar o modelo com todos os dados que temos da Corretora B.

model.fit(train.loc[:,b_inputs[:-2]],train['B Target'])
Agora vamos nos preparar para exportar nosso modelo para o formato ONNX, para que possamos integrar facilmente nosso modelo de inteligência artificial ao nosso aplicativo MQL5.
import skl2onnx,onnx
Vamos definir o número de entradas que nosso modelo ONNX aceita.
initial_types = [('float_input',skl2onnx.common.data_types.FloatTensorType([1,4]))]
Vamos converter o modelo em um protótipo ONNX.
onnx_proto = skl2onnx.convert_sklearn(model,initial_types=initial_types,target_opset=12)

Vamos salvar o protótipo ONNX em disco.

onnx.save(onnx_proto,"EURUSD GBR D1.onnx")


Começamos no MQL5

Agora que nosso modelo ONNX está pronto, podemos começar a criar nosso aplicativo MQL5. Primeiro, vamos carregar as bibliotecas necessárias.
//+------------------------------------------------------------------+
//|                                                       EURUSD.mq5 |
//|                                               Gamuchirai Ndawana |
//|                    https://www.mql5.com/ru/users/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Ndawana"
#property link      "https://www.mql5.com/ru/users/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| System Constants Definitions                                     |
//+------------------------------------------------------------------+
#include  <Trade\Trade.mqh>
CTrade Trade;

Também precisaremos de constantes do sistema para garantir que nosso aplicativo reflita parâmetros importantes que definimos anteriormente em nossa discussão, como o período de retorno de 10 dias.

//+------------------------------------------------------------------+
//| System Constants Definitions                                     |
//+------------------------------------------------------------------+
#define  ONNX_INPUT_SHAPE 4
#define  ONNX_OUTPUT_SHAPE 1
#define  SYSTEM_TIME_FRAME PERIOD_D1
#define  RETURN_PERIOD 10
#define  TRADING_VOLUME SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN)
Vamos carregar o arquivo ONNX como recurso do sistema, para que ele seja compilado com nosso aplicativo.
//+------------------------------------------------------------------+
//| System Resources                                                 |
//+------------------------------------------------------------------+
#resource "\\Files\\Broker Manipulation\\EURUSD GBR D1.onnx" as const uchar onnx_proto[];

Para implementar nossa estratégia de trading, precisaremos de algumas variáveis globais.

//+------------------------------------------------------------------+
//| Global Variables                                                 |
//+------------------------------------------------------------------+
long   model;
int    position_timer;
double bid,ask;
double o,h,l,c;
bool   bullish;
double sl_width;

Quando nosso sistema for inicializado pela primeira vez, configuraremos nosso modelo ONNX e, em seguida, redefiniremos variáveis globais importantes para nossa estratégia de trading.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   model = OnnxCreateFromBuffer(onnx_proto,ONNX_DATA_TYPE_FLOAT);

   ulong input_shape[] = {1,ONNX_INPUT_SHAPE};
   ulong output_shape[] = {1,ONNX_OUTPUT_SHAPE};

   if(model == INVALID_HANDLE)
     {
      Comment("Failed To Load EURUSD Auto-Encoder-Decoder: ",GetLastError());
      return(INIT_FAILED);
     }

   if(!OnnxSetInputShape(model,0,input_shape))
     {
      Comment("Failed To Set EURUSD Auto-Encoder-Decoder Input Shape: ",GetLastError());
      return(INIT_FAILED);
     }

   else
      if(!OnnxSetOutputShape(model,0,output_shape))
        {
         Comment("Failed To Set EURUSD Auto-Encoder-Decoder Output Shape: ",GetLastError());
         return(INIT_FAILED);
        }

   position_timer = 0;
   sl_width = 30;
//---
   return(INIT_SUCCEEDED);
  }

Se não estivermos mais usando nossa estratégia de trading, liberaremos os recursos consumidos pelo nosso modelo ONNX.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   OnnxRelease(model);
  }

Sempre que recebermos preços atualizados, salvaremos os novos níveis de preço uma vez por dia e, então, se não tivermos posições abertas, obteremos uma previsão do nosso modelo e operaremos de acordo com ela. Caso contrário, se já tivermos uma operação aberta, tentaremos definir um stop-loss sempre que possível, enquanto fazemos a contagem do período de holding de 10 dias para cada operação.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   static datetime time_stamp;
   datetime current_time = iTime(Symbol(),SYSTEM_TIME_FRAME,0);

   if(time_stamp != current_time)
     {
      time_stamp = current_time;
      ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);
      bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);
      o = iOpen(Symbol(),SYSTEM_TIME_FRAME,1);
      h = iHigh(Symbol(),SYSTEM_TIME_FRAME,1);
      l = iLow(Symbol(),SYSTEM_TIME_FRAME,1);
      c = iClose(Symbol(),SYSTEM_TIME_FRAME,1);
      bullish = (o < c) && (c > iClose(Symbol(),SYSTEM_TIME_FRAME,2));

      if(PositionsTotal() == 0)
        {
         position_timer = 0;
         find_setup();
        }

      else
         if(PositionsTotal() > 0)
           {
            if(PositionSelect(Symbol()))
              {
               long position_type = PositionGetInteger(POSITION_TYPE);
               double current_sl = PositionGetDouble(POSITION_SL);
               double new_sl;

               //--- Buy Trades
               if(position_type == POSITION_TYPE_BUY)
                 {
                  new_sl = bid - ((h-l)*sl_width);
                  if(new_sl > current_sl)
                     Trade.PositionModify(Symbol(),new_sl,0);
                 }

               //--- Sell Trades
               else
                  if(position_type == POSITION_TYPE_SELL)
                    {
                     new_sl = ask + ((h-l)*sl_width);
                     if(new_sl < current_sl)
                        Trade.PositionModify(Symbol(),new_sl,0);
                    }
              }

            if(position_timer < RETURN_PERIOD)
               position_timer+=1;

            else
               Trade.PositionClose(Symbol());
           }
     }
  }

Por fim, esta função obterá uma previsão do nosso modelo e depois verificará se temos uma oportunidade de trading válida.

//+------------------------------------------------------------------+
//| Find A Trading Setup                                             |
//+------------------------------------------------------------------+
void find_setup(void)
  {
   vectorf model_inputs(ONNX_INPUT_SHAPE);

   model_inputs[0] = (float) iOpen(Symbol(),SYSTEM_TIME_FRAME,0);
   model_inputs[1] = (float) iHigh(Symbol(),SYSTEM_TIME_FRAME,0);
   model_inputs[2] = (float) iLow(Symbol(),SYSTEM_TIME_FRAME,0);
   model_inputs[3] = (float) iClose(Symbol(),SYSTEM_TIME_FRAME,0);

   vectorf model_output(ONNX_OUTPUT_SHAPE);

   if(!OnnxRun(model,ONNX_DATA_TYPE_FLOAT,model_inputs,model_output))
     {
      Comment("Failed To Get A Prediction From Our Model: ",GetLastError());
      return;
     }

   else
     {
      Comment("Prediction: ",model_output[0]);

      vector open,close;

      open.CopyRates(Symbol(),SYSTEM_TIME_FRAME,COPY_RATES_OPEN,1,2);
      close.CopyRates(Symbol(),SYSTEM_TIME_FRAME,COPY_RATES_CLOSE,1,2);

      if(open.Mean() < close.Mean())
        {
         if((model_output[0] > 0) && (bullish))
            Trade.Buy(TRADING_VOLUME,Symbol(),ask,(bid - ((h-l) * sl_width)),0);
        }


      else
         if(open.Mean() > close.Mean())
           {
            if((model_output[0] < 0) && (!bullish))
               Trade.Sell(TRADING_VOLUME,Symbol(),bid,(ask + ((h-l) * sl_width)),0);
           }
     }
  }

Não se esqueça de remover a definição de todas as constantes do sistema que você criar no seu aplicativo.

//+------------------------------------------------------------------+
//| Undefine System Constants                                        |
//+------------------------------------------------------------------+
#undef  ONNX_INPUT_SHAPE
#undef  ONNX_OUTPUT_SHAPE
#undef  SYSTEM_TIME_FRAME
#undef  TRADING_VOLUME
#undef  RETURN_PERIOD
//+------------------------------------------------------------------+

Os intervalos de datas que usaremos no nosso backtest foram definidos com base no período de treinamento do nosso modelo. Essas datas serão as mesmas tanto para nossos testes na Corretora A quanto para os testes na Corretora B. Lembremos que a Corretora B representa a corretora que o desenvolvedor MQL5 usa para criar seu aplicativo, enquanto a Corretora A simboliza a corretora na qual seus clientes podem eventualmente implantar o aplicativo.

Fig. 12: Vamos selecionar as datas de entrada para o nosso período de teste

As duas configurações indicadas na Fig. 12 acima e na Fig. 13 abaixo serão usadas nos dois testes que realizaremos.

Fig. 13: Também vamos selecionar parâmetros de modelagem complexos para obter uma visão realista das capacidades da nossa estratégia

Como podemos ver na Fig. 14, nossa estratégia parece promissora quando a testamos com a Corretora B. Ela lida bem com dados fora da amostra e nos incentiva a dedicar mais tempo ao refinamento da estratégia, para extrair dela o melhor desempenho possível. No entanto, o ponto que estamos tentando transmitir ao leitor é que talvez seja ingênuo presumir sempre que as melhorias obtidas com uma corretora se refletirão de forma significativa nas melhorias obtidas com qualquer outra corretora.

Fig. 14: A curva de equity construída de acordo com nossa estratégia apresenta dinâmica positiva em relação à corretora pretendida

No entanto, ao aplicar a mesma estratégia com a Corretora A, deixamos de observar a tendência positiva de crescimento do saldo da conta que vimos com a Corretora B. Evidentemente, a estratégia nos traz muito pouco benefício se trocarmos de corretora sem alterar o modelo base. Os desenvolvedores devem entender que isso nem sempre é culpa deles. É difícil para qualquer desenvolvedor adaptar seus modelos a cada corretora existente no mundo.

No entanto, essa é apenas uma forma visual de entender o problema. Desenvolvedores e clientes podem acabar com entendimentos completamente diferentes se a relação entre eles não for definida com cuidado suficiente. 

Fig. 15: Ao aplicar nossa estratégia com a Corretora A, não conseguimos reproduzir a tendência de crescimento do saldo da conta pela qual trabalhamos tanto

Também podemos examinar com mais detalhes os resultados obtidos com a Corretora B na Fig. 16 e compará-los com os resultados do nosso modelo na Corretora A na Fig. 17. 

Fig. 16: Análise detalhada dos nossos indicadores de trading quando nos concentramos na corretora pretendida

É evidente que desenvolver uma estratégia capaz de funcionar de forma eficaz com várias corretoras certamente não é uma tarefa trivial. À medida que os modelos de aprendizado de máquina se tornam mais complexos, eles também se tornam mais sensíveis a pequenas mudanças em seus dados de entrada. Essas diferenças nas características numéricas do instrumento podem ter consequências graves para nossa capacidade de compartilhar e reproduzir estratégias de trading de forma consistente. 

Fig. 17: Análise detalhada da estratégia que tenta funcionar com uma corretora na qual ela não foi treinada


Conclusão

O caráter descentralizado dos mercados financeiros globais impõe limitações reais que dificultam que os membros da nossa comunidade reproduzam os resultados uns dos outros. As corretoras não dão nenhuma garantia de que seus preços coincidirão, o que significa que as falhas exploráveis que você encontra na sua corretora podem não existir na minha, mesmo usando a mesma estratégia no mesmo instrumento.

Dependendo do papel que você prefere desempenhar em nossa comunidade, essas ideias podem ter importância prática:

  • Se você prefere usar a seção "Freelance" do site MQL5, informe sua corretora ao solicitar aplicativos e peça aos desenvolvedores que criem contas demo com sua corretora, para que seja possível obter soluções personalizadas. Evite solicitações aleatórias e genéricas, como "Preciso de um aplicativo de trading para o par EURUSD", porque, como já vimos, pode ser mais seguro para você especificar tudo com o máximo de detalhes possível.
  • Os usuários que compram aplicativos com frequência no Marketplace agora entendem por que produtos voltados a uma corretora específica podem trazer mais benefício do que aqueles que se apresentam como universais.
  • Assinantes de sinais de trading podem alcançar maior satisfação escolhendo criteriosamente provedores de sinais de trading que usam a mesma corretora, o que garante que o retorno declarado e o retorno realizado sempre coincidam.
  • Por fim, meus colegas desenvolvedores MQL5 passam a ter uma visão mais clara do que talvez seja necessário para oferecermos produtos consistentes e serviços confiáveis que deixem nossos clientes satisfeitos.

Ao reconhecer esses problemas, podemos trabalhar na criação de soluções mais reproduzíveis, voltadas a corretoras específicas, que beneficiem todos os membros da nossa comunidade diversa e inclusiva. Este artigo foi concebido como uma ilustração dos perigos associados à tentativa de usar o mesmo modelo ONNX em diferentes corretoras. Como desenvolvedores MQL5, acredito que devemos adotar padrões profissionais mais elevados e evitar expor nossos clientes a riscos desse tipo.

Nome do arquivo Descrição do arquivo
Requesting Broker Data.ipynb Notebook Jupyter que usamos para obter dados históricos diários do par EURUSD das nossas duas corretoras.
Analyzing Broker Data.ipynb Notebook Jupyter que usamos para verificar a consistência dos dados históricos diários do par EURUSD fornecidos pelas nossas duas corretoras.
EURUSD.mq5 EA que criamos para avaliar nossa lucratividade usando o mesmo modelo em duas corretoras diferentes.

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

Construindo Expert Advisors Autootimizáveis em MQL5 (Parte 6): Regras de Trading Autoajustáveis (II) Construindo Expert Advisors Autootimizáveis em MQL5 (Parte 6): Regras de Trading Autoajustáveis (II)
Este artigo explora a otimização dos níveis e períodos do RSI para obter melhores sinais de trading. Introduzimos métodos para estimar valores ótimos do RSI e automatizar a seleção de períodos usando busca em grade e modelos estatísticos. Por fim, implementamos a solução em MQL5 enquanto utilizamos Python para análise. Nossa abordagem busca ser pragmática e direta para ajudá-lo a resolver problemas potencialmente complicados, com simplicidade.
Automatizando Estratégias de Trading em MQL5 (Parte 12): Implementação da Estratégia Mitigation Order Blocks (MOB) Automatizando Estratégias de Trading em MQL5 (Parte 12): Implementação da Estratégia Mitigation Order Blocks (MOB)
Neste artigo, construímos um sistema de trading em MQL5 que automatiza a detecção de order blocks para trading Smart Money. Descrevemos as regras da estratégia, implementamos a lógica em MQL5 e integramos o gerenciamento de risco para uma execução eficaz das operações. Por fim, realizamos o backtest do sistema para avaliar seu desempenho e refiná-lo para obter resultados ideais.
Está chegando o novo MetaTrader 5 e MQL5 Está chegando o novo MetaTrader 5 e MQL5
Esta é apenas uma breve resenha do MetaTrader 5. Eu não posso descrever todos os novos recursos do sistema por um período tão curto de tempo - os testes começaram em 09.09.2009. Esta é uma data simbólica, e tenho certeza que será um número de sorte. Alguns dias passaram-se desde que eu obtive a versão beta do terminal MetaTrader 5 e MQL5. Eu ainda não consegui testar todos os seus recursos, mas já estou impressionado.
Do iniciante ao especialista: Criação de um EA animado para notícias em MQL5 (VIII): botões de negociação rápida para trading de notícias Do iniciante ao especialista: Criação de um EA animado para notícias em MQL5 (VIII): botões de negociação rápida para trading de notícias
Enquanto os sistemas algorítmicos de trading gerenciam operações automatizadas, muitos traders de notícias e scalpers preferem manter controle ativo durante eventos importantes de notícias e condições de mercado que mudam rapidamente, exigindo execução e gestão rápidas das ordens. Isso evidencia a necessidade de ferramentas de interface intuitivas que integrem feeds de notícias em tempo real, dados do calendário econômico, leituras dos indicadores, análises baseadas em IA e gestão adaptativa do trading.