English Русский 中文 Español Deutsch 日本語
preview
Construindo Expert Advisors Auto Otimizáveis em MQL5 (Parte 6): Prevenção de Stop Out

Construindo Expert Advisors Auto Otimizáveis em MQL5 (Parte 6): Prevenção de Stop Out

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

É possível que traders prevejam corretamente a futura variação de preço de um mercado, mas fechem suas posições com prejuízo. Nos círculos de trading, isso é comumente chamado de “Ser estopado”. Esse problema surge do fato de que os níveis de preço não se movem em linhas retas e previsíveis. 

Na Fig 1, fornecemos um snapshot da variação horária de preço do par EURUSD. As linhas verticais brancas tracejadas marcam o início e o fim do dia de negociação. Um trader que estivesse confiante de que os níveis de preço cairiam naquele dia teria previsto corretamente a futura variação de preço. Infelizmente, os níveis de preço subiram consideravelmente antes de cair, o que significa que, se o stop loss do nosso trader estivesse dentro da zona vermelha destacada na Fig 1, ele teria fechado sua posição com prejuízo, mesmo tendo previsto corretamente a variação futura de preço.

Captura de tela 1

Fig 1: Visualizando situações em que traders normalmente são estopados

Ao longo dos anos, traders sugeriram diversas soluções para esse problema, mas, como veremos em nossa discussão, a maioria delas não são soluções válidas. A solução mais citada é simplesmente “aumente seus stops”. 

Isso implica que os traders deveriam ampliar seus stop losses em dias de negociação particularmente voláteis, para evitar serem estopados. Mas isso é um conselho inadequado, pois incentiva os traders a desenvolverem o hábito de assumir níveis variados de risco em negociações semelhantes, sem regras fixas e bem definidas orientando sua tomada de decisão. 

Outra solução comumente citada é “esperar por confirmação antes de entrar nas negociações”. Novamente, esse é um conselho inadequado considerando a natureza do problema que estamos analisando. Pode-se constatar que esperar por confirmação apenas adia o processo de ser estopado, e não resolve completamente o problema. 

Em resumo, o problema de ser estopado torna desafiador para os traders seguirem princípios sólidos de gestão de risco, ao mesmo tempo em que reduz a lucratividade das sessões de negociação, entre outros pontos de preocupação que gera. 

Portanto, tornaremos nosso objetivo equipar você, leitor, com regras mais razoáveis e bem definidas para minimizar a frequência com que você é estopado em negociações vencedoras. 

Nossa solução proposta irá contra as soluções mais comumente citadas e incentivará a construção de hábitos de negociação sólidos, como manter o tamanho do stop loss fixo, em oposição à solução frequentemente citada de “aumentar o stop loss”. 



Visão Geral da Estratégia de Negociação

Nossa estratégia de negociação será uma estratégia de reversão à média, composta por uma combinação de níveis de suporte e resistência juntamente com análise técnica. Primeiramente, marcaremos nossos níveis de preço de interesse utilizando a máxima e a mínima do dia anterior. A partir daí, aguardaremos para verificar se os níveis de preço do dia anterior serão rompidos durante o dia atual. Se, por exemplo, a máxima do dia anterior for rompida por uma nova máxima durante o dia atual, buscaremos oportunidades para assumir posições vendidas no mercado, apostando que os níveis de preço retornarão à sua média. The signal to enter short positions will clear for us when we observe price levels closing above the moving average indicator, after successfully closing above the previous high. 

Fig 2: Visualizando nossa estratégia de negociação em ação



Visão Geral do Período de Backtest

Para analisar a eficácia das alterações propostas à estratégia de negociação, primeiro precisamos definir um período fixo sobre o qual compararemos as mudanças que estamos fazendo em nosso sistema. Para esta discussão, iniciaremos nosso teste em 1º de janeiro de 2022 até 1º de janeiro de 2025. O período em questão foi destacado na Fig 1; observe que, na figura, estamos analisando o EURUSD no timeframe mensal.

Fig 3: O período sobre o qual realizaremos nosso backtest

Nossos testes reais serão realizados no timeframe M30. Na Fig 2 destacamos o mercado pretendido que utilizaremos para nossos testes, bem como o período discutido anteriormente. Essas configurações permanecerão fixas ao longo do restante do artigo, justificando a necessidade de discuti-las aqui. Para todos os testes que se seguem, manteremos essas configurações inalteradas. Além disso, certifique-se de selecionar o EURUSD se desejar acompanhar conosco, ou qualquer outro símbolo de sua preferência.

Fig 4: Nosso período de backtest

Adicionalmente, selecione "Every tick based on real ticks" para reproduzir a emulação mais precisa possível dos eventos históricos de mercado. Observe que essa configuração buscará os dados relevantes junto ao seu corretor, o que pode levar um tempo considerável dependendo da sua conexão de rede.

