Tipos de Ordens em Trading Algorítmico: do Limit com Chasing às Ordens Virtuais

Um guia completo sobre tipos de ordens em trading algorítmico: ordens padrão de exchange, limit com chasing, ordens baseadas em tempo, ordens virtuais/sintéticas. Com exemplos de código e casos de uso reais.

Quando um iniciante abre o terminal de uma exchange, vê dois botões: "Comprar" e "Vender". Quando um algo trader abre seu código, vê vinte e sete tipos de ordem, três níveis de abstração e uma pilha de casos extremos que dão vontade de fechar o notebook e ir vender pepinos na feira. Mas os pepinos, infelizmente, não permitem rodar uma arbitragem de funding rate às 3:59 UTC — então vamos direto ao assunto.

Neste artigo, percorreremos todo o caminho desde as ordens básicas de exchange até construções sintéticas virtuais que existem apenas dentro do seu sistema e nunca aparecem no order book. Espere TypeScript, Python, alguma dor e um pouco de esclarecimento.


1. Ordens Padrão de Exchange: a Base que Você Não Pode Pular

Ordens padrão Classificação dos tipos de ordem padrão: de market a iceberg

Antes de construir algo complexo, precisamos garantir que entendemos corretamente os blocos básicos. É surpreendente quantas pessoas confundem stop-limit com stop-market, e depois se perguntam por que seu stop "não disparou" (spoiler: ele disparou, mas a ordem limit não foi executada devido ao slippage).

Ordem market

O tipo mais simples e, ao mesmo tempo, o mais perigoso. Você diz à exchange: "compre/venda agora, a qualquer preço disponível." A exchange retira liquidez do order book, começando pelo melhor preço. Se o volume no melhor nível não for suficiente — ela desliza para níveis piores.

Quando usar: saída emergencial de posição, execução de um sinal em que velocidade importa mais que preço.

Armadilhas: em um mercado com pouca liquidez, uma ordem market de 100 BTC pode mover o preço vários pontos percentuais. Backtests que modelam ordens market sem considerar o impacto são pura fantasia.

Ordem limit

Você especifica um preço exato. A ordem entra no order book e espera até que alguém concorde com seu preço. Se o preço de uma ordem limit de compra estiver acima do mercado atual — ela é executada imediatamente (como uma ordem market, mas com preço máximo garantido).

Ponto-chave: uma ordem limit não garante execução. O preço pode alcançar seu nível e reverter, deixando você na fila (mais sobre isso em nosso artigo sobre posição na fila).

Stop-market e Stop-limit

É aqui que começa a confusão. Ambos os tipos são ordens "adormecidas" que se ativam quando o preço de disparo (stop price) é atingido. Mas:

  • Stop-market: ao disparar, converte-se em ordem market. Garante execução, mas não o preço.
  • Stop-limit: ao disparar, converte-se em ordem limit. Garante o preço (não pior que o especificado), mas não a execução.

No volátil mercado cripto, um stop-limit pode "falhar" — o preço rompeu o stop, a ordem limit foi colocada, mas o mercado já passou voando. Você fica com uma ordem limit não executada e um prejuízo crescente. É exatamente por isso que o stop-market é mais usado para stop-loss.

Trailing stop

Um stop que "segue" o preço a uma distância definida. O preço sobe — o stop sobe junto. O preço cai — o stop permanece no lugar. Útil para proteger lucros em estratégias de tendência.

Suporte das exchanges: nem todas as exchanges oferecem trailing stop nativo. Algo traders frequentemente o implementam programaticamente — isso dá mais controle sobre os parâmetros (callback rate, preço de ativação, tamanho do passo).

Ordem iceberg

Uma ordem em que apenas uma fração do volume total é visível no order book. Você quer comprar 1.000 BTC, mas mostra apenas 10 no book. Quando os primeiros 10 são executados — os próximos 10 aparecem.

Por quê: para esconder suas verdadeiras intenções do mercado. Uma ordem grande no book sinaliza a todos que "alguém grande quer comprar/vender." Em resposta, algoritmos de HFT começam a fazer front-running, e o preço se afasta de você.

Ressalva: em muitas exchanges cripto, ordens iceberg não são suportadas ou são facilmente detectadas pelo padrão de volumes idênticos. Algoritmos avançados randomizam o tamanho da porção visível.

