English Русский Deutsch 日本語
preview
Formulación de un Asesor Experto Multipar Dinámico (Parte 4): Ajuste de volatilidad y riesgo

Formulación de un Asesor Experto Multipar Dinámico (Parte 4): Ajuste de volatilidad y riesgo

MetaTrader 5Ejemplos |
175 3
Hlomohang John Borotho
Hlomohang John Borotho

Introducción

En el trading con múltiples pares, uno de los retos a los que se enfrentan los operadores es la irregularidad en los resultados, provocada por las diferencias de volatilidad entre los distintos pares de divisas. Como se comentó en el artículo anterior, una estrategia que da buenos resultados con el EURUSD puede tener un rendimiento inferior o resultar excesivamente arriesgada con el GBPJPY debido a sus diferentes perfiles de volatilidad. El uso de tamaños de lote fijos o de stop loss estáticos puede dar lugar a posiciones excesivamente grandes en mercados volátiles o a la pérdida de oportunidades en mercados estables. Esta falta de adaptabilidad suele traducirse en una exposición desigual al riesgo, mayores drawdowns y resultados impredecibles, especialmente durante publicaciones macroeconómicas de alto impacto o cambios repentinos en el mercado.

Para resolver esto, incorporamos ajustes por volatilidad y riesgo en el EA. Al incorporar herramientas como el rango verdadero medio (ATR) y el cálculo dinámico del tamaño de la posición en función del riesgo, el EA adapta automáticamente los parámetros de las operaciones a las condiciones actuales del mercado. Esto garantiza que cada posición quede equilibrada en proporción a su volatilidad, favoreciendo una gestión del riesgo coherente y mejorando el rendimiento del EA en todos los pares operados.



Plan de desarrollo del EA

Lógica de trading:

1. Gestor multisímbolo:

  • Analizador de listas de símbolos
  • Seguimiento de datos por símbolo
  • Gestión simultánea de posiciones


2. Condición de entrada:

3. Niveles de riesgo basados en la volatilidad:

4. Determinación del tamaño de la posición:

Importe arriesgado = Capital de la cuenta * % de riesgo
Tamaño de la posición = Importe arriesgado / (Distancia del SL * Valor del punto)

5. Identificación del objetivo V-stop:

[ Precio actual ]
        |
        v
[ Zona de resistencia ] <-- V-Stop superior previo (TP para ventas)
        |
        v
[ Precio actual ]
        |
        v
[ Zona de soporte ] <-- V-Stop inferior previo (TP para compras)

6. Cronología del ciclo de vida de una operación:



Primeros pasos

//+------------------------------------------------------------------+
//|                               MultiSymbolVolatilityTraderEA.mq5  |
//|                                  Copyright 2025, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#include <Trade/Trade.mqh>

Como de costumbre, empezamos importando la biblioteca de operaciones necesaria para que nuestro asesor experto pueda ejecutar órdenes y gestionar posiciones.

//--- Input settings
input string   Symbols = "XAUUSD,GBPUSD,USDCAD,USDJPY";  // Symbols to trade
input int      RSI_Period = 14;                           // RSI Period
input double   RSI_Overbought = 70.0;                     // RSI Overbought Level
input double   RSI_Oversold = 30.0;                       // RSI Oversold Level
input uint     ATR_Period = 14;                           // ATR Period for Volatility
input double   ATR_Multiplier = 2.0;                      // ATR Multiplier for SL
input double   RiskPercent_High = 0.02;                   // Risk % High Volatility
input double   RiskPercent_Mod = 0.01;                    // Risk % Moderate Volatility
input double   RiskPercent_Low = 0.005;                   // Risk % Low Volatility
input int      Min_Bars = 50;                             // Minimum Bars Required
input double   In_Lot = 0.01;                             // Default lot size
input int      StopLoss = 100;                            // SL in points
input int      TakeProfit = 100;                          // TP in points

En este bloque definimos los parámetros de entrada que determinan el funcionamiento del asesor experto en varios símbolos. El parámetro «Symbols» permite al operador especificar los instrumentos con los que desea operar, mientras que los parámetros relacionados con el RSI (RSI_Period, RSI_Overbought y RSI_Oversold) se utilizan para identificar posibles puntos de entrada basándose en condiciones de sobrecompra o sobreventa. La volatilidad se tiene en cuenta mediante el rango verdadero medio (ATR), con parámetros ajustables para su periodo y un multiplicador que permite escalar el stop loss de forma dinámica.

