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
- MQL5 — Fundamentos
- Ordens Avançadas em MQL5
- Otimização e Robustez
- Position Sizing — conceito
- Risk Management — conceito
Referências externas
MQL5 — Indicadores Customizados
Como criar indicadores personalizados em MQL5: SetIndexBuffer, estilos de plotagem (DRAW_LINE, DRAW_HISTOGRAM, DRAW_ARROW, DRAW_FILLING, DRAW_CANDLES), buffers, propriedades e exemplos completos.
MQL5 — Ordens Avançadas e Gerenciamento de Posição
Trailing stop, fechamento parcial, ordens pendentes, OCO, gestão de múltiplas posições e padrões avançados de execução em MQL5.