English Русский 中文 Español Deutsch 日本語
preview
Desenvolvimento de um EA dinâmico para múltiplos pares (Parte 4): Ajuste do risco com base na volatilidade

Desenvolvimento de um EA dinâmico para múltiplos pares (Parte 4): Ajuste do risco com base na volatilidade

MetaTrader 5 — Exemplos |
11 3
Hlomohang John Borotho
Hlomohang John Borotho

Introdução

Um dos principais desafios da negociação de múltiplos pares é a inconsistência dos resultados decorrente das diferenças de volatilidade entre os pares de moedas. Como vimos no artigo anterior, uma estratégia que apresenta bons resultados no EURUSD pode ter um desempenho inferior ou envolver riscos excessivos no GBPJPY, devido às diferenças de volatilidade entre esses pares. O uso de lotes fixos ou níveis estáticos de stop-loss pode resultar em posições excessivamente grandes em mercados voláteis ou em oportunidades perdidas nos mercados mais estáveis. Essa falta de adaptação costuma provocar uma distribuição desigual do risco, aumentar os drawdowns e tornar os resultados menos previsíveis, especialmente durante a divulgação de notícias importantes ou diante de mudanças bruscas nas condições de mercado.

Para resolver esse problema, incorporamos ao EA um mecanismo de ajuste do risco com base na volatilidade. Com o uso de indicadores como o Average True Range (ATR), ou Amplitude Verdadeira Média, e do dimensionamento dinâmico das posições conforme o risco, o EA adapta automaticamente seus parâmetros de negociação às condições atuais do mercado. Dessa forma, cada posição é dimensionada proporcionalmente à volatilidade do respectivo instrumento, permitindo uma gestão de risco mais consistente e melhorando o desempenho do EA em todos os pares negociados.



Plano de desenvolvimento do EA

Lógica de negociação:

1. Processador de múltiplos símbolos:

  • Parser da lista de símbolos
  • Acompanhamento dos dados do símbolo anterior
  • Gestão simultânea das posições


2. Condição de entrada:

3. Níveis de risco com base na volatilidade:

4. Dimensionamento da posição:

Risk Amount = Account Equity * Risk %
Position Size = Risk Amount / (SL Distance * Point Value)

5. Identificação do nível-alvo do V-stop:

[  Current Price  ]
        |
        v
[ Resistance Area ]  <-- Previous V-Stop Upper (TP for sells)
        |
        v
[  Current Price  ]
        |
        v
[  Support Area   ]  <-- Previous V-Stop Lower (TP for buys)

6. Linha do tempo do ciclo de vida da operação:



Vamos começar

//+------------------------------------------------------------------+
//|                               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 costume, começamos importando a biblioteca de negociação essencial para que o EA possa executar ordens e gerenciar posições.

//--- 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

Neste bloco, definimos os parâmetros de entrada que controlam o funcionamento do EA com múltiplos símbolos. O parâmetro Symbols permite ao trader especificar quais instrumentos serão negociados, enquanto os parâmetros do RSI (RSI_Period, RSI_Overbought e RSI_Oversold) são utilizados para identificar possíveis pontos de entrada com base nas condições de sobrecompra e sobrevenda. A volatilidade é considerada por meio do indicador Average True Range (ATR), com período e multiplicador configuráveis para ajustar dinamicamente a distância do stop-loss.

O EA ajusta sua exposição ao risco conforme o nível de volatilidade, aplicando percentuais de risco diferentes em condições de volatilidade alta, moderada ou baixa. Entre os parâmetros adicionais estão Min_Bars, que garante uma quantidade suficiente de dados históricos; In_Lot, que define o tamanho padrão do lote; e StopLoss e TakeProfit, que estabelecem valores fixos utilizados como alternativa quando os níveis dinâmicos não são aplicados.

//--- 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;

Nesta seção, declaramos as variáveis globais utilizadas pelo EA. Symb_List[] é um array que armazena a lista de símbolos a serem negociados, enquanto Num_symbs contém o número total desses símbolos. Os arrays ATR_Handles[] e RSI_Handles[] armazenam os handles dos indicadores ATR e RSI, permitindo que o EA processe vários símbolos simultaneamente.