Parâmetros de time-in-force: GTC, GTD, IOC, FOK

Não são tipos de ordem separados, mas parâmetros de tempo de vida — quanto tempo uma ordem permanece ativa:

ParâmetroNome CompletoComportamento
GTCGood Till CancelledPermanece ativa até ser cancelada. O padrão default
GTDGood Till DatePermanece ativa até uma data/hora especificada
IOCImmediate or CancelExecuta imediatamente (total ou parcialmente), o restante é cancelado
FOKFill or KillExecuta apenas de forma total e imediata. Se impossível — é cancelada por completo

IOC vs FOK: a diferença é crítica. IOC pode ser executada parcialmente — você queria comprar 100 BTC, comprou 3, o resto foi cancelado. FOK é tudo ou nada.

Post-only (somente Maker)

Uma ordem que garante entrar no order book como maker e nunca é executada como taker. Se, no momento em que é colocada, o preço causaria execução imediata — a exchange a rejeita (ou ajusta o preço, dependendo da exchange).

Por quê: as taxas de maker geralmente são menores que as de taker (na Binance — 0,02% vs 0,04% para os níveis VIP). Para um market maker que coloca milhares de ordens por dia, a diferença de taxa é a diferença entre lucro e prejuízo.


2. TWAP e VWAP: Como as Instituições Escondem um Elefante no Order Book

Quando um hedge fund quer comprar uma posição de US$ 50 milhões, ele não coloca uma única ordem market. Ele usa algoritmos de execução — algoritmos que dividem uma ordem grande em muitas menores e as executam ao longo do tempo, minimizando o impacto no mercado.

TWAP (Time-Weighted Average Price)

A ideia é simples: dividir o volume total em partes iguais e executar em intervalos de tempo iguais.

import asyncio
from datetime import datetime, timedelta

class TWAPExecutor:
    """
    TWAP executor: splits a large order into equal parts
    and executes them at equal time intervals.
    """
    def __init__(self, exchange, symbol: str, side: str,
                 total_qty: float, duration_minutes: int, num_slices: int):
        self.exchange = exchange
        self.symbol = symbol
        self.side = side
        self.total_qty = total_qty
        self.slice_qty = total_qty / num_slices
        self.interval = (duration_minutes * 60) / num_slices
        self.num_slices = num_slices
        self.executed_qty = 0.0
        self.fills: list[dict] = []

    async def execute(self):
        for i in range(self.num_slices):
            remaining = self.total_qty - self.executed_qty
            qty = min(self.slice_qty, remaining)
            if qty <= 0:
                break

            try:
                order = await self.exchange.create_order(
                    symbol=self.symbol,
                    type="market",
                    side=self.side,
                    amount=qty,
                )
                self.executed_qty += float(order["filled"])
                self.fills.append(order)
                print(f"[TWAP] slice {i+1}/{self.num_slices}: "
                      f"filled {order['filled']} @ {order['average']}")
            except Exception as e:
                print(f"[TWAP] slice {i+1} failed: {e}")

            if i < self.num_slices - 1:
                await asyncio.sleep(self.interval)

        avg_price = (
            sum(f["cost"] for f in self.fills) /
            sum(f["filled"] for f in self.fills)
        ) if self.fills else 0
        print(f"[TWAP] done: {self.executed_qty}/{self.total_qty} "
              f"avg price: {avg_price:.2f}")

VWAP (Volume-Weighted Average Price)

O VWAP é mais inteligente: leva em conta o perfil típico de volume de negociação. Se 30% do volume diário costuma ser negociado entre 9:00 e 10:00, o VWAP executará 30% da ordem nessa janela. O objetivo é obter o preço médio de execução o mais próximo possível do VWAP de mercado.

class VWAPExecutor:
    """
    VWAP executor: distributes volume proportionally
    to the historical volume profile.
    """
    def __init__(self, exchange, symbol: str, side: str,
                 total_qty: float, volume_profile: list[float]):
        self.exchange = exchange
        self.symbol = symbol
        self.side = side
        self.total_qty = total_qty
        # volume_profile: share of total volume for each slice
        # Example: [0.15, 0.25, 0.30, 0.20, 0.10] — 5 periods
        total_weight = sum(volume_profile)
        self.weights = [w / total_weight for w in volume_profile]

    async def execute(self, interval_seconds: float = 60.0):
        executed = 0.0
        for i, weight in enumerate(self.weights):
            qty = self.total_qty * weight
            remaining = self.total_qty - executed
            qty = min(qty, remaining)

            if qty <= 0:
                break

            order = await self.exchange.create_order(
                symbol=self.symbol,
                type="market",
                side=self.side,
                amount=qty,
            )
            executed += float(order["filled"])
            print(f"[VWAP] period {i+1}: weight={weight:.2%}, "
                  f"filled={order['filled']} @ {order['average']}")

            await asyncio.sleep(interval_seconds)

