Análise pós-fato da negociação: ajustando TrailingStop e novos stops no testador de estratégias
Conteúdo
- Introdução
- Escolhendo e aprimorando a classe de trailing das posições
- Testando diferentes tipos de TrailingStop
- Considerações finais
Introdução
No último artigo, criamos um EA que opera no testador de estratégias do terminal cliente com base nos resultados da negociação em conta real. Adicionamos a possibilidade de definir novos valores de StopLoss e TakeProfit para testar a própria negociação no testador com tamanhos diferentes desses stops. O resultado foi inesperado: em vez de prejuízo, obteve-se lucro, equivalente ao prejuízo real anteriormente registrado. Isso significa que, se tivéssemos usado níveis de StopLoss e TakeProfit como os que deram lucro no testador, também teríamos tido lucro na conta real. E tudo isso com uma simples alteração no tamanho dos stops. Fica então a curiosidade: e se adicionarmos um trailing ao StopLoss? Como isso mudaria a negociação?
Hoje vamos integrar ao EA diferentes métodos de trailing e testar nossa negociação no testador de estratégias. Vamos ver qual será o resultado obtido.
Escolhendo e aprimorando a classe de trailing das posições
Vamos tentar integrar um trailing baseado no indicador Parabolic SAR e na média móvel. O indicador Parabolic SAR, de acordo com sua descrição e pelo próprio nome (SAR = Stop And Reverse), deve se encaixar perfeitamente no deslocamento da linha de stop com base em seus valores:
O indicador técnico Sistema Parabólico SAR (Parabolic SAR) foi desenvolvido para análise de mercados com tendência. O indicador é desenhado no gráfico de preços. Em essência, é semelhante à média móvel, com a diferença de que o Parabolic SAR se move com maior aceleração e pode mudar de posição em relação ao preço. Em uma tendência de alta (Up Trend), o indicador fica abaixo dos preços, em uma tendência de baixa (Down Trend), fica acima.
Se o preço cruza a linha do Parabolic SAR, ocorre a reversão do indicador, e os próximos valores passam a ficar do outro lado do preço. Nesse ponto de "reversão", é usada como referência a máxima ou mínima do período anterior. A reversão do indicador é um sinal de que a tendência terminou (ou entrou em correção ou lateralização), ou de que houve inversão da tendência.
Parabolic SAR define de forma excelente os pontos de saída do mercado. Posições longas devem ser fechadas quando o preço cai abaixo da linha do indicador técnico, e posições curtas, quando o preço sobe acima da linha do Parabolic SAR. Ou seja, é necessário acompanhar a direção do movimento do Parabolic SAR e manter abertas no mercado apenas as posições que estejam na direção desse movimento. Frequentemente, esse indicador é utilizado como linha de stop móvel (trailing stop).
Se estiver aberta uma posição longa (ou seja, o preço está acima da linha do Parabolic SAR), então a linha do indicador continuará subindo, independentemente da direção dos preços. O quanto a linha do Parabolic SAR se desloca depende da magnitude do movimento de preço.
A média móvel, devido ao seu atraso natural, também é adequada para mover a linha do stop conforme o preço. Para uma posição longa, se a linha do indicador estiver abaixo do preço, é possível ajustar o stop para cima conforme o valor da média móvel. O preço cruzará a média móvel de forma natural, e o stop será acionado. Para uma posição curta, o stop deve seguir a linha da média móvel caso o preço esteja abaixo dela.
Já publiquei anteriormente um artigo sobre como integrar qualquer tipo de trailing aos EAs: "Como fazer qualquer tipo de Trailing Stop e conectar ao EA". Nele, é proposto criar classes de trailing e simplesmente conectá-las ao EA.
Vamos aproveitar essa abordagem. A única coisa que não se encaixa muito bem aqui é o fato de que, no artigo, cada símbolo ou magic number executa seu próprio laço de iteração sobre as posições abertas. Sendo assim, precisaremos modificar a classe apresentada no artigo. Felizmente, isso não é difícil.
Vamos carregar, a partir dos arquivos anexos ao artigo, o único arquivo que nos interessa: Trailings.mqh. No artigo anterior, criamos uma pasta onde colocamos os arquivos de classes e EAs para negociação baseada no histórico de ordens: \MQL5\ExpertsTradingByHistoryDeals. Salvamos o arquivo Trailings.mqh nesta pasta e o abrimos no editor de código MetaEditor.
O método principal e mais importante, chamado a partir do programa, é o método Run(). Se observarmos seu código, veremos que aqui é realizada uma iteração sobre as posições abertas:
//+------------------------------------------------------------------+ //| Launch simple trailing with StopLoss offset from the price | //+------------------------------------------------------------------+ bool CSimpleTrailing::Run(void) { //--- if disabled, leave if(!this.m_active) return false; //--- trailing variables MqlTick tick = {}; // price structure bool res = true; // result of modification of all positions //--- check the correctness of the data by symbol if(this.m_point==0) { //--- let's try to get the data again ::ResetLastError(); this.SetSymbol(this.m_symbol); if(this.m_point==0) { ::PrintFormat("%s: Correct data was not received for the %s symbol. Error %d",__FUNCTION__, this.m_symbol, ::GetLastError()); return false; } } //--- in a loop by the total number of open positions int total =::PositionsTotal(); for(int i = total - 1; i >= 0; i--) { //--- get the ticket of the next position ulong pos_ticket =::PositionGetTicket(i); if(pos_ticket == 0) continue; //--- get the symbol and position magic string pos_symbol = ::PositionGetString(POSITION_SYMBOL); long pos_magic = ::PositionGetInteger(POSITION_MAGIC); //--- if the position does not match the filter by symbol and magic number, leave if((this.m_magic != -1 && pos_magic != this.m_magic) || (pos_symbol != this.m_symbol)) continue; //--- if failed to get the prices, move on if(!::SymbolInfoTick(this.m_symbol, tick)) continue; //--- get the position type, its opening price and StopLoss level ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)::PositionGetInteger(POSITION_TYPE); double pos_open =::PositionGetDouble(POSITION_PRICE_OPEN); double pos_sl =::PositionGetDouble(POSITION_SL); //--- get the calculated StopLoss level double value_sl = this.GetStopLossValue(pos_type, tick); //--- if StopLoss modification conditions are suitable, modify the position stop level and add the result to the res variable if(this.CheckCriterion(pos_type, pos_open, pos_sl, value_sl, tick)) res &=this.ModifySL(pos_ticket, value_sl); } //--- at the end of the loop, return the result of modifying each position that matches the "symbol/magic" filter return res; }
Se para cada objeto de negociação por símbolo (ver o primeiro artigo) chamarmos esse método, teremos tantos laços de iteração de posições abertas quanto símbolos utilizados na negociação. Isso não é eficiente. Vamos fazer com que o próprio programa tenha um laço de iteração sobre todas as posições abertas, e que esses métodos sejam chamados a partir desse laço. Porém, o método também contém um laço embutido... Portanto, precisaremos criar na classe de trailing um outro método Run(), que aceite o ticket da posição cujo stop deverá ser ajustado, e chamaremos esse novo método a partir do laço principal.
Na classe do trailing simples, declaramos um novo método, que receberá como argumento o ticket da posição desejada:
//+------------------------------------------------------------------+ //| Class of the position StopLoss simple trailing | //+------------------------------------------------------------------+ class CSimpleTrailing : public CObject { private: //--- check the criteria for modifying the StopLoss position and return the flag bool CheckCriterion(ENUM_POSITION_TYPE pos_type, double pos_open, double pos_sl, double value_sl, MqlTick &tick); //--- modify StopLoss of a position by its ticket bool ModifySL(const ulong ticket, const double stop_loss); //--- return StopLevel in points int StopLevel(void); protected: string m_symbol; // trading symbol long m_magic; // EA ID double m_point; // Symbol Point int m_digits; // Symbol digits int m_offset; // stop distance from price int m_trail_start; // profit in points for launching trailing uint m_trail_step; // trailing step uint m_spread_mlt; // spread multiplier for returning StopLevel value bool m_active; // trailing activity flag //--- calculate and return the StopLoss level of the selected position virtual double GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick); public: //--- set trailing parameters void SetSymbol(const string symbol) { this.m_symbol = (symbol==NULL || symbol=="" ? ::Symbol() : symbol); this.m_point =::SymbolInfoDouble(this.m_symbol, SYMBOL_POINT); this.m_digits = (int)::SymbolInfoInteger(this.m_symbol, SYMBOL_DIGITS); } void SetMagicNumber(const long magic) { this.m_magic = magic; } void SetStopLossOffset(const int offset) { this.m_offset = offset; } void SetTrailingStart(const int start) { this.m_trail_start = start; } void SetTrailingStep(const uint step) { this.m_trail_step = step; } void SetSpreadMultiplier(const uint value) { this.m_spread_mlt = value; } void SetActive(const bool flag) { this.m_active = flag; } //--- return trailing parameters string Symbol(void) const { return this.m_symbol; } long MagicNumber(void) const { return this.m_magic; } int StopLossOffset(void) const { return this.m_offset; } int TrailingStart(void) const { return this.m_trail_start; } uint TrailingStep(void) const { return this.m_trail_step; } uint SpreadMultiplier(void) const { return this.m_spread_mlt; } bool IsActive(void) const { return this.m_active; } //--- launch trailing with StopLoss offset from the price bool Run(void); bool Run(const ulong pos_ticket); //--- constructors CSimpleTrailing() : m_symbol(::Symbol()), m_point(::Point()), m_digits(::Digits()), m_magic(-1), m_trail_start(0), m_trail_step(0), m_offset(0), m_spread_mlt(2) {} CSimpleTrailing(const string symbol, const long magic, const int trailing_start, const uint trailing_step, const int offset); //--- destructor ~CSimpleTrailing() {} };
Fora do corpo da classe, escreveremos sua implementação:
//+------------------------------------------------------------------+ //| Launch simple trailing with StopLoss offset from the price | //+------------------------------------------------------------------+ bool CSimpleTrailing::Run(const ulong pos_ticket) { //--- if trailing is disabled, or the ticket is invalid, we leave if(!this.m_active || pos_ticket==0) return false; //--- trailing variables MqlTick tick = {}; // price structure //--- check the correctness of the data by symbol ::ResetLastError(); if(this.m_point==0) { //--- let's try to get the data again this.SetSymbol(this.m_symbol); if(this.m_point==0) { ::PrintFormat("%s: Correct data was not received for the %s symbol. Error %d",__FUNCTION__, this.m_symbol, ::GetLastError()); return false; } } //--- select a position by ticket if(!::PositionSelectByTicket(pos_ticket)) { ::PrintFormat("%s: PositionSelectByTicket(%I64u) failed. Error %d",__FUNCTION__, pos_ticket, ::GetLastError()); return false; } //--- if prices could not be obtained, return 'false' if(!::SymbolInfoTick(this.m_symbol, tick)) return false; //--- get the position type, its opening price and StopLoss level ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)::PositionGetInteger(POSITION_TYPE); double pos_open =::PositionGetDouble(POSITION_PRICE_OPEN); double pos_sl =::PositionGetDouble(POSITION_SL); //--- get the calculated StopLoss level double value_sl = this.GetStopLossValue(pos_type, tick); //--- if the conditions for modifying StopLoss are suitable, return the result of modifying the position stop if(this.CheckCriterion(pos_type, pos_open, pos_sl, value_sl, tick)) return(this.ModifySL(pos_ticket, value_sl)); //--- conditions for modification are not suitable return false; }
Agora, esse método poderá ser chamado tanto a partir do método Run(), que não possui parâmetros formais mas contém o laço de iteração sobre todas as posições abertas, quanto diretamente pelo programa, passando-se o ticket necessário.
Vamos ajustar o método Run() sem parâmetros formais. Removeremos do laço o bloco de código:
for(int i = total - 1; i >= 0; i--) { //--- get the ticket of the next position ulong pos_ticket =::PositionGetTicket(i); if(pos_ticket == 0) continue; //--- get the symbol and position magic string pos_symbol = ::PositionGetString(POSITION_SYMBOL); long pos_magic = ::PositionGetInteger(POSITION_MAGIC); //--- if the position does not match the filter by symbol and magic number, leave if((this.m_magic != -1 && pos_magic != this.m_magic) || (pos_symbol != this.m_symbol)) continue; //--- if failed to get the prices, move on if(!::SymbolInfoTick(this.m_symbol, tick)) continue; //--- get the position type, its opening price and StopLoss level ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)::PositionGetInteger(POSITION_TYPE); double pos_open =::PositionGetDouble(POSITION_PRICE_OPEN); double pos_sl =::PositionGetDouble(POSITION_SL); //--- get the calculated StopLoss level double value_sl = this.GetStopLossValue(pos_type, tick); //--- if StopLoss modification conditions are suitable, modify the position stop level and add the result to the res variable if(this.CheckCriterion(pos_type, pos_open, pos_sl, value_sl, tick)) res &=this.ModifySL(pos_ticket, value_sl); }
Em seu lugar, incluiremos a chamada do novo método:
//+------------------------------------------------------------------+ //| Launch simple trailing with StopLoss offset from the price | //+------------------------------------------------------------------+ bool CSimpleTrailing::Run(void) { //--- if disabled, leave if(!this.m_active) return false; //--- trailing variables bool res = true; // result of modification of all positions //--- check the correctness of the data by symbol if(this.m_point==0) { //--- let's try to get the data again ::ResetLastError(); this.SetSymbol(this.m_symbol); if(this.m_point==0) { ::PrintFormat("%s: Correct data was not received for the %s symbol. Error %d",__FUNCTION__, this.m_symbol, ::GetLastError()); return false; } } //--- in a loop by the total number of open positions int total =::PositionsTotal(); for(int i = total - 1; i >= 0; i--) { //--- get the ticket of the next position ulong pos_ticket =::PositionGetTicket(i); if(pos_ticket == 0) continue; //--- get the symbol and position magic string pos_symbol = ::PositionGetString(POSITION_SYMBOL); long pos_magic = ::PositionGetInteger(POSITION_MAGIC); //--- if the position does not match the filter by symbol and magic number, leave if((this.m_magic != -1 && pos_magic != this.m_magic) || (pos_symbol != this.m_symbol)) continue; res &=this.Run(pos_ticket); } //--- at the end of the loop, return the result of modifying each position that matches the "symbol/magic" filter return res; }
Na classe de trailing baseada em valor específico, declaramos da mesma forma o novo método Run() com o argumento do ticket da posição:
//+------------------------------------------------------------------+ //| Trailing class based on a specified value | //+------------------------------------------------------------------+ class CTrailingByValue : public CSimpleTrailing { protected: double m_value_sl_long; // StopLoss level for long positions double m_value_sl_short; // StopLoss level for short positions //--- calculate and return the StopLoss level of the selected position virtual double GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick); public: //--- return StopLoss level for (2) long and (2) short positions double StopLossValueLong(void) const { return this.m_value_sl_long; } double StopLossValueShort(void) const { return this.m_value_sl_short; } //--- launch trailing with the specified StopLoss offset from the price bool Run(const double value_sl_long, double value_sl_short); bool Run(const ulong pos_ticket, const double value_sl_long, double value_sl_short); //--- constructors CTrailingByValue(void) : CSimpleTrailing(::Symbol(), -1, 0, 0, 0), m_value_sl_long(0), m_value_sl_short(0) {} CTrailingByValue(const string symbol, const long magic, const int trail_start, const uint trail_step, const int trail_offset) : CSimpleTrailing(symbol, magic, trail_start, trail_step, trail_offset), m_value_sl_long(0), m_value_sl_short(0) {} //--- destructor ~CTrailingByValue(void){} };
E fora do corpo da classe, escreveremos sua implementação:
//+------------------------------------------------------------------+ //| Launch trailing with the specified StopLoss offset from the price| //+------------------------------------------------------------------+ bool CTrailingByValue::Run(const ulong pos_ticket,const double value_sl_long,double value_sl_short) { this.m_value_sl_long =value_sl_long; this.m_value_sl_short=value_sl_short; return CSimpleTrailing::Run(pos_ticket); }
Aqui, é chamado o método Run() com o ticket da posição, que foi adicionado à classe de trailing simples.
É recomendável se familiarizar com as classes de trailing através do artigo, do qual extraímos o arquivo com os trailings.
A classe de negociação por símbolo, criada no artigo anterior, armazena as listas de ordens e a classe de negociação CTrade da Biblioteca Padrão. Para não alterá-la diretamente ao conectar as classes de trailing, vamos criar uma nova classe baseada nela, onde adicionaremos o controle dos trailings.
No diretório do terminal \MQL5\ExpertsTradingByHistoryDeals\ criaremos um novo arquivo SymbolTradeExt.mqh para a classe CSymbolTradeExt. Ao arquivo, devem ser conectados o arquivo das classes de trailing e o arquivo da classe CSymbolTrade, da qual nossa nova classe será herdada:
//+------------------------------------------------------------------+ //| SymbolTradeExt.mqh | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #include "Trailings.mqh" #include "SymbolTrade.mqh" class CSymbolTradeExt : public CSymbolTrade { }
Ao usar a classe de trailing tirada do artigo, teremos à disposição trailings baseados no Parabolic e em todos os tipos padrão de médias móveis. A classe também possui um trailing por valores informados manualmente, mas este não será usado aqui, pois exige que os níveis de stop sejam calculados diretamente no programa e passados como parâmetros ao método Run() da classe de trailing. Um exemplo seria calcular os níveis com base no indicador ATR e passar os valores calculados para a classe de trailing.
Vamos escrever uma enumeração dos modos de trailing:
//+------------------------------------------------------------------+ //| SymbolTradeExt.mqh | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #include "Trailings.mqh" #include "SymbolTrade.mqh" enum ENUM_TRAILING_MODE // Enumeration of trailing modes { TRAILING_MODE_SIMPLE=2, // Simple trailing TRAILING_MODE_SAR, // Trailing by Parabolic SAR TRAILING_MODE_AMA, // Trailing by adjustable moving average TRAILING_MODE_DEMA, // Trailing by double exponential moving average TRAILING_MODE_FRAMA, // Trailing by fractal adaptive moving average TRAILING_MODE_MA, // Trailing by simple moving average TRAILING_MODE_TEMA, // Trailing by triple exponential moving average TRAILING_MODE_VIDYA, // Trailing by moving average with dynamic averaging period }; class CSymbolTradeExt : public CSymbolTrade { }
Por que os valores das constantes da enumeração começam em 2, e não em zero? No EA que usaremos como base para o novo, também há uma enumeração de modos de teste:
enum ENUM_TESTING_MODE { TESTING_MODE_ORIGIN, /* Original trading */ TESTING_MODE_SLTP, /* Specified StopLoss and TakeProfit values */ };
Aqui existem duas constantes: negociação original (0) e negociação com valores definidos para ordens de stop (1). Mais adiante, adicionaremos novas constantes aqui, correspondentes às constantes da enumeração de trailing. Por isso, os valores das constantes do enum de trailing começam em 2.
Na seção privada da classe, vamos declarar um ponteiro para o objeto da classe de trailing e uma variável para armazenar o período do gráfico, que será usado no cálculo dos indicadores utilizados nos trailings. Na seção pública, declararemos o método para configurar os parâmetros do trailing, o método que inicia o trailing das posições, além dos construtores e do destrutor:
class CSymbolTradeExt : public CSymbolTrade { private: CSimpleTrailing *m_trailing; // Trailing class object ENUM_TIMEFRAMES m_timeframe; // Timeframe for calculating the indicator for trailing public: //--- Set trailing and its parameters bool SetTrailing(const ENUM_TRAILING_MODE trailing_mode, const int data_index, const long magic, const int start, const int step, const int offset, const MqlParam ¶m[]); //--- Start a trail of the position specified by the ticket void Trailing(const ulong pos_ticket); //--- Constructor/destructor CSymbolTradeExt() : m_trailing(NULL), m_timeframe(::Period()) { this.SetSymbol(::Symbol()); } CSymbolTradeExt(const string symbol, const ENUM_TIMEFRAMES timeframe); ~CSymbolTradeExt(); };
No construtor da classe, na linha de inicialização, o símbolo para o qual será criado o objeto é passado para o construtor da classe pai, o valor do timeframe para cálculo do indicador recebe o valor fornecido nos parâmetros formais, e o ponteiro para o objeto da classe de trailing é inicializado com NULL:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CSymbolTradeExt::CSymbolTradeExt(const string symbol, const ENUM_TIMEFRAMES timeframe) : CSymbolTrade(symbol) { this.m_trailing=NULL; this.m_timeframe=timeframe; }
No destrutor da classe, caso o objeto de trailing tenha sido criado, ele é deletado:
//+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CSymbolTradeExt::~CSymbolTradeExt() { //--- delete the created trailing object if(this.m_trailing!=NULL) delete this.m_trailing; }
Como diferentes tipos de trailing, baseados em diferentes tipos de indicadores, possuem parâmetros distintos entre si, todos esses parâmetros serão passados para o método de configuração de trailing por meio da estrutura MqlParam. Para cada tipo de trailing, o conjunto de parâmetros será diferente, e cada campo da estrutura conterá o valor do parâmetro correspondente ao indicador específico usado.
//+------------------------------------------------------------------+ //| Set trailing parameters | //+------------------------------------------------------------------+ bool CSymbolTradeExt::SetTrailing(const ENUM_TRAILING_MODE trailing_mode, const int data_index, const long magic, const int start, const int step, const int offset, const MqlParam ¶m[]) { //--- Set trailing parameters (only necessary structure fields are used for each indicator type) int ma_period = (int)param[0].integer_value; int ma_shift = (int)param[1].integer_value; ENUM_APPLIED_PRICE ma_price = (ENUM_APPLIED_PRICE)param[2].integer_value; ENUM_MA_METHOD ma_method = (ENUM_MA_METHOD)param[3].integer_value; int fast_ema = (int)param[4].integer_value; int slow_ema = (int)param[5].integer_value; int period_cmo = (int)param[6].integer_value; double sar_step = param[0].double_value; double sar_max = param[1].double_value; //--- depending on the trailing type, we create a trailing object //--- if the value passed as the calculation period is less than the allowed value, then each indicator is assigned its own default value switch(trailing_mode) { case TRAILING_MODE_SIMPLE : this.m_trailing=new CSimpleTrailing(this.Symbol(), magic, start, step, offset); break; case TRAILING_MODE_AMA : this.m_trailing=new CTrailingByAMA(this.Symbol(), this.m_timeframe, magic, (ma_period<1 ? 9 : ma_period), (fast_ema<1 ? 2 : fast_ema), (slow_ema<1 ? 30 : slow_ema), ma_shift, ma_price, start, step, offset); break; case TRAILING_MODE_DEMA : this.m_trailing=new CTrailingByDEMA(this.Symbol(), this.m_timeframe, magic, (ma_period==0 ? 14 : ma_period), ma_shift, ma_price, start, step, offset); break; case TRAILING_MODE_FRAMA : this.m_trailing=new CTrailingByFRAMA(this.Symbol(), this.m_timeframe, magic, (ma_period==0 ? 14 : ma_period), ma_shift, ma_price, start, step, offset); break; case TRAILING_MODE_MA : this.m_trailing=new CTrailingByMA(this.Symbol(), this.m_timeframe, magic, ma_period, (ma_period==0 ? 10 : ma_period), ma_method, ma_price, start, step, offset); break; case TRAILING_MODE_TEMA : this.m_trailing=new CTrailingByTEMA(this.Symbol(), this.m_timeframe, magic, (ma_period==0 ? 14 : ma_period), ma_shift, ma_price, start, step, offset); break; case TRAILING_MODE_VIDYA : this.m_trailing=new CTrailingByVIDYA(this.Symbol(), this.m_timeframe, magic, (period_cmo<1 ? 9 : period_cmo), (ma_period==0 ? 12 : ma_period), ma_shift, ma_price, start, step, offset); break; case TRAILING_MODE_SAR : this.m_trailing=new CTrailingBySAR(this.Symbol(), this.m_timeframe, magic, (sar_step<0.0001 ? 0.02 : sar_step), (sar_max<0.02 ? 0.2 : sar_max), start, step, offset); break; default : break; } //--- something went wrong - return 'false' if(this.m_trailing==NULL) return false; //--- all is well - make the trail active and return 'true' this.m_trailing.SetActive(true); return true; }
Ao chamar esse método para diferentes tipos de trailing, é importante preencher corretamente a estrutura MqlParam. Isso será tratado quando ajustarmos o EA para o uso com trailing.
Método que inicia o trailing da posição indicada por seu ticket:
//+------------------------------------------------------------------+ //| Start trailing of the position specified by the ticket | //+------------------------------------------------------------------+ void CSymbolTradeExt::Trailing(const ulong pos_ticket) { if(this.m_trailing!=NULL) this.m_trailing.Run(pos_ticket); }
A partir do EA, em seu laço sobre as posições abertas, dependendo do símbolo da posição, obteremos da lista o objeto de negociação do símbolo, e a partir dele chamaremos esse método para aplicar o trailing stop à posição, cujo ticket será passado como argumento. Se o objeto de trailing existir, seu novo método Run() será chamado com o ticket da posição.
Testando diferentes tipos de TrailingStop
Para o teste, vamos utilizar o arquivo do EA TradingByHistoryDeals_SLTP.mq5 da matéria anterior e salvá-lo na mesma pasta \MQL5\ExpertsTradingByHistoryDeals\ com um novo nome: TradingByHistoryDeals_Ext.mq5.
A inclusão do arquivo da classe CSymbolTrade será substituída pela inclusão do arquivo da classe CSymbolTradeExt, incluiremos também o arquivo das classes de trailing e ampliaremos a lista de constantes da enumeração dos modos de teste, adicionando a opção de escolha do tipo de trailing desejado:
//+------------------------------------------------------------------+ //| TradingByHistoryDeals_Ext.mq5 | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #include "SymbolTradeExt.mqh" #include "Trailings.mqh" enum ENUM_TESTING_MODE { TESTING_MODE_ORIGIN, /* Original trading */ TESTING_MODE_SLTP, /* Specified StopLoss and TakeProfit values */ TESTING_MODE_TRAIL_SIMPLE, /* Simple Trailing */ TESTING_MODE_TRAIL_SAR, /* Trailing by Parabolic SAR indicator */ TESTING_MODE_TRAIL_AMA, /* Trailing by AMA indicator */ TESTING_MODE_TRAIL_DEMA, /* Trailing by DEMA indicator */ TESTING_MODE_TRAIL_FRAMA, /* Trailing by FRAMA indicator */ TESTING_MODE_TRAIL_MA, /* Trailing by MA indicator */ TESTING_MODE_TRAIL_TEMA, /* Trailing by TEMA indicator */ TESTING_MODE_TRAIL_VIDYA, /* Trailing by VIDYA indicator */ }; //+------------------------------------------------------------------+ //| Expert | //+------------------------------------------------------------------+
Nos parâmetros de entrada do EA, adicionaremos variáveis para configuração dos parâmetros do trailing:
//+------------------------------------------------------------------+ //| Expert | //+------------------------------------------------------------------+ //--- input parameters input group " - Strategy parameters - " input string InpTestedSymbol = ""; /* The symbol being tested in the tester */ input long InpTestedMagic = -1; /* The magic number being tested in the tester */ sinput bool InpShowDataInLog = false; /* Show collected data in the log */ input group " - Stops parameters - " input ENUM_TESTING_MODE InpTestingMode = TESTING_MODE_ORIGIN; /* Testing Mode */ input int InpStopLoss = 300; /* StopLoss in points */ input int InpTakeProfit = 500; /* TakeProfit in points */ input group " - Trailing Parameters -" input bool InpSetStopLoss = true; /* Set Initial StopLoss */ input bool InpSetTakeProfit = true; /* Set Initial TakeProfit */ input int InpTrailingStart = 150; /* Trailing start */ // Profit in points to start trailing input int InpTrailingStep = 50; /* Trailing step in points */ input int InpTrailingOffset = 0; /* Trailing offset in points */ input group " - Indicator Parameters -" input ENUM_TIMEFRAMES InpIndTimeframe = PERIOD_CURRENT; /* Indicator's timeframe */ // Timeframe of the indicator used in trailing calculation input int InpMAPeriod = 0; /* MA Period */ input int InpMAShift = 0; /* MA Shift */ input int InpFastEMAPeriod = 2; /* AMA Fast EMA Period */ input int InpSlowEMAPeriod = 30; /* AMA Slow EMA Period */ input int InpCMOPeriod = 9; /* VIDYA CMO Period */ input double InpSARStep = 0.02; /* Parabolic SAR Step */ input double InpSARMax = 0.2; /* Parabolic SAR Max */ input ENUM_APPLIED_PRICE InpAppliedPrice = PRICE_CLOSE; /* MA Applied Price */ input ENUM_MA_METHOD InpMAMethod = MODE_SMA; /* MA Smoothing Method */ input int InpDataIndex = 1; /* Indicator data index */ // Bar of data received frrom the indicator
InpMAPeriod está definido como zero por padrão. Isso se deve ao fato de que cada tipo de média móvel possui um valor de período padrão específico. Como, ao receber o valor zero, a classe de trailing passa para o indicador o valor padrão correspondente, todos os dados serão válidos caso desejemos usar os valores padrão simples para o indicador utilizado no trailing.
Em todo o código, substituímos todas as ocorrências da string "CSymbolTrade" por "CSymbolTradeExt". Agora temos uma nova classe de objeto de negociação por símbolo, CSymbolTradeExt, herdada da antiga classe CSymbolTrade, escrita no artigo anterior. E nessa nova classe, foi declarado um objeto da classe de trailing. Por isso substituímos o tipo da antiga classe pela nova. Na prática, não seria necessário fazer essa substituição em todos os lugares, apenas onde for necessário usar trailing. Mas, para simplificar, não entraremos aqui em detalhes sobre herança de classes e apenas faremos a substituição completa.
Os trailings serão acionados para cada posição, com base em seu ticket. E para acessar o trailing de uma posição, precisamos obter da lista o objeto de negociação correspondente ao símbolo no qual a posição está aberta. Para isso, escreveremos uma função que retorna o ponteiro para o objeto de negociação a partir do nome do símbolo:
//+------------------------------------------------------------------+ //| Return the pointer to the symbol trading object by name | //+------------------------------------------------------------------+ CSymbolTrade *GetSymbolTrade(const string symbol, CArrayObj *list) { SymbTradeTmp.SetSymbol(symbol); list.Sort(); int index=list.Search(&SymbTradeTmp); return list.At(index); }
A função recebe como argumento o nome do símbolo para o qual deve ser retornado o objeto de negociação, além do ponteiro para a lista que contém os ponteiros para os objetos de negociação dos símbolos. No objeto de negociação temporário, é atribuído o nome do símbolo passado para a função, e em seguida o índice do objeto com esse nome é buscado na lista. No final, o ponteiro para o objeto procurado é retornado com base nesse índice. Caso o objeto não esteja presente na lista, o índice será igual a -1, e será retornado NULL.
Durante a criação do array de símbolos utilizados, os objetos de negociação dos símbolos são criados, e é dentro desses objetos que os trailings definidos nos parâmetros de entrada devem ser inicializados. Vamos ajustar a função que cria o array de símbolos utilizados:
//+------------------------------------------------------------------+ //| Creates an array of used symbols | //+------------------------------------------------------------------+ bool CreateListSymbolTrades(SDeal &array_deals[], CArrayObj *list_symbols) { bool res=true; // result MqlParam param[7]={}; // trailing parameters int total=(int)array_deals.Size(); // total number of deals in the array //--- if the deal array is empty, return 'false' if(total==0) { PrintFormat("%s: Error! Empty deals array passed",__FUNCTION__); return false; } //--- in a loop through the deal array CSymbolTradeExt *SymbolTrade=NULL; for(int i=0; i<total; i++) { //--- get the next deal and, if it is neither buy nor sell, move on to the next one SDeal deal_str=array_deals[i]; if(deal_str.type!=DEAL_TYPE_BUY && deal_str.type!=DEAL_TYPE_SELL) continue; //--- find a trading object in the list whose symbol is equal to the deal symbol string symbol=deal_str.Symbol(); SymbTradeTmp.SetSymbol(symbol); list_symbols.Sort(); int index=list_symbols.Search(&SymbTradeTmp); //--- if the index of the desired object in the list is -1, there is no such object in the list if(index==WRONG_VALUE) { //--- we create a new trading symbol object and, if creation fails, //--- add 'false' to the result and move on to the next deal SymbolTrade=new CSymbolTradeExt(symbol, InpIndTimeframe); if(SymbolTrade==NULL) { res &=false; continue; } //--- if failed to add a symbol trading object to the list, //--- delete the newly created object, add 'false' to the result //--- and we move on to the next deal if(!list_symbols.Add(SymbolTrade)) { delete SymbolTrade; res &=false; continue; } //--- initialize trailing specified in the settings in the trading object ENUM_TRAILING_MODE mode=(ENUM_TRAILING_MODE)InpTestingMode; SetTrailingParams(mode, param); SymbolTrade.SetTrailing(mode, InpDataIndex, InpTestedMagic, InpTrailingStart, InpTrailingStep, InpTrailingOffset, param); } //--- otherwise, if the trading object already exists in the list, we get it by index else { SymbolTrade=list_symbols.At(index); if(SymbolTrade==NULL) continue; } //--- if the current deal is not yet in the list of deals of the symbol trading object if(SymbolTrade.GetDealByTime(deal_str.time)==NULL) { //--- create a deal object according to its sample structure CDeal *deal=CreateDeal(deal_str); if(deal==NULL) { res &=false; continue; } //--- add the result of adding the deal object to the list of deals of a symbol trading object to the result value res &=SymbolTrade.AddDeal(deal); } } //--- return the final result of creating trading objects and adding deals to their lists return res; }
Aqui, adicionamos a declaração da estrutura de parâmetros de entrada do indicador e um pequeno bloco de código, onde o trailing é inicializado no objeto de negociação.
Para configurar os parâmetros do tipo de trailing escolhido nas configurações, vamos escrever uma função especial:
//+------------------------------------------------------------------+ //| Set the trailing parameters according to its selected type | //+------------------------------------------------------------------+ void SetTrailingParams(const ENUM_TRAILING_MODE mode, MqlParam ¶m[]) { //--- reset all parameters ZeroMemory(param); //--- depending on the selected trailing type, we set the indicator parameters switch(mode) { case TRAILING_MODE_SAR : param[0].type=TYPE_DOUBLE; param[0].double_value=InpSARStep; param[1].type=TYPE_DOUBLE; param[1].double_value=InpSARMax; break; case TRAILING_MODE_AMA : param[0].type=TYPE_INT; param[0].integer_value=InpMAPeriod; param[1].type=TYPE_INT; param[1].integer_value=InpMAShift; param[2].type=TYPE_INT; param[2].integer_value=InpAppliedPrice; param[4].type=TYPE_INT; param[4].integer_value=InpFastEMAPeriod; param[5].type=TYPE_INT; param[5].integer_value=InpSlowEMAPeriod; break; case TRAILING_MODE_DEMA : param[0].type=TYPE_INT; param[0].integer_value=InpMAPeriod; param[1].type=TYPE_INT; param[1].integer_value=InpMAShift; param[2].type=TYPE_INT; param[2].integer_value=InpAppliedPrice; break; case TRAILING_MODE_FRAMA : param[0].type=TYPE_INT; param[0].integer_value=InpMAPeriod; param[1].type=TYPE_INT; param[1].integer_value=InpMAShift; param[2].type=TYPE_INT; param[2].integer_value=InpAppliedPrice; break; case TRAILING_MODE_MA : param[0].type=TYPE_INT; param[0].integer_value=InpMAPeriod; param[1].type=TYPE_INT; param[1].integer_value=InpMAShift; param[2].type=TYPE_INT; param[2].integer_value=InpAppliedPrice; param[3].type=TYPE_INT; param[3].integer_value=InpMAMethod; break; case TRAILING_MODE_TEMA : param[0].type=TYPE_INT; param[0].integer_value=InpMAPeriod; param[1].type=TYPE_INT; param[1].integer_value=InpMAShift; param[2].type=TYPE_INT; param[2].integer_value=InpAppliedPrice; break; case TRAILING_MODE_VIDYA : param[0].type=TYPE_INT; param[0].integer_value=InpMAPeriod; param[1].type=TYPE_INT; param[1].integer_value=InpMAShift; param[2].type=TYPE_INT; param[2].integer_value=InpAppliedPrice; param[6].type=TYPE_INT; param[6].integer_value=InpCMOPeriod; break; case TRAILING_MODE_SIMPLE : break; default: break; } }
Nessa função, para cada tipo de trailing, são atribuídos à estrutura os valores dos indicadores utilizados no trailing, com base nos parâmetros de entrada do EA. Os valores atribuídos aos campos da estrutura são verificados quanto à sua validade e, se necessário, são ajustados na nova classe CSymbolTradeExt, a classe do objeto de negociação por símbolo que já analisamos acima.
Vamos aprimorar a função de negociação baseada no histórico de ordens. Agora precisamos distinguir claramente qual tipo de teste está sendo utilizado.
Para o teste da negociação original, não é necessário definir ordens de stop, pois todas as posições são encerradas com base nas ordens de fechamento do histórico.
Se o teste for com tamanhos diferentes de ordens de stop, será necessário obter os tamanhos corretos e aplicá-los à posição aberta. No caso dos testes com trailings, não é obrigatório definir os stops, mas, para evitar grandes rebaixamentos e "alongamentos" indesejados, é recomendado definir stops iniciais, que depois serão ajustados automaticamente pelo trailing, conforme a lógica definida. Nas configurações do EA, existem parâmetros que determinam se esses stops iniciais devem ou não ser aplicados:
input bool InpSetStopLoss = true; /* Set Initial StopLoss */ input bool InpSetTakeProfit = true; /* Set Initial TakeProfit */
Ao usar esses modos, também é necessário obter os tamanhos corretos dos stops e aplicá-los à posição que será aberta:
//+------------------------------------------------------------------+ //| Trading by history | //+------------------------------------------------------------------+ void TradeByHistory(const string symbol="", const long magic=-1) { datetime time=0; int total=ExtListSymbols.Total(); // number of trading objects in the list //--- in a loop by all symbol trading objects for(int i=0; i<total; i++) { //--- get another trading object CSymbolTradeExt *obj=ExtListSymbols.At(i); if(obj==NULL) continue; //--- get the current deal pointed to by the deal list index CDeal *deal=obj.GetDealCurrent(); if(deal==NULL) continue; //--- sort the deal by magic number and symbol if((magic>-1 && deal.Magic()!=magic) || (symbol!="" && deal.Symbol()!=symbol)) continue; //--- sort the deal by type (only buy/sell deals) ENUM_DEAL_TYPE type=deal.TypeDeal(); if(type!=DEAL_TYPE_BUY && type!=DEAL_TYPE_SELL) continue; //--- if this is a deal already handled in the tester, move on to the next one if(deal.TicketTester()>0) continue; //--- if the deal time has not yet arrived, move to the next trading object of the next symbol if(!obj.CheckTime(deal.Time())) continue; //--- in case of a market entry deal ENUM_DEAL_ENTRY entry=deal.Entry(); if(entry==DEAL_ENTRY_IN) { //--- set the sizes of stop orders depending on the stop setting method //--- stop orders are not used initially (for original trading) ENUM_ORDER_TYPE order_type=(deal.TypeDeal()==DEAL_TYPE_BUY ? ORDER_TYPE_BUY : ORDER_TYPE_SELL); double sl=0; double tp=0; //--- in case of the mode for setting the specified stop order values if(InpTestingMode==TESTING_MODE_SLTP) { //--- get correct values for StopLoss and TakeProfit sl=CorrectStopLoss(deal.Symbol(), order_type, ExtStopLoss); tp=CorrectTakeProfit(deal.Symbol(), order_type, ExtTakeProfit); } //--- otherwise, if testing with trailing stops else { if(InpTestingMode!=TESTING_MODE_ORIGIN) { //--- if allowed in the settings, we set correct stop orders for the positions being opened if(InpSetStopLoss) sl=CorrectStopLoss(deal.Symbol(), order_type, ExtStopLoss); if(InpSetTakeProfit) tp=CorrectTakeProfit(deal.Symbol(), order_type, ExtTakeProfit); } } //--- open a position by deal type ulong ticket=(type==DEAL_TYPE_BUY ? obj.Buy(deal.Volume(), deal.Magic(), sl, tp, deal.Comment()) : type==DEAL_TYPE_SELL ? obj.Sell(deal.Volume(),deal.Magic(), sl, tp, deal.Comment()) : 0); //--- if a position is opened (we received its ticket) if(ticket>0) { //--- increase the number of deals handled by the tester and write the deal ticket in the tester to the properties of the deal object obj.SetNumProcessedDeals(obj.NumProcessedDeals()+1); deal.SetTicketTester(ticket); //--- get the position ID in the tester and write it to the properties of the deal object long pos_id_tester=0; if(HistoryDealSelect(ticket)) { pos_id_tester=HistoryDealGetInteger(ticket, DEAL_POSITION_ID); deal.SetPosIDTester(pos_id_tester); } } } //--- in case of a market exit deal if(entry==DEAL_ENTRY_OUT || entry==DEAL_ENTRY_INOUT || entry==DEAL_ENTRY_OUT_BY) { //--- get a deal a newly opened position is based on CDeal *deal_in=obj.GetDealInByPosID(deal.PositionID()); if(deal_in==NULL) continue; //--- get the position ticket in the tester from the properties of the opening deal //--- if the ticket is zero, then most likely the position in the tester is already closed ulong ticket_tester=deal_in.TicketTester(); if(ticket_tester==0) { PrintFormat("Could not get position ticket, apparently position #%I64d (#%I64d) is already closed \n", deal.PositionID(), deal_in.PosIDTester()); obj.SetNextDealIndex(); continue; } //--- if we reproduce the original trading history in the tester, if(InpTestingMode==TESTING_MODE_ORIGIN) { //--- if the position is closed by ticket if(obj.ClosePos(ticket_tester)) { //--- increase the number of deals handled by the tester and write the deal ticket in the tester to the properties of the deal object obj.SetNumProcessedDeals(obj.NumProcessedDeals()+1); deal.SetTicketTester(ticket_tester); } } //--- otherwise, in the tester we work with stop orders placed according to different algorithms, and closing deals are skipped; //--- accordingly, for the closing deal, we simply increase the number of deals handled by the tester and //--- write the deal ticket in the tester to the properties of the deal object else { obj.SetNumProcessedDeals(obj.NumProcessedDeals()+1); deal.SetTicketTester(ticket_tester); } } //--- if a ticket is now set in the deal object, then the deal has been successfully handled - //--- set the deal index in the list to the next deal if(deal.TicketTester()>0) { obj.SetNextDealIndex(); } } }
Agora vamos escrever a função responsável por executar o trailing das posições:
//+------------------------------------------------------------------+ //| Trail positions | //+------------------------------------------------------------------+ void Trailing(void) { //--- variables for getting position properties long magic=-1; string symbol=""; //--- in a loop through all positions int total=PositionsTotal(); for(int i=total-1; i>=0; i--) { //--- get the ticket of the next position ulong ticket=PositionGetTicket(i); if(ticket==0) continue; //--- get the magic number and position symbol ResetLastError(); if(!PositionGetInteger(POSITION_MAGIC, magic)) { Print("PositionGetInteger() failed. Error ", GetLastError()); continue; } if(!PositionGetString(POSITION_SYMBOL, symbol)) { Print("PositionGetString() failed. Error ", GetLastError()); continue; } //--- if the position does not meet the specified conditions of the magic number and symbol, we move to the next one if((InpTestedMagic>-1 && magic!=InpTestedMagic) || (InpTestedSymbol!="" && symbol!=InpTestedSymbol)) continue; //--- get a trading object by a symbol name and call its method for trailing a position by ticket CSymbolTradeExt *obj=GetSymbolTrade(symbol, &ExtListSymbols); if(obj!=NULL) obj.Trailing(ticket); } }
Aqui não há mistério: percorremos cada posição em um laço, verificamos se o magic number e o símbolo correspondem aos definidos nas configurações do EA e, se a posição for válida, obtemos o objeto de negociação do símbolo da posição e, a partir dele, chamamos o método de trailing, informando o ticket da posição.
Vamos inserir a chamada dessa função no manipulador OnTick() do EA:
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- work only in the strategy tester if(!MQLInfoInteger(MQL_TESTER)) return; //--- Trail open positions Trailing(); //--- Handle the list of deals from the file TradeByHistory(InpTestedSymbol, InpTestedMagic); }
Pronto. O EA está finalizado. O código completo do EA pode ser consultado nos arquivos anexados ao artigo.
Vamos testar os diferentes tipos de trailing e comparar os resultados da negociação original com os da negociação usando trailing stop com diferentes algoritmos.
Ao iniciar o EA no gráfico de um símbolo, ele coleta o histórico de negociações, grava em um arquivo e exibe um alerta indicando as configurações ideais para o testador — data de início dos testes, depósito inicial e alavancagem:

No log, ele exibe os símbolos utilizados na negociação e a quantidade de ordens executadas em cada símbolo:
Alert: Now you can run testing Interval: 2024.09.13 - current date Initial deposit: 3000.00, leverage 1:500 Symbols used in trading: 1. AUDUSD trade object. Total deals: 222 2. EURJPY trade object. Total deals: 120 3. EURUSD trade object. Total deals: 526 4. GBPUSD trade object. Total deals: 352 5. NZDUSD trade object. Total deals: 182 6. USDCAD trade object. Total deals: 22 7. USDCHF trade object. Total deals: 250 8. USDJPY trade object. Total deals: 150 9. XAUUSD trade object. Total deals: 118
Agora, vamos executar o EA no testador com os parâmetros recomendados, conforme informados no alerta e repetidos no log.
Negociação original:

Observamos que a negociação original gerou um prejuízo de 658 dólares.
Vamos testar os diferentes trailings. Permitiremos a definição de um stop inicial com tamanho de 100 pontos, e executaremos cada tipo de trailing no testador.
Um por vez, sem alterar outros parâmetros, para ver como a negociação se comporta.
Trailing simples:

Registrou prejuízo de 746,1 dólares. Ainda maior que na negociação original.
Vamos verificar os trailings com base em indicadores.
Trailing com Parabolic SAR:

Lucro - 541,8 dólares.
Agora analisaremos trailings baseados em diferentes tipos de médias móveis.
Trailing com AMA:

Lucro - 806,5 dólares.
Trailing com DEMA:

Lucro de 1397,1 dólares.
Trailing com FRAMA:

Lucro de 1291,6 dólares.
Trailing com MA:

Lucro - 563,1 dólares.
Trailing com TEMA:

Lucro - 1355,1 dólares.
Trailing com VIDYA:

Lucro - 283,3 dólares.
Assim, a tabela final mostra os resultados obtidos com o uso de diferentes tipos de trailing StopLoss em comparação com a negociação original:
| # | Tipo de trailing | Tamanho do StopLoss | Tamanho do TakeProfit | Lucro final |
|---|---|---|---|---|
| 1 | Negociação original | 100 | 500 | - 658.0 |
| 2 | TrailingStop simples | Inicialmente 100, depois stop a 100 pontos do preço | 0 | - 746.1 |
| 3 | TrailingStop por Parabolic SAR | Inicialmente 100, depois stop conforme o valor do indicador | 0 | + 541.8 |
| 4 | TrailingStop por VIDYA | Inicialmente 100, depois stop conforme o valor do indicador | 0 | -283.3 |
| 5 | TrailingStop por MA | Inicialmente 100, depois stop conforme o valor do indicador | 0 | + 563.1 |
| 6 | TrailingStop por AMA | Inicialmente 100, depois stop conforme o valor do indicador | 0 | + 806.5 |
| 7 | TrailingStop por FRAMA | Inicialmente 100, depois stop conforme o valor do indicador | 0 | + 1291.6 |
| 8 | TrailingStop por TEMA | Inicialmente 100, depois stop conforme o valor do indicador | 0 | + 1355.1 |
| 9 | TrailingStop por DEMA | Inicialmente 100, depois stop conforme o valor do indicador | 0 | + 1397.1 |
Como resultado, quando a negociação original foi negativa, o trailing simples — um equivalente aproximado ao trailing padrão do terminal cliente — gerou ainda mais prejuízo. Vamos analisar o trailing baseado no Parabolic SAR, apresentado como um indicador que mostra reversões e níveis de stop (Stop And Reverse). Sim, ao seguir o nível de StopLoss conforme a linha do indicador no primeiro candle, obtivemos um lucro de 540 dólares.
Agora, vamos observar os resultados ao mover os stops das posições com base nos valores de diferentes tipos de médias móveis no primeiro candle. O trailing com o indicador VIDYA teve prejuízo de 280 dólares, mas todos os outros apresentaram lucro. Inclusive, seguir o stop com a média móvel simples deu um lucro maior do que o trailing com Parabolic SAR. E o verdadeiro "campeão" na melhora dos resultados da negociação foi a média móvel exponencial dupla — +1397 dólares de lucro.
Considerações finais
Criamos um EA que permite testar sua negociação no testador de estratégias e escolher o trailing stop mais adequado ao seu estilo de operação. Deve-se levar em conta que todos os parâmetros dos indicadores utilizados nos trailings durante os testes estavam com valores padrão, e as configurações dos trailings foram escolhidas de forma aleatória, "no olhômetro".
A abordagem mais correta seria realizar testes individualizados para cada símbolo, e ajustar as configurações dos trailings levando em conta as particularidades de movimento e volatilidade de cada par de moedas. No entanto, este EA ainda não oferece configurações separadas para definir "seus" próprios parâmetros para cada símbolo negociado individualmente. Mas o mais importante foi demonstrar que, com o testador de estratégias, é possível testar, analisar e melhorar sua negociação. E isso significa que o EA pode ser aprimorado para permitir a definição de parâmetros personalizados por símbolo — e isso não é difícil. O principal é ter vontade.
Estão anexados ao artigo todos os arquivos das classes e do EA discutidos. Também está incluído um arquivo compactado que, ao ser extraído, já traz os arquivos prontos para teste nas pastas corretas do terminal.
Programas utilizados no artigo:
| # | Nome | Tipo | Descrição |
|---|---|---|---|
| 1 | Trailings.mqh | Biblioteca de classe | Biblioteca de classes de trailing |
| 2 | SymbolTrade.mqh | Biblioteca de classe | Biblioteca de estrutura e classe de negociação, classe de negociação por símbolo |
| 3 | SymbolTradeExt.mqh | Biblioteca de classe | Classe de negociação por símbolo com trailing |
| 4 | TradingByHistoryDeals_Ext.mq5 | Expert Advisor | EA para visualização e modificação no testador de estratégias de ordens e negociações executadas na conta, com uso de StopLoss, TakeProfit e trailings |
| 5 | MQL5.zip | Arquivo compactado | Arquivo compactado com os arquivos acima, pronto para extração na pasta MQL5 do terminal cliente |
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/16991
Aviso: Todos os direitos sobre esses materiais pertencem à MetaQuotes Ltd. É proibida a reimpressão total ou parcial.
Esse artigo foi escrito por um usuário do site e reflete seu ponto de vista pessoal. A MetaQuotes Ltd. não se responsabiliza pela precisão das informações apresentadas nem pelas possíveis consequências decorrentes do uso das soluções, estratégias ou recomendações descritas.
Simulação de mercado: Iniciando o SQL no MQL5 (V)
Redes neurais em trading: Aprendizado dependente de contexto com memória (Conclusão)
Do básico ao intermediário: Eventos em Objetos (II)
Usando PSAR, Heiken Ashi e Aprendizado Profundo Juntos para Operações de Trading
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso
Трал по VIDYA:
Lucro - 283,3 dólares.Erro: Perda - 283,3 dólares.
Erro: Perda - US$ 283,3.
No artigo, o valor negativo do lucro está escrito.
Entretanto, o espaço após o sinal de menos foi inserido acidentalmente.