Fig 5: As configurações de conta que utilizaremos para nosso backtest


Primeiros Passos em MQL5

Agora que nos familiarizamos com o período de backtest para nosso teste de hoje, vamos primeiro estabelecer uma medição base que iremos superar. Começaremos construindo uma aplicação de negociação para implementar uma estratégia de suporte e resistência que busca operar rompimentos. Primeiro, importaremos a biblioteca de trade.

//+------------------------------------------------------------------+
//|                                               Baseline Model.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                                                        |
//+------------------------------------------------------------------+
#include <Trade/Trade.mqh>
CTrade Trade;

Definir constantes do sistema. Essas constantes nos ajudam a garantir controle definitivo sobre o comportamento da nossa aplicação em todos os testes.

//+------------------------------------------------------------------+
//| System constants                                                 |
//+------------------------------------------------------------------+
#define MA_PERIOD 14                //--- Moving Average Period
#define MA_TYPE   MODE_EMA          //--- Type of moving average we have
#define MA_PRICE PRICE_CLOSE        //---- Applied Price of Moving Average
#define TF_1 PERIOD_D1              //--- Our time frame for technical analysis
#define TF_2 PERIOD_M30             //--- Our time frame for managing positions
#define VOL 0.1                     //--- Our trading volume
#define SL_SIZE  1e3 * _Point       //--- The size of our stop loss

Também precisaremos de algumas variáveis globais para acompanhar os níveis de preço de interesse do dia anterior.

//+------------------------------------------------------------------+
//| Our global variables                                             |
//+------------------------------------------------------------------+
int ma_handler,system_state;
double ma[];
double bid,ask,yesterday_high,yesterday_low;
const string last_high = "LAST_HIGH";
const string last_low = "LAST_LOW";

Quando nossa aplicação for carregada pela primeira vez, configuraremos todos os nossos indicadores técnicos.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   setup();
//---
   return(INIT_SUCCEEDED);
  }

Se nossa aplicação não estiver mais em uso, liberaremos os indicadores técnicos que não estamos mais utilizando.

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

Sempre que recebermos preços atualizados, iremos armazená-los e também recalcular os valores dos nossos indicadores.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   update();
  }

Agora definiremos cada uma das funções que chamamos em nosso ciclo de eventos. Primeiro, nossa função de atualização trabalhará em 2 timeframes diferentes. Temos rotinas e procedimentos que devem ser executados uma vez por dia, e outros que devem ser executados em intervalos muito menores. Essa separação de responsabilidades é gerenciada pelas 2 constantes do sistema que definimos, TF_1 (Time Frame Diário) e TF_2 (Time Frame M30). Tarefas como buscar a máxima e mínima do dia anterior precisam ser feitas apenas uma vez por dia. Por outro lado, tarefas como procurar posições precisam ser realizadas apenas a cada novo candle de 30 minutos.

//+------------------------------------------------------------------+
//| Perform our update routines                                      |
//+------------------------------------------------------------------+
void update()
  {
//--- Daily procedures
     {
      static datetime time_stamp;
      datetime current_time = iTime(Symbol(),TF_1,0);
      if(time_stamp != current_time)
        {
         yesterday_high = iHigh(Symbol(),TF_1,1);
         yesterday_low = iLow(Symbol(),TF_1,1);
         //--- Mark yesterday's levels
         ObjectDelete(0,last_high);
         ObjectDelete(0,last_low);
         ObjectCreate(0,last_high,OBJ_HLINE,0,0,yesterday_high);
         ObjectCreate(0,last_low,OBJ_HLINE,0,0,yesterday_low);
        }
     }
//--- M30 procedures
     {
      static datetime time_stamp;
      datetime current_time = iTime(Symbol(),TF_2,0);
      if(time_stamp != current_time)
        {
         time_stamp = current_time;
         //--- Get updated prices
         bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);
         ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);
         //--- Update our technical indicators
         CopyBuffer(ma_handler,0,0,1,ma);
         //--- Check for a setup
         if(PositionsTotal()==0)  find_setup();
        }
     }
  }

Esta aplicação específica depende apenas de 1 indicador técnico. Portanto, nosso procedimento para definir a função de setup é simples.

//+------------------------------------------------------------------+
//| Custom functions                                                 |
//+------------------------------------------------------------------+
void setup(void)
  {
   ma_handler    = iMA(Symbol(),TF_2,MA_PERIOD,0,MA_TYPE,MA_PRICE);
  };

Nossas condições para abrir uma posição serão atendidas se detectarmos que nosso preço extremo atual ultrapassa o extremo oposto observado no dia anterior. Além de romper os níveis definidos no dia anterior, também desejamos registrar confirmação adicional da relação do preço com sua média móvel.

