Algoritmos avanzados de ejecución de órdenes en MQL5: TWAP, VWAP y órdenes Iceberg
- Introducción
- Entender los algoritmos de ejecución
- Implementación en MQL5
- Implementación del analizador de rendimiento
- Comparación del rendimiento de los algoritmos
- Integración de algoritmos de ejecución con estrategias comerciales
- Ejemplos de integración
- Integración de estrategias
- Resultados de las pruebas retrospectivas
- Conclusión
Introducción
Imagina que estás de pie al borde de la sala de operaciones, con el corazón latiendo con fuerza mientras los precios cambian en tiempo real. Un movimiento en falso, un pedido excesivo, y tu ventaja se esfuma en un instante. Bienvenido al mundo en el que la calidad de la ejecución no es solo algo deseable, sino el arma secreta que separa a los ganadores del resto.
Durante décadas, los pesos pesados institucionales han utilizado discretamente sofisticados algoritmos para dividir, fragmentar y desplegar sigilosamente sus órdenes, todo ello con el fin de evitar desviaciones y controlar el impacto en el mercado. Ahora, gracias a la flexibilidad de MQL5, ese mismo potente manual de estrategias está al alcance de todos los operadores minoristas ambiciosos.
¿Por qué tanto escándalo?
Imagínate esto: ves una oportunidad de oro y decides apostar fuerte. Usted ejecuta una orden de mercado por el tamaño total, solo para ver cómo el precio se desploma bajo el peso de su propia operación. En cuestión de segundos, tu entrada ideal se convierte en un compromiso inestable. Esa es la famosa desventaja del impacto en el mercado, y se nota incluso en los mercados más líquidos.
Los algoritmos de ejecución son tu antídoto. Al dividir un pedido grande en una secuencia de partes más pequeñas y estratégicamente sincronizadas, suavizan su huella en el libro de pedidos. ¿El resultado? Menos deslizamientos, ejecuciones más ajustadas y una mejora general en su precio medio de ejecución.
De la teoría al trading
«Claro», dirás encogiéndote de hombros, «pero yo no muevo sumas institucionales». Aquí está la clave: no tienes por qué hacerlo. Tanto si está desplegando medio lote como un puñado de minilotes, la volatilidad puede seguir afectando a su ejecución. Estas herramientas te ayudan a:
- Control del deslizamiento: Incluso las órdenes modestas pueden variar en mercados volátiles.
- Afilá tu ventaja: Las ejecuciones por capas suelen ofrecerte un precio medio más favorable que una apuesta única.
- Mantén la calma: Los flujos de trabajo automatizados eliminan la tentación de comprar o vender de forma precipitada.
- Escalabilidad sin fisuras: A medida que su cuenta crece, la ejecución sigue siendo ágil, independientemente del volumen de sus órdenes.
-
Pasa desapercibido: Las órdenes Iceberg, en particular, ocultan el tamaño real de tu orden, lo que mantiene a los algoritmos entrometidos en la incertidumbre.
El panorama democratizado actual significa que la misma tecnología de ejecución que antes requería presupuestos multimillonarios ahora puede funcionar en su estación de operaciones personal. Al incorporar el código MQL5 optimizado para las estrategias TWAP, VWAP e Iceberg en su plataforma, se dotará de la potencia institucional sin abandonar el ámbito minorista.
Prepárate para darle un giro radical a tu proceso de ejecución. El juego está cambiando, y con estos algoritmos en tu arsenal, jugarás para ganar.
Entender los algoritmos de ejecución
Antes de profundizar en los detalles de la implementación, es esencial comprender la teoría que subyace a cada algoritmo de ejecución y por qué son eficaces en diferentes escenarios de mercado.
- Precio medio ponderado por tiempo (Time-Weighted Average Price, TWAP): El TWAP es un algoritmo de ejecución sencillo que divide una orden grande en partes iguales y las envía a intervalos de tiempo fijos durante un período determinado. Su objetivo es igualar el precio medio del instrumento durante ese tiempo.
-
Cómo funciona:
- Envía órdenes a intervalos de tiempo regulares entre el inicio y el final.
- Generalmente se utilizan órdenes de igual tamaño (aunque puedes agregarle aleatoriedad a los tamaños).
- Sigue un calendario predeterminado, independientemente de los movimientos de precios.
-
Distribuye el impacto del mercado de manera uniforme a lo largo del tiempo para mantener bajo el deslizamiento.
-
Cuándo utilizarlo:
- Necesitas un precio medio de ejecución durante un periodo de tiempo específico.
- La liquidez se mantiene estable durante todo el período de negociación.
- Tienes un plazo determinado para completar tu orden.
-
Prefieres un enfoque sencillo y predecible.
-
- Precio medio ponderado por volumen (Volume-Weighted Average Price, VWAP): El VWAP mejora el TWAP al ponderar los tamaños de las órdenes según el volumen esperado. En lugar de tramos iguales, envía operaciones más grandes cuando el volumen tiende a ser mayor.
-
Cómo funciona:
- Asigna el tamaño de la orden en proporción a los patrones históricos de volumen.
- Analiza los volúmenes de negociación pasados para predecir la distribución futura del volumen.
- Puede adaptarse a cambios de volumen en tiempo real en algunas implementaciones.
-
Se ejecuta más durante los periodos de gran volumen para reducir el impacto.
-
Cuándo utilizarlo:
- Tu rendimiento se mide en función del VWAP.
- El volumen sigue un patrón diario predecible.
- Usted está operando en un mercado en el que la liquidez varía a lo largo de la sesión.
-
Quieres alinearte con el flujo natural del mercado.
-
- Órdenes Iceberg: Las órdenes Iceberg se centran en ocultar el tamaño real de una orden grande. Solo se ve una pequeña «punta» en cada momento; una vez que se llena, aparece la siguiente parte.
-
Cómo funciona:
- Muestra solo una parte del pedido total.
- Libera nuevos fragmentos visibles después de ejecutar cada consejo.
- Puede fijar o aleatorizar el tamaño visible para reducir la detección.
-
A menudo se colocan como órdenes limitadas para un mejor control del precio.
-
Cuándo utilizarlo:
- Debe ocultar el tamaño total de su pedido.
- El mercado no es muy líquido y las operaciones de gran volumen pueden influir en los precios.
- Desea mantener la ejecución a un nivel de precio específico.
-
Le preocupa que otros operadores detecten y se adelanten a su orden.
-
Implementación en MQL5
Ahora que comprendemos la teoría detrás de estos algoritmos de ejecución, implementémoslos en MQL5. Crearemos un marco modular y orientado a objetos que permita utilizar estos algoritmos de forma individual o combinarlos en un sistema de ejecución unificado.
Clase base: CExecutionAlgorithm
Comenzaremos definiendo una clase base que proporcione funcionalidad común para todos los algoritmos de ejecución:
//+------------------------------------------------------------------+ //| 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; } };
El método PlaceOrder es especialmente importante, ya que se encarga de la ejecución real de la orden y actualiza el seguimiento del volumen:
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; }
Esta función crea y envía una orden de mercado inicializando a cero MqlTradeRequest y MqlTradeResult, rellenando el símbolo, el volumen, el tipo de orden, el precio, el deslizamiento y un número mágico, y luego llamando a OrderSend. Si el envío falla o el código de retorno del bróker no es TRADE_RETCODE_DONE, registra el error y devuelve false. Si tiene éxito, actualiza los contadores internos (recuentos totales/de llenado, volumen ejecutado y restante), recalcula el precio medio, captura el ID del ticket y devuelve el valor true.
Implementación del TWAP
El algoritmo TWAP divide el período de ejecución en intervalos de tiempo iguales y coloca órdenes de igual tamaño (o aleatorias) en 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 clave: CalculateNextExecutionTime
Este método garantiza que los pedidos se espacien adecuadamente en el tiempo, con un retraso inicial para el primer pedido: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; }
Este método divide la ventana desde m_startTime hasta m_endTime en m_intervals segmentos iguales y devuelve cuándo debe activarse la siguiente operación: en la primera llamada es simplemente m_startTime + m_initialDelay, y en cada llamada posterior es TimeCurrent() + el valor de un intervalo en segundos (pero nunca más allá de m_endTime).
Método Execute:El método Execute comprueba si es el momento de realizar un pedido y se encarga de la realización del pedido propiamente dicha:
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; }
Este método Execute gestiona una parte de su ejecución TWAP. En primer lugar, se interrumpe si la estrategia no está activa o si aún no es el momento de operar. Cuando lo hace, selecciona una parte fija o aleatoria del volumen restante (sin superar nunca lo que queda) y, a continuación, busca el precio de venta actual (para compras) o el precio de compra (para ventas). Registra el intervalo, el volumen y el precio, crea una MqlTradeRequest con su símbolo, volumen, tipo, precio, deslizamiento y número mágico, y llama a OrderSend. Si el envío falla o el bróker devuelve cualquier código distinto de TRADE_RETCODE_DONE, se muestra un error y se devuelve false.
Si tiene éxito, incrementa los contadores de órdenes, ajusta el volumen ejecutado y restante, aumenta el recuento de intervalos, marca que se ha enviado la primera orden y, a continuación, programa la siguiente hora de ejecución o desactiva la estrategia si se han agotado los intervalos o el volumen. Finalmente, registra lo que ha ocurrido y devuelve verdadero.
Implementación de VWAP
El algoritmo VWAP es similar al TWAP, pero distribuye los tamaños de las órdenes basándose en patrones de volumen históricos://+------------------------------------------------------------------+ //| 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(); };Al igual que TWAP, VWAP también implementa el método CalculateNextExecutionTime para garantizar un espaciado adecuado de las órdenes:
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; }
Implementación de órdenes Iceberg
Las órdenes Iceberg ocultan el tamaño real de una orden, mostrando solo una pequeña parte al mercado en un momento dado://+------------------------------------------------------------------+ //| 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; } };El método Execute coloca una nueva parte visible de la orden:
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; }
Cuando se ejecuta Execute, primero verifica que el algoritmo esté activo. Si ya hay un pedido de Iceberg en curso, llama a CheckAndReplaceOrder() para ver si es necesario cancelarlo o reponerlo. De lo contrario, selecciona una porción visible (fija o aleatoria), la limita al total restante y registra el tamaño y el precio.
A continuación, crea una solicitud de orden pendiente ( TRADE_ACTION_PENDING ) con el símbolo, el volumen, el precio límite, el deslizamiento y el número mágico, y llama a OrderSend. En caso de error o códigos de retorno no completados, registra y devuelve false; en caso de éxito, guarda el nuevo ticket, marca el pedido como activo, registra la hora de colocación, registra los detalles y devuelve true.
El método Update incluye la detección del tiempo de espera de los pedidos para garantizar que estos no permanezcan activos 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; }
Periódicamente actualiza las condiciones del mercado y el estado de las órdenes a intervalos definidos por m_checkInterval. Si se ha completado todo el volumen, se termina. De lo contrario, una vez que llega la hora de la comprobación, registra la oferta/demanda actual y el precio límite. Si una orden está activa, comprueba si ha expirado: si ha expirado, la cancela, restablece el estado, permanece inactiva durante m_orderPlacementDelay y se vuelve a ejecutar para colocar una nueva porción; si no ha expirado, se remite a CheckAndReplaceOrder(). Si no hay ningún pedido activo, simplemente llama a Execute para enviar la siguiente parte visible.
Implementación del analizador de rendimiento
Nuestra clase de analizador de rendimiento realiza un seguimiento de estas métricas y proporciona métodos para analizar y comparar el rendimiento de los 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 agrupa todas tus métricas post-negociación en un solo lugar. Cuando lo construyes, le asignas un símbolo y el precio de referencia en el momento de la decisión; este marca la hora actual como el inicio. A medida que se completa cada orden secundaria, se llama a RecordExecution(tiempo, precio, volumen), que actualiza los totales acumulados: volumen acumulado, precio medio ponderado de ejecución y marcas de tiempo. Una vez finalizada la estrategia (o periódicamente), se llama a CalculateMetrics(), que calcula:
- Déficit de implementación (la diferencia entre el precio de su orden y el precio real de ejecución),
- El deslizamiento medio frente a los precios cotizados,
- Impacto estimado en el mercado de su actividad,
- Tiempo total de ejecución (fin menos inicio),
- Mejora del precio si la hubiera en comparación con los índices de referencia.
Incluso puedes comparar dos ejecuciones mediante CompareAlgorithms(otherAnalyzer) para ver qué estrategia ha funcionado mejor. Por último, PrintReport() envía las estadísticas clave al registro para su rápida revisión, y SaveReportToFile(nombre de archivo) le permite guardar un informe completo externamente. Los captadores livianos exponen cada métrica para paneles personalizados o análisis adicionales.
Comparación del rendimiento de los algoritmos
Las diferentes condiciones del mercado favorecen diferentes algoritmos de ejecución. Aquí hay una comparación general, es decir, una regla general:- TWAP:
- Ideal para: Mercados estables con liquidez constante.
- Ventajas: Patrón de ejecución sencillo y predecible.
- Desventajas: No se adapta a las condiciones cambiantes del mercado.
- VWAP:
- Ideal para: Mercados con patrones de volumen predecibles.
- Ventajas: Se ajusta al ritmo natural del mercado y, a menudo, permite obtener mejores precios.
- Desventajas: Requiere datos históricos de volumen, implementación más compleja.
- Órdenes Iceberg:
- Ideal para: Mercados menos líquidos o cuando la sensibilidad al precio es alta.
- Ventajas: Minimiza el impacto en el mercado, mantiene el control de los precios.
- Desventajas: El tiempo de ejecución puede ser impredecible, riesgo de ejecución parcial.
Integración de algoritmos de ejecución con estrategias comerciales
El verdadero poder de estos algoritmos de ejecución se manifiesta cuando se integran con estrategias de negociación. En esta sección se muestra cómo incorporar nuestros algoritmos de ejecución en sistemas de negociación completos.
Gestor de ejecución
Para simplificar la integración, crearemos una clase Execution Manager que sirva como fachada para todos nuestros algoritmos de ejecución:
//+------------------------------------------------------------------+ //| 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 actúa como una fachada simple sobre cualquiera de sus algoritmos de ejecución y los vincula en un flujo de trabajo comercial unificado. Internamente, contiene un puntero al CExecutionAlgorithm seleccionado actualmente (TWAP, VWAP o Iceberg) más un CPerformanceAnalyzer para rastrear el rendimiento de sus pedidos.
Usted elige su estrategia llamando a uno de los métodos Create…, pasando el símbolo, el volumen total, las horas de inicio y fin (para TWAP/VWAP), los conteos de intervalos o tamaños de porción, el tipo de orden y cualquier perilla específica del algoritmo (aleatorización, ventana de historial, precio límite, etc.). Una vez creado, lo ejecuta a través del ciclo de vida habitual:
- Initialize() configura cualquier estado o dato que necesites.
- Execute() activa la siguiente porción o orden secundaria.
- La función Update() completa las encuestas, los datos del mercado o los tiempos de espera.
-
Terminate() limpia una vez que hayas llenado todo o quieras detenerlo.
Si habilita el análisis de rendimiento con EnablePerformanceAnalysis(), el administrador registrará los precios de ejecución contra un punto de referencia de decisión y usted puede enviar un informe conciso de pérdidas y ganancias y deslizamiento a través de PrintPerformanceReport(). Siempre puedes obtener el objeto de algoritmo sin procesar con GetAlgorithm() para realizar sondeos o métricas personalizadas.
Ejemplos de integración
A continuación se muestran ejemplos de cómo integrar nuestros algoritmos de ejecución con diferentes tipos de estrategias comerciales:
-
Estrategia de seguimiento de tendencias con ejecución 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."); } } } }
-
Estrategia de reversión a la media con ejecución 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."); } } }
- Estrategia de ruptura con órdenes 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."); } } } }
En los tres ejemplos, la integración sigue el mismo patrón de alto nivel:
-
Mantener el estado
- Un valor booleano inPosition rastrea si actualmente estás completando un pedido.
-
Un CExecutionManager estático reside en los ticks para administrar el ciclo de vida del algoritmo elegido.
-
Lógica de entrada
- En su señal de entrada (cruce de MA, umbral RSI, ruptura de canal), llame al método de creación apropiado en executionManager (TWAP, VWAP o Iceberg), pasando símbolo, volumen total, ventana de tiempo o precio límite, parámetros de segmento y tipo de orden.
-
Si la creación es exitosa, llame inmediatamente a executionManager.Initialize(), establezca inPosition=true y registre su inicio.
-
Ejecución en curso
- Mientras inPosition sea true, cada OnTick() invoca executionManager.Update().
-
Dentro de Update(), el gestor llamará internamente a Execute() según sea necesario, sondeará los rellenos, gestionará los tiempos de espera o las actualizaciones del mercado y programará la siguiente porción (o cancelará/sustituirá las órdenes secundarias para Iceberg).
-
Finalización y salida
- Después de cada actualización, verifique executionManager.GetAlgorithm()->IsActive(). Una vez que devuelva false (todos los intervalos completados o volumen agotado), establezca inPosition=false y registre que la ejecución ha finalizado.
-
En el ejemplo de reversión a la media VWAP, hay una comprobación de salida adicional: si el precio se revierte más allá de su umbral RSI durante la ejecución, llame a executionManager.Terminate() para detenerlo antes de tiempo.
- Seguimiento de tendencias + TWAP
Entrada: El cruce rápido de la media móvil por encima de la media móvil lenta activa la señal.Ejecución: Divide su compra de 1 lote en 5 partes iguales durante la siguiente hora. - Reversión a la media + VWAP
Entrada: RSI < 30 para compras, > 70 para ventas
Ejecución: Distribuye 1 lote en función del volumen histórico en 5 tramos en 1 hora
Salida anticipada: Si la señal cambia (por ejemplo, RSI > 70 durante una compra), executionManager.Terminate() aborta las porciones restantes - Rompimiento (Breakout) + Iceberg
Entrada: El precio rompe el máximo (compra) o el mínimo (venta) del canal.Ejecución: Coloca un límite pendiente al precio de ruptura, revelando solo ~0,1 lotes a la vez y rellenando hasta completar el lote completo de 1.
Al intercambiar CreateTWAP, CreateVWAP o CreateIcebergOrder, puede conectar cualquier algoritmo de ejecución a su lógica de señales sin duplicar el código repetitivo de gestión de órdenes.
Integración de estrategias
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); } } } //+------------------------------------------------------------------+
El asesor experto IntegratedStrategy.mq5 comienza declarando sus metadatos (derechos de autor, enlace, versión) e incluyendo los encabezados de todas nuestras clases de algoritmos de ejecución y el analizador de rendimiento. A continuación, define cuatro entradas ajustables por el usuario: los períodos SMA rápidos y lentos, el volumen total de negociación por señal y un indicador booleano para alternar la ejecución «adaptativa» (donde el gestor decide si utilizar TWAP, VWAP o Iceberg bajo el capó). También se declararon un puntero global a CExecutionManager y dos identificadores de indicadores para que persistan entre ticks.
En OnInit(), instanciamos el gestor de ejecución, pasándole el símbolo actual, un máximo de tres algoritmos concurrentes y nuestro indicador adaptativo, y luego creamos los dos identificadores del indicador SMA. Si falla cualquiera de los dos identificadores, la inicialización se interrumpe. OnDeinit() simplemente limpia eliminando el administrador y liberando los identificadores del indicador, lo que garantiza que no haya fugas de memoria o identificadores cuando se elimina el EA o se apaga la plataforma.
La lógica central reside en OnTick(). En primer lugar, llamamos a UpdateAlgorithms() en el gestor de ejecución para que cualquier orden secundaria existente (porciones TWAP, cubos VWAP o tramos Iceberg) se procese, cancele o reponga según sea necesario. A continuación, esperamos una nueva barra (saltándonos si el volumen de ticks sigue aumentando). Una vez que se abre el bar, extraemos los dos últimos valores SMA tanto para los periodos rápidos como para los lentos. Un cruce de abajo hacia arriba activa una señal de compra; lo contrario activa una señal de venta.
Si la ejecución adaptativa está habilitada, transferimos la señal y el volumen a g_executionManager.ExecuteSignal(), dejando que este elija el algoritmo adecuado. De lo contrario, activamos manualmente una instancia TWAP durante una ventana de una hora y seis segmentos. Este patrón separa claramente la lógica de entrada (detección de tendencias) de la lógica de gestión de órdenes, lo que permite que la misma fachada impulse múltiples estilos de ejecución sin duplicar el código repetitivo.
Después de incorporar Take Profit, el código cambia a:
//+------------------------------------------------------------------+ //| 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 de las pruebas retrospectivas


