Automatización de estrategias de trading en MQL5 (Parte 18): Estrategia de scalping «Trend Bounce» con envolventes: Ejecución de operaciones y gestión del riesgo (Parte II)
Introducción
En nuestro artículo anterior (Parte 18), sentamos las bases para la estrategia de scalping «Envelopes» basada en rebotes de tendencia en MetaQuotes Language 5 (MQL5), creando la infraestructura básica del asesor experto y la lógica de generación de señales. Ahora, en la parte 19, damos un paso más al implementar la ejecución de operaciones y la gestión de riesgos para automatizar la estrategia por completo. Trataremos los siguientes temas:
- Hoja de ruta estratégica y arquitectura
- Implementación en MQL5
- Backtesting y optimización
- Conclusión
Al final, tendrás un sistema de trading completo en MQL5 para operar con rebotes de tendencia mediante scalping, optimizado para ofrecer el máximo rendimiento. ¡Empecemos!
Hoja de ruta estratégica y arquitectura
En la parte 18, sentamos las bases de la estrategia de scalping «Envelopes Trend Bounce», configurando el sistema para detectar señales de trading a partir de las interacciones del precio con el indicador «Envelopes», confirmadas por filtros de tendencia como las medias móviles y el RSI. Nos centramos en construir la infraestructura para supervisar las condiciones del mercado e identificar posibles oportunidades de trading, pero no habilitamos el sistema para ejecutar operaciones ni gestionar riesgos. Nuestra hoja de ruta para la Parte 19 se centra en la activación de la ejecución de operaciones y la implementación de la gestión de riesgos para garantizar que la estrategia pueda actuar sobre las señales de manera eficaz y segura.
Nuestro plan arquitectónico prioriza un enfoque modular, estableciendo un proceso claro para convertir las señales en operaciones al tiempo que incorpora medidas de seguridad. Nuestro objetivo es desarrollar un mecanismo para realizar órdenes de compra y venta basadas en señales validadas, junto con un marco de gestión de riesgos que establezca niveles de stop-loss y take-profit, ajuste el tamaño de las posiciones en función del saldo de la cuenta y limite las pérdidas totales para proteger el capital. Este diseño dará lugar a un sistema de scalping automatizado y coherente. Definiremos algunas clases más para gestionar las operaciones e integraremos toda la funcionalidad en el procesamiento de ticks para que todo funcione correctamente. En resumen, esto es lo que pretendemos lograr.