Prev_ATR[] e Prev_RSI[] armazenam os valores mais recentes desses indicadores para cada símbolo, utilizados na lógica de tomada de decisão. LastBarTime[] registra o horário da última barra processada de cada símbolo, evitando a execução de operações duplicadas na mesma vela. Por fim, o objeto trade da classe CTrade oferece acesso às funções de negociação integradas ao MQL5, permitindo executar ordens e gerenciar posições.

//+------------------------------------------------------------------+
//| 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;
}

A função OnInit() é responsável pela inicialização do EA quando ele é carregado no gráfico. Primeiro, a string do parâmetro Symbols é dividida usando a vírgula como delimitador, e os símbolos resultantes são armazenados no array symb_List. Em seguida, é calculado o número total de símbolos que serão gerenciados. A função então redimensiona e inicializa vários arrays globais, incluindo os que armazenam os handles dos indicadores ATR e RSI, seus valores anteriores e os horários das últimas velas processadas. Dessa forma, cada símbolo dispõe de estruturas próprias para armazenar seus dados e acompanhar suas atualizações. Os valores iniciais são definidos para evitar comportamentos indefinidos durante o primeiro ciclo de execução do EA.

Em seguida, a função percorre todos os símbolos e cria os handles dos indicadores ATR e RSI por meio das funções iATR e iRSI, respectivamente. Esses handles são necessários para obter os valores atualizados dos indicadores durante a negociação. Se a criação de qualquer um deles falhar, ou seja, se retornar INVALID_HANDLE, uma mensagem de erro será exibida e a inicialização será interrompida com o retorno de INIT_FAILED. Se todos os indicadores forem inicializados corretamente, a função retornará INIT_SUCCEEDED, indicando que o EA está pronto para começar a operar.

//+------------------------------------------------------------------+
//| 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]);
    }
}

A função OnDeinit() é chamada quando o EA é removido do gráfico ou reinicializado. Sua principal finalidade é liberar os recursos associados aos handles dos indicadores de cada símbolo. Para isso, a função chama IndicatorRelease() para os handles do ATR e do RSI, desde que sejam válidos. Isso permite liberar corretamente a memória utilizada e evitar vazamentos de memória ou consumo desnecessário de recursos. Essa medida contribui para a estabilidade da plataforma, especialmente quando vários EAs ou indicadores estão em execução simultaneamente.

//+------------------------------------------------------------------+
//| 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]);
    }
}

A função OnTick() processa o evento de novo tick do símbolo do gráfico e percorre todos os símbolos da lista de negociação para realizar a análise em tempo real e gerenciar as operações. Primeiro, verifica se uma nova vela foi formada para o símbolo, comparando a marca de tempo da vela atual com a última registrada. Se houver uma nova vela, a função obtém os valores mais recentes do ATR e do RSI por meio de CopyBuffer() e os armazena nos arrays globais para utilização nas decisões de negociação. Os preços atuais Ask e Bid também são obtidos por meio de SymbolInfoTick(), permitindo determinar com precisão os níveis de entrada e saída.

Em seguida, a função utiliza CheckExistingPositions() para verificar se há posições de compra ou venda abertas para o símbolo atual. Depois, calcula o nível de risco adequado à volatilidade do símbolo por meio de CalculateRiskLevel() e determina os níveis mais recentes do V-Stop, utilizados nos critérios de entrada e no ajuste dos stops móveis. Com base nessas informações, o EA aplica suas regras de entrada: uma operação de venda é acionada quando o RSI indica sobrecompra e o preço cai abaixo do V-Stop superior; já uma operação de compra é acionada quando o RSI indica sobrevenda e o preço rompe o V-Stop inferior. Em ambos os casos, os níveis dinâmicos de stop-loss e take-profit são calculados com base no ATR e no V-Stop, enquanto o tamanho da posição é ajustado conforme o nível de risco determinado.

Por fim, independentemente da abertura de uma nova operação, o EA chama UpdateTrailingStops() para gerenciar as posições já abertas, caso existam. Essa função ajusta os níveis de stop-loss de acordo com os dados mais recentes de volatilidade, ajudando a proteger os lucros acumulados e limitar as perdas conforme as condições de mercado mudam. Com essa abordagem dinâmica, a estratégia consegue acompanhar as mudanças do mercado e adaptar suas decisões em tempo real para os diferentes símbolos negociados.

//+------------------------------------------------------------------+
//| 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);
}

