English Русский 中文 Español Deutsch 日本語
preview
Repensando Estratégias Clássicas (Parte 14): Configurações de Alta Probabilidade

Repensando Estratégias Clássicas (Parte 14): Configurações de Alta Probabilidade

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

Em nossas discussões anteriores, analisamos como o trading de cruzamentos de médias móveis pode ser repensado fixando os períodos dos 2 indicadores de média móvel em questão. Demonstramos que, ao fazer isso, podemos exercer efetivamente um nível razoável de controle sobre a quantidade de atraso em nossa estratégia de trading. Em seguida, observamos que, ao aplicar uma média móvel sobre o preço de abertura e a outra sobre o preço de fechamento, obtivemos uma forma muito mais sensível da versão clássica de cruzamento de médias móveis. Nossa nova estrutura oferece algumas garantias que não podíamos obter com a versão clássica. Os leitores que ainda não leram nossa discussão anterior podem encontrar o artigo facilmente disponível aqui.

Hoje, exploraremos mais a fundo se há algum benefício em tentar extrair mais desempenho do cruzamento de médias móveis. Ao modelarmos cuidadosamente a relação entre nossa estratégia de cruzamento de médias móveis e o mercado EURUSD, esperamos aprender qual é a diferença entre as condições de mercado nas quais nossa estratégia apresenta excelente desempenho e aquelas que se mostram desafiadoras demais para nossa estratégia. Nosso objetivo é, então, construir uma estratégia de trading que aprenda a interromper as operações quando detectar condições de mercado desfavoráveis.


Visão Geral da Estratégia de Trading

É amplamente aceito pela maioria dos membros da nossa comunidade que os traders devem buscar ativamente operar setups de alta probabilidade. No entanto, existem poucas definições formais sobre o que exatamente caracteriza uma oportunidade de alta probabilidade. Como podemos medir, de forma empírica, a probabilidade associada a uma determinada configuração de trading? Dependendo de quem você consultar, encontrará diferentes definições sobre como identificar essas oportunidades e aproveitá-las de maneira responsável.

Este artigo busca abordar essas questões propondo uma estrutura algorítmica que nos permita nos afastar das definições tradicionais e avançar para definições numéricas baseadas em evidências. Dessa forma, nossas estratégias de trading poderão identificar essas oportunidades e operá-las de maneira consistente e lucrativa por conta própria. 

Nosso objetivo é modelar a relação entre uma determinada estratégia de trading e qualquer ativo que tenhamos escolhido operar. Para isso, primeiro obteremos, diretamente do terminal MetaTrader 5, dados que descrevam adequadamente o mercado, juntamente com todos os parâmetros que compõem nossa estratégia de trading.
Em seguida, ajustaremos um modelo estatístico para classificar se a estratégia deverá produzir sinais lucrativos ou se o sinal gerado provavelmente resultará em prejuízo. 

A probabilidade estimada pelo nosso modelo passa, então, a representar a probabilidade associada àquele sinal específico. Dessa forma, podemos começar a tratar as "configurações de alta probabilidade" de maneira mais científica e empírica, com base em evidências e dados de mercado relevantes.

Essa estrutura nos permite, essencialmente, desenvolver estratégias de trading "conscientes de seus objetivos", instruídas explicitamente a realizar apenas ações para as quais se espera um resultado favorável. Estamos começando a formalizar os componentes necessários para desenvolver estratégias de trading algorítmico capazes de estimar as consequências mais prováveis de suas próprias ações. Esse conceito pode ser entendido como uma aplicação dos princípios de aprendizado por reforço por meio de uma abordagem supervisionada.


Introdução ao MQL5

Nossa tarefa de hoje está centrada em aprender a relação entre nossa estratégia de trading e o ativo que desejamos operar. Para atingir esse objetivo, obteremos as variações nos 4 principais preços (Open, High, Low e Close), bem como as alterações em nossos dois indicadores de média móvel. 

Observe que, para rotular os dados, também precisaremos dos valores reais originais dos dois indicadores e do preço de fechamento. No total, gravaremos nossos dados em um arquivo CSV com 10 colunas e, em seguida, aprenderemos a relação que nossa estratégia possui com esse ativo específico. Em cada etapa do processo, compararemos esse resultado com o desempenho de um modelo idêntico que tenta prever diretamente o preço de mercado.

Isso nos mostrará qual variável-alvo (target) é mais fácil de aprender, e a vantagem de nossa abordagem está no simples fato de que, independentemente de qual target seja mais fácil de prever, ambos nos fornecem informações sobre a direção do preço.