Implementación en MQL5
Para crear el programa en MQL5, abre el MetaEditor, ve al Navegador, busca la carpeta «Experts», haz clic en la pestaña «Nuevo» y sigue las instrucciones para crear el archivo. Una vez creado, en el entorno de programación tendremos que declarar algunas interfaces y clases para mejorar la generación de señales, la organización y la gestión de operaciones. Empecemos por la interfaz básica de expresión de señales.
//--- Define interface for strategy signal expressions interface IAdvisorStrategyExpression { bool Evaluate(); //--- Evaluate signal bool GetFireOnlyWhenReset(); //--- Retrieve fire-only-when-reset flag void SetFireOnlyWhenReset(bool value); //--- Set fire-only-when-reset flag void ResetSignalValue(); //--- Reset signal value }; //--- Define base class for advisor signals class ASSignal : public IAdvisorStrategyExpression { private: bool _fireOnlyWhenReset; //--- Store fire-only-when-reset flag int _previousSignalValue; //--- Store previous signal value int _signalValue; //--- Store current signal value protected: //--- Declare pure virtual method for signal evaluation bool virtual EvaluateSignal() = 0; //--- Require derived classes to implement public: //--- Initialize signal void ASSignal() { _fireOnlyWhenReset = false; //--- Set fire-only-when-reset to false _previousSignalValue = -1; //--- Set previous value to -1 _signalValue = -1; //--- Set current value to -1 } //--- Evaluate signal bool Evaluate() { if (_signalValue == -1) { //--- Check if signal uncomputed _signalValue = EvaluateSignal(); //--- Compute signal } if (_fireOnlyWhenReset) { //--- Check fire-only-when-reset return (_previousSignalValue <= 0 && _signalValue == 1); //--- Return true if signal transitions to 1 } else { return _signalValue == 1; //--- Return true if signal is 1 } } //--- Retrieve fire-only-when-reset flag bool GetFireOnlyWhenReset() { return _fireOnlyWhenReset; //--- Return flag } //--- Set fire-only-when-reset flag void SetFireOnlyWhenReset(bool value) { _fireOnlyWhenReset = value; //--- Set flag } //--- Reset signal value void ResetSignalValue() { _previousSignalValue = _signalValue; //--- Store current as previous _signalValue = -1; //--- Reset current value } };
Para establecer un marco optimizado para el manejo de señales de trading, nos centramos en un sistema de evaluación de señales modular y fiable. Comenzamos definiendo la interfaz "IAdvisorStrategyExpression", que utilizamos para crear un modelo coherente para las operaciones de señalización. Esta interfaz incluye cuatro funciones clave: la función "Evaluate" para determinar si una señal está activa, la función "GetFireOnlyWhenReset" para comprobar un indicador que controla los disparadores de señal, la función "SetFireOnlyWhenReset" para modificar este indicador y la función "ResetSignalValue" para borrar el estado de la señal para evaluaciones posteriores.
A continuación, desarrollamos la clase «ASSignal», que diseñamos para que implemente la interfaz «IAdvisorStrategyExpression» y que servirá de base para los tipos de señales específicos de nuestra estrategia. Dentro de la clase «ASSignal», definimos tres variables privadas: «_fireOnlyWhenReset», para controlar si las señales se activan únicamente tras un reinicio; «_previousSignalValue», para registrar el estado anterior de la señal; y «_signalValue», para almacenar el estado actual. Los inicializamos en la «ASSignal» constructor de la clase, estableciendo «_fireOnlyWhenReset» en false y tanto «_previousSignalValue» como «_signalValue» en -1, lo que indica un estado no calculado. Nuestra función «Evaluate» comprueba si «_signalValue» es -1, invocando la función virtual pura «EvaluateSignal» (que se definirá en las clases derivadas) para calcular la señal, y devuelve «true» en función de «_fireOnlyWhenReset»: bien cuando «_signalValue» es 1, bien cuando se produce una transición de un «_previousSignalValue» no positivo a 1.
Para gestionar el comportamiento de las señales, implementamos la función «GetFireOnlyWhenReset» para recuperar el valor del indicador «_fireOnlyWhenReset» y la función «SetFireOnlyWhenReset» para actualizarlo, lo que nos permite ajustar con precisión cuándo se activan las señales. También incluimos la función «ResetSignalValue», que utilizamos para almacenar «_signalValue» en «_previousSignalValue» y restablecer «_signalValue» a -1, preparándonos así para el siguiente ciclo de evaluación. Al marcar «EvaluateSignal» como método virtual puro, exigimos que las clases derivadas proporcionen una lógica de señal específica, lo que garantiza la flexibilidad. Ahora podemos definir algunas clases más para la lógica de gestión de señales.
//--- Define class for managing trade signals class TradeSignalCollection { private: IAdvisorStrategyExpression* _tradeSignals[]; //--- Store array of signal pointers int _pointer; //--- Track current iteration index int _size; //--- Track number of signals public: //--- Initialize empty signal collection void TradeSignalCollection() { _pointer = -1; //--- Set initial pointer to -1 _size = 0; //--- Set initial size to 0 } //--- Destructor to clean up signals void ~TradeSignalCollection() { for (int i = 0; i < ArraySize(_tradeSignals); i++) { //--- Iterate signals delete(_tradeSignals[i]); //--- Delete each signal } } //--- Add signal to collection void Add(IAdvisorStrategyExpression* item) { _size = _size + 1; //--- Increment size ArrayResize(_tradeSignals, _size, 8); //--- Resize array with reserve _tradeSignals[(_size - 1)] = item; //--- Store signal at last index } //--- Remove signal at index IAdvisorStrategyExpression* Remove(int index) { IAdvisorStrategyExpression* removed = NULL; //--- Initialize removed signal as null if (index >= 0 && index < _size) { //--- Check valid index removed = _tradeSignals[index]; //--- Store signal to remove for (int i = index; i < (_size - 1); i++) { //--- Shift signals left _tradeSignals[i] = _tradeSignals[i + 1]; //--- Move signal } ArrayResize(_tradeSignals, ArraySize(_tradeSignals) - 1, 8); //--- Reduce array size _size = _size - 1; //--- Decrement size } return removed; //--- Return removed signal or null } //--- Retrieve signal at index IAdvisorStrategyExpression* Get(int index) { if (index >= 0 && index < _size) { //--- Check valid index return _tradeSignals[index]; //--- Return signal } return NULL; //--- Return null for invalid index } //--- Retrieve number of signals int Count() { return _size; //--- Return current size } //--- Reset iterator to start void Rewind() { _pointer = -1; //--- Set pointer to -1 } //--- Move to next signal IAdvisorStrategyExpression* Next() { _pointer++; //--- Increment pointer if (_pointer == _size) { //--- Check if at end Rewind(); //--- Reset pointer return NULL; //--- Return null } return Current(); //--- Return current signal } //--- Move to previous signal IAdvisorStrategyExpression* Prev() { _pointer--; //--- Decrement pointer if (_pointer == -1) { //--- Check if before start return NULL; //--- Return null } return Current(); //--- Return current signal } //--- Check if more signals exist bool HasNext() { return (_pointer < (_size - 1)); //--- Return true if pointer is before end } //--- Retrieve current signal IAdvisorStrategyExpression* Current() { return _tradeSignals[_pointer]; //--- Return signal at pointer } //--- Retrieve current iterator index int Key() { return _pointer; //--- Return current pointer } }; //--- Define class for managing trading signals class AdvisorStrategy { private: TradeSignalCollection* _openBuySignals; //--- Store open Buy signals TradeSignalCollection* _openSellSignals; //--- Store open Sell signals TradeSignalCollection* _closeBuySignals; //--- Store close Buy signals TradeSignalCollection* _closeSellSignals; //--- Store close Sell signals //--- Evaluate signal at specified level bool EvaluateASLevel(TradeSignalCollection* signals, int level) { if (level > 0 && level <= signals.Count()) { //--- Check valid level return signals.Get(level - 1).Evaluate(); //--- Evaluate signal } return false; //--- Return false for invalid level } public: //--- Initialize strategy void AdvisorStrategy() { _openBuySignals = new TradeSignalCollection(); //--- Create open Buy signals collection _openSellSignals = new TradeSignalCollection(); //--- Create open Sell signals collection _closeBuySignals = new TradeSignalCollection(); //--- Create close Buy signals collection _closeSellSignals = new TradeSignalCollection(); //--- Create close Sell signals collection } //--- Destructor to clean up signals void ~AdvisorStrategy() { delete(_openBuySignals); //--- Delete open Buy signals delete(_openSellSignals); //--- Delete open Sell signals delete(_closeBuySignals); //--- Delete close Buy signals delete(_closeSellSignals); //--- Delete close Sell signals } //--- Retrieve trading advice bool GetAdvice(TradeAction tradeAction, int level) { if (tradeAction == OpenBuyAction) { //--- Check open Buy action return EvaluateASLevel(_openBuySignals, level); //--- Evaluate Buy signal } else if (tradeAction == OpenSellAction) { //--- Check open Sell action return EvaluateASLevel(_openSellSignals, level); //--- Evaluate Sell signal } else if (tradeAction == CloseBuyAction) { //--- Check close Buy action return EvaluateASLevel(_closeBuySignals, level); //--- Evaluate close Buy signal } else if (tradeAction == CloseSellAction) { //--- Check close Sell action return EvaluateASLevel(_closeSellSignals, level); //--- Evaluate close Sell signal } else { Alert("Unsupported TradeAction in Advisor Strategy. TradeAction: " + DoubleToStr(tradeAction)); //--- Log unsupported action } return false; //--- Return false for invalid action } //--- Register open Buy signal void RegisterOpenBuy(IAdvisorStrategyExpression* openBuySignal, int level) { if (level <= _openBuySignals.Count()) { //--- Check if level already set Alert("Register Open Buy failed: level already set."); //--- Log failure return; //--- Exit } _openBuySignals.Add(openBuySignal); //--- Add signal } //--- Register open Sell signal void RegisterOpenSell(IAdvisorStrategyExpression* openSellSignal, int level) { if (level <= _openSellSignals.Count()) { //--- Check if level already set Alert("Register Open Sell failed: level already set."); //--- Log failure return; //--- Exit } _openSellSignals.Add(openSellSignal); //--- Add signal } //--- Register close Buy signal void RegisterCloseBuy(IAdvisorStrategyExpression* closeBuySignal, int level) { if (level <= _closeBuySignals.Count()) { //--- Check if level already set Alert("Register Close Buy failed: level already set."); //--- Log failure return; //--- Exit } _closeBuySignals.Add(closeBuySignal); //--- Add signal } //--- Register close Sell signal void RegisterCloseSell(IAdvisorStrategyExpression* closeSellSignal, int level) { if (level <= _closeSellSignals.Count()) { //--- Check if level already set Alert("Register Close Sell failed: level already set."); //--- Log failure return; //--- Exit } _closeSellSignals.Add(closeSellSignal); //--- Add signal } //--- Retrieve number of signals for action int GetNumberOfExpressions(TradeAction tradeAction) { if (tradeAction == OpenBuyAction) { //--- Check open Buy action return _openBuySignals.Count(); //--- Return Buy signal count } else if (tradeAction == OpenSellAction) { //--- Check open Sell action return _openSellSignals.Count(); //--- Return Sell signal count } else if (tradeAction == CloseBuyAction) { //--- Check close Buy action return _closeBuySignals.Count(); //--- Return close Buy signal count } else if (tradeAction == CloseSellAction) { //--- Check close Sell action return _closeSellSignals.Count(); //--- Return close Sell signal count } return 0; //--- Return 0 for invalid action } //--- Set fire-only-when-reset for all signals void SetFireOnlyWhenReset(bool value) { _openBuySignals.Rewind(); //--- Reset Buy signals iterator while (_openBuySignals.Next() != NULL) { //--- Iterate Buy signals _openBuySignals.Current().SetFireOnlyWhenReset(value); //--- Set flag } _openSellSignals.Rewind(); //--- Reset Sell signals iterator while (_openSellSignals.Next() != NULL) { //--- Iterate Sell signals _openSellSignals.Current().SetFireOnlyWhenReset(value); //--- Set flag } _closeBuySignals.Rewind(); //--- Reset close Buy signals iterator while (_closeBuySignals.Next() != NULL) { //--- Iterate close Buy signals _closeBuySignals.Current().SetFireOnlyWhenReset(value); //--- Set flag } _closeSellSignals.Rewind(); //--- Reset close Sell signals iterator while (_closeSellSignals.Next() != NULL) { //--- Iterate close Sell signals _closeSellSignals.Current().SetFireOnlyWhenReset(value); //--- Set flag } } //--- Handle tick event for signals void HandleTick() { _openBuySignals.Rewind(); //--- Reset Buy signals iterator while (_openBuySignals.Next() != NULL) { //--- Iterate Buy signals _openBuySignals.Current().ResetSignalValue(); //--- Reset signal value if (_openBuySignals.Current().GetFireOnlyWhenReset()) { //--- Check fire-only-when-reset _openBuySignals.Current().Evaluate(); //--- Evaluate signal } } _openSellSignals.Rewind(); //--- Reset Sell signals iterator while (_openSellSignals.Next() != NULL) { //--- Iterate Sell signals _openSellSignals.Current().ResetSignalValue(); //--- Reset signal value if (_openSellSignals.Current().GetFireOnlyWhenReset()) { //--- Check fire-only-when-reset _openSellSignals.Current().Evaluate(); //--- Evaluate signal } } _closeBuySignals.Rewind(); //--- Reset close Buy signals iterator while (_closeBuySignals.Next() != NULL) { //--- Iterate close Buy signals _closeBuySignals.Current().ResetSignalValue(); //--- Reset signal value if (_closeBuySignals.Current().GetFireOnlyWhenReset()) { //--- Check fire-only-when-reset _closeBuySignals.Current().Evaluate(); //--- Evaluate signal } } _closeSellSignals.Rewind(); //--- Reset close Sell signals iterator while (_closeSellSignals.Next() != NULL) { //--- Iterate close Sell signals _closeSellSignals.Current().ResetSignalValue(); //--- Reset signal value if (_closeSellSignals.Current().GetFireOnlyWhenReset()) { //--- Check fire-only-when-reset _closeSellSignals.Current().Evaluate(); //--- Evaluate signal } } } };
Aquí implementamos más clases para gestionar las señales de trading. Dado que ya hemos explicado en parte qué son las clases en las secciones anteriores, esperamos que ya conozcas la sintaxis de las clases, por lo que nos limitaremos a mostrar los aspectos más importantes. Creamos la clase «TradeSignalCollection», utilizando «_tradeSignals» para almacenar punteros a «IAdvisorStrategyExpression», «_pointer» para la iteración y «_size» para el recuento de señales, inicializados en el constructor de «TradeSignalCollection». Añadimos señales con la función «Add», las eliminamos con «Remove» y nos desplazamos utilizando las funciones «Next», «Prev», «Current», «Rewind» y «HasNext», mientras que la limpieza la gestiona el destructor.
En la clase «AdvisorStrategy», definimos «_openBuySignals», «_openSellSignals», «_closeBuySignals» y «_closeSellSignals» como objetos de tipo «TradeSignalCollection», que se configuran en el constructor de «AdvisorStrategy». Evaluamos las señales con las funciones «GetAdvice» y «EvaluateASLevel», registramos las señales mediante «RegisterOpenBuy», «RegisterOpenSell», «RegisterCloseBuy» y «RegisterCloseSell», y gestionamos el recuento de señales con «GetNumberOfExpressions». La función «SetFireOnlyWhenReset» aplica indicadores de reinicio, y «HandleTick» reinicia y evalúa las señales mediante «ResetSignalValue» y «Evaluate», lo que garantiza una gestión eficiente de las señales para nuestra estrategia. Ahora podemos definir clases para las señales de venta y compra en niveles determinados. Empecemos por la clase de ventas.
//--- Define class for open Sell signal at level 1 class ASOpenSellLevel1 : public ASSignal { protected: //--- Evaluate Sell signal bool EvaluateSignal() { Order* openOrder = _ea.GetWallet().GetMostRecentOpenOrder(); //--- Retrieve recent open order if (openOrder != NULL && openOrder.Type == ORDER_TYPE_BUY) { //--- Check if Buy order openOrder = NULL; //--- Clear if Buy to avoid conflict } if (((((openOrder != NULL ? TimeCurrent() - openOrder.OpenTime : EMPTY_VALUE) == EMPTY_VALUE) //--- Check no recent order && ((BidFunc.GetValue(0) < fn_iMA_SMA_4(Symbol(), 0)) //--- Check Bid below 4-period SMA && ((BidFunc.GetValue(0) > fn_iMA_SMA8(Symbol(), 0)) //--- Check Bid above 8-period SMA && ((BidFunc.GetValue(0) < fn_iEnvelopes_ENV_UPPER(Symbol(), 0, 0)) //--- Check Bid below upper Envelope && ((fn_iRSI_RSI(Symbol(), 1) < OpenSell_Const_0) //--- Check previous RSI below threshold && (fn_iRSI_RSI(Symbol(), 0) >= OpenSell_Const_0) //--- Check current RSI above threshold ) ) ) ) ) || (((openOrder != NULL ? TimeCurrent() - openOrder.OpenTime : EMPTY_VALUE) != EMPTY_VALUE) //--- Check existing order && (BidFunc.GetValue(0) > ((openOrder != NULL ? openOrder.OpenPrice : EMPTY_VALUE) + (PipPoint * OpenSell_Const_1))) //--- Check Bid above open price plus offset ) )) { return true; //--- Return true for Sell signal } return false; //--- Return false if no signal } public: //--- Initialize Sell signal void ASOpenSellLevel1() {} //--- Empty constructor };
Desarrollamos una lógica de señal de venta utilizando la clase «ASOpenSellLevel1», derivada de la clase «ASSignal». En la función protegida «EvaluateSignal», comprobamos la última orden mediante la función «GetMostRecentOpenOrder» desde el «Wallet» de «_ea», y descartamos esa referencia si se trata de una orden de compra. Activamos una señal de venta si no existe ninguna orden reciente y el precio de compra, obtenido mediante la función «GetValue» de «BidFunc», se encuentra por debajo de la media móvil simple (SMA) de 4 períodos («fn_iMA_SMA_4»), por encima de la media móvil simple (SMA) de 8 períodos («fn_iMA_SMA8»), por debajo de la banda superior de las envolventes («fn_iEnvelopes_ENV_UPPER»), con el RSI anterior («fn_iRSI_RSI») por debajo de «OpenSell_Const_0» y el RSI actual igual o superior a dicho valor, o si existe una orden y la oferta supera el precio de apertura más «PipPoint» multiplicado por «OpenSell_Const_1».
Devolvemos «true» si la señal es válida; en caso contrario, «false». La función constructora «ASOpenSellLevel1» está vacía, ya que aprovecha la inicialización de «ASSignal». Aplicamos la misma lógica para una señal de compra.
//--- Define class for open Buy signal at level 1 class ASOpenBuyLevel1 : public ASSignal { protected: //--- Evaluate Buy signal bool EvaluateSignal() { Order* openOrder = _ea.GetWallet().GetMostRecentOpenOrder(); //--- Retrieve most recent open order if (openOrder != NULL && openOrder.Type == ORDER_TYPE_SELL) { //--- Check if Sell order openOrder = NULL; //--- Clear if Sell to avoid conflict } if (((((openOrder != NULL ? TimeCurrent() - openOrder.OpenTime : EMPTY_VALUE) == EMPTY_VALUE) //--- Check no recent order && ((AskFunc.GetValue(0) > fn_iMA_SMA_4(Symbol(), 0)) //--- Check Ask above 4-period SMA && ((AskFunc.GetValue(0) < fn_iMA_SMA8(Symbol(), 0)) //--- Check Ask below 8-period SMA && ((AskFunc.GetValue(0) > fn_iEnvelopes_ENV_LOW(Symbol(), 1, 0)) //--- Check Ask above lower Envelope && ((fn_iRSI_RSI(Symbol(), 1) > OpenBuy_Const_0) //--- Check previous RSI above threshold && (fn_iRSI_RSI(Symbol(), 0) <= OpenBuy_Const_0) //--- Check current RSI below threshold ) ) ) ) ) || (((openOrder != NULL ? TimeCurrent() - openOrder.OpenTime : EMPTY_VALUE) != EMPTY_VALUE) //--- Check existing order && (AskFunc.GetValue(0) < ((openOrder != NULL ? openOrder.OpenPrice : EMPTY_VALUE) - (PipPoint * OpenBuy_Const_1))) //--- Check Ask below open price minus offset ) )) { return true; //--- Return true for Buy signal } return false; //--- Return false if no signal } public: //--- Initialize Buy signal void ASOpenBuyLevel1() {} //--- Empty constructor };
Para definir la lógica de compra, simplemente utilizamos la misma lógica que utilizamos para la venta. A continuación, podemos añadir algunas entradas para controlar la lógica de negociación de señales.
//--- Define input group for trade and risk module settings input string trademodule = "------TRADE/RISK MODULE------";//--- Label trade/risk module inputs //--- Define input group for open Buy constants input double LotSizePercentage = 1; //--- Set lot size as percentage of account balance (default: 1%) input double OpenBuy_Const_0 = 11; //--- Set RSI threshold for Buy signal (default: 11) input double OpenBuy_Const_1 = 10; //--- Set pip offset for additional Buy orders (default: 10 pips) input double OpenSell_Const_0 = 89; //--- Set RSI threshold for Sell signal (default: 89) input double OpenSell_Const_1 = 10; //--- Set pip offset for additional Sell orders (default: 10 pips)
Una vez definidas las variables input para la generación de señales, podemos pasar a definir la lógica para la gestión de riesgos y la gestión de operaciones tras la ejecución de la señal. Para ello, necesitaremos otra interfaz y otra clase.
//--- Define interface for money management interface IMoneyManager { double GetLotSize(); //--- Retrieve lot size int GetNextLevel(Wallet* wallet); //--- Retrieve next trading level }; //--- Define class for money management class MoneyManager : public IMoneyManager { public: //--- Initialize money manager void MoneyManager(Wallet* wallet) { _minLot = MarketInfo_LibFunc(Symbol(), MODE_MINLOT); //--- Set minimum lot size _maxLot = MarketInfo_LibFunc(Symbol(), MODE_MAXLOT); //--- Set maximum lot size _lotStep = MarketInfo_LibFunc(Symbol(), MODE_LOTSTEP); //--- Set lot step } //--- Retrieve calculated lot size double GetLotSize() { double lotSize = NormalizeLots(NormalizeDouble(AccountInfoDouble(ACCOUNT_BALANCE) * 0.0001 * LotSizePercentage / 100.0, 2)); //--- Calculate lot size return lotSize; //--- Return normalized lot size } //--- Retrieve next trading level int GetNextLevel(Wallet* wallet) { return wallet.GetOpenOrders().Count() + 1; //--- Return next level based on open orders } private: double _minLot; //--- Store minimum lot size double _maxLot; //--- Store maximum lot size double _lotStep; //--- Store lot step //--- Normalize lot size to broker specifications double NormalizeLots(double lots) { lots = MathRound(lots / _lotStep) * _lotStep; //--- Round to lot step if (lots < _minLot) lots = _minLot; //--- Enforce minimum lot else if (lots > _maxLot) lots = _maxLot; //--- Enforce maximum lot return lots; //--- Return normalized lot size } };
Para configurar la gestión del capital de la estrategia, definimos la interfaz «IMoneyManager» con las funciones «GetLotSize» y «GetNextLevel», con el fin de estandarizar el tamaño de las operaciones y la progresión de niveles. En la clase «MoneyManager», que implementa la interfaz «IMoneyManager», inicializamos las variables «_minLot», «_maxLot» y «_lotStep» en el constructor de «MoneyManager» mediante la función «MarketInfo_LibFunc». Nuestra función «GetLotSize» calcula el tamaño del lote a partir del saldo de la cuenta mediante AccountInfoDouble, lo ajusta según «LotSizePercentage» y lo normaliza con «NormalizeLots» para que se ajuste a las normas del bróker.
La función «GetNextLevel» devuelve el número de órdenes abiertas obtenido con «GetOpenOrders» más uno. La función «NormalizeLots» garantiza que los tamaños de lote sean válidos redondeando a «_lotStep» y respetando los valores de «_minLot» y «_maxLot». Esto contribuirá a crear un sistema conciso de gestión del volumen y el nivel de operaciones. Ahora podemos pasar a gestionar las operaciones, y para ello necesitaremos otra interfaz.
//--- Define enumeration for trading module demands enum TradingModuleDemand { NoneDemand = 0, //--- Represent no demand NoBuyDemand = 1, //--- Prevent Buy orders NoSellDemand = 2, //--- Prevent Sell orders NoOpenDemand = 4, //--- Prevent all open orders OpenBuySellDemand = 8, //--- Demand both Buy and Sell opens OpenBuyDemand = 16, //--- Demand Buy open OpenSellDemand = 32, //--- Demand Sell open CloseBuyDemand = 64, //--- Demand Buy close CloseSellDemand = 128, //--- Demand Sell close CloseBuySellDemand = 256 //--- Demand both Buy and Sell closes }; //--- Define interface for trading module signals interface ITradingModuleSignal { string GetName(); //--- Retrieve signal name bool Evaluate(Order* openOrder = NULL); //--- Evaluate signal }; //--- Define interface for trading module values interface ITradingModuleValue { string GetName(); //--- Retrieve value name double Evaluate(Order* openOrder = NULL); //--- Evaluate value }; //--- Define interface for trade strategy modules interface ITradeStrategyModule { TradingModuleDemand Evaluate(Wallet* wallet, TradingModuleDemand demand, int level = 1); //--- Evaluate module void RegisterTradeSignal(ITradingModuleSignal* tradeSignal); //--- Register signal }; //--- Define interface for open trade strategy modules interface ITradeStrategyOpenModule : public ITradeStrategyModule { TradingModuleDemand EvaluateOpenSignals(Wallet* wallet, TradingModuleDemand demand, int requestedEvaluationLevel = 0); //--- Evaluate open signals TradingModuleDemand EvaluateCloseSignals(Wallet* wallet, TradingModuleDemand demand); //--- Evaluate close signals }; //--- Define interface for close trade strategy modules interface ITradeStrategyCloseModule : public ITradeStrategyModule { ORDER_GROUP_TYPE GetOrderGroupingType(); //--- Retrieve grouping type void RegisterTradeValue(ITradingModuleValue* tradeValue); //--- Register value };
Aquí sentamos las bases para gestionar las decisiones de negociación mediante la definición de un conjunto estructurado de enumeraciones e interfaces. Empezamos creando la enumeración «TradingModuleDemand», que utilizamos para clasificar las acciones y restricciones de negociación, como «NoneDemand» para indicar que no hay acción, «NoBuyDemand» y «NoSellDemand» para bloquear tipos de órdenes específicos, «OpenBuyDemand» y «OpenSellDemand» para iniciar operaciones, y «CloseBuyDemand» y «CloseSellDemand» para cerrar posiciones, entre otras. Esta enumeración servirá para indicar claramente la intención de la estrategia en cuanto a la ejecución de las operaciones.
A continuación, definimos la interfaz «ITradingModuleSignal», que hemos diseñado para estandarizar las operaciones relacionadas con las señales de negociación, incluyendo la función «GetName» para recuperar el identificador de una señal y la función «Evaluate» para determinar si una señal está activa, teniendo en cuenta, de forma opcional, una orden abierta. Del mismo modo, introducimos la interfaz «ITradingModuleValue», que incluye las funciones «GetName» y «Evaluate», para gestionar los valores numéricos asociados a las decisiones de negociación, como los objetivos de beneficios. Para los módulos de estrategia, creamos la interfaz «ITradeStrategyModule», que incluye la función «Evaluate» para procesar las órdenes de negociación basadas en un objeto «Wallet» y la función «RegisterTradeSignal» para incorporar señales.
Ampliamos esto con la interfaz «ITradeStrategyOpenModule», añadiendo las funciones «EvaluateOpenSignals» y «EvaluateCloseSignals» para gestionar las señales de apertura y cierre de operaciones, y con la interfaz «ITradeStrategyCloseModule», que incluye la función «GetOrderGroupingType» para la agrupación de órdenes y la función «RegisterTradeValue» para el registro de valores. En conjunto, estos componentes constituirán un marco flexible para coordinar las acciones operativas de nuestra estrategia. Ahora podemos definir clases de gestión basadas en estos módulos.
//--- Define class for managing trade strategy class TradeStrategy { public: ITradeStrategyCloseModule* CloseModules[]; //--- Store close modules private: ITradeStrategyModule* _preventOpenModules[]; //--- Store prevent-open modules ITradeStrategyOpenModule* _openModule; //--- Store open module //--- Evaluate prevent-open modules TradingModuleDemand EvaluatePreventOpenModules(Wallet* wallet, TradingModuleDemand preventOpenDemand, int evaluationLevel = 1) { TradingModuleDemand preventOpenDemands[]; //--- Declare prevent-open demands array ArrayResize(preventOpenDemands, ArraySize(_preventOpenModules), 8); //--- Resize array for (int i = 0; i < ArraySize(_preventOpenModules); i++) { //--- Iterate modules preventOpenDemands[i] = _preventOpenModules[i].Evaluate(wallet, NoneDemand, evaluationLevel); //--- Evaluate module } return PreventOpenModuleBase::GetCombinedPreventOpenDemand(preventOpenDemands); //--- Return combined demand } //--- Evaluate close modules TradingModuleDemand EvaluateCloseModules(Wallet* wallet, TradingModuleDemand closeDemand, int evaluationLevel = 1) { TradingModuleDemand closeDemands[]; //--- Declare close demands array ArrayResize(closeDemands, ArraySize(CloseModules), 8); //--- Resize array for (int i = 0; i < ArraySize(CloseModules); i++) { //--- Iterate modules closeDemands[i] = CloseModules[i].Evaluate(wallet, NoneDemand, evaluationLevel); //--- Evaluate module } return CloseModuleBase::GetCombinedCloseDemand(closeDemands); //--- Return combined demand } //--- Evaluate close conditions for TP/SL void EvaluateCloseConditions(Wallet* wallet, TradingModuleDemand signalDemand) { OrderCollection* openOrders = wallet.GetOpenOrders(); //--- Retrieve open orders if (openOrders.Count() == 0) { //--- Check if no open orders return; //--- Exit } double bid = Bid_LibFunc(); //--- Retrieve Bid price double ask = Ask_LibFunc(); //--- Retrieve Ask price for (int i = openOrders.Count() - 1; i >= 0; i--) { //--- Iterate open orders Order* order = openOrders.Get(i); //--- Get order bool closeSignal = (order.Type == OP_BUY && signalDemand == CloseBuyDemand) || //--- Check Buy close signal (order.Type == OP_SELL && signalDemand == CloseSellDemand) || //--- Check Sell close signal signalDemand == CloseBuySellDemand; //--- Check Buy/Sell close signal bool closeManualSLTP = AllowManualTPSLChanges && ((order.StopLossManual != 0 && order.Type == OP_BUY && bid <= order.StopLossManual) || //--- Check manual Buy SL (order.StopLossManual != 0 && order.Type == OP_SELL && ask >= order.StopLossManual) || //--- Check manual Sell SL (order.TakeProfitManual != 0 && order.Type == OP_BUY && bid >= order.TakeProfitManual) || //--- Check manual Buy TP (order.TakeProfitManual != 0 && order.Type == OP_SELL && ask <= order.TakeProfitManual)); //--- Check manual Sell TP bool fullOrderClose = closeSignal || closeManualSLTP; //--- Determine full close OrderCloseInfo* activePartialCloseCloseInfo = NULL; //--- Initialize partial close info if (!fullOrderClose) { //--- Check if not full close if (!AllowManualTPSLChanges || order.StopLossManual == 0) { //--- Check manual SL for (int cli = 0; cli < ArraySize(order.CloseInfosSL); cli++) { //--- Iterate SL info if (order.CloseInfosSL[cli].IsOld) continue; //--- Skip old info if (order.CloseInfosSL[cli].IsClosePriceSLHit(order.Type, ask, bid)) { //--- Check SL hit if (activePartialCloseCloseInfo == NULL || order.CloseInfosSL[cli].Percentage > activePartialCloseCloseInfo.Percentage) { //--- Check higher percentage activePartialCloseCloseInfo = order.CloseInfosSL[cli]; //--- Set active info } } } } if (!AllowManualTPSLChanges || order.TakeProfitManual == 0) { //--- Check manual TP for (int cli = 0; cli < ArraySize(order.CloseInfosTP); cli++) { //--- Iterate TP info if (order.CloseInfosTP[cli].IsOld) continue; //--- Skip old info if (order.CloseInfosTP[cli].IsClosePriceTPHit(order.Type, ask, bid)) { //--- Check TP hit if (activePartialCloseCloseInfo == NULL || order.CloseInfosTP[cli].Percentage > activePartialCloseCloseInfo.Percentage) { //--- Check higher percentage activePartialCloseCloseInfo = order.CloseInfosTP[cli]; //--- Set active info } } } } fullOrderClose = activePartialCloseCloseInfo != NULL && activePartialCloseCloseInfo.Percentage == 100; //--- Check if full close } if (fullOrderClose) { //--- Handle full close TradingModuleDemand finalPreventOpenAdvice = EvaluatePreventOpenModules(wallet, NoneDemand, 0); //--- Evaluate prevent-open TradingModuleDemand openDemand = _openModule.EvaluateOpenSignals(wallet, finalPreventOpenAdvice, 1); //--- Evaluate open signals int orderTypeOfOpeningOrder = wallet.GetOpenOrders().Get(0).Type; //--- Get first order type if ((orderTypeOfOpeningOrder == ORDER_TYPE_BUY && openDemand == OpenBuyDemand) || //--- Check Buy re-entry (orderTypeOfOpeningOrder == ORDER_TYPE_SELL && openDemand == OpenSellDemand) || //--- Check Sell re-entry (openDemand == OpenBuySellDemand)) { //--- Check Buy/Sell re-entry return; //--- Block close to prevent re-entry } wallet.SetOpenOrderToPendingClose(order); //--- Move order to pending close } else if (activePartialCloseCloseInfo != NULL) { //--- Handle partial close Order* partialCloseOrder = order.SplitOrder(activePartialCloseCloseInfo.Percentage); //--- Split order if (partialCloseOrder.Lots < 1e-13) { //--- Check if last piece delete(partialCloseOrder); //--- Delete split order wallet.SetOpenOrderToPendingClose(order); //--- Move to pending close } else { partialCloseOrder.ParentOrder = order; //--- Link to parent order if (wallet.AddPendingCloseOrder(partialCloseOrder)) { //--- Add to pending close activePartialCloseCloseInfo.IsOld = true; //--- Mark info as old } } } } } public: //--- Initialize trade strategy void TradeStrategy(ITradeStrategyOpenModule* openModule) { _openModule = openModule; //--- Set open module } //--- Destructor to clean up strategy void ~TradeStrategy() { for (int i = ArraySize(_preventOpenModules) - 1; i >= 0; i--) { //--- Iterate prevent-open modules delete(_preventOpenModules[i]); //--- Delete module } delete(_openModule); //--- Delete open module for (int i = ArraySize(CloseModules) - 1; i >= 0; i--) { //--- Iterate close modules delete(CloseModules[i]); //--- Delete module } } //--- Evaluate trading strategy void Evaluate(Wallet* wallet) { int orderCount = wallet.GetOpenOrders().Count(); //--- Retrieve open order count TradingModuleDemand finalPreventOpenAdvice = EvaluatePreventOpenModules(wallet, NoneDemand, orderCount + 1); //--- Evaluate prevent-open if (orderCount > 0) { //--- Check if orders exist EvaluateCloseModules(wallet, NoneDemand); //--- Evaluate close modules TradingModuleDemand signalDemand = _openModule.EvaluateCloseSignals(wallet, finalPreventOpenAdvice); //--- Evaluate close signals EvaluateCloseConditions(wallet, signalDemand); //--- Evaluate close conditions } _openModule.Evaluate(wallet, finalPreventOpenAdvice, 0); //--- Evaluate open module } //--- Register prevent-open module void RegisterPreventOpenModule(ITradeStrategyModule* preventOpenModule) { int size = ArraySize(_preventOpenModules); //--- Get current array size ArrayResize(_preventOpenModules, size + 1, 8); //--- Resize array _preventOpenModules[size] = preventOpenModule; //--- Add module } //--- Register close module void RegisterCloseModule(ITradeStrategyCloseModule* closeModule) { int size = ArraySize(CloseModules); //--- Get current array size ArrayResize(CloseModules, size + 1, 8); //--- Resize array CloseModules[size] = closeModule; //--- Add module } }; //--- Define base class for module calculations class ModuleCalculationsBase { public: //--- Calculate profit for order collection static double CalculateOrderCollectionProfit(OrderCollection &orders, ORDER_PROFIT_CALCULATION_TYPE calculationType) { double collectionProfit = 0; //--- Initialize profit for (int i = 0; i < orders.Count(); i++) { //--- Iterate orders Order* order = orders.Get(i); //--- Get order collectionProfit += CalculateOrderProfit(order, calculationType); //--- Add order profit } return collectionProfit; //--- Return total profit } //--- Calculate profit for single order static double CalculateOrderProfit(Order* order, ORDER_PROFIT_CALCULATION_TYPE calculationType) { if (calculationType == Pips) { //--- Check pips calculation return order.CalculateProfitPips(); //--- Return profit in pips } else if (calculationType == Money) { //--- Check money calculation return order.CalculateProfitCurrency(); //--- Return profit in currency } else if (calculationType == EquityPercentage) { //--- Check equity percentage return order.CalculateProfitEquityPercentage(); //--- Return profit as percentage } else { Alert("Can't execute CalculateOrderCollectionProfit. Unknown calculationType: " + IntegerToString(calculationType)); //--- Log error return 0; //--- Return 0 for invalid type } } }; //--- Define base class for open trade modules class OpenModuleBase : public ITradeStrategyOpenModule { protected: AdvisorStrategy* _advisorStrategy; //--- Store advisor strategy IMoneyManager* _moneyManager; //--- Store money manager //--- Create new order Order* OpenOrder(ENUM_ORDER_TYPE orderType, bool mustBeVisibleOnChart) { Order* order = new Order(mustBeVisibleOnChart); //--- Create new order order.SymbolCode = Symbol(); //--- Set symbol order.Type = orderType; //--- Set order type order.MagicNumber = MagicNumber; //--- Set magic number order.Lots = _moneyManager.GetLotSize(); //--- Set lot size if (order.Type == ORDER_TYPE_BUY) { //--- Check Buy order order.OpenPrice = Ask_LibFunc(); //--- Set open price to Ask order.StopLoss = -DBL_MAX; //--- Set initial SL to minimum order.TakeProfit = DBL_MAX; //--- Set initial TP to maximum } else if (order.Type == ORDER_TYPE_SELL) { //--- Check Sell order order.OpenPrice = Bid_LibFunc(); //--- Set open price to Bid order.StopLoss = DBL_MAX; //--- Set initial SL to maximum order.TakeProfit = -DBL_MAX; //--- Set initial TP to minimum } order.LowestProfitPips = DBL_MAX; //--- Set initial lowest profit order.HighestProfitPips = -DBL_MAX; //--- Set initial highest profit order.Comment = OrderComment; //--- Set order comment OrderRepository::CalculateAndSetCommision(order); //--- Calculate and set commission return order; //--- Return order } public: //--- Initialize open module void OpenModuleBase(AdvisorStrategy* advisorStrategy, IMoneyManager* moneyManager) { _advisorStrategy = advisorStrategy; //--- Set advisor strategy _moneyManager = moneyManager; //--- Set money manager } //--- Retrieve trade actions void GetTradeActions(Wallet* wallet, TradingModuleDemand preventOpenDemand, TradeAction& result[]) { TradeAction tempresult[]; //--- Declare temporary actions array if (wallet.GetOpenOrders().Count() > 0) { //--- Check if open orders exist Order* firstOrder = wallet.GetOpenOrders().Get(0); //--- Get first open order if (firstOrder.Type == ORDER_TYPE_BUY) { //--- Check if Buy order ArrayResize(tempresult, ArraySize(tempresult) + 1, 8); //--- Resize array tempresult[0] = OpenBuyAction; //--- Add open Buy action ArrayResize(tempresult, ArraySize(tempresult) + 1, 8); //--- Resize array tempresult[1] = CloseBuyAction; //--- Add close Buy action } else if (firstOrder.Type == ORDER_TYPE_SELL) { //--- Check if Sell order ArrayResize(tempresult, ArraySize(tempresult) + 1, 8); //--- Resize array tempresult[0] = OpenSellAction; //--- Add open Sell action ArrayResize(tempresult, ArraySize(tempresult) + 1, 8); //--- Resize array tempresult[1] = CloseSellAction; //--- Add close Sell action } else { Alert("Unsupported ordertype. Ordertype: " + DoubleToStr(firstOrder.Type)); //--- Log error } } else { ArrayResize(tempresult, ArraySize(tempresult) + 1, 8); //--- Resize array tempresult[0] = OpenBuyAction; //--- Add open Buy action ArrayResize(tempresult, ArraySize(tempresult) + 1, 8); //--- Resize array tempresult[1] = OpenSellAction; //--- Add open Sell action } for (int i = 0; i < ArraySize(tempresult); i++) { //--- Iterate actions if ((preventOpenDemand == NoOpenDemand && (tempresult[i] == OpenBuyAction || tempresult[i] == OpenSellAction)) || //--- Check no open demand (preventOpenDemand == NoBuyDemand && tempresult[i] == OpenBuyAction) || //--- Check no Buy demand (preventOpenDemand == NoSellDemand && tempresult[i] == OpenSellAction)) { //--- Check no Sell demand continue; //--- Skip action } ArrayResize(result, ArraySize(result) + 1, 8); //--- Resize result array result[ArraySize(result) - 1] = tempresult[i]; //--- Add action } } //--- Register trade signal (empty implementation) virtual void RegisterTradeSignal(ITradingModuleSignal* tradeSignal) {} //--- Do nothing //--- Combine open demands static TradingModuleDemand GetCombinedOpenDemand(TradingModuleDemand &openDemands[]) { TradingModuleDemand result = NoneDemand; //--- Initialize result for (int i = 0; i < ArraySize(openDemands); i++) { //--- Iterate demands if (result == OpenBuySellDemand) { //--- Check if Buy/Sell demand return OpenBuySellDemand; //--- Return Buy/Sell demand } if (openDemands[i] == OpenBuySellDemand) { //--- Check if demand is Buy/Sell result = OpenBuySellDemand; //--- Set Buy/Sell demand } else if (result == NoneDemand && openDemands[i] == OpenBuyDemand) { //--- Check Buy demand result = OpenBuyDemand; //--- Set Buy demand } else if (result == NoneDemand && openDemands[i] == OpenSellDemand) { //--- Check Sell demand result = OpenSellDemand; //--- Set Sell demand } else if (result == OpenBuyDemand && openDemands[i] == OpenSellDemand) { //--- Check mixed demands result = OpenBuySellDemand; //--- Set Buy/Sell demand } else if (result == OpenSellDemand && openDemands[i] == OpenBuyDemand) { //--- Check mixed demands result = OpenBuySellDemand; //--- Set Buy/Sell demand } } return result; //--- Return combined demand } //--- Combine close demands static TradingModuleDemand GetCombinedCloseDemand(TradingModuleDemand &closeDemands[]) { TradingModuleDemand result = NoneDemand; //--- Initialize result for (int i = 0; i < ArraySize(closeDemands); i++) { //--- Iterate demands if (result == CloseBuySellDemand) { //--- Check if Buy/Sell demand return CloseBuySellDemand; //--- Return Buy/Sell demand } if (closeDemands[i] == CloseBuySellDemand) { //--- Check if demand is Buy/Sell result = CloseBuySellDemand; //--- Set Buy/Sell demand } else if (result == NoneDemand && closeDemands[i] == CloseBuyDemand) { //--- Check Buy demand result = CloseBuyDemand; //--- Set Buy demand } else if (result == NoneDemand && closeDemands[i] == CloseSellDemand) { //--- Check Sell demand result = CloseSellDemand; //--- Set Sell demand } else if (result == CloseBuyDemand && closeDemands[i] == CloseSellDemand) { //--- Check mixed demands result = CloseBuySellDemand; //--- Set Buy/Sell demand } else if (result == CloseSellDemand && closeDemands[i] == CloseBuyDemand) { //--- Check mixed demands result = CloseBuySellDemand; //--- Set Buy/Sell demand } } return result; //--- Return combined demand } //--- Retrieve number of open orders int GetNumberOfOpenOrders(Wallet* wallet) { return wallet.GetOpenOrders().Count(); //--- Return open order count } };
Creamos el marco de la estrategia de negociación utilizando la clase «TradeStrategy», almacenando los objetos «ITradeStrategyCloseModule» en «CloseModules», los objetos «ITradeStrategyModule» en «_preventOpenModules» y un objeto «ITradeStrategyOpenModule» en «_openModule». Inicializamos «_openModule» en el constructor de «TradeStrategy» y liberamos los módulos en el destructor. Nuestra función «Evaluate» analiza las órdenes abiertas mediante «GetOpenOrders», evalúa «_preventOpenModules» con «EvaluatePreventOpenModules» y «GetCombinedPreventOpenDemand», y procesa «CloseModules» con «EvaluateCloseModules» y «GetCombinedCloseDemand».
La función «EvaluateCloseConditions» comprueba si los tipos de orden coinciden con «CloseBuyDemand» o «CloseSellDemand», verifica los stop-loss y take-profit manuales mediante «Ask_LibFunc» y «Bid_LibFunc», y gestiona los cierres con «SetOpenOrderToPendingClose» o «SplitOrder». Registramos los módulos mediante «RegisterPreventOpenModule» y «RegisterCloseModule». La clase «ModuleCalculationsBase» calcula los beneficios mediante los métodos «CalculateOrderCollectionProfit» y «CalculateOrderProfit», mientras que «OpenModuleBase» crea órdenes con «OpenOrder» y determina las acciones con «GetTradeActions», fusionando las demandas mediante «GetCombinedOpenDemand» y «GetCombinedCloseDemand» para lograr un sistema coherente.
Ya estamos listos para realizar operaciones y, para asegurarnos de no operar en exceso, podemos utilizar una variable externa que controle el número máximo de operaciones por instancia.
//--- Define input for maximum open orders input int MaxNumberOfOpenOrders1 = 1; //--- Set maximum number of open orders (default: 1)
Tras establecer la variable de restricción del número de operaciones, ya podemos definir una clase para abrir varios módulos.
//--- Define class for multiple open module class MultipleOpenModule_1 : public OpenModuleBase { protected: TradingModuleDemand previousSignalDemand; //--- Store previous signal demand public: //--- Initialize multiple open module void MultipleOpenModule_1(AdvisorStrategy* advisorStrategy, MoneyManager* moneyManager) : OpenModuleBase(advisorStrategy, moneyManager) { _advisorStrategy.SetFireOnlyWhenReset(true); //--- Configure signals to fire only when reset } //--- Evaluate and act on signals TradingModuleDemand Evaluate(Wallet* wallet, TradingModuleDemand preventOpenDemand, int level) { TradingModuleDemand newSignalsDemand = EvaluateSignals(wallet, preventOpenDemand, level); //--- Evaluate signals if (newSignalsDemand != NoneDemand) { //--- Check if demand exists EvaluateOpenConditions(wallet, newSignalsDemand); //--- Evaluate open conditions } return newSignalsDemand; //--- Return new signal demand } //--- Evaluate open signals without acting TradingModuleDemand EvaluateOpenSignals(Wallet* wallet, TradingModuleDemand preventOpenDemand, int requestedEvaluationLevel) { TradingModuleDemand openDemands[]; //--- Declare open demands array TradeAction tradeActionsToEvaluate[]; //--- Declare actions to evaluate GetTradeActions(wallet, preventOpenDemand, tradeActionsToEvaluate); //--- Retrieve actions AddPreviousDemandTradeActionIfMissing(tradeActionsToEvaluate); //--- Add previous demand actions int level; //--- Declare level if (requestedEvaluationLevel == 0) { //--- Check if level unspecified level = _moneyManager.GetNextLevel(wallet); //--- Set level based on orders } else { level = requestedEvaluationLevel; //--- Use specified level } for (int i = 0; i < ArraySize(tradeActionsToEvaluate); i++) { //--- Iterate actions if (tradeActionsToEvaluate[i] == CloseBuyAction || tradeActionsToEvaluate[i] == CloseSellAction) { //--- Skip close actions continue; //--- Move to next } if (requestedEvaluationLevel == 0) { //--- Check if level unspecified level = GetTopLevel(tradeActionsToEvaluate[i], level); //--- Cap level if (wallet.GetOpenOrders().Count() >= MaxNumberOfOpenOrders1) { //--- Check order limit level += 1; //--- Increment level } } if (_advisorStrategy.GetAdvice(tradeActionsToEvaluate[i], level)) { //--- Check if action advised if (tradeActionsToEvaluate[i] == OpenBuyAction) { //--- Check Buy action int size = ArraySize(openDemands); //--- Get current size int newSize = size + 1; //--- Calculate new size ArrayResize(openDemands, newSize, 8); //--- Resize array openDemands[newSize - 1] = OpenBuyDemand; //--- Add Buy demand } else if (tradeActionsToEvaluate[i] == OpenSellAction) { //--- Check Sell action int size = ArraySize(openDemands); //--- Get current size int newSize = size + 1; //--- Calculate new size ArrayResize(openDemands, newSize, 8); //--- Resize array openDemands[newSize - 1] = OpenSellDemand; //--- Add Sell demand } } } TradingModuleDemand combinedOpenSignalDemand = OpenModuleBase::GetCombinedOpenDemand(openDemands); //--- Combine open demands TradingModuleDemand multiOrderOpenSignal = GetOpenDemandBasedOnPreviousOpenDemand(combinedOpenSignalDemand, level - 1); //--- Adjust for previous demand multiOrderOpenSignal = FilterPreventOpenDemand(multiOrderOpenSignal, preventOpenDemand); //--- Filter prevent-open demands return multiOrderOpenSignal; //--- Return final open signal } //--- Retrieve trade actions (custom for multiple orders) void GetTradeActions(Wallet* wallet, TradingModuleDemand preventOpenDemand, TradeAction& result[]) { if (wallet.GetOpenOrders().Count() > 0) { //--- Check if open orders exist Order* firstOrder = wallet.GetOpenOrders().Get(0); //--- Get first open order if (firstOrder.Type == ORDER_TYPE_BUY) { //--- Check if Buy order ArrayResize(result, ArraySize(result) + 1, 8); //--- Resize array result[0] = OpenBuyAction; //--- Add open Buy action ArrayResize(result, ArraySize(result) + 1, 8); //--- Resize array result[1] = CloseBuyAction; //--- Add close Buy action } else if (firstOrder.Type == ORDER_TYPE_SELL) { //--- Check if Sell order ArrayResize(result, ArraySize(result) + 1, 8); //--- Resize array result[0] = OpenSellAction; //--- Add open Sell action ArrayResize(result, ArraySize(result) + 1, 8); //--- Resize array result[1] = CloseSellAction; //--- Add close Sell action } else { Alert("Unsupported ordertype"); //--- Log error } } else { ArrayResize(result, ArraySize(result) + 1, 8); //--- Resize array result[0] = OpenBuyAction; //--- Add open Buy action ArrayResize(result, ArraySize(result) + 1, 8); //--- Resize array result[1] = OpenSellAction; //--- Add open Sell action } } //--- Evaluate close signals TradingModuleDemand EvaluateCloseSignals(Wallet* wallet, TradingModuleDemand preventOpenDemand) { TradingModuleDemand closeDemands[]; //--- Declare close demands array TradeAction tradeActionsToEvaluate[]; //--- Declare actions to evaluate GetTradeActions(wallet, preventOpenDemand, tradeActionsToEvaluate); //--- Retrieve actions for (int i = 0; i < ArraySize(tradeActionsToEvaluate); i++) { //--- Iterate actions if (tradeActionsToEvaluate[i] != CloseBuyAction && tradeActionsToEvaluate[i] != CloseSellAction) { //--- Skip non-close actions continue; //--- Move to next } if (_advisorStrategy.GetAdvice(tradeActionsToEvaluate[i], 1)) { //--- Check if action advised (level 1) if (tradeActionsToEvaluate[i] == CloseBuyAction) { //--- Check Buy close int size = ArraySize(closeDemands); //--- Get current size int newSize = size + 1; //--- Calculate new size ArrayResize(closeDemands, newSize, 8); //--- Resize array closeDemands[newSize - 1] = CloseBuyDemand; //--- Add Buy close demand } else if (tradeActionsToEvaluate[i] == CloseSellAction) { //--- Check Sell close int size = ArraySize(closeDemands); //--- Get current size int newSize = size + 1; //--- Calculate new size ArrayResize(closeDemands, newSize, 8); //--- Resize array closeDemands[newSize - 1] = CloseSellDemand; //--- Add Sell close demand } } } TradingModuleDemand combinedCloseSignalDemand = OpenModuleBase::GetCombinedCloseDemand(closeDemands); //--- Combine close demands return combinedCloseSignalDemand; //--- Return combined demand } private: //--- Evaluate open and close signals TradingModuleDemand EvaluateSignals(Wallet* wallet, TradingModuleDemand preventOpenDemand, int requestedEvaluationLevel) { TradingModuleDemand openDemands[]; //--- Declare open demands array TradingModuleDemand closeDemands[]; //--- Declare close demands array TradeAction tradeActionsToEvaluate[]; //--- Declare actions to evaluate GetTradeActions(wallet, preventOpenDemand, tradeActionsToEvaluate); //--- Retrieve actions AddPreviousDemandTradeActionIfMissing(tradeActionsToEvaluate); //--- Add previous demand actions int moneyManagementLevel; //--- Declare level if (requestedEvaluationLevel == 0) { //--- Check if level unspecified moneyManagementLevel = _moneyManager.GetNextLevel(wallet); //--- Set level based on orders } else { moneyManagementLevel = requestedEvaluationLevel; //--- Use specified level } for (int i = 0; i < ArraySize(tradeActionsToEvaluate); i++) { //--- Iterate actions int tradeActionEvaluationLevel = GetTopLevel(tradeActionsToEvaluate[i], moneyManagementLevel); //--- Cap level if (wallet.GetOpenOrders().Count() >= MaxNumberOfOpenOrders1) { //--- Check order limit tradeActionEvaluationLevel += 1; //--- Increment level } if (_advisorStrategy.GetAdvice(tradeActionsToEvaluate[i], tradeActionEvaluationLevel)) { //--- Check if action advised if (tradeActionsToEvaluate[i] == OpenBuyAction) { //--- Check Buy open int size = ArraySize(openDemands); //--- Get current size int newSize = size + 1; //--- Calculate new size ArrayResize(openDemands, newSize, 8); //--- Resize array openDemands[newSize - 1] = OpenBuyDemand; //--- Add Buy demand } else if (tradeActionsToEvaluate[i] == OpenSellAction) { //--- Check Sell open int size = ArraySize(openDemands); //--- Get current size int newSize = size + 1; //--- Calculate new size ArrayResize(openDemands, newSize, 8); //--- Resize array openDemands[newSize - 1] = OpenSellDemand; //--- Add Sell demand } else if (tradeActionsToEvaluate[i] == CloseBuyAction) { //--- Check Buy close int size = ArraySize(closeDemands); //--- Get current size int newSize = size + 1; //--- Calculate new size ArrayResize(closeDemands, newSize, 8); //--- Resize array closeDemands[newSize - 1] = CloseBuyDemand; //--- Add Buy close demand } else if (tradeActionsToEvaluate[i] == CloseSellAction) { //--- Check Sell close int size = ArraySize(closeDemands); //--- Get current size int newSize = size + 1; //--- Calculate new size ArrayResize(closeDemands, newSize, 8); //--- Resize array closeDemands[newSize - 1] = CloseSellDemand; //--- Add Sell close demand } } } TradingModuleDemand combinedCloseSignalDemand = OpenModuleBase::GetCombinedCloseDemand(closeDemands); //--- Combine close demands if (combinedCloseSignalDemand != NoneDemand) { //--- Check if close demand exists return combinedCloseSignalDemand; //--- Return close demand } TradingModuleDemand combinedOpenSignalDemand = OpenModuleBase::GetCombinedOpenDemand(openDemands); //--- Combine open demands TradingModuleDemand multiOrderOpenSignal = GetOpenDemandBasedOnPreviousOpenDemand(combinedOpenSignalDemand, GetNumberOfOpenOrders(wallet)); //--- Adjust for previous demand previousSignalDemand = combinedOpenSignalDemand; //--- Update previous demand multiOrderOpenSignal = FilterPreventOpenDemand(multiOrderOpenSignal, preventOpenDemand); //--- Filter prevent-open demands return multiOrderOpenSignal; //--- Return final signal } //--- Evaluate open conditions and add orders void EvaluateOpenConditions(Wallet* wallet, TradingModuleDemand signalDemand) { if (signalDemand == OpenBuySellDemand) { //--- Check Buy/Sell demand return; //--- Exit (hedging not supported) } else { double currentFreeMargin = AccountFreeMargin_LibFunc(); //--- Retrieve free margin double requiredMargin; //--- Declare required margin if (signalDemand == OpenBuyDemand) { //--- Check Buy demand if (!MarginRequired(ORDER_TYPE_BUY, _moneyManager.GetLotSize(), requiredMargin)) { //--- Check margin return; //--- Exit if margin check fails } if (currentFreeMargin < requiredMargin) { //--- Check sufficient margin HandleErrors("Not enough free margin to open buy order with requested volume."); //--- Log error return; //--- Exit } wallet.GetPendingOpenOrders().Add(OpenOrder(ORDER_TYPE_BUY, false)); //--- Add Buy order } else if (signalDemand == OpenSellDemand) { //--- Check Sell demand if (!MarginRequired(ORDER_TYPE_SELL, _moneyManager.GetLotSize(), requiredMargin)) { //--- Check margin return; //--- Exit if margin check fails } if (currentFreeMargin < requiredMargin) { //--- Check sufficient margin HandleErrors("Not enough free margin to open sell order with requested volume."); //--- Log error return; //--- Exit } wallet.GetPendingOpenOrders().Add(OpenOrder(ORDER_TYPE_SELL, false)); //--- Add Sell order } } } //--- Adjust open demand based on previous demand TradingModuleDemand GetOpenDemandBasedOnPreviousOpenDemand(TradingModuleDemand openDemand, int numberOfOpenOrders) { if (numberOfOpenOrders == 0 || previousSignalDemand == NoneDemand) { //--- Check no orders or no previous demand return openDemand; //--- Return current demand } if (previousSignalDemand == OpenBuyDemand && openDemand == OpenSellDemand) { //--- Check Buy to Sell switch return openDemand; //--- Allow Sell demand } else if (previousSignalDemand == OpenSellDemand && openDemand == OpenBuyDemand) { //--- Check Sell to Buy switch return openDemand; //--- Allow Buy demand } return NoneDemand; //--- Block same-direction or mixed demands } private: //--- Cap evaluation level int GetTopLevel(TradeAction tradeAction, int level) { int numberOfExpressions = _advisorStrategy.GetNumberOfExpressions(tradeAction); //--- Retrieve expression count if (level > numberOfExpressions) { //--- Check if level exceeds expressions level = numberOfExpressions; //--- Cap level } return level; //--- Return capped level } //--- Add previous demand action if missing void AddPreviousDemandTradeActionIfMissing(TradeAction& result[]) { if (previousSignalDemand == NoneDemand) { //--- Check if no previous demand return; //--- Exit } bool foundPreviousDemand = false; //--- Initialize found flag if (previousSignalDemand == OpenBuyDemand) { //--- Check Buy demand AddPreviousDemandTradeAction(result, OpenBuyDemand, OpenBuyAction); //--- Add Buy action } else if (previousSignalDemand == OpenSellDemand) { //--- Check Sell demand AddPreviousDemandTradeAction(result, OpenSellDemand, OpenSellAction); //--- Add Sell action } else if (previousSignalDemand == OpenBuySellDemand) { //--- Check Buy/Sell demand AddPreviousDemandTradeAction(result, OpenBuyDemand, OpenBuyAction); //--- Add Buy action AddPreviousDemandTradeAction(result, OpenSellDemand, OpenSellAction); //--- Add Sell action } } //--- Add specific previous demand action void AddPreviousDemandTradeAction(TradeAction& result[], TradingModuleDemand demand, TradeAction action) { bool foundPreviousDemand = false; //--- Initialize found flag if (previousSignalDemand == demand) { //--- Check matching demand for (int i = 0; i < ArraySize(result); i++) { //--- Iterate actions if (action == result[i]) { //--- Check if action exists foundPreviousDemand = true; //--- Set found flag } } if (!foundPreviousDemand) { //--- Check if action missing ArrayResize(result, ArraySize(result) + 1, 8); //--- Resize array result[ArraySize(result) - 1] = action; //--- Add action } } } //--- Filter prevent-open demands TradingModuleDemand FilterPreventOpenDemand(TradingModuleDemand multiOrderOpendDemand, TradingModuleDemand preventOpenDemand) { if (multiOrderOpendDemand == NoneDemand) { //--- Check no demand return multiOrderOpendDemand; //--- Return no demand } else if (multiOrderOpendDemand == OpenBuyDemand && (preventOpenDemand == NoBuyDemand || preventOpenDemand == NoOpenDemand)) { //--- Check blocked Buy return NoneDemand; //--- Block Buy demand } else if (multiOrderOpendDemand == OpenSellDemand && (preventOpenDemand == NoSellDemand || preventOpenDemand == NoOpenDemand)) { //--- Check blocked Sell return NoneDemand; //--- Block Sell demand } else if (multiOrderOpendDemand == OpenBuySellDemand) { //--- Check Buy/Sell demand if (preventOpenDemand == NoBuyDemand) { //--- Check no Buy return OpenSellDemand; //--- Allow Sell demand } else if (preventOpenDemand == NoSellDemand) { //--- Check no Sell return OpenBuyDemand; //--- Allow Buy demand } else if (preventOpenDemand == NoOpenDemand) { //--- Check no open return NoneDemand; //--- Block all demands } } return multiOrderOpendDemand; //--- Return unfiltered demand } };
En este apartado, desarrollamos un módulo para gestionar múltiples posiciones abiertas utilizando la clase «MultipleOpenModule_1», derivada de «OpenModuleBase». Lo inicializamos con el constructor «MultipleOpenModule_1», configurando «_advisorStrategy» para que active las señales únicamente tras un reinicio mediante «SetFireOnlyWhenReset» y almacenando las demandas anteriores en «previousSignalDemand». Nuestra función «Evaluate» evalúa las señales mediante «EvaluateSignals» y actúa sobre las demandas válidas utilizando «EvaluateOpenConditions», devolviendo la demanda.
En «EvaluateOpenSignals», recuperamos las acciones con «GetTradeActions», añadimos las demandas anteriores mediante «AddPreviousDemandTradeActionIfMissing» y establecemos el nivel utilizando «GetNextLevel» de «_moneyManager» o un valor especificado. Iteramos sobre las acciones, omitiendo «CloseBuyAction» y «CloseSellAction», limitamos los niveles con «GetTopLevel» y comprobamos los límites de las órdenes con «GetOpenOrders» y «MaxNumberOfOpenOrders1». Las señales válidas procedentes del método «GetAdvice» de «_advisorStrategy» añaden «OpenBuyDemand» u «OpenSellDemand» a un array, se combinan con «GetCombinedOpenDemand», se ajustan mediante «GetOpenDemandBasedOnPreviousOpenDemand» y se filtran mediante «FilterPreventOpenDemand».
La función «EvaluateCloseSignals» evalúa de forma similar «CloseBuyAction» y «CloseSellAction», añadiendo «CloseBuyDemand» o «CloseSellDemand» y combinándolas con «GetCombinedCloseDemand».
En «EvaluateOpenConditions», verificamos el margen con «AccountFreeMargin_LibFunc» y «MarginRequired», y, si es suficiente, añadimos órdenes de compra o venta mediante «OpenOrder»; de lo contrario, registramos los errores con «HandleErrors». Utilizamos una lógica similar para cerrar las órdenes abiertas, establecer el «take profit» y el «stop loss», así como de las variables de entrada correspondientes. Tras la ejecución, ahora disponemos de los siguientes datos de entrada.