Diferença TWAP vs VWAP: o TWAP é mais simples e previsível. O VWAP entrega um preço médio melhor, mas exige um perfil de volume confiável. No mercado cripto, onde volumes podem ser wash-traded, o perfil de VWAP precisa ser construído com cuidado.


3. Limit com Chasing: Quando Sua Ordem Sabe Perseguir o Preço

Ordens limit com chasing Chasing limit: a ordem persegue um preço em movimento com agressividade configurável

Agora as coisas ficam realmente interessantes. Uma ordem limit padrão é uma entidade passiva: fica no order book e espera. Se o preço se mover — a ordem permanece sem execução. Para um algo trader, isso costuma ser inaceitável: o sinal de entrada disparou, mas a posição não foi construída porque o mercado se moveu 0,1%.

Chasing limit order é um wrapper programático em torno de uma ordem limit que:

  1. Coloca uma ordem limit no melhor preço atual (ou com um pequeno offset)
  2. Monitora o preço via WebSocket
  3. Se o preço se afastar da ordem — cancela e recoloca mais perto do preço atual
  4. Repete até que a ordem seja executada ou exceda o desvio permitido

Parâmetros-chave

  • chase_interval_ms — com que frequência verificar e recolocar a ordem. 100ms — agressivo, 1000ms — relaxado.
  • max_chase_distance — desvio máximo em relação ao preço inicial antes de a ordem ser cancelada. Proteção contra perseguir um mercado descontrolado.
  • aggression_level — quão perto do preço de mercado colocar a ordem limit. 0 — no melhor bid/ask (passivo), 1 — cruzando o spread (agressivo, efetivamente um taker).
  • chase_on_partial — se deve continuar perseguindo caso a ordem seja parcialmente executada.

Implementação em TypeScript

interface ChasingOrderParams {
  symbol: string;
  side: "buy" | "sell";
  totalQty: number;
  /** 0 = passive (at best bid/ask), 1 = cross spread */
  aggression: number;
  /** max price deviation from initial price */
  maxChaseDistance: number;
  /** how often to re-evaluate, ms */
  chaseIntervalMs: number;
  /** stop chasing after this many ms */
  timeoutMs: number;
}

class ChasingLimitOrder {
  private currentOrderId: string | null = null;
  private filledQty = 0;
  private initialPrice: number | null = null;
  private startTime = Date.now();

  constructor(
    private exchange: any, // ccxt exchange instance
    private params: ChasingOrderParams
  ) {}

