
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:
//+------------------------------------------------------------------+ //| Запускает простой трейлинг с отступом StopLoss от цены | //+------------------------------------------------------------------+ bool CSimpleTrailing::Run(void) { //--- если отключен - уходим if(!this.m_active) return false; //--- переменные трала MqlTick tick = {}; // структура цен bool res = true; // результат модификации всех позиций //--- проверка корректности данных по символу if(this.m_point==0) { //--- попробуем ещё раз получить данные ::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; } } //--- в цикле по общему количеству открытых позиций int total =::PositionsTotal(); for(int i = total - 1; i >= 0; i--) { //--- получаем тикет очередной позиции ulong pos_ticket =::PositionGetTicket(i); if(pos_ticket == 0) continue; //--- получаем символ и магик позиции string pos_symbol = ::PositionGetString(POSITION_SYMBOL); long pos_magic = ::PositionGetInteger(POSITION_MAGIC); //--- если позиция не соответствует фильтру по символу и магику - уходим if((this.m_magic != -1 && pos_magic != this.m_magic) || (pos_symbol != this.m_symbol)) continue; //--- если цены получить не удалось - идём дальше if(!::SymbolInfoTick(this.m_symbol, tick)) continue; //--- получаем тип позиции, цену её открытия и уровень StopLoss ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)::PositionGetInteger(POSITION_TYPE); double pos_open =::PositionGetDouble(POSITION_PRICE_OPEN); double pos_sl =::PositionGetDouble(POSITION_SL); //--- получаем рассчитанный уровень StopLoss double value_sl = this.GetStopLossValue(pos_type, tick); //--- если условия для модификации StopLoss подходят - модифицируем стоп позиции и добавляем результат к переменной res if(this.CheckCriterion(pos_type, pos_open, pos_sl, value_sl, tick)) res &=this.ModifySL(pos_ticket, value_sl); } //--- по окончании цикла возвращаем результат модификации каждой подходящей по фильтру "символ/магик" позиции 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:
//+------------------------------------------------------------------+ //| Класс простого трала StopLoss позиций | //+------------------------------------------------------------------+ class CSimpleTrailing : public CObject { private: //--- проверяет критерии модификации StopLoss позициии и возвращает флаг bool CheckCriterion(ENUM_POSITION_TYPE pos_type, double pos_open, double pos_sl, double value_sl, MqlTick &tick); //--- модифицирует StopLoss позиции по её тикету bool ModifySL(const ulong ticket, const double stop_loss); //--- возвращает размер StopLevel в пунктах int StopLevel(void); protected: string m_symbol; // торговый символ long m_magic; // идентификатор эксперта double m_point; // Point символа int m_digits; // Digits символа int m_offset; // дистанция стопа от цены int m_trail_start; // прибыль в пунктах для запуска трала uint m_trail_step; // шаг трала uint m_spread_mlt; // множитель спреда для возврата значения StopLevel bool m_active; // флаг активности трала //--- рассчитывает и возвращает уровень StopLoss выбранной позиции virtual double GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick); public: //--- установка параметров трала 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; } //--- возврат параметров трала 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; } //--- запуск трала с отступом StopLoss от цены bool Run(void); bool Run(const ulong pos_ticket); //--- конструкторы 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); //--- деструктор ~CSimpleTrailing() {} };
Fora do corpo da classe, escreveremos sua implementação:
//+------------------------------------------------------------------+ //| Запускает простой трейлинг с отступом StopLoss от цены | //+------------------------------------------------------------------+ bool CSimpleTrailing::Run(const ulong pos_ticket) { //--- если трейлинг отключен, или невалидный тикет - уходим if(!this.m_active || pos_ticket==0) return false; //--- переменные трала MqlTick tick = {}; // структура цен //--- проверка корректности данных по символу ::ResetLastError(); if(this.m_point==0) { //--- попробуем ещё раз получить данные 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; } } //--- выбираем позицию по тикету if(!::PositionSelectByTicket(pos_ticket)) { ::PrintFormat("%s: PositionSelectByTicket(%I64u) failed. Error %d",__FUNCTION__, pos_ticket, ::GetLastError()); return false; } //--- если цены получить не удалось - возвращаем false if(!::SymbolInfoTick(this.m_symbol, tick)) return false; //--- получаем тип позиции, цену её открытия и уровень StopLoss ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)::PositionGetInteger(POSITION_TYPE); double pos_open =::PositionGetDouble(POSITION_PRICE_OPEN); double pos_sl =::PositionGetDouble(POSITION_SL); //--- получаем рассчитанный уровень StopLoss double value_sl = this.GetStopLossValue(pos_type, tick); //--- если условия для модификации StopLoss подходят - возвращаем результат модифицикации стопа позиции if(this.CheckCriterion(pos_type, pos_open, pos_sl, value_sl, tick)) return(this.ModifySL(pos_ticket, value_sl)); //--- условия для модификации не подходят 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--) { //--- получаем тикет очередной позиции ulong pos_ticket =::PositionGetTicket(i); if(pos_ticket == 0) continue; //--- получаем символ и магик позиции string pos_symbol = ::PositionGetString(POSITION_SYMBOL); long pos_magic = ::PositionGetInteger(POSITION_MAGIC); //--- если позиция не соответствует фильтру по символу и магику - уходим if((this.m_magic != -1 && pos_magic != this.m_magic) || (pos_symbol != this.m_symbol)) continue; //--- если цены получить не удалось - идём дальше if(!::SymbolInfoTick(this.m_symbol, tick)) continue; //--- получаем тип позиции, цену её открытия и уровень StopLoss ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)::PositionGetInteger(POSITION_TYPE); double pos_open =::PositionGetDouble(POSITION_PRICE_OPEN); double pos_sl =::PositionGetDouble(POSITION_SL); //--- получаем рассчитанный уровень StopLoss double value_sl = this.GetStopLossValue(pos_type, tick); //--- если условия для модификации StopLoss подходят - модифицируем стоп позиции и добавляем результат к переменной res 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:
//+------------------------------------------------------------------+ //| Запускает простой трейлинг с отступом StopLoss от цены | //+------------------------------------------------------------------+ bool CSimpleTrailing::Run(void) { //--- если отключен - уходим if(!this.m_active) return false; //--- переменные трала bool res = true; // результат модификации всех позиций //--- проверка корректности данных по символу if(this.m_point==0) { //--- попробуем ещё раз получить данные ::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; } } //--- в цикле по общему количеству открытых позиций int total =::PositionsTotal(); for(int i = total - 1; i >= 0; i--) { //--- получаем тикет очередной позиции ulong pos_ticket =::PositionGetTicket(i); if(pos_ticket == 0) continue; //--- получаем символ и магик позиции string pos_symbol = ::PositionGetString(POSITION_SYMBOL); long pos_magic = ::PositionGetInteger(POSITION_MAGIC); //--- если позиция не соответствует фильтру по символу и магику - уходим if((this.m_magic != -1 && pos_magic != this.m_magic) || (pos_symbol != this.m_symbol)) continue; res &=this.Run(pos_ticket); } //--- по окончании цикла возвращаем результат модификации каждой подходящей по фильтру "символ/магик" позиции 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:
//+------------------------------------------------------------------+ //| Класс трала по указанному значению | //+------------------------------------------------------------------+ class CTrailingByValue : public CSimpleTrailing { protected: double m_value_sl_long; // значение уровня StopLoss длинных позиций double m_value_sl_short; // значение уровня StopLoss коротких позиций //--- рассчитывает и возвращает уровень StopLoss выбранной позиции virtual double GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick); public: //--- возвращает уровень StopLoss (2) длинных, (2) коротких позиций double StopLossValueLong(void) const { return this.m_value_sl_long; } double StopLossValueShort(void) const { return this.m_value_sl_short; } //--- запуск трала с указанным отступом StopLoss от цены 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); //--- конструкторы 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) {} //--- деструктор ~CTrailingByValue(void){} };
E fora do corpo da classe, escreveremos sua implementação:
//+------------------------------------------------------------------+ //| Запуск трала с указанным отступом StopLoss от цены | //+------------------------------------------------------------------+ 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 // Перечисление режимов трейлинга { TRAILING_MODE_SIMPLE=2, // Простой трал TRAILING_MODE_SAR, // Трал по Parabolic SAR TRAILING_MODE_AMA, // Трал по адартивной скользящей средней TRAILING_MODE_DEMA, // Трал по двойной экспоненциальной скользящей средней TRAILING_MODE_FRAMA, // Трал по фрактальной адартивной скользящей средней TRAILING_MODE_MA, // Трал по простой скользящей средней TRAILING_MODE_TEMA, // Трал по тройной экспоненциальной скользящей средней TRAILING_MODE_VIDYA, // Трал по скользящей средней с динамическим периодом усреднения }; 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 */ // Указанные значения StopLoss и TakeProfit };
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; // Объект класса трейлингов ENUM_TIMEFRAMES m_timeframe; // Таймфрейм расчёта индикатора для трала public: //--- Устанавливает трал и его параметры 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[]); //--- Запускает трал указанной по тикету позиции void Trailing(const ulong pos_ticket); //--- Конструктор/деструктор 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:
//+------------------------------------------------------------------+ //| Конструктор | //+------------------------------------------------------------------+ 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:
//+------------------------------------------------------------------+ //| Деструктор | //+------------------------------------------------------------------+ CSymbolTradeExt::~CSymbolTradeExt() { //--- удаляем созданный объект трала 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.
//+------------------------------------------------------------------+ //| Устанавливает параметры трала | //+------------------------------------------------------------------+ 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[]) { //--- Устанавливаем параметры трала (для каждого типа индикаторов используются только свои, нужные индикатору, поля структуры) 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; //--- в зависимости от типа трейлинга создаём объект трала //---если в качестве периода расчёта передано значение, меньше допустимого, то для каждого индикатора устанавливается его собственное значение по умолчанию 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; } //--- что-то пошло не так - возвращаем false if(this.m_trailing==NULL) return false; //--- всё успешно - делаем трал активным и возвращаем 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:
//+------------------------------------------------------------------+ //| Запускает трал указанной по тикету позиции | //+------------------------------------------------------------------+ 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 */ // Указанные значения StopLoss и TakeProfit TESTING_MODE_TRAIL_SIMPLE, /* Simple Trailing */ // Простой трал TESTING_MODE_TRAIL_SAR, /* Trailing by Parabolic SAR indicator */ // Трал по Parabolic SAR 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 */ // Отступ StopLoss в пунктах input int InpTakeProfit = 500; /* TakeProfit in points */ // Отступ TakeProfit в пунктах input group " - Trailing Parameters -" input bool InpSetStopLoss = true; /* Set Initial StopLoss */ // Устанавливать StopLoss input bool InpSetTakeProfit = true; /* Set Initial TakeProfit */ // Устанавливать TakeProfit input int InpTrailingStart = 150; /* Trailing start */ // Прибыль в пунктах для старта трейлинга 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 */ // Таймфрейм индикатора, используемого в расчёте трала input int InpMAPeriod = 0; /* MA Period */ // Период расчёта скользящей средней input int InpMAShift = 0; /* MA Shift */ // Горизонтальный сдвиг скользящей средней input int InpFastEMAPeriod = 2; /* AMA Fast EMA Period */ // Период расчёта быстрой EMA адаптивной скользящей средней input int InpSlowEMAPeriod = 30; /* AMA Slow EMA Period */ // Период расчёта медленной EMA адаптивной скользящей средней input int InpCMOPeriod = 9; /* VIDYA CMO Period */ // Период CMO скользящей средней с динамическим периодом усреднения input double InpSARStep = 0.02; /* Parabolic SAR Step */ // Шаг Parabolic SAR input double InpSARMax = 0.2; /* Parabolic SAR Max */ // Максимум Parabolic SAR 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 */ // Бар данных, получаемых от индикатора
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:
//+------------------------------------------------------------------+ //| Возвращает указатель на торговый объект символа по имени | //+------------------------------------------------------------------+ 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:
//+------------------------------------------------------------------+ //| Создаёт массив используемых символов | //+------------------------------------------------------------------+ bool CreateListSymbolTrades(SDeal &array_deals[], CArrayObj *list_symbols) { bool res=true; // результат MqlParam param[7]={}; // параметры тралов int total=(int)array_deals.Size(); // общее количество сделок в массиве //--- если массив сделок пустой - возвращаем false if(total==0) { PrintFormat("%s: Error! Empty deals array passed",__FUNCTION__); return false; } //--- в цикле по массиву сделок CSymbolTradeExt *SymbolTrade=NULL; for(int i=0; i<total; i++) { //--- получаем очередную сделку и, если это не покупка и не продажа - идём к следующей SDeal deal_str=array_deals[i]; if(deal_str.type!=DEAL_TYPE_BUY && deal_str.type!=DEAL_TYPE_SELL) continue; //--- найдём торговый объект в списке, у которого символ равен символу сделки string symbol=deal_str.Symbol(); SymbTradeTmp.SetSymbol(symbol); list_symbols.Sort(); int index=list_symbols.Search(&SymbTradeTmp); //--- если индекс искомого объекта в списке равен -1 - такого объекта в списке нет if(index==WRONG_VALUE) { //--- создаём новый торговый объект символа и, если создать не получилось - //--- добавляем к результату значение false и идём к следующей сделке SymbolTrade=new CSymbolTradeExt(symbol, InpIndTimeframe); if(SymbolTrade==NULL) { res &=false; continue; } //--- если торговый объект символа не удалось добавить в список - //--- удаляем вновь созданный объект, добавляем к результату значение false //--- и идём к следующей сделке if(!list_symbols.Add(SymbolTrade)) { delete SymbolTrade; res &=false; continue; } //--- инициализируем в торговом объекте заданный в настройках трейлинг ENUM_TRAILING_MODE mode=(ENUM_TRAILING_MODE)InpTestingMode; SetTrailingParams(mode, param); SymbolTrade.SetTrailing(mode, InpDataIndex, InpTestedMagic, InpTrailingStart, InpTrailingStep, InpTrailingOffset, param); } //--- иначе, если торговый объект уже существует в списке - получаем его по индексу else { SymbolTrade=list_symbols.At(index); if(SymbolTrade==NULL) continue; } //--- если текущей сделки ещё нет в списке сделок торгового объекта символа if(SymbolTrade.GetDealByTime(deal_str.time)==NULL) { //--- создаём объект сделки по её образцу-структуре CDeal *deal=CreateDeal(deal_str); if(deal==NULL) { res &=false; continue; } //--- к значению результата добавляем результат добавления объекта сделки в список сделок торгового объекта символа res &=SymbolTrade.AddDeal(deal); } } //--- возвращаем итоговый результат создания торговых объектов и добавления сделок в их списки 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:
//+------------------------------------------------------------------+ //| Устанавливает параметры трала по выбранному его типу | //+------------------------------------------------------------------+ void SetTrailingParams(const ENUM_TRAILING_MODE mode, MqlParam ¶m[]) { //--- обнуляем все параметры ZeroMemory(param); //--- в зависимости от выбранного типа трейлинга устанавливаем параметры индикатора 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 */ // Устанавливать начальный StopLoss input bool InpSetTakeProfit = true; /* Set Initial TakeProfit */ // Устанавливать начальный TakeProfit
Ao usar esses modos, também é necessário obter os tamanhos corretos dos stops e aplicá-los à posição que será aberta:
//+------------------------------------------------------------------+ //| Торговля по истории | //+------------------------------------------------------------------+ void TradeByHistory(const string symbol="", const long magic=-1) { datetime time=0; int total=ExtListSymbols.Total(); // количество торговых объектов в списке //--- в цикле по всем торговым объектам символов for(int i=0; i<total; i++) { //--- получаем очередной торговый объект CSymbolTradeExt *obj=ExtListSymbols.At(i); if(obj==NULL) continue; //--- получаем текущую сделку, на которую указывает индекс списка сделок CDeal *deal=obj.GetDealCurrent(); if(deal==NULL) continue; //--- фильтруем сделку по магику и символу if((magic>-1 && deal.Magic()!=magic) || (symbol!="" && deal.Symbol()!=symbol)) continue; //--- фильтруем сделку по типу (только сделки покупки/продажи) ENUM_DEAL_TYPE type=deal.TypeDeal(); if(type!=DEAL_TYPE_BUY && type!=DEAL_TYPE_SELL) continue; //--- если это уже обработанная в тестере сделка - идём к следующей if(deal.TicketTester()>0) continue; //--- если время сделки ещё не настало - идём к следующему торговому объекту следующего символа if(!obj.CheckTime(deal.Time())) continue; //--- если сделка входа в рынок ENUM_DEAL_ENTRY entry=deal.Entry(); if(entry==DEAL_ENTRY_IN) { //--- устанавливаем размеры стоп-приказов в зависимости от метода установки стопов //--- изначально стоп-приказы не используются (для оригинальной торговли) ENUM_ORDER_TYPE order_type=(deal.TypeDeal()==DEAL_TYPE_BUY ? ORDER_TYPE_BUY : ORDER_TYPE_SELL); double sl=0; double tp=0; //--- если режим установки указанных значений стоп-приказов if(InpTestingMode==TESTING_MODE_SLTP) { //--- получаем корректные значения для StopLoss и TakeProfit sl=CorrectStopLoss(deal.Symbol(), order_type, ExtStopLoss); tp=CorrectTakeProfit(deal.Symbol(), order_type, ExtTakeProfit); } //--- иначе, если тестирование с трейлингами else { if(InpTestingMode!=TESTING_MODE_ORIGIN) { //--- если разрешено в настройках, выставляем корректные стоп-приказы открываемым позициям if(InpSetStopLoss) sl=CorrectStopLoss(deal.Symbol(), order_type, ExtStopLoss); if(InpSetTakeProfit) tp=CorrectTakeProfit(deal.Symbol(), order_type, ExtTakeProfit); } } //--- открываем позицию по типу сделки 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(ticket>0) { //--- увеличиваем количество обработанных тестером сделок и записываем тикет сделки в тестере в свойства объекта сделки obj.SetNumProcessedDeals(obj.NumProcessedDeals()+1); deal.SetTicketTester(ticket); //--- получаем идентификатор позиции в тестере и записываем его в свойства объекта сделки long pos_id_tester=0; if(HistoryDealSelect(ticket)) { pos_id_tester=HistoryDealGetInteger(ticket, DEAL_POSITION_ID); deal.SetPosIDTester(pos_id_tester); } } } //--- если сделка выхода из рынка if(entry==DEAL_ENTRY_OUT || entry==DEAL_ENTRY_INOUT || entry==DEAL_ENTRY_OUT_BY) { //--- получаем сделку, на основании которой была открыта позиция CDeal *deal_in=obj.GetDealInByPosID(deal.PositionID()); if(deal_in==NULL) continue; //--- получаем тикет позиции в тестере из свойств открывающей сделки //--- если тикет равен нулю, значит скорее всего позиция в тестере уже закрыта 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(InpTestingMode==TESTING_MODE_ORIGIN) { //--- если позиция закрыта по тикету if(obj.ClosePos(ticket_tester)) { //--- увеличиваем количество обработанных тестером сделок и записываем тикет сделки в тестере в свойства объекта сделки obj.SetNumProcessedDeals(obj.NumProcessedDeals()+1); deal.SetTicketTester(ticket_tester); } } //--- иначе - в тестере работаем со стоп-приказами, выставляемыми по различным алгоритмам, и сделки закрытия пропускаются; //--- соответственно, для закрывающей сделки просто увеличиваем количество обработанных тестером сделок и //--- записываем тикет сделки в тестере в свойства объекта сделки else { obj.SetNumProcessedDeals(obj.NumProcessedDeals()+1); deal.SetTicketTester(ticket_tester); } } //--- если теперь в объекте сделки записан тикет - значит сделка успешно обработана - //--- устанавливаем индекс сделки в списке на следующую сделку if(deal.TicketTester()>0) { obj.SetNextDealIndex(); } } }
Agora vamos escrever a função responsável por executar o trailing das posições:
//+------------------------------------------------------------------+ //| Трейлинг позиций | //+------------------------------------------------------------------+ void Trailing(void) { //--- переменные для получения свойств позиции long magic=-1; string symbol=""; //--- в цикле по всем позициям int total=PositionsTotal(); for(int i=total-1; i>=0; i--) { //--- получаем тикет очередной позиции ulong ticket=PositionGetTicket(i); if(ticket==0) continue; //--- получаем магик и символ позиции ResetLastError(); if(!PositionGetInteger(POSITION_MAGIC, magic)) { Print("PositionGetInteger() failed. Error ", GetLastError()); continue; } if(!PositionGetString(POSITION_SYMBOL, symbol)) { Print("PositionGetString() failed. Error ", GetLastError()); continue; } //--- если позиция не проходит по заданным условиям магика и символа - идём к следующей if((InpTestedMagic>-1 && magic!=InpTestedMagic) || (InpTestedSymbol!="" && symbol!=InpTestedSymbol)) continue; //--- получаем торговый объект по имени символа и вызываем его метод трейлинга позиции по тикету 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() { //--- работаем только в тестере стратегий if(!MQLInfoInteger(MQL_TESTER)) return; //--- Тралим открытые позиции Trailing(); //--- Обрабатываем список сделок из файла 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.





- 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.