Automatização de estratégias de negociação em MQL5 (Parte 23): Recuperação por zonas com trailing stop e lógica de cestas
Introdução
No artigo anterior (Parte 22), desenvolvemos um sistema de recuperação por zonas (Zone Recovery System), implementado em MetaQuotes Language 5 (MQL5), para operações a favor da tendência baseadas nos indicadores RSI e Envelopes, que automatiza as operações e gerencia perdas por meio de zonas de recuperação bem definidas. Agora, vamos aprimorar essa estratégia com a inclusão de trailing stops para proteger os lucros dinamicamente e de um sistema de múltiplas cestas (multi-basket system) para processar com eficiência múltiplos sinais de negociação, aumentando sua capacidade de adaptação a mercados voláteis. Abordaremos os seguintes tópicos:
- Arquitetura aprimorada de trailing stops e múltiplas cestas
- Implementação em MQL5
- Testes com dados históricos
- Conclusão
Ao final, teremos um sistema de negociação em MQL5 aprimorado, com recursos adicionais e pronto para novos testes e ajustes.
Arquitetura aprimorada de trailing stops e múltiplas cestas
A estratégia de recuperação por zonas que vamos aperfeiçoar busca transformar perdas potenciais em lucro por meio da abertura de operações em sentido contrário, dentro de uma faixa de preços definida, quando o mercado se move contra nossas posições. Agora, vamos reforçá-la com duas melhorias principais: trailing stops e negociação com múltiplas cestas. Os trailing stops são importantes porque permitem proteger os lucros à medida que o mercado se move a nosso favor, sem encerrar as operações prematuramente. Isso é especialmente útil em mercados com tendências bem definidas, nos quais os preços podem subir significativamente. A negociação com múltiplas cestas também desempenha um papel importante, pois permite gerenciar simultaneamente vários sinais de negociação independentes. Assim, podemos aproveitar mais oportunidades de mercado sem comprometer o gerenciamento de risco de cada grupo de operações. Veja a figura abaixo.

Para implementar essas melhorias, vamos integrar um mecanismo de trailing stop que ajusta dinamicamente o nível de stop-loss conforme os movimentos do mercado, protegendo os lucros acumulados e permitindo que as operações continuem aproveitando movimentos favoráveis. Para viabilizar a negociação com múltiplas cestas, vamos desenvolver um sistema capaz de processar várias instâncias de MarketZoneTrader, cada uma responsável por uma cesta de operações identificada por um número mágico exclusivo. Isso permitirá acompanhar e gerenciar vários ciclos de recuperação por zonas simultaneamente, sem interferência entre eles. Pretendemos integrar esses recursos ao RSI e ao indicador Envelopes já utilizados na estratégia, para identificar com precisão os pontos de entrada nas operações. Com os trailing stops e o gerenciamento de cestas atuando em conjunto, poderemos melhorar a proteção dos lucros e ampliar as oportunidades de negociação, tornando a estratégia mais robusta e adaptável a diferentes condições de mercado. Vamos colocar essas melhorias em prática!
Implementação em MQL5
Para implementar essas melhorias em MQL5, vamos adicionar novos parâmetros de entrada para o trailing stop e renomear o parâmetro que limita o número máximo de ordens, já que agora trabalharemos com múltiplas instâncias de recuperação por zonas.
input group "======= EA GENERAL SETTINGS =======" input TradingLotSizeOptions lotOption = UNFIXED_LOTSIZE; // Lot Size Option input double initialLotSize = 0.01; // Initial Lot Size input double riskPercentage = 1.0; // Risk Percentage (%) input int riskPoints = 300; // Risk Points input int baseMagicNumber = 123456789; // Base Magic Number input int maxInitialPositions = 1; // Maximum Initial Positions (Baskets/Signals) input double zoneTargetPoints = 600; // Zone Target Points input double zoneSizePoints = 300; // Zone Size Points input bool enableInitialTrailing = true; // Enable Trailing Stop for Initial Positions input int trailingStopPoints = 50; // Trailing Stop Points input int minProfitPoints = 50; // Minimum Profit Points to Start Trailing
Começaremos a aprimorar nosso sistema de recuperação por zonas, implementado em MQL5, para operações a favor da tendência baseadas no indicador Envelopes, atualizando os parâmetros de entrada do grupo EA GENERAL SETTINGS (configurações gerais do EA) para oferecer suporte a trailing stops e à negociação com múltiplas cestas. Faremos quatro alterações principais nesses parâmetros. Primeiro, renomearemos magicNumber para baseMagicNumber e definiremos seu valor como 123456789. Esse valor servirá de base para gerar "números mágicos" exclusivos para cada cesta de operações, permitindo que o sistema acompanhe cada uma separadamente. Em segundo lugar, substituiremos maxOrders por maxInitialPositions, com valor definido como 1, para limitar o número de cestas iniciais e permitir o gerenciamento eficiente de múltiplos sinais de negociação.
Em terceiro lugar, adicionaremos o parâmetro booleano enableInitialTrailing, definido como true, que permitirá ativar ou desativar os trailing stops das posições iniciais, oferecendo controle sobre o novo recurso de proteção dos lucros. Em quarto lugar, definiremos trailingStopPoints como 50 e minProfitPoints também como 50. Esses parâmetros determinam, respectivamente, a distância do trailing stop e o lucro mínimo, em pontos, necessário para ativá-lo, permitindo proteger os lucros dinamicamente. Com essas alterações, nosso sistema poderá gerenciar múltiplas cestas de operações e proteger os lucros de forma eficiente, estabelecendo a base para novos aprimoramentos. Vamos destacar as alterações para facilitar sua identificação. Após a compilação, teremos o seguinte conjunto de parâmetros de entrada.