  async execute(): Promise<{ filledQty: number; avgPrice: number }> {
    const fills: Array<{ qty: number; price: number }> = [];

    while (this.filledQty < this.params.totalQty) {
      // Timeout
      if (Date.now() - this.startTime > this.params.timeoutMs) {
        console.log("[CHASE] timeout reached, cancelling");
        await this.cancelCurrent();
        break;
      }

      // Get current order book
      const book = await this.exchange.fetchOrderBook(
        this.params.symbol, 5
      );
      const bestBid = book.bids[0][0];
      const bestAsk = book.asks[0][0];
      const spread = bestAsk - bestBid;

      // Calculate target price
      let targetPrice: number;
      if (this.params.side === "buy") {
        targetPrice = bestBid + spread * this.params.aggression;
      } else {
        targetPrice = bestAsk - spread * this.params.aggression;
      }

      // Remember the initial price
      if (this.initialPrice === null) {
        this.initialPrice = targetPrice;
      }

      // Check max chase distance
      const deviation = Math.abs(targetPrice - this.initialPrice);
      if (deviation > this.params.maxChaseDistance) {
        console.log(
          `[CHASE] max deviation exceeded: ${deviation.toFixed(4)} > ` +
          `${this.params.maxChaseDistance}`
        );
        await this.cancelCurrent();
        break;
      }

      // Check current order
      if (this.currentOrderId) {
        const order = await this.exchange.fetchOrder(
          this.currentOrderId, this.params.symbol
        );

        if (order.status === "closed") {
          fills.push({ qty: order.filled, price: order.average });
          this.filledQty += order.filled;
          this.currentOrderId = null;
          continue;
        }

        // Update filledQty for partial fills
        if (order.filled > 0) {
          const newFilled = order.filled - (
            fills.reduce((s, f) => s + f.qty, 0) - this.filledQty
          );
          // Order is in place — do we need to reprice?
        }

        const currentPrice = parseFloat(order.price);
        const priceDiff = Math.abs(currentPrice - targetPrice);
        const tickSize = spread * 0.1 || 0.01;

        if (priceDiff > tickSize) {
          // Price moved — reprice
          console.log(
            `[CHASE] repricing: ${currentPrice} -> ` +
            `${targetPrice.toFixed(4)}`
          );
          await this.cancelCurrent();
        } else {
          // Order is at the right price — wait
          await this.sleep(this.params.chaseIntervalMs);
          continue;
        }
      }

      // Place new order
      const remainingQty = this.params.totalQty - this.filledQty;
      const order = await this.exchange.createLimitOrder(
        this.params.symbol,
        this.params.side,
        remainingQty,
        targetPrice
      );
      this.currentOrderId = order.id;
      console.log(
        `[CHASE] placed ${this.params.side} ${remainingQty} ` +
        `@ ${targetPrice.toFixed(4)}`
      );

      await this.sleep(this.params.chaseIntervalMs);
    }

    const totalCost = fills.reduce((s, f) => s + f.qty * f.price, 0);
    const avgPrice = this.filledQty > 0 ? totalCost / this.filledQty : 0;
    return { filledQty: this.filledQty, avgPrice };
  }

  private async cancelCurrent(): Promise<void> {
    if (this.currentOrderId) {
      try {
        await this.exchange.cancelOrder(
          this.currentOrderId, this.params.symbol
        );
      } catch { /* order already filled or cancelled */ }
      this.currentOrderId = null;
    }
  }

  private sleep(ms: number): Promise<void> {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }
}

Quando o Chasing É Prejudicial

O chasing é uma ferramenta poderosa, mas é fácil transformá-lo em um gerador de perdas:

  1. Spam de cancel/replace. Cada cancelamento e recolocação é uma carga na API. As exchanges limitam requisições (rate limit), e um chasing agressivo pode fazer sua chave de API ser banida.
  2. Seleção adversa. Se o preço está fugindo de você — o mercado pode saber algo que você não sabe. Perseguir o preço nessa situação significa comprar no topo.
  3. Transição de Maker para Taker. Com alta agressividade, você efetivamente paga taxas de taker, mas com atraso (cancel + nova ordem). Às vezes é mais simples apenas colocar uma ordem market.

4. Ordens Baseadas em Tempo: Precisão de Milissegundos

Há situações em que você precisa executar uma ordem não "no preço X", mas "no momento T". Parece estranho? Na verdade é uma classe inteira de estratégias.

Casos de uso

Arbitragem de funding rate. Em futuros perpétuos, o funding é pago a cada 8 horas (00:00, 08:00, 16:00 UTC na Binance). Se a taxa de funding = +0,1%, você precisa estar vendido no momento do acerto. Estratégia: abrir um short poucos segundos antes do acerto, coletar o funding, fechar a posição. O timing é crítico — um segundo de atraso significa funding perdido.

Aberturas/fechamentos de sessão. Em mercados tradicionais e alguns derivativos cripto, existem sessões fixas. O leilão de abertura (NYSE, CME) é o momento em que a liquidez está no auge. Colocar uma ordem 100ms antes do leilão é uma vantagem.

Execução baseada em notícias. Dados de inflação são divulgados em horário programado. O algoritmo faz parsing do número em um feed de notícias e coloca uma ordem em 50ms. Aqui, a execução baseada em tempo é combinada com lógica orientada a eventos.

Implementação

class TimeBasedOrder {
  constructor(
    private exchange: any,
    private symbol: string,
    private side: "buy" | "sell",
    private qty: number,
    private orderType: "market" | "limit",
    private limitPrice?: number
  ) {}

