Tipos de Ordens em Trading Algorítmico: do Limit com Chasing às Ordens Virtuais
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
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âmetro | Nome Completo | Comportamento |
|---|---|---|
| GTC | Good Till Cancelled | Permanece ativa até ser cancelada. O padrão default |
| GTD | Good Till Date | Permanece ativa até uma data/hora especificada |
| IOC | Immediate or Cancel | Executa imediatamente (total ou parcialmente), o restante é cancelado |
| FOK | Fill or Kill | Executa 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
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
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:
- Coloca uma ordem limit no melhor preço atual (ou com um pequeno offset)
- Monitora o preço via WebSocket
- Se o preço se afastar da ordem — cancela e recoloca mais perto do preço atual
- 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:
- 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.
- 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.
- 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: 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
- Seu algoritmo decide: "quero comprar BTC a US$ 40.000"
- Em vez de enviar uma ordem limit à exchange, ele cria uma ordem virtual na memória
- Assina o stream de preço via WebSocket
- 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
-
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).
-
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.
-
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,
):
tp = await self.exchange.create_limit_sell_order(
self.symbol, qty, take_profit_price
)
self.order_a_id = tp["id"]
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:
- Entrada: ordem limit de compra
- Take-profit: ordem limit de venda (acima)
- 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:
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.
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 Ordem | Garantia de Execução | Garantia de Preço | Visível no Book | Nativo nas Exchanges | Complexidade de Implementação |
|---|---|---|---|---|---|
| Market | Sim | Não | Não (instantâneo) | Sim | Nenhuma |
| Limit | Não | Sim | Sim | Sim | Nenhuma |
| Stop-market | Sim (após disparo) | Não | Não | Sim | Nenhuma |
| Stop-limit | Não | Sim | Não (até disparo) | Sim | Nenhuma |
| Trailing stop | Sim (após disparo) | Não | Não | Parcial | Baixa |
| Iceberg | Não | Sim | Parcial | Parcial | Média |
| Post-only | Não | Sim | Sim | Sim | Nenhuma |
| TWAP | Não (depende das fatias) | Não | Parcial | Não | Média |
| VWAP | Não | Não | Parcial | Não | Alta |
| Chasing limit | Maior que limit | Parcial | Sim (ordem atual) | Não | Média |
| Baseada em tempo | Depende do tipo | Depende do tipo | Não (até o momento T) | Não | Baixa |
| Virtual/Sintética | Menor que limit | Depende do tipo | Não | Não | Média |
| OCO | Sim (uma das duas) | Parcial | Sim (ambas) | Parcial | Média |
| Bracket | Sim | Parcial | Sim | Raro | Alta |
| Oculta | Não | Sim | Não | Raro | Nenhuma |
| Pegged | Não | Dinâmica | Sim | Muito raro | Alta (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:
- Comece com ordens padrão, garanta que entende as nuances (stop-limit vs stop-market, IOC vs FOK). A maioria dos erros acontece aqui.
- 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.
- 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.
- Execução baseada em tempo é de nicho, mas poderosa para arbitragem de funding e estratégias orientadas a eventos.
- 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
- Biblioteca CCXT — uma biblioteca unificada para trabalhar com exchanges cripto, suportando mais de 100 exchanges
- Documentação da API Binance — documentação dos tipos de ordem da Binance
- Bybit API v5 — documentação da Bybit, incluindo ordens em lote
- Moallemi, C. & Yuan, K. (2017). The Value of Queue Position in a Limit Order Book. Columbia Business School Research Paper
- Cartea, A., Jaimungal, S., & Penalva, J. (2015). Algorithmic and High-Frequency Trading. Cambridge University Press
- Avellaneda, M. & Stoikov, S. (2008) — High-frequency trading in a limit order book. Quantitative Finance
- Erik Rigtorp — Order Queue Position Estimation — materiais sobre estimativa de posição na fila
- Trading Technologies (TT) — uma plataforma profissional com tipos de ordem avançados
Authors
Trading-systems engineer
Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.