/+-------------------------------------------------------------------+
//|                                                      ProjectName |
//|                                      Copyright 2020, CompanyName |
//|                                       http://www.companyname.net |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property script_show_inputs

//--- Define our moving average indicator
#define MA_PERIOD 3                 //--- Moving Average Period
#define MA_TYPE   MODE_SMA          //--- Type of moving average we have
#define  HORIZON 10

//--- Our handlers for our indicators
int ma_handle,ma_o_handle;

//--- Data structures to store the readings from our indicators
double ma_reading[],ma_o_reading[];

//--- File name
string file_name = Symbol() + " Reward Modelling.csv";

//--- Amount of data requested
input int size = 3000;

//+------------------------------------------------------------------+
//| Our script execution                                             |
//+------------------------------------------------------------------+
void OnStart()
  {
   int fetch = size + (HORIZON * 2);
//---Setup our technical indicators
   ma_handle = iMA(_Symbol,PERIOD_CURRENT,MA_PERIOD,0,MA_TYPE,PRICE_CLOSE);
   ma_o_handle = iMA(_Symbol,PERIOD_CURRENT,MA_PERIOD,0,MA_TYPE,PRICE_OPEN);

//---Set the values as series
   CopyBuffer(ma_handle,0,0,fetch,ma_reading);
   ArraySetAsSeries(ma_reading,true);
   CopyBuffer(ma_o_handle,0,0,fetch,ma_o_reading);
   ArraySetAsSeries(ma_o_reading,true);

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

   for(int i=size;i>=1;i--)
     {
      if(i == size)
        {
         FileWrite(file_handle,"Time","True Close","True MA C","True MA O","Open","High","Low","Close","MA Close 2","MA Open 2");
        }

      else
        {
         FileWrite(file_handle,
                   iTime(_Symbol,PERIOD_CURRENT,i),
                   iClose(_Symbol,PERIOD_CURRENT,i),
                   ma_reading[i],
                   ma_o_reading[i],
                   iOpen(_Symbol,PERIOD_CURRENT,i)   - iOpen(_Symbol,PERIOD_CURRENT,(i + HORIZON)),
                   iHigh(_Symbol,PERIOD_CURRENT,i)   - iHigh(_Symbol,PERIOD_CURRENT,(i + HORIZON)),
                   iLow(_Symbol,PERIOD_CURRENT,i)    - iLow(_Symbol,PERIOD_CURRENT,(i + HORIZON)),
                   iClose(_Symbol,PERIOD_CURRENT,i)  - iClose(_Symbol,PERIOD_CURRENT,(i + HORIZON)),
                   ma_reading[i] - ma_reading[(i + HORIZON)],
                   ma_o_reading[i] - ma_o_reading[(i + HORIZON)]
                  );
        }
     }
//--- Close the file
   FileClose(file_handle);
  }
//+------------------------------------------------------------------+


Analisando os Dados

Para começar, vamos carregar nossos dados de mercado em um Jupyter Notebook para que possamos realizar nossa análise numérica do desempenho da estratégia.

import pandas as pd

Defina até que ponto no futuro gostaríamos de prever nossos lucros ou perdas.

HORIZON = 10

Carregue os dados e adicione as colunas de que precisamos. A primeira coluna, 'Target', representa o retorno tradicional do mercado; neste caso, trata-se do retorno do mercado EURUSD em 10 dias. A coluna 'Class' é igual a 1 para dias de alta; caso contrário, é igual a 0. Em terceiro lugar, a coluna 'Action' indica qual ação nossa estratégia de trading teria sido acionada para executar: 1 corresponde à compra e -1 à venda. A coluna 'Reward' é calculada como o produto elemento a elemento das colunas 'Target' e 'Action'. Essa multiplicação produzirá recompensas positivas somente se nossa estratégia:

  • Escolheu a ação -1 e o variável-alvo (target) clássico foi menor que 0 (isso significa que nossa estratégia vendeu e os níveis futuros de preço caíram)
  • Escolheu a ação 1 e o variável-alvo (target) clássico foi maior que 0 (isso significa que nossa estratégia comprou e os níveis futuros de preço subiram)