A função ExecuteTrade() é responsável pela execução das operações de acordo com o tipo de ordem especificado (compra ou venda) e o símbolo correspondente. Primeiro, determina o preço de entrada adequado, utilizando o preço Ask ou Bid atual, conforme a direção da operação. Em seguida, calcula os níveis de stop-loss e take-profit, convertendo as distâncias em pontos definidas pelo usuário em variações de preço com base no tamanho do ponto do símbolo. Os níveis de SL e TP são então definidos em relação ao preço de entrada, conforme a direção da operação, e o objeto CTrade é utilizado para abrir a posição com o volume em lotes e os parâmetros de preço 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];
}

A função CalculateVStop() calcula o nível dinâmico do V-Stop para um determinado símbolo e deslocamento de barra, ajustando-o à volatilidade. Para isso, obtém o valor do ATR e os preços de máxima, mínima e fechamento da vela especificada, utilizando CopyBuffer() e as funções de séries de preços. Em seguida, calcula o preço típico pela média dos preços de máxima, mínima e fechamento. Conforme o nível desejado seja o V-Stop superior ou inferior, indicado pelo sinalizador isUpper, a função soma ao preço típico ou subtrai dele um múltiplo do ATR. O resultado é um nível de suporte ou resistência ajustado à volatilidade, utilizado tanto para filtrar as entradas quanto para definir os ajustes do trailing stop.

//+------------------------------------------------------------------+
//| 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;
}

A função GetProfitTarget() determina o nível dinâmico de take-profit com base nos valores históricos do V-Stop. Ela examina até 50 barras anteriores em busca do nível válido de V-Stop mais próximo: o limite inferior para operações de compra, que atua como suporte, ou o limite superior para operações de venda, que atua como resistência. Se encontrar um nível válido, a função o utiliza como alvo de lucro. Caso não encontre nenhum nível válido no intervalo analisado, utiliza um valor padrão calculado a partir de uma distância equivalente a cinco vezes o ATR em relação ao preço Ask ou Bid atual. Assim, o EA consegue definir um nível de take-profit mesmo quando não há dados históricos válidos do 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;
}

A função CalculateRiskLevel() ajusta dinamicamente a exposição ao risco do EA de acordo com a volatilidade atual do mercado. Para isso, obtém os 20 valores mais recentes do ATR para o símbolo e calcula a média, que serve como referência. Em seguida, compara o ATR mais recente com essa média: se o valor for significativamente maior, ultrapassando 1,5 vez a média, o mercado é classificado como de alta volatilidade, e a função aplica um percentual de risco mais elevado. Se o valor ficar abaixo de 0,5 vez a média, aplica um percentual de risco menor. Nos demais casos, utiliza um nível de risco moderado. Isso permite ajustar dinamicamente o tamanho das posições e adaptar o funcionamento do EA às condições de mercado em tempo 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;
}

A função CalculatePositionSize() determina o volume adequado da operação, em lotes, com base no percentual de risco definido e na distância até o stop-loss. Primeiro, obtém os parâmetros necessários para o cálculo, como o tamanho do tick, o valor do tick e o tamanho do ponto do símbolo. Com base no patrimônio líquido da conta e no percentual de risco selecionado, calcula o valor monetário máximo que poderá ser arriscado na operação.

Em seguida, calcula o risco monetário por lote a partir da distância do stop-loss em pontos, convertendo essa distância em um valor monetário com base nas propriedades do tick. Depois, divide o valor total destinado ao risco pelo risco calculado por lote para determinar o volume da posição. Por fim, ajusta esse volume aos limites mínimo e máximo e ao incremento de lote permitidos pela corretora, garantindo que o tamanho da posição seja válido e esteja corretamente arredondado para a execução.

//+------------------------------------------------------------------+
//| 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);
        }
    }
}

A função UpdateTrailingStops() gerencia as posições abertas, ajustando seus níveis de stop-loss conforme a volatilidade atual do mercado. Para cada posição associada ao símbolo analisado, calcula um novo nível de trailing stop com base no ATR mais recente, multiplicado por um fator predefinido. Nas posições de compra, o stop-loss é elevado quando o novo nível está acima do stop-loss atual e do preço de abertura, ajudando a proteger o lucro assim que a operação entra em território positivo. Nas posições de venda, o stop-loss é deslocado para baixo quando condições equivalentes são atendidas. Esse ajuste dinâmico do trailing stop ajuda a proteger os lucros sem restringir excessivamente as oscilações de preço em mercados voláteis.