//+------------------------------------------------------------------+
//| Check if we have any trading setups                              |
//+------------------------------------------------------------------+
void find_setup(void)
  {
   if(iHigh(Symbol(),TF_2,1) < yesterday_low)
     {
         if(iClose(Symbol(),TF_2,1) < ma[0])
            {
               Trade.Buy(VOL,Symbol(),ask,(bid - (SL_SIZE)),(bid + (SL_SIZE)));
            }
     }

   if(iLow(Symbol(),TF_2,1) > yesterday_high)
     {
         if(iClose(Symbol(),TF_2,1) > ma[0])
            {
              Trade.Sell(VOL,Symbol(),bid,(ask + (SL_SIZE)),(ask - (SL_SIZE)));
            }
     }
  }

Se não estivermos utilizando nosso Expert Advisor, devemos liberar os recursos do sistema que não são mais necessários.

//+------------------------------------------------------------------+
//| Free resources we are no longer using up                         |
//+------------------------------------------------------------------+
void release(void)
  {
   IndicatorRelease(ma_handler);
  }

Por fim, ao final do ciclo de execução do programa, excluiremos as constantes do sistema que definimos anteriormente.

//+------------------------------------------------------------------+
//| Undefine the system constants we created                         |
//+------------------------------------------------------------------+
#undef TF_1
#undef TF_2
#undef VOL
#undef SL_SIZE
#undef MA_PERIOD
#undef MA_PRICE
#undef MA_TYPE

A curva de equity produzida por nossa estratégia atual não é estável. O saldo gerado pelo sistema apresenta uma tendência de queda ao longo do tempo. Desejamos uma estratégia que ocasionalmente tenha quedas, mas que apresente tendência de crescimento ao longo do tempo. Portanto, manteremos constantes as regras utilizadas para abrir posições e tentaremos filtrar as negociações que acreditamos que atingirão o stop loss. Esse exercício será desafiador. No entanto, é melhor aplicar alguma solução do que nenhuma.

Fig 6: A curva de equity produzida pela versão atual da nossa estratégia de negociação

Ao analisarmos os resultados detalhados da estratégia, observamos que nosso algoritmo perdeu mais de $1000 durante o período de backtest de 3 anos. Isso está longe de ser encorajador. Além disso, nossa perda média e máxima excedem nosso lucro médio e máximo. Isso nos dá uma expectativa negativa quanto ao desempenho futuro da estratégia. Portanto, não desejaríamos utilizar a estratégia em sua forma atual para negociar uma conta com capital real.

Fig 7: Analisando os resultados detalhados produzidos por nossa estratégia de negociação


Aprimorando a Linha de Base

A base da nossa estratégia de prevenção de stop out está em uma observação que fizemos anteriormente em nossa série de discussões. Leitores que desejarem revisitar a discussão anterior podem encontrá-la facilmente aqui. Em resumo, observamos que, em mais de 200 diferentes símbolos no nosso terminal MetaTrader 5, o indicador técnico de média móvel parecia consistentemente mais fácil de prever do que o próprio preço. 

Podemos utilizar essa observação de forma produtiva ao prever se o valor futuro da média móvel deverá ultrapassar nosso nível de stop loss. Se nosso computador esperar que isso aconteça, então ele não deverá realizar nenhuma negociação enquanto prever que a média móvel atingirá nosso stop loss; caso contrário, nossa aplicação estará autorizada a executar suas negociações. 

Essa é a essência da nossa solução. Ela é claramente definida do início ao fim e é baseada em princípios sólidos e raciocínio objetivo. Observe que podemos ser ainda mais específicos e exigir que, além de não esperar que nosso stop loss seja atingido, nosso computador também deva prever que a média móvel ultrapassará nosso nível de take profit antes de executar qualquer negociação. Caso contrário, por que alguém realizaria uma negociação se não tivesse motivo para acreditar que sua ordem de take profit será executada?

Para começar, precisamos primeiro coletar os dados de mercado relevantes do nosso terminal MetaTrader 5 utilizando um script em MQL5. 

//+------------------------------------------------------------------+
//|                                                      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 14                //--- Moving Average Period
#define MA_TYPE   MODE_EMA          //--- Type of moving average we have
#define MA_PRICE PRICE_CLOSE        //---- Applied Price of Moving Average

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

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

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

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

//+------------------------------------------------------------------+
//| Our script execution                                             |
//+------------------------------------------------------------------+
void OnStart()
  {
//---Setup our technical indicators
   ma_handle = iMA(_Symbol,PERIOD_M30,MA_PERIOD,0,MA_TYPE,MA_PRICE);

//---Set the values as series
   CopyBuffer(ma_handle,0,0,size,ma_reading);
   ArraySetAsSeries(ma_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","Open","High","Low","Close","MA 14");
        }

      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),
                   ma_reading[i]);
        }
     }
