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

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.

MQL5 — Ordens Avançadas e Gerenciamento de Posição

Saber abrir uma ordem é só o começo. O diferencial de um EA profissional está em gerenciar posições abertas — trailing stops dinâmicos, fechamento parcial, cancelamento inteligente de pendentes e proteção contra condições adversas de mercado.

Fundamentos: MQL5 — Fundamentos | Gestão de risco: MQL5 — Gestão de Risco


Classe CTrade — Referência Rápida

A biblioteca <Trade/Trade.mqh> encapsula todas as operações de trading.

#include <Trade/Trade.mqh>

CTrade trade;

// Configurações recomendadas
trade.SetExpertMagicNumber(12345);       // identificar ordens do EA
trade.SetSlippage(10);                   // slippage máximo em pontos
trade.SetTypeFilling(ORDER_FILLING_FOK); // Fill or Kill (alternativa: IOC, RETURN)
trade.SetDeviationInPoints(10);          // desvio máximo de preço

// Abertura de ordens a mercado
trade.Buy(lots, _Symbol, ask, sl, tp, "comentário");
trade.Sell(lots, _Symbol, bid, sl, tp, "comentário");

// Ordens pendentes
trade.BuyLimit(lots, price, _Symbol, sl, tp, 0, 0, "comentário");
trade.SellLimit(lots, price, _Symbol, sl, tp, 0, 0, "comentário");
trade.BuyStop(lots, price, _Symbol, sl, tp, 0, 0, "comentário");
trade.SellStop(lots, price, _Symbol, sl, tp, 0, 0, "comentário");

// Modificar posição
trade.PositionModify(ticket, newSL, newTP);

// Fechar posição
trade.PositionClose(ticket);
trade.PositionClosePartial(ticket, lotsToClose);

// Cancelar ordem pendente
trade.OrderDelete(ticket);

Trailing Stop

Trailing simples (n pontos atrás do preço)

input double TrailStart  = 30.0;  // pontos de lucro para ativar
input double TrailStep   = 20.0;  // distância do SL ao preço atual
input double TrailPoints = 5.0;   // mover SL apenas quando ganhar X pontos

void TrailingStop()
{
    CTrade trade;

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

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

        if(type == POSITION_TYPE_BUY)
        {
            double bid   = SymbolInfoDouble(_Symbol, SYMBOL_BID);
            double newSL = bid - TrailStep * _Point;

            // Ativar apenas após TrailStart pontos de lucro
            if(bid - openPrice < TrailStart * _Point) continue;

            // Mover apenas se o novo SL for melhor que o atual E ganho mínimo
            if(newSL > currentSL + TrailPoints * _Point)
                trade.PositionModify(ticket, newSL, tp);
        }
        else if(type == POSITION_TYPE_SELL)
        {
            double ask   = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
            double newSL = ask + TrailStep * _Point;

            if(openPrice - ask < TrailStart * _Point) continue;

            if(newSL < currentSL - TrailPoints * _Point)
                trade.PositionModify(ticket, newSL, tp);
        }
    }
}

Trailing baseado em ATR

O trailing dinâmico com ATR se adapta à volatilidade atual — não persegue o preço de perto em mercados voláteis.

int atrHandle;

void TrailingStopATR(double atrMultiplier = 2.0)
{
    double atr[];
    ArraySetAsSeries(atr, true);
    CopyBuffer(atrHandle, 0, 0, 3, atr);
    double atrDist = atr[1] * atrMultiplier;

    CTrade trade;

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

        double sl   = PositionGetDouble(POSITION_SL);
        double tp   = PositionGetDouble(POSITION_TP);
        ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);

        if(type == POSITION_TYPE_BUY)
        {
            double bid   = SymbolInfoDouble(_Symbol, SYMBOL_BID);
            double newSL = NormalizeDouble(bid - atrDist, _Digits);
            if(newSL > sl) trade.PositionModify(ticket, newSL, tp);
        }
        else
        {
            double ask   = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
            double newSL = NormalizeDouble(ask + atrDist, _Digits);
            if(newSL < sl || sl == 0) trade.PositionModify(ticket, newSL, tp);
        }
    }
}

Trailing por estrutura (swing high/low)

void TrailingByStructure(int lookback = 20)
{
    MqlRates rates[];
    ArraySetAsSeries(rates, true);
    CopyRates(_Symbol, PERIOD_CURRENT, 0, lookback + 1, rates);

    double swingLow  = rates[0].low;
    double swingHigh = rates[0].high;

    for(int i = 1; i <= lookback; i++)
    {
        if(rates[i].low  < swingLow)  swingLow  = rates[i].low;
        if(rates[i].high > swingHigh) swingHigh = rates[i].high;
    }

    CTrade trade;

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

        double sl = PositionGetDouble(POSITION_SL);
        double tp = PositionGetDouble(POSITION_TP);
        ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);

        if(type == POSITION_TYPE_BUY && swingLow > sl)
            trade.PositionModify(ticket, swingLow - _Point, tp);
        else if(type == POSITION_TYPE_SELL && (swingHigh < sl || sl == 0))
            trade.PositionModify(ticket, swingHigh + _Point, tp);
    }
}

Fechamento Parcial (Partial Take Profit)

Técnica profissional: fechar parte da posição no primeiro alvo e deixar o restante rodar com trailing.