Ahora podemos crear una clase final que se encargue de toda la ejecución y la gestión ya implementadas. Para lograrlo, utilizamos la siguiente lógica.
//--- Define interface for trader interface ITrader { void HandleTick(); //--- Handle tick event void Init(); //--- Initialize trader Wallet* GetWallet(); //--- Retrieve wallet }; //--- Declare global trader pointer ITrader *_ea; //--- Store EA instance //--- Define main Expert Advisor class class EA : public ITrader { private: bool _firstTick; //--- Track first tick TradeStrategy* _tradeStrategy; //--- Store trade strategy AdvisorStrategy* _advisorStrategy; //--- Store advisor strategy IMoneyManager* _moneyManager; //--- Store money manager Wallet* _wallet; //--- Store wallet public: //--- Initialize EA void EA() { _firstTick = true; //--- Set first tick flag _wallet = new Wallet(); //--- Create wallet _wallet.SetLastClosedOrdersByTimeframe(DisplayOrderDuringTimeframe); //--- Set closed orders timeframe _advisorStrategy = new AdvisorStrategy(); //--- Create advisor strategy _advisorStrategy.RegisterOpenBuy(new ASOpenBuyLevel1(), 1); //--- Register Buy signal _advisorStrategy.RegisterOpenSell(new ASOpenSellLevel1(), 1); //--- Register Sell signal _moneyManager = new MoneyManager(_wallet); //--- Create money manager _tradeStrategy = new TradeStrategy(new MultipleOpenModule_1(_advisorStrategy, _moneyManager)); //--- Create trade strategy _tradeStrategy.RegisterCloseModule(new TakeProfitCloseModule_1()); //--- Register TP module _tradeStrategy.RegisterCloseModule(new StopLossCloseModule_1()); //--- Register SL module } //--- Destructor to clean up EA void ~EA() { delete(_tradeStrategy); //--- Delete trade strategy delete(_moneyManager); //--- Delete money manager delete(_advisorStrategy); //--- Delete advisor strategy delete(_wallet); //--- Delete wallet } //--- Initialize EA components void Init() { IsDemoLiveOrVisualMode = !MQLInfoInteger(MQL_TESTER) || MQLInfoInteger(MQL_VISUAL_MODE); //--- Set mode flag UnitsOneLot = MarketInfo_LibFunc(Symbol(), MODE_LOTSIZE); //--- Set lot size SetOrderGrouping(); //--- Configure order grouping _wallet.LoadOrdersFromBroker(); //--- Load orders from broker } //--- Handle tick event void HandleTick() { if (MQLInfoInteger(MQL_TESTER) == 0) { //--- Check if not in tester SyncOrders(); //--- Synchronize orders } if (AllowManualTPSLChanges) { //--- Check if manual TP/SL allowed SyncManualTPSLChanges(); //--- Synchronize manual TP/SL } AskFunc.Evaluate(); //--- Update Ask price BidFunc.Evaluate(); //--- Update Bid price UpdateOrders(); //--- Update order profits if (!StopEA) { //--- Check if EA not stopped _wallet.HandleTick(); //--- Handle wallet tick _advisorStrategy.HandleTick(); //--- Handle strategy tick if (_wallet.GetPendingOpenOrders().Count() == 0 && _wallet.GetPendingCloseOrders().Count() == 0) { //--- Check no pending orders _tradeStrategy.Evaluate(_wallet); //--- Evaluate strategy } if (ExecutePendingCloseOrders()) { //--- Execute close orders if (!ExecutePendingOpenOrders()) { //--- Execute open orders HandleErrors(StringFormat("Open (all) order(s) failed. Please check EA %d and look at the Journal and Expert tab.", MagicNumber)); //--- Log error } } else { HandleErrors(StringFormat("Close (all) order(s) failed! Please check EA %d and look at the Journal and Expert tab.", MagicNumber)); //--- Log error } } else { if (ExecutePendingCloseOrders()) { //--- Execute close orders _wallet.SetAllOpenOrdersToPendingClose(); //--- Move open orders to pending close } else { HandleErrors(StringFormat("Close (all) order(s) failed! Please check EA %d and look at the Journal and Expert tab.", MagicNumber)); //--- Log error } } if (_firstTick) { //--- Check if first tick _firstTick = false; //--- Clear first tick flag } } //--- Retrieve wallet Wallet* GetWallet() { return _wallet; //--- Return wallet } private: //--- Configure order grouping void SetOrderGrouping() { int size = ArraySize(_tradeStrategy.CloseModules); //--- Get close modules size ORDER_GROUP_TYPE groups[]; //--- Declare groups array ArrayResize(groups, size); //--- Resize array for (int i = 0; i < ArraySize(_tradeStrategy.CloseModules); i++) { //--- Iterate modules groups[i] = _tradeStrategy.CloseModules[i].GetOrderGroupingType(); //--- Set grouping type } _wallet.ActivateOrderGroups(groups); //--- Activate groups } //--- Synchronize orders with broker void SyncOrders() { OrderCollection* currentOpenOrders = OrderRepository::GetOpenOrders(MagicNumber, NULL, Symbol()); //--- Retrieve open orders if (currentOpenOrders.Count() != (_wallet.GetOpenOrders().Count() + _wallet.GetPendingCloseOrders().Count())) { //--- Check order mismatch Print("(Manual) orderchanges detected" + " (found in MT: " + IntegerToString(currentOpenOrders.Count()) + " and in wallet: " + IntegerToString(_wallet.GetOpenOrders().Count()) + "), resetting EA, loading open orders."); //--- Log mismatch _wallet.ResetOpenOrders(); //--- Reset open orders _wallet.ResetPendingOrders(); //--- Reset pending orders _wallet.LoadOrdersFromBroker(); //--- Reload orders } delete(currentOpenOrders); //--- Delete orders collection } //--- Synchronize manual TP/SL changes void SyncManualTPSLChanges() { _wallet.GetOpenOrders().Rewind(); //--- Reset orders iterator while (_wallet.GetOpenOrders().HasNext()) { //--- Iterate orders Order* order = _wallet.GetOpenOrders().Next(); //--- Get order uint lineFindResult = ObjectFind(ChartID(), IntegerToString(order.Ticket) + "_SL"); //--- Find SL line if (lineFindResult != UINT_MAX) { //--- Check if SL line exists double currentPosition = ObjectGetDouble(ChartID(), IntegerToString(order.Ticket) + "_SL", OBJPROP_PRICE); //--- Get SL position if ((order.StopLossManual == 0 && currentPosition != order.GetClosestSL()) || //--- Check manual SL change (order.StopLossManual != 0 && currentPosition != order.StopLossManual)) { //--- Check manual SL mismatch order.StopLossManual = currentPosition; //--- Update manual SL } } lineFindResult = ObjectFind(ChartID(), IntegerToString(order.Ticket) + "_TP"); //--- Find TP line if (lineFindResult != UINT_MAX) { //--- Check if TP line exists double currentPosition = ObjectGetDouble(ChartID(), IntegerToString(order.Ticket) + "_TP", OBJPROP_PRICE); //--- Get TP position if ((order.TakeProfitManual == 0 && currentPosition != order.GetClosestTP()) || //--- Check manual TP change (order.TakeProfitManual != 0 && currentPosition != order.TakeProfitManual)) { //--- Check manual TP mismatch order.TakeProfitManual = currentPosition; //--- Update manual TP } } } } //--- Update order profits void UpdateOrders() { _wallet.GetOpenOrders().Rewind(); //--- Reset orders iterator while (_wallet.GetOpenOrders().HasNext()) { //--- Iterate orders Order* order = _wallet.GetOpenOrders().Next(); //--- Get order double pipsProfit = order.CalculateProfitPips(); //--- Calculate profit order.CurrentProfitPips = pipsProfit; //--- Update current profit if (pipsProfit < order.LowestProfitPips) { //--- Check if lowest profit order.LowestProfitPips = pipsProfit; //--- Update lowest profit } else if (pipsProfit > order.HighestProfitPips) { //--- Check if highest profit order.HighestProfitPips = pipsProfit; //--- Update highest profit } } } //--- Execute pending close orders bool ExecutePendingCloseOrders() { OrderCollection* pendingCloseOrders = _wallet.GetPendingCloseOrders(); //--- Retrieve pending close orders int ordersToCloseCount = pendingCloseOrders.Count(); //--- Get count if (ordersToCloseCount == 0) { //--- Check if no orders return true; //--- Return true } if (_wallet.AreOrdersBeingOpened()) { //--- Check if orders being opened return true; //--- Return true } int ordersCloseSuccessCount = 0; //--- Initialize success count for (int i = ordersToCloseCount - 1; i >= 0; i--) { //--- Iterate orders Order* pendingCloseOrder = pendingCloseOrders.Get(i); //--- Get order if (pendingCloseOrder.IsAwaitingDealExecution) { //--- Check if awaiting execution ordersCloseSuccessCount++; //--- Increment success count continue; //--- Move to next } bool success; //--- Declare success flag if (AccountMarginMode == ACCOUNT_MARGIN_MODE_RETAIL_NETTING) { //--- Check netting mode Order* reversedOrder = new Order(pendingCloseOrder, false); //--- Create reversed order reversedOrder.Type = pendingCloseOrder.Type == ORDER_TYPE_BUY ? ORDER_TYPE_SELL : ORDER_TYPE_BUY; //--- Set opposite type success = OrderRepository::OpenOrder(reversedOrder); //--- Open reversed order if (success) { //--- Check if successful pendingCloseOrder.Ticket = reversedOrder.Ticket; //--- Update ticket } delete(reversedOrder); //--- Delete reversed order } else { success = OrderRepository::ClosePosition(pendingCloseOrder); //--- Close position } if (success) { //--- Check if successful ordersCloseSuccessCount++; //--- Increment success count } } return ordersCloseSuccessCount == ordersToCloseCount; //--- Return true if all successful } //--- Execute pending open orders bool ExecutePendingOpenOrders() { OrderCollection* pendingOpenOrders = _wallet.GetPendingOpenOrders(); //--- Retrieve pending open orders int ordersToOpenCount = pendingOpenOrders.Count(); //--- Get count if (ordersToOpenCount == 0) { //--- Check if no orders return true; //--- Return true } int ordersOpenSuccessCount = 0; //--- Initialize success count for (int i = ordersToOpenCount - 1; i >= 0; i--) { //--- Iterate orders Order* order = pendingOpenOrders.Get(i); //--- Get order if (order.IsAwaitingDealExecution) { //--- Check if awaiting execution ordersOpenSuccessCount++; //--- Increment success count continue; //--- Move to next } bool isTradeContextFree = false; //--- Initialize trade context flag double StartWaitingTime = GetTickCount(); //--- Start timer while (true) { //--- Wait for trade context if (MQL5InfoInteger(MQL5_TRADE_ALLOWED)) { //--- Check if trade allowed isTradeContextFree = true; //--- Set trade context free break; //--- Exit loop } int MaxWaiting_sec = 10; //--- Set max wait time if (IsStopped()) { //--- Check if EA stopped HandleErrors("The expert was stopped by a user action."); //--- Log error break; //--- Exit loop } if (GetTickCount() - StartWaitingTime > MaxWaiting_sec * 1000) { //--- Check if timeout HandleErrors(StringFormat("The (%d seconds) waiting time exceeded. Trade not allowed: EA disabled, market closed or trade context still not free.", MaxWaiting_sec)); //--- Log error break; //--- Exit loop } Sleep(100); //--- Wait briefly } if (!isTradeContextFree) { //--- Check if trade context not free if (!_wallet.CancelPendingOpenOrder(order)) { //--- Attempt to cancel order HandleErrors("Failed to cancel an order (because it couldn't open). Please see the Journal and Expert tab in Metatrader for more information."); //--- Log error } continue; //--- Move to next } bool success = OrderRepository::OpenOrder(order); //--- Open order if (success) { //--- Check if successful ordersOpenSuccessCount++; //--- Increment success count } else { if (!_wallet.CancelPendingOpenOrder(order)) { //--- Attempt to cancel order HandleErrors("Failed to cancel an order (because it couldn't open). Please see the Journal and Expert tab in Metatrader for more information."); //--- Log error } } } return ordersOpenSuccessCount == ordersToOpenCount; //--- Return true if all successful } };
Por último, definimos la lógica de control principal utilizando la clase «EA», que implementa la interfaz «ITrader» con las funciones «HandleTick», «Init» y «GetWallet». A estas alturas ya deberías conocer a la perfección estas funciones que ya hemos visto. Definimos las variables privadas «_firstTick», «_tradeStrategy», «_advisorStrategy», «_moneyManager» y «_wallet» para gestionar el estado y los componentes. En el constructor de «EA», establecemos «_firstTick» en «true», inicializamos «_wallet» con un nuevo objeto «Wallet», configuramos su intervalo de tiempo mediante «SetLastClosedOrdersByTimeframe», y creamos «_advisorStrategy» con un nuevo objeto «AdvisorStrategy», registrando las señales «ASOpenBuyLevel1» y «ASOpenSellLevel1» mediante «RegisterOpenBuy» y «RegisterOpenSell».
También instanciamos «_moneyManager» con un objeto «MoneyManager» y «_tradeStrategy» con un objeto «TradeStrategy» que contiene un «MultipleOpenModule_1», registrando «TakeProfitCloseModule_1» y «StopLossCloseModule_1» mediante «RegisterCloseModule». El destructor libera todos los componentes.
Nuestra función «Init» establece los indicadores de modo con MQLInfoInteger, configura el tamaño del lote mediante «MarketInfo_LibFunc», llama a «SetOrderGrouping» para activar los grupos de órdenes utilizando «GetOrderGroupingType» y carga las órdenes con «LoadOrdersFromBroker». La función «HandleTick» gestiona los ticks, sincroniza las órdenes con «SyncOrders» si no se está en modo de prueba, actualiza los TP/SL manuales con «SyncManualTPSLChanges» si está permitido y actualiza los precios a través de las funciones «Evaluate» de «AskFunc» y «BidFunc».
Actualiza los beneficios con «UpdateOrders», procesa los ticks de la cartera y de la estrategia con «HandleTick», evalúa «_tradeStrategy» con «Evaluate» si no hay órdenes pendientes y ejecuta los cierres y aperturas pendientes con «ExecutePendingCloseOrders» y «ExecutePendingOpenOrders», registrando los errores con «HandleErrors» si se producen fallos. Si se detiene el EA, este cierra las órdenes mediante «SetAllOpenOrdersToPendingClose». Ahora podemos llamar a esto en el controlador de eventos OnTick para gestionar las operaciones.
//--- Handle tick event datetime LastActionTime = 0; //--- Track last action time void OnTick() { string AccountServer = AccountInfoString(ACCOUNT_SERVER); //--- Retrieve account server string AccountCurrency = AccountInfoString(ACCOUNT_CURRENCY); //--- Retrieve account currency string AccountName = AccountInfoString(ACCOUNT_NAME); //--- Retrieve account name long AccountTradeMode = AccountInfoInteger(ACCOUNT_TRADE_MODE); //--- Retrieve trade mode string ReadableAccountTrademode = ""; //--- Initialize readable trade mode if (AccountTradeMode == 0) ReadableAccountTrademode = "DEMO ACCOUNT"; //--- Set demo mode if (AccountTradeMode == 1) ReadableAccountTrademode = "CONTEST ACCOUNT"; //--- Set contest mode if (AccountTradeMode == 2) ReadableAccountTrademode = "REAL ACCOUNT"; //--- Set real mode long AccountLogin = AccountInfoInteger(ACCOUNT_LOGIN); //--- Retrieve account login string AccountCompany = AccountInfoString(ACCOUNT_COMPANY); //--- Retrieve account company long AccountLeverage = AccountInfoInteger(ACCOUNT_LEVERAGE); //--- Retrieve account leverage long AccountLimitOrders = AccountInfoInteger(ACCOUNT_LIMIT_ORDERS); //--- Retrieve order limit double AccountMarginFree = AccountInfoDouble(ACCOUNT_MARGIN_FREE); //--- Retrieve free margin bool AccountTradeAllowed = AccountInfoInteger(ACCOUNT_TRADE_ALLOWED); //--- Retrieve trade allowed bool AccountTradeExpert = AccountInfoInteger(ACCOUNT_TRADE_EXPERT); //--- Retrieve expert allowed string ReadableAccountMarginMode = ""; //--- Initialize readable margin mode if (AccountMarginMode == 0) ReadableAccountMarginMode = "NETTING MODE"; //--- Set netting mode if (AccountMarginMode == 1) ReadableAccountMarginMode = "EXCHANGE MODE"; //--- Set exchange mode if (AccountMarginMode == 2) ReadableAccountMarginMode = "HEDGING MODE"; //--- Set hedging mode if (OneQuotePerBar) { //--- Check one quote per bar datetime currentTime = iTime(_Symbol, _Period, 0); //--- Get current bar time if (LastActionTime == currentTime) { //--- Check if same bar return; //--- Exit } else { LastActionTime = currentTime; //--- Update last action time } } Error = NULL; //--- Clear current error _ea.HandleTick(); //--- Handle tick if (IsDemoLiveOrVisualMode) { //--- Check visual mode MqlDateTime mql_datetime; //--- Declare datetime TimeCurrent(mql_datetime); //--- Get current time string comment = "\n" + (string)mql_datetime.year + "." + (string)mql_datetime.mon + "." + (string)mql_datetime.day + " " + TimeToString(TimeCurrent(), TIME_SECONDS) + OrderInfoComment; //--- Build comment if (DisplayOnChartError) { //--- Check if error display enabled if (Error != NULL) comment += "\n :: Current error : " + Error; //--- Add current error if (ErrorPreviousQuote != NULL) comment += "\n :: Last error : " + ErrorPreviousQuote; //--- Add previous error } comment += ""; //--- Append empty line Comment("ACCOUNT SERVER: ", AccountServer, "\n", //--- Display account info "ACCOUNT CURRENCY: ", AccountCurrency, "\n", "ACCOUNT NAME: ", AccountName, "\n", "ACCOUNT TRADEMODE: ", ReadableAccountTrademode, "\n", "ACCOUNT LOGIN: ", AccountLogin, "\n", "ACCOUNT COMPANY: ", AccountCompany, "\n", "ACCOUNT LEVERAGE: ", AccountLeverage, "\n", "ACCOUNT LIMIT ORDERS: ", AccountLimitOrders, "\n", "ACCOUNT MARGIN FREE: ", AccountMarginFree, "\n", "ACCOUNT TRADING ALLOWED: ", AccountTradeAllowed, "\n", "ACCOUNT EXPERT ALLOWED: ", AccountTradeExpert, "\n", "ACCOUNT MARGIN ALLOWED: ", ReadableAccountMarginMode); } }
Aquí gestionamos las actualizaciones del mercado en tiempo real para el programa mediante el controlador de eventos OnTick, que procesa cada nuevo tick de precios. Comenzamos recopilando los datos de la cuenta mediante funciones como «AccountInfoString» para el servidor, la divisa, el nombre y la empresa; AccountInfoInteger para el modo de negociación, el login de la cuenta, el apalancamiento y los límites de las órdenes; y AccountInfoDouble para el margen libre. Convertimos el modo de negociación en una cadena legible («DEMO ACCOUNT», «CONTEST ACCOUNT» o «REAL ACCOUNT») y el modo de margen, almacenado en «AccountMarginMode», en «NETTING MODE», «EXCHANGE MODE» o «HEDGING MODE» para mayor claridad.
Para controlar el procesamiento de los ticks, comprobamos el indicador «OneQuotePerBar» y, si está activado, utilizamos la función iTime para obtener la hora de inicio de la barra actual y la comparamos con «LastActionTime». Si coinciden, salimos para evitar un procesamiento duplicado; en caso contrario, actualizamos «LastActionTime». Borramos cualquier error existente estableciendo «Error» en nulo y llamamos a la función «HandleTick» en «_ea» para ejecutar la lógica de la estrategia.
Para la visualización, comprobamos «IsDemoLiveOrVisualMode» y, a continuación, utilizamos TimeCurrent y MqlDateTime para dar formato a la marca de tiempo, creando una cadena de comentario con «OrderInfoComment». Si la opción «DisplayOnChartError» está activada, añadimos los errores de «Error» y «ErrorPreviousQuote». Por último, utilizamos la función Comment para mostrar los detalles de la cuenta en el gráfico, lo que garantiza un seguimiento e información en tiempo real para el sistema de trading. Al ejecutar el programa, obtenemos el siguiente resultado.