El EA ajusta su exposición al riesgo en función de los niveles de volatilidad, aplicando diferentes porcentajes de riesgo para situaciones de volatilidad alta, moderada y baja. Otros parámetros incluyen «Min_Bars» para garantizar que haya datos históricos suficientes, un tamaño predeterminado de «In_Lot» y valores de StopLoss y TakeProfit definidos en puntos fijos, que actúan como valores de reserva si no se aplican los niveles dinámicos.

//--- Global variables
string symb_List[];
int    Num_symbs = 0;
int    ATR_Handles[];
int    RSI_Handles[];
double Prev_ATR[];
double Prev_RSI[];
datetime LastBarTime[];
CTrade trade;

En esta sección se declaran las variables globales que se utilizan en todo el asesor experto. Symb_List[] es una matriz que almacena la lista de símbolos con los que operar, mientras que Num_symbs contiene el recuento total de dichos símbolos. Las matrices como ATR_Handles[] y RSI_Handles[] gestionan los identificadores de los indicadores para los cálculos de ATR y RSI, lo que permite al EA procesar varios símbolos a la vez.

Prev_ATR[] y Prev_RSI[] almacenan los valores más recientes de estos indicadores para cada símbolo, que se utilizan en la lógica de toma de decisiones. LastBarTime[] registra la última barra procesada de cada símbolo para evitar operaciones duplicadas en la misma vela. Por último, el objeto de operación CTrade permite acceder a las funciones de operación integradas en MQL5, lo que facilita la ejecución de órdenes y la gestión de posiciones.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
    //--- Split symbol list
    ushort separator = StringGetCharacter(",", 0);
    StringSplit(Symbols, separator, symb_List);
    Num_symbs = ArraySize(symb_List);
    
    //--- Resize arrays
    ArrayResize(ATR_Handles, Num_symbs);
    ArrayResize(RSI_Handles, Num_symbs);
    ArrayResize(Prev_ATR, Num_symbs);
    ArrayResize(Prev_RSI, Num_symbs);
    ArrayResize(LastBarTime, Num_symbs);
    ArrayInitialize(Prev_ATR, 0.0);
    ArrayInitialize(Prev_RSI, 50.0);
    ArrayInitialize(LastBarTime, 0);
    
    //--- Create indicator handles
    for(int i = 0; i < Num_symbs; i++)
    {
        string symbol = symb_List[i];
        ATR_Handles[i] = iATR(symbol, PERIOD_CURRENT, ATR_Period);
        RSI_Handles[i] = iRSI(symbol, PERIOD_CURRENT, RSI_Period, PRICE_CLOSE);
        
        if(ATR_Handles[i] == INVALID_HANDLE || RSI_Handles[i] == INVALID_HANDLE)
        {
            Print("Error creating indicator handles for ", symbol, " - Error: ", GetLastError());
            return INIT_FAILED;
        }
    }
    
    return INIT_SUCCEEDED;
}

La función OnInit() se encarga de inicializar el asesor experto cuando se carga en el gráfico. En primer lugar, divide la entrada de símbolos, separada por comas, en una matriz denominada `symb_List` y calcula el número total de símbolos que hay que gestionar. A continuación, redimensiona e inicializa varias matrices globales, como las correspondientes a los identificadores ATR y RSI, los valores anteriores de los indicadores y las horas de las últimas barras procesadas, para garantizar que cada símbolo disponga de un espacio de almacenamiento y un seguimiento específicos. Los valores iniciales se establecen para evitar comportamientos indefinidos durante el primer ciclo de ejecución del EA.