Qualquer outra combinação de ações e variáveis-alvo (target) produziria recompensas negativas, pois nossa estratégia de trading teria agido de maneira inadequada. Se nosso classificador conseguir aprender a prever se nossa recompensa será positiva, teremos efetivamente encontrado uma ferramenta muito confiável para nos manter fora de condições de mercado desfavoráveis e nos ajudar a realizar nossas operações quando houver maior probabilidade de elas serem lucrativas. A última coluna, 'Trade Signal', é definida inicialmente como 0 e somente passa a ser 1 quando nossa recompensa é positiva. Inicialmente, todas as 5 colunas serão definidas como 0.

data = pd.read_csv("..\EURUSD Reward Modelling.csv")

data['Target'] = 0
data['Class']  = 0
data['Action'] = 0
data['Reward'] = 0
data['Trade Signal'] = 0

Agora, preenchemos nosso variável-alvo (target) clássico, a variação de 10 dias no preço de fechamento do EURUSD.

data['Target'] = data['True Close'].shift(-HORIZON) - data['True Close']
data.dropna(inplace=True)

Precisamos rotular as classes para que possamos distinguir rapidamente os dias de alta e de baixa em nossos gráficos.

data.loc[data['Target'] > 0,'Class'] = 1

Agora, vamos preencher quais ações nossa estratégia teria realizado.

data.loc[data['True MA C'] > data['True MA O'],'Action'] = 1
data.loc[data['True MA C'] < data['True MA O'],'Action'] = -1

E o lucro ou prejuízo que nossa estratégia teria obtido ao realizar essas ações.

data['Reward'] = data['Target'] * data['Action']

Agora, vamos preencher o sinal de trading que nos informa se devemos agir ou aguardar.

data.loc[((data['Target'] < 0) & (data['Action'] == -1)),'Trade Signal'] = 1
data.loc[((data['Target'] > 0) & (data['Action'] == 1)),'Trade Signal'] = 1

Podemos analisar os dados. Podemos perceber rapidamente que é difícil separar adequadamente a dinâmica do mercado. Os pontos laranja representam sinais de trading que deveríamos ter seguido, enquanto os azuis representam sinais que deveríamos ter ignorado. O fato de os pontos laranja e azuis aparecerem sobrepostos neste gráfico de dispersão demonstra matematicamente ao leitor que sinais de trading lucrativos e não lucrativos podem surgir sob condições praticamente idênticas. Nossos modelos estatísticos podem ser capazes de identificar semelhanças e diferenças que nós, humanos, talvez não consigamos perceber com facilidade ou que exigiriam um trabalho considerável para serem identificadas.

import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt

sns.scatterplot(data=data,x='MA Close 2',y='MA Open 2',hue='Trade Signal')
plt.title('Analyzing How Well Moving Average Cross Overs Separate The Market')
plt.grid()

Fig 1: Nossos cruzamentos de médias móveis parecem apresentar dificuldades para separar a ação do preço

Vamos estimar rapidamente se nosso novo variável-alvo (target) é mais fácil de prever do que o target tradicional. 

from sklearn.linear_model import RidgeClassifier
from sklearn.model_selection import TimeSeriesSplit,cross_val_score

Os modelos lineares são especialmente úteis para obter aproximações confiáveis e com baixo custo computacional. Vamos criar um objeto de divisão de séries temporais para realizar a validação cruzada de um classificador linear.

tscv = TimeSeriesSplit(n_splits=5,gap=HORIZON)
model = RidgeClassifier()
scores = []

Faça a validação cruzada do modelo, primeiro utilizando o variável-alvo (target) tradicional e, em seguida, nosso novo target, que busca estimar o lucro ou prejuízo gerado pela estratégia. Como estamos realizando uma tarefa de classificação, certifique-se de definir o método de avaliação como 'acurácia'.

scores.append(np.mean(np.abs(cross_val_score(model,data.iloc[:,4:-5],data.loc[:,'Class'],cv=tscv,scoring='accuracy'))))
scores.append(np.mean(np.abs(cross_val_score(model,data.iloc[:,4:-5],data.loc[:,'Trade Signal'],cv=tscv,scoring='accuracy'))))

Ao criarmos um gráfico de barras com os resultados, podemos perceber rapidamente que o modelo que prevê o lucro ou prejuízo da estratégia apresenta um desempenho superior ao modelo que tenta prever diretamente o mercado. O modelo que tenta prever diretamente o mercado ficou abaixo do limite de 50%, enquanto nosso novo variável-alvo (target) conseguiu superar a marca de 50%, embora, na prática, apenas por uma pequena margem.

