Kaique Mitsuo Silva Yamamoto
Mercado financeiroAutomação de EstratégiasMetaTrader 5 / MQL5

MQL5 — Gestão de Risco em Código

Como implementar position sizing, risco por trade, stops baseados em ATR, drawdown máximo e proteção de capital em Expert Advisors MQL5.

MQL5 — Gestão de Risco em Código

Gestão de risco é o módulo mais crítico de qualquer EA. Um sistema sem controle de risco pode ser lucrativo em 95% do tempo e destruir a conta nos 5% restantes. Esta página cobre os padrões profissionais de implementação em MQL5.

Fundamentos da linguagem: MQL5 — Fundamentos


Position Sizing — Tamanho da Posição

Risco fixo por trade (% do equity)

O método mais robusto: arriscar um percentual fixo do equity por operação.

//+------------------------------------------------------------------+
// Calcula o lote com base em risco % do equity e stop em pontos
//+------------------------------------------------------------------+
double CalculateLotByRisk(double riskPercent, double stopLossPoints)
{
    double equity      = AccountInfoDouble(ACCOUNT_EQUITY);
    double riskAmount  = equity * riskPercent / 100.0;

    // Valor monetário de 1 ponto para 1 lote
    double tickValue   = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
    double tickSize    = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
    double pointValue  = tickValue / tickSize * _Point;

    // Lote bruto
    double rawLot = riskAmount / (stopLossPoints * pointValue);

    // Normalizar para limites do símbolo
    double minLot  = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
    double maxLot  = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
    double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);

    double lot = MathFloor(rawLot / lotStep) * lotStep;
    lot = MathMax(lot, minLot);
    lot = MathMin(lot, maxLot);

    return NormalizeDouble(lot, 2);
}

// Uso: arriscando 1% com stop de 50 pontos
double lots = CalculateLotByRisk(1.0, 50.0);

Stop Loss baseado em ATR

Stops fixos em pontos ignoram a volatilidade atual do ativo. Usar ATR garante que o stop seja proporcional ao movimento real do mercado.

input int    ATRPeriod    = 14;
input double ATRMultiply  = 1.5;  // Stop = 1.5x ATR
input double RiskPercent  = 1.0;

int atrHandle;

int OnInit()
{
    atrHandle = iATR(_Symbol, PERIOD_CURRENT, ATRPeriod);
    return(INIT_SUCCEEDED);
}

double GetATRStop()
{
    double atr[];
    ArraySetAsSeries(atr, true);
    CopyBuffer(atrHandle, 0, 0, 3, atr);
    return atr[1] * ATRMultiply;  // ATR da barra fechada × multiplicador
}

void OnTick()
{
    double stopDistance = GetATRStop();
    double lots         = CalculateLotByRisk(RiskPercent, stopDistance / _Point);

    double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
    double sl  = ask - stopDistance;
    double tp  = ask + stopDistance * 2.0;  // RR 1:2

    // ... enviar ordem com sl, tp e lots calculados
}

Classe CRiskManager Completa

class CRiskManager
{
private:
    double m_riskPercent;
    double m_maxDailyLossPercent;
    double m_maxDrawdownPercent;
    double m_dayStartEquity;
    double m_peakEquity;
    int    m_atrHandle;

public:
    CRiskManager(double riskPct, double maxDailyLossPct, double maxDDPct, int atrPeriod)
    {
        m_riskPercent        = riskPct;
        m_maxDailyLossPercent = maxDailyLossPct;
        m_maxDrawdownPercent  = maxDDPct;
        m_dayStartEquity     = AccountInfoDouble(ACCOUNT_EQUITY);
        m_peakEquity         = m_dayStartEquity;
        m_atrHandle          = iATR(_Symbol, PERIOD_CURRENT, atrPeriod);
    }

    ~CRiskManager() { IndicatorRelease(m_atrHandle); }

    // Verifica se o trading está permitido hoje
    bool IsTradingAllowed()
    {
        double equity   = AccountInfoDouble(ACCOUNT_EQUITY);
        double balance  = AccountInfoDouble(ACCOUNT_BALANCE);

        // Atualiza pico de equity
        if(equity > m_peakEquity) m_peakEquity = equity;

        // Checar perda diária
        double dailyLoss = (m_dayStartEquity - equity) / m_dayStartEquity * 100.0;
        if(dailyLoss >= m_maxDailyLossPercent)
        {
            Comment("TRADING BLOQUEADO: perda diária de ", DoubleToString(dailyLoss, 2), "%");
            return false;
        }

        // Checar drawdown máximo (pico → atual)
        double drawdown = (m_peakEquity - equity) / m_peakEquity * 100.0;
        if(drawdown >= m_maxDrawdownPercent)
        {
            Comment("TRADING BLOQUEADO: drawdown de ", DoubleToString(drawdown, 2), "%");
            return false;
        }

        return true;
    }

    // Resetar equity do dia (chamar no início de cada novo dia)
    void OnNewDay()
    {
        m_dayStartEquity = AccountInfoDouble(ACCOUNT_EQUITY);
    }

    // Calcular lote baseado em ATR
    double GetLotSize(double atrMultiplier = 1.5)
    {
        double atr[];
        ArraySetAsSeries(atr, true);
        CopyBuffer(m_atrHandle, 0, 0, 3, atr);
        double stopDist = atr[1] * atrMultiplier;
        return CalculateLotByRisk(m_riskPercent, stopDist / _Point);
    }