//--- Close the file
   FileClose(file_handle);
  }
//+------------------------------------------------------------------+


Analisando Nossos Dados em Python

Após aplicar o script ao mercado de sua escolha, podemos começar a analisar nossos dados financeiros utilizando bibliotecas Python. Nosso objetivo é construir uma rede neural que nos ajude a prever o valor futuro da média móvel e, potencialmente, nos manter fora de negociações perdedoras. 

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

Agora, leia os dados que extraímos do nosso terminal.

data = pd.read_csv("EURUSD Stop Out Prevention Market Data.csv")
data

Rotule os dados.

LOOK_AHEAD = 48
data['Target'] = data['MA 14'].shift(-LOOK_AHEAD)
data.dropna(inplace=True)
data.reset_index(drop=True,inplace=True)

Remova os períodos que se sobrepõem ao nosso backtest.

#Let's entirely drop off the last 2 years of data
data.iloc[-((48 * 365 * 2) + (48 * 31 * 2) + (48 * 14) - (3)):,:]

Substitua os dados originais de mercado pelos novos dados que não contêm as observações do período de backtest.

#Let's entirely drop off the last 2 years of data
_ = data.iloc[-((48 * 365 * 2) + (48 * 31 * 2) + (48 * 14) - (3)):,:]
data = data.iloc[:-((48 * 365 * 2) + (48 * 31 * 2) + (48 * 14) - (3)),:]
data

Agora carregue nossas bibliotecas de machine learning.

from sklearn.neural_network import MLPRegressor
from sklearn.model_selection import train_test_split,TimeSeriesSplit,cross_val_score
Crie um objeto de divisão de série temporal para que possamos validar cruzadamente nosso modelo de forma rápida.
tscv = TimeSeriesSplit(n_splits=5,gap=LOOK_AHEAD)

Especifique as entradas e o alvo.

X = data.columns[1:-1]
y = data.columns[-1:]
Divida os dados ao meio para treinar e testar o novo modelo.

train , test = train_test_split(data,test_size=0.5,shuffle=False)

Prepare as partições de treino e teste para serem normalizadas e escaladas.

train_X = train.loc[:,X]
train_y = train.loc[:,y]

test_X = test.loc[:,X]
test_y = test.loc[:,y]

Calcule os parâmetros para nossos z-scores.

mean_scores = train_X.mean()
std_scores = train_X.std()
Normalize os dados de entrada do modelo.
train_X = ((train_X - mean_scores) / std_scores)
test_X = ((test_X - mean_scores) / std_scores)

Queremos realizar uma busca em linha pelo número ideal de iterações de treinamento para nossa rede neural profunda. Iteraremos por potências crescentes de 2. Começando de 2 elevado a 0 até 2 elevado a 14.

MAX_POWER = 15
results = pd.DataFrame(index=["Train","Test"],columns=[np.arange(0,MAX_POWER)])

Defina um loop for que nos ajudará a estimar o número ideal de iterações de treinamento necessárias para ajustar nosso modelo de rede neural profunda aos dados que temos.

#Classical Inputs
for i in np.arange(0,MAX_POWER):
    print(i)
    model = MLPRegressor(hidden_layer_sizes=(5,10,4,2),solver="adam",activation="relu",max_iter=(2**i),early_stopping=False)
    results.iloc[0,i] = np.mean(np.abs(cross_val_score(model,train_X.loc[:,:],train_y.values.ravel(),cv=tscv)))
    results.iloc[1,i] = np.mean(np.abs(cross_val_score(model,test_X.loc[:,:],test_y.values.ravel(),cv=tscv)))
    results
  0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 
 Treinar 19675.492496 19765.297106
19609.7644 19511.588484
19859.734807
19942.30371
18831.617167
10703.554068
 4930.771654
1639.952482
1389.615052 2938.371438
1.536765 2.193895
30.553918
 Test 13171.519137 14113.252994 14428.159203 13649.157525 13655.643066 12919.773346 11472.770729 5878.964564 11293.444345
3788.388634  2545.368419 3599.364028 2240.598518
1041.641869  882.696622

A visualização dos dados mostra que precisamos utilizar o número máximo de iterações para obter o resultado ideal do modelo. No entanto, o leitor deve manter a mente aberta para a possibilidade de que nosso procedimento de busca possa ter sido encerrado prematuramente. Isso significa que poderíamos ter obtido melhores resultados se tivéssemos utilizado potências de 2 superiores a 14. Mas, devido ao custo computacional de treinar esses modelos, nossa busca não ultrapassou 2 elevado a 14.