sns.barplot(scores,color='black')
plt.axhline(np.max(scores),linestyle=':',color='red')
plt.title('Forcasting Market Returns vs Forecasting Strategy Reward')
plt.ylabel('Percentage Accuracy Levels %')
plt.xlabel('0: Market Return Forecast | 1: Strategy Profit/Loss Forecast')

Fig 2: Nossos modelos lineares sugerem que pode ser mais vantajoso prever o lucro ou prejuízo gerado pela estratégia

Podemos calcular exatamente o ganho percentual de desempenho obtido. Os resultados indicam um aumento de 7,6% na acurácia quando prevemos a relação entre a estratégia e o mercado, em vez de tentarmos prever diretamente o mercado.

scores = (((scores / scores[0]) - 1) * 100)

scores[1]

7.595993322203687

Remova todos os dados que se sobrepõem ao período do nosso teste retrospectivo / backtest, para que ele represente uma simulação materialmente mais fiel às condições reais de mercado.

#Drop all the data that overlaps with your backtest period
data = data.iloc[:-((365 * 4) + (30 * 5) + 17),:]
data

Fig 3: Certifique-se de que as datas presentes no DataFrame não se sobreponham ao período que será utilizado no teste retrospectivo / backtest

O modelo linear nos deu mais confiança de que prever o lucro ou prejuízo gerado pela estratégia pode ser mais vantajoso do que prever diretamente o preço. No entanto, utilizaremos um modelo mais flexível em nossos testes retrospectivos / backtests para garantir que ele seja capaz de extrair o máximo possível de informações relevantes dos dados.

from sklearn.ensemble import GradientBoostingRegressor

model = GradientBoostingRegressor()

Defina os dados de entrada e o variável-alvo (target).

X = ['Open','High','Low','Close','MA Close 2','MA Open 2']
y = 'Trade Signal'

Ajuste o modelo.

model.fit(data.loc[:,X],data.loc[:,y])

Prepare o modelo para exportação para ONNX.

import onnx
from skl2onnx import convert_sklearn
from skl2onnx.common.data_types import FloatTensorType

Defina o tamanho da entrada do modelo.

initial_types = [("float input",FloatTensorType([1,6]))]

Salve o modelo.

onnx_proto = convert_sklearn(model,initial_types=initial_types,target_opset=12)
onnx.save(onnx_proto,"EURUSD Reward Model.onnx")


Primeiros Passos em MQL5

Agora estamos prontos para iniciar o desenvolvimento da nossa aplicação de trading. Criaremos duas versões da estratégia para avaliar a eficácia das alterações que fizemos no processo de treinamento do nosso modelo estatístico. As duas versões do algoritmo de trading serão submetidas a testes retrospectivos / backtests sob condições idênticas. Vamos conhecer essas condições para evitar repetir desnecessariamente as mesmas informações mais adiante. 

A primeira configuração importante é o símbolo. Como já discutimos, neste exemplo operaremos o par EURUSD. Operaremos esse ativo no período gráfico diário durante um período de 5 anos, de 1º de janeiro de 2020 a 1º de abril de 2025. Isso nos proporcionará um período suficientemente amplo para avaliar o desempenho da nossa aplicação. O leitor deve se lembrar de que, na Fig 3, removemos todos os dados de mercado posteriores a 29 de dezembro de 2019.

Fig 4: Período utilizado no teste retrospectivo / backtestdas duas versões da nossa estratégia de trading

Por fim, as condições utilizadas para modelar o mercado serão configuradas de forma a reproduzir a natureza imprevisível das operações reais. Por esse motivo, definiremos o atraso como Random delay e utilizaremos cada tick com base em ticks reais, buscando reproduzir as condições de mercado da forma mais realista possível. 

Fig 5: As configurações utilizadas para reproduzir as condições reais de mercado são extremamente importantes

Agora, vamos começar a desenvolver uma aplicação que possa ser submetida a teste retrospectivo / backtestpara avaliarmos os benefícios das alterações propostas em nosso processo de treinamento. Começaremos medindo o desempenho da estratégia sem utilizar a nova abordagem de modelagem que desenvolvemos para identificar configurações de alta probabilidade. Em essência, a primeira versão da nossa estratégia simplesmente aplica a estratégia discricionária de operar os cruzamentos de médias móveis sempre que eles ocorrerem. Isso nos fornecerá uma referência para compararmos o desempenho da nossa estratégia proposta de modelagem de recompensa. Para começar, importaremos a biblioteca de trading.