    double GetATRValue(double multiplier = 1.0)
    {
        double atr[];
        ArraySetAsSeries(atr, true);
        CopyBuffer(m_atrHandle, 0, 0, 3, atr);
        return atr[1] * multiplier;
    }
};

Proteção de Drawdown Diário

Essencial para contas prop (funded accounts) e para consistência profissional.

input double MaxDailyLoss   = 3.0;  // % máximo de perda no dia
input double MaxDrawdown     = 8.0;  // % máximo de drawdown total

double g_dayStartBalance;
datetime g_lastDayCheck;

void CheckDailyReset()
{
    MqlDateTime now;
    TimeToStruct(TimeCurrent(), now);

    MqlDateTime last;
    TimeToStruct(g_lastDayCheck, last);

    if(now.day != last.day)
    {
        g_dayStartBalance = AccountInfoDouble(ACCOUNT_BALANCE);
        g_lastDayCheck    = TimeCurrent();
        Print("Novo dia: balance inicial = ", g_dayStartBalance);
    }
}

bool IsDailyLimitReached()
{
    double balance    = AccountInfoDouble(ACCOUNT_BALANCE);
    double dailyLoss  = (g_dayStartBalance - balance) / g_dayStartBalance * 100.0;

    if(dailyLoss >= MaxDailyLoss)
    {
        Print("Limite diário atingido: ", DoubleToString(dailyLoss, 2), "%");
        return true;
    }
    return false;
}

void OnTick()
{
    CheckDailyReset();

    if(IsDailyLimitReached()) return;  // para o trading do dia

    // ... lógica normal ...
}

Break Even — Mover SL para Zero a Zero

Após o trade andar X pontos a favor, mover o stop para o preço de entrada (sem perda).

input double BreakEvenTrigger = 30.0;  // pontos para ativar BE
input double BreakEvenOffset  = 5.0;   // pontos acima do entry no SL

void ManageBreakEven()
{
    for(int i = PositionsTotal() - 1; i >= 0; i--)
    {
        ulong ticket = PositionGetTicket(i);
        if(!PositionSelectByTicket(ticket)) continue;
        if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;

        double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
        double currentSL = PositionGetDouble(POSITION_SL);
        double currentTP = PositionGetDouble(POSITION_TP);
        ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);

        double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
        double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);

        if(type == POSITION_TYPE_BUY)
        {
            double profit = bid - openPrice;
            double newSL  = openPrice + BreakEvenOffset * _Point;

            if(profit >= BreakEvenTrigger * _Point && currentSL < newSL)
            {
                CTrade trade;
                trade.PositionModify(ticket, newSL, currentTP);
                Print("Break Even ativado: SL movido para ", newSL);
            }
        }
        else if(type == POSITION_TYPE_SELL)
        {
            double profit = openPrice - ask;
            double newSL  = openPrice - BreakEvenOffset * _Point;

            if(profit >= BreakEvenTrigger * _Point && currentSL > newSL)
            {
                CTrade trade;
                trade.PositionModify(ticket, newSL, currentTP);
                Print("Break Even ativado: SL movido para ", newSL);
            }
        }
    }
}

Validações antes de abrir uma posição

bool CanOpenTrade()
{
    // Conta permitida
    if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
    {
        Print("Terminal não permite trading");
        return false;
    }

    if(!MQLInfoInteger(MQL_TRADE_ALLOWED))
    {
        Print("EA sem permissão de trading (habilite nas configurações)");
        return false;
    }

    // Verificar spread máximo
    double spread = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD) * _Point;
    if(spread > MaxAllowedSpread * _Point)
    {
        Print("Spread muito alto: ", spread);
        return false;
    }

    // Não abrir perto de fechamento de sessão
    MqlDateTime dt;
    TimeToStruct(TimeCurrent(), dt);
    if(dt.hour == 17 && dt.min >= 55)
    {
        Print("Muito perto do fechamento da sessão");
        return false;
    }

    // Posição já aberta (modo netting)
    if(PositionsTotal() > 0) return false;

    return true;
}

Métricas de risco para journaling

void LogTradeResult(ulong ticket)
{
    if(!HistoryDealSelect(ticket)) return;

    double profit    = HistoryDealGetDouble(ticket, DEAL_PROFIT);
    double volume    = HistoryDealGetDouble(ticket, DEAL_VOLUME);
    double price     = HistoryDealGetDouble(ticket, DEAL_PRICE);
    datetime dealTime = (datetime)HistoryDealGetInteger(ticket, DEAL_TIME);

    double equity    = AccountInfoDouble(ACCOUNT_EQUITY);
    double balance   = AccountInfoDouble(ACCOUNT_BALANCE);

    int file = FileOpen("trade_journal.csv", FILE_WRITE | FILE_READ | FILE_CSV | FILE_ANSI);
    FileSeek(file, 0, SEEK_END);
    FileWrite(file,
        TimeToString(dealTime),
        _Symbol,
        DoubleToString(volume, 2),
        DoubleToString(price, _Digits),
        DoubleToString(profit, 2),
        DoubleToString(balance, 2),
        DoubleToString(equity, 2));
    FileClose(file);
}

Referências

Referências externas

On this page