input double PartialClosePercent = 50.0;  // % do lote a fechar no 1º alvo
input double TP1_Points          = 30.0;  // 1º alvo em pontos
input double TP2_Points          = 80.0;  // 2º alvo (restante)

bool g_partialClosed = false;

void ManagePartialClose()
{
    if(g_partialClosed) return;

    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 volume    = PositionGetDouble(POSITION_VOLUME);
        ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);

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

        bool tp1Reached = (type == POSITION_TYPE_BUY  && bid >= openPrice + TP1_Points * _Point) ||
                          (type == POSITION_TYPE_SELL && ask <= openPrice - TP1_Points * _Point);

        if(tp1Reached)
        {
            double lotsToClose = NormalizeDouble(volume * PartialClosePercent / 100.0, 2);
            double minLot      = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);

            if(lotsToClose >= minLot)
            {
                CTrade trade;
                if(trade.PositionClosePartial(ticket, lotsToClose))
                {
                    g_partialClosed = true;
                    Print("Fechamento parcial: ", lotsToClose, " lotes no alvo 1");

                    // Mover SL para break even na posição restante
                    double newSL = (type == POSITION_TYPE_BUY)
                                   ? openPrice + 2 * _Point
                                   : openPrice - 2 * _Point;
                    trade.PositionModify(ticket, newSL, 0);
                }
            }
        }
    }
}

Gerenciamento de Ordens Pendentes

Cancelar ordens antigas

input int MaxOrderAgeMinutes = 60;  // cancelar pendentes com mais de X minutos

void CancelOldPendingOrders()
{
    CTrade trade;
    datetime cutoff = TimeCurrent() - MaxOrderAgeMinutes * 60;

    for(int i = OrdersTotal() - 1; i >= 0; i--)
    {
        ulong ticket = OrderGetTicket(i);
        if(!OrderSelect(ticket)) continue;
        if(OrderGetString(ORDER_SYMBOL) != _Symbol) continue;

        datetime orderTime = (datetime)OrderGetInteger(ORDER_TIME_SETUP);
        if(orderTime < cutoff)
        {
            trade.OrderDelete(ticket);
            Print("Ordem pendente cancelada por tempo: ticket ", ticket);
        }
    }
}

Atualizar preço de ordem pendente (chasing)

void UpdatePendingOrderPrice(ulong ticket, double newPrice)
{
    if(!OrderSelect(ticket)) return;

    double sl = OrderGetDouble(ORDER_SL);
    double tp = OrderGetDouble(ORDER_TP);

    CTrade trade;
    trade.OrderModify(ticket, newPrice, sl, tp, 0, 0);
}

Iterar sobre posições com segurança

O loop deve ser de trás para frente (i--) para evitar índices inválidos ao fechar posições.

void CloseAllPositions()
{
    CTrade trade;

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

        trade.PositionClose(ticket);
    }
}

// Fechar apenas posições no lucro
void ClosePositionsInProfit()
{
    CTrade trade;

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

        double profit = PositionGetDouble(POSITION_PROFIT);
        if(profit > 0)
            trade.PositionClose(ticket);
    }
}

OnTradeTransaction — reagir a eventos de execução

void OnTradeTransaction(const MqlTradeTransaction &trans,
                        const MqlTradeRequest     &request,
                        const MqlTradeResult      &result)
{
    // Posição aberta
    if(trans.type == TRADE_TRANSACTION_DEAL_ADD)
    {
        ulong dealTicket = trans.deal;
        if(HistoryDealSelect(dealTicket))
        {
            ENUM_DEAL_ENTRY entry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(dealTicket, DEAL_ENTRY);
            if(entry == DEAL_ENTRY_IN)
                Print("Posição aberta: ticket ", dealTicket);
            else if(entry == DEAL_ENTRY_OUT)
                Print("Posição fechada: profit = ",
                      HistoryDealGetDouble(dealTicket, DEAL_PROFIT));
        }
    }

    // Ordem cancelada pelo servidor
    if(trans.type == TRADE_TRANSACTION_ORDER_DELETE)
    {
        Print("Ordem deletada: ticket ", trans.order);
    }
}

Padrão OCO — One Cancels Other

MT5 não tem OCO nativo, mas é possível implementar: se um lado for executado, cancela o outro.

ulong g_buyStopTicket  = 0;
ulong g_sellStopTicket = 0;

void PlaceOCO(double buyStopPrice, double sellStopPrice, double lots, double sl, double tp)
{
    CTrade trade;
    trade.BuyStop(lots,  buyStopPrice,  _Symbol, buyStopPrice  - sl, buyStopPrice  + tp);
    g_buyStopTicket  = trade.ResultOrder();

    trade.SellStop(lots, sellStopPrice, _Symbol, sellStopPrice + sl, sellStopPrice - tp);
    g_sellStopTicket = trade.ResultOrder();
}

void ManageOCO()
{
    // Se compra foi executada, cancela venda
    bool buyExecuted  = PositionSelectByTicket(g_buyStopTicket);
    bool sellExecuted = PositionSelectByTicket(g_sellStopTicket);

    if(buyExecuted  && OrderSelect(g_sellStopTicket))
        { CTrade t; t.OrderDelete(g_sellStopTicket); g_sellStopTicket = 0; }

    if(sellExecuted && OrderSelect(g_buyStopTicket))
        { CTrade t; t.OrderDelete(g_buyStopTicket); g_buyStopTicket = 0; }
}

Referências

Referências externas

On this page