Como criar qualquer tipo de Trailing Stop e anexá-lo ao EA
Conteúdo
- Introdução
- Classe base de trailing
- Classes de trailing por indicadores
- Classe de trailing por níveis de StopLoss especificados
- Anexando o trailing stop ao EA
- Considerações finais
Introdução
Dando continuidade ao tema do trailing stop, iniciado no artigo anterior, hoje vamos analisar classes de trailing para facilitar a criação de vários algoritmos de trailing do StopLoss de posições. Com base nas classes criadas, será possível desenvolver qualquer algoritmo de ajuste de stops: por deslocamento do stop em relação ao preço atual, por indicadores, por níveis de StopLoss especificados, etc. Após a leitura do artigo, poderemos criar e anexar a qualquer EA qualquer algoritmo de ajuste de stops das posições. Além disso, a conexão e o uso do trailing serão fáceis e claros.
Vamos rever brevemente o algoritmo de funcionamento de um trailing stop. Vamos estabelecer que, para cada trailing, podem ser usadas três condições de operação:
- início do trailing — número de pontos de lucro da posição, ao atingir o qual o trailing stop é ativado;
- passo do trailing — número de pontos que o preço deve percorrer na direção do lucro da posição para o próximo ajuste do StopLoss da posição;
- distância do trailing — distância em relação ao preço atual na qual o StopLoss é mantido.
Esses três parâmetros podem ser aplicados a qualquer trailing. Qualquer um desses parâmetros pode estar presente nas configurações do trailing ou ausente, se não for necessário ou substituído por algum valor no algoritmo de trailing. Um exemplo de substituição do parâmetro "distância do trailing" pode ser o valor do indicador, onde o stop-loss da posição é definido. Nesse caso, se esse parâmetro for ativado, o stop será definido não no preço indicado pelo indicador, mas com um desvio do preço indicado no valor da distância em pontos.
Em geral, esses três parâmetros são os mais usados em diferentes trailing stops, e os consideraremos ao criar as classes de trailing.
Ao mover o stop-loss da posição para o preço exigido, devem ser feitas verificações:
- o preço do StopLoss não deve estar mais próximo do preço atual do que o nível de StopLevel do símbolo (SYMBOL_TRADE_STOPS_LEVEL — desvio mínimo em pontos do preço atual de fechamento para definir ordens Stop);
- o preço do StopLoss não deve ser igual ao já definido, e deve ser maior que o nível atual do stop-loss para uma posição longa e menor para uma posição curta;
- se os parâmetros mencionados acima forem usados no algoritmo de trailing, também será necessário verificar as condições estabelecidas por esses parâmetros.
Essas são verificações básicas. Qualquer algoritmo de trailing funciona da mesma forma: o preço necessário para definir o stop é passado como entrada, todas as verificações necessárias são feitas e, se forem aprovadas, o stop da posição é movido para o nível indicado.
Um trailing simples pode funcionar assim:
- no parâmetro "início do trailing", o lucro em pontos é especificado, ao atingir o qual o trailing deve ser ativado, ou zero, se esse parâmetro não for usado;
- no parâmetro "passo do trailing", é indicado quantos pontos de lucro devem ser percorridos para ajustar o stop atrás do preço, ou zero, se esse parâmetro não for usado;
- no parâmetro "distância do trailing", é definida a distância entre o preço atual e o stop da posição, na qual o stop deve ser mantido, ou zero, se esse parâmetro não for usado;
- também é possível definir o símbolo e o magic das posições que precisam ser trailadas, ou NULL e -1, respectivamente, se todas as posições de todos os símbolos e com qualquer magic precisarem ser trailadas.
Ao atingir o número de pontos de lucro da posição especificado, o trailing é iniciado, e o stop da posição é definido a uma distância especificada do preço atual. Em seguida, depois que o preço percorre o número de pontos indicado em direção ao lucro da posição (passo do trailing), o stop da posição é novamente ajustado para manter a distância especificada. E assim continuamente até que o preço se mova contra a posição. Nesse caso, o stop permanece no nível já definido. Assim que o preço atinge esse nível, a posição é fechada com lucro pelo StopLoss. Assim, o trailing stop permite manter o lucro, fechando a posição quando o preço inverte, e ao mesmo tempo permitindo que o preço "se mova" dentro do desvio de stop especificado, ajustando o stop passo a passo atrás do preço, se ele se mover na direção da posição.
Em vez do parâmetro "distância do trailing", pode ser especificado o valor de um preço obtido, por exemplo, de um indicador. Nesse caso, obtemos um trailing pelo indicador. Pode-se passar o valor de uma das velas anteriores (High, Low, por exemplo) — assim obtemos um trailing pelos preços das barras, e assim por diante. Mas o trailing básico — seu algoritmo — permanece o mesmo.
Portanto, para criar qualquer algoritmo de trailing stop, primeiro precisamos criar um trailing simples, que permita passar para ele os preços necessários para os stops das posições, e mova o StopLoss da posição para esse nível com todas as verificações necessárias.
No artigo anterior, já analisamos as funções anexáveis, onde havia um trailing simples e trailing por vários indicadores. Essa abordagem permite anexar o trailing stop ao EA para trailing das posições do símbolo atual. Mas há também limitações: para cada trailing por indicador, no próprio EA, é necessário criar o indicador e enviar seu handle para a função de trailing. As próprias funções de trailing podem trabalhar apenas com as posições do símbolo atual.
Se usarmos classes de trailing, podemos criar várias instâncias de uma classe de trailing com diferentes configurações, e então, em um único EA, todos os trailings criados dessa forma poderão funcionar simultaneamente, com um algoritmo específico definido pelo programador. Ou seja, para cada conjunto de parâmetros, praticamente "com algumas linhas de código", será criado um trailing próprio, que funcionará com seu próprio algoritmo. Cada trailing criado pode ser executado no EA sob certas condições, criando algoritmos complexos de trailing de StopLoss das posições.
Mesmo que se criem dois trailings idênticos, mas para símbolos diferentes, cada símbolo terá seu próprio trailing, que funcionará com os dados daquele símbolo especificado ao criar o trailing. Isso simplifica bastante o uso de trailing stops em seus programas: basta criar um trailing com os parâmetros necessários e chamá-lo nos manipuladores do EA. Justiça seja feita, deve-se notar que essa abordagem, em alguns algoritmos, pode ser subótima, pois para cada tipo de trailing é realizada uma verificação separada das posições existentes no terminal. Se quisermos desenvolver um algoritmo realmente universal, que utilize a mesma lista de posições do terminal para cada trailing, pode acontecer que os custos de seu projeto e desenvolvimento não sejam comparáveis à sua demanda. Além disso, isso foge ao escopo deste artigo.
Portanto, para começar, precisamos escrever a base: uma classe simples de trailing stop que mova o stop-loss das posições para o preço especificado, com todas as verificações necessárias.
No artigo anterior, esse trailing simples básico foi chamado de TrailingStopByValue(). Aqui, o chamaremos de SimpleTrailing — "Trailing Simples". E ele poderá ser totalmente utilizado em seus programas.
Como no artigo anterior, todas as classes de trailing serão organizadas em um único arquivo. Esse arquivo é simplesmente anexado ao EA e utilizado. Normalmente, os arquivos anexáveis são armazenados na pasta \MQL5\Include\ ou em uma subpasta deste diretório.
Classe base de trailing
Na pasta do terminal \MQL5\Include\, criaremos uma nova subpasta Trailings\, e nela, um novo arquivo de classe com o nome Trailings.mqh:
O nome da classe deve ser CSimpleTrailing, o nome do arquivo criado será Trailings.mqh, e a classe base será a classe base de objeto da Biblioteca Padrão, CObject:
Após a conclusão do assistente MQL, obtemos a seguinte estrutura da classe na pasta \MQL5\Include\Trailings\Trailings.mqh:
//+------------------------------------------------------------------+ //| Trailings.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" class CSimpleTrailing : public CObject { private: public: CSimpleTrailing(); ~CSimpleTrailing(); }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CSimpleTrailing::CSimpleTrailing() { } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CSimpleTrailing::~CSimpleTrailing() { } //+------------------------------------------------------------------+
Anexamos o arquivo do objeto base da Biblioteca Padrão ao arquivo criado:
//+------------------------------------------------------------------+ //| Trailings.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 <Object.mqh> //+------------------------------------------------------------------+ //| Class of the position StopLoss simple trailing | //+------------------------------------------------------------------+ class CSimpleTrailing : public CObject
Por que é necessário herdar todas as classes de trailing criadas do objeto base da Biblioteca Padrão?
Isso permitirá criar facilmente coleções de trailing a partir dessas classes — colocá-las em listas, procurar os objetos necessários nas listas, etc. Ou seja, se quisermos apenas usar futuros objetos de classes como instâncias em arquivos de EA, não é necessário herdar de CObject. Mas se quisermos criar coleções completas a partir dessas classes e usar todas as funcionalidades da Biblioteca Padrão, essas classes devem ser herdeiras da classe base CObject:
A classe CObject é a classe base para a construção da biblioteca padrão do MQL5 e oferece a todos os seus descendentes a capacidade de ser um elemento de uma lista encadeada.
Além disso, vários métodos virtuais são definidos para implementação futura nas classes derivadas.
Na seção privada da classe, declararemos três métodos:
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:
O método CheckCriterion() retornará uma flag indicando que todas as verificações necessárias para a modificação do StopLoss da posição foram aprovadas (ou não).
O método ModifySL() modificará o stop-loss da posição de acordo com o valor especificado.
O método StopLevel() retornará o valor da propriedade StopLevel do símbolo.
Todos esses métodos, no artigo anterior, eram funções separadas. Agora, eles trabalharão dentro da classe de trailing base, da qual herdaremos outras classes de trailing.
Na seção protegida da classe, declararemos todas as variáveis necessárias para o funcionamento da classe:
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:
Das variáveis declaradas aqui, apenas o "multiplicador do spread" exige explicação. O valor StopLevel do símbolo é a distância até o preço atual, abaixo da qual não é permitido definir um stop-loss. Se o valor StopLevel estiver definido como zero no servidor, isso não significa a ausência de um StopLevel. Isso significa que o nível de stop é flutuante e geralmente equivale a dois spreads. Às vezes, três. Depende das configurações do servidor. Por isso, será possível definir esse multiplicador manualmente aqui. Se for utilizado o spread duplo para o valor StopLevel, o valor da variável m_spread_mlt será igual a dois (por padrão). Se for o spread triplo, então três, e assim por diante.
O método virtual GetStopLossValue() calcula e retorna o preço StopLoss para a posição. Em diferentes tipos de trailing stops, o nível do stop-loss para o qual o stop da posição deve ser definido é calculado de maneira diferente. Por essa razão, esse método é declarado como virtual, e deve ser substituído em cada classe derivada de trailing se o cálculo do stop-loss for diferente do que será escrito neste método desta classe.
Na seção pública da classe, escreveremos métodos para definir e retornar os valores das variáveis declaradas na seção protegida da classe, além dos construtores e do destrutor da classe:
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); //--- 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() {} };
A classe terá dois construtores:
- O primeiro construtor será padrão, onde o símbolo utilizado será o atual, o magic será definido como -1 (para todas as posições do símbolo atual), e os parâmetros do trailing serão definidos como zero.
- O segundo construtor será paramétrico, onde em seus parâmetros formais serão passados o nome do símbolo, o valor do magic das posições e os três parâmetros do trailing — início, passo e distância.
Vejamos a implementação do construtor paramétrico:
//+------------------------------------------------------------------+ //| Parametric constructor | //+------------------------------------------------------------------+ CSimpleTrailing::CSimpleTrailing(const string symbol, const long magic, const int trail_start, const uint trail_step, const int offset) : m_spread_mlt(2) { this.SetSymbol(symbol); this.m_magic = magic; this.m_trail_start= trail_start; this.m_trail_step = trail_step; this.m_offset = offset; }
Ao contrário do construtor padrão, onde na linha de inicialização todos os valores de variáveis são definidos para o símbolo atual e valores de trailing zerados, aqui os valores são passados para o construtor e atribuídos às variáveis correspondentes. O método SetSymbol() define os valores de várias variáveis de uma vez:
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); }
Em ambos os construtores, o multiplicador de spread é definido como 2. Se necessário, ele pode ser alterado pelo método SetSpreadMultiplier().
Método virtual que calcula e retorna o nível de StopLoss da posição selecionada:
//+------------------------------------------------------------------+ //| Calculate and return the StopLoss level of the selected position | //+------------------------------------------------------------------+ double CSimpleTrailing::GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick) { //--- calculate and return the StopLoss level depending on the position type switch(pos_type) { case POSITION_TYPE_BUY : return(tick.bid - this.m_offset * this.m_point); case POSITION_TYPE_SELL : return(tick.ask + this.m_offset * this.m_point); default : return 0; } }
Como este é um trailing simples, aqui o stop-loss da posição será sempre mantido a uma distância especificada do preço atual.
Os valores de StopLoss calculados para as posições são retornados pelo método e verificados em seguida no método CheckCriterion():
//+------------------------------------------------------------------+ //|Check the StopLoss modification criteria and return a flag | //+------------------------------------------------------------------+ bool CSimpleTrailing::CheckCriterion(ENUM_POSITION_TYPE pos_type, double pos_open, double pos_sl, double value_sl, MqlTick &tick) { //--- if the stop position and the stop level for modification are equal or a zero StopLoss is passed, return 'false' if(::NormalizeDouble(pos_sl - value_sl, this.m_digits) == 0 || value_sl==0) return false; //--- trailing variables double trailing_step = this.m_trail_step * this.m_point; // convert the trailing step into price double stop_level = this.StopLevel() * this.m_point; // convert the symbol StopLevel into price int pos_profit_pt = 0; // position profit in points //--- depending on the type of position, check the conditions for modifying StopLoss switch(pos_type) { //--- long position case POSITION_TYPE_BUY : pos_profit_pt = int((tick.bid - pos_open) / this.m_point); // calculate the position profit in points if(tick.bid - stop_level > value_sl // if the StopLoss level is lower than the price with the StopLevel level set down from it (the distance according to StopLevel is maintained) && pos_sl + trailing_step < value_sl // if the StopLoss level exceeds the trailing step set upwards from the current position StopLoss && (this.m_trail_start == 0 || pos_profit_pt > this.m_trail_start) // if we trail at any profit or position profit in points exceeds the trailing start, return 'true' ) return true; break; //--- short position case POSITION_TYPE_SELL : pos_profit_pt = int((pos_open - tick.ask) / this.m_point); // calculate the position profit in points if(tick.ask + stop_level < value_sl // if the StopLoss level is higher than the price with the StopLevel level set upwards from it (the distance according to StopLevel is maintained) && (pos_sl - trailing_step > value_sl || pos_sl == 0) // if the StopLoss level is below the trailing step set downwards from the current StopLoss or a position has no StopLoss && (this.m_trail_start == 0 || pos_profit_pt > this.m_trail_start) // if we trail at any profit or position profit in points exceeds the trailing start, return 'true' ) return true; break; //--- return 'false' by default default: break; } //--- conditions are not met - return 'false' return false; }
Se o valor de StopLoss passado para o método atender a todos os critérios de todos os filtros, o método retornará true, e o stop-loss da posição será modificado usando o método ModifySL():
//+------------------------------------------------------------------+ //| Modify StopLoss of a position by ticket | //+------------------------------------------------------------------+ bool CSimpleTrailing::ModifySL(const ulong ticket, const double stop_loss) { //--- if the EA stop flag is set, report this in the journal and return 'false' if(::IsStopped()) { Print("The Expert Advisor is stopped, trading is disabled"); return false; } //--- if failed to select a position by ticket, report this in the journal and return 'false' ::ResetLastError(); if(!::PositionSelectByTicket(ticket)) { ::PrintFormat("%s: Failed to select position by ticket number %I64u. Error %d", __FUNCTION__, ticket, ::GetLastError()); return false; } //--- declare the structures of the trade request and the request result MqlTradeRequest request= {}; MqlTradeResult result = {}; //--- fill in the request structure request.action = TRADE_ACTION_SLTP; request.symbol = ::PositionGetString(POSITION_SYMBOL); request.magic = ::PositionGetInteger(POSITION_MAGIC); request.tp = ::PositionGetDouble(POSITION_TP); request.position = ticket; request.sl = ::NormalizeDouble(stop_loss, this.m_digits); //--- if the trade operation could not be sent, report this to the journal and return 'false' if(!::OrderSend(request, result)) { PrintFormat("%s: OrderSend() failed to modify position #%I64u. Error %d",__FUNCTION__, ticket, ::GetLastError()); return false; } //--- request to change StopLoss position successfully sent return true; }
Para obter o nível de stop, usamos o método StopLevel():
//+------------------------------------------------------------------+ //| Return StopLevel in points | //+------------------------------------------------------------------+ int CSimpleTrailing::StopLevel(void) { int spread = (int)::SymbolInfoInteger(this.m_symbol, SYMBOL_SPREAD); int stop_level = (int)::SymbolInfoInteger(this.m_symbol, SYMBOL_TRADE_STOPS_LEVEL); return int(stop_level == 0 ? spread * this.m_spread_mlt : stop_level); }
Como o nível de stop é uma variável não constante e pode mudar dinamicamente, cada vez que chamamos o método, solicitamos os valores necessários das propriedades do símbolo. Se o StopLevel do símbolo estiver definido como zero, usamos o valor do spread do símbolo multiplicado por 2. Esse valor é definido por padrão.
Todos os três métodos mencionados anteriormente já foram implementados como funções separadas no artigo anterior. Agora, esses métodos estão ocultos na seção privada da classe e executam suas funções sem acesso externo a eles.
O principal método do trailing é o método Run(), que inicia o ciclo de seleção de posições e move seus stops para os valores calculados:
//+------------------------------------------------------------------+ //| 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; }
O método percorre as posições na lista de posições ativas do terminal, filtra-as por símbolo e magic, e move os stops das posições para a distância calculada a partir do preço atual. O resultado da modificação do StopLoss de cada posição é adicionado à variável res. Ao final do ciclo de modificação dos stops de todas as posições, a variável conterá o valor true — apenas se a modificação dos stops de todas as posições filtradas por símbolo/magic for bem-sucedida. Caso contrário, se ao menos uma posição não for modificada com sucesso, o valor será false. Isso é feito para fornecer controle adicional sobre a modificação dos stops externamente.
Agora, vamos anexar essa classe ao EA para verificar seu funcionamento.
Para o teste, vamos usar o EA Moving Average.mq5 da pasta \MQL5\Experts\Examples\ do terminal e salvá-lo com o nome MovingAverageWithSimpleTrail.mq5.
Anexamos o arquivo da classe de trailing ao EA, inserimos parâmetros adicionais nas configurações e criamos uma instância da classe SimpleTrailing:
//+------------------------------------------------------------------+ //| MovingAverageWithSimpleTrail.mq5 | //| Copyright 2000-2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #include <Trade\Trade.mqh> #include <Trailings\Trailings.mqh> input group " - Moving Averages Expert Parameters -" input double MaximumRisk = 0.02; // Maximum Risk in percentage input double DecreaseFactor = 3; // Descrease factor input int MovingPeriod = 12; // Moving Average period input int MovingShift = 6; // Moving Average shift input group " - Simple Trailing Parameters -" input int InpTrailingStart = 10; // Trailing start input int InpTrailingStep = 20; // Trailing step in points input int InpTrailingOffset = 30; // Trailing offset in points input bool InpUseTrailing = true; // Use Trailing Stop //--- int ExtHandle=0; bool ExtHedging=false; CTrade ExtTrade; CSimpleTrailing ExtTrailing; // simple trailing class instance #define MA_MAGIC 1234501 //+------------------------------------------------------------------+
No manipulador OnInit(), definimos os parâmetros do trailing:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(void) { //--- prepare trade class to control positions if hedging mode is active ExtHedging=((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING); ExtTrade.SetExpertMagicNumber(MA_MAGIC); ExtTrade.SetMarginMode(); ExtTrade.SetTypeFillingBySymbol(Symbol()); //--- Moving Average indicator ExtHandle=iMA(_Symbol,_Period,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE); if(ExtHandle==INVALID_HANDLE) { printf("Error creating MA indicator"); return(INIT_FAILED); } //--- set trailing parameters ExtTrailing.SetActive(InpUseTrailing); ExtTrailing.SetSymbol(Symbol()); ExtTrailing.SetMagicNumber(MA_MAGIC); ExtTrailing.SetTrailingStart(InpTrailingStart); ExtTrailing.SetTrailingStep(InpTrailingStep); ExtTrailing.SetStopLossOffset(InpTrailingOffset); //--- ok return(INIT_SUCCEEDED); }
No manipulador OnTick(), iniciamos o trailing:
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick(void) { //--- if(SelectPosition()) CheckForClose(); else CheckForOpen(); //--- launch trailing ExtTrailing.Run(); }
Isso é tudo o que precisa ser adicionado ao arquivo do EA. Vamos compilá-lo e executá-lo no testador de estratégias no gráfico EURUSD, M15, em um teste individual do último ano. Todos os ticks sem atrasos de execução.
Os parâmetros serão definidos como segue (trailing desativado):
Executamos o teste com o trailing desativado e observamos o resultado:
Agora, ativamos o uso do trailing:
e executamos o mesmo teste novamente.
O resultado do teste foi o seguinte:
O resultado foi um pouco melhor. Percebe-se que o trailing melhora um pouco as estatísticas com essas configurações.
Agora, com base no trailing simples criado, podemos desenvolver qualquer outro tipo de trailing.
Vamos criar algumas classes de trailing por indicadores — pelo indicador Parabolic SAR e por diferentes médias móveis da lista de indicadores de tendência disponíveis no terminal do cliente.
Classes de trailing por indicadores
Vamos continuar escrevendo o código no mesmo arquivo. Mas antes de iniciar uma nova classe de trailing por indicador, lembremos que, para trailing baseado em valores de indicadores, é necessário criar o próprio indicador, a partir do qual obteremos os valores dos níveis de stop-loss. Para os indicadores, ao criá-los, é necessário especificar o símbolo e o timeframe, sobre cujos dados o indicador construirá sua série de dados. Além disso, é necessário armazenar o handle do indicador em uma variável, para podermos acessá-lo e obter os dados. A obtenção dos dados pelo handle independe do tipo de indicador — os dados são obtidos usando a função CopyBuffer() com o handle do indicador.
Todas as ações de obtenção de dados do indicador devem ser escritas nas classes de trailing por indicadores. Mas, nesse caso, seria necessário repetir essas ações em cada classe de trailing para diferentes indicadores. Isso é possível, mas não é ideal. É melhor criar uma classe base de trailing por indicadores, onde os métodos para obter dados do indicador pelo handle serão implementados, e algumas variáveis comuns a todos os indicadores (símbolo, timeframe, índice da série de dados e handle do indicador) serão definidas. As classes de trailing seriam herdadas dessa classe base. Assim, a estrutura da classe ficaria organizada, sendo que a classe base gerencia o acesso aos dados do indicador pelo handle, enquanto as classes herdadas criam o indicador necessário e trabalham com seus dados.
Vamos continuar escrevendo o código no arquivo Trailings.mqh. Vamos escrever a classe base de trailing por indicadores:
//+------------------------------------------------------------------+ //| Base class of indicator-based trailings | //+------------------------------------------------------------------+ class CTrailingByInd : public CSimpleTrailing { protected: ENUM_TIMEFRAMES m_timeframe; // indicator timeframe int m_handle; // indicator handle uint m_data_index; // indicator data bar string m_timeframe_descr; // timeframe description //--- return indicator data double GetDataInd(void) const { return this.GetDataInd(this.m_data_index); } //--- calculate and return the StopLoss level of the selected position virtual double GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick); public: //--- set parameters void SetTimeframe(const ENUM_TIMEFRAMES timeframe) { this.m_timeframe=(timeframe==PERIOD_CURRENT ? ::Period() : timeframe); this.m_timeframe_descr=::StringSubstr(::EnumToString(this.m_timeframe), 7); } void SetDataIndex(const uint index) { this.m_data_index=index; } //--- return parameters ENUM_TIMEFRAMES Timeframe(void) const { return this.m_timeframe; } uint DataIndex(void) const { return this.m_data_index; } string TimeframeDescription(void) const { return this.m_timeframe_descr; } //--- return indicator data from the specified timeseries index double GetDataInd(const int index) const; //--- constructors CTrailingByInd(void) : CSimpleTrailing(::Symbol(), -1, 0, 0, 0), m_timeframe(::Period()), m_handle(INVALID_HANDLE), m_data_index(1), m_timeframe_descr(::StringSubstr(::EnumToString(::Period()), 7)) {} CTrailingByInd(const string symbol, const ENUM_TIMEFRAMES timeframe, 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_data_index(1), m_handle(INVALID_HANDLE) { this.SetTimeframe(timeframe); } //--- destructor ~CTrailingByInd(void) { if(this.m_handle!=INVALID_HANDLE) { ::IndicatorRelease(this.m_handle); this.m_handle=INVALID_HANDLE; } } };
Essa classe herda da classe SimpleTrailing, portanto, tem disponível toda a funcionalidade dessa classe, além dos métodos para acessar os dados do indicador pelo handle.
Na seção protegida da classe, estão declaradas as variáveis da classe, o método para obter dados do indicador pelo handle e um método virtual para calcular os níveis de StopLoss, que sobrescreve o método de mesmo nome da classe pai.
Na seção pública da classe, estão localizados os métodos de definição e obtenção das propriedades do indicador, além dos construtores e do destrutor.
No construtor padrão, na linha de inicialização, o símbolo atual, o magic com valor -1 (todas as posições) e parâmetros de trailing zerados são passados para a classe pai. O timeframe do gráfico atual é definido como o período para o cálculo do indicador.
No construtor paramétrico, o símbolo e o período do gráfico para o cálculo do indicador, assim como todos os parâmetros do trailing, são passados nos parâmetros formais do construtor.
No destrutor da classe, a parte de cálculo do indicador é liberada, e a variável que armazena o handle recebe o valor INVALID_HANDLE.
Método que retorna os dados do indicador no índice da série de dados especificado:
//+------------------------------------------------------------------+ //| Return indicator data from the specified timeseries index | //+------------------------------------------------------------------+ double CTrailingByInd::GetDataInd(const int index) const { //--- if the handle is invalid, report this and return "empty value" if(this.m_handle==INVALID_HANDLE) { ::PrintFormat("%s: Error. Invalid handle",__FUNCTION__); return EMPTY_VALUE; } //--- get the value of the indicator buffer by the specified index double array[1]; ::ResetLastError(); if(::CopyBuffer(this.m_handle, 0, index, 1, array)!=1) { ::PrintFormat("%s: CopyBuffer() failed. Error %d", __FUNCTION__, ::GetLastError()); return EMPTY_VALUE; } //--- return the received value return array[0]; }
Com CopyBuffer(), obtemos o valor de uma barra do indicador no índice especificado e retornamos esse valor se a obtenção for bem-sucedida. Em caso de erro, uma mensagem é exibida no log e um valor vazio (EMPTY_VALUE) é retornado.
A função CopyBuffer() funciona com os dados de qualquer indicador — pelo seu handle. Assim, esse método é universal e pode ser usado sem modificações em qualquer classe de trailing por indicadores.
Método virtual que calcula e retorna o nível de StopLoss da posição selecionada:
//+------------------------------------------------------------------+ //| Calculate and return the StopLoss level of the selected position | //+------------------------------------------------------------------+ double CTrailingByInd::GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick) { //--- get the indicator value as a level for StopLoss double data=this.GetDataInd(); //--- calculate and return the StopLoss level depending on the position type switch(pos_type) { case POSITION_TYPE_BUY : return(data!=EMPTY_VALUE ? data - this.m_offset * this.m_point : tick.bid); case POSITION_TYPE_SELL : return(data!=EMPTY_VALUE ? data + this.m_offset * this.m_point : tick.ask); default : return 0; } }
Nesse método, na classe pai, o cálculo da distância do nível de stop-loss em relação ao preço é realizado. Nesta classe, no entanto, será necessário obter os dados do indicador e usá-los como o nível de StopLoss. Se os dados do indicador não forem obtidos, o método retornará o preço atual, o que impedirá a colocação do StopLoss da posição, pois não é permitido definir o stop no preço atual devido ao nível StopLevel.
Como resultado, essa classe implementa o acesso aos dados do indicador via seu handle e o cálculo do nível para o StopLoss da posição com base no valor do indicador, e todos os seus métodos estarão disponíveis nas classes derivadas.
Agora, podemos criar classes de trailing com base no indicador utilizando essa classe base.
Classe de trailing por Parabolic SAR
Vamos continuar escrevendo o código no mesmo arquivo \MQL5\Include\Trailings\Trailings.mqh.
A classe de trailing por Parabolic SAR, como todas as outras classes de trailing baseadas em indicadores, deve ser herdada da classe base de trailing por indicadores.
//+------------------------------------------------------------------+ //| Parabolic SAR position StopLoss trailing class | //+------------------------------------------------------------------+ class CTrailingBySAR : public CTrailingByInd { private: double m_sar_step; // Parabolic SAR Step parameter double m_sar_max; // Parabolic SAR Maximum parameter public: //--- set Parabolic SAR parameters void SetSARStep(const double step) { this.m_sar_step=(step<0.0001 ? 0.0001 : step); } void SetSARMaximum(const double max) { this.m_sar_max =(max <0.0001 ? 0.0001 : max); } //--- return Parabolic SAR parameters double SARStep(void) const { return this.m_sar_step; } double SARMaximum(void) const { return this.m_sar_max; } //--- create Parabolic SAR indicator and return the result bool Initialize(const string symbol, const ENUM_TIMEFRAMES timeframe, const double sar_step, const double sar_maximum); //--- constructors CTrailingBySAR() : CTrailingByInd(::Symbol(), ::Period(), -1, 0, 0, 0) { this.Initialize(this.m_symbol, this.m_timeframe, 0.02, 0.2); } CTrailingBySAR(const string symbol, const ENUM_TIMEFRAMES timeframe, const long magic, const double sar_step, const double sar_maximum, const int trail_start, const uint trail_step, const int trail_offset); //--- destructor ~CTrailingBySAR(){} };
Na seção privada da classe, são declaradas variáveis para armazenar os valores dos parâmetros do Parabolic SAR.
Na seção pública, estão os métodos para definir e retornar os valores das propriedades do indicador, um método para criar o indicador e dois construtores — o padrão e o paramétrico.
No construtor padrão, o indicador é criado com base no símbolo e no período do gráfico atual, e os parâmetros do trailing são definidos como zero.
No construtor paramétrico, todos os parâmetros são passados pelas variáveis de entrada do construtor e, em seguida, o indicador é criado com os parâmetros fornecidos:
//+------------------------------------------------------------------+ //| Parametric constructor | //+------------------------------------------------------------------+ CTrailingBySAR::CTrailingBySAR(const string symbol, const ENUM_TIMEFRAMES timeframe, const long magic, const double sar_step, const double sar_maximum, const int trail_start, const uint trail_step, const int trail_offset) : CTrailingByInd(symbol, timeframe, magic, trail_start, trail_step, trail_offset) { this.Initialize(symbol, timeframe, sar_step, sar_maximum); }
O método Initialize() cria o Parabolic SAR e retorna o resultado da sua criação:
//+------------------------------------------------------------------+ //| create Parabolic SAR indicator and return the result | //+------------------------------------------------------------------+ bool CTrailingBySAR::Initialize(const string symbol, const ENUM_TIMEFRAMES timeframe, const double sar_step, const double sar_maximum) { this.SetSymbol(symbol); this.SetTimeframe(timeframe); this.SetSARStep(sar_step); this.SetSARMaximum(sar_maximum); ::ResetLastError(); this.m_handle=::iSAR(this.m_symbol, this.m_timeframe, this.m_sar_step, this.m_sar_max); if(this.m_handle==INVALID_HANDLE) { ::PrintFormat("Failed to create iSAR(%s, %s, %.3f, %.2f) handle. Error %d", this.m_symbol, this.TimeframeDescription(), this.m_sar_step, this.m_sar_max, ::GetLastError()); } return(this.m_handle!=INVALID_HANDLE); }
Vamos testar a classe de trailing por Parabolic SAR criada.
Para o teste, vamos usar o EA da distribuição padrão \MQL5\Experts\Advisors\ExpertMACD.mq5, salvá-lo com um novo nome ExpertMACDWithTrailingBySAR.mq5 e adicionar as linhas de código necessárias.
Anexamos o arquivo com as classes de trailing, adicionamos novas variáveis de entrada e declaramos uma instância da classe de trailing:
//+------------------------------------------------------------------+ //| ExpertMACD.mq5 | //| Copyright 2000-2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //+------------------------------------------------------------------+ //| Include | //+------------------------------------------------------------------+ #include <Expert\Expert.mqh> #include <Expert\Signal\SignalMACD.mqh> #include <Expert\Trailing\TrailingNone.mqh> #include <Expert\Money\MoneyNone.mqh> #include <Trailings\Trailings.mqh> //+------------------------------------------------------------------+ //| Inputs | //+------------------------------------------------------------------+ //--- inputs for expert input group " - ExpertMACD Parameters -" input string Inp_Expert_Title ="ExpertMACD"; int Expert_MagicNumber =10981; bool Expert_EveryTick =false; //--- inputs for signal input int Inp_Signal_MACD_PeriodFast =12; input int Inp_Signal_MACD_PeriodSlow =24; input int Inp_Signal_MACD_PeriodSignal=9; input int Inp_Signal_MACD_TakeProfit =50; input int Inp_Signal_MACD_StopLoss =20; input group " - Trailing By SAR Parameters -" input ENUM_TIMEFRAMES InpTimeframeSAR = PERIOD_CURRENT; // Parabolic SAR Timeframe input double InpStepSAR = 0.02; // Parabolic SAR Step input double InpMaximumSAR = 0.2; // Parabolic SAR Maximum input int InpTrailingStart = 0; // Trailing Start input uint InpTrailingStep = 0; // Trailing Step input int InpTrailingOffset = 0; // Trailing Offset input bool InpUseTrailing = true; // Use Trailing Stop //+------------------------------------------------------------------+ //| Global expert object | //+------------------------------------------------------------------+ CExpert ExtExpert; CTrailingBySAR ExtTrailing;
No manipulador OnInit(), inicializamos o trailing pelo Parabolic SAR:
//+------------------------------------------------------------------+ //| Initialization function of the expert | //+------------------------------------------------------------------+ int OnInit(void) { //--- Initializing trailing if(ExtTrailing.Initialize(NULL,PERIOD_CURRENT,InpStepSAR,InpMaximumSAR)) { ExtTrailing.SetMagicNumber(Expert_MagicNumber); ExtTrailing.SetTrailingStart(InpTrailingStart); ExtTrailing.SetTrailingStep(InpTrailingStep); ExtTrailing.SetStopLossOffset(InpTrailingOffset); ExtTrailing.SetActive(InpUseTrailing); } //--- Initializing expert
No manipulador OnTick(), iniciamos o trailing:
//+------------------------------------------------------------------+ //| Function-event handler "tick" | //+------------------------------------------------------------------+ void OnTick(void) { ExtExpert.OnTick(); ExtTrailing.Run(); }
Compilamos o EA e o executamos no testador com os parâmetros padrão:
Vemos que os stops das posições estão sendo ajustados corretamente conforme os valores da primeira barra do Parabolic SAR.
Agora, vamos criar classes de trailing baseadas nas médias móveis disponíveis no terminal do cliente.
Classes de trailing por médias móveis
Essas classes diferem da classe de trailing por Parabolic SAR apenas no conjunto de parâmetros de entrada e no fato de que, no método Initialize(), é criado o indicador correspondente à classe.
Com base nisso, vamos analisar apenas uma classe: a classe de trailing por média móvel adaptativa:
//+------------------------------------------------------------------+ //| Adaptive Moving Average position StopLoss trailing class | //+------------------------------------------------------------------+ class CTrailingByAMA : public CTrailingByInd { private: int m_period; // Period AMA parameter int m_fast_ema; // Fast EMA Period parameter int m_slow_ema; // Slow EMA Period parameter int m_shift; // Shift AMA parameter ENUM_APPLIED_PRICE m_price; // Applied Price AMA parameter public: //--- set AMA parameters void SetPeriod(const uint period) { this.m_period=int(period<1 ? 9 : period); } void SetFastEMAPeriod(const uint period) { this.m_fast_ema=int(period<1 ? 2 : period); } void SetSlowEMAPeriod(const uint period) { this.m_slow_ema=int(period<1 ? 30 : period); } void SetShift(const int shift) { this.m_shift = shift; } void SetPrice(const ENUM_APPLIED_PRICE price) { this.m_price = price; } //--- return AMA parameters int Period(void) const { return this.m_period; } int FastEMAPeriod(void) const { return this.m_fast_ema; } int SlowEMAPeriod(void) const { return this.m_slow_ema; } int Shift(void) const { return this.m_shift; } ENUM_APPLIED_PRICE Price(void) const { return this.m_price; } //--- create AMA indicator and return the result bool Initialize(const string symbol, const ENUM_TIMEFRAMES timeframe, const int period, const int fast_ema, const int slow_ema, const int shift, const ENUM_APPLIED_PRICE price); //--- constructors CTrailingByAMA() : CTrailingByInd(::Symbol(), ::Period(), -1, 0, 0, 0) { this.Initialize(this.m_symbol, this.m_timeframe, 9, 2, 30, 0, PRICE_CLOSE); } CTrailingByAMA(const string symbol, const ENUM_TIMEFRAMES timeframe, const long magic, const int period, const int fast_ema, const int slow_ema, const int shift, const ENUM_APPLIED_PRICE price, const int trail_start, const uint trail_step, const int trail_offset); //--- destructor ~CTrailingByAMA(){} }; //+------------------------------------------------------------------+ //| Parametric constructor | //+------------------------------------------------------------------+ CTrailingByAMA::CTrailingByAMA(const string symbol, const ENUM_TIMEFRAMES timeframe, const long magic, const int period, const int fast_ema, const int slow_ema, const int shift, const ENUM_APPLIED_PRICE price, const int trail_start, const uint trail_step, const int trail_offset) : CTrailingByInd(symbol, timeframe, magic, trail_start, trail_step, trail_offset) { this.Initialize(symbol, timeframe, period, fast_ema, slow_ema, shift, price); } //+------------------------------------------------------------------+ //| create AMA indicator and return the result | //+------------------------------------------------------------------+ bool CTrailingByAMA::Initialize(const string symbol, const ENUM_TIMEFRAMES timeframe, const int period, const int fast_ema, const int slow_ema, const int shift, const ENUM_APPLIED_PRICE price) { this.SetSymbol(symbol); this.SetTimeframe(timeframe); this.SetPeriod(period); this.SetFastEMAPeriod(fast_ema); this.SetSlowEMAPeriod(slow_ema); this.SetShift(shift); this.SetPrice(price); ::ResetLastError(); this.m_handle=::iAMA(this.m_symbol, this.m_timeframe, this.m_period, this.m_fast_ema, this.m_slow_ema, this.m_shift, this.m_price); if(this.m_handle==INVALID_HANDLE) { ::PrintFormat("Failed to create iAMA(%s, %s, %d, %d, %d, %s) handle. Error %d", this.m_symbol, this.TimeframeDescription(), this.m_period, this.m_fast_ema, this.m_slow_ema, ::StringSubstr(::EnumToString(this.m_price),6), ::GetLastError()); } return(this.m_handle!=INVALID_HANDLE); }
A classe é absolutamente idêntica à classe de trailing por Parabolic SAR discutida acima. Em cada classe de trailing por médias móveis, haverá um conjunto de variáveis para armazenar os parâmetros do indicador e os métodos correspondentes para definir e retornar os valores. Todas as classes de trailing por médias móveis são idênticas entre si, e você pode estudá-las revisando os códigos no arquivo de classes de trailing anexado, Trailings.mqh. Para testar a classe de trailing por média móvel simples, você pode executar o EA de teste do arquivo ExpertMACDWithTrailingByMA.mq5, também anexado no final do artigo.
Criamos as classes de trailing simples e de trailing por indicadores padrão disponíveis no terminal, adequadas para definir os níveis de StopLoss das posições.
No entanto, esses não são todos os tipos de trailing que podem ser criados usando as classes apresentadas. Há trailing stops que movem os stops das posições para níveis de preços específicos, indicados separadamente para posições longas e curtas. Um exemplo desse tipo de trailing seria o trailing pelos valores de High/Low das velas ou, por exemplo, pelo indicador de fractais.
Para implementar esse trailing, precisamos da capacidade de especificar separadamente o nível de StopLoss para cada tipo de posição. Vamos criar tal classe.
Classe de trailing por níveis de StopLoss especificados
Tudo o que é necessário para criar um trailing com base em valores especificados é, em vez de parâmetros de indicador, definir variáveis para armazenar os valores de StopLoss para posições longas e curtas. Em vez de obter dados do indicador, precisamos calcular os valores do StopLoss das posições com base nos valores dessas variáveis. Os valores devem ser inseridos nas variáveis pelo programa controlador, diretamente ao chamar o método Run() do trailing.
Vamos escrever essa classe:
//+------------------------------------------------------------------+ //| 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); //--- 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){} };
Percebe-se que a classe é praticamente idêntica a todas as classes de trailing por indicadores discutidas anteriormente. No entanto, aqui não há o método Initialize(), pois não é necessário criar nenhum indicador.
No método virtual que calcula e retorna o nível de StopLoss da posição selecionada, os valores para StopLoss são calculados a partir dos níveis estabelecidos para as posições longas e curtas:
//+------------------------------------------------------------------+ //| Calculate and return the StopLoss level of the selected position | //+------------------------------------------------------------------+ double CTrailingByValue::GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick) { //--- calculate and return the StopLoss level depending on the position type switch(pos_type) { case POSITION_TYPE_BUY : return(this.m_value_sl_long - this.m_offset * this.m_point); case POSITION_TYPE_SELL : return(this.m_value_sl_short + this.m_offset * this.m_point); default : return 0; } }
No método Run(), os valores de StopLoss para posições longas e curtas são passados para a classe através de seus parâmetros formais:
//+------------------------------------------------------------------+ //| Launch trailing with the specified StopLoss offset from the price| //+------------------------------------------------------------------+ bool CTrailingByValue::Run(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(); }
Primeiro, os valores passados são gravados nas variáveis da classe e, em seguida, o método Run() da classe pai é chamado. O método virtual GetStopLossValue(), sobrescrito nesta classe, será chamado da classe pai, e os stops das posições serão definidos para os valores calculados.
Anexando o trailing stop ao EA
Como anexar o trailing ao EA já foi discutido acima, durante os testes dos trailing stops por indicadores. Agora, vamos ver como anexar e executar um trailing stop baseado nos valores passados, especificamente pelos valores High e Low das velas.
O trailing será anexado ao EA da distribuição padrão \MQL5\Experts\Advisors\ExpertMACD.mq5. Vamos salvá-lo com um novo nome ExpertMACDWithTrailingByValue.mq5 e fazer as modificações necessárias.
Anexamos o arquivo com as classes de trailing ao EA, adicionamos variáveis de entrada para configurar o trailing e declaramos uma instância da classe de trailing por valores especificados:
//+------------------------------------------------------------------+ //| ExpertMACD.mq5 | //| Copyright 2000-2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //+------------------------------------------------------------------+ //| Include | //+------------------------------------------------------------------+ #include <Expert\Expert.mqh> #include <Expert\Signal\SignalMACD.mqh> #include <Expert\Trailing\TrailingNone.mqh> #include <Expert\Money\MoneyNone.mqh> #include <Trailings\Trailings.mqh> //+------------------------------------------------------------------+ //| Inputs | //+------------------------------------------------------------------+ //--- inputs for expert input group " - ExpertMACD Parameters -" input string Inp_Expert_Title ="ExpertMACD"; int Expert_MagicNumber =10981; bool Expert_EveryTick =false; //--- inputs for signal input int Inp_Signal_MACD_PeriodFast =12; input int Inp_Signal_MACD_PeriodSlow =24; input int Inp_Signal_MACD_PeriodSignal=9; input int Inp_Signal_MACD_TakeProfit =50; input int Inp_Signal_MACD_StopLoss =20; input group " - Trailing By Value Parameters -" input ENUM_TIMEFRAMES InpTimeframe = PERIOD_CURRENT; // Data Rates Timeframe input uint InpDataRatesIndex = 2; // Data Rates Index for StopLoss input uint InpTrailingStep = 0; // Trailing Step input int InpTrailingStart = 0; // Trailing Start input int InpTrailingOffset = 0; // Trailing Offset input bool InpUseTrailing = true; // Use Trailing Stop //+------------------------------------------------------------------+ //| Global expert object | //+------------------------------------------------------------------+ CExpert ExtExpert; CTrailingByValue ExtTrailing;
A variável de entrada Data Rates Timeframe é o período do gráfico a partir do qual serão extraídos os preços High para stops de posições curtas e Low para stops de posições longas.
A variável de entrada Data Rates Index for StopLoss é o índice da barra no gráfico com o período Data Rates Timeframe, a partir do qual os preços High e Low serão usados para definir os StopLoss das posições.
No manipulador OnInit() do EA, inicializamos os parâmetros do trailing:
//+------------------------------------------------------------------+ //| Initialization function of the expert | //+------------------------------------------------------------------+ int OnInit(void) { //--- Initializing trailing ExtTrailing.SetMagicNumber(Expert_MagicNumber); ExtTrailing.SetTrailingStart(InpTrailingStart); ExtTrailing.SetTrailingStep(InpTrailingStep); ExtTrailing.SetStopLossOffset(InpTrailingOffset); ExtTrailing.SetActive(InpUseTrailing); //--- Initializing expert
Serão trailadas apenas as posições cujos magics correspondem ao magic do EA, ou seja, trailaremos apenas as posições abertas por este EA.
No manipulador OnTick() do EA, obtemos os dados da barra com o índice especificado na variável de entrada InpDataRatesIndex, e iniciamos o trailing especificando os preços de StopLoss (High e Low da barra) para posições longas e curtas:
//+------------------------------------------------------------------+ //| Function-event handler "tick" | //+------------------------------------------------------------------+ void OnTick(void) { ExtExpert.OnTick(); MqlRates rates[1]={}; if(CopyRates(ExtTrailing.Symbol(),InpTimeframe,InpDataRatesIndex,1,rates)) ExtTrailing.Run(rates[0].low,rates[0].high); }
Isso é tudo o que precisa ser feito para anexar o trailing ao EA. Como já deve ter sido percebido, a principal diferença ao anexar diferentes tipos de trailing a EAs está na declaração das instâncias dos diferentes tipos de trailing. Todas as outras ações para anexar os trailings são praticamente as mesmas e não devem gerar dúvidas ou dificuldades.
Vamos compilar o EA e executá-lo no modo visual do testador com as configurações padrão:
Vemos que os stops das posições são definidos nos valores de High e Low das velas com índice 2 na série temporal.
O quanto esse trailing pode melhorar o desempenho da estratégia de trading já é uma questão para testes individuais.
Além disso, qualquer pessoa pode criar seu próprio trailing stop com base em seu próprio algoritmo usando as classes apresentadas no artigo — o campo para experimentação é amplo e praticamente sem limites.
Considerações finais
Hoje, criamos classes de diferentes tipos de trailing que permitem anexar trailing stops facilmente a qualquer EA. As classes criadas também constituem um bom conjunto de ferramentas para desenvolver trailing stops com base em algoritmos próprios.
Nos exemplos, consideramos apenas uma maneira de trabalhar com objetos de classes — criando instâncias de objetos da classe de trailing. Essa abordagem permite determinar previamente, no código do programa, quais tipos de trailing serão necessários e quais parâmetros eles terão. Para cada trailing, é criado um objeto na área global. Essa abordagem é válida — simples e fácil de entender. No entanto, para criar objetos de trailing dinamicamente durante a execução do programa usando o operador new, é melhor aproveitar os recursos oferecidos pela Biblioteca Padrão para criar listas encadeadas de objetos. É para esses fins que todas as classes de trailing herdam o objeto base da Biblioteca Padrão, CObject. Esse método pode ser discutido nos comentários do artigo, pois já foge ao escopo deste tema.
Todas as classes apresentadas no artigo podem ser usadas "como estão" em seus próprios projetos ou podem ser ajustadas para atender às suas necessidades e objetivos.
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/14862
- 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