Depois de adicionar os parâmetros de entrada, podemos fazer uma declaração antecipada da classe MarketZoneTrader, para que a classe base possa referenciá-la, já que agora precisamos gerenciar múltiplas instâncias do sistema, cada uma associada a uma cesta de operações.
//--- Forward Declaration of MarketZoneTrader class MarketZoneTrader;
Neste ponto, introduzimos a declaração antecipada da classe MarketZoneTrader. Vamos posicioná-la antes da definição da classe BasketManager, que será apresentada em seguida. Assim, BasketManager poderá fazer referência a MarketZoneTrader sem que seja necessário fornecer sua definição completa nesse momento. Essa alteração é necessária porque nosso novo sistema de múltiplas cestas, gerenciado por BasketManager, precisará criar e gerenciar várias instâncias de MarketZoneTrader, cada uma responsável por uma cesta de operações diferente. Ao declarar MarketZoneTrader antecipadamente, garantimos que o compilador reconheça essa classe quando ela for referenciada em BasketManager, permitindo que o sistema gerencie vários ciclos de negociação simultaneamente. Em seguida, podemos definir a classe responsável por esse gerenciamento.
//--- Basket Manager Class to Handle Multiple Traders class BasketManager { private: MarketZoneTrader* m_traders[]; //--- Array of trader instances int m_handleRsi; //--- RSI indicator handle int m_handleEnvUpper; //--- Upper Envelopes handle int m_handleEnvLower; //--- Lower Envelopes handle double m_rsiBuffer[]; //--- RSI data buffer double m_envUpperBandBuffer[]; //--- Upper Envelopes buffer double m_envLowerBandBuffer[]; //--- Lower Envelopes buffer string m_symbol; //--- Trading symbol int m_baseMagicNumber; //--- Base magic number int m_maxInitialPositions; //--- Maximum baskets (signals) //--- Initialize Indicators bool initializeIndicators() { m_handleRsi = iRSI(m_symbol, PERIOD_CURRENT, 8, PRICE_CLOSE); if (m_handleRsi == INVALID_HANDLE) { Print("Failed to initialize RSI indicator"); return false; } m_handleEnvUpper = iEnvelopes(m_symbol, PERIOD_CURRENT, 150, 0, MODE_SMA, PRICE_CLOSE, 0.1); if (m_handleEnvUpper == INVALID_HANDLE) { Print("Failed to initialize upper Envelopes indicator"); return false; } m_handleEnvLower = iEnvelopes(m_symbol, PERIOD_CURRENT, 95, 0, MODE_SMA, PRICE_CLOSE, 1.4); if (m_handleEnvLower == INVALID_HANDLE) { Print("Failed to initialize lower Envelopes indicator"); return false; } ArraySetAsSeries(m_rsiBuffer, true); ArraySetAsSeries(m_envUpperBandBuffer, true); ArraySetAsSeries(m_envLowerBandBuffer, true); return true; } }
Para simplificar o gerenciamento das operações de cada cesta, vamos definir a classe BasketManager com membros privados responsáveis por gerenciar múltiplas instâncias de MarketZoneTrader e os dados dos indicadores. Criaremos m_traders, um array de ponteiros para instâncias de MarketZoneTrader, cada uma responsável por uma cesta de operações e seu respectivo ciclo de recuperação por zonas. Essa alteração é fundamental porque permitirá processar vários sinais de negociação simultaneamente, ao contrário da versão anterior, que utilizava uma única instância. Também declararemos os arrays m_handleRsi, m_handleEnvUpper e m_handleEnvLower para armazenar os handles dos indicadores, além de m_rsiBuffer, m_envUpperBandBuffer e m_envLowerBandBuffer para armazenar os dados do RSI e do indicador Envelopes. Com isso, transferiremos o gerenciamento dos indicadores de MarketZoneTrader para BasketManager, concentrando no BasketManager o gerenciamento dos indicadores utilizados por todas as cestas.
Também adicionaremos m_symbol para armazenar o símbolo de negociação, m_baseMagicNumber para gerar números mágicos exclusivos para cada cesta e m_maxInitialPositions para limitar a quantidade de cestas ativas, conforme o novo parâmetro de entrada maxInitialPositions. Na função initializeIndicators, configuraremos o RSI por meio de iRSI, com período 8, e o indicador Envelopes por meio de iEnvelopes, utilizando configurações de 150 períodos com desvio de 0.1 e de 95 períodos com desvio de 1.4. Verificaremos se os handles retornados são iguais a INVALID_HANDLE e registraremos eventuais erros usando Print. Em seguida, configuraremos m_rsiBuffer, m_envUpperBandBuffer e m_envLowerBandBuffer como arrays de séries temporais por meio de ArraySetAsSeries. Essa nova estrutura de classes permitirá coordenar várias cestas de operações de forma eficiente, centralizando os dados dos indicadores para garantir a geração consistente de sinais em todas elas. Depois, precisaremos implementar a lógica para contabilizar as posições individuais de cada cesta, facilitando seu acompanhamento e a remoção das cestas encerradas.
//--- Count Active Baskets int countActiveBaskets() { int count = 0; for (int i = 0; i < ArraySize(m_traders); i++) { if (m_traders[i] != NULL && m_traders[i].getCurrentState() != MarketZoneTrader::INACTIVE) { count++; } } return count; } //--- Cleanup Terminated Baskets void cleanupTerminatedBaskets() { int newSize = 0; for (int i = 0; i < ArraySize(m_traders); i++) { if (m_traders[i] != NULL && m_traders[i].getCurrentState() == MarketZoneTrader::INACTIVE) { delete m_traders[i]; m_traders[i] = NULL; } if (m_traders[i] != NULL) newSize++; } MarketZoneTrader* temp[]; ArrayResize(temp, newSize); int index = 0; for (int i = 0; i < ArraySize(m_traders); i++) { if (m_traders[i] != NULL) { temp[index] = m_traders[i]; index++; } } ArrayFree(m_traders); ArrayResize(m_traders, newSize); for (int i = 0; i < newSize; i++) { m_traders[i] = temp[i]; } ArrayFree(temp); }
Neste ponto, adicionaremos duas novas funções à classe BasketManager: countActiveBaskets e cleanupTerminatedBaskets. Começaremos por countActiveBaskets, responsável por contabilizar as cestas de operações ativas. Inicializaremos a variável count com 0 e percorreremos o array m_traders, utilizando ArraySize para determinar seu tamanho. Para cada ponteiro não nulo em m_traders, verificaremos se o estado retornado por getCurrentState é diferente de MarketZoneTrader::INACTIVE. Se essa condição for atendida, incrementaremos count. Por fim, retornaremos count, obtendo assim a quantidade de cestas ativas simultaneamente. Essa contagem é essencial para respeitar o limite definido por m_maxInitialPositions ao abrir novas cestas.
Em seguida, vamos criar a função cleanupTerminatedBaskets para remover as cestas inativas e otimizar o uso da memória. Primeiro, percorreremos o array m_traders para contar as entradas com ponteiros não nulos. Se trader não for nulo e getCurrentState retornar MarketZoneTrader::INACTIVE, usaremos delete para liberar a memória ocupada pela instância e definiremos o ponteiro como NULL. A variável newSize registrará a quantidade de ponteiros não nulos restantes. Depois, criaremos o array temp, ajustaremos seu tamanho para newSize com ArrayResize e copiaremos para ele os ponteiros não nulos de m_traders, utilizando o contador index. Em seguida, liberaremos o array m_traders com ArrayFree, redimensionaremos m_traders para newSize e copiaremos de volta os ponteiros armazenados em temp. Por fim, liberaremos temp com ArrayFree. Essa limpeza remove as cestas que não estão mais em uso, preservando a eficiência do sistema e deixando-o pronto para novas operações. Agora, passaremos à seção de acesso público da classe, na qual ajustaremos o construtor e o destrutor para inicializar e liberar corretamente os membros e elementos da classe.
public: BasketManager(string symbol, int baseMagic, int maxInitPos) { m_symbol = symbol; m_baseMagicNumber = baseMagic; m_maxInitialPositions = maxInitPos; ArrayResize(m_traders, 0); m_handleRsi = INVALID_HANDLE; m_handleEnvUpper = INVALID_HANDLE; m_handleEnvLower = INVALID_HANDLE; } ~BasketManager() { for (int i = 0; i < ArraySize(m_traders); i++) { if (m_traders[i] != NULL) delete m_traders[i]; } ArrayFree(m_traders); cleanupIndicators(); }
Começaremos pelo construtor de BasketManager, que recebe os parâmetros symbol, baseMagic e maxInitPos. Atribuiremos esses valores a m_symbol, m_baseMagicNumber e m_maxInitialPositions, respectivamente, para definir o símbolo de negociação, o número mágico base utilizado na identificação exclusiva de cada cesta e o limite de cestas ativas. Inicializaremos o array m_traders com tamanho zero por meio de ArrayResize e definiremos os handles dos indicadores m_handleRsi, m_handleEnvUpper e m_handleEnvLower como INVALID_HANDLE, preparando-os para a configuração posterior dos indicadores. Esse construtor é fundamental para a inicialização do sistema de múltiplas cestas.
Em seguida, criaremos o destrutor ~BasketManager para liberar os recursos utilizados. Vale lembrar que, por convenção, os nomes dos destrutores são precedidos pelo sinal de til (~). Percorreremos o array m_traders, utilizando ArraySize, e excluiremos com delete todas as instâncias de MarketZoneTrader referenciadas por ponteiros não nulos, liberando a memória correspondente. Depois, liberaremos o array m_traders com ArrayFree e chamaremos cleanupIndicators para liberar os handles dos indicadores e os buffers. Isso garantirá o encerramento correto do sistema, evitando vazamentos de memória quando o EA for interrompido. Na versão anterior, precisamos adicionar a lógica de exclusão à função de tratamento do evento OnDeinit após identificar um vazamento de memória. Agora, podemos incorporar essa lógica em uma etapa anterior, pois já sabemos que é necessário prevenir esse problema. Em seguida, precisaremos modificar a lógica de inicialização para carregar as posições existentes nas cestas correspondentes. Veja como faremos isso.
bool initialize() { if (!initializeIndicators()) return false; //--- Load existing positions into baskets int totalPositions = PositionsTotal(); for (int i = 0; i < totalPositions; i++) { ulong ticket = PositionGetTicket(i); if (PositionSelectByTicket(ticket)) { if (PositionGetString(POSITION_SYMBOL) == m_symbol) { long magic = PositionGetInteger(POSITION_MAGIC); if (magic >= m_baseMagicNumber && magic < m_baseMagicNumber + m_maxInitialPositions) { //--- Check if basket already exists for this magic bool exists = false; for (int j = 0; j < ArraySize(m_traders); j++) { if (m_traders[j] != NULL && m_traders[j].getMagicNumber() == magic) { exists = true; break; } } if (!exists && countActiveBaskets() < m_maxInitialPositions) { createNewBasket(magic, ticket); } } } } } Print("BasketManager initialized with ", ArraySize(m_traders), " existing baskets"); return true; } /* //--- PREVIOUS INITIALIZATION int initialize() { //--- Initialization Start m_tradeExecutor.SetExpertMagicNumber(m_tradeConfig.tradeIdentifier); //--- Set magic number int totalPositions = PositionsTotal(); //--- Get total positions for (int i = 0; i < totalPositions; i++) { //--- Iterate positions ulong ticket = PositionGetTicket(i); //--- Get ticket if (PositionSelectByTicket(ticket)) { //--- Select position if (PositionGetString(POSITION_SYMBOL) == m_tradeConfig.marketSymbol && PositionGetInteger(POSITION_MAGIC) == m_tradeConfig.tradeIdentifier) { //--- Check symbol and magic if (activateTrade(ticket)) { //--- Activate position Print("Existing position activated: Ticket=", ticket); //--- Log activation } else { Print("Failed to activate existing position: Ticket=", ticket); //--- Log failure } } } } m_handleRsi = iRSI(m_tradeConfig.marketSymbol, PERIOD_CURRENT, 8, PRICE_CLOSE); //--- Initialize RSI if (m_handleRsi == INVALID_HANDLE) { //--- Check RSI Print("Failed to initialize RSI indicator"); //--- Log failure return INIT_FAILED; //--- Return failure } m_handleEnvUpper = iEnvelopes(m_tradeConfig.marketSymbol, PERIOD_CURRENT, 150, 0, MODE_SMA, PRICE_CLOSE, 0.1); //--- Initialize upper Envelopes if (m_handleEnvUpper == INVALID_HANDLE) { //--- Check upper Envelopes Print("Failed to initialize upper Envelopes indicator"); //--- Log failure return INIT_FAILED; //--- Return failure } m_handleEnvLower = iEnvelopes(m_tradeConfig.marketSymbol, PERIOD_CURRENT, 95, 0, MODE_SMA, PRICE_CLOSE, 1.4); //--- Initialize lower Envelopes if (m_handleEnvLower == INVALID_HANDLE) { //--- Check lower Envelopes Print("Failed to initialize lower Envelopes indicator"); //--- Log failure return INIT_FAILED; //--- Return failure } ArraySetAsSeries(m_rsiBuffer, true); //--- Set RSI buffer ArraySetAsSeries(m_envUpperBandBuffer, true); //--- Set upper Envelopes buffer ArraySetAsSeries(m_envLowerBandBuffer, true); //--- Set lower Envelopes buffer Print("EA initialized successfully"); //--- Log success return INIT_SUCCEEDED; //--- Return success //--- Initialization End } */
Neste ponto, implementaremos a versão atualizada da função initialize na classe BasketManager, aprimorando o suporte à negociação com múltiplas cestas por meio da inicialização dos indicadores e do carregamento das posições existentes nas respectivas cestas. Primeiro, chamaremos initializeIndicators para configurar o RSI e o indicador Envelopes. Se a inicialização falhar, retornaremos false, evitando prosseguir sem os dados de mercado necessários ao funcionamento do sistema. Na versão anterior, os indicadores eram configurados diretamente na função initialize de MarketZoneTrader. Agora, essa configuração ficará centralizada em BasketManager, permitindo utilizar os dados dos indicadores em várias cestas. Em seguida, usaremos PositionsTotal para obter a quantidade de posições abertas e percorreremos cada uma delas, recuperando seu ticket por meio de PositionGetTicket.
Se PositionSelectByTicket for bem-sucedida e o símbolo da posição, obtido por PositionGetString, corresponder a m_symbol, verificaremos se o número mágico, recuperado por PositionGetInteger, está no intervalo entre m_baseMagicNumber e m_baseMagicNumber + m_maxInitialPositions. Em seguida, percorreremos o array m_traders e chamaremos getMagicNumber para cada ponteiro não nulo, verificando se já existe uma cesta associada a esse número mágico. Caso não exista e o valor retornado por countActiveBaskets seja menor que m_maxInitialPositions, chamaremos createNewBasket, passando o número mágico e o ticket para carregar a posição em uma nova cesta. Por fim, exibiremos com Print a quantidade de cestas inicializadas, obtida com ArraySize(m_traders), e retornaremos true. Ao executar o programa, teremos o seguinte resultado.

Agora, podemos passar ao processamento dos ticks. Na função processTick, precisaremos processar as cestas existentes a cada tick e criar novas cestas quando houver confirmação de novos sinais. Isso difere da versão anterior, na qual bastava iniciar operações com base nos sinais confirmados.
void processTick() { //--- Process existing baskets for (int i = 0; i < ArraySize(m_traders); i++) { if (m_traders[i] != NULL) { m_traders[i].processTick(m_rsiBuffer, m_envUpperBandBuffer, m_envLowerBandBuffer); } } cleanupTerminatedBaskets(); //--- Check for new signals on new bar if (!isNewBar()) return; if (!CopyBuffer(m_handleRsi, 0, 0, 3, m_rsiBuffer)) { Print("Error loading RSI data. Reverting."); return; } if (!CopyBuffer(m_handleEnvUpper, 0, 0, 3, m_envUpperBandBuffer)) { Print("Error loading upper envelopes data. Reverting."); return; } if (!CopyBuffer(m_handleEnvLower, 1, 0, 3, m_envLowerBandBuffer)) { Print("Error loading lower envelopes data. Reverting."); return; } const int rsiOverbought = 70; const int rsiOversold = 30; int ticket = -1; ENUM_ORDER_TYPE signalType = (ENUM_ORDER_TYPE)-1; double askPrice = NormalizeDouble(SymbolInfoDouble(m_symbol, SYMBOL_ASK), Digits()); double bidPrice = NormalizeDouble(SymbolInfoDouble(m_symbol, SYMBOL_BID), Digits()); if (m_rsiBuffer[1] < rsiOversold && m_rsiBuffer[2] > rsiOversold && m_rsiBuffer[0] < rsiOversold) { if (askPrice > m_envUpperBandBuffer[0]) { if (countActiveBaskets() < m_maxInitialPositions) { signalType = ORDER_TYPE_BUY; } } } else if (m_rsiBuffer[1] > rsiOverbought && m_rsiBuffer[2] < rsiOverbought && m_rsiBuffer[0] > rsiOverbought) { if (bidPrice < m_envLowerBandBuffer[0]) { if (countActiveBaskets() < m_maxInitialPositions) { signalType = ORDER_TYPE_SELL; } } } if (signalType != (ENUM_ORDER_TYPE)-1) { //--- Create new basket with unique magic number int newMagic = m_baseMagicNumber + ArraySize(m_traders); if (newMagic < m_baseMagicNumber + m_maxInitialPositions) { MarketZoneTrader* newTrader = new MarketZoneTrader(lotOption, initialLotSize, riskPercentage, riskPoints, zoneTargetPoints, zoneSizePoints, newMagic); ticket = newTrader.openInitialOrder(signalType); //--- Open INITIAL position if (ticket > 0 && newTrader.activateTrade(ticket)) { int size = ArraySize(m_traders); ArrayResize(m_traders, size + 1); m_traders[size] = newTrader; Print("New basket created: Magic=", newMagic, ", Ticket=", ticket, ", Type=", EnumToString(signalType)); } else { delete newTrader; Print("Failed to create new basket: Ticket=", ticket); } } else { Print("Maximum initial positions (baskets) reached: ", m_maxInitialPositions); } } }
Começaremos percorrendo o array m_traders, utilizando ArraySize para determinar seu tamanho. Para cada ponteiro não nulo de m_traders, chamaremos seu método processTick, passando m_rsiBuffer, m_envUpperBandBuffer e m_envLowerBandBuffer para executar a lógica específica daquela cesta. Na versão anterior, processTick gerenciava diretamente um único ciclo de negociação. Em seguida, chamaremos cleanupTerminatedBaskets para remover as cestas inativas e otimizar o uso dos recursos. Depois, usaremos isNewBar para permitir a verificação de novos sinais de negociação somente quando uma nova barra for formada. Se a função retornar false, encerraremos o processamento do tick nesse ponto, evitando cálculos desnecessários.
Usaremos CopyBuffer para copiar os dados dos indicadores associados a m_handleRsi, m_handleEnvUpper e m_handleEnvLower para seus respectivos buffers. Se ocorrer algum erro, registraremos a falha com Print e interromperemos o processamento. Na versão anterior, essa operação era realizada em MarketZoneTrader. Definiremos rsiOverbought como 70 e rsiOversold como 30 e inicializaremos as variáveis ticket e signalType. Também obteremos askPrice e bidPrice por meio de SymbolInfoDouble, utilizando SYMBOL_ASK e SYMBOL_BID, respectivamente, e normalizaremos os preços com NormalizeDouble.
Para identificar um sinal de compra, verificaremos se m_rsiBuffer indica condições de sobrevenda e se askPrice está acima do valor correspondente em m_envUpperBandBuffer. Se essas condições forem atendidas e countActiveBaskets for menor que m_maxInitialPositions, definiremos signalType como ORDER_TYPE_BUY. Para identificar um sinal de venda, verificaremos se m_rsiBuffer indica condições de sobrecompra e se bidPrice está abaixo do valor correspondente em m_envLowerBandBuffer. Nesse caso, definiremos signalType como ORDER_TYPE_SELL. Se signalType contiver um tipo de ordem válido, geraremos um número mágico exclusivo somando ArraySize(m_traders) a m_baseMagicNumber. Se o número mágico gerado estiver dentro do intervalo permitido, definido a partir de m_baseMagicNumber e limitado por m_maxInitialPositions, criaremos uma nova instância de MarketZoneTrader, passando os parâmetros de entrada e o novo número mágico.
Chamaremos openInitialOrder, passando signalType como argumento. Se o ticket retornado for válido e activateTrade for executada com sucesso, adicionaremos a nova instância de MarketZoneTrader ao array m_traders por meio de ArrayResize e registraremos a operação bem-sucedida com Print e EnumToString. Caso contrário, excluiremos a instância para liberar a memória e registraremos o erro. Também indicaremos quando o limite de cestas tiver sido atingido. Depois de abrir novas operações, precisaremos criar cestas para elas. A seguir, apresentamos a lógica necessária.
private: void createNewBasket(long magic, ulong ticket) { MarketZoneTrader* newTrader = new MarketZoneTrader(lotOption, initialLotSize, riskPercentage, riskPoints, zoneTargetPoints, zoneSizePoints, magic); if (newTrader.activateTrade(ticket)) { int size = ArraySize(m_traders); ArrayResize(m_traders, size + 1); m_traders[size] = newTrader; Print("Existing position loaded into basket: Magic=", magic, ", Ticket=", ticket); } else { delete newTrader; Print("Failed to load existing position into basket: Ticket=", ticket); } }
Vamos implementar a função createNewBasket na seção privada da classe BasketManager. Essa nova função ampliará os recursos do nosso sistema de negociação com múltiplas cestas, permitindo criar e gerenciar cestas individuais para posições já existentes. Primeiro, criaremos uma instância de MarketZoneTrader chamada newTrader, utilizando os parâmetros de entrada lotOption, initialLotSize, riskPercentage, riskPoints, zoneTargetPoints, zoneSizePoints e o número mágico fornecido para configurar uma cesta de operações com identificação exclusiva. Vale lembrar que, na versão anterior, esse número mágico era um parâmetro de entrada definido durante a inicialização, pois trabalhávamos com apenas uma instância de recuperação por zonas. Por isso, o mesmo valor era utilizado em todas as novas posições. Agora, passaremos esse parâmetro individualmente a cada nova instância da classe. Veja o código para facilitar a comparação.
//--- PREVIOUS VERSION OF NEW CLASS INSTANCE //--- Global Instance MarketZoneTrader *trader = NULL; //--- Declare trader instance int OnInit() { //--- EA Initialization Start trader = new MarketZoneTrader(lotOption, initialLotSize, riskPercentage, riskPoints, maxOrders, restrictMaxOrders, zoneTargetPoints, zoneSizePoints); //--- Create trader instance return trader.initialize(); //--- Initialize EA //--- EA Initialization End }
Em seguida, chamaremos activateTrade para o objeto newTrader, passando o ticket especificado para associar a posição existente à cesta. Se a operação for bem-sucedida, obteremos o tamanho atual do array m_traders com ArraySize, aumentaremos seu tamanho em uma unidade por meio de ArrayResize e armazenaremos newTrader na nova posição do array. Registraremos a operação bem-sucedida com Print, incluindo os valores de magic e ticket. Se activateTrade falhar, excluiremos newTrader para liberar a memória e registraremos o erro com Print. Dessa forma, a função permitirá associar posições existentes a cestas independentes, um dos principais recursos do nosso sistema de múltiplas cestas, em contraste com a abordagem baseada em uma única instância utilizada anteriormente. A classe BasketManager permitirá gerenciar essas cestas de operações com eficiência. Agora, vamos modificar a classe base para incorporar os novos recursos, incluindo o suporte a múltiplas cestas e trailing stops. Começaremos pelos membros da classe.
//--- Modified MarketZoneTrader Class class MarketZoneTrader { private: enum TradeState { INACTIVE, RUNNING, TERMINATING }; struct TradeMetrics { bool operationSuccess; double totalVolume; double netProfitLoss; }; struct ZoneBoundaries { double zoneHigh; double zoneLow; double zoneTargetHigh; double zoneTargetLow; }; struct TradeConfig { string marketSymbol; double openPrice; double initialVolume; long tradeIdentifier; string initialTradeLabel; //--- Label for initial positions string recoveryTradeLabel; //--- Label for recovery positions ulong activeTickets[]; ENUM_ORDER_TYPE direction; double zoneProfitSpan; double zoneRecoverySpan; double accumulatedBuyVolume; double accumulatedSellVolume; TradeState currentState; bool hasRecoveryTrades; //--- Flag to track recovery trades double trailingStopLevel; //--- Virtual trailing stop level }; struct LossTracker { double tradeLossTracker; }; TradeConfig m_tradeConfig; ZoneBoundaries m_zoneBounds; LossTracker m_lossTracker; string m_lastError; int m_errorStatus; CTrade m_tradeExecutor; TradingLotSizeOptions m_lotOption; double m_initialLotSize; double m_riskPercentage; int m_riskPoints; double m_zoneTargetPoints; double m_zoneSizePoints; }
Nesta etapa, vamos aprimorar nosso programa modificando a seção privada da classe MarketZoneTrader para adicionar os recursos necessários aos trailing stops e aprimorar a rotulagem das operações. Manteremos a estrutura principal da classe, mas faremos alterações importantes na estrutura TradeConfig para adaptá-la à estratégia aprimorada. Preservaremos a enumeração TradeState, com os estados INACTIVE, RUNNING e TERMINATING, e as estruturas TradeMetrics, ZoneBoundaries e LossTracker sem alterações em relação à versão anterior, pois elas continuam responsáveis, respectivamente, pelo gerenciamento dos estados das operações, pelas métricas de desempenho, pela definição dos limites das zonas e pelo acompanhamento das perdas.
Na estrutura TradeConfig, adicionaremos duas novas variáveis do tipo string: initialTradeLabel e recoveryTradeLabel. Esses rótulos permitirão identificar separadamente as operações iniciais e as operações de recuperação, facilitando sua identificação e seu acompanhamento dentro de cada cesta. Isso será especialmente útil para o gerenciamento de múltiplas cestas em nosso novo sistema. Também introduziremos a variável booleana hasRecoveryTrades, que indicará se uma cesta contém operações de recuperação, informação essencial para controlar corretamente a ativação e a desativação do trailing stop. Além disso, adicionaremos trailingStopLevel, do tipo double, para armazenar o nível virtual do trailing stop de cada cesta, permitindo proteger dinamicamente os lucros das operações iniciais.
Entre as variáveis-membro, manteremos m_tradeConfig, m_zoneBounds, m_lossTracker, m_lastError, m_errorStatus, m_tradeExecutor, m_lotOption, m_initialLotSize, m_riskPercentage, m_riskPoints, m_zoneTargetPoints e m_zoneSizePoints sem alterações. No entanto, essas variáveis agora também darão suporte aos trailing stops e à arquitetura de múltiplas cestas, na qual cada instância de MarketZoneTrader é responsável por sua própria cesta. Em particular, removeremos dessa classe as variáveis relacionadas aos indicadores, como m_handleRsi e m_rsiBuffer, pois seu gerenciamento ficará centralizado em BasketManager. Assim, cada instância de MarketZoneTrader poderá se concentrar exclusivamente nas operações de sua própria cesta. Também precisaremos fazer alguns ajustes no construtor e no destrutor para incorporar os novos recursos.
public: MarketZoneTrader(TradingLotSizeOptions lotOpt, double initLot, double riskPct, int riskPts, double targetPts, double sizePts, long magic) { m_tradeConfig.currentState = INACTIVE; ArrayResize(m_tradeConfig.activeTickets, 0); m_tradeConfig.zoneProfitSpan = targetPts * _Point; m_tradeConfig.zoneRecoverySpan = sizePts * _Point; m_lossTracker.tradeLossTracker = 0.0; m_lotOption = lotOpt; m_initialLotSize = initLot; m_riskPercentage = riskPct; m_riskPoints = riskPts; m_zoneTargetPoints = targetPts; m_zoneSizePoints = sizePts; m_tradeConfig.marketSymbol = _Symbol; m_tradeConfig.tradeIdentifier = magic; m_tradeConfig.initialTradeLabel = "EA_INITIAL_" + IntegerToString(magic); //--- Label for initial positions m_tradeConfig.recoveryTradeLabel = "EA_RECOVERY_" + IntegerToString(magic); //--- Label for recovery positions m_tradeConfig.hasRecoveryTrades = false; //--- Initialize recovery flag m_tradeConfig.trailingStopLevel = 0.0; //--- Initialize trailing stop m_tradeExecutor.SetExpertMagicNumber(magic); } ~MarketZoneTrader() { ArrayFree(m_tradeConfig.activeTickets); }
Começaremos pelo construtor de MarketZoneTrader. Agora, ele receberá um parâmetro adicional chamado magic, que permitirá atribuir um número mágico exclusivo a cada cesta de operações, diferentemente da versão anterior, que utilizava um número mágico fixo. Para melhorar a identificação das operações, definiremos m_tradeConfig.initialTradeLabel pela concatenação de EA_INITIAL com magic, convertido em string por IntegerToString, e m_tradeConfig.recoveryTradeLabel pela concatenação de EA_RECOVERY com magic. Dessa forma, poderemos distinguir claramente as operações iniciais das operações de recuperação dentro de cada cesta. Também inicializaremos m_tradeConfig.hasRecoveryTrades como false, para indicar a ausência de operações de recuperação, e definiremos m_tradeConfig.trailingStopLevel como 0.0, estabelecendo o valor inicial do trailing stop virtual. Esses dois recursos são novos. Por fim, configuraremos m_tradeExecutor por meio de SetExpertMagicNumber, passando o parâmetro magic. Destacamos as principais alterações para facilitar sua identificação.
Em seguida, simplificaremos o destrutor ~MarketZoneTrader em relação à rotina cleanup utilizada na versão anterior. Agora, precisaremos apenas liberar o array m_tradeConfig.activeTickets com ArrayFree, pois a liberação dos recursos dos indicadores ficará sob a responsabilidade de BasketManager. Assim, o destrutor cuidará exclusivamente dos recursos específicos de cada cesta. Depois, poderemos atualizar a função responsável pela ativação das operações para que ela também inicialize o nível do trailing stop e o estado de recuperação das operações iniciais.
bool activateTrade(ulong ticket) { m_tradeConfig.hasRecoveryTrades = false; m_tradeConfig.trailingStopLevel = 0.0; //--- THE REST OF THE LOGIC REMAINS return true; }
Aqui, basta adicionar a lógica que inicializa o nível do trailing stop da primeira operação com o valor 0 e define o estado de recuperação como false, indicando que se trata da primeira posição da cesta. Por fim, poderemos adicionar uma função para abrir a posição inicial.
int openInitialOrder(ENUM_ORDER_TYPE orderType) { //--- Open INITIAL position based on signal int ticket; double openPrice; if (orderType == ORDER_TYPE_BUY) { openPrice = NormalizeDouble(getMarketAsk(), Digits()); } else if (orderType == ORDER_TYPE_SELL) { openPrice = NormalizeDouble(getMarketBid(), Digits()); } else { Print("Invalid order type [Magic=", m_tradeConfig.tradeIdentifier, "]"); return -1; } double lotSize = 0; if (m_lotOption == FIXED_LOTSIZE) { lotSize = m_initialLotSize; } else if (m_lotOption == UNFIXED_LOTSIZE) { lotSize = calculateLotSize(m_riskPercentage, m_riskPoints); } if (lotSize <= 0) { Print("Invalid lot size [Magic=", m_tradeConfig.tradeIdentifier, "]: ", lotSize); return -1; } if (m_tradeExecutor.PositionOpen(m_tradeConfig.marketSymbol, orderType, lotSize, openPrice, 0, 0, m_tradeConfig.initialTradeLabel)) { ticket = (int)m_tradeExecutor.ResultOrder(); Print("INITIAL trade opened [Magic=", m_tradeConfig.tradeIdentifier, "]: Ticket=", ticket, ", Type=", EnumToString(orderType), ", Volume=", lotSize); } else { ticket = -1; Print("Failed to open INITIAL order [Magic=", m_tradeConfig.tradeIdentifier, "]: Type=", EnumToString(orderType), ", Volume=", lotSize); } return ticket; }
Na seção pública da classe MarketZoneTrader, adicionamos a nova função openInitialOrder para dar suporte à negociação com múltiplas cestas e melhorar a identificação das operações. Essa função permite abrir posições iniciais para uma cesta específica, identificada por um número mágico exclusivo. Começaremos inicializando as variáveis ticket e openPrice. Se orderType for ORDER_TYPE_BUY, obteremos openPrice por meio de getMarketAsk e normalizaremos o valor com NormalizeDouble e Digits. Para ORDER_TYPE_SELL, utilizaremos getMarketBid. Se orderType for inválido, registraremos um erro com Print, incluindo m_tradeConfig.tradeIdentifier, e retornaremos -1.
Determinaremos lotSize de acordo com m_lotOption: para FIXED_LOTSIZE, utilizaremos m_initialLotSize; para UNFIXED_LOTSIZE, chamaremos calculateLotSize, passando m_riskPercentage e m_riskPoints. Se lotSize for inválido, registraremos o erro com Print e retornaremos -1. Em seguida, abriremos a posição por meio de m_tradeExecutor.PositionOpen, passando m_tradeConfig.marketSymbol, orderType, lotSize, openPrice e m_tradeConfig.initialTradeLabel, para identificar claramente as operações iniciais. Se a abertura for bem-sucedida, atribuiremos a ticket o valor retornado por ResultOrder e registraremos o resultado com Print, incluindo m_tradeConfig.tradeIdentifier e a conversão realizada por EnumToString. Caso contrário, definiremos ticket como -1 e exibiremos uma mensagem de erro. Por fim, retornaremos ticket. Diferentemente de openOrder, utilizada na versão anterior, essa nova função emprega o parâmetro initialTradeLabel e é dedicada exclusivamente à abertura de posições iniciais, atendendo às necessidades da arquitetura de múltiplas cestas. Após a compilação, teremos o seguinte resultado.

A imagem mostra que conseguimos abrir uma operação inicial e criar uma nova instância de MarketZoneTrader para gerenciar a cesta correspondente a essa operação. Agora, precisamos implementar a lógica do trailing stop.
void evaluateMarketTick() { if (m_tradeConfig.currentState == INACTIVE) return; if (m_tradeConfig.currentState == TERMINATING) { finalizePosition(); return; } double currentPrice; double profitPoints = 0.0; //--- Handle BUY initial position if (m_tradeConfig.direction == ORDER_TYPE_BUY) { currentPrice = getMarketBid(); profitPoints = (currentPrice - m_tradeConfig.openPrice) / _Point; //--- Trailing Stop Logic for Initial Position if (enableInitialTrailing && !m_tradeConfig.hasRecoveryTrades && profitPoints >= minProfitPoints) { //--- Calculate desired trailing stop level double newTrailingStop = currentPrice - trailingStopPoints * _Point; //--- Start or update trailing stop if profit exceeds minProfitPoints + trailingStopPoints if (profitPoints >= minProfitPoints + trailingStopPoints) { if (m_tradeConfig.trailingStopLevel == 0.0 || newTrailingStop > m_tradeConfig.trailingStopLevel) { m_tradeConfig.trailingStopLevel = newTrailingStop; Print("Trailing stop updated [Magic=", m_tradeConfig.tradeIdentifier, "]: Level=", m_tradeConfig.trailingStopLevel, ", Profit=", profitPoints, " points"); } } //--- Check if price has hit trailing stop if (m_tradeConfig.trailingStopLevel > 0.0 && currentPrice <= m_tradeConfig.trailingStopLevel) { Print("Trailing stop triggered [Magic=", m_tradeConfig.tradeIdentifier, "]: Bid=", currentPrice, " <= TrailingStop=", m_tradeConfig.trailingStopLevel); finalizePosition(); return; } } //--- Zone Recovery Logic if (currentPrice > m_zoneBounds.zoneTargetHigh) { Print("Closing position [Magic=", m_tradeConfig.tradeIdentifier, "]: Bid=", currentPrice, " > TargetHigh=", m_zoneBounds.zoneTargetHigh); finalizePosition(); return; } else if (currentPrice < m_zoneBounds.zoneLow) { Print("Triggering RECOVERY trade [Magic=", m_tradeConfig.tradeIdentifier, "]: Bid=", currentPrice, " < ZoneLow=", m_zoneBounds.zoneLow); triggerRecoveryTrade(ORDER_TYPE_SELL, currentPrice); } } //--- Handle SELL initial position else if (m_tradeConfig.direction == ORDER_TYPE_SELL) { currentPrice = getMarketAsk(); profitPoints = (m_tradeConfig.openPrice - currentPrice) / _Point; //--- Trailing Stop Logic for Initial Position if (enableInitialTrailing && !m_tradeConfig.hasRecoveryTrades && profitPoints >= minProfitPoints) { //--- Calculate desired trailing stop level double newTrailingStop = currentPrice + trailingStopPoints * _Point; //--- Start or update trailing stop if profit exceeds minProfitPoints + trailingStopPoints if (profitPoints >= minProfitPoints + trailingStopPoints) { if (m_tradeConfig.trailingStopLevel == 0.0 || newTrailingStop < m_tradeConfig.trailingStopLevel) { m_tradeConfig.trailingStopLevel = newTrailingStop; Print("Trailing stop updated [Magic=", m_tradeConfig.tradeIdentifier, "]: Level=", m_tradeConfig.trailingStopLevel, ", Profit=", profitPoints, " points"); } } //--- Check if price has hit trailing stop if (m_tradeConfig.trailingStopLevel > 0.0 && currentPrice >= m_tradeConfig.trailingStopLevel) { Print("Trailing stop triggered [Magic=", m_tradeConfig.tradeIdentifier, "]: Ask=", currentPrice, " >= TrailingStop=", m_tradeConfig.trailingStopLevel); finalizePosition(); return; } } //--- Zone Recovery Logic if (currentPrice < m_zoneBounds.zoneTargetLow) { Print("Closing position [Magic=", m_tradeConfig.tradeIdentifier, "]: Ask=", currentPrice, " < TargetLow=", m_zoneBounds.zoneTargetLow); finalizePosition(); return; } else if (currentPrice > m_zoneBounds.zoneHigh) { Print("Triggering RECOVERY trade [Magic=", m_tradeConfig.tradeIdentifier, "]: Ask=", currentPrice, " > ZoneHigh=", m_zoneBounds.zoneHigh); triggerRecoveryTrade(ORDER_TYPE_BUY, currentPrice); } } }
Nesta etapa, vamos aprimorar o programa atualizando a função evaluateMarketTick para incorporar o trailing stop sem alterar a lógica de recuperação por zonas existente. Primeiro, verificaremos se m_tradeConfig.currentState corresponde a INACTIVE ou TERMINATING. Como na versão anterior, encerraremos a execução da função ou chamaremos finalizePosition, conforme o estado identificado. Para uma posição de compra, quando m_tradeConfig.direction for ORDER_TYPE_BUY, obteremos currentPrice por meio de getMarketBid e calcularemos profitPoints dividindo a diferença entre currentPrice e m_tradeConfig.openPrice por _Point. A nova lógica do trailing stop verificará se enableInitialTrailing é true, se m_tradeConfig.hasRecoveryTrades é false e se profitPoints é maior ou igual a minProfitPoints. Quando essas condições forem atendidas, calcularemos newTrailingStop subtraindo de currentPrice o produto de trailingStopPoints por _Point. Se profitPoints também for maior que a soma de minProfitPoints e trailingStopPoints, e m_tradeConfig.trailingStopLevel for igual a 0.0 ou estiver abaixo de newTrailingStop, atualizaremos m_tradeConfig.trailingStopLevel com o novo valor e registraremos a alteração por meio de Print.
Se houver um nível de trailing stop ativo em m_tradeConfig.trailingStopLevel e currentPrice cair abaixo dele, registraremos o acionamento do trailing stop e chamaremos finalizePosition para encerrar a operação. A lógica de recuperação por zonas permanecerá inalterada: a posição será fechada se currentPrice ultrapassar m_zoneBounds.zoneTargetHigh, ou uma operação de recuperação de venda será iniciada por meio de triggerRecoveryTrade se o preço cair abaixo de m_zoneBounds.zoneLow.
Para uma posição de venda, quando m_tradeConfig.direction for ORDER_TYPE_SELL, obteremos currentPrice por meio de getMarketAsk e calcularemos profitPoints dividindo a diferença entre m_tradeConfig.openPrice e currentPrice por _Point. A lógica do trailing stop será simétrica à utilizada nas posições de compra. Calcularemos newTrailingStop somando a currentPrice o produto de trailingStopPoints por _Point e atualizaremos m_tradeConfig.trailingStopLevel quando as condições forem atendidas. A posição será fechada se currentPrice ultrapassar esse nível. Quanto à recuperação por zonas, a posição será encerrada se currentPrice cair abaixo de m_zoneBounds.zoneTargetLow, ou uma operação de recuperação de compra será iniciada se o preço ultrapassar m_zoneBounds.zoneHigh. Não utilizaremos um trailing stop físico, pois queremos manter controle total sobre o sistema. Dessa forma, poderemos acompanhar e gerenciar todas as instâncias de MarketZoneTrader, cada uma responsável por sua própria cesta de operações. A seguir, apresentamos o resultado da execução da lógica do trailing stop.

A imagem mostra que conseguimos acompanhar a posição e encerrá-la quando o preço retorna ao nível do trailing stop. Por fim, vamos criar uma instância de BasketManager e utilizá-la para centralizar o gerenciamento das cestas.
//--- Global Instance BasketManager *manager = NULL; int OnInit() { manager = new BasketManager(_Symbol, baseMagicNumber, maxInitialPositions); if (!manager.initialize()) { delete manager; manager = NULL; return INIT_FAILED; } return INIT_SUCCEEDED; } void OnDeinit(const int reason) { if (manager != NULL) { delete manager; manager = NULL; Print("EA deinitialized"); } } void OnTick() { if (manager != NULL) { manager.processTick(); } }
Vamos atualizar a instância global e os manipuladores de eventos para utilizar a nova classe BasketManager no lugar de MarketZoneTrader, empregada na versão anterior. Essa alteração aprimorará nosso sistema de negociação com múltiplas cestas, concentrando seu gerenciamento em uma única classe. Começaremos declarando o ponteiro global manager, do tipo BasketManager, inicializado como NULL, para substituir o antigo ponteiro trader, do tipo MarketZoneTrader. Essa mudança é fundamental porque permitirá gerenciar várias cestas de operações por meio de um único objeto, em vez de trabalhar com uma única instância de negociação, como na versão anterior.
No manipulador de eventos OnInit, criaremos uma nova instância de BasketManager e atribuiremos seu endereço a manager, passando _Symbol, baseMagicNumber e maxInitialPositions. Esses parâmetros permitirão configurar o gerenciador com o símbolo do gráfico atual, o número mágico base utilizado como ponto de partida para gerar identificadores exclusivos para cada cesta e o limite máximo de cestas. Em seguida, chamaremos manager.initialize para configurar os indicadores e carregar as posições existentes. Se a inicialização falhar, excluiremos a instância referenciada por manager, definiremos o ponteiro como NULL e retornaremos INIT_FAILED. Se a inicialização for bem-sucedida, retornaremos INIT_SUCCEEDED.
No manipulador de eventos OnDeinit, verificaremos se manager é diferente de NULL. Nesse caso, excluiremos a instância com delete, definiremos o ponteiro como NULL e registraremos a desinicialização por meio de Print. Em OnTick, verificaremos se manager é diferente de NULL e chamaremos manager.processTick para processar os ticks do mercado em todas as cestas, substituindo a chamada anterior a trader.processTick. Assim, o processamento dos ticks ficará centralizado em BasketManager, permitindo que o sistema gerencie com mais eficiência os sinais de negociação recebidos simultaneamente. Após a compilação, teremos o seguinte resultado.

A imagem mostra que podemos criar e gerenciar cestas independentes para cada sinal, utilizando diferentes rótulos gerados a partir do número mágico fornecido. Agora, resta testar o programa com dados históricos.
Testes com dados históricos
Após realizar testes rigorosos com dados históricos, obtivemos os seguintes resultados.
Gráfico dos testes com dados históricos:

Relatório dos testes com dados históricos:

Conclusão
Aprimoramos nosso sistema de recuperação por zonas, implementado em MQL5, para operações a favor da tendência baseadas no indicador Envelopes, incorporando trailing stops e um mecanismo de negociação com múltiplas cestas. O aplicativo desenvolvido na Parte 22 foi ampliado com novos componentes, como a classe BasketManager e as funções atualizadas de MarketZoneTrader. Essas melhorias proporcionam uma plataforma de negociação mais flexível e robusta, que pode ser aperfeiçoada com ajustes em parâmetros como trailingStopPoints e maxInitialPositions.
Aviso: Este artigo tem finalidade exclusivamente educacional. A negociação envolve riscos financeiros significativos, e a volatilidade do mercado pode provocar perdas. Antes de colocar este programa em operação no mercado real, é fundamental realizar testes rigorosos com dados históricos e adotar uma gestão de riscos cuidadosa.
Com esses aprimoramentos, você poderá aperfeiçoar o sistema ou adaptar sua arquitetura para desenvolver novas estratégias, ampliando seus conhecimentos sobre negociação algorítmica. Boas operações!
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/18778
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.
Desenvolvimento de ferramentas para análise de Price Action (Parte 31): Módulo de reconhecimento de padrões de candles em Python (I): reconhecimento manual
Recursos do Assistente MQL5 que você precisa conhecer (Parte 74): Uso dos padrões Ichimoku e ADX-Wilder com aprendizado supervisionado
Ferramentas de negociação em MQL5 (Parte 4): Aprimorando o painel do scanner multitimeframe com posicionamento dinâmico e recursos de recolhimento e expansão
Desenvolvimento de um EA dinâmico para múltiplos pares (Parte 3): Estratégias de reversão à média e momentum
- 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
Senhor, não há operações de venda iniciais em aberto.
Isso está relacionado à lógica de negociação?