Algoritmos avançados de execução de ordens em MQL5: TWAP, VWAP e ordens Iceberg
- Introdução
- Conhecendo os algoritmos de execução
- Implementação com recursos do MQL5
- Implementação do analisador de desempenho
- Comparação do desempenho dos algoritmos
- Integração dos algoritmos de execução com estratégias de trading
- Exemplos de integração
- Estratégia integrada
- Resultados do teste em histórico
- Conclusão
Introdução
Imagine que você está à margem de um salão de trading e sente o coração acelerar enquanto acompanha as variações de preço em tempo real. Um movimento errado, uma ordem grande demais, e sua vantagem desaparece em um piscar de olhos. Bem-vindo ao mundo em que a qualidade da execução não é apenas um bônus desejável, mas a arma secreta que separa os vencedores dos demais.
Durante décadas, os pesos-pesados institucionais usaram discretamente algoritmos complexos para fatiar, filtrar e distribuir suas ordens de forma discreta, evitando slippage e reduzindo o impacto de mercado. Agora, graças à flexibilidade do MQL5, esse mesmo arsenal de execução está ao alcance de qualquer trader de varejo ambicioso.
Qual é o problema, afinal?
Imagine a cena: você identifica uma excelente oportunidade e decide aumentar o tamanho da posição. Você coloca uma ordem a mercado pelo volume total, e acaba vendo o preço ceder sob o peso da sua própria operação. Em poucos segundos, sua entrada perfeita vira uma solução precária. Esse é o famoso arrasto do impacto de mercado, e ele cobra seu preço até nos ambientes mais líquidos.
Os algoritmos de execução são o seu antídoto. Ao dividir uma ordem grande em uma sequência de partes menores, distribuídas estrategicamente ao longo do tempo, eles suavizam sua pegada no livro de ofertas. O resultado? Menos slippage, execução mais precisa e uma melhora geral no preço médio de execução.
Das torres de marfim para a sua área de trabalho
"Claro", você talvez diga, dando de ombros, "mas eu não movimento volumes institucionais". A questão é justamente esta: você não precisa. Quer você opere meio lote ou alguns minilotes, a volatilidade ainda pode distorcer sua execução. Essas ferramentas ajudam você a:
- Domar o slippage: Até ordens modestas podem sofrer desvios em mercados instáveis.
- Aprimorar sua vantagem: Execuções escalonadas muitas vezes oferecem um preço médio melhor do que uma operação feita de uma só vez.
- Manter o estado zen: Rotinas automatizadas eliminam a tentação de comprar ou vender em pânico.
- Escalar sem perder precisão: À medida que sua conta cresce, a execução continua ajustada, independentemente do tamanho que suas ordens atinjam.
-
Permanecer discreto: Em especial, as ordens Iceberg ocultam o tamanho real da sua ordem, deixando algoritmos curiosos apenas tentando adivinhar.
No ambiente democratizado atual, as mesmas tecnologias de execução que antes exigiam orçamentos milionários agora podem rodar no seu terminal pessoal de trading. Ao adicionar à sua plataforma um código MQL5 refinado para estratégias TWAP, VWAP e Iceberg, você passa a contar com poder de fogo institucional sem sair do segmento de varejo.
Prepare-se para incorporar esse playbook à sua rotina de execução. O jogo está mudando, e com esses algoritmos no seu arsenal, você estará jogando para vencer.
Conhecendo os algoritmos de execução
Antes de mergulhar nos detalhes da implementação, é importante entender a teoria por trás de cada algoritmo de execução e compreender por que eles são eficazes em diferentes cenários de mercado.
- Preço médio ponderado pelo tempo (Time-Weighted Average Price, TWAP): TWAP é um algoritmo de execução simples que divide uma ordem grande em partes iguais e as envia em intervalos de tempo fixos durante um período definido. Seu objetivo é acompanhar o preço médio do instrumento nesse período.
-
Como funciona:
- Envia ordens em intervalos regulares entre o início e o fim.
- Normalmente usa ordens de mesmo tamanho, embora seja possível randomizar o tamanho das fatias.
- Segue um cronograma predefinido, independentemente do movimento dos preços.
-
Distribui uniformemente o impacto de mercado ao longo do tempo, reduzindo o slippage ao mínimo.
-
Quando usar:
- Você precisa de um preço médio de execução em um timeframe específico.
- A liquidez permanece estável ao longo de todo o período de negociação.
- Você tem um prazo fixo para executar sua ordem.
-
Você prefere uma abordagem simples e previsível.
-
- Preço médio ponderado pelo volume (Volume-Weighted Average Price, VWAP): VWAP aprimora o TWAP ao dimensionar as fatias da ordem de acordo com o volume esperado de acordo com o volume esperado. Em vez de partes iguais, ele envia operações maiores nos períodos de maior volume.
-
Como funciona:
- Distribui o tamanho da ordem proporcionalmente aos padrões históricos de volume.
- Analisa o histórico de volumes negociados para prever a distribuição futura do volume.
- Em algumas implementações, pode se adaptar a mudanças de volume em tempo real.
-
Executa ordens com maior frequência nos períodos de pico de volume para reduzir o impacto de mercado.
-
Quando usar:
- Seu desempenho é avaliado em relação ao VWAP.
- O volume segue um padrão diário previsível.
- Você opera em um mercado no qual a liquidez varia ao longo da sessão.
-
Você quer acompanhar o fluxo natural do mercado.
-
- Ordens Iceberg: As ordens do tipo Iceberg têm como objetivo ocultar o tamanho real de uma ordem grande. A qualquer momento, apenas uma pequena "ponta" fica visível; assim que ela é preenchida, a próxima parcela aparece.
-
Como funciona:
- Exibe apenas uma parte da ordem total.
- Libera novos blocos visíveis após a execução de cada parcela visível.
- O tamanho visível pode ser fixo ou randomizado para reduzir a probabilidade de detecção.
-
Frequentemente são colocadas como ordens limitadas para melhor controle de preço.
-
Quando usar:
- É necessário ocultar o tamanho total da sua ordem.
- O mercado não é muito líquido, e operações grandes podem afetar os preços.
- Você precisa manter a execução em um determinado nível de preço.
-
Você está preocupado com a possibilidade de outros traders detectarem sua ordem e praticarem front-running contra ela.
-
Implementação com recursos do MQL5
Agora que entendemos a teoria por trás desses algoritmos de execução, vamos implementá-los em MQL5. Criaremos um framework modular e orientado a objetos que permitirá usar esses algoritmos separadamente ou combiná-los em um único sistema de execução.
Classe base: CExecutionAlgorithm
Começaremos definindo uma classe base que fornece uma funcionalidade comum a todos os algoritmos de execução:
//+------------------------------------------------------------------+ //| Base class for all execution algorithms | //+------------------------------------------------------------------+ class CExecutionAlgorithm { protected: string m_symbol; // Symbol to trade double m_totalVolume; // Total volume to execute double m_executedVolume; // Volume already executed double m_remainingVolume; // Volume remaining to execute datetime m_startTime; // Start time for execution datetime m_endTime; // End time for execution bool m_isActive; // Flag indicating if the algorithm is active int m_totalOrders; // Total number of orders placed int m_filledOrders; // Number of filled orders double m_avgExecutionPrice; // Average execution price double m_executionValue; // Total value of executed orders int m_slippage; // Allowed slippage in points public: // Constructor CExecutionAlgorithm(string symbol, double volume, datetime startTime, datetime endTime, int slippage = 3); // Destructor virtual ~CExecutionAlgorithm(); // Common methods virtual bool Initialize(); virtual bool Execute() = 0; virtual bool Update() = 0; virtual bool Terminate(); // Utility methods bool PlaceOrder(ENUM_ORDER_TYPE orderType, double volume, double price); bool CancelOrder(ulong ticket); void UpdateAverageExecutionPrice(double price, double volume); // Getters string GetSymbol() const { return m_symbol; } double GetTotalVolume() const { return m_totalVolume; } double GetExecutedVolume() const { return m_executedVolume; } double GetRemainingVolume() const { return m_remainingVolume; } datetime GetStartTime() const { return m_startTime; } datetime GetEndTime() const { return m_endTime; } bool IsActive() const { return m_isActive; } int GetTotalOrders() const { return m_totalOrders; } int GetFilledOrders() const { return m_filledOrders; } double GetAverageExecutionPrice() const { return m_avgExecutionPrice; } };
O método PlaceOrder é especialmente importante, pois trata a execução efetiva da ordem e atualiza o acompanhamento do volume:
bool CExecutionAlgorithm::PlaceOrder(ENUM_ORDER_TYPE orderType, double volume, double price) { // Prepare the trade request MqlTradeRequest request; MqlTradeResult result; ZeroMemory(request); ZeroMemory(result); request.action = TRADE_ACTION_DEAL; request.symbol = m_symbol; request.volume = volume; request.type = orderType; request.price = price; request.deviation = m_slippage; request.magic = 123456; // Magic number for identification // Send the order bool success = OrderSend(request, result); if(!success) { Print("OrderSend error: ", GetLastError()); return false; } // Check the result if(result.retcode != TRADE_RETCODE_DONE) { Print("OrderSend failed with code: ", result.retcode); return false; } // Update statistics m_totalOrders++; m_filledOrders++; m_executedVolume += volume; m_remainingVolume -= volume; UpdateAverageExecutionPrice(price, volume); // Store the order ticket for future reference ulong ticket = result.order; return true; }
Essa função cria e envia uma ordem a mercado zerando MqlTradeRequest e MqlTradeResult, preenchendo o símbolo, o volume, o tipo de ordem, o preço, o slippage e o magic number, e então chamando OrderSend. Se o envio falhar ou se o código de retorno da corretora não for TRADE_RETCODE_DONE, ela registra o erro e retorna false. Em caso de sucesso, atualiza os contadores internos, incluindo o os contadores internos, incluindo a contagem total, a quantidade de execuções, o volume executado e o volume restante, recalcula o preço médio, armazena o ticket e retorna true.
Implementação do TWAP
O algoritmo TWAP divide o período de execução em intervalos de tempo iguais e envia ordens de tamanho igual ou aleatório, em cada intervalo:
//+------------------------------------------------------------------+ //| Time-Weighted Average Price (TWAP) Algorithm | //+------------------------------------------------------------------+ class CTWAP : public CExecutionAlgorithm { private: int m_intervals; // Number of time intervals int m_currentInterval; // Current interval datetime m_nextExecutionTime; // Next execution time double m_intervalVolume; // Volume per interval bool m_useRandomization; // Whether to randomize order sizes double m_randomizationFactor; // Factor for randomization (0-1) ENUM_ORDER_TYPE m_orderType; // Order type (buy or sell) bool m_firstOrderPlaced; // Flag to track if first order has been placed int m_initialDelay; // Initial delay in seconds before first execution datetime m_lastCheckTime; // Last time order status was checked int m_checkInterval; // How often to check order status (seconds) public: // Constructor CTWAP(string symbol, double volume, datetime startTime, datetime endTime, int intervals, ENUM_ORDER_TYPE orderType, bool useRandomization = false, double randomizationFactor = 0.2, int slippage = 3, int initialDelay = 10); // Implementation of virtual methods virtual bool Initialize() override; virtual bool Execute() override; virtual bool Update() override; virtual bool Terminate() override; // TWAP specific methods void CalculateIntervalVolume(); datetime CalculateNextExecutionTime(); double GetRandomizedVolume(double baseVolume); bool IsTimeToExecute(); };
Método-chave: CalculateNextExecutionTime
Esse método cuida da distribuição adequada das ordens ao longo do tempo, com um atraso inicial para a primeira ordem:datetime CTWAP::CalculateNextExecutionTime() { // Calculate the duration of each interval int totalSeconds = (int)(m_endTime - m_startTime); int intervalSeconds = totalSeconds / m_intervals; // Calculate the next execution time datetime nextTime; if(m_currentInterval == 0) { // First interval - start at the defined start time plus initial delay nextTime = m_startTime + m_initialDelay; Print("TWAP: First execution time calculated with ", m_initialDelay, " seconds delay: ", TimeToString(nextTime)); } else { // For subsequent intervals, ensure proper spacing from current time datetime currentTime = TimeCurrent(); nextTime = currentTime + intervalSeconds; // Make sure we don't exceed the end time if(nextTime > m_endTime) nextTime = m_endTime; Print("TWAP: Next execution time calculated: ", TimeToString(nextTime), " (interval: ", intervalSeconds, " seconds)"); } return nextTime; }
Esse método divide a janela entre m_startTime e m_endTime em segmentos iguais, definidos por m_intervals, e retorna o momento em que o momento em que a próxima operação deve ser disparada: na primeira chamada, esse momento é simplesmente m_startTime + m_initialDelay; em cada chamada posterior, é TimeCurrent() + um intervalo em segundos, mas nunca além de m_endTime.
Método Execute:O método Execute verifica se chegou a hora de colocar uma ordem e trata o envio efetivo da ordem:
bool CTWAP::Execute() { if(!m_isActive) return false; // Check if it's time to execute the next order if(!IsTimeToExecute()) return true; // Not time yet // Calculate the volume for this execution double volumeToExecute = m_useRandomization ? GetRandomizedVolume(m_intervalVolume) : m_intervalVolume; // Ensure we don't exceed the remaining volume if(volumeToExecute > m_remainingVolume) volumeToExecute = m_remainingVolume; // Get current market price double price = 0.0; if(m_orderType == ORDER_TYPE_BUY) price = SymbolInfoDouble(m_symbol, SYMBOL_ASK); else price = SymbolInfoDouble(m_symbol, SYMBOL_BID); Print("TWAP: Placing order for interval ", m_currentInterval, ", Volume: ", DoubleToString(volumeToExecute, 2), ", Price: ", DoubleToString(price, _Digits)); // Place the order using OrderSend directly for more control MqlTradeRequest request; MqlTradeResult result; ZeroMemory(request); ZeroMemory(result); request.action = TRADE_ACTION_DEAL; request.symbol = m_symbol; request.volume = volumeToExecute; request.type = m_orderType; request.price = price; request.deviation = m_slippage; request.magic = 123456; // Magic number for identification // Send the order bool success = OrderSend(request, result); if(!success) { Print("TWAP: OrderSend error: ", GetLastError()); return false; } // Check the result if(result.retcode != TRADE_RETCODE_DONE) { Print("TWAP: OrderSend failed with code: ", result.retcode); return false; } // Update statistics m_totalOrders++; m_filledOrders++; m_executedVolume += volumeToExecute; m_remainingVolume -= volumeToExecute; // Update interval counter m_currentInterval++; m_firstOrderPlaced = true; // Calculate the time for the next execution if(m_currentInterval < m_intervals && m_remainingVolume > 0) m_nextExecutionTime = CalculateNextExecutionTime(); else m_isActive = false; // All intervals completed or no volume left Print("TWAP: Executed ", DoubleToString(volumeToExecute, 2), " at price ", DoubleToString(price, _Digits), ". Remaining: ", DoubleToString(m_remainingVolume, 2), ", Next execution: ", TimeToString(m_nextExecutionTime)); return true; }
Esse método Execute gerencia um único fragmento da execução do TWAP. Primeiro, ele interrompe a execução se a estratégia não estiver ativa ou se ainda não tiver chegado o momento de operar. Em seguida, seleciona uma fatia fixa ou aleatória do volume restante, sem nunca exceder o que ainda resta, e então busca o Ask atual para compra ou o Bid atual para venda. Ele registra o intervalo, o volume e o preço, cria MqlTradeRequest com o símbolo, volume, tipo, preço, slippage e magic number, e então chama OrderSend. Se o envio falhar ou se a corretora retornar qualquer valor diferente de TRADE_RETCODE_DONE, ele exibe o erro e retorna false.
Em caso de sucesso, incrementa os contadores de ordens, ajusta o volume executado e o volume restante, incrementa o contador de intervalos, marca que a primeira ordem foi enviada e então agenda o horário da próxima execução ou desativa a estratégia caso os intervalos ou o volume tenham se esgotado. Por fim, registra o evento ocorrido e retorna true.
Implementação do VWAP
O algoritmo VWAP é parecido com o TWAP, mas distribui as fatias da ordem com base em padrões históricos de volume://+------------------------------------------------------------------+ //| Volume-Weighted Average Price (VWAP) Algorithm | //+------------------------------------------------------------------+ class CVWAP : public CExecutionAlgorithm { private: int m_intervals; // Number of time intervals int m_currentInterval; // Current interval datetime m_nextExecutionTime; // Next execution time double m_volumeProfile[]; // Historical volume profile double m_intervalVolumes[]; // Volume per interval based on profile bool m_adaptiveMode; // Whether to adapt to real-time volume ENUM_ORDER_TYPE m_orderType; // Order type (buy or sell) int m_historyDays; // Number of days to analyze for volume profile bool m_profileLoaded; // Flag indicating if profile was loaded bool m_firstOrderPlaced; // Flag to track if first order has been placed int m_initialDelay; // Initial delay in seconds before first execution datetime m_lastCheckTime; // Last time order status was checked int m_checkInterval; // How often to check order status (seconds) public: // Constructor CVWAP(string symbol, double volume, datetime startTime, datetime endTime, int intervals, ENUM_ORDER_TYPE orderType, int historyDays = 5, bool adaptiveMode = true, int slippage = 3, int initialDelay = 10); // Implementation of virtual methods virtual bool Initialize() override; virtual bool Execute() override; virtual bool Update() override; virtual bool Terminate() override; // VWAP specific methods bool LoadVolumeProfile(); void CalculateIntervalVolumes(); void AdjustToRealTimeVolume(); datetime CalculateNextExecutionTime(); double GetCurrentVWAP(); bool IsTimeToExecute(); };Assim como no caso do TWAP, o VWAP também implementa o método CalculateNextExecutionTime para garantir o intervalo adequado entre as ordens:
datetime CVWAP::CalculateNextExecutionTime() { // Calculate the duration of each interval int totalSeconds = (int)(m_endTime - m_startTime); int intervalSeconds = totalSeconds / m_intervals; // Calculate the next execution time datetime nextTime; if(m_currentInterval == 0) { // First interval - start at the defined start time plus initial delay nextTime = m_startTime + m_initialDelay; Print("VWAP: First execution time calculated with ", m_initialDelay, " seconds delay: ", TimeToString(nextTime)); } else { // For subsequent intervals, ensure proper spacing from current time datetime currentTime = TimeCurrent(); nextTime = currentTime + intervalSeconds; // Make sure we don't exceed the end time if(nextTime > m_endTime) nextTime = m_endTime; Print("VWAP: Next execution time calculated: ", TimeToString(nextTime), " (interval: ", intervalSeconds, " seconds)"); } return nextTime; }
Implementação das ordens Iceberg
As ordens do tipo Iceberg ocultam o tamanho real da ordem, expondo ao mercado apenas uma pequena parte dela a qualquer momento://+------------------------------------------------------------------+ //| Iceberg Order Implementation | //+------------------------------------------------------------------+ class CIcebergOrder : public CExecutionAlgorithm { private: double m_visibleVolume; // Visible portion of the order double m_minVisibleVolume; // Minimum visible volume double m_maxVisibleVolume; // Maximum visible volume bool m_useRandomVisibleVolume; // Whether to randomize visible volume int m_orderPlacementDelay; // Delay between order placements (ms) bool m_avoidRoundNumbers; // Whether to avoid round numbers in price double m_limitPrice; // Limit price for the orders ulong m_currentOrderTicket; // Current active order ticket ENUM_ORDER_TYPE m_orderType; // Order type (buy or sell) bool m_orderActive; // Flag indicating if an order is currently active int m_priceDeviation; // Price deviation to avoid round numbers (in points) datetime m_lastCheckTime; // Last time order status was checked int m_checkInterval; // How often to check order status (seconds) int m_maxOrderLifetime; // Maximum lifetime for an order in seconds datetime m_orderPlacementTime; // When the current order was placed public: // Constructor CIcebergOrder(string symbol, double volume, double limitPrice, ENUM_ORDER_TYPE orderType, double visibleVolume, double minVisibleVolume = 0.0, double maxVisibleVolume = 0.0, bool useRandomVisibleVolume = true, int orderPlacementDelay = 1000, bool avoidRoundNumbers = true, int priceDeviation = 2, int slippage = 3); // Implementation of virtual methods virtual bool Initialize() override; virtual bool Execute() override; virtual bool Update() override; virtual bool Terminate() override; // Iceberg specific methods double GetRandomVisibleVolume(); double AdjustPriceToAvoidRoundNumbers(double price); bool CheckAndReplaceOrder(); bool IsOrderFilled(ulong ticket); bool IsOrderPartiallyFilled(ulong ticket, double &filledVolume); bool IsOrderCancelled(ulong ticket); bool IsOrderExpired(ulong ticket); bool IsOrderTimeout(); ulong GetCurrentOrderTicket() { return m_currentOrderTicket; } bool IsOrderActive() { return m_orderActive; } };O método Execute envia uma nova parcela visível da ordem:
bool CIcebergOrder::Execute() { if(!m_isActive) { Print("Iceberg: Execute called but algorithm is not active"); return false; } // If an order is already active, check its status if(m_orderActive) { Print("Iceberg: Execute called with active order ", m_currentOrderTicket); return CheckAndReplaceOrder(); } // Calculate the volume for this execution double volumeToExecute = m_useRandomVisibleVolume ? GetRandomVisibleVolume() : m_visibleVolume; // Ensure we don't exceed the remaining volume if(volumeToExecute > m_remainingVolume) volumeToExecute = m_remainingVolume; Print("Iceberg: Placing order for ", DoubleToString(volumeToExecute, 2), " at price ", DoubleToString(m_limitPrice, _Digits)); // Place the order using OrderSend directly for more control MqlTradeRequest request; MqlTradeResult result; ZeroMemory(request); ZeroMemory(result); request.action = TRADE_ACTION_PENDING; request.symbol = m_symbol; request.volume = volumeToExecute; request.type = m_orderType; request.price = m_limitPrice; request.deviation = m_slippage; request.magic = 123456; // Magic number for identification // Send the order bool success = OrderSend(request, result); if(!success) { Print("Iceberg: OrderSend error: ", GetLastError()); return false; } // Check the result if(result.retcode != TRADE_RETCODE_DONE) { Print("Iceberg: OrderSend failed with code: ", result.retcode); return false; } // Store the order ticket m_currentOrderTicket = result.order; m_orderActive = true; m_orderPlacementTime = TimeCurrent(); Print("Iceberg: Order placed successfully. Ticket: ", m_currentOrderTicket, ", Volume: ", DoubleToString(volumeToExecute, 2), ", Remaining: ", DoubleToString(m_remainingVolume, 2)); return true; }
Ao ser iniciado, Execute verifica primeiro se o algoritmo está ativo. Se já houver uma ordem filha Iceberg ativa, ele chama CheckAndReplaceOrder() para verificar se é necessário cancelá-la ou repor a parcela visível. Caso contrário, seleciona uma fatia visível, fixa ou aleatória, limita essa fatia ao volume restante e registra o tamanho e o preço.
Em seguida, cria uma requisição de ordem pendente (TRADE_ACTION_PENDING) com o símbolo, o volume, o preço limite, o slippage e o magic number, e chama OrderSend. Em caso de erro ou se o código de retorno não indicar TRADE_RETCODE_DONE, ele registra o problema e retorna false; em caso de sucesso, armazena o novo ticket, marca a ordem como ativa, registra o horário de envio, grava os dados e retorna true.
O método Update inclui a detecção de timeout da ordem para garantir que as ordens não permaneçam ativas indefinidamente:
bool CIcebergOrder::Update() { if(!m_isActive) { Print("Iceberg: Update called but algorithm is not active"); return false; } // Check if all volume has been executed if(m_remainingVolume <= 0) { Print("Iceberg: All volume executed. Terminating algorithm."); return Terminate(); } // Check if it's time to check order status datetime currentTime = TimeCurrent(); if(currentTime >= m_lastCheckTime + m_checkInterval) { m_lastCheckTime = currentTime; // Log current market conditions double currentBid = SymbolInfoDouble(m_symbol, SYMBOL_BID); double currentAsk = SymbolInfoDouble(m_symbol, SYMBOL_ASK); Print("Iceberg: Market update - Bid: ", DoubleToString(currentBid, _Digits), ", Ask: ", DoubleToString(currentAsk, _Digits), ", Limit Price: ", DoubleToString(m_limitPrice, _Digits)); // If an order is active, check its status if(m_orderActive) { // Check if the order has been active too long if(IsOrderTimeout()) { Print("Iceberg: Order ", m_currentOrderTicket, " has timed out. Replacing it."); // Cancel the current order if(!CancelOrder(m_currentOrderTicket)) { Print("Iceberg: Failed to cancel timed out order ", m_currentOrderTicket); } // Reset order tracking m_orderActive = false; m_currentOrderTicket = 0; // Place a new order after a delay Sleep(m_orderPlacementDelay); return Execute(); } return CheckAndReplaceOrder(); } else { // If no order is active, execute a new one Print("Iceberg: No active order, executing new order"); return Execute(); } } return true; }
Update consulta periodicamente o mercado e o status das ordens em intervalos definidos por m_checkInterval. Se todo o volume tiver sido esgotado, ele encerra a execução. Caso contrário, assim que chega o momento da verificação, registra os preços Bid/Ask atuais e o preço limite. Se houver uma ordem ativa, ela é verificada quanto ao timeout: se a ordem tiver expirado, ela é cancelada, o estado é redefinido, o algoritmo entra no modo m_orderPlacementDelay e Execute é chamado novamente para colocar uma nova fatia; se o timeout ainda não tiver expirado, a verificação é delegada a CheckAndReplaceOrder(). Se não houver ordem ativa, ele simplesmente chama Execute para enviar a próxima parcela visível.
Implementação do analisador de desempenho
Nossa classe de analisador de desempenho acompanha essas métricas e fornece métodos para analisar e comparar o funcionamento dos algoritmos://+------------------------------------------------------------------+ //| Performance Analyzer for Execution Algorithms | //+------------------------------------------------------------------+ class CPerformanceAnalyzer { private: string m_symbol; // Symbol being analyzed datetime m_startTime; // Analysis start time datetime m_endTime; // Analysis end time double m_decisionPrice; // Price at decision time double m_avgExecutionPrice; // Average execution price double m_totalVolume; // Total volume executed double m_implementationShortfall; // Implementation shortfall double m_marketImpact; // Estimated market impact double m_slippage; // Average slippage int m_executionTime; // Total execution time in seconds double m_priceImprovement; // Total price improvement public: // Constructor CPerformanceAnalyzer(string symbol, double decisionPrice); // Analysis methods void RecordExecution(datetime time, double price, double volume); void CalculateMetrics(); void CompareAlgorithms(CPerformanceAnalyzer &other); // Reporting methods void PrintReport(); void SaveReportToFile(string filename); // Getters double GetImplementationShortfall() const { return m_implementationShortfall; } double GetMarketImpact() const { return m_marketImpact; } double GetSlippage() const { return m_slippage; } int GetExecutionTime() const { return m_executionTime; } double GetPriceImprovement() const { return m_priceImprovement; } };
CPerformanceAnalyzer reúne todos os seus indicadores pós-trade em um só lugar. Ao instanciá-lo, você atribui a ele o símbolo e o preço de referência no momento da decisão; como ponto inicial, ele marca o horário atual. À medida que cada ordem filha é executada, você chama RecordExecution(tempo, preço, volume), que atualiza os totais acumulados: volume acumulado, preço médio ponderado de execução e timestamps. Após a conclusão da estratégia, ou periodicamente, você chama CalculateMetrics(), que calcula:
- Déficit de execução, isto é, a diferença no P&L entre o preço da decisão e a execução real,
- Slippage médio em comparação com os preços cotados,
- Impacto estimado da sua presença no mercado,
- Tempo total de execução, fim menos início,
- Melhoria de preço, se houver, em comparação com os benchmarks.
Você pode até comparar duas rodadas com CompareAlgorithms(otherAnalyzer) para ver qual estratégia apresentou os melhores resultados. Por fim, PrintReport() imprime as principais estatísticas no log para consulta rápida, enquanto SaveReportToFile(filename) permite salvar o relatório completo em um armazenamento externo. Métodos acessores leves dão acesso a cada métrica para dashboards personalizados ou análises posteriores.
Comparação do desempenho dos algoritmos
Diferentes condições de mercado favorecem diferentes algoritmos de execução. Veja uma comparação geral, isto é, uma regra prática:- TWAP:
- Mais indicado para: Mercados estáveis com liquidez constante
- Vantagens: Padrão de execução simples e previsível
- Desvantagens: Não se adapta a mudanças nas condições de mercado
- VWAP:
- Mais indicado para: Mercados com padrões de volume previsíveis
- Vantagens: Acompanha o ritmo natural do mercado e muitas vezes alcança preços melhores
- Desvantagens: Exige histórico suficiente de volumes negociados e tem implementação mais complexa
- Ordens Iceberg:
- Mais indicado para: Mercados menos líquidos ou situações em que a sensibilidade dos preços é alta
- Vantagens: Minimiza o impacto de mercado e preserva o controle sobre o preço
- Desvantagens: O tempo de execução pode ser imprevisível, com risco de execução parcial
Integração dos algoritmos de execução com estratégias de trading
A verdadeira força desses algoritmos de execução aparece quando eles são integrados a estratégias de trading. Esta seção mostra como integrar nossos algoritmos de execução a sistemas de trading completos.
Gerenciador
Para simplificar a integração, criaremos uma classe gerenciadora (Execution Manager) que servirá como fachada para todos os nossos algoritmos de execução:
//+------------------------------------------------------------------+ //| Execution Manager - Facade for all execution algorithms | //+------------------------------------------------------------------+ class CExecutionManager { private: CExecutionAlgorithm* m_algorithm; // Current execution algorithm CPerformanceAnalyzer* m_analyzer; // Performance analyzer public: // Constructor CExecutionManager(); // Destructor ~CExecutionManager(); // Algorithm creation methods bool CreateTWAP(string symbol, double volume, datetime startTime, datetime endTime, int intervals, ENUM_ORDER_TYPE orderType, bool useRandomization = false, double randomizationFactor = 0.2, int slippage = 3); bool CreateVWAP(string symbol, double volume, datetime startTime, datetime endTime, int intervals, ENUM_ORDER_TYPE orderType, int historyDays = 5, bool adaptiveMode = true, int slippage = 3); bool CreateIcebergOrder(string symbol, double volume, double limitPrice, ENUM_ORDER_TYPE orderType, double visibleVolume, double minVisibleVolume = 0.0, double maxVisibleVolume = 0.0, bool useRandomVisibleVolume = true, int orderPlacementDelay = 1000, bool avoidRoundNumbers = true, int priceDeviation = 2, int slippage = 3); // Execution methods bool Initialize(); bool Execute(); bool Update(); bool Terminate(); // Performance analysis void EnablePerformanceAnalysis(double decisionPrice); void PrintPerformanceReport(); // Getters CExecutionAlgorithm* GetAlgorithm() { return m_algorithm; } };
CExecutionManager atua como uma fachada simples para qualquer um dos seus algoritmos de execução e os reúne em um único fluxo de trading. Internamente, ele armazena um ponteiro para o CExecutionAlgorithm selecionado no momento, TWAP, VWAP ou Iceberg, além de um CPerformanceAnalyzer para acompanhar quão bem suas ordens estão sendo executadas.
Você escolhe sua estratégia chamando um dos métodos Create…, passando o símbolo, o volume total, os horários de início/fim (para TWAP/VWAP), a quantidade de intervalos ou os tamanhos das fatias, o tipo de ordem e quaisquer parâmetros específicos do algoritmo, como randomização, janela histórica, preço limite etc. Depois de criado, você conduz o algoritmo pelo ciclo de vida padrão:
- Initialize() configura os estados ou dados necessários.
- Execute() dispara a próxima fatia ou ordem filha.
- Update() consulta execuções de ordens, dados de mercado ou timeouts.
-
Terminate() faz a limpeza assim que todas as ordens forem executadas ou quando você quiser parar.
Se você ativar a análise de desempenho com EnablePerformanceAnalysis(), o gerenciador registrará os preços de execução em comparação com o benchmark no momento da decisão, e você poderá gerar um relatório resumido de P&L e slippage com PrintPerformanceReport(). Você sempre pode obter o objeto subjacente do algoritmo com GetAlgorithm() para inspeção ou medição personalizada.
Exemplos de integração
Abaixo estão exemplos de como integrar nossos algoritmos de execução a diferentes tipos de estratégias de trading:
-
Estratégia de seguimento de tendência com execução TWAP.
//+------------------------------------------------------------------+ //| Trend-Following Strategy with TWAP Execution | //+------------------------------------------------------------------+ void OnTick() { // Strategy parameters int maPeriodFast = 20; int maPeriodSlow = 50; double volume = 1.0; int executionIntervals = 5; // Calculate indicators double maFast = iMA(Symbol(), PERIOD_CURRENT, maPeriodFast, 0, MODE_SMA, PRICE_CLOSE, 0); double maSlow = iMA(Symbol(), PERIOD_CURRENT, maPeriodSlow, 0, MODE_SMA, PRICE_CLOSE, 0); // Check for entry conditions static bool inPosition = false; static CExecutionManager executionManager; if(!inPosition) { // Buy signal: Fast MA crosses above Slow MA if(maFast > maSlow) { // Create TWAP execution algorithm datetime startTime = TimeCurrent(); datetime endTime = startTime + 3600; // 1 hour execution window if(executionManager.CreateTWAP(Symbol(), volume, startTime, endTime, executionIntervals, ORDER_TYPE_BUY)) { executionManager.Initialize(); inPosition = true; Print("Buy signal detected. Starting TWAP execution."); } } } else { // Update the execution algorithm if(executionManager.Update()) { // Check if execution is complete if(!executionManager.GetAlgorithm().IsActive()) { inPosition = false; Print("TWAP execution completed."); } } } }
-
Estratégia de retorno à média com execução VWAP.
//+------------------------------------------------------------------+ //| Mean-Reversion Strategy with VWAP Execution | //+------------------------------------------------------------------+ void OnTick() { // Strategy parameters int rsiPeriod = 14; int rsiOversold = 30; int rsiOverbought = 70; double volume = 1.0; int executionIntervals = 5; // Calculate indicators double rsi = iRSI(Symbol(), PERIOD_CURRENT, rsiPeriod, PRICE_CLOSE, 0); // Check for entry conditions static bool inPosition = false; static bool isLong = false; static CExecutionManager executionManager; if(!inPosition) { // Buy signal: RSI oversold if(rsi < rsiOversold) { // Create VWAP execution algorithm datetime startTime = TimeCurrent(); datetime endTime = startTime + 3600; // 1 hour execution window if(executionManager.CreateVWAP(Symbol(), volume, startTime, endTime, executionIntervals, ORDER_TYPE_BUY)) { executionManager.Initialize(); inPosition = true; isLong = true; Print("Buy signal detected. Starting VWAP execution."); } } // Sell signal: RSI overbought else if(rsi > rsiOverbought) { // Create VWAP execution algorithm datetime startTime = TimeCurrent(); datetime endTime = startTime + 3600; // 1 hour execution window if(executionManager.CreateVWAP(Symbol(), volume, startTime, endTime, executionIntervals, ORDER_TYPE_SELL)) { executionManager.Initialize(); inPosition = true; isLong = false; Print("Sell signal detected. Starting VWAP execution."); } } } else { // Update the execution algorithm if(executionManager.Update()) { // Check if execution is complete if(!executionManager.GetAlgorithm().IsActive()) { inPosition = false; Print("VWAP execution completed."); } } // Check for exit conditions if(isLong && rsi > rsiOverbought) { executionManager.Terminate(); inPosition = false; Print("Exit signal detected. Terminating VWAP execution."); } else if(!isLong && rsi < rsiOversold) { executionManager.Terminate(); inPosition = false; Print("Exit signal detected. Terminating VWAP execution."); } } }
- Estratégia de rompimento com ordens Iceberg.
//+------------------------------------------------------------------+ //| Breakout Strategy with Iceberg Orders | //+------------------------------------------------------------------+ void OnTick() { // Strategy parameters int channelPeriod = 20; double volume = 1.0; double visibleVolume = 0.1; // Calculate indicators double upperChannel = iHigh(Symbol(), PERIOD_CURRENT, iHighest(Symbol(), PERIOD_CURRENT, MODE_HIGH, channelPeriod, 1)); double lowerChannel = iLow(Symbol(), PERIOD_CURRENT, iLowest(Symbol(), PERIOD_CURRENT, MODE_LOW, channelPeriod, 1)); double currentPrice = SymbolInfoDouble(Symbol(), SYMBOL_BID); // Check for entry conditions static bool inPosition = false; static CExecutionManager executionManager; if(!inPosition) { // Buy signal: Price breaks above upper channel if(currentPrice > upperChannel) { // Create Iceberg Order double limitPrice = upperChannel; // Place limit order at breakout level if(executionManager.CreateIcebergOrder(Symbol(), volume, limitPrice, ORDER_TYPE_BUY, visibleVolume, visibleVolume * 0.8, visibleVolume * 1.2, true, 1000, true, 2, 3)) { executionManager.Initialize(); inPosition = true; Print("Buy breakout detected. Starting Iceberg Order execution."); } } // Sell signal: Price breaks below lower channel else if(currentPrice < lowerChannel) { // Create Iceberg Order double limitPrice = lowerChannel; // Place limit order at breakout level if(executionManager.CreateIcebergOrder(Symbol(), volume, limitPrice, ORDER_TYPE_SELL, visibleVolume, visibleVolume * 0.8, visibleVolume * 1.2, true, 1000, true, 2, 3)) { executionManager.Initialize(); inPosition = true; Print("Sell breakout detected. Starting Iceberg Order execution."); } } } else { // Update the execution algorithm if(executionManager.Update()) { // Check if execution is complete if(!executionManager.GetAlgorithm().IsActive()) { inPosition = false; Print("Iceberg Order execution completed."); } } } }
Em todos os três exemplos, a integração segue o mesmo padrão de alto nível:
-
Manutenção do estado
- A variável booleana inPosition acompanha se, no momento, há uma ordem em execução.
-
O gerenciador estático CExecutionManager persiste entre ticks e gerencia o ciclo de vida do algoritmo escolhido.
-
Lógica de entrada
- Após o seu sinal de entrada (cruzamento de médias móveis, limite de RSI ou rompimento de canal), chame o método de criação correspondente em executionManager (TWAP, VWAP ou Iceberg), passando o símbolo, o volume total, a janela de tempo ou o preço limite, os parâmetros das fatias e o tipo de ordem.
-
Se o objeto for criado com sucesso, chame imediatamente executionManager.Initialize(), defina inPosition=true e registre o início do fluxo de execução.
-
Execução contínua
- Enquanto inPosition for true, cada OnTick() chama executionManager.Update().
-
Dentro de Update(), o gerenciador chamará Execute() conforme necessário, consultará fills, tratará timeouts ou atualizações de mercado e agendará a próxima fatia ou cancelará/substituirá ordens filhas no caso do Iceberg.
-
Conclusão e saída
- Após cada atualização, verifique executionManager.GetAlgorithm()->IsActive(). Assim que ele retornar false (quando todos os intervalos tiverem sido concluídos ou o volume tiver sido esgotado), defina inPosition=false e registre no log que a execução foi concluída.
-
No exemplo de retorno à média com VWAP, há uma verificação adicional de saída: se o preço reverter e ultrapassar o limite de RSI no meio da execução, chame executionManager.Terminate() para interromper antecipadamente as fatias restantes.
- Seguimento de tendência + TWAP
Entrada: O cruzamento da média móvel rápida acima da média móvel lenta aciona a entrada
Execução: Divide sua compra de 1 lote em 5 partes iguais ao longo da próxima hora - Retorno à média + VWAP
Entrada: RSI < 30 para operações de compra, > 70 para operações de venda
Execução: Distribui 1 lote em 5 partes ao longo de 1 hora, conforme o perfil histórico de volume.
Saída antecipada: Se o sinal mudar, por exemplo, RSI > 70 durante uma operação de compra, executionManager.Terminate() interrompe as partes restantes - Rompimento + Iceberg
Entrada: O preço rompe a máxima do canal (compra) ou a mínima do canal (venda).
Execução: Coloca uma ordem pendente limitada no nível do preço de rompimento, tornando visível apenas ~0,1 lote por vez e repondo essa parcela até que 1 lote inteiro seja executado.
Ao trocar entre CreateTWAP, CreateVWAP e CreateIcebergOrder, você conecta qualquer algoritmo de execução à sua lógica de sinal sem duplicar o lógica padrão de gerenciamento de ordens.
Estratégia integrada
Código completo:
//+------------------------------------------------------------------+ //| IntegratedStrategy.mq5 | //| Copyright 2025, MetaQuotes Software Corp. | //| https://www.metaquotes.net | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Software Corp." #property link "https://www.metaquotes.net" #property version "1.00" #include "ExecutionAlgorithm.mqh" #include "TWAP.mqh" #include "VWAP.mqh" #include "IcebergOrder.mqh" #include "PerformanceAnalyzer.mqh" #include "ExecutionManager.mqh" // Input parameters input int FastMA = 20; // Fast moving average period input int SlowMA = 50; // Slow moving average period input double TradingVolume = 0.1; // Trading volume input bool UseAdaptiveExecution = true; // Use adaptive execution based on market conditions // Global variables CExecutionManager *g_executionManager = NULL; int g_maHandle1 = INVALID_HANDLE; int g_maHandle2 = INVALID_HANDLE; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // Initialize execution manager g_executionManager = new CExecutionManager(Symbol(), 3, UseAdaptiveExecution); // Initialize indicators g_maHandle1 = iMA(Symbol(), Period(), FastMA, 0, MODE_SMA, PRICE_CLOSE); g_maHandle2 = iMA(Symbol(), Period(), SlowMA, 0, MODE_SMA, PRICE_CLOSE); if(g_maHandle1 == INVALID_HANDLE || g_maHandle2 == INVALID_HANDLE) { Print("Failed to create indicator handles"); return INIT_FAILED; } return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { // Clean up if(g_executionManager != NULL) { delete g_executionManager; g_executionManager = NULL; } // Release indicator handles IndicatorRelease(g_maHandle1); IndicatorRelease(g_maHandle2); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { // Update execution algorithms if(g_executionManager != NULL) g_executionManager.UpdateAlgorithms(); // Only process at bar open if(iVolume(_Symbol, PERIOD_CURRENT, 0) > 1) return; // Get indicator values double fastMA[2], slowMA[2]; if(CopyBuffer(g_maHandle1, 0, 0, 2, fastMA) <= 0 || CopyBuffer(g_maHandle2, 0, 0, 2, slowMA) <= 0) { Print("Failed to copy indicator buffers"); return; } // Check for trend signals bool buySignal = (fastMA[0] > slowMA[0]) && (fastMA[1] <= slowMA[1]); bool sellSignal = (fastMA[0] < slowMA[0]) && (fastMA[1] >= slowMA[1]); // Execute signals using the execution manager if(buySignal) { Print("Buy signal detected"); if(UseAdaptiveExecution) { // Let the execution manager select the best algorithm g_executionManager.ExecuteSignal(SIGNAL_TYPE_BUY, TradingVolume); } else { // Manually create a TWAP algorithm datetime currentTime = TimeCurrent(); CTWAP *twap = g_executionManager.CreateTWAP(TradingVolume, currentTime, currentTime + 3600, 6, ORDER_TYPE_BUY, true); } } else if(sellSignal) { Print("Sell signal detected"); if(UseAdaptiveExecution) { // Let the execution manager select the best algorithm g_executionManager.ExecuteSignal(SIGNAL_TYPE_SELL, TradingVolume); } else { // Manually create a TWAP algorithm datetime currentTime = TimeCurrent(); CTWAP *twap = g_executionManager.CreateTWAP(TradingVolume, currentTime, currentTime + 3600, 6, ORDER_TYPE_SELL, true); } } } //+------------------------------------------------------------------+
O EA IntegratedStrategy.mq5 começa declarando seus metadados, copyright, link e versão, e incluindo os cabeçalhos de todas as nossas classes de algoritmos de execução e do analisador de desempenho. Em seguida, define quatro entradas configuráveis pelo usuário: os períodos das médias móveis simples rápida e lenta, o volume total de negociação por sinal e uma flag booleana para alternar para a execução "adaptativa", em que o gerenciador decide se deve usar TWAP, VWAP ou Iceberg. Também são declarados um ponteiro global para CExecutionManager e dois handles de indicador, para que sejam preservados entre os ticks.
Em OnInit(), instanciamos o gerenciador de execução, passando a ele o símbolo atual, o máximo de três algoritmos paralelos e nossa flag adaptativa; em seguida, criamos dois handles de indicador SMA. Se qualquer um dos handles falhar, a inicialização é interrompida. OnDeinit() apenas faz a limpeza, excluindo o gerenciador e liberando os handles dos indicadores, garantindo que não haja vazamentos de memória ou de handles quando o EA for removido ou quando a plataforma for encerrada.
A lógica principal fica em OnTick(). Primeiro, chamamos UpdateAlgorithms() no gerenciador de execução para que quaisquer ordens filhas existentes, fatias TWAP, intervalos VWAP ou ramificações Iceberg, sejam processadas, canceladas ou reabastecidas conforme necessário. Em seguida, aguardamos uma nova barra, ignorando-a se o volume de ticks ainda estiver aumentando. Após a abertura da barra, extraímos os dois últimos valores da SMA para os períodos rápido e lento. O cruzamento de baixo para cima gera um sinal de compra; o cruzamento inverso gera um sinal de venda.
Se a execução adaptativa estiver ativada, passamos o sinal e o volume para g_executionManager.ExecuteSignal(), para que ele escolha o algoritmo adequado. Caso contrário, iniciamos manualmente uma instância TWAP para uma hora e seis fatias. Esse padrão separa claramente a lógica de entrada, detecção de tendência, da lógica de gerenciamento de ordens, permitindo que a mesma fachada gerencie vários estilos de execução sem duplicar o padrão.
Após ativar o Take Profit, o código muda para:
//+------------------------------------------------------------------+ //| IntegratedStrategy.mq5 | //| Copyright 2025, MetaQuotes Software Corp. | //| https://www.metaquotes.net | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Software Corp." #property link "https://www.metaquotes.net" #property version "1.00" #include "ExecutionAlgorithm.mqh" #include "TWAP.mqh" #include "VWAP.mqh" #include "IcebergOrder.mqh" #include "PerformanceAnalyzer.mqh" #include "ExecutionManager.mqh" #include <Trade\Trade.mqh> // Input parameters input int FastMA = 20; // Fast moving average period input int SlowMA = 50; // Slow moving average period input double TradingVolume = 0.1; // Trading volume input bool UseAdaptiveExecution = true; // Use adaptive execution based on market conditions input double EquityTPPercent = 10.0; // Equity Take Profit in percent input double EquitySLPercent = 5.0; // Equity Stop Loss in percent // Global variables CExecutionManager *g_executionManager = NULL; int g_maHandle1 = INVALID_HANDLE; int g_maHandle2 = INVALID_HANDLE; double g_initialEquity = 0.0; CTrade trade; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // Record initial equity g_initialEquity = AccountInfoDouble(ACCOUNT_EQUITY); // Initialize execution manager g_executionManager = new CExecutionManager(Symbol(), 3, UseAdaptiveExecution); // Initialize indicators g_maHandle1 = iMA(Symbol(), Period(), FastMA, 0, MODE_SMA, PRICE_CLOSE); g_maHandle2 = iMA(Symbol(), Period(), SlowMA, 0, MODE_SMA, PRICE_CLOSE); if(g_maHandle1 == INVALID_HANDLE || g_maHandle2 == INVALID_HANDLE) { Print("Failed to create indicator handles"); return INIT_FAILED; } return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { // Clean up if(g_executionManager != NULL) { delete g_executionManager; g_executionManager = NULL; } // Release indicator handles IndicatorRelease(g_maHandle1); IndicatorRelease(g_maHandle2); } //+------------------------------------------------------------------+ //| Check equity-based TP and SL, then reset baseline | //+------------------------------------------------------------------+ void CheckEquityTPandSL() { double currentEquity = AccountInfoDouble(ACCOUNT_EQUITY); double tpEquity = g_initialEquity * (1.0 + EquityTPPercent / 100.0); double slEquity = g_initialEquity * (1.0 - EquitySLPercent / 100.0); if(currentEquity >= tpEquity) { Print("Equity Take Profit reached: ", currentEquity); CloseAllPositions(); g_initialEquity = currentEquity; Print("Equity baseline reset to: ", g_initialEquity); } else if(currentEquity <= slEquity) { Print("Equity Stop Loss reached: ", currentEquity); CloseAllPositions(); g_initialEquity = currentEquity; Print("Equity baseline reset to: ", g_initialEquity); } } //+------------------------------------------------------------------+ //| Close all open positions | //+------------------------------------------------------------------+ void CloseAllPositions() { CPositionInfo m_position; CTrade m_trade; for(int i = PositionsTotal() - 1; i >= 0; i--) // loop all Open Positions if(m_position.SelectByIndex(i)) { // select a position m_trade.PositionClose(m_position.Ticket()); // then delete it --period } } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { // Check and reset equity thresholds CheckEquityTPandSL(); // Update execution algorithms if(g_executionManager != NULL) g_executionManager.UpdateAlgorithms(); // Only process at bar open if(iVolume(_Symbol, PERIOD_CURRENT, 0) > 1) return; // Get indicator values double fastMA[2], slowMA[2]; if(CopyBuffer(g_maHandle1, 0, 0, 2, fastMA) <= 0 || CopyBuffer(g_maHandle2, 0, 0, 2, slowMA) <= 0) { Print("Failed to copy indicator buffers"); return; } // Check for trend signals bool buySignal = (fastMA[0] > slowMA[0]) && (fastMA[1] <= slowMA[1]); bool sellSignal = (fastMA[0] < slowMA[0]) && (fastMA[1] >= slowMA[1]); // Execute signals using the execution manager if(buySignal) { Print("Buy signal detected"); if(UseAdaptiveExecution) { // Let the execution manager select the best algorithm g_executionManager.ExecuteSignal(SIGNAL_TYPE_BUY, TradingVolume); } else { datetime currentTime = TimeCurrent(); CTWAP *twap = g_executionManager.CreateTWAP(TradingVolume, currentTime, currentTime + 3600, 6, ORDER_TYPE_BUY, true); } } else if(sellSignal) { Print("Sell signal detected"); if(UseAdaptiveExecution) { // Let the execution manager select the best algorithm g_executionManager.ExecuteSignal(SIGNAL_TYPE_SELL, TradingVolume); } else { datetime currentTime = TimeCurrent(); CTWAP *twap = g_executionManager.CreateTWAP(TradingVolume, currentTime, currentTime + 3600, 6, ORDER_TYPE_SELL, true); } } } //+------------------------------------------------------------------+
Resultados do teste em histórico


1. Curvas de equity e saldo
A escada verde "Balance" mostra o saldo da sua conta sempre que o EA fecha uma posição; a linha azul "Equity" suaviza os lucros e perdas não realizados entre as operações. Podemos observar uma tendência clara de alta de janeiro ao início de março, com alguns recuos, cada recuo chega a um máximo de cerca de 10-16% antes que a próxima sequência de ganhos recupere o crescimento. Esse padrão sugere que o sistema prospera em condições de tendência, mas ainda passa por quedas de capital toleráveis.
2. Volume e uso de risco
Na parte inferior, os triângulos "Carga sobre o depósito" (Deposit Load) diminuem gradualmente ao longo do tempo, isto é, o tamanho da sua posição como percentual do capital. Ele começa em torno de 10% do seu saldo e diminui à medida que o seu capital cresce, com tamanho de volume fixo, ou seja, nosso risco por operação efetivamente diminui conforme a conta cresce. É por isso que os drawdowns permanecem proporcionalmente semelhantes, mesmo que seu capital em dólares aumente.
3. Principais indicadores de rentabilidade
-
Depósito inicial: $1 000
-
Lucro líquido: + $703, rentabilidade de 70% em ~2 meses
-
Fator de lucro: 2,34, você ganha 2,34 dólares para cada 1 dólar perdido
-
Lucro esperado: em média 2,34 dólares por trade
-
Índice de Sharpe: 5,47, muito alto, alta rentabilidade ajustada ao risco
Esses números mostram que a estratégia não é apenas lucrativa, mas também oferece uma margem substancial acima da própria volatilidade.
4. Drawdown e recuperação
-
Drawdown máximo do saldo: 156 pontos ou 9.99%
-
Drawdown máximo da equity: 228 pontos ou 15.89%
-
Fator de recuperação: 3.08, lucro líquido ÷ drawdown máximo
Um fator de recuperação acima de 2 geralmente é considerado bom, portanto, com 3,08, você obtém um lucro três vezes maior que sua maior perda.
5. Distribuição dos trades
-
Total de trades: 300, 600 deals, portanto cada entrada+saída conta como dois
-
Taxa de acerto: 76%, 228 ganhos contra 72 perdas
-
Ganho médio: $5,39
-
Perda média: – $7,31
Embora sua taxa de acerto e seu fator de lucro sejam altos, observe que, em média, seus trades perdedores são maiores que os vencedores. Esse é um ponto que merece atenção caso as condições de mercado mudem.
6. Sequências e consistência
-
Maior sequência de trades vencedores: 87 trades, + $302
-
Maior sequência de trades perdedores: 23 trades, – $156
-
Sequência vencedora média: 57 trades
-
Sequência perdedora média: 18 trades
Sequências vencedoras longas impulsionam o crescimento, enquanto a maior sequência de perdas ainda custa apenas cerca de 15% do capital.
Conclusão
Imagine que você é um trader individual em uma arena de mercado lotada, onde cada tick importa e cada preço de execução antecipa lucro ou prejuízo. Ao incorporar ordens TWAP, VWAP e Iceberg ao seu conjunto de ferramentas, você deixa de apenas reagir às oscilações de preço e passa a regê-las. Esses algoritmos de nível institucional, antes reservados à elite, agora estão ao seu alcance, fatiando a liquidez como um laser e transformando livros de ofertas caóticos em oportunidades.
O TWAP se torna seu metrônomo constante, ajustando o tamanho de forma uniforme dentro de um intervalo definido, ideal quando a corrente está tranquila e você só precisa de uma travessia suave. O VWAP transforma você em um trader experiente, acompanhando os momentos de maior intensidade do dia e sentindo o pulso do mercado. E, quando você precisa ocultar suas intenções, as ordens Iceberg escondem seu tamanho real sob a superfície, revelando apenas o necessário para executar a ordem sem assustar os grandes players.
Mas não se trata apenas de técnicas isoladas. Com nosso framework MQL5 modular, você pode integrá-las a qualquer estratégia, seja seguimento de tendência, retorno à média ou busca por rompimentos, alternando facilmente para uma nova abordagem. A fachada unificada ExecutionManager permite trocar, combinar ou até sobrepor algoritmos durante o trading, enquanto PerformanceAnalyzer mantém o placar com olhos de águia, medindo slippage, déficit e impacto de mercado com precisão até o último pip.
O que vem a seguir? Pense na execução como um organismo vivo que se adapta. Permita que seu TWAP aprenda com os picos de volatilidade. Direcione as fatias do VWAP para os poços de liquidez mais profundos. Ensine seu Iceberg a sentir onde os predadores estão à espreita e a se esconder mais fundo. E por que parar por aí? Incorpore machine learning para prever o microssegundo ideal de disparo ou combine tipos de ordens em híbridos personalizados que se ajustem à sua vantagem única.
O mundo do trading nunca fica parado, e a execução das suas ordens também não deve parar. Mergulhe, experimente com ousadia e transforme cada fatia, cada execução, em uma vantagem calculada. Sua vantagem está esperando no código.
Para facilitar, segue uma visão geral rápida dos arquivos incluídos neste artigo:
| Nome do arquivo | Descrição |
|---|---|
| ExecutionAlgorithm.mqh | Classe base para todos os algoritmos de execução |
| TWAP.mqh | Implementação do preço médio ponderado pelo tempo |
| VWAP.mqh | Implementação do preço médio ponderado pelo volume |
| IcebergOrder.mqh | Implementação das ordens Iceberg |
| PerformanceAnalyzer.mqh | Ferramentas para análise do desempenho da execução |
| ExecutionManager.mqh | Fachada para integração fácil com estratégias de trading |
| IntegratedStrategy.mq5 | Exemplo de EA que demonstra a integração com uma estratégia de trading |
| IntegratedStrategy - Take Profit.mq5 | Exemplo de EA que demonstra a integração com uma estratégia de trading com Take Profit e Stop Loss em percentual do saldo da conta |
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/17934
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.
Caminhe em novos trilhos: Personalize indicadores no MQL5
Otimização e ajuste fino do código-fonte para melhorar os resultados do backtesting
Está chegando o novo MetaTrader 5 e MQL5
Rede neural na prática: Uma questão de escala
- 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
Testando seu algoritmo.
No arquivo ExecutionAlgorithm.mqh, adicionei esta linha request.type_filling = ORDER_FILLING_IOC; ao colocar a ordem para corrigir o problema de colocação da ordem.
Testado novamente no M5, ele abriu apenas uma negociação por um período de 2 meses, nenhuma ordem parcial foi aberta.
Testado no H1, ele nunca aplicou o SL ou TP e todas as negociações foram fechadas com prejuízo.
Também durante a compilação, ele gera avisos
Sugira como testar o algoritmo,
período de tempo e quaisquer outras recomendações.
também durante a compilação, ele gera avisos
Alterei a linha de código
m_volumeProfile[intervalIndex] += rates[i].tick_volu
para
Isso corrigiu os avisos
Agora preciso de sua orientação em relação às minhas outras dúvidas, como
Time frame
E também
por que todas as negociações durante o backtest resultam em perda
como testar esse excelente trabalho de vocês...
Sugira como testar o algoritmo,
período de tempo e quaisquer outras recomendações.
Os avisos não são o problema, mas podem ser corrigidos rapidamente. Mas, sim, seria ótimo se o autor pudesse mostrar passo a passo quais configurações e entradas ele usou para o backtest.
Concordo com Dominic, pois os avisos são apenas avisos. Os resultados de I_Virgo provavelmente se devem ao fato de ele ter usado o período de tempo e o par de moedas errados. Pelo relatório do Back Test, de quase 2.000 barras, deve ter sido M1 ou M5 como o período de tempo com um par desconhecido.
Seria bom se a MQ adicionasse o período de tempo e o par ou pares de moedas e também separasse os resultados dos pares em mais detalhes no relatório de back-test para que pudéssemos replicar mais de perto os resultados do back-test do autor, bem como determinar sua aplicabilidade nos pares de moedas estrangeiras.
Também acho que é um ótimo artigo e planejo estudá-lo minuciosamente na expectativa de adaptar suas técnicas a outros EAs
CapeCoddah