A continuación, la función recorre cada símbolo y crea identificadores de indicador para el ATR y el RSI utilizando iATR e iRSI, respectivamente. Estas funciones son esenciales para obtener los valores de los indicadores en tiempo real durante las operaciones de trading. Si falla alguna de las operaciones de creación de identificadores (es decir, devuelve INVALID_HANDLE), se muestra un mensaje de error y se interrumpe la inicialización devolviendo INIT_FAILED. Si todos los indicadores se han configurado correctamente, la función devuelve INIT_SUCCEEDED, lo que indica que el EA está listo para comenzar la ejecución.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
    //--- Release indicator handles
    for(int i = 0; i < Num_symbs; i++)
    {
        if(ATR_Handles[i] != INVALID_HANDLE) 
            IndicatorRelease(ATR_Handles[i]);
        if(RSI_Handles[i] != INVALID_HANDLE) 
            IndicatorRelease(RSI_Handles[i]);
    }
}

La función OnDeinit() se activa cuando el asesor experto se elimina del gráfico o se reinicializa. Su objetivo principal es liberar recursos mediante la liberación de los identificadores asociados a cada símbolo. Al llamar a IndicatorRelease() para los identificadores de ATR y RSI únicamente si son válidos, se garantiza que la memoria del sistema se libere correctamente, lo que evita fugas de memoria o un consumo innecesario de recursos. Esto ayuda a mantener la estabilidad de la plataforma, sobre todo cuando se ejecutan varios EA o indicadores.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
    for(int i = 0; i < Num_symbs; i++)
    {
        string symbol = symb_List[i];
        
        //--- Check for new bar
        datetime currentBarTime = iTime(symbol, PERIOD_CURRENT, 0);
        if(LastBarTime[i] == currentBarTime) continue;
        LastBarTime[i] = currentBarTime;
        
        //--- Get indicator values
        double atr[2] = {0.0, 0.0};
        double rsi[2] = {50.0, 50.0};
        
        if(CopyBuffer(ATR_Handles[i], 0, 1, 2, atr) < 2 || 
           CopyBuffer(RSI_Handles[i], 0, 1, 2, rsi) < 2)
            continue;
        
        Prev_ATR[i] = atr[0];  // Previous bar's ATR
        Prev_RSI[i] = rsi[0];  // Previous bar's RSI
        
        //--- Get current prices
        MqlTick lastTick;
        if(!SymbolInfoTick(symbol, lastTick)) continue;
        double ask = lastTick.ask;
        double bid = lastTick.bid;
        
        //--- Check existing positions
        bool hasLong = false, hasShort = false;
        CheckExistingPositions(symbol, hasLong, hasShort);
        
        //--- Calculate volatility-based risk
        double riskPercent = CalculateRiskLevel(symbol);
        
        //--- Get V-Stop levels
        double vStopUpper = CalculateVStop(symbol, 1, true);  // Previous bar's upper band
        double vStopLower = CalculateVStop(symbol, 1, false); // Previous bar's lower band
        
        //--- Trade Entry Logic
        if(!hasLong && !hasShort)
        {
            //--- Sell signal: RSI overbought + price below V-Stop upper band
            if(rsi[0] > RSI_Overbought && bid < vStopUpper)
            {
                double tp = GetProfitTarget(symbol, false);  // Previous V-Stop level
                double sl = ask + ATR_Multiplier * atr[0];
                double lots = CalculatePositionSize(symbol, riskPercent, sl, ask);
                
                if(lots > 0)
                    ExecuteTrade(ORDER_TYPE_SELL, symbol);
            }
            //--- Buy signal: RSI oversold + price above V-Stop lower band
            else if(rsi[0] < RSI_Oversold && ask > vStopLower)
            {
                double tp = GetProfitTarget(symbol, true);   // Previous V-Stop level
                double sl = bid - ATR_Multiplier * atr[0];
                double lots = CalculatePositionSize(symbol, riskPercent, sl, bid);
                
                if(lots > 0)
                    ExecuteTrade(ORDER_TYPE_BUY, symbol);
            }
        }
        
        //--- Trailing Stop Logic
        UpdateTrailingStops(symbol, atr[0]);
    }
}

La función OnTick() se ejecuta cada vez que se actualiza el mercado, recorriendo cada símbolo de la lista de operaciones para realizar análisis en tiempo real y gestionar las operaciones. En primer lugar, comprueba si se ha formado una nueva barra para el símbolo comparando la marca de tiempo de la barra actual con la última registrada. Si se trata de una nueva barra, el programa recupera los últimos valores de ATR y RSI mediante la función CopyBuffer(), y los almacena en las matrices globales para utilizarlos en la toma de decisiones. Los precios actuales de compra y venta también se obtienen mediante la función SymbolInfoTick() para garantizar la precisión de los niveles de entrada y salida.