En la imagen podemos ver que podemos generar y ejecutar una operación; en este caso, una operación de compra. Lo que tenemos que hacer ahora es gestionar las operaciones y, para ello, debemos llevar un seguimiento de las transacciones; por eso utilizaremos el controlador de eventos OnTradeTransaction para detectar las operaciones que se hayan abierto correctamente y gestionarlas de la siguiente manera.
//--- Handle trade transactions void OnTradeTransaction(const MqlTradeTransaction& trans, const MqlTradeRequest& request, const MqlTradeResult& result) { switch (trans.type) { //--- Handle transaction type case TRADE_TRANSACTION_DEAL_ADD: { //--- Handle deal addition datetime end = TimeCurrent(); //--- Get current server time datetime start = end - PeriodSeconds(PERIOD_D1); //--- Set start time (1 day ago) HistorySelect(start, end + PeriodSeconds(PERIOD_D1)); //--- Select history int dealsTotal = HistoryDealsTotal(); //--- Get total deals if (dealsTotal == 0) { //--- Check if no deals Print("No deals found"); //--- Log message return; //--- Exit } ulong orderTicketId = HistoryDealGetInteger(trans.deal, DEAL_ORDER); //--- Get order ticket CDealInfo dealInfo; //--- Declare deal info dealInfo.Ticket(trans.deal); //--- Set deal ticket ENUM_DEAL_ENTRY deal_entry = dealInfo.Entry(); //--- Get deal entry type bool found = false; //--- Initialize found flag if (deal_entry == DEAL_ENTRY_IN) { //--- Handle deal entry OrderCollection* pendingOpenOrders = _ea.GetWallet().GetPendingOpenOrders(); //--- Retrieve pending open orders for (int i = 0; i < pendingOpenOrders.Count(); i++) { //--- Iterate orders Order* order = pendingOpenOrders.Get(i); //--- Get order if (order.Ticket == orderTicketId) { //--- Check matching ticket found = true; //--- Set found flag order.OpenTime = dealInfo.Time(); //--- Set open time order.OpenPrice = trans.price; //--- Set open price order.TradePrice = order.OpenPrice; //--- Set trade price if (OrderFillingType == ORDER_FILLING_FOK) { //--- Check FOK filling order.OrderFilledLots += trans.volume; //--- Add volume if (MathAbs(order.Lots - order.OrderFilledLots) < 1e-5) { //--- Check if fully filled order.IsAwaitingDealExecution = false; //--- Clear execution flag order.Lots = order.OrderFilledLots; //--- Update lots order.TradeVolume = order.Lots; //--- Update trade volume _ea.GetWallet().SetPendingOpenOrderToOpen(order); //--- Move to open Print(StringFormat("Execution done for order (%d) by EA (%d)", orderTicketId, MagicNumber)); //--- Log success } } else { order.IsAwaitingDealExecution = false; //--- Clear execution flag bool actualVolumeDiffers = MathAbs(order.Lots - trans.volume) > 1e-5; //--- Check volume difference order.OrderFilledLots += trans.volume; //--- Add volume order.Lots = order.OrderFilledLots; //--- Update lots order.TradeVolume = order.Lots; //--- Update trade volume if (actualVolumeDiffers) { //--- Check if volume differs Print("Broker executed volume differs from requested volume. Executed volume: " + DoubleToStr(trans.volume)); //--- Log difference OrderRepository::CalculateAndSetCommision(order); //--- Recalculate commission } _ea.GetWallet().SetPendingOpenOrderToOpen(order); //--- Move to open Print(StringFormat("Execution done for order (%d) by EA (%d)", orderTicketId, MagicNumber)); //--- Log success } } } } else if (deal_entry == DEAL_ENTRY_OUT) { //--- Handle deal exit OrderCollection* pendingCloseOrders = _ea.GetWallet().GetPendingCloseOrders(); //--- Retrieve pending close orders for (int i = 0; i < pendingCloseOrders.Count(); i++) { //--- Iterate orders Order* order = pendingCloseOrders.Get(i); //--- Get order if (order.Ticket == orderTicketId) { //--- Check matching ticket found = true; //--- Set found flag if (OrderFillingType == ORDER_FILLING_FOK) { //--- Check FOK filling order.OrderFilledLots += trans.volume; //--- Add volume if (MathAbs(order.Lots - order.OrderFilledLots) < 1e-5) { //--- Check if fully filled order.IsAwaitingDealExecution = false; //--- Clear execution flag order.CloseTime = dealInfo.Time(); //--- Set close time order.ClosePrice = trans.price; //--- Set close price if (order.MagicNumber == MagicNumber) { //--- Check EA order TotalCommission += order.Commission; //--- Add commission } if (IsDemoLiveOrVisualMode) { //--- Check visual mode AnyChartObjectDelete(ChartID(), IntegerToString(order.Ticket) + "_TP"); //--- Delete TP line AnyChartObjectDelete(ChartID(), IntegerToString(order.Ticket) + "_SL"); //--- Delete SL line } if (order.ParentOrder != NULL) { //--- Check if parent order order.ParentOrder.Paint(); //--- Redraw parent } _ea.GetWallet().SetPendingCloseOrderToClosed(order); //--- Move to closed Print(StringFormat("Execution done for order (%d) by EA (%d)", orderTicketId, MagicNumber)); //--- Log success } } else { bool actualVolumeDiffers = MathAbs(order.Lots - trans.volume) > 1e-5; //--- Check volume difference if (actualVolumeDiffers) { //--- Handle partial close Print("Broker executed volume differs from requested volume.Requested volume: " + DoubleToStr(order.Lots) + ".Executed volume: " + DoubleToStr(trans.volume)); //--- Log difference Order* remainderOrder = new Order(order, false); //--- Create remainder order remainderOrder.Ticket = 0; //--- Clear ticket remainderOrder.Lots = order.Lots - trans.volume; //--- Set remaining volume remainderOrder.TradeVolume = remainderOrder.Lots; //--- Update trade volume OrderRepository::CalculateAndSetCommision(remainderOrder); //--- Recalculate commission _ea.GetWallet().GetPendingCloseOrders().Add(remainderOrder); //--- Add remainder order.Lots = trans.volume; //--- Update original volume order.TradeVolume = order.Lots; //--- Update trade volume OrderRepository::CalculateAndSetCommision(order); //--- Recalculate commission } else { Print("Broker executed volume: " + DoubleToStr(trans.volume)); //--- Log volume } order.IsAwaitingDealExecution = false; //--- Clear execution flag order.CloseTime = dealInfo.Time(); //--- Set close time order.ClosePrice = trans.price; //--- Set close price if (order.MagicNumber == MagicNumber) { //--- Check EA order TotalCommission += order.Commission; //--- Add commission } if (IsDemoLiveOrVisualMode) { //--- Check visual mode AnyChartObjectDelete(ChartID(), IntegerToString(order.Ticket) + "_TP"); //--- Delete TP line AnyChartObjectDelete(ChartID(), IntegerToString(order.Ticket) + "_SL"); //--- Delete SL line } _ea.GetWallet().SetPendingCloseOrderToClosed(order); //--- Move to closed Print(StringFormat("Execution done for order (%d) by EA (%d)", orderTicketId, MagicNumber)); //--- Log success } } } } if (found) { //--- Check if deal found Print("Updated order with deal info."); //--- Log update } else if (trans.symbol == Symbol() && dealInfo.Magic() == MagicNumber) { //--- Check EA deal Print("Couldn't find deal info for place/done order"); //--- Log missing deal } break; } } }
En el controlador de eventos OnTradeTransaction, donde procesamos los eventos MqlTradeTransaction, nos centramos en las transacciones TRADE_TRANSACTION_DEAL_ADD, en las que recuperamos una ventana de historial de un día con TimeCurrent y «PeriodSeconds» a través de HistorySelect. Si no se encuentran operaciones con «HistoryDealsTotal», registramos un mensaje con «Print» y salimos. Para cada operación, extraemos el comprobante de pedido mediante «HistoryDealGetInteger» y creamos un objeto «CDealInfo» con «Ticket» para comprobar el tipo de entrada a través de «Entry».
Para DEAL_ENTRY_IN (nuevas operaciones), iteramos sobre «GetPendingOpenOrders» desde el «Wallet» de «_ea», haciendo coincidir los tickets para actualizar las órdenes con el «Time» y el «price» de «trans». Si «OrderFillingType» es «ORDER_FILLING_FOK», actualizamos «OrderFilledLots» y, si se ha ejecutado, desactivamos «IsAwaitingDealExecution» y movemos la orden al estado de abierta con «SetPendingOpenOrderToOpen». De lo contrario, gestionamos los llenados parciales, registrando las diferencias de volumen con «Print» y actualizando los datos mediante «Calcular y establecer comisión».
En el caso de «DEAL_ENTRY_OUT» (cierres), procesamos «GetPendingCloseOrders», actualizando las órdenes coincidentes de forma similar, eliminando las líneas del gráfico con «AnyChartObjectDelete» si «IsDemoLiveOrVisualMode», añadiendo comisiones a «TotalCommission» y pasando las órdenes a cerradas con «SetPendingCloseOrderToClosed». Los cierres parciales generan una nueva orden con «CalculateAndSetCommission» para el volumen restante. Registramos las actualizaciones o los errores con «Print», lo que garantiza un seguimiento preciso de las operaciones. Ahora, al compilar, obtenemos el siguiente resultado.