  /**
   * Schedule execution at a precise time.
   * Uses a busy-wait loop for maximum precision.
   */
  async executeAt(targetTime: Date): Promise<any> {
    const targetMs = targetTime.getTime();

    // Phase 1: coarse wait (sleep)
    const coarseWait = targetMs - Date.now() - 500; // wake up 500ms early
    if (coarseWait > 0) {
      console.log(
        `[TIME-ORDER] sleeping for ${(coarseWait / 1000).toFixed(1)}s`
      );
      await new Promise((r) => setTimeout(r, coarseWait));
    }

    // Phase 2: precise wait (busy-wait)
    while (Date.now() < targetMs) {
      // spin — burns CPU, but achieves ~1ms precision
    }

    // Phase 3: execution
    const sendTime = Date.now();
    const order = await this.exchange.createOrder(
      this.symbol,
      this.orderType,
      this.side,
      this.qty,
      this.limitPrice
    );

    console.log(
      `[TIME-ORDER] executed at ${new Date(sendTime).toISOString()}, ` +
      `target was ${targetTime.toISOString()}, ` +
      `delta: ${sendTime - targetMs}ms`
    );

    return order;
  }
}

// Example: place an order exactly at 00:00:00 UTC (funding settlement)
const executor = new TimeBasedOrder(exchange, "BTC/USDT", "sell", 0.1, "market");
const target = new Date("2026-03-24T00:00:00.000Z");
await executor.executeAt(target);

Ressalva importante: a precisão de uma ordem baseada em tempo é limitada não pelo seu código, mas pela latência de rede até a exchange. Se o seu ping para a API é de 50ms, mesmo um busy-wait perfeito terá um delta de 50ms. Para HFT sério, usa-se co-location — o servidor fica fisicamente ao lado do matching engine da exchange.


5. Ordens Virtuais/Sintéticas: as Invisíveis do Seu Sistema

Ordens virtuais para grid bots Ordens virtuais: existem apenas na memória do bot até o gatilho disparar

Esta talvez seja a ferramenta mais subestimada no arsenal do algo trader. Uma ordem virtual (também conhecida como ordem sintética) é uma ordem que existe apenas no seu sistema. Ela não é enviada à exchange até que uma condição de gatilho seja atendida (tipicamente — o preço atingir um determinado nível).

Como Funciona

  1. Seu algoritmo decide: "quero comprar BTC a US$ 40.000"
  2. Em vez de enviar uma ordem limit à exchange, ele cria uma ordem virtual na memória
  3. Assina o stream de preço via WebSocket
  4. Quando o bid/ask atinge US$ 40.000 — envia uma ordem market ou limit real à exchange

Por Que Ordens Virtuais Importam

Nenhum vazamento de informação. Sua ordem é invisível no order book. Ninguém — nem outros traders, nem algoritmos de HFT, nem mesmo a própria exchange — sabe de suas intenções até o momento da execução. Isso muda fundamentalmente o equilíbrio de poder.

Proteção contra front-running. Em exchanges cripto, especialmente as menos transparentes, há suspeita razoável de que informações sobre grandes ordens limit possam ser usadas para front-running (há até estudos sobre isso). Ordens virtuais eliminam esse risco.

Grid bots. Um grid bot clássico coloca uma grade de 50 a 200 ordens em diferentes níveis de preço. Se você enviar todas para a exchange — são 200 ordens no book que: (a) são visíveis para todos, (b) consomem o limite de ordens na exchange (tipicamente 200-300 ordens abertas por conta), (c) se o preço se mover bruscamente, todas são executadas e você acaba com uma posição enorme. Ordens virtuais resolvem os três problemas.

Pegando facas caindo. Estratégia: colocar ordens virtuais de compra nos níveis -5%, -10%, -15% abaixo do preço atual. Se o mercado cair — as ordens disparam gradualmente. Se não cair — você não arrisca nada e não usa nenhum slot de ordem na exchange.

Implementação em TypeScript

interface VirtualOrder {
  id: string;
  symbol: string;
  side: "buy" | "sell";
  triggerPrice: number;
  qty: number;
  /** Order type sent to the exchange upon triggering */
  executionType: "market" | "limit";
  /** For limit: offset from trigger price */
  limitOffset?: number;
  status: "pending" | "triggered" | "filled" | "failed";
}

class VirtualOrderManager {
  private orders: Map<string, VirtualOrder> = new Map();
  private orderCounter = 0;

