Almgren-Chriss senza fumo negli occhi: esecuzione ottimale implementabile in un pomeriggio
Almgren e Chriss (2001), "Optimal execution of portfolio transactions" (Journal of Risk 3(2), 5–39), è probabilmente l'articolo più citato nella ricerca sull'esecuzione e uno dei meno implementati correttamente. Buona parte del codice "Almgren-Chriss" in circolazione è in realtà uno scheduler TWAP con un blocco di commenti. Buona parte dei post che ne parlano saltano la derivazione, agitano le mani davanti a una funzione sinh e non toccano mai la parte che conta davvero in produzione: da dove vengono i parametri.
Questo articolo fa tutto per intero. Le assunzioni del modello, dichiarate onestamente. La traiettoria in forma chiusa, derivata, non asserita. La frontiera efficiente e come scegliere il parametro di avversione al rischio. E la calibrazione dei tre input — impatto temporaneo , impatto permanente , volatilità — a partire da dati dell'order book e dei trade di Binance, con Python funzionante e una discussione onesta sul perché il fit sia rumoroso. Questa è la spina dorsale intellettuale di tutto il resto in materia di esecuzione: TWAP, VWAP e POV sono casi particolari o cugini euristici (vedi TWAP, VWAP, POV: gli algoritmi di esecuzione che tutti usano), e i moderni approcci basati su ML — scheduler a reinforcement learning e modelli di impatto neurali — sono tentativi di rilassare esattamente le assunzioni che stiamo per scrivere.
L'impostazione: cosa assume davvero il modello
Detenete unità di un asset (diciamo, 100 BTC) e dovete liquidarle completamente entro il tempo . Dividete in intervalli di lunghezza . La vostra variabile di decisione è la traiettoria delle posizioni , con i trade eseguiti nell'intervallo .
Tre assunzioni reggono l'intero modello:
1. Random walk aritmetico. Il prezzo non perturbato segue
dove sono normali standard i.i.d. e è la volatilità assoluta (dollari per unità di tempo, non percentuale). Aritmetico, non geometrico — su un arco di poche ore la differenza è trascurabile e il random walk aritmetico mantiene l'algebra lineare-quadratica. Nessun drift: Almgren e Chriss discutono il termine di drift, ma per una liquidazione intraday la vostra stima di alpha su 4 ore è quasi sempre rumore, e porla a zero è l'opzione di default onesta.
2. Impatto permanente lineare. : negoziare al ritmo sposta il prezzo in modo permanente, proporzionale, e lo spostamento non decade mai. Ogni unità venduta spinge il mid verso il basso di per sempre. Sembra rozzo, ma c'è una ragione profonda per mantenerlo lineare: Huberman e Stanzl (2004) hanno dimostrato che un impatto permanente non lineare nella dimensione del trade ammette strategie di round-trip con profitto atteso positivo — manipolazione del prezzo. L'impatto permanente lineare non è una semplificazione di comodo; è l'unica scelta arbitrage-free in questa classe di modelli.
3. Impatto temporaneo lineare. Il prezzo effettivamente ricevuto nell'intervallo è
cattura metà dello spread bid-ask più le commissioni; è la pendenza del costo marginale di richiedere liquidità al ritmo . L'impatto temporaneo influisce solo sul proprio fill e svanisce istantaneamente — il book si ricostituisce completamente prima del child order successivo. Ognuna delle tre parole "lineare", "istantaneamente", "completamente" è sbagliata nei mercati reali, e l'ultima sezione tratta di quanto lo sia. Ma prima, il payoff per averle accettate.
Costo e varianza di una traiettoria
Sommate i fill, sottraeteli dal mark iniziale , e ottenete l'implementation shortfall. Il suo valore atteso e la sua varianza rispetto al rumore sono:
con (una correzione di discretizzazione che svanisce quando ).
Due osservazioni che la maggior parte delle implementazioni si perde:
- Il costo permanente non dipende dalla traiettoria. Con impatto permanente lineare e non decadente, si paga lo stesso pedaggio permanente indipendentemente da come si pianifica l'esecuzione. L'ottimizzazione è interamente una lotta tra il termine di costo temporaneo (che vuole un trading lento e uniforme — è minimizzato da , cioè TWAP) e il termine di varianza (che vuole che il vostro inventario sparisca il prima possibile — è minimizzato dalla liquidazione immediata).
- Il termine di varianza è pesato per l'inventario, non per il trade. Il rischio matura su ciò che ancora detenete, , non su ciò che negoziate. Questo è il motivo per cui l'urgenza sposta il carico dello schedule verso l'inizio.
La forma chiusa: sinh, cosh e il parametro di urgenza
Almgren-Chriss minimizzano l'obiettivo media-varianza
dove è l'avversione al rischio in unità di 1/dollari. Ponendo per i punti interni. Il termine temporaneo contribuisce con le differenze seconde di , il termine di varianza contribuisce con stesso, e si ottiene un'equazione alle differenze lineare del secondo ordine:
Questo è l'analogo discreto di , e con le condizioni al contorno , la soluzione è iperbolica:
dove risolve , che per piccolo è semplicemente .

Tutto ciò che riguarda la strategia è compresso in un unico numero:
è l'urgenza. Il suo reciproco è il tempo caratteristico del trade: la scala temporale su cui conviene mantenere il rischio di inventario per risparmiare sul costo di impatto. Notate cosa non dipende: dalla dimensione dell'ordine e dalla scadenza . Se la scala temporale intrinseca del vostro trade è 20 minuti o 6 ore è determinato solo da avversione al rischio, volatilità e liquidità. Se , la vostra scadenza è irrilevante — il modello liquida secondo il proprio schedule e la coda dell'orizzonte resta inutilizzata. Se , la scadenza diventa vincolante e state effettivamente facendo TWAP.
Il limite risk-neutral è il TWAP — ecco perché il TWAP esiste
Prendete , quindi . Allora e
La traiettoria ottimale degenera nella linea retta: quantità uguali in intervalli di tempo uguali. Il TWAP non è un'euristica che per caso funziona; è l'ottimo esatto del modello Almgren-Chriss per un trader risk-neutral con impatto lineare. Ogni volta che qualcuno esegue un TWAP, sta implicitamente asserendo : "non mi interessa la varianza del mio shortfall, solo la sua media". È una posizione difendibile per ordini piccoli e orizzonti brevi. È una posizione strana per liquidare il 5% del volume giornaliero in 8 ore su un asset con il 4% di vol giornaliera, che è esattamente dove le persone lo eseguono comunque. Il limite opposto dà per ogni : scaricare tutto nel primo intervallo, pagando qualsiasi costo. Tra questi estremi, interpola: è a malapena distinguibile dal TWAP, è aggressivamente front-loaded.
La frontiera efficiente dell'esecuzione
Per ogni si ottiene una traiettoria, un costo atteso e una varianza . Facendo variare si traccia una curva nel piano — la frontiera efficiente dell'esecuzione, in diretta analogia con Markowitz. È convessa e decrescente: meno varianza costa sempre più shortfall atteso, con rendimenti marginali fortemente decrescenti. Nel punto della frontiera selezionato da un dato , l'avversione al rischio è la pendenza (negativa) della tangente: .

La frontiera riformula la domanda "quanto vale ?" — a cui nessuno sa rispondere per introspezione — in "quale trade-off costo/rischio voglio?", a cui un desk può effettivamente rispondere. Tre modi pratici per scegliere il punto:
- Ragionamento a costo marginale. Percorrete la frontiera e chiedetevi: "spostandomi da questo punto al successivo, pago dollari di costo atteso in più per rimuovere dollari di deviazione standard dello shortfall — accetto questo scambio?" Il ginocchio della frontiera è di solito evidente entro un fattore 2, e la traiettoria è insensibile a a quella risoluzione.
- Budget di rischio. Fissate una deviazione standard massima accettabile dello shortfall (ad es. "l'1-sigma giornaliero della posizione residua deve restare sotto i 15 bps del nozionale") e prendete la traiettoria più economica che la soddisfa. Questo è un problema vincolato il cui moltiplicatore di Lagrange è .
- Targeting del tempo caratteristico. Scegliete direttamente ("questo ordine dovrebbe avere un'emivita di 90 minuti") e ricavate . È ciò che la maggior parte dei practitioner fa implicitamente quando imposta uno slider di "urgenza".
Un esempio numerico con valori che giustificheremo nella sezione di calibrazione. Vendete BTC a S_0 = \100{,}000T = 4\sigma = $600\sqrt{\text{h}}\eta = 1.0\ $\cdot\text{h}/\text{BTC}\lambda = 10^{-6}\ $^{-1}$. Allora
Lo schedule ottimale vende 46.2 BTC nella prima ora (TWAP: 25). Costo temporaneo: \eta \int_0^T v(t)^2 dt \approx \3{,}290$2{,}500$53{,}000$69{,}300 per il TWAP. Quindi per 0.8 bps in più di costo atteso su un nozionale da \10M si riduce il rischio di shortfall del 23%. Questo è l'intero contenuto del modello in una frase: prezza l'assicurazione e vi lascia decidere se comprarla.
La traiettoria stessa è dieci righe di Python:
import numpy as np
def almgren_chriss(X, T, N, sigma, eta, gamma, lam):
"""Optimal liquidation trajectory (discrete AC 2001)."""
tau = T / N
eta_t = eta - 0.5 * gamma * tau # tilde-eta
kappa_t2 = lam * sigma**2 / eta_t # tilde-kappa^2
kappa = np.arccosh(0.5 * kappa_t2 * tau**2 + 1) / tau
t = np.arange(N + 1) * tau
x = X * np.sinh(kappa * (T - t)) / np.sinh(kappa * T)
n = x[:-1] - x[1:] # child order sizes
E = 0.5 * gamma * X**2 + (eta_t / tau) * np.sum(n**2)
V = sigma**2 * tau * np.sum(x[1:]**2)
return x, n, E, V
x, n, E, V = almgren_chriss(X=100, T=4, N=48, sigma=600,
eta=1.0, gamma=0.3, lam=1e-6)
print(f"first-hour qty: {n[:12].sum():.1f} BTC, "
f"E=${E:,.0f}, std=${np.sqrt(V):,.0f}")
Fate variare lam su np.logspace(-8, -4, 50) e plottate rispetto a — quella è la vostra frontiera, e l'intera superficie decisionale sta su un unico grafico che potete consegnare a un risk committee.
Calibrazione: , , da dati Binance
Qui è dove il 90% delle implementazioni muore in silenzio. La formula della traiettoria è banale; i parametri no. Tre grandezze, tre problemi di stima diversi.

: quello facile
Volatilità assoluta per radice-ora, dai rendimenti del mid-price. Usate barre a 1 minuto e riscalate; a campionamenti più fini, il rumore di microstruttura (bid-ask bounce) distorce verso l'alto la varianza realizzata. Questa stima è solida — la vol è l'unico parametro che otterrete correttamente.
: impatto temporaneo dal book e dai trade
Due strade complementari. Strada A — percorrere il book L2. Dagli snapshot di profondità, calcolate il costo di uno sweep marketable ipotetico di dimensione : il VWAP dei livelli consumati meno il mid. Il fit di dà l'impatto istantaneo per BTC. Per convertirlo nell' basato sul ritmo del modello dovete assumere un tempo di ricostituzione del book (dell'ordine di 10–60 s per BTCUSDT): negoziare al ritmo consuma per ciclo di refresh, quindi . Questa assunzione — ricostituzione istantanea e completa su un orologio fisso — è esattamente la crepa da cui entra Obizhaeva-Wang; ne parliamo più avanti. Strada B — regredire lo slippage realizzato sul participation rate. Raggruppate il flusso aggressivo (taker) in bin da 1 minuto; per ogni bin, regredite il VWAP lato taker meno l'open-mid sul tasso di volume taker. La strada B misura ciò che il mercato ha effettivamente addebitato agli aggressori; la strada A misura ciò che il book in attesa addebiterebbe in questo momento. Quando differiscono di un fattore 3, fidatevi di B per il livello e di A per la forma intraday.
: impatto permanente tramite regressione in stile Kyle
Regredite le variazioni del mid-price sul flusso taker netto firmato su finestre di 5 minuti:
Poi verificate che l'impatto non sia rientrato una finestra dopo — la componente non revertente è "permanente" alla vostra scala temporale di trading. Questa è di gran lunga la più rumorosa delle tre.
Codice funzionante, usando solo endpoint pubblici REST di Binance:
import requests, numpy as np, pandas as pd
B = "https://api.binance.com/api/v3"
sym = "BTCUSDT"
kl = requests.get(f"{B}/klines", params=dict(
symbol=sym, interval="1m", limit=1000)).json()
close = np.array([float(k[4]) for k in kl])
sigma = np.diff(close).std() * np.sqrt(60) # $/sqrt(h)
d = requests.get(f"{B}/depth",
params=dict(symbol=sym, limit=5000)).json()
bids = np.array(d["bids"], dtype=float) # [price, qty]
mid = (bids[0, 0] + float(d["asks"][0][0])) / 2
cq = np.cumsum(bids[:, 1]) # cum qty
cn = np.cumsum(bids[:, 0] * bids[:, 1]) # cum notional
sizes = np.linspace(0.5, 50, 40) # BTC probes
cost = [mid - np.interp(q, cq, cn) / q for q in sizes]
eps, eta_inst = np.polyfit(sizes, cost, 1)[::-1] # cost ~ eps + k*q
eta = eta_inst * (30 / 3600) # 30s refresh -> $*h/BTC
tr = requests.get(f"{B}/aggTrades",
params=dict(symbol=sym, limit=1000)).json()
df = pd.DataFrame(dict(
t=[t["T"] for t in tr],
p=[float(t["p"]) for t in tr],
q=[float(t["q"]) * (-1 if t["m"] else 1) for t in tr]))
df["bin"] = df.t // 300_000 # 5-min bins
g = df.groupby("bin").agg(dp=("p", lambda s: s.iloc[-1] - s.iloc[0]),
qn=("q", "sum"))
gamma = np.polyfit(g.qn, g.dp, 1)[0] # $/BTC
print(f"sigma={sigma:.0f} $/sqrt(h) eta={eta:.2f} $*h/BTC "
f"gamma={gamma:.3f} $/BTC")
Per la produzione, sostituite la singola pagina di aggTrades con qualche giorno di dump storici ed eseguite la regressione di su migliaia di bin, non su una manciata.
Perché la calibrazione su crypto è rumorosa, e cosa farci
Eseguite la regressione di su dati reali e otterrete un di pochi punti percentuali e un coefficiente che si sposta di un fattore 2–5 tra un giorno e l'altro. Non è un bug nel vostro codice. Le ragioni sono strutturali:
- Endogeneità. Il flusso risponde al prezzo tanto quanto il prezzo risponde al flusso. I trader momentum comprano perché il prezzo è salito; una regressione OLS ingenua dei rendimenti sul flusso cattura la loro reazione e la attribuisce all'impatto. La soluzione pulita è usare i propri fill (esogeni per costruzione) — che si hanno solo dopo aver negoziato per un po'.
- Concavità. L'impatto reale è concavo nella dimensione — empiricamente vicino a una radice quadrata (Almgren, Thum, Hauptmann e Li, 2005, "Direct estimation of equity market impact", hanno trovato un esponente vicino a 0.6; la stessa concavità è robusta anche nelle crypto). Fittare una retta a una funzione concava significa che e dipendono dall'intervallo di dimensioni nel campione. Calibrate ai participation rate a cui negozierete effettivamente.
- Frammentazione e leadership dei derivati. BTCUSDT spot su Binance è una venue tra molte, e la price discovery avviene spesso sui perpetual. Il flusso che non vedete muove il prezzo su cui regredite, gonfiando il termine di rumore.
- Dipendenza dal regime. L' misurato nella sessione asiatica non descrive l'apertura USA; l'impatto raddoppia più o meno quando la volatilità raddoppia. Calibrate per sessione, e ri-fittate almeno settimanalmente.
L'aspetto salvifico: la traiettoria è indulgente. , quindi un errore di un fattore 2 su sposta solo di , e la funzione di costo è piatta vicino all'ottimo. Ottenere corretto entro un fattore 2 e corretto entro un ordine di grandezza cattura già la maggior parte del miglioramento disponibile rispetto al TWAP. La precisione conta per le stime di costo pre-trade; la robustezza è sufficiente per la pianificazione.
Dove il modello si rompe, e gli articoli che lo correggono
Almgren-Chriss è un'impalcatura, e sapere esattamente quale trave è portante indica a quale estensione rivolgersi.
Impatto non lineare — Almgren (2003). "Optimal execution with nonlinear impact functions and trading-enhanced risk" (Applied Mathematical Finance 10, 1–18) rifà il programma con impatto temporaneo a legge di potenza . Per l'esponente empiricamente favorito — la legge della radice quadrata — le traiettorie ottimali cambiano carattere: l'impatto concavo punisce le raffiche meno di quanto faccia l'impatto lineare, quindi gli schedule ottimali diventano più front-loaded a parità di . La struttura qualitativa (parametro di urgenza, frontiera efficiente) sopravvive; la formula sinh no.
Resilienza e il LOB — Obizhaeva e Wang (2013). "Optimal trading strategy and supply/demand dynamics" (Journal of Financial Markets 16(1), 1–32; il working paper circolava dal 2005) sostituisce la dicotomia temporaneo/permanente con un limit order book a profondità finita e impatto a decadimento esponenziale: il vostro trade consuma il book, e il book si ricostituisce al tasso di resilienza . L'assunzione di Almgren-Chriss "l'impatto temporaneo svanisce istantaneamente" è il limite . La strategia ottimale cambia forma in modo drastico: un blocco di trade all'inizio, un blocco alla fine, e un tasso di negoziazione costante nel mezzo — i blocchi sfruttano il recupero del book. Se il vostro intervallo tra child order è paragonabile al tempo di ricostituzione della venue (nel crypto, da secondi a un minuto — spesso è così), siete in territorio Obizhaeva-Wang, non Almgren-Chriss, e l'artificio nella calibrazione sopra è il vostro segnale d'allarme.
No-dynamic-arbitrage — Gatheral (2010). "No-dynamic-arbitrage and market impact" (Quantitative Finance 10(7), 749–759) chiede quali combinazioni di funzione di impatto istantaneo e kernel di decadimento sono internamente coerenti, ossia non ammettono strategie di round-trip con costo atteso negativo. I risultati sono netti: il decadimento esponenziale è compatibile solo con impatto lineare — combinare decadimento esponenziale con impatto a radice quadrata rende il modello sfruttabile per profitto tramite una sequenza di trade oscillante; l'impatto non lineare richiede decadimento a legge di potenza, con una disuguaglianza che lega i due esponenti (per impatto e decadimento , all'incirca ). Questo è l'articolo da leggere prima di innestare un fantasioso kernel di decadimento sul vostro modello di impatto: la maggior parte delle combinazioni ad hoc sono segretamente arbitraggiabili, il che in pratica significa che il vostro ottimizzatore scoprirà l'arbitraggio e produrrà schedule oscillanti assurdi. Se la vostra traiettoria "ottimale" alterna acquisti e vendite durante una pura liquidazione, avete violato Gatheral, non scoperto alpha.
I seguiti basati su ML. Due filoni di questa serie si costruiscono direttamente su questa impalcatura. Primo, l'esecuzione via reinforcement learning: una volta ammesso che l'impatto è non lineare, dipendente dallo stato e parzialmente osservabile, la forma chiusa scompare, e l'RL (dal paper di Q-learning del 2006 di Nevmyvaka, Feng e Kearns in poi) è un modo naturale per esplorare lo spazio delle traiettorie — ma ogni agente RL di esecuzione serio viene benchmarkato contro, e spesso inizializzato dalla, soluzione AC. Secondo, i modelli neurali di price impact: sostituire con un funzionale appreso dello stato dell'order book, mantenendo sopra la logica di scheduling media-varianza. Nessuno dei due seguiti ha senso senza sapere esattamente cos'è il baseline lineare-gaussiano e perché ha quella forma.
Cosa portare a casa
Il modello è tre assunzioni, un'equazione alle differenze e un numero. Impatto permanente lineare (imposto dall'assenza di manipolazione), impatto temporaneo lineare (un'approssimazione attorno alla quale si calibra), rumore browniano aritmetico. L'ottimo bilancia la varianza di inventario contro il costo di impatto, l'intera strategia collassa nell'urgenza , e il TWAP emerge come il caso degenere — che è il fatto singolarmente più utile del modello, perché trasforma "dovremmo fare TWAP su questo?" da questione di abitudine in un'affermazione verificabile sull'avversione al rischio. La calibrazione è il vero lavoro: è facile, è un gioco a fattore 2, è un progetto di ricerca, e la traiettoria perdona tutti e tre. Implementatelo in un pomeriggio; spendete il resto del mese sulla diagnostica della regressione.
Riferimenti bibliografici
- Almgren, R., Chriss, N. (2001). "Optimal execution of portfolio transactions." Journal of Risk, 3(2), 5–39.
- Almgren, R. (2003). "Optimal execution with nonlinear impact functions and trading-enhanced risk." Applied Mathematical Finance, 10(1), 1–18.
- Almgren, R., Thum, C., Hauptmann, E., Li, H. (2005). "Direct estimation of equity market impact." Risk, 18(7), 58–62.
- Huberman, G., Stanzl, W. (2004). "Price manipulation and quasi-arbitrage." Econometrica, 72(4), 1247–1275.
- Obizhaeva, A., Wang, J. (2013). "Optimal trading strategy and supply/demand dynamics." Journal of Financial Markets, 16(1), 1–32.
- Gatheral, J. (2010). "No-dynamic-arbitrage and market impact." Quantitative Finance, 10(7), 749–759.
- Nevmyvaka, Y., Feng, Y., Kearns, M. (2006). "Reinforcement learning for optimized trade execution." ICML 2006.
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.