plt.title("Neural Network RMSE Forecasting 14 Period MA")
plt.ylabel("5 CV RMSE")
plt.xlabel("Training Iterations As Powers of 2")
plt.grid()
sns.lineplot(np.array(results.iloc[1,:]).transpose())
plt.axhline(results.min(1)[1],linestyle='--',color='red')
plt.axvline(14,linestyle='--',color='red')

Fig 8: Resultados da busca pelo número ideal de iterações de treinamento para nosso modelo de rede neural profunda

Agora que nosso modelo foi treinado, podemos nos preparar para exportá-lo no formato ONNX.

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

Prepare-se para ajustar o modelo utilizando o número ideal de iterações de treinamento estimado.

model = MLPRegressor(hidden_layer_sizes=(5,10,4,2),solver="adam",activation="relu",max_iter=(2**14),early_stopping=False)

Carregue os z-scores para todo o conjunto de dados.

mean_scores = data.loc[:,X].mean()
std_scores = data.loc[:,X].std()

mean_scores.to_csv("EURUSD StopOut Mean.csv")
std_scores.to_csv("EURUSD StopOut Std.csv")

Transforme todo o conjunto de dados.

data[X] = ((data.loc[:,X] - mean_scores) / std_scores)

Ajuste o modelo utilizando todos os dados disponíveis, excluindo as datas de teste.

model.fit(data.loc[:,X],data.loc[:,'Target'].values.ravel())

Especifique o formato de entrada do modelo.

initial_types = [("float_input",FloatTensorType([1,5]))]

Prepare-se para converter o modelo para o formato ONNX.

model_proto = skl2onnx.convert_sklearn(model,initial_types=initial_types,target_opset=12)

Salve o modelo como um arquivo ONNX.

onnx.save(model_proto,"EURUSD StopOut Prevention Model.onnx")


Construindo Uma Versão Refinada da Nossa Estratégia

Vamos começar a construir a nova versão refinada da nossa estratégia de negociação. Primeiro, carregue o modelo ONNX que acabamos de criar.

//+------------------------------------------------------------------+
//|                                               Baseline Model.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"

//+------------------------------------------------------------------+
//| Resources                                                        |
//+------------------------------------------------------------------+
#resource "\\Files\\EURUSD StopOut Prevention Model.onnx" as uchar onnx_model_buffer[];

Criaremos algumas constantes adicionais do sistema para esta versão da aplicação.

//+------------------------------------------------------------------+
//| System constants                                                 |
//+------------------------------------------------------------------+
#define MA_PERIOD 14                 //--- Moving Average Period
#define MA_TYPE   MODE_EMA           //--- Type of moving average we have
#define MA_PRICE PRICE_CLOSE         //---- Applied Price of Moving Average
#define TF_1 PERIOD_D1               //--- Our time frame for technical analysis
#define TF_2 PERIOD_M30              //--- Our time frame for managing positions
#define VOL 0.1                      //--- Our trading volume
#define SL_SIZE  1e3 * _Point        //--- The size of our stop loss
#define SL_ADJUSTMENT 1e-5 * _Point  //--- The step size for our trailing stop
#define ONNX_MODEL_INPUTS 5          //---- Total model inputs for our ONNX model

Além disso, nossos z-scores globais devem ser carregados em arrays.

//+------------------------------------------------------------------+
//| Our global variables                                             |
//+------------------------------------------------------------------+
int     ma_handler,system_state;
double  ma[];
double  mean_values[ONNX_MODEL_INPUTS]  = {1.157641086508574,1.1581085911361018,1.1571729541088953,1.1576420747040126,1.157640521193191};
double  std_values[ONNX_MODEL_INPUTS]   = {0.04070388112283021,0.040730761156963606,0.04067819202368064,0.040703752648947544,0.040684857239172416};
double  bid,ask,yesterday_high,yesterday_low;
const   string last_high = "LAST_HIGH";
const   string last_low  = "LAST_LOW";
long    onnx_model;
vectorf model_forecast = vectorf::Zeros(1);

Antes de utilizar nossos modelos ONNX, precisamos configurá-los corretamente e verificar se foram inicializados adequadamente.

//+------------------------------------------------------------------+
//| Prepare the resources our EA requires                            |
//+------------------------------------------------------------------+
bool setup(void)
  {
   onnx_model = OnnxCreateFromBuffer(onnx_model_buffer,ONNX_DEFAULT);

   if(onnx_model == INVALID_HANDLE)
     {
      Comment("Failed to create ONNX model: ",GetLastError());
      return(false);
     }

   ulong input_shape[] = {1,ONNX_MODEL_INPUTS};
   ulong output_shape[] = {1,1};

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

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

   ma_handler    = iMA(Symbol(),TF_2,MA_PERIOD,0,MA_TYPE,MA_PRICE);

   if(ma_handler == INVALID_HANDLE)
     {
      Comment("Failed to load technical indicator: ",GetLastError());
      return(false);
     }

   return(true);
  };