  constructor(private exchange: any) {}

  /**
   * Create a virtual order. Nothing is sent to the exchange.
   */
  addOrder(params: Omit<VirtualOrder, "id" | "status">): string {
    const id = `virt_${++this.orderCounter}`;
    this.orders.set(id, { ...params, id, status: "pending" });
    console.log(
      `[VIRTUAL] created ${params.side} ${params.qty} ` +
      `${params.symbol} @ trigger ${params.triggerPrice}`
    );
    return id;
  }

  /**
   * Called on every price tick (from WebSocket).
   */
  async onPriceUpdate(
    symbol: string, bestBid: number, bestAsk: number
  ): Promise<void> {
    for (const [id, order] of this.orders) {
      if (order.symbol !== symbol || order.status !== "pending") continue;

      const triggered =
        (order.side === "buy" && bestAsk <= order.triggerPrice) ||
        (order.side === "sell" && bestBid >= order.triggerPrice);

      if (!triggered) continue;

      order.status = "triggered";
      console.log(
        `[VIRTUAL] ${id} triggered! bid=${bestBid} ask=${bestAsk}`
      );

      try {
        let realOrder: any;

        if (order.executionType === "market") {
          realOrder = await this.exchange.createMarketOrder(
            order.symbol, order.side, order.qty
          );
        } else {
          const limitPrice = order.side === "buy"
            ? order.triggerPrice + (order.limitOffset ?? 0)
            : order.triggerPrice - (order.limitOffset ?? 0);
          realOrder = await this.exchange.createLimitOrder(
            order.symbol, order.side, order.qty, limitPrice
          );
        }

        order.status = "filled";
        console.log(
          `[VIRTUAL] ${id} filled: ${realOrder.filled} ` +
          `@ ${realOrder.average ?? realOrder.price}`
        );
      } catch (err) {
        order.status = "failed";
        console.error(`[VIRTUAL] ${id} execution failed:`, err);
      }
    }
  }

  /**
   * Get all active virtual orders.
   */
  getPendingOrders(): VirtualOrder[] {
    return [...this.orders.values()].filter(
      (o) => o.status === "pending"
    );
  }

  cancelOrder(id: string): boolean {
    const order = this.orders.get(id);
    if (order && order.status === "pending") {
      this.orders.delete(id);
      return true;
    }
    return false;
  }
}

// --- Example: Grid bot with virtual orders ---

async function gridBot(exchange: any) {
  const manager = new VirtualOrderManager(exchange);
  const currentPrice = 42000;
  const gridStep = 200;      // grid step
  const gridLevels = 20;     // levels in each direction
  const qtyPerLevel = 0.01;  // BTC per level

  // Create virtual grid
  for (let i = 1; i <= gridLevels; i++) {
    // Buy orders below current price
    manager.addOrder({
      symbol: "BTC/USDT",
      side: "buy",
      triggerPrice: currentPrice - gridStep * i,
      qty: qtyPerLevel,
      executionType: "limit",
      limitOffset: 1,  // limit price = trigger + 1 USDT
    });

    // Sell orders above current price
    manager.addOrder({
      symbol: "BTC/USDT",
      side: "sell",
      triggerPrice: currentPrice + gridStep * i,
      qty: qtyPerLevel,
      executionType: "limit",
      limitOffset: 1,
    });
  }

  console.log(
    `[GRID] created ${gridLevels * 2} virtual orders, ` +
    `0 on exchange`
  );

  // WebSocket subscription (pseudocode for ccxt.pro)
  while (true) {
    const ticker = await exchange.watchTicker("BTC/USDT");
    await manager.onPriceUpdate(
      "BTC/USDT", ticker.bid, ticker.ask
    );
  }
}

Armadilhas das Ordens Virtuais

  1. Gap de latência. Entre o momento em que você vê o preço e o momento em que a ordem real chega à exchange, o tempo passa. Em um mercado volátil, o preço pode fugir nesses 20-100ms. Solução: enviar uma ordem limit ligeiramente agressiva (com uma margem).

  2. Execuções perdidas. Se o preço "perfurou" seu nível em um único tick (flash crash) e voltou — você pode não reagir a tempo. Uma ordem limit comum, esperando no book, teria sido executada; uma virtual — não.

  3. Gestão de estado. Ordens virtuais vivem em memória. Se o processo travar — as ordens são perdidas. Solução: armazenamento persistente (Redis, SQLite, arquivo) com recuperação ao reiniciar.