//+------------------------------------------------------------------+
//|                                             Reward Modelling.mq5 |
//|                                               Gamuchirai Ndawana |
//|                    https://www.mql5.com/en/users/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Ndawana"
#property link      "https://www.mql5.com/en/users/gamuchiraindawa"
#property version   "1.00"

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

Defina as constantes necessárias do sistema. Essas constantes correspondem às que utilizamos tanto no script MQL5 quanto no script Python.

//+------------------------------------------------------------------+
//| System constants                                                 |
//+------------------------------------------------------------------+
#define MA_PERIOD 3                //--- Moving Average Period
#define MA_TYPE   MODE_SMA         //--- Type of moving average we have
#define HORIZON 10                 //--- How far into the future we should forecast

Configure nossas variáveis globais e indicadores técnicos.

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
int fetch = HORIZON + 1;

//+------------------------------------------------------------------+
//| Technical indicators                                             |
//+------------------------------------------------------------------+
int ma_handle,ma_o_handle;
double ma_reading[],ma_o_reading[];
int position_timer;

Cada manipulador de eventos foi associado a um método correspondente, que será chamado sempre que o respectivo evento for acionado. Esse estilo de desenvolvimento torna o código mais fácil de manter e ampliar futuramente, caso você queira implementar novas funcionalidades. Ao escrever o código diretamente no próprio manipulador de eventos, o desenvolvedor precisa analisar cuidadosamente inúmeras linhas antes de realizar qualquer alteração, para garantir que nada seja comprometido. Com nosso padrão de desenvolvimento, por outro lado, basta adicionar uma chamada à função desejada antes das funções que já fornecemos, caso seja necessário ampliar as funcionalidades da aplicação.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   if(!setup())
      return(INIT_FAILED);
//---
   return(INIT_SUCCEEDED);
  }
  
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   release();
  }
  
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   update();
  }
//+------------------------------------------------------------------+

Agora, analisaremos cada método individualmente. A primeira função que configuraremos será responsável por carregar nossos indicadores técnicos e reiniciar o temporizador da posição. Esse temporizador é necessário para garantir que cada operação permaneça aberta durante o período definido pela constante de sistema horizon.

//+------------------------------------------------------------------+
//| Setup the system                                                 |
//+------------------------------------------------------------------+
bool setup(void)
  {
//---Setup our technical indicators
   ma_handle = iMA(_Symbol,PERIOD_CURRENT,MA_PERIOD,0,MA_TYPE,PRICE_CLOSE);
   ma_o_handle = iMA(_Symbol,PERIOD_CURRENT,MA_PERIOD,0,MA_TYPE,PRICE_OPEN);
   position_timer = 0;

   return(true);
  }

O método release simplesmente libera os indicadores técnicos que não estão mais sendo utilizados. No MQL5, é uma boa prática liberar os recursos que não são mais necessários.

//+------------------------------------------------------------------+
//| Release system variables we are no longer using                  |
//+------------------------------------------------------------------+
void release(void)
  {
   IndicatorRelease(ma_handle);
   IndicatorRelease(ma_o_handle);
   return;
  }

O método update obtém os níveis atuais de preço e os copia para os buffers dos nossos indicadores. Além disso, ele acompanha há quanto tempo a posição atual está aberta, garantindo que ela seja encerrada no momento correto.

//+------------------------------------------------------------------+
//| Update system parameters                                         |
//+------------------------------------------------------------------+
void update(void)
  {
   //--- Time stamps
   static datetime time_stamp;
   datetime current_time = iTime(Symbol(),PERIOD_D1,0);

   //--- We are on a new day
   if(time_stamp != current_time)
     {
      time_stamp = current_time;
      if(PositionsTotal() == 0)
        {
         //--- Copy indicator values
         CopyBuffer(ma_handle,0,0,fetch,ma_reading);
         CopyBuffer(ma_o_handle,0,0,fetch,ma_o_reading);
         //---Set the values as series
         ArraySetAsSeries(ma_reading,true);
         ArraySetAsSeries(ma_o_reading,true);
         find_setup();
         position_timer = 0;
        }

      //--- Forecasts are only valid for HORIZON days
      if(PositionsTotal() > 0)
        {
         position_timer += 1;
        }

      //--- Otherwise close the position
      if(position_timer == HORIZON)
         Trade.PositionClose(Symbol());
     }
     return;
  }