En la imagen se puede ver que gestionamos la ejecución de las operaciones y mostramos los metadatos en la ventana. Lo que queda por hacer es realizar pruebas retrospectivas del programa para asegurarnos de que funciona correctamente, y eso se trata en la siguiente sección.
Backtesting y optimización
Al probar el programa, nos dimos cuenta de un descuido que se nos había pasado por alto: la liberación del programa y sus componentes al eliminarlo del gráfico. El asunto era el siguiente.

En la imagen podemos ver una representación de los problemas. Para resolver estos problemas, hemos implementado la siguiente lógica en el controlador de eventos OnDeinit.
//--- Deinitialize Expert Advisor void OnDeinit(const int reason) { Comment(""); delete(_ea); //--- Delete EA instance delete(AskFunc); //--- Delete Ask function delete(BidFunc); //--- Delete Bid function }
Hemos gestionado el proceso de limpieza del programa mediante el controlador de eventos OnDeinit, que se activa cuando se elimina el asesor experto o se cierra la terminal. Empezamos borrando todos los comentarios del gráfico con la función Comment, para dejar el gráfico limpio. A continuación, liberamos memoria eliminando el objeto «_ea», que representa la instancia principal de «EA», mediante el operador delete. También hemos eliminado los objetos «AskFunc» y «BidFunc», que se encargan de recuperar los datos de precios, para liberar recursos.
Este proceso conciso garantizaba una desinicialización adecuada, evitando fugas de memoria y manteniendo la eficiencia del sistema cuando se detenía el programa. Con eso se solucionó todo y, tras una optimización exhaustiva y una serie de pruebas retrospectivas, utilizando la configuración predeterminada y modificando los parámetros del módulo de operaciones o de riesgo para utilizar valores del 1 %, 5, 30, 10 y 60, respectivamente, hemos obtenido los siguientes resultados.
Gráfico de backtest:

Informe de backtest:

Conclusión
En conclusión, hemos desarrollado una implementación en MQL5 de la estrategia de scalping «Envelopes Trend Bounce», que permite la ejecución automatizada de operaciones basada en señales precisas de los indicadores «Envelopes», «Medias móviles» e «Índice de fuerza relativa», con una gestión de riesgos integrada para controlar el tamaño de las posiciones y proteger el capital frente a pérdidas. Nuestro marco modular, que incluye la evaluación dinámica de señales, la ejecución de operaciones y el seguimiento de órdenes en tiempo real, ofrece una base escalable para el scalping. Puedes perfeccionar aún más este programa ajustando los umbrales de señal, optimizando los parámetros de riesgo o incorporando indicadores adicionales para adaptarlo a tus objetivos de negociación.
Descargo de responsabilidad: Esta implementación tiene fines exclusivamente educativos. El trading conlleva riesgos financieros considerables, y la volatilidad del mercado puede provocar pérdidas importantes. Es fundamental realizar pruebas retrospectivas exhaustivas y aplicar una gestión prudente del riesgo antes de utilizar este programa en entornos de negociación reales.
Al dominar estas técnicas, podemos mejorar la adaptabilidad y la solidez del programa o aprovechar su estructura para desarrollar otras estrategias de negociación, mejorando así el algoritmo de negociación del programa.
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/18298
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.
Utilizando redes neuronales en MetaTrader
De novato a experto: Utilidad de control de parámetros
Particularidades del trabajo con números del tipo double en MQL4
Características del Wizard MQL5 que debe conocer (Parte 69): Uso de patrones del SAR y del RVI
- 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