6. Ordens Condicionais/Inteligentes: Combinatória de Ordens

Quando uma única ordem não é suficiente, os traders as combinam em construções condicionais. Algumas são suportadas nativamente pelas exchanges, outras são implementadas programaticamente.

OCO (One Cancels Other)

Duas ordens são vinculadas: se uma é executada — a outra é automaticamente cancelada. Exemplo clássico: você está em uma posição long e quer definir tanto um take-profit quanto um stop-loss. Qual disparar primeiro — a outra deve ser cancelada.

class OCOHandler:
    """
    OCO: when one order fills, the other is cancelled.
    """
    def __init__(self, exchange, symbol: str):
        self.exchange = exchange
        self.symbol = symbol
        self.order_a_id: str | None = None
        self.order_b_id: str | None = None

    async def place(
        self,
        take_profit_price: float,
        stop_loss_price: float,
        qty: float,
    ):
        # Take-profit: limit sell order
        tp = await self.exchange.create_limit_sell_order(
            self.symbol, qty, take_profit_price
        )
        self.order_a_id = tp["id"]

        # Stop-loss: stop-market order
        sl = await self.exchange.create_order(
            self.symbol, "stop", "sell", qty,
            None, {"stopPrice": stop_loss_price}
        )
        self.order_b_id = sl["id"]

        print(f"[OCO] TP @ {take_profit_price}, SL @ {stop_loss_price}")

    async def monitor(self):
        """Checks statuses and cancels the paired order."""
        while True:
            if self.order_a_id:
                a = await self.exchange.fetch_order(
                    self.order_a_id, self.symbol
                )
                if a["status"] == "closed":
                    print("[OCO] take-profit filled, cancelling stop-loss")
                    await self.exchange.cancel_order(
                        self.order_b_id, self.symbol
                    )
                    break

            if self.order_b_id:
                b = await self.exchange.fetch_order(
                    self.order_b_id, self.symbol
                )
                if b["status"] == "closed":
                    print("[OCO] stop-loss filled, cancelling take-profit")
                    await self.exchange.cancel_order(
                        self.order_a_id, self.symbol
                    )
                    break

            await asyncio.sleep(0.5)

Bracket order

Uma construção de três componentes: uma ordem de entrada primária + OCO para saída (take-profit + stop-loss). Essencialmente, um ciclo de vida completo de trade em uma única chamada:

  1. Entrada: ordem limit de compra
  2. Take-profit: ordem limit de venda (acima)
  3. Stop-loss: ordem stop-market de venda (abaixo)

Quando a entrada é executada, TP e SL são colocados automaticamente. Quando um dos dois é executado — o outro é cancelado.

Lógica If-Then

A opção mais flexível — cadeias de ordens com condições arbitrárias:

# If BTC breaks above 50000 — buy ETH
# If ETH then reaches 4000 — take profit
# If ETH drops below 3500 — stop-loss

rules = [
    {
        "condition": {"symbol": "BTC/USDT", "price_above": 50000},
        "action": {"type": "market_buy", "symbol": "ETH/USDT", "qty": 10},
        "then": [
            {
                "condition": {"symbol": "ETH/USDT", "price_above": 4000},
                "action": {"type": "market_sell", "symbol": "ETH/USDT", "qty": 10},
            },
            {
                "condition": {"symbol": "ETH/USDT", "price_below": 3500},
                "action": {"type": "market_sell", "symbol": "ETH/USDT", "qty": 10},
            },
        ]
    }
]

Tais construções não são suportadas nativamente por nenhuma exchange — apenas por implementação programática. Essa é uma das razões pelas quais os sistemas de algotrading inevitavelmente desenvolvem sua própria camada de gestão de ordens.


7. Como Market Makers Usam Tipos de Ordem Especializados

Market making é um universo à parte, e o conjunto de ferramentas de ordens acompanha isso. O trabalho de um market maker é cotar continuamente bid e ask, ganhando com o spread, ao mesmo tempo minimizando a seleção adversa (a situação em que um trader informado negocia contra você).

Post-only como Obrigatório

Para um market maker, post-only não é uma opção — é um requisito. Se sua ordem acidentalmente for executada como taker — em vez de receber um rebate de maker, você paga uma taxa de taker. Em milhares de ordens por dia, isso é catastrófico.

# Market maker: placing a two-sided quote
async def quote(exchange, symbol, mid_price, half_spread, qty):
    bid_price = mid_price - half_spread
    ask_price = mid_price + half_spread

    bid = await exchange.create_order(
        symbol, "limit", "buy", qty, bid_price,
        {"postOnly": True}  # CRITICAL for market makers
    )
    ask = await exchange.create_order(
        symbol, "limit", "sell", qty, ask_price,
        {"postOnly": True}
    )
    return bid, ask

Ordens ocultas

Em algumas exchanges (Kraken, Bitfinex), há ordens ocultas disponíveis — elas não aparecem no order book, mas estão na exchange e participam do matching. A contrapartida: você paga taxas de taker mesmo como maker, mas ganha anonimato.

Para um market maker, essa é uma ferramenta de gestão de inventário: se uma posição grande se acumulou, você pode colocar uma ordem oculta para desfazê-la sem revelar sua intenção ao mercado.

Ordens pegged

Uma ordem ancorada (pegged) ao melhor bid/ask. Na Coinbase Advanced Trade, por exemplo, você pode colocar uma ordem que acompanha automaticamente o melhor bid e sempre fica na frente da fila. Isso é uma ordem de chasing nativa em nível de exchange — mas está longe de ser universalmente disponível.

Gestão de ordens em massa

Market makers profissionais usam APIs em lote (batch) para cancelar e colocar simultaneamente dezenas de ordens em uma única requisição HTTP. Na Binance isso é batchOrders, na Bybit — place-batch-order. Isso reduz a latência e a pressão do rate limit.


8. Tabela Comparativa de Tipos de Ordem

Tipo de OrdemGarantia de ExecuçãoGarantia de PreçoVisível no BookNativo nas ExchangesComplexidade de Implementação
MarketSimNãoNão (instantâneo)SimNenhuma
LimitNãoSimSimSimNenhuma
Stop-marketSim (após disparo)NãoNãoSimNenhuma
Stop-limitNãoSimNão (até disparo)SimNenhuma
Trailing stopSim (após disparo)NãoNãoParcialBaixa
IcebergNãoSimParcialParcialMédia
Post-onlyNãoSimSimSimNenhuma
TWAPNão (depende das fatias)NãoParcialNãoMédia
VWAPNãoNãoParcialNãoAlta
Chasing limitMaior que limitParcialSim (ordem atual)NãoMédia
Baseada em tempoDepende do tipoDepende do tipoNão (até o momento T)NãoBaixa
Virtual/SintéticaMenor que limitDepende do tipoNãoNãoMédia
OCOSim (uma das duas)ParcialSim (ambas)ParcialMédia
BracketSimParcialSimRaroAlta
OcultaNãoSimNãoRaroNenhuma
PeggedNãoDinâmicaSimMuito raroAlta (se programática)

Conclusão: A Ordem como Bloco de Construção de Estratégia

Tipos de ordem não são apenas "botões em uma interface." São primitivas fundamentais a partir das quais é construída a camada de execução de qualquer sistema de trading. A diferença entre "a estratégia é lucrativa no backtest" e "a estratégia é lucrativa em produção" muitas vezes está exatamente aqui — em como exatamente você envia ordens à exchange.

Algumas conclusões práticas:

  1. Comece com ordens padrão, garanta que entende as nuances (stop-limit vs stop-market, IOC vs FOK). A maioria dos erros acontece aqui.
  2. Ordens virtuais são obrigatórias para grid bots. Se você está colocando mais de 50 ordens — não as envie todas para a exchange.
  3. Chasing é necessário quando a taxa de execução importa mais que o preço. Mas sempre defina max_chase_distance — caso contrário você pode se desviar muito.
  4. Execução baseada em tempo é de nicho, mas poderosa para arbitragem de funding e estratégias orientadas a eventos.
  5. Uma camada customizada de gestão de ordens é inevitável para qualquer sistema de algotrading sério. Os tipos de ordem nativos das exchanges não são suficientes.

Se você está construindo um sistema de trading e quer se aprofundar — confira nossos artigos sobre posição na fila do order book, métodos WebSocket em CCXT, e arbitragem de funding rate.


Referências e Fontes