Tipi di ordine nel trading algoritmico: dal limite con inseguimento agli ordini virtuali
Quando un principiante apre il terminale di borsa, vede due pulsanti: "Compra" e "Vendi." Quando un trader algoritmico apre il suo codebase, vede ventisette tipi di ordine, tre livelli di astrazione e un mucchio di edge case che fanno venire voglia di chiudere il laptop e andare a vendere cetrioli al mercato contadino. Ma i cetrioli, purtroppo, non ti permettono di fare arbitraggio sul funding rate alle 3:59 UTC — quindi andiamo a fondo.
In questo articolo percorreremo l'intero viaggio dagli ordini di borsa di base alle costruzioni virtuali sintetiche che esistono solo all'interno del tuo sistema e non compaiono mai nel book degli ordini. Aspettatevi TypeScript, Python, un po' di dolore e un pizzico di illuminazione.
1. Ordini standard di borsa: le basi che non puoi saltare
Classificazione dei tipi di ordine standard: dal market all'iceberg
Prima di costruire qualcosa di complesso, dobbiamo assicurarci di capire correttamente i mattoni di base. È sorprendente quante persone confondano stop-limit e stop-market, e poi si chiedano perché il loro stop "non ha scattato" (spoiler: ha scattato, ma l'ordine limite non è stato eseguito a causa dello slippage).
Ordine market
Il tipo più semplice e allo stesso tempo il più pericoloso. Dici alla borsa: "Compra/vendi adesso, a qualsiasi prezzo disponibile." La borsa preleva liquidità dal book degli ordini, partendo dal prezzo migliore. Se il volume al livello migliore non è sufficiente — scivola oltre.
Quando usarlo: uscita di emergenza da una posizione, esecuzione di un segnale dove la velocità conta più del prezzo.
Insidie: su un mercato sottile, un ordine market per 100 BTC può spostare il prezzo di diversi punti percentuali. I backtest che modellano gli ordini market senza tener conto dell'impatto sono pura fantasia.
Ordine limite
Specifichi un prezzo esatto. L'ordine entra nel book degli ordini e aspetta che qualcuno accetti il tuo prezzo. Se il prezzo di un ordine limite bid è superiore al mercato corrente — si esegue immediatamente (come un ordine market, ma con un prezzo massimo garantito).
Punto chiave: un ordine limite non garantisce l'esecuzione. Il prezzo può raggiungere il tuo livello e invertire, lasciandoti in coda (ne parleremo nel nostro articolo sulla posizione in coda).
Stop-market e Stop-limit
È qui che inizia la confusione. Entrambi i tipi sono ordini "dormienti" che si attivano quando viene raggiunto il prezzo di innesco (stop price). Ma:
- Stop-market: all'attivazione, si converte in un ordine market. Garantisce l'esecuzione, ma non il prezzo.
- Stop-limit: all'attivazione, si converte in un ordine limite. Garantisce il prezzo (non peggiore del specificato), ma non l'esecuzione.
Sul volatile mercato crypto, uno stop-limit può "mancarlo" — il prezzo ha attraversato lo stop, l'ordine limite è stato piazzato, ma il mercato era già volato via. Ti ritrovi con un ordine limite non eseguito e una perdita crescente. È esattamente per questo che lo stop-market è più comunemente usato per gli stop-loss.
Trailing stop
Uno stop che "segue" il prezzo a una distanza stabilita. Il prezzo sale — lo stop sale. Il prezzo scende — lo stop rimane fermo. Utile per proteggere i profitti nelle strategie trend-following.
Supporto degli exchange: non tutti gli exchange supportano i trailing stop nativi. I trader algoritmici spesso li implementano programmaticamente — questo dà più controllo sui parametri (callback rate, prezzo di attivazione, dimensione del passo).
Ordine iceberg
Un ordine in cui solo una frazione del volume totale è visibile nel book degli ordini. Vuoi comprare 1.000 BTC, ma mostri solo 10 nel book. Quando i primi 10 si eseguono — compaiono i successivi 10.
Perché: per nascondere le tue vere intenzioni al mercato. Un ordine grande nel book segnala a tutti che "qualcuno di importante vuole comprare/vendere." In risposta, gli algoritmi HFT iniziano il front-running e il prezzo si allontana da te.
Avvertenza: su molti exchange crypto, gli ordini iceberg non sono supportati o sono facilmente rilevabili dal pattern di volumi identici. Gli algoritmi avanzati randomizzano la dimensione della porzione visibile.
Parametri time-in-force: GTC, GTD, IOC, FOK
Questi non sono tipi di ordine separati ma parametri time-in-force — quanto a lungo vive un ordine:
| Parametro | Nome completo | Comportamento |
|---|---|---|
| GTC | Good Till Cancelled | Vive fino alla cancellazione. Lo standard predefinito |
| GTD | Good Till Date | Vive fino a una data/ora specificata |
| IOC | Immediate or Cancel | Si esegue immediatamente (totalmente o parzialmente), il resto viene cancellato |
| FOK | Fill or Kill | Si esegue solo integralmente e immediatamente. Se impossibile — cancellato del tutto |
IOC vs FOK: la differenza è critica. IOC può eseguirsi parzialmente — volevi comprare 100 BTC, ne hai comprati 3, il resto è stato cancellato. FOK è o 100 o niente.
Post-only (Maker-only)
Un ordine che è garantito di entrare nel book degli ordini come maker e non viene mai eseguito come taker. Se al momento del piazzamento il prezzo causerebbe un'esecuzione immediata — l'exchange lo rifiuta (o aggiusta il prezzo, a seconda dell'exchange).
Perché: le commissioni maker sono di solito inferiori alle commissioni taker (su Binance — 0,02% vs 0,04% per i livelli VIP). Per un market maker che piazza migliaia di ordini al giorno, la differenza di commissione è la differenza tra profitto e perdita.
2. TWAP e VWAP: come le istituzioni nascondono un elefante nel book degli ordini
Quando un hedge fund vuole comprare una posizione da 50 milioni di dollari, non piazza un singolo ordine market. Utilizza algoritmi di esecuzione — algoritmi che suddividono un ordine grande in molti più piccoli e li eseguono nel tempo, minimizzando l'impatto di mercato.
TWAP (Time-Weighted Average Price)
L'idea è semplicissima: suddividi il volume totale in parti uguali e le esegui a intervalli di tempo uguali.
import asyncio
from datetime import datetime, timedelta
class TWAPExecutor:
"""
Executor TWAP: suddivide un ordine grande in parti uguali
e le esegue a intervalli di tempo uguali.
"""
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)
VWAP è più intelligente: tiene conto del profilo tipico dei volumi di trading. Se il 30% del volume giornaliero viene tipicamente scambiato tra le 9:00 e le 10:00, VWAP eseguirà il 30% dell'ordine in quella finestra. L'obiettivo è avvicinare il più possibile il prezzo medio di esecuzione al VWAP di mercato.
class VWAPExecutor:
"""
Executor VWAP: distribuisce il volume proporzionalmente
al profilo storico dei volumi.
"""
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)
Differenza TWAP vs VWAP: TWAP è più semplice e prevedibile. VWAP offre un prezzo medio migliore ma richiede un profilo di volume affidabile. Sul mercato crypto, dove i volumi possono essere wash-traded, il profilo VWAP deve essere costruito con attenzione.
3. Limite con inseguimento: quando il tuo ordine sa come inseguire il prezzo
Chasing limit: l'ordine insegue un prezzo in movimento con aggressività configurabile
Ora le cose si fanno davvero interessanti. Un ordine limite standard è un'entità passiva: sta nel book degli ordini e aspetta. Se il prezzo si è spostato — l'ordine rimane non eseguito. Per un trader algoritmico, questo è spesso inaccettabile: il segnale di entrata è scattato, ma la posizione non è stata costruita perché il mercato si è spostato dello 0,1%.
Un ordine limite con inseguimento (chasing limit) è un wrapper programmatico attorno a un ordine limite che:
- Piazza un ordine limite al prezzo migliore corrente (o con un piccolo offset)
- Monitora il prezzo via WebSocket
- Se il prezzo si allontana dall'ordine — lo cancella e lo rimpiazza più vicino al prezzo corrente
- Ripete finché l'ordine non viene eseguito o supera la deviazione consentita
Parametri chiave
- chase_interval_ms — ogni quanto controllare e sostituire l'ordine. 100ms — aggressivo, 1000ms — rilassato.
- max_chase_distance — deviazione massima dal prezzo iniziale prima che l'ordine venga cancellato. Protezione contro l'inseguimento di un mercato in fuga.
- aggression_level — quanto vicino al prezzo di mercato piazzare l'ordine limite.
0— al miglior bid/ask (passivo),1— attraversando lo spread (aggressivo, effettivamente un taker). - chase_on_partial — se continuare l'inseguimento se l'ordine è parzialmente eseguito.
Implementazione 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 l'inseguimento è dannoso
L'inseguimento è uno strumento potente, ma è facile trasformarlo in un generatore di perdite:
- Spam di cancel/replace. Ogni cancel e replace è un carico sull'API. Gli exchange limitano le richieste, e un inseguimento aggressivo può far bannare la tua chiave API.
- Selezione avversa. Se il prezzo ti sta scappando — il mercato potrebbe sapere qualcosa che tu non sai. Inseguire il prezzo in questa situazione significa comprare ai massimi.
- Transizione da Maker a Taker. Con alta aggressività stai effettivamente pagando commissioni taker, ma con un ritardo (cancel + nuovo ordine). A volte è più semplice piazzare direttamente un ordine market.
4. Ordini temporizzati: precisione al millisecondo
Ci sono situazioni in cui hai bisogno di eseguire un ordine non "al prezzo X" ma "al tempo T." Suona strano? In realtà è un'intera classe di strategie.
Casi d'uso
Arbitraggio del funding rate. Sui futures perpetui, il funding viene pagato ogni 8 ore (00:00, 08:00, 16:00 UTC su Binance). Se il funding rate = +0,1%, devi essere short al momento del settlement. Strategia: apri uno short pochi secondi prima del settlement, incassi il funding, chiudi la posizione. Il timing è critico — un secondo di ritardo significa funding perso.
Aperture/chiusure di sessione. Sui mercati tradizionali e su alcuni derivati crypto, esistono sessioni fisse. L'asta di apertura (NYSE, CME) è il momento in cui la liquidità è al suo picco. Piazzare un ordine 100ms prima dell'asta è un vantaggio.
Esecuzione basata sulle notizie. I dati sull'inflazione vengono rilasciati a un'ora prestabilita. L'algoritmo analizza il numero da un feed di notizie e piazza un ordine entro 50ms. Qui l'esecuzione temporizzata si combina con la logica event-driven.
Implementazione
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;
}
}
// Esempio: piazza un ordine esattamente alle 00:00:00 UTC (settlement funding)
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);
Avvertenza importante: la precisione di un ordine temporizzato è limitata non dal tuo codice ma dalla latenza di rete verso l'exchange. Se il tuo ping all'API è di 50ms, anche un busy-wait perfetto avrà un delta di 50ms. Per HFT serio si usa la co-location — il server si trova fisicamente vicino al matching engine dell'exchange.
5. Ordini virtuali/sintetici: gli invisibili nel tuo sistema
Ordini virtuali: gli ordini esistono solo nella memoria del bot fino a quando non scatta il trigger
Questo è forse lo strumento più sottovalutato nell'arsenale del trader algoritmico. Un ordine virtuale (detto anche ordine sintetico) è un ordine che esiste solo nel tuo sistema. Non viene inviato all'exchange fino a quando non si verifica una condizione trigger (tipicamente — il prezzo che raggiunge un certo livello).
Come funziona
- Il tuo algoritmo decide: "Voglio comprare BTC a $40.000"
- Invece di inviare un ordine limite all'exchange, crea un ordine virtuale in memoria
- Si iscrive allo stream di prezzi WebSocket
- Quando bid/ask raggiunge $40.000 — invia un ordine market o limite reale all'exchange
Perché gli ordini virtuali sono importanti
Nessuna fuga di informazioni. Il tuo ordine è invisibile nel book degli ordini. Nessuno — né altri trader, né algoritmi HFT, né l'exchange stesso — conosce le tue intenzioni fino al momento dell'esecuzione. Questo sposta fondamentalmente il bilanciamento del potere.
Protezione dal front-running. Sugli exchange crypto, specialmente quelli meno trasparenti, c'è il ragionevole sospetto che le informazioni sugli ordini limite grandi possano essere usate per il front-running (esistono persino studi al riguardo). Gli ordini virtuali eliminano questo rischio.
Grid bot. Un classico grid bot piazza una griglia di 50-200 ordini a diversi livelli di prezzo. Se li invii tutti all'exchange — sono 200 ordini nel book che: (a) sono visibili a tutti, (b) usano il limite di ordini sull'exchange (tipicamente 200-300 ordini aperti per account), (c) se il prezzo si muove bruscamente, si eseguono tutti e ti ritrovi con una posizione massiccia. Gli ordini virtuali risolvono tutti e tre i problemi.
Catchare i coltelli in caduta. Strategia: piazza ordini di acquisto virtuali a livelli -5%, -10%, -15% sotto il prezzo corrente. Se il mercato scende — gli ordini si attivano gradualmente. Se non scende — non rischi nulla e non usi slot di ordini sull'exchange.
Implementazione 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;
}
}
// --- Esempio: Grid bot con ordini virtuali ---
async function gridBot(exchange: any) {
const manager = new VirtualOrderManager(exchange);
const currentPrice = 42000;
const gridStep = 200; // passo della griglia
const gridLevels = 20; // livelli in ogni direzione
const qtyPerLevel = 0.01; // BTC per livello
// Crea griglia virtuale
for (let i = 1; i <= gridLevels; i++) {
// Ordini di acquisto sotto il prezzo corrente
manager.addOrder({
symbol: "BTC/USDT",
side: "buy",
triggerPrice: currentPrice - gridStep * i,
qty: qtyPerLevel,
executionType: "limit",
limitOffset: 1, // limit price = trigger + 1 USDT
});
// Ordini di vendita sopra il prezzo corrente
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`
);
// Iscrizione WebSocket (pseudocodice per ccxt.pro)
while (true) {
const ticker = await exchange.watchTicker("BTC/USDT");
await manager.onPriceUpdate(
"BTC/USDT", ticker.bid, ticker.ask
);
}
}
Insidie degli ordini virtuali
-
Latency gap. Tra il momento in cui vedi il prezzo e il momento in cui l'ordine reale raggiunge l'exchange, passa del tempo. Su un mercato volatile, il prezzo può volare via in quei 20-100ms. Soluzione: invia un ordine limite leggermente aggressivo (con un buffer).
-
Esecuzioni mancate. Se il prezzo ha "attraversato" il tuo livello in un singolo tick (flash crash) e è rimbalzato — potresti non reagire in tempo. Un normale ordine limite seduto nel book avrebbe eseguito; uno virtuale — no.
-
Gestione dello stato. Gli ordini virtuali vivono in memoria. Se il processo va in crash — gli ordini vengono persi. Soluzione: storage persistente (Redis, SQLite, file) con recovery al riavvio.
6. Ordini condizionali/smart: combinatoria degli ordini
Quando un singolo ordine non è sufficiente, i trader li combinano in costruzioni condizionali. Alcune sono supportate nativamente dagli exchange, altre vengono implementate programmaticamente.
OCO (One Cancels Other)
Due ordini sono collegati: se uno si esegue — l'altro viene automaticamente cancellato. Esempio classico: sei in una posizione long e vuoi impostare sia un take-profit che uno stop-loss. Qualunque dei due scatta per primo — l'altro deve essere cancellato.
class OCOHandler:
"""
OCO: quando un ordine si esegue, l'altro viene cancellato.
"""
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):
"""Controlla gli stati e cancella l'ordine accoppiato."""
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)
Ordine bracket
Una costruzione a tre componenti: un ordine di entrata primario + OCO per l'uscita (take-profit + stop-loss). Essenzialmente, un intero ciclo di vita del trade in una singola chiamata:
- Entrata: ordine limite di acquisto
- Take-profit: ordine limite di vendita (sopra)
- Stop-loss: ordine stop-market di vendita (sotto)
Quando l'entrata si esegue, TP e SL vengono piazzati automaticamente. Quando uno dei due si esegue — l'altro viene cancellato.
Logica If-Then
L'opzione più flessibile — catene di ordini con condizioni arbitrarie:
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},
},
]
}
]
Tali costruzioni non sono supportate nativamente da nessun exchange — solo implementazione programmatica. Questo è uno dei motivi per cui i sistemi di algotrading inevitabilmente crescono il proprio layer di gestione degli ordini.
7. Come i market maker usano tipi di ordine specializzati
Il market making è il proprio universo, e il toolkit degli ordini è all'altezza. Il lavoro di un market maker è quotare continuamente bid e ask, guadagnando sullo spread, mentre minimizza la selezione avversa (la situazione in cui un trader informato opera contro di te).
Post-only come Must-Have
Per un market maker, il post-only non è un'opzione — è un requisito. Se il tuo ordine si esegue accidentalmente come taker — invece di ricevere un rebate maker, paghi una commissione taker. Su migliaia di ordini al giorno, è catastrofico.
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} # CRITICO per i market maker
)
ask = await exchange.create_order(
symbol, "limit", "sell", qty, ask_price,
{"postOnly": True}
)
return bid, ask
Ordini nascosti
Su alcuni exchange (Kraken, Bitfinex), sono disponibili ordini nascosti — non appaiono nel book degli ordini ma sono sull'exchange e partecipano al matching. Il compromesso: paghi commissioni taker anche come maker, ma guadagni anonimato.
Per un market maker, questo è uno strumento per la gestione dell'inventario: se si è accumulata una posizione grande, puoi piazzare un ordine nascosto per smontarla senza rivelare le tue intenzioni al mercato.
Ordini pegged
Un ordine ancorato al miglior bid/ask. Su Coinbase Advanced Trade, ad esempio, puoi piazzare un ordine che traccia automaticamente il miglior bid e si trova sempre in testa alla coda. Questo è un ordine di inseguimento nativo a livello di exchange — ma è tutt'altro che universalmente disponibile.
Gestione degli ordini in bulk
I market maker professionisti usano le API batch per cancellare e piazzare contemporaneamente decine di ordini in una singola richiesta HTTP. Su Binance è batchOrders, su Bybit — place-batch-order. Questo riduce la latenza e la pressione sui rate limit.
8. Tabella di confronto dei tipi di ordine
| Tipo di ordine | Garanzia di esecuzione | Garanzia di prezzo | Visibile nel book | Nativo sugli exchange | Complessità di implementazione |
|---|---|---|---|---|---|
| Market | Sì | No | No (istantaneo) | Sì | Nessuna |
| Limite | No | Sì | Sì | Sì | Nessuna |
| Stop-market | Sì (dopo il trigger) | No | No | Sì | Nessuna |
| Stop-limit | No | Sì | No (fino al trigger) | Sì | Nessuna |
| Trailing stop | Sì (dopo il trigger) | No | No | Parziale | Bassa |
| Iceberg | No | Sì | Parziale | Parziale | Media |
| Post-only | No | Sì | Sì | Sì | Nessuna |
| TWAP | No (dipende dalle slice) | No | Parziale | No | Media |
| VWAP | No | No | Parziale | No | Alta |
| Chasing limit | Più alta del limite | Parziale | Sì (ordine corrente) | No | Media |
| Temporizzato | Dipende dal tipo | Dipende dal tipo | No (fino al tempo T) | No | Bassa |
| Virtuale/Sintetico | Più bassa del limite | Dipende dal tipo | No | No | Media |
| OCO | Sì (uno dei due) | Parziale | Sì (entrambi) | Parziale | Media |
| Bracket | Sì | Parziale | Sì | Raro | Alta |
| Nascosto | No | Sì | No | Raro | Nessuna |
| Pegged | No | Dinamico | Sì | Molto raro | Alta (se programmatico) |
Conclusione: l'ordine come mattone della strategia
I tipi di ordine non sono solo "pulsanti in un'interfaccia." Sono primitivi fondamentali da cui è costruito il layer di esecuzione di qualsiasi sistema di trading. La differenza tra "la strategia è redditizia nel backtesting" e "la strategia è redditizia in produzione" spesso risiede proprio qui — in esattamente come invii gli ordini all'exchange.
Alcune conclusioni pratiche:
- Inizia con gli ordini standard, assicurati di capire le sfumature (stop-limit vs stop-market, IOC vs FOK). La maggior parte degli errori avviene qui.
- Gli ordini virtuali sono indispensabili per i grid bot. Se stai piazzando più di 50 ordini — non inviarli tutti all'exchange.
- L'inseguimento è necessario quando il tasso di esecuzione conta più del prezzo. Ma imposta sempre max_chase_distance — altrimenti puoi andare molto lontano.
- L'esecuzione temporizzata è di nicchia ma potente per l'arb del funding e le strategie event-driven.
- Un layer personalizzato di gestione degli ordini è inevitabile per qualsiasi sistema di algotrading serio. I tipi di ordine nativi degli exchange non sono sufficienti.
Se stai costruendo un sistema di trading e vuoi approfondire — leggi i nostri articoli sulla posizione in coda nel book degli ordini, metodi WebSocket in CCXT, e arbitraggio del funding rate.
Riferimenti e fonti
- Libreria CCXT — una libreria unificata per lavorare con gli exchange crypto, che supporta 100+ exchange
- Documentazione API Binance — documentazione sui tipi di ordine di Binance
- Bybit API v5 — documentazione Bybit, inclusi gli ordini batch
- 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 — materiali sulla stima della posizione in coda
- Trading Technologies (TT) — una piattaforma professionale con tipi di ordine avanzati
Autori
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.