Nosso procedimento para encontrar uma oportunidade de negociação mudará ligeiramente. Primeiro, obteremos uma previsão do nosso modelo. Depois disso, nossas condições para abrir e fechar posições permanecem as mesmas. No entanto, além dessas condições serem atendidas, também verificaremos novas condições adicionais. 

//+------------------------------------------------------------------+
//| Check if we have any trading setups                              |
//+------------------------------------------------------------------+
void find_setup(void)
  {
   if(!model_predict())
     {
      Comment("Failed to get a forecast from our model");
      return;
     }

   if((iHigh(Symbol(),TF_2,1) < yesterday_low) && (iHigh(Symbol(),TF_2,2) < yesterday_low))
     {
      if(iClose(Symbol(),TF_2,1) > ma[0])
        {
         check_buy();
        }
     }

   if((iLow(Symbol(),TF_2,1) > yesterday_high) && (iLow(Symbol(),TF_2,2) > yesterday_high))
     {
      if(iClose(Symbol(),TF_2,1) < ma[0])
        {
         check_sell();
        }
     }
  }

As novas condições se aplicarão tanto às posições compradas quanto às vendidas. Primeiro, verificaremos se nossa previsão da média móvel é maior que a leitura atual da média móvel disponível. Além disso, também verificaremos se o valor futuro esperado da média móvel é maior que o preço atual ofertado.

Isso significa que nosso computador suspeita que a tendência provavelmente continuará se movendo em uma direção. Por fim, verificaremos se o computador espera que a média móvel permaneça abaixo do stop loss. Se todas as condições forem atendidas, abriremos uma posição no mercado imediatamente.

//+------------------------------------------------------------------+
//| Check if we have a valid buy setup                               |
//+------------------------------------------------------------------+
void check_buy(void)
  {
   if((model_forecast[0] > ma[0]) && (model_forecast[0] > iClose(Symbol(),TF_2,0)))
     {
      if(model_forecast[0] > (bid - (SL_SIZE)))
         Trade.Buy(VOL,Symbol(),ask,(bid - (SL_SIZE)),(bid + (SL_SIZE)));
     }
  }

Nossas condições para abrir posições vendidas serão as mesmas especificadas para posições compradas, porém funcionarão na ordem inversa.

//+------------------------------------------------------------------+
//| Check if we have a valid sell setup                              |
//+------------------------------------------------------------------+
void check_sell(void)
  {
   if((model_forecast[0] < ma[0]) && (model_forecast[0] < iClose(Symbol(),TF_2,0)))
     {
      if(model_forecast[0] < (ask + (SL_SIZE)))
         Trade.Sell(VOL,Symbol(),bid,(ask + (SL_SIZE)),(ask - (SL_SIZE)));
     }
  }

Após abrir uma posição, devemos continuar monitorando-a. Nossa função de atualização do stop loss servirá a dois propósitos, dependendo de como for chamada. Ela recebe um parâmetro flag que modifica seu comportamento. Se o flag estiver definido como 0, estaremos apenas buscando uma oportunidade para mover nossos níveis de stop para preços mais favoráveis. Caso contrário, se o flag estiver definido como 1, primeiro obteremos uma nova previsão do modelo e verificaremos se o valor futuro da média móvel pode ultrapassar nosso nível atual de stop loss. 

Se a média móvel for prevista para ultrapassar nosso stop loss, mas ainda formar um movimento lucrativo, ajustaremos o stop loss para o nível que esperamos que a média móvel atinja em seu pico. Caso contrário, se a negociação for prevista para cair abaixo de seu preço de abertura, instruiremos o computador a arriscar menos em negociações que apresentem pouco potencial de lucro.

//+------------------------------------------------------------------+
//| Update our stop loss                                             |
//+------------------------------------------------------------------+
void update_sl(int flag)
  {
   //--- First find our open position
   if(PositionSelect(Symbol()))
     {
      double current_sl = PositionGetDouble(POSITION_SL);
      double current_tp = PositionGetDouble(POSITION_TP);
      double open_price = PositionGetDouble(POSITION_PRICE_OPEN);

      //--- Flag 0 means we just want to push the stop loss and take profit forward if its possible
      if(flag == 0)
        {
         //--- Buy Setup
         if(current_tp > current_sl)
           {
            if((bid - SL_SIZE) > current_sl)
               Trade.PositionModify(Symbol(),(bid - SL_SIZE),(bid + SL_SIZE));
           }

         //--- Sell setup
         if(current_tp < current_sl)
           {
            if((ask + SL_SIZE) < current_sl)
               Trade.PositionModify(Symbol(),(ask + SL_SIZE),(ask - SL_SIZE));
           }
        }

      //--- Flag 1 means we want to check if the stop loss may be hit soon, and act accordingly
      if(flag == 1)
        {
         model_predict();

         //--- Buy setup
         if(current_tp > current_sl)
           {
            
            if(model_forecast[0] < current_sl)
              {
               if((model_forecast[0] > ma[0]) && (model_forecast[0] > yesterday_low))
                  Trade.PositionModify(Symbol(),model_forecast[0],current_tp);
              }

            if(model_forecast[0] < open_price)
               Trade.PositionModify(Symbol(),model_forecast[0] * 1.5,current_tp);
           }

         //--- Sell setup
         if(current_tp < current_sl)
           {
            if(model_forecast[0] > current_sl)
              {
               if((model_forecast[0] < ma[0]) && (model_forecast[0] < yesterday_high))
                  Trade.PositionModify(Symbol(),model_forecast[0],current_tp);
              }

            if(model_forecast[0] > open_price)
               Trade.PositionModify(Symbol(),model_forecast[0] * 0.5,current_tp);
           }
        }
     }
  }