1. Curvas de riesgo y rentabilidad
Los escalones verdes de «Balance» muestran el capital contable de su cuenta cada vez que el EA cierra una posición; la línea azul «Equity» suaviza las ganancias y pérdidas no realizadas entre operaciones. Podemos observar una clara tendencia al alza desde enero hasta principios de marzo, con algunos retrocesos, cada uno de los cuales alcanza un máximo de entre el 10 % y el 16 % antes de que la siguiente serie de ganancias recupere el terreno perdido. Ese patrón sugiere que el sistema prospera en condiciones de tendencia, pero aún así sufre caídas tolerables en el valor neto.
2. Volumen y explotación del riesgo
En la parte inferior, los triángulos «Deposit Load» (carga de depósito) se reducen gradualmente con el tiempo: este es el tamaño de su posición como porcentaje del capital. Comienza cerca del 10 % de su saldo y se reduce a medida que crece su capital (con un tamaño de volumen fijo), lo que significa que nuestro riesgo por operación disminuye a medida que la cuenta aumenta. Por eso las reducciones se mantienen proporcionalmente similares incluso cuando aumenta su capital en dólares.
3. Métricas clave de rentabilidad
-
Depósito inicial: 1000 $
-
Beneficio neto: +703 $ (un rendimiento del 70 % en aproximadamente dos meses)
-
Factor de beneficio: 2,34 (ganas 2,34 $ por cada 1 $ perdido)
-
Ganancia esperada: 2,34 dólares por operación de media.
-
Ratio Sharpe: 5,47 (muy alto: fuertes rendimientos ajustados al riesgo)
Estas cifras nos indican que la estrategia no solo es rentable, sino que genera un margen considerable por encima de su propia volatilidad.
4. Reducción y recuperación
-
Pérdida máxima: 156 puntos o 9,99 %
-
Pérdida máxima de capital: 228 puntos o 15,89 %.
-
Factor de recuperación: 3,08 (beneficio neto ÷ caída máxima)
Un factor de recuperación superior a 2 se considera generalmente bueno, por lo que con un 3,08 estás generando más del triple de tu peor pérdida en ganancias.
5. Distribución comercial
-
Total de operaciones: 300 (600 transacciones, por lo que cada entrada y salida cuenta como dos).
-
Porcentaje de victorias: 76 % (228 victorias frente a 72 derrotas)
-
Ganancia media: 5,39 $
-
Pérdida media: – 7,31 $
Aunque su tasa de ganancias y su factor de beneficio son sólidos, tenga en cuenta que sus pérdidas son, en promedio, mayores que sus ganancias, algo a tener en cuenta si las condiciones del mercado cambian.
6. Rachas y consistencia
-
Máximo de ganancias consecutivas: 87 operaciones, +302 $.
-
Máximo de pérdidas consecutivas: 23 operaciones, – 156 $.
-
Racha ganadora promedio: 57 operaciones
-
Racha de pérdidas promedio: 18 operaciones
Las largas rachas ganadoras impulsan la tendencia al alza, mientras que la racha perdedora más larga solo cuesta alrededor del 15 % del capital.
Conclusión
Imagina que eres un operador independiente en un mercado saturado: cada tick cuenta, cada precio de ejecución susurra ganancias o pérdidas. Al incorporar TWAP, VWAP y órdenes Iceberg a su conjunto de herramientas, ya no solo reaccionará ante las fluctuaciones de precios, sino que las coordinará. Estos algoritmos, que antes eran exclusivos de las instituciones, ahora están al alcance de tu mano, cortando la liquidez como un láser y convirtiendo las caóticas carteras de pedidos en oportunidades.
TWAP se convierte en tu metrónomo constante, regulando tu tamaño de manera uniforme a lo largo de un intervalo establecido, perfecto para cuando la marea está tranquila y simplemente deseas un paseo suave. VWAP te convierte en un experto rastreador de volúmenes, atacando los momentos de mayor actividad bursátil del día y siguiendo el pulso del propio mercado. Y cuando necesitas ocultar tus intenciones, las órdenes Iceberg ocultan tu verdadero tamaño bajo la superficie, revelando solo lo necesario para que se cumpla la orden sin asustar a los grandes operadores.
Pero estos no son solo trucos aislados. Con nuestro marco modular MQL5, puede incorporarlos a cualquier estrategia (seguidores de tendencias, reversores medios, cazadores de rupturas) con la misma facilidad con la que se coloca una nueva lente. Una única interfaz (ExecutionManager) le permite intercambiar, combinar o incluso superponer algoritmos durante la negociación, mientras que PerformanceAnalyzer lleva la cuenta con gran precisión, midiendo el deslizamiento, el déficit y el impacto en el mercado hasta el último pip.
¿Qué sigue? Piensa en la ejecución como un ser vivo que se adapta. Deja que tu TWAP aprenda de los picos de volatilidad. Dirige tus porciones de VWAP a los fondos más profundos. Enseña a tu Iceberg a detectar dónde acechan los depredadores y a esconderse más profundamente. ¿Y por qué detenerse ahí? Incorpore el aprendizaje automático para predecir el microsegundo perfecto para disparar, o combine tipos de órdenes en híbridos personalizados que se adapten a su ventaja competitiva única.
El mundo del trading nunca se detiene, y tampoco debería hacerlo la ejecución de sus órdenes. Sumérgete, experimenta con audacia y convierte cada corte, cada relleno, en una ventaja calculada. Tu ventaja te espera en el código.
Para su comodidad, aquí tiene un resumen de los archivos incluidos en este artículo:
| Nombre del archivo | Descripción |
|---|---|
| ExecutionAlgorithm.mqh | Clase base para todos los algoritmos de ejecución. |
| TWAP.mqh | Implementación del precio promedio ponderado en el tiempo. |
| VWAP.mqh | Implementación del precio promedio ponderado por volumen. |
| IcebergOrder.mqh | Implementación de órdenes Iceberg. |
| PerformanceAnalyzer.mqh | Herramientas para analizar el rendimiento de la ejecución. |
| ExecutionManager.mqh | Fachada para una fácil integración con estrategias las comerciales. |
| IntegratedStrategy.mq5 | EA de ejemplo que muestra la integración con una estrategia de trading. |
| IntegratedStrategy - Take Profit.mq5 | EA de ejemplo que muestra la integración con una estrategia de trading con take profit y stop loss en porcentaje sobre el saldo de la cuenta. |
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/17934
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.
Análisis cuantitativo de tendencias: Recopilamos estadísticas en Python
Modelos ocultos de Márkov en sistemas comerciales de aprendizaje automático
Particularidades del trabajo con números del tipo double en MQL4
Automatización de estrategias de trading en MQL5 (Parte 17): Dominar la estrategia de scalping Grid-Mart con un panel de control dinámico
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso
Prueba de su algo.
EN el archivo, ExecutionAlgorithm.mqh, añadido este request.type_filling línea = ORDER_FILLING_IOC; en la colocación de la orden para solucionar el problema de colocación de pedidos.
Probado en M5, abrió sólo 1 operación durante 2 meses, no abrió ninguna orden parcial.
Probado en H1, nunca aplicó el SL, o TP y todas las operaciones cerraron con pérdidas.
también durante la compilación genera advertencias
sugiera cómo probar el algo,
Time frame. y cualquier otra recomendación.
también al compilar genera warnings
he cambiado la línea de código
m_volumeProfile[intervalIndex] += rates[i].tick_volu
a
Se fijaron las advertencias
Ahora necesito que guidence con respecto a mis otras preguntas, como
marco de tiempo
y también
por qué todas las operaciones durante backtest resultado en la pérdida
cómo probar este gran trabajo de usted ..
sugiera cómo probar el algo,
Time frame. y cualquier otra recomendación.
Las advertencias no son el problema pero podrían arreglarse rápidamente. Pero sí sería genial si el autor pudiera mostrar paso a paso qué ajustes y entradas utilizó para el backtest.
Estoy de acuerdo con Dominic ya que las advertencias son sólo advertencias. Los resultados de I_Virgo son probablemente porque utilizó el marco de tiempo y el par de divisas equivocado. Desde el informe de Back Test, de casi 2000 barras, debe haber sido M1 o M5 como el marco de tiempo con un par desconocido.
Sería bueno si MQ añadiera el marco de tiempo y el par de divisas o pares y también separara los resultados de los pares en más detalles en el informe de la prueba retrospectiva para que pudiéramos replicar más de cerca los resultados de la prueba retrospectiva del autor, así como determinar su aplicabilidad en los pares de divisas. Además, sería extremadamente útil si el EA pudiera publicar texto en el gráfico mientras se está ejecutando.
Yo también creo que es un gran artículo y el plan para estudiarlo a fondo en previsión de la adaptación de sus técnicas a otras EAs
CapeCoddah