A continuación, comprueba si hay posiciones largas o cortas abiertas para el símbolo actual mediante la función CheckExistingPositions(). A continuación, calcula el nivel de riesgo adecuado basándose en la volatilidad del símbolo mediante la función CalculateRiskLevel(), y determina los niveles V-Stop más recientes para orientar la lógica de entrada y de seguimiento. A partir de esta información, el EA aplica sus reglas de entrada en el mercado: se activa una orden de venta cuando el RSI indica condiciones de sobrecompra y el precio cae por debajo del V-Stop superior, mientras que se activa una orden de compra cuando el RSI muestra condiciones de sobreventa y el precio supera el V-Stop inferior. En ambos casos, los niveles dinámicos de stop loss y take profit se calculan utilizando el ATR y el V-Stop, y el tamaño de la posición se ajusta para adaptarse al nivel de riesgo definido.

Por último, independientemente de si se abre una nueva operación, el EA llama a la función UpdateTrailingStops() para gestionar las posiciones abiertas. Esta función ajusta los stop loss en función de los últimos datos de volatilidad, lo que ayuda a asegurar las ganancias y limitar las pérdidas a medida que cambian las condiciones del mercado. Este enfoque dinámico garantiza que la estrategia siga siendo flexible y se adapte a múltiples símbolos en tiempo real.

//+------------------------------------------------------------------+
//| Execute trade with risk parameters                               |
//+------------------------------------------------------------------+
void ExecuteTrade(ENUM_ORDER_TYPE tradeType, string symbol)
{
   double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
   double price = (tradeType == ORDER_TYPE_BUY) ? SymbolInfoDouble(symbol, SYMBOL_ASK) :
                                                SymbolInfoDouble(symbol, SYMBOL_BID);

   // Convert StopLoss and TakeProfit from pips to actual price distances
   double sl_distance = StopLoss * point;
   double tp_distance = TakeProfit * point;
   
   double sl = (tradeType == ORDER_TYPE_BUY) ? price - sl_distance :
                                             price + sl_distance;
   
   double tp = (tradeType == ORDER_TYPE_BUY) ? price + tp_distance :
                                             price - tp_distance;

   trade.PositionOpen(symbol, tradeType, In_Lot, price, sl, tp, NULL);
}

La función ExecuteTrade() se encarga de ejecutar la operación en función del tipo de orden especificado (compra o venta) y del símbolo. En primer lugar, determina el precio de entrada correcto utilizando el precio de venta o de compra actual, dependiendo de la dirección de la operación. A continuación, calcula los niveles de stop loss y take profit convirtiendo esos valores en distancias reales de precio, utilizando el tamaño del punto del símbolo. En función de si se trata de una operación de compra o de venta, establece el SL y el TP en la dirección adecuada y, por último, utiliza el objeto CTrade para abrir una posición con el tamaño de lote y los parámetros de precio definidos.

//+------------------------------------------------------------------+
//| Calculate V-Stop levels                                          |
//+------------------------------------------------------------------+
double CalculateVStop(string symbol, int shift, bool isUpper)
{
    double atr[1];
    double high[1], low[1], close[1];
    
    if(CopyBuffer(ATR_Handles[GetSymbolIndex(symbol)], 0, shift, 1, atr) < 1 ||
       CopyHigh(symbol, PERIOD_CURRENT, shift, 1, high) < 1 ||
       CopyLow(symbol, PERIOD_CURRENT, shift, 1, low) < 1 ||
       CopyClose(symbol, PERIOD_CURRENT, shift, 1, close) < 1)
        return 0.0;
    
    double price = (high[0] + low[0] + close[0]) / 3.0;  // Typical price
    return isUpper ? price + ATR_Multiplier * atr[0] : price - ATR_Multiplier * atr[0];
}