Nosso procedimento de atualização foi levemente modificado para chamar a função de atualização do stop loss.

//+------------------------------------------------------------------+
//| Perform our update routines                                      |
//+------------------------------------------------------------------+
void update()
  {
//--- Daily procedures
     {
      static datetime time_stamp;
      datetime current_time = iTime(Symbol(),TF_1,0);
      if(time_stamp != current_time)
        {
         yesterday_high = iHigh(Symbol(),TF_1,1);
         yesterday_low = iLow(Symbol(),TF_1,1);
         //--- Mark yesterday's levels
         ObjectDelete(0,last_high);
         ObjectDelete(0,last_low);
         ObjectCreate(0,last_high,OBJ_HLINE,0,0,yesterday_high);
         ObjectCreate(0,last_low,OBJ_HLINE,0,0,yesterday_low);
        }
     }
//--- M30 procedures
     {
      static datetime time_stamp;
      datetime current_time = iTime(Symbol(),TF_2,0);
      if(time_stamp != current_time)
        {
         time_stamp = current_time;
         //--- Get updated prices
         bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);
         ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);
         //--- Update our technical indicators
         CopyBuffer(ma_handler,0,0,1,ma);
         //--- Check for a setup
         if(PositionsTotal()==0)
            find_setup();

         //--- Check for a setup
         if(PositionsTotal() > 0)
            update_sl(1);
        }
     }
//--- Per tick procedures
     {
      //--- These function calls can become expensive and may slow down the speed of your back tests
      //--- Be thoughtful when placing any function calls in this scope
      update_sl(0);
     }
  }

Também precisamos de uma função dedicada responsável por obter previsões do nosso modelo de rede neural. Primeiro, prepararemos as entradas em um vetor do tipo float e, em seguida, padronizaremos as entradas para obter uma previsão do modelo.

//+------------------------------------------------------------------+
//| Get a forecast from our deep neural network                      |
//+------------------------------------------------------------------+
bool model_predict(void)
  {
   double ma_input[] = {0};
   CopyBuffer(ma_handler,0,1,1,ma_input);
   vectorf model_inputs =
     {
      (float) iOpen(Symbol(),TF_2,1),
      (float) iHigh(Symbol(),TF_2,1),
      (float) iLow(Symbol(),TF_2,1),
      (float) iClose(Symbol(),TF_2,1),
      (float) ma_input[0]
     };

   for(int i = 0; i < ONNX_MODEL_INPUTS;i++)
     {
      model_inputs[i] = (float)((model_inputs[i] - mean_values[i]) / std_values[i]);
     }

   if(!OnnxRun(onnx_model,ONNX_DEFAULT,model_inputs,model_forecast))
     {
      Comment("Failed to obtain forecast: ",GetLastError());
      return(false);
     }

   Comment(StringFormat("Expected MA Value: %f",model_forecast[0]));
   return(true);
  }

Por fim, quando nossa aplicação não estiver mais em uso, liberaremos o indicador e o modelo ONNX.

//+------------------------------------------------------------------+
//| Free resources we are no longer using up                         |
//+------------------------------------------------------------------+
void release(void)
  {
   OnnxRelease(onnx_model);
   IndicatorRelease(ma_handler);
  }
//+------------------------------------------------------------------+

Ao analisarmos a curva de equity produzida pela nova versão refinada do nosso algoritmo de negociação, podemos observar rapidamente que a inclinação negativa característica da primeira implementação foi corrigida, e nossa estratégia agora apresenta uma tendência positiva, com quedas ocasionais. Isso é mais desejável do que o estado inicial da estratégia.

Fig 9: Visualizando a curva de lucro produzida pela nova versão refinada do nosso algoritmo de prevenção de stop out