//+------------------------------------------------------------------+
//| 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;
}

A função ModifySL() atualiza o nível de stop-loss de uma posição existente, identificada pelo seu ticket. Para isso, cria uma requisição de negociação com a ação de modificação do stop-loss (TRADE_ACTION_SLTP), define o novo preço do stop-loss e estabelece o desvio máximo de preço permitido. Em seguida, envia a requisição por meio de OrderSend(). Se a modificação falhar, uma mensagem será exibida com o código de erro correspondente. A função retorna true quando a modificação é concluída com sucesso e false quando não é possível atualizar o stop-loss, permitindo que o EA trate adequadamente eventuais falhas.

//+------------------------------------------------------------------+
//| 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;
        }
    }
}

Neste bloco de código, a função CheckExistingPositions() percorre todas as posições abertas para identificar se existem posições de compra ou venda ativas para o símbolo especificado. Inicialmente, define os sinalizadores de saída hasLong e hasShort como false e, em seguida, examina cada posição. Quando encontra uma posição correspondente ao símbolo, atribui true a hasLong se for uma posição de compra ou a hasShort se for uma posição de venda.

//+------------------------------------------------------------------+
//| 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 fim, a função GetSymbolIndex() permite obter o índice de um determinado símbolo no array symb_List. Ela percorre os símbolos armazenados e retorna o índice correspondente quando encontra o símbolo procurado. Se o símbolo não estiver na lista, retorna -1. Essa função é essencial para manter a correspondência entre os símbolos e seus respectivos dados, como os handles dos indicadores e os valores armazenados nos diferentes arrays utilizados pelo EA.

Resultados do backtest

O backtest foi realizado no timeframe de 1 hora, considerando um período de aproximadamente dois meses, de 11 de junho de 2025 a 1º de agosto de 2025, com os seguintes parâmetros de entrada:

  • Período do RSI = 60
  • Nível de sobrecompra do RSI = 70
  • Nível de sobrevenda do RSI = 45
  • Período do ATR para volatilidade = 62
  • Multiplicador do ATR para SL = 3.2
  • % de risco em alta volatilidade = 0.19
  • % de risco em alta volatilidade = 0.064
  • % de risco em alta volatilidade = 0.0335
  • Número mínimo de barras = 50
  • Tamanho padrão do lote = 0.01
  • SL em pontos = 610
  • TP em pontos = 980



Conclusão

Em resumo, desenvolvemos um EA dinâmico para múltiplos pares, capaz de se adaptar às mudanças nas condições de mercado por meio da gestão de risco baseada na volatilidade. O sistema processa vários símbolos simultaneamente e utiliza indicadores técnicos, como ATR e RSI, para identificar oportunidades de negociação. O EA calcula os níveis de V-Stop para filtrar as entradas e gerenciar as saídas, enquanto ajusta dinamicamente o tamanho dos lotes e os níveis de stop-loss conforme a volatilidade atual. Sua estrutura modular reúne funções responsáveis pela execução das operações, pelo ajuste dos trailing stops e pela classificação do risco em três níveis, alto, moderado e baixo, conforme a volatilidade. Dessa forma, os parâmetros de cada operação são ajustados ao comportamento atual do respectivo símbolo.

Essa abordagem oferece aos traders uma ferramenta robusta e adaptável, capaz de manter um desempenho mais consistente em diferentes pares de moedas. Ao ajustar a exposição ao risco e os parâmetros de negociação conforme a volatilidade, o EA reduz a exposição excessiva nos mercados mais voláteis e evita a perda de eficiência nos períodos de menor volatilidade.

Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/18165

Arquivos anexados |
Últimos Comentários | Ir para discussão (3)
CapeCoddah
CapeCoddah | 11 ago. 2025 em 11:29

Olá,


Um artigo muito interessante. Acabei de dar uma olhada rápida nele e decidi que farei uma análise detalhada, pois é semelhante a um EA que estou desenvolvendo.

Minha primeira pergunta é: há algum motivo para todas as operações terem sido de compra e nenhuma de venda? Além disso, você planeja publicar mais artigos sobre esse assunto?