Por fim, temos a função find setup. Nossas configurações são identificadas sempre que ocorre um cruzamento das médias móveis. Se a média aplicada ao preço de abertura cruzar acima da média aplicada ao preço de fechamento, isso será interpretado como um sinal de venda. Caso contrário, teremos um sinal de compra.

//+------------------------------------------------------------------+
//| Find a trading oppurtunity                                       |
//+------------------------------------------------------------------+
void find_setup(void)
  {
         double bid = SymbolInfoDouble(Symbol(),SYMBOL_BID) , ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);
         double vol = SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN);

         vector ma_o,ma_c;

         ma_o.CopyIndicatorBuffer(ma_o_handle,0,0,1);
         ma_c.CopyIndicatorBuffer(ma_handle,0,0,1);

         if(ma_o[0] > ma_c[0])
           {
            Trade.Sell(vol,Symbol(),ask,0,0,"");
           }

         if(ma_o[0] < ma_c[0])
           {
            Trade.Buy(vol,Symbol(),bid,0,0,"");
           }
     return;
  }    

Não se esqueça de remover as definições das constantes de sistema que criamos anteriormente.

//+------------------------------------------------------------------+
//| Undefine system constatns                                        |
//+------------------------------------------------------------------+
#undef HORIZON
#undef MA_PERIOD
#undef MA_TYPE

Já apresentamos anteriormente as configurações que utilizaremos em nosso teste retrospectivo / backtest. Nosso objetivo é observar a eficácia da estratégia sem utilizar as técnicas de modelagem estatística que desenvolvemos. Basta carregar o Expert Advisor e iniciar o teste retrospectivo / backtest utilizando as configurações apresentadas nas Fig 4 e 5.

Fig 6: Estabelecendo uma referência para comparação

A versão discricionária da nossa estratégia de trading foi lucrativa, embora a margem de lucro tenha sido pequena. A diferença entre o lucro e o prejuízo médios foi de apenas US$ 0,90, e somente 51% de todas as operações realizadas foram lucrativas, o que não é um resultado muito animador. Nosso Sharpe Ratio foi de 0,62 e poderia melhorar se reformulássemos o sistema.

Fig 7: Análise detalhada do desempenho da nossa estratégia de trading discricionária

Ao analisarmos as curvas de saldo e de patrimônio geradas por essa versão da estratégia, podemos identificar imediatamente algumas limitações evidentes. A estratégia apresenta instabilidade e alta volatilidade. Na verdade, após 4 anos de teste retrospectivo / backtest, em fevereiro de 2024, a estratégia quase retornou ao saldo inicial registrado no começo do teste. Ela teve dificuldade para se recuperar de uma sequência de operações não lucrativas iniciada no final de 2020 e que se prolongou por quase 4 anos, até 2024. Esse comportamento não é desejável para nós, como traders algorítmicos. 

Fig 8: Visualização da curva de lucros e perdas gerada pela versão discricionária da nossa estratégia de trading


Melhorando o Desempenho do Nosso Expert Advisor

Agora, vamos aprimorar nossa estratégia de trading, dando à aplicação a capacidade de reproduzir um dar à aplicação a capacidade de imitar o processo humano de considerar as consequências de suas ações antes de agir 

Começaremos importando nosso modelo ONNX para a aplicação como um recurso a partir do diretório de arquivos do sistema. 

//+------------------------------------------------------------------+
//| System resources                                                 |
//+------------------------------------------------------------------+
#resource "\\Files\\EURUSD Reward Model.onnx" as uchar onnx_proto[];

Precisaremos de algumas variáveis globais adicionais para utilizar nosso modelo. Principalmente, precisamos de variáveis que representem o identificador do modelo e a quantidade de dados que deverá ser obtida.

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
long onnx_model;
int fetch = HORIZON + 1;

Em seguida, precisamos configurar o modelo ONNX na função setup. No exemplo de código abaixo, omitimos propositalmente os trechos que permaneceram inalterados entre as duas versões da aplicação. Primeiro, criamos o modelo ONNX a partir de seu buffer, validamos o modelo e, em seguida, definimos adequadamente os tamanhos de entrada e saída. Se alguma dessas etapas falhar, interromperemos completamente o processo de inicialização.