Ao analisar mais detalhadamente, constatamos que nossa nova estratégia agora é lucrativa. A versão inicial perdeu aproximadamente $1000, enquanto a versão atual gerou pouco mais de $1000. Isso representa uma melhoria significativa. Nosso Sharpe ratio inicial era -0,39 e o novo Sharpe ratio é 0,79. O leitor também notará que o lucro médio por operação aumentou de $98 para $130, enquanto a perda média caiu de $102 para $63. Isso demonstra que nossos lucros médios estão crescendo a uma taxa significativamente maior do que nossas perdas médias. Esses indicadores nos fornecem expectativas positivas caso consideremos utilizar esta versão da estratégia.

Embora tenhamos feito progresso significativo, o problema de ser estopado ainda é difícil de resolver. Isso fica evidente pelo fato de que cerca de 60% de todas as posições abertas foram negociações perdedoras. É desafiador tentar filtrar completamente todas as negociações que levariam um trader a ser estopado; hoje conseguimos filtrar a maioria das negociações grandes e não lucrativas.

Fig 10: Uma análise detalhada dos resultados obtidos utilizando nosso novo algoritmo de prevenção de stop out



Conclusão

Neste artigo, conduzimos o leitor por uma possível solução para o problema persistente de ser estopado em negociações vencedoras. Esse problema está no cerne do sucesso no trading e pode nunca ser completamente resolvido. Cada nova solução introduz seu próprio conjunto de vulnerabilidades na estratégia. Após a leitura deste artigo, o leitor passa a contar com uma estrutura mais quantitativa para gerenciar seus níveis de stop loss. Identificar e filtrar negociações que desnecessariamente geram drawdown na sua conta é um componente crucial de qualquer estratégia de negociação. 

Nome do Arquivo Descrição dos Arquivos
Baseline Model.mq5 Nossa estratégia de negociação original que buscamos superar.
Stop Out Prevention Model.mq5 Nossa versão refinada da estratégia de negociação, impulsionada por uma rede neural profunda.
EURUSD Stop Out Moving Average Model.ipynb O Jupyter Notebook que utilizamos para analisar os dados financeiros extraídos do nosso terminal MetaTrader 5.
EURUSD Stop Out Prevention Model.onnx Nossa Rede Neural Profunda.
Fetch Data MA.mq5 O script em MQL5 que utilizamos para coletar os dados de mercado necessários.

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

Automatizando Estratégias de Trading em MQL5 (Parte 8): Construindo um Expert Advisor com Padrões Harmônicos Butterfly Automatizando Estratégias de Trading em MQL5 (Parte 8): Construindo um Expert Advisor com Padrões Harmônicos Butterfly
Neste artigo, construímos um Expert Advisor em MQL5 para detectar padrões harmônicos Butterfly. Identificamos pontos de pivô e validamos níveis de Fibonacci para confirmar o padrão. Em seguida, visualizamos o padrão no gráfico e executamos negociações automaticamente quando confirmado.
Desenvolvimento do Kit de Ferramentas de Análise de Ação de Preço (Parte 14): Ferramenta de Stop Parabolic e Reversão Desenvolvimento do Kit de Ferramentas de Análise de Ação de Preço (Parte 14): Ferramenta de Stop Parabolic e Reversão
Adotar indicadores técnicos na análise de price action é uma abordagem poderosa. Esses indicadores frequentemente destacam níveis-chave de reversões e retrações, oferecendo insights valiosos sobre a dinâmica do mercado. Neste artigo, demonstramos como desenvolvemos uma ferramenta automatizada que gera sinais utilizando o indicador Parabolic SAR.
Automatizando Estratégias de Trading em MQL5 (Parte 9): Construindo um Expert Advisor para a Estratégia Asian Breakout Automatizando Estratégias de Trading em MQL5 (Parte 9): Construindo um Expert Advisor para a Estratégia Asian Breakout
Neste artigo, construímos um Expert Advisor em MQL5 para a Estratégia Asian Breakout calculando a máxima e a mínima da sessão e aplicando filtragem de tendência com uma média móvel. Implementamos estilização dinâmica de objetos, entradas de tempo definidas pelo usuário e gestão de risco robusta. Por fim, demonstramos técnicas de backtesting e otimização para refinar o programa.
Algoritmo de ecolocalização de golfinhos — Dolphin Echolocation Algorithm (DEA) Algoritmo de ecolocalização de golfinhos — Dolphin Echolocation Algorithm (DEA)
Neste artigo, analisaremos detalhadamente o algoritmo DEA, um método metaheurístico de otimização inspirado na capacidade única dos golfinhos de encontrar presas por meio da ecolocalização. Das bases matemáticas à implementação prática em MQL5, da análise à comparação com algoritmos clássicos, vamos examinar minuciosamente por que esse método relativamente jovem merece um lugar no arsenal de quem enfrenta tarefas de otimização.