Com base na minha breve análise do seu código, gostaria de sugerir que GetSymbolIndex e outras variáveis, como point, sejam movidas para o início do loop de símbolos e atribuídas a variáveis, a fim de melhorar a eficiência ao reduzir a redundância. À medida que mais símbolos forem adicionados à lista de pares, será gasto exponencialmente mais tempo na execução de código redundante. Você também poderia considerar adicionar um índice “PairCode” às suas matrizes para que elas possam ser acessadas diretamente.


CapeCoddah

Hlomohang John Borotho
Hlomohang John Borotho | 12 ago. 2025 em 15:52
CapeCoddah exponencialmente mais tempo na execução de código redundante. Você também poderia considerar adicionar um índice “PairCode” às suas matrizes para que elas possam ser acessadas diretamente.


CapeCoddah

Ei,

O motivo de todas as operações serem de compra depende das configurações de entrada que você tenha inserido; e, se você usou as mesmas configurações que eu usei no backtest, o motivo é que eu otimizei o EA. Artigos adicionais sobre esse assunto poderão ser publicados no momento oportuno.

Obrigado pela sugestão, vou levar isso em consideração.

JohnHlomohang
Ademir Temoteo De Vasco
Ademir Temoteo De Vasco | 6 out. 2025 em 01:28

Olá... boa noite!!!

Em primeiro lugar, gostaria de parabenizá-los pelo excelente trabalho.

Estou realizando testes e estou muito satisfeito com os resultados, mas confesso que estou usando apenas uma moeda para realizar o teste, já que as configurações são diferentes para cada ativo.

Não entendo como funciona essa lógica de colocar várias moedas ao mesmo tempo.

Se você pudesse me explicar, ficaria muito grato.

Tradução automática aplicada pelo moderador. No fórum em inglês, por favor, escreva em inglês.

Caminhe em novos trilhos: Personalize indicadores no MQL5 Caminhe em novos trilhos: Personalize indicadores no MQL5
Vou agora listar todas as possibilidades novas e recursos do novo terminal e linguagem. Elas são várias, e algumas novidades valem a discussão em um artigo separado. Além disso, não há códigos aqui escritos com programação orientada ao objeto, é um tópico muito importante para ser simplesmente mencionado em um contexto como vantagens adicionais para os desenvolvedores. Neste artigo vamos considerar os indicadores, sua estrutura, desenho, tipos e seus detalhes de programação em comparação com o MQL4. Espero que este artigo seja útil tanto para desenvolvedores iniciantes quanto para experientes, talvez alguns deles encontrem algo novo.
Analizado  Como criar um sistema de trading lucrativo (Parte 2): As nuances do dimensionamento de posições Analizado Como criar um sistema de trading lucrativo (Parte 2): As nuances do dimensionamento de posições
Mesmo com um sistema de trading de expectativa matemática positiva, o tamanho das posições pode influenciar o sucesso ou o fracasso. Esse é um aspecto fundamental da gestão de risco, pois permite transformar uma vantagem estatística em resultados concretos sem comprometer a proteção do capital.
Está chegando o novo MetaTrader 5 e MQL5 Está chegando o novo MetaTrader 5 e MQL5
Esta é apenas uma breve resenha do MetaTrader 5. Eu não posso descrever todos os novos recursos do sistema por um período tão curto de tempo - os testes começaram em 09.09.2009. Esta é uma data simbólica, e tenho certeza que será um número de sorte. Alguns dias passaram-se desde que eu obtive a versão beta do terminal MetaTrader 5 e MQL5. Eu ainda não consegui testar todos os seus recursos, mas já estou impressionado.
Desenvolvimento de EAs auto-otimizáveis em MQL5 (Parte 8): Análise de múltiplas estratégias (3) - Política de votação ponderada Desenvolvimento de EAs auto-otimizáveis em MQL5 (Parte 8): Análise de múltiplas estratégias (3) - Política de votação ponderada
Neste artigo, analisamos a dificuldade de determinar o número ideal de estratégias em um ensemble e como o otimizador genético do MetaTrader 5 pode facilitar essa tarefa. Também utilizamos a rede MQL5 Cloud como um recurso essencial para acelerar o backtesting e a otimização. De modo geral, esta discussão estabelece as bases para o desenvolvimento de modelos estatísticos capazes de avaliar e aprimorar estratégias de negociação com base nos resultados obtidos pelo nosso ensemble inicial.