//+------------------------------------------------------------------+
//| Setup the system                                                 |
//+------------------------------------------------------------------+
bool setup(void)
  {
//---Omitted code that hasn't changed

//--- Setup the ONNX model
   onnx_model = OnnxCreateFromBuffer(onnx_proto,ONNX_DEFAULT);

//--- Validate the ONNX model
   if(onnx_model == INVALID_HANDLE)
     {
      Comment("Failed to create ONNX model");
      return(false);
     }

//--- Register the ONNX model I/O parameters
   ulong input_shape[] = {1,6};
   ulong output_shape[] = {1,1};

   if(!OnnxSetInputShape(onnx_model,0,input_shape))
     {
      Comment("Failed to set input shape");
      return(false);
     }

   if(!OnnxSetOutputShape(onnx_model,0,output_shape))
     {
      Comment("Failed to set output shape");
      return(false);
     }

   return(true);
  }

Além disso, quando a aplicação for encerrada, precisaremos liberar os recursos do sistema que não estiverem mais sendo utilizados.

//+------------------------------------------------------------------+
//| Release system variables we are no longer using                  |
//+------------------------------------------------------------------+
void release(void)
  {
//--- Omitted code segments that haven't changed
   OnnxRelease(onnx_model);
   return;
  }

Vamos instruir a aplicação a obter primeiro uma previsão do nosso modelo de trading antes de decidir se deve realizar uma operação. Se a previsão do modelo for superior a 0,5, isso significa que nosso algoritmo espera que o sinal gerado pela estratégia seja lucrativo e, portanto, autoriza a operação. Caso contrário, aguardaremos até que as condições desfavoráveis do mercado passem.

//+------------------------------------------------------------------+
//| Find a trading oppurtunity                                       |
//+------------------------------------------------------------------+
void find_setup(void)
  {
//--- Skipped parts of the code base that haven't changed

//--- Prepare the model's inputs
   vectorf model_input(6);
   model_input[0] = (float)(iOpen(_Symbol,PERIOD_CURRENT,0)   - iOpen(_Symbol,PERIOD_CURRENT,(HORIZON)));
   model_input[1] = (float)(iHigh(_Symbol,PERIOD_CURRENT,0)   - iHigh(_Symbol,PERIOD_CURRENT,(HORIZON)));
   model_input[2] = (float)(iLow(_Symbol,PERIOD_CURRENT,0)    - iLow(_Symbol,PERIOD_CURRENT,(HORIZON)));
   model_input[3] = (float)(iClose(_Symbol,PERIOD_CURRENT,0)  - iClose(_Symbol,PERIOD_CURRENT,(HORIZON)));
   model_input[4] = (float)(ma_reading[0] - ma_reading[(HORIZON)]);
   model_input[5] = (float)(ma_o_reading[0] - ma_o_reading[(HORIZON)]);

//--- Prepare the model's output
   vectorf model_output(1);

//--- We failed to run the model
   if(!OnnxRun(onnx_model,ONNX_DATA_TYPE_FLOAT,model_input,model_output))
      Comment("Failed to obtain a forecast");

//--- Everything went fine
   else
     {
      Comment("Forecast: ",model_output[0]);

      //--- Our model forecasts that our strategy is likely to be profitable
      if(model_output[0] > 0.5)
        {
         if(ma_o[0] > ma_c[0])
           {
            Trade.Sell(vol,Symbol(),ask,0,0,"");
           }

         if(ma_o[0] < ma_c[0])
           {
            Trade.Buy(vol,Symbol(),bid,0,0,"");
           }
        }
     }
   return;
  }

Execute a aplicação em um teste retrospectivo / backtest utilizando as configurações especificadas nas Fig 4 e 5, para que possamos realizar uma comparação justa entre as duas estratégias.

Parâmetros do teste retrospectivo / backtest

Fig 9: Executando nosso segundo teste retrospectivo / backtest com a versão revisada da estratégia de trading, que modela os lucros e prejuízos

Nosso Sharpe Ratio aumentou de 0,62, registrado na estratégia discricionária utilizada como referência, para 1,07 na versão atual. Isso representa um aumento de 72%. Nosso lucro líquido total aumentou 38%, passando de US$ 117,13 para US$ 162,75 com a utilização da nova estratégia de "configurações de alta probabilidade" definidas algoritmicamente. A proporção de operações com prejuízo caiu 17%, passando de 48,78% para 40,48%. Por fim, o número total de operações realizadas pela estratégia caiu de 164 para 126. Isso significa que nosso novo sistema gerou 38% mais lucro utilizando apenas 76% do número total de operações realizadas pelo sistema anterior. Na prática, nosso sistema se tornou mais eficiente, pois passou a gerar retornos maiores com menor exposição ao risco.

Fig 10: Resumo detalhado do desempenho da nossa nova estratégia de trading

Apresentamos ao leitor as curvas de saldo e de patrimônio da versão revisada da nossa estratégia de trading. Logo abaixo, incluímos a curva produzida pela versão original, permitindo comparar os resultados sem precisar voltar às seções anteriores. Podemos observar claramente que, antes mesmo do término do primeiro ano do teste retrospectivo / backtest, a versão original da estratégia quase retornou ao ponto de equilíbrio, enquanto nossa nova estratégia atravessou esse período de maneira mais consistente. 

É interessante observar que ambas as estratégias tiveram dificuldades de desempenho entre maio e dezembro de 2022. Isso pode indicar um período de mercado particularmente instável, que exigirá uma abordagem mais elaborada para ser tratado de maneira eficaz.

Fig 11: Curva de lucros e perdas produzida pela nossa nova estratégia de trading, capaz de considerar as possíveis consequências de suas ações

Fig 12: A curva de patrimônio produzida pela versão original da nossa estratégia de trading foi reproduzida aqui para facilitar a comparação com os novos resultados obtidos


Conclusão

Após a leitura deste artigo, o leitor conheceu uma nova abordagem para o trading algorítmico utilizando modelos estatísticos supervisionados. Agora, o leitor possui os conhecimentos necessários para modelar as relações existentes entre suas próprias estratégias de trading e os mercados em que opera. Essa abordagem pode proporcionar uma vantagem em relação aos participantes do mercado que tentam prever diretamente os movimentos do mercado, pois demonstramos que essa nem sempre é a melhor alternativa disponível. 

Nome do Arquivo  Descrição do Arquivo
Reward Modelling Benchmark.mq5 Esta é a versão tradicional da nossa estratégia de trading, que não tenta considerar as consequências de suas ações. Ela atribui o mesmo peso a todas as oportunidades de trading e parte do pressuposto de que cada operação deverá ser lucrativa.
Reward Modelling.mq5 Esta é a versão aprimorada da nossa estratégia de trading, que procura estimar explicitamente as consequências de suas ações antes de realizar qualquer operação.
EURUSD Reward Model.onnx Nosso modelo estatístico ONNX, que estima a probabilidade de o sinal gerado pela estratégia resultar em uma operação lucrativa.
Reward Modelling.ipynb O Jupyter Notebook que utilizamos para analisar nossos dados históricos de mercado e ajustar nosso modelo estatístico.

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

Caminhe em novos trilhos: Personalize indicadores no MQL5 Caminhe em novos trilhos: Personalize indicadores no MQL5
Vou agora listar todas as possibilidades novas e recursos do novo terminal e linguagem. Elas são várias, e algumas novidades valem a discussão em um artigo separado. Além disso, não há códigos aqui escritos com programação orientada ao objeto, é um tópico muito importante para ser simplesmente mencionado em um contexto como vantagens adicionais para os desenvolvedores. Neste artigo vamos considerar os indicadores, sua estrutura, desenho, tipos e seus detalhes de programação em comparação com o MQL4. Espero que este artigo seja útil tanto para desenvolvedores iniciantes quanto para experientes, talvez alguns deles encontrem algo novo.
Introdução à linguagem MQL5 (Parte 16): Criação de EAs com padrões de análise técnica Introdução à linguagem MQL5 (Parte 16): Criação de EAs com padrões de análise técnica
Este artigo mostra aos iniciantes como criar um EA em MQL5 capaz de identificar o clássico padrão de análise técnica "Cabeça e Ombros" e operar com base nele. Veremos como detectar esse padrão por meio da ação do preço, desenhá-lo no gráfico, definir os níveis de entrada, Stop Loss e Take Profit e automatizar a execução das operações com base no padrão.
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.
Desenvolvimento de ferramentas para análise de Price Action (Parte 24): Ferramenta de análise quantitativa de Price Action Desenvolvimento de ferramentas para análise de Price Action (Parte 24): Ferramenta de análise quantitativa de Price Action
Os padrões de candles fornecem informações valiosas sobre possíveis movimentos do mercado. Alguns candles sinalizam a continuação da tendência atual, enquanto outros antecipam uma reversão, dependendo de onde se formam na estrutura do movimento do preço. Neste artigo, apresentamos um EA que identifica automaticamente quatro padrões de candles importantes. Nas seções a seguir, você verá como essa ferramenta pode aprimorar sua análise de Price Action.