La función CalculateVStop() calcula el nivel dinámico de V-Stop para un símbolo y un desplazamiento de barra determinados, ajustándolo en función de la volatilidad. Recupera los valores de ATR, máximo, mínimo y cierre de la barra especificada mediante CopyBuffer() y las funciones de la serie de precios. A continuación, calcula el precio típico como la media entre el máximo, el mínimo y el cierre. Dependiendo de si se necesita el V-Stop superior o inferior (indicador «isUpper»), suma o resta un múltiplo del ATR a este precio típico para generar un nivel de soporte o resistencia ajustado a la volatilidad, que se utiliza para filtrar operaciones y aplicar la lógica de trailing.

//+------------------------------------------------------------------+
//| Get profit target from V-Stop history                            |
//+------------------------------------------------------------------+
double GetProfitTarget(string symbol, bool forLong)
{
    int bars = 50;  // Look back 50 bars
    double target = 0.0;
    
    for(int i = 1; i <= bars; i++)
    {
        double vStop = forLong ? 
            CalculateVStop(symbol, i, false) :  // For longs, find support levels
            CalculateVStop(symbol, i, true);     // For shorts, find resistance levels
        
        if(vStop != 0.0)
        {
            target = vStop;
            break;
        }
    }
    
    // Fallback: Use fixed multiplier if no V-Stop found
    MqlTick lastTick;
    SymbolInfoTick(symbol, lastTick);
    return (target == 0.0) ? 
        (forLong ? lastTick.ask + 5*Prev_ATR[GetSymbolIndex(symbol)] : 
                   lastTick.bid - 5*Prev_ATR[GetSymbolIndex(symbol)]) : 
        target;
}

La función GetProfitTarget() determina un nivel dinámico de cierre de ganancias basándose en los valores históricos de V-Stop. Analiza los últimos 50 barras en busca del nivel V-Stop válido más cercano, ya sea una banda inferior para operaciones en largo (que actúa como soporte) o una banda superior para operaciones en corto (que actúa como resistencia). Si se encuentra un nivel válido, se utiliza como objetivo de ganancias. Si no hay datos disponibles dentro del periodo de referencia, la función recurre a un objetivo predeterminado, calculado como una distancia de 5 veces el ATR respecto al precio de compra o venta actual, lo que garantiza que el EA siga estableciendo un nivel de cierre de ganancias lógico incluso en ausencia de datos históricos de V-Stop.

//+------------------------------------------------------------------+
//| Calculate risk level based on volatility                         |
//+------------------------------------------------------------------+
double CalculateRiskLevel(string symbol)
{
    double atrValues[20];
    if(CopyBuffer(ATR_Handles[GetSymbolIndex(symbol)], 0, 1, 20, atrValues) < 20)
        return RiskPercent_Mod;
    
    double avgATR = 0.0;
    for(int i = 0; i < 20; i++) avgATR += atrValues[i];
    avgATR /= 20.0;
    
    double currentATR = atrValues[0];  // Most recent ATR
    
    if(currentATR > avgATR * 1.5)
        return RiskPercent_High;
    else if(currentATR < avgATR * 0.5)
        return RiskPercent_Low;
    
    return RiskPercent_Mod;
}

La función CalculateRiskLevel() ajusta dinámicamente la exposición al riesgo del EA en función de la volatilidad actual del mercado. Recupera los últimos 20 valores de ATR del valor en cuestión y calcula su media para establecer una línea de referencia. A continuación, se compara el valor más reciente del ATR con esta media: si es significativamente superior (más de 1,5 veces), se considera que el mercado es muy volátil y se aplica el porcentaje de riesgo más alto; si es mucho menor (menos de 0,5 veces), se aplica el porcentaje de riesgo más bajo. De lo contrario, se opta por un nivel de riesgo moderado. Esto garantiza que el tamaño de las operaciones sea adaptable y se ajuste a las condiciones del mercado en tiempo real.

//+------------------------------------------------------------------+
//| Calculate position size based on risk                            |
//+------------------------------------------------------------------+
double CalculatePositionSize(string symbol, double riskPercent, double sl, double entryPrice)
{
    double tickSize = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
    double tickValue = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
    double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
    
    if(tickSize == 0 || point == 0) return 0.0;
    
    double riskAmount = AccountInfoDouble(ACCOUNT_EQUITY) * riskPercent;
    double slDistance = MathAbs(entryPrice - sl) / point;
    double moneyRisk = slDistance * tickValue / (tickSize / point);
    
    if(moneyRisk <= 0) return 0.0;
    double lots = riskAmount / moneyRisk;
    
    // Normalize and validate lot size
    double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
    double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
    double step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
    
    lots = MathMax(minLot, MathMin(maxLot, lots));
    lots = MathRound(lots / step) * step;
    
    return lots;
}

La función CalculatePositionSize() determina el tamaño de posición adecuado para una operación en función del porcentaje de riesgo y la distancia del stop loss especificados. En primer lugar, recopila los parámetros de negociación esenciales, como el tamaño del tick, el valor del tick y el tamaño del punto para el símbolo. A partir del capital de la cuenta y del porcentaje de riesgo seleccionado, calcula la cantidad total de dinero que el operador está dispuesto a arriesgar.

A continuación, calcula el riesgo monetario por lote teniendo en cuenta la distancia del stop loss en puntos y lo convierte en un valor basado en los parámetros del tick. La función divide el importe total del riesgo entre este riesgo por lote para obtener el tamaño óptimo del lote. Por último, ajusta el tamaño de la operación para cumplir con los requisitos de mínimo, máximo y incremento del bróker, garantizando que el tamaño de la posición sea válido y esté correctamente redondeado para su ejecución.

//+------------------------------------------------------------------+
//| Update trailing stops                                            |
//+------------------------------------------------------------------+
void UpdateTrailingStops(string symbol, double currentATR)
{
    double newSL = 0.0;
    for(int pos = PositionsTotal()-1; pos >= 0; pos--)
    {
        if(PositionGetSymbol(pos) != symbol) continue;
        
        ulong ticket = PositionGetInteger(POSITION_TICKET);
        double currentSL = PositionGetDouble(POSITION_SL);
        double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
        double currentProfit = PositionGetDouble(POSITION_PROFIT);
        double currentPrice = PositionGetDouble(POSITION_PRICE_CURRENT);
        
        if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
        {
            newSL = currentPrice - ATR_Multiplier * currentATR;
            if(newSL > currentSL && newSL > openPrice && currentProfit > 0)
                ModifySL(ticket, newSL);
        }
        else
        {
            newSL = currentPrice + ATR_Multiplier * currentATR;
            if((newSL < currentSL || currentSL == 0) && newSL < openPrice && currentProfit > 0)
                ModifySL(ticket, newSL);
        }
    }
}

La función UpdateTrailingStops() gestiona de forma activa las posiciones abiertas ajustando sus órdenes de stop-loss en función de la volatilidad actual del mercado. Para cada posición que coincida con el símbolo indicado, calcula un nuevo nivel de stop dinámico utilizando el último ATR multiplicado por un factor predefinido. En el caso de las posiciones largas, el stop loss se desplaza al alza si el nuevo nivel es superior al stop loss actual y se sitúa por encima del precio de apertura, lo que ayuda a asegurar las ganancias una vez que la operación entra en terreno positivo. Por el contrario, en el caso de las posiciones cortas, el stop loss se ajusta a la baja en condiciones similares. Este enfoque de stop dinámico protege las ganancias al tiempo que deja margen para que la operación se desarrolle con naturalidad en mercados volátiles.

//+------------------------------------------------------------------+
//| Modify stop loss                                                 |
//+------------------------------------------------------------------+
bool ModifySL(ulong ticket, double newSL)
{
    MqlTradeRequest request = {};
    MqlTradeResult result = {};
    
    request.action = TRADE_ACTION_SLTP;
    request.position = ticket;
    request.sl = newSL;
    request.deviation = 5;
    
    if(!OrderSend(request, result))
    {
        Print("Modify SL error: ", GetLastError());
        return false;
    }
    return true;
}

La función ModifySL() actualiza el nivel de stop loss de una posición existente identificada por su número de ticket. Crea una solicitud de operación en la que se especifica la acción de modificar el stop loss (TRADE_ACTION_SLTP), asigna el nuevo precio de stop loss y establece una desviación máxima permitida del precio. A continuación, la solicitud se envía mediante OrderSend(), y si la modificación falla, se muestra un mensaje de error con el código de error correspondiente. La función devuelve «true» si la operación se realiza correctamente y «false» si la actualización del stop loss no se ha podido llevar a cabo, lo que permite al EA gestionar los errores de forma adecuada.

//+------------------------------------------------------------------+
//| Check existing positions                                         |
//+------------------------------------------------------------------+
void CheckExistingPositions(string symbol, bool &hasLong, bool &hasShort)
{
    hasLong = false;
    hasShort = false;
    
    for(int pos = PositionsTotal()-1; pos >= 0; pos--)
    {
        if(PositionGetSymbol(pos) == symbol)
        {
            if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
                hasLong = true;
            else
                hasShort = true;
        }
    }
}

En este bloque de código, la función CheckExistingPositions() analiza todas las posiciones abiertas para determinar si hay operaciones activas en largo o en corto para el símbolo especificado. Inicializa las banderas de salida `hasLong` y `hasShort` como `false` y, a continuación, recorre todas las posiciones. Para cada posición que coincida con el símbolo, establece hasLong en «true» si se trata de una posición de compra, o hasShort en «true» si se trata de una posición de venta.

//+------------------------------------------------------------------+
//| Get symbol index                                                 |
//+------------------------------------------------------------------+
int GetSymbolIndex(string symbol)
{
    for(int i = 0; i < Num_symbs; i++)
        if(symb_List[i] == symbol)
            return i;
    return -1;
}

Por último, la función GetSymbolIndex() ofrece una sencilla función auxiliar para obtener el índice de un símbolo determinado de la matriz symb_List. Recorre todos los símbolos almacenados y devuelve el índice correspondiente cuando encuentra el símbolo. Si el símbolo no aparece en la lista, devuelve -1. Esta función es esencial para sincronizar datos específicos de los símbolos, como los identificadores de los indicadores o los valores almacenados, entre las distintas matrices que se utilizan en todo el EA.

Resultados del backtest

El backtest se evaluó en el marco temporal H1 durante un periodo de prueba de aproximadamente dos meses (del 11 de junio de 2025 al 1 de agosto de 2025), con los siguientes parámetros de entrada:

  • Período del RSI = 60
  • Nivel de sobrecompra del RSI = 70
  • Nivel de sobreventa del RSI = 45
  • Período de la ATR para la volatilidad = 62
  • Multiplicador ATR para el nivel de stop loss = 3,2
  • Riesgo % Alta volatilidad = 0,19
  • Riesgo % Volatilidad moderada = 0,064
  • Riesgo % Baja volatilidad = 0,0335
  • Número mínimo de barras requerido = 50
  • Tamaño de lote predeterminado = 0,01
  • SL en puntos = 610
  • TP en puntos = 980



Conclusión

En resumen, hemos desarrollado un asesor experto dinámico para múltiples pares de divisas que se adapta a las condiciones cambiantes del mercado mediante la integración de una gestión de riesgos basada en la volatilidad. El sistema procesa varios instrumentos simultáneamente, aplicando indicadores técnicos como el ATR y el RSI para identificar oportunidades de negociación. Calcula los niveles de V-Stop para filtrar las entradas y gestionar las salidas, al tiempo que ajusta dinámicamente el tamaño de las posiciones y los stop loss en función de la volatilidad en tiempo real. Gracias a sus funciones modulares, el EA se encarga de la ejecución de operaciones, los trailing stops y la clasificación del riesgo (alto, moderado, bajo) para garantizar que cada operación se ajuste al comportamiento actual del símbolo.

En conclusión, este enfoque proporciona a los operadores una herramienta sólida y adaptable que mantiene un rendimiento constante en diversos pares de divisas. Al ajustar la exposición al riesgo y los parámetros de negociación en función de la volatilidad, el EA reduce la sobreexposición en mercados volátiles y evita un rendimiento inferior al esperado en mercados tranquilos.

Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/18165

Archivos adjuntos |
CapeCoddah
CapeCoddah | 11 ago 2025 en 11:29

Hola,


Un artículo muy interesante. Acabo de hojearlo y decidí que le daré una evaluación detallada, ya que es similar a un EA que estoy desarrollando.

Mi primera pregunta es ¿hay alguna razón por la que todos los oficios fueron compras y no vende? ¿También está planeando en otros artículos sobre este tema?

De mi breve revisión de su código, me permito sugerir que GetSymbolIndex y otras variables como el punto debe ser movido a la parte superior del bucle de símbolo y se asigna a las variables para mejorar la eficiencia mediante la reducción de la redundancia. A medida que más símbolos se añaden a la lista de pares, exponencialmente más tiempo se gastará en la ejecución de código redundante. También podría considerar la adición de un índice PairCode a sus matrices para que puedan ser accedidos directamente.


CapeCoddah

Hlomohang John Borotho
Hlomohang John Borotho | 12 ago 2025 en 15:52
CapeCoddah exponencialmente más tiempo se gastará en la ejecución de código redundante. También podría considerar la adición de un índice PairCode a sus matrices por lo que se podría acceder directamente.


CapeCoddah

Hey,

La razón por la que todas las operaciones se compran depende de la configuración de entrada que puede haber introducido, y si se utiliza la misma configuración que hice en la prueba retrospectiva, la razón es que he optimizado la EA. Otros artículos sobre este tema pueden venir a su debido tiempo.

Gracias por su sugerencia, lo tendré en cuenta.

JohnHlomohang
Ademir Temoteo De Vasco
Ademir Temoteo De Vasco | 6 oct 2025 en 01:28

Hola... buenas noches!!!

En primer lugar felicitaros por vuestro excelente trabajo.

Estoy realizando pruebas y estoy muy satisfecho con los resultados, pero confieso que sólo estoy utilizando una divisa para realizar la prueba, ya que la configuración es diferente para cada activo.

No entiendo cómo funciona esta lógica de colocar varias divisas al mismo tiempo.

Si me lo pudierais explicar os lo agradecería.

Traducción automática aplicada por moderador. En el foro en inglés, por favor escriba en inglés.

Motor de decisión Multi-IA para MQL5 (Parte 1): Integrar múltiples IA con votación por consenso Motor de decisión Multi-IA para MQL5 (Parte 1): Integrar múltiples IA con votación por consenso
Presentamos una arquitectura en la que el EA habla con un AI Manager que consulta a OpenAI, Claude, Gemini y DeepSeek, parsea sus JSON y los convierte a un estándar AIResponse. Con un prompt común y un sistema de votación ponderada con quórum, se obtiene una señal final COMPRAR/VENDER/MANTENER. Incluye gestión de errores, temporizador y un EA mínimo para su integración práctica.
De novato a experto: Noticias animadas utilizando MQL5 (VIII) Botones de operación rápida para trading de noticias De novato a experto: Noticias animadas utilizando MQL5 (VIII) Botones de operación rápida para trading de noticias
Aunque los sistemas de trading algorítmico gestionan las operaciones de forma automatizada, muchos traders que operan en función de las noticias y los scalpers prefieren mantener un control activo durante noticias de alto impacto y en condiciones de mercado de ritmo acelerado, lo que exige una rápida ejecución y gestión de las órdenes. Esto pone de relieve la necesidad de contar con herramientas front-end intuitivas que integren fuentes de noticias en tiempo real, datos del calendario económico, análisis de indicadores, análisis basados en inteligencia artificial y controles de trading ágiles y de respuesta inmediata.
De novato a experto: Noticias animadas utilizando MQL5 (IX) Gestión de múltiples símbolos en un único gráfico para el trading de noticias De novato a experto: Noticias animadas utilizando MQL5 (IX) Gestión de múltiples símbolos en un único gráfico para el trading de noticias
El trading basado en noticias suele requerir la gestión de múltiples posiciones y símbolos en muy poco tiempo debido al aumento de la volatilidad. En este artículo, abordamos los retos que plantea el trading con múltiples símbolos mediante la integración de esta función en nuestro EA «News Headline». En este artículo veremos cómo el trading algorítmico con MQL5 hace que el trading con múltiples símbolos sea más eficiente y eficaz.
Los componentes de Vista y Controlador para tablas en el paradigma MVC de MQL5: elementos redimensionables Los componentes de Vista y Controlador para tablas en el paradigma MVC de MQL5: elementos redimensionables
En este artículo, añadiremos la funcionalidad de cambiar el tamaño de los controles arrastrando los bordes y las esquinas del elemento con el ratón.