Almgren-Chriss zonder de vaagheid: optimale executie die je in een middag kunt implementeren
Almgren en Chriss (2001), "Optimal execution of portfolio transactions" (Journal of Risk 3(2), 5–39), is waarschijnlijk het meest geciteerde paper in executieonderzoek en een van de minst correct geïmplementeerde. De meeste "Almgren-Chriss"-code in het wild is een TWAP-scheduler met een commentaarblok. De meeste blogposts erover slaan de afleiding over, wuiven naar een sinh-functie, en raken nooit het deel dat er in productie werkelijk toe doet: waar de parameters vandaan komen.
Dit artikel doet het complete verhaal. De modelaannames, eerlijk gepresenteerd. De closed-form trajectorie, afgeleid, niet beweerd. De efficiënte grens en hoe je de risicoaversieparameter kiest. En kalibratie van de drie inputs — tijdelijke impact , permanente impact , volatiliteit — op basis van Binance orderbook- en tradedata, met werkende Python en een eerlijke discussie over waarom de fit ruizig is. Dit is de intellectuele ruggengraat van al het andere in executie: TWAP, VWAP en POV zijn speciale gevallen of heuristische neefjes (zie TWAP, VWAP, POV: de executiealgoritmes die iedereen draait), en de moderne ML-benaderingen — reinforcement-learning-schedulers en neurale impactmodellen — zijn pogingen om precies de aannames te versoepelen die we zo dadelijk gaan opschrijven.
De opzet: wat het model werkelijk aanneemt
Je houdt eenheden van een asset (zeg 100 BTC) en moet volledig liquideren vóór tijdstip . Verdeel in intervallen van lengte . Je beslissingsvariabele is de holdings-trajectorie , met trades uitgevoerd in interval .
Drie aannames dragen het hele model:
1. Rekenkundige random walk. De ongestoorde prijs volgt
waarbij i.i.d. standaardnormalen zijn en absolute volatiliteit is (dollars per eenheid tijd, niet procent). Rekenkundig, niet geometrisch — over een paar uur is het verschil verwaarloosbaar en de rekenkundige walk houdt de algebra lineair-kwadratisch. Geen drift: Almgren en Chriss bespreken de driftterm, maar voor een intraday-liquidatie is je alpha-schatting over 4 uur bijna altijd ruis, en die op nul zetten is de eerlijke default.
2. Lineaire permanente impact. : handelen tegen snelheid verschuift de prijs permanent, proportioneel, en de verschuiving vervalt nooit. Elke eenheid die je verkoopt drukt de mid voor altijd omlaag met . Dit oogt grof, maar er is een diepe reden om het lineair te houden: Huberman en Stanzl (2004) bewezen dat permanente impact die niet-lineair is in de tradegrootte round-trip-strategieën toelaat met positieve verwachte winst — prijsmanipulatie. Lineaire permanente impact is geen vereenvoudiging uit gemakzucht; het is de enige arbitragevrije keuze binnen deze klasse modellen.
3. Lineaire tijdelijke impact. De prijs die je daadwerkelijk ontvangt in interval is
vangt de halve bid-ask spread plus fees; is de helling van de marginale kost van het opeisen van liquiditeit tegen snelheid . Tijdelijke impact raakt alleen jouw eigen fill en verdwijnt onmiddellijk — het boek herstelt volledig vóór je volgende child order. Elk van de drie woorden "lineair", "onmiddellijk", "volledig" klopt niet in echte markten, en de laatste sectie gaat over hoezeer niet. Maar eerst de opbrengst van ze te accepteren.
Kosten en variantie van een trajectorie
Tel de fills op, trek af van de initiële mark , en je krijgt de implementation shortfall. De verwachting en variantie ervan over de ruis zijn:
met (een discretisatiecorrectie die verdwijnt als ).
Twee observaties die de meeste implementaties missen:
- De permanente kost hangt niet af van de trajectorie. Met lineaire, niet-vervallende permanente impact betaal je dezelfde permanente tol ongeacht hoe je plant. De optimalisatie is volledig een gevecht tussen de tijdelijke-kostenterm (die trage, gelijkmatige handel wil — geminimaliseerd door , dus TWAP) en de variantieterm (die wil dat je inventory gisteren al weg was — geminimaliseerd door onmiddellijke liquidatie).
- De variantieterm is gewogen naar inventory, niet naar trades. Risico accumuleert op wat je nog aanhoudt, , niet op wat je verhandelt. Daarom laadt urgentie het schema naar voren.
De closed form: sinh, cosh en de urgentieparameter
Almgren-Chriss minimaliseren het mean-variance objectief
waarbij risicoaversie is in eenheden van 1/dollar. Stel voor de interne punten. De tijdelijke term levert tweede verschillen van , de variantieterm levert zelf, en je krijgt een lineaire tweede-orde verschilvergelijking:
Dit is het discrete analogon van , en met randvoorwaarden , is de oplossing hyperbolisch:
waarbij voldoet aan , wat voor kleine simpelweg is.

Alles aan de strategie is samengeperst tot één getal:
is de urgentie. De reciproque is de karakteristieke tijd van de trade: de tijdschaal waarover het de moeite waard is inventory-risico aan te houden om impactkosten te besparen. Merk op waar niet van afhangt: de ordergrootte en de deadline . Of de intrinsieke tijdschaal van je trade 20 minuten of 6 uur is, wordt uitsluitend bepaald door risicoaversie, volatiliteit en liquiditeit. Als , is je deadline irrelevant — het model liquideert op zijn eigen schema en de staart van de horizon blijft ongebruikt. Als , bindt de deadline en doe je in feite TWAP.
De risiconeutrale limiet is TWAP — dit is waarom TWAP bestaat
Neem , dus . Dan geldt en
De optimale trajectorie degenereert tot de rechte lijn: gelijke hoeveelheden in gelijke tijdsintervallen. TWAP is geen heuristiek die toevallig werkt; het is het exacte optimum van het Almgren-Chriss-model voor een risiconeutrale trader met lineaire impact. Elke keer dat iemand TWAP draait, beweert diegene impliciet : "Het kan me niet schelen wat de variantie van mijn shortfall is, alleen het gemiddelde." Dat is een verdedigbaar standpunt voor kleine orders en korte horizonten. Het is een merkwaardig standpunt voor het liquideren van 5% van het dagvolume over 8 uur in een asset met 4% dagelijkse vol, wat precies is waar mensen het toch op toepassen. De tegenovergestelde limiet geeft voor alle : alles dumpen in het eerste interval, betalen wat het ook kost. Tussen deze extremen interpoleert : is nauwelijks te onderscheiden van TWAP, is agressief naar voren geladen.
De efficiënte grens van executie
Voor elke krijg je één trajectorie, één verwachte kost , en één variantie . Door te doorlopen teken je een curve in het -vlak — de efficiënte grens van executie, in directe analogie met Markowitz. Ze is convex en dalend: minder variantie kost altijd meer verwachte shortfall, met sterk afnemende meeropbrengsten. Op het grenspunt gekozen door een gegeven is de risicoaversie de (negatieve) helling van de raaklijn: .

De grens herformuleert de vraag "wat is ?" — die niemand introspectief kan beantwoorden — naar "welke kosten/risico-afweging wil ik?", die een desk daadwerkelijk kan beantwoorden. Drie praktische manieren om het punt te kiezen:
- Marginale-kostenredenering. Loop de grens af en vraag: "als ik van dit punt naar het volgende ga, betaal ik dollar verwachte kost om dollar standaarddeviatie van shortfall te verwijderen — neem ik die trade?" De knik in de grens is meestal duidelijk binnen een factor 2, en de trajectorie is ongevoelig voor op die resolutie.
- Risicobudget. Leg een maximaal aanvaardbare shortfall-std vast (bijv. "de één-dags 1-sigma van de resterende positie moet onder 15 bps van het notional blijven") en neem de goedkoopste trajectorie die daaraan voldoet. Dit is een beperkt probleem waarvan de Lagrange-multiplicator precies is.
- Targeting van karakteristieke tijd. Kies direct ("deze order moet een halfwaardetijd van 90 minuten hebben") en leid af. Dit is wat de meeste practitioners impliciet doen wanneer ze een "urgentie"-schuifje instellen.
Een uitgewerkt voorbeeld met cijfers die we in de kalibratiesectie zullen onderbouwen. Verkoop BTC bij S_0 = \100{,}000T = 4\sigma = $600\sqrt{\text{u}}\eta = 1.0\ $\cdot\text{u}/\text{BTC}\lambda = 10^{-6}\ $^{-1}$. Dan
Het optimale schema verkoopt 46.2 BTC in het eerste uur (TWAP: 25). Tijdelijke kost: \eta \int_0^T v(t)^2 dt \approx \3{,}290$2{,}500$53{,}000$69{,}300 voor TWAP. Dus voor een extra 0.8 bps verwachte kost op het \10M notional verlaag je het shortfall-risico met 23%. Dat is de gehele inhoud van het model in één zin: het prijst de verzekering, en laat jou beslissen of je die koopt.
De trajectorie zelf is tien regels 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}")
Doorloop lam met np.logspace(-8, -4, 50) en plot tegen — dat is je grens, en het hele beslissingsoppervlak past op één chart die je aan een risicocommissie kunt geven.
Kalibratie: , , uit Binance-data
Hier sterven 90% van de implementaties in stilte. De trajectorie-formule is triviaal; de parameters zijn dat niet. Drie grootheden, drie verschillende schattingsproblemen.

: de makkelijke
Absolute volatiliteit per wortel-uur, uit mid-price returns. Gebruik 1-minuutbars en schaal; bij fijnere sampling zorgt microstructuurruis (bid-ask bounce) voor een opwaartse vertekening van de gerealiseerde variantie. Deze schatting is solide — vol is de ene parameter die je goed krijgt.
: tijdelijke impact uit het boek en uit trades
Twee complementaire routes. Route A — het L2-boek doorlopen. Bereken vanuit depth-snapshots de kost van een hypothetische marketable sweep van grootte : de VWAP van de geconsumeerde niveaus minus de mid. Het fitten van geeft instantane impact per BTC. Om dit om te zetten in de snelheidsgebaseerde van het model moet je een boek-hervultijd aannemen (in de orde van 10–60 s voor BTCUSDT): handelen tegen snelheid verbruikt per verversingscyclus, dus . Die aanname — onmiddellijke, volledige hervulling op een vaste klok — is precies de scheur waardoor Obizhaeva-Wang binnenkomt; hieronder meer. Route B — gerealiseerde slippage regresseren op participatie. Verdeel agressieve (taker) flow in bins van 1 minuut; regresseer voor elke bin de taker-side VWAP-minus-open-mid tegen de taker-volumesnelheid. Route B meet wat de markt daadwerkelijk aan aggressors in rekening bracht; Route A meet wat het rustende boek er nu voor zou rekenen. Als ze een factor 3 verschillen, vertrouw dan B voor het niveau en A voor de intraday-vorm.
: permanente impact via Kyle-achtige regressie
Regresseer mid-prijsveranderingen op signed netto taker-flow over vensters van 5 minuten:
Controleer vervolgens dat de impact niet één venster later reverteert — de niet-reverterende component is "permanent" op je handelstijdschaal. Dit is verreweg de ruizigste van de drie.
Werkende code, met alleen publieke Binance REST-endpoints:
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")
Vervang voor productie de enkele aggTrades-pagina door een paar dagen aan historische dumps en draai de -regressie op duizenden bins, niet een handjevol.
Waarom crypto-kalibratie ruizig is, en wat je eraan doet
Draai de -regressie op echte data en je krijgt een van een paar procent en een coëfficiënt die tussen dagen met een factor 2–5 beweegt. Dit is geen bug in je code. De redenen zijn structureel:
- Endogeniteit. Flow reageert net zo goed op prijs als prijs op flow. Momentum-traders kopen omdat de prijs steeg; een naïeve OLS van returns op flow pikt hun reactie op en schrijft die toe aan impact. De schone oplossing is het gebruiken van je eigen fills (exogeen per definitie) — die je pas hebt nadat je een tijdje hebt gehandeld.
- Concaviteit. Echte impact is concaaf in grootte — empirisch dicht bij vierkantswortel (Almgren, Thum, Hauptmann en Li, 2005, "Direct estimation of equity market impact", vonden een exponent nabij 0.6; dezelfde concaviteit is robuust in crypto). Een rechte lijn fitten op een concave functie betekent dat je en afhangen van het groottebereik in de steekproef. Kalibreer op de participatiegraden waarop je daadwerkelijk gaat handelen.
- Fragmentatie en derivatenleiderschap. BTCUSDT spot op Binance is één venue van vele, en prijsontdekking vindt vaak plaats op perps. Flow die je nooit ziet beweegt de prijs waarop je regresseert, wat de ruisterm opblaast.
- Regime-afhankelijkheid. gemeten in de Aziatische sessie beschrijft de US-open niet; impact verdubbelt ruwweg wanneer volatiliteit verdubbelt. Kalibreer per sessie, en herfit minstens wekelijks.
De reddende genade: de trajectorie is vergevingsgezind. , dus een factor-2-fout in verplaatst met slechts , en de kostenfunctie is vlak nabij het optimum. correct krijgen binnen een factor 2 en binnen een orde van grootte vangt al het grootste deel van de beschikbare verbetering ten opzichte van TWAP. Precisie doet ertoe voor pre-trade kostenschattingen; robuustheid volstaat voor scheduling.
Waar het model breekt, en de papers die het repareren
Almgren-Chriss is een steiger, en precies weten welke balk dragend is vertelt je welke uitbreiding je moet pakken.
Niet-lineaire impact — Almgren (2003). "Optimal execution with nonlinear impact functions and trading-enhanced risk" (Applied Mathematical Finance 10, 1–18) herwerkt het programma met machtswet tijdelijke impact . Voor de empirisch bevoorrechte — de vierkantswortelwet — veranderen de optimale trajectorieën van karakter: concave impact straft uitbarstingen minder af dan lineaire impact, waardoor optimale schema's bij dezelfde sterker naar voren worden geladen. De kwalitatieve structuur (urgentieparameter, efficiënte grens) overleeft; de sinh-formule niet.
Veerkracht en het LOB — Obizhaeva en Wang (2013). "Optimal trading strategy and supply/demand dynamics" (Journal of Financial Markets 16(1), 1–32; het working paper circuleerde vanaf 2005) vervangt de tijdelijke/permanente tweedeling door een limit order book met eindige diepte en exponentieel vervallende impact: je trade eet het boek op, en het boek vult zich weer op met veerkrachtsnelheid . De AC-aanname "tijdelijke impact verdwijnt onmiddellijk" is de limiet . De optimale strategie verandert dramatisch van vorm: een blok aan het begin, een blok aan het eind, en een constante handelssnelheid ertussenin — de blokken benutten het herstel van het boek. Als je child-order-interval vergelijkbaar is met de hervultijd van de venue (bij crypto, seconden tot een minuut — vaak het geval), zit je in Obizhaeva-Wang-territorium, niet Almgren-Chriss-territorium, en is de -fudge in de kalibratie hierboven je waarschuwingssignaal.
No-dynamic-arbitrage — Gatheral (2010). "No-dynamic-arbitrage and market impact" (Quantitative Finance 10(7), 749–759) vraagt welke combinaties van instantane impactfunctie en vervalkernel intern consistent zijn, d.w.z. geen round-trip-strategie met negatieve verwachte kost toelaten. De resultaten zijn scherp: exponentieel verval is alleen verenigbaar met lineaire impact — combineer exponentieel verval met vierkantswortelimpact en het model kan leeggepompt worden door een oscillerende tradesequentie; niet-lineaire impact vereist machtswetverval, met een ongelijkheid die de twee exponenten koppelt (voor impact en verval , ruwweg ). Dit is het paper om te lezen voordat je een fancy vervalkernel op je impactmodel bout: de meeste ad-hoc-combinaties zijn heimelijk arbitreerbaar, wat in de praktijk betekent dat je optimizer de arbitrage ontdekt en absurde oscillerende schema's produceert. Als je "optimale" trajectorie tijdens een pure liquidatie afwisselend koopt en verkoopt, heb je Gatheral geschonden, geen alpha ontdekt.
De ML-vervolgen. Twee draden in deze reeks bouwen direct op deze steiger voort. Ten eerste, reinforcement-learning-executie: zodra je toegeeft dat impact niet-lineair, toestandsafhankelijk en gedeeltelijk observeerbaar is, is de closed form weg, en RL (vanaf Nevmyvaka, Feng en Kearns' Q-learning-paper uit 2006) is een natuurlijke manier om de trajectorieruimte te doorzoeken — maar elke serieuze RL-executieagent wordt benchmarked tegen, en vaak geïnitialiseerd vanuit, de AC-oplossing. Ten tweede, neurale prijsimpactmodellen: vervangen door een geleerde functionaal van de orderbook-toestand, terwijl de mean-variance-schedulinglogica erbovenop blijft staan. Geen van beide vervolgen is zinvol zonder precies te weten wat de lineair-Gaussische baseline is en waarom die die vorm heeft.
Wat je moet onthouden
Het model bestaat uit drie aannames, één verschilvergelijking, en één getal. Lineaire permanente impact (afgedwongen door geen manipulatie), lineaire tijdelijke impact (een benadering waar je omheen kalibreert), rekenkundige Brownse ruis. Het optimum wisselt inventory-variantie af tegen impactkost, de gehele strategie valt samen in de urgentie , en TWAP valt eruit als het gedegenereerde geval — wat het nuttigste feit in het hele model is, omdat het "moeten we dit TWAP'en?" verandert van een kwestie van gewoonte naar een controleerbare bewering over risicoaversie. Kalibratie is het echte werk: is makkelijk, is een factor-2-spel, is een onderzoeksproject, en de trajectorie vergeeft alle drie. Implementeer het in een middag; besteed de rest van de maand aan de regressiediagnostiek.
Referenties
- 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.
Auteurs
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.