Алмгрен-Чрисс без размахивания руками: оптимальное исполнение, которое можно реализовать за один вечер
Работа Алмгрена и Чрисса (2001), "Optimal execution of portfolio transactions" (Journal of Risk 3(2), 5–39), — вероятно, самая цитируемая статья в исследованиях по исполнению и одна из наименее корректно реализованных. Большая часть кода "Алмгрена-Чрисса", встречающегося в дикой природе, — это планировщик TWAP с блоком комментариев. Большинство постов в блогах о ней пропускают вывод, машут рукой в сторону функции sinh и никогда не касаются той части, которая реально важна в продакшене: откуда берутся параметры.
Эта статья разбирает все целиком. Допущения модели, названные честно. Замкнутую траекторию, выведенную, а не постулированную. Эффективную границу и то, как выбирать параметр неприятия риска. И калибровку трех входных величин — временного импакта , постоянного импакта , волатильности — по данным стакана заявок и сделок Binance, с рабочим Python и честным разбором того, почему подгонка получается шумной. Это интеллектуальный стержень всего остального в исполнении: TWAP, VWAP и POV — это частные случаи или эвристические родственники (см. TWAP, VWAP, POV: алгоритмы исполнения, которые запускают все), а современные подходы на основе машинного обучения — планировщики на обучении с подкреплением и нейросетевые модели импакта — это попытки ослабить ровно те допущения, которые мы сейчас выпишем.
Постановка: что модель на самом деле предполагает
У вас есть единиц актива (скажем, 100 BTC), и вы должны полностью ликвидировать позицию к моменту . Разбейте на интервалов длины . Ваша управляющая переменная — траектория позиции , где сделки исполняются в интервале .
Всю модель несут три допущения:
1. Арифметическое случайное блуждание. Невозмущенная цена следует уравнению
где — независимые одинаково распределенные стандартные нормальные величины, а — это абсолютная волатильность (доллары на единицу времени, а не проценты). Арифметическое, а не геометрическое: за несколько часов разница пренебрежимо мала, а арифметическое блуждание сохраняет алгебру линейно-квадратичной. Без сноса: Алмгрен и Чрисс обсуждают член со сносом, но для внутридневной ликвидации ваша оценка альфы на горизонте 4 часа почти всегда шум, и обнулить ее — честное значение по умолчанию.
2. Линейный постоянный импакт. : торговля со скоростью сдвигает цену навсегда, пропорционально, и сдвиг никогда не затухает. Каждая проданная единица толкает середину вниз на навсегда. Это выглядит грубо, но есть глубокая причина оставить его линейным: Хуберман и Штанцль (2004) доказали, что постоянный импакт, нелинейный по объему сделки, допускает стратегии кругового оборота с положительной ожидаемой прибылью — то есть манипуляцию ценой. Линейный постоянный импакт — не упрощение ради удобства; это единственный безарбитражный выбор в этом классе моделей.
3. Линейный временный импакт. Цена, которую вы реально получаете в интервале , равна
учитывает половину бид-аск спреда плюс комиссии; — наклон предельной стоимости требования ликвидности со скоростью . Временный импакт влияет только на ваше собственное исполнение и исчезает мгновенно — стакан полностью восстанавливается до вашей следующей дочерней заявки. Каждое из трех слов — "линейный", "мгновенно", "полностью" — неверно на реальных рынках, и последний раздел о том, насколько неверно. Но сначала — выигрыш от того, чтобы их принять.
Стоимость и дисперсия траектории
Просуммируйте исполнения, вычтите из начальной оценки , и вы получите implementation shortfall (издержки реализации). Его математическое ожидание и дисперсия по шуму равны:
где (поправка дискретизации, исчезающая при ).
Два наблюдения, которые большинство реализаций упускают:
- Постоянная стоимость не зависит от траектории. При линейном, незатухающем постоянном импакте вы платите одну и ту же постоянную пошлину независимо от того, как расписываете сделки. Оптимизация целиком сводится к борьбе между членом временной стоимости (который хочет медленной, равномерной торговли — он минимизируется при , то есть TWAP) и членом дисперсии (который хочет, чтобы вашей позиции не стало еще вчера — он минимизируется мгновенной ликвидацией).
- Член дисперсии взвешен по позиции, а не по объему сделок. Риск накапливается на том, что вы еще держите, , а не на том, чем торгуете. Именно поэтому срочность смещает расписание к началу.
Замкнутая форма: sinh, cosh и параметр срочности
Алмгрен-Чрисс минимизируют средне-дисперсионный функционал
где — неприятие риска в единицах 1/доллар. Приравняйте для внутренних точек. Временный член дает вторые разности , член дисперсии дает сам , и вы получаете линейное разностное уравнение второго порядка:
Это дискретный аналог , и с граничными условиями , решение гиперболическое:
где есть решение уравнения , что для малых сводится просто к .

Все, что касается стратегии, сжато в одно число:
— это срочность. Обратная величина — характерное время сделки: масштаб времени, на протяжении которого имеет смысл держать риск позиции ради экономии на стоимости импакта. Обратите внимание, от чего не зависит: от размера ордера и от дедлайна . То, составляет ли внутренний временной масштаб вашей сделки 20 минут или 6 часов, определяется исключительно неприятием риска, волатильностью и ликвидностью. Если , ваш дедлайн неважен — модель ликвидирует по своему собственному расписанию, и хвост горизонта остается неиспользованным. Если , дедлайн становится связывающим, и вы фактически делаете TWAP.
Риск-нейтральный предел — это TWAP, и вот почему TWAP существует
Возьмите , так что . Тогда и
Оптимальная траектория вырождается в прямую линию: равные объемы за равные промежутки времени. TWAP — это не эвристика, которая случайно работает; это точный оптимум модели Алмгрена-Чрисса для риск-нейтрального трейдера с линейным импактом. Каждый раз, когда кто-то запускает TWAP, он неявно утверждает, что : "мне неважна дисперсия моих издержек реализации, важно только их среднее". Это защитимая позиция для небольших ордеров и коротких горизонтов. Это странная позиция для ликвидации 5% дневного объема за 8 часов в активе с дневной волатильностью 4%, — а именно там его все равно и запускают. Противоположный предел дает для всех : выбросить все в первом интервале, заплатив сколько бы это ни стоило. Между этими крайностями интерполирует: почти неотличимо от TWAP, а агрессивно смещено к началу.
Эффективная граница исполнения
Для каждого вы получаете одну траекторию, одну ожидаемую стоимость и одну дисперсию . Пробегая , вы вычерчиваете кривую в плоскости — эффективную границу исполнения, в прямой аналогии с Марковицем. Она выпукла и убывает: меньшая дисперсия всегда стоит большего ожидаемого shortfall, с резко убывающей отдачей. В точке границы, выбранной данным , неприятие риска равно (отрицательному) наклону касательной: .

Граница переформулирует вопрос "чему равно ?" — на который никто не может ответить интроспективно — в вопрос "какой компромисс между стоимостью и риском я хочу?", на который трейдинговый деск реально способен ответить. Три практических способа выбрать точку:
- Рассуждение через предельную стоимость. Пройдите вдоль границы и спросите: "переходя из этой точки в следующую, я плачу долларов ожидаемой стоимости, чтобы убрать долларов стандартного отклонения shortfall, — беру ли я эту сделку?". Излом границы обычно очевиден с точностью до множителя 2, а траектория нечувствительна к на таком разрешении.
- Бюджет риска. Зафиксируйте максимально допустимое стандартное отклонение shortfall (например, "однодневный 1-сигма остаточной позиции должен оставаться ниже 15 б.п. номинала") и возьмите самую дешевую траекторию, которая ему удовлетворяет. Это задача с ограничением, множитель Лагранжа которой и есть .
- Задание характерного времени. Выберите напрямую ("у этого ордера должен быть период полураспада 90 минут") и восстановите . Именно это большинство практиков неявно и делают, когда выставляют ползунок "срочности".
Разобранный пример с числами, которые мы обоснуем в разделе про калибровку. Продаем BTC по S_0 = \100{,}000T = 4\sigma = $600\sqrt{\text{ч}}\eta = 1.0\ $\cdot\text{ч}/\text{BTC}\lambda = 10^{-6}\ $^{-1}$. Тогда
Оптимальное расписание продает 46.2 BTC в первый час (TWAP: 25). Временная стоимость: \eta \int_0^T v(t)^2 dt \approx \3{,}290$2{,}500$53{,}000$69{,}300 у TWAP. То есть за дополнительные 0.8 б.п. ожидаемой стоимости на номинале \10M вы срезаете риск shortfall на 23%. В этом и заключено все содержание модели одним предложением: она оценивает стоимость страховки и позволяет вам решить, покупать ли ее.
Сама траектория — это десять строк 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}")
Пробегите lam по np.logspace(-8, -4, 50) и постройте против — это и есть ваша граница, и вся поверхность принятия решений умещается на одном графике, который можно вручить риск-комитету.
Калибровка: , , по данным Binance
Вот где тихо умирают 90% реализаций. Формула траектории тривиальна; параметры — нет. Три величины, три разные задачи оценивания.

: простой параметр
Абсолютная волатильность за корень из часа, из доходностей середины. Используйте минутные бары и масштабируйте; на более частой выборке микроструктурный шум (bid-ask bounce, отскок по спреду) смещает реализованную дисперсию вверх. Эта оценка надежна — волатильность единственный параметр, который вы получите правильно.
: временный импакт из стакана и из сделок
Два взаимодополняющих пути. Путь A — проход по стакану L2. По снимкам глубины вычислите стоимость гипотетического рыночного выноса объема : VWAP использованных уровней минус середина. Подгонка дает мгновенный импакт на BTC. Чтобы перевести это в основанный на скорости из модели, нужно предположить время восстановления стакана (порядка 10–60 с для BTCUSDT): торговля со скоростью потребляет за цикл обновления, так что . Это предположение — мгновенное, полное восстановление по фиксированным часам — и есть в точности та трещина, через которую входит Обижаева-Ванг; подробнее ниже. Путь B — регрессия реализованного проскальзывания на участие. Сгруппируйте агрессивный (тейкерский) поток в минутные корзины; для каждой корзины прогоните регрессию VWAP тейкерской стороны минус середина на открытии против скорости тейкерского объема. Путь B измеряет, сколько рынок реально взял с агрессоров; путь A измеряет, сколько взял бы стоящий в стакане объем прямо сейчас. Когда они расходятся в 3 раза, доверяйте B для уровня и A для внутридневной формы.
: постоянный импакт через регрессию в духе Кайла
Прогоните регрессию изменений цены середины на знаковый чистый тейкерский поток за 5-минутные окна:
Затем проверьте, что импакт не откатился одним окном позже — не откатившаяся компонента и есть "постоянная" на вашем торговом масштабе. Это с большим отрывом самый шумный из трех.
Рабочий код, использующий только публичные REST-эндпоинты 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")
Для продакшена замените одну страницу aggTrades на несколько дней исторических дампов и прогоните регрессию на тысячах корзин, а не на горстке.
Почему калибровка в крипте шумная и что с этим делать
Прогоните регрессию на реальных данных, и вы получите в несколько процентов и коэффициент, который меняется в 2–5 раз от дня ко дню. Это не баг в вашем коде. Причины структурные:
- Эндогенность. Поток реагирует на цену ровно так же, как цена реагирует на поток. Моментум-трейдеры покупают потому что цена выросла; наивный МНК доходностей на поток подхватывает их реакцию и приписывает ее импакту. Чистое решение — использовать свои собственные исполнения (экзогенные по построению) — которые появляются у вас только после того, как вы уже какое-то время поторговали.
- Вогнутость. Реальный импакт вогнут по объему — эмпирически близок к корню квадратному (Almgren, Thum, Hauptmann and Li, 2005, "Direct estimation of equity market impact", нашли показатель около 0.6; та же вогнутость устойчиво воспроизводится в крипте). Подгонка прямой к вогнутой функции означает, что ваши и зависят от диапазона объемов в выборке. Калибруйте на тех ставках участия, на которых вы реально будете торговать.
- Фрагментация и лидерство деривативов. Спот BTCUSDT на Binance — одна из многих площадок, и ценообразование часто происходит на перпетуалах. Поток, которого вы никогда не видите, двигает цену, на которую вы регрессируете, раздувая член шума.
- Зависимость от режима. , измеренный в азиатскую сессию, не описывает открытие в США; импакт примерно удваивается, когда удваивается волатильность. Калибруйте по сессиям и перекалибровывайте как минимум еженедельно.
Спасительное обстоятельство: сама траектория прощает ошибки. , так что ошибка в в 2 раза сдвигает лишь в раза, а функция стоимости плоская вблизи оптимума. Оценить с точностью до множителя 2, а с точностью до порядка величины уже достаточно, чтобы поймать большую часть доступного улучшения над TWAP. Точность важна для предторговых оценок стоимости; для расписания достаточно устойчивости.
Где модель ломается и статьи, которые это чинят
Алмгрен-Чрисс — это каркас, и понимание того, какая именно балка несущая, подсказывает, за каким расширением тянуться.
Нелинейный импакт — Almgren (2003). "Optimal execution with nonlinear impact functions and trading-enhanced risk" (Applied Mathematical Finance 10, 1–18) переделывает всю программу со степенным временным импактом . Для эмпирически предпочтительного — закона корня квадратного — оптимальные траектории меняют характер: вогнутый импакт наказывает всплески меньше, чем линейный, поэтому при том же оптимальные расписания сильнее смещаются к началу. Качественная структура (параметр срочности, эффективная граница) сохраняется; формула sinh — нет.
Устойчивость и стакан — Obizhaeva and Wang (2013). "Optimal trading strategy and supply/demand dynamics" (Journal of Financial Markets 16(1), 1–32; рабочая версия ходила с 2005) заменяет дихотомию временный/постоянный на стакан заявок с конечной глубиной и экспоненциально затухающим импактом: ваша сделка съедает стакан, а стакан пополняется со скоростью восстановления . Допущение AC "временный импакт исчезает мгновенно" — это предел . Оптимальная стратегия резко меняет форму: блочная сделка в начале, блок в конце и постоянная скорость торговли между ними — блоки эксплуатируют восстановление стакана. Если интервал вашей дочерней заявки сопоставим со временем восстановления площадки (в крипте — от секунд до минуты, а так часто и бывает), вы находитесь на территории Обижаевой-Ванга, а не Алмгрена-Чрисса, и костыль в калибровке выше — ваш предупредительный знак.
Отсутствие динамического арбитража — Gatheral (2010). "No-dynamic-arbitrage and market impact" (Quantitative Finance 10(7), 749–759) спрашивает, какие комбинации функции мгновенного импакта и ядра затухания внутренне непротиворечивы, то есть не допускают стратегии кругового оборота с отрицательной ожидаемой стоимостью. Результаты резкие: экспоненциальное затухание совместимо только с линейным импактом — соедините экспоненциальное затухание с импактом корня квадратного, и из модели можно выкачивать деньги колеблющейся последовательностью сделок; нелинейный импакт требует степенного затухания, с неравенством, связывающим два показателя (для импакта и затухания приблизительно ). Это та статья, которую стоит прочитать, прежде чем прикручивать модное ядро затухания к своей модели импакта: большинство произвольных комбинаций тайно арбитрируемы, что на практике означает, что ваш оптимизатор найдет арбитраж и выдаст абсурдные колеблющиеся расписания. Если ваша "оптимальная" траектория чередует покупки и продажи при чистой ликвидации, вы нарушили Gatheral, а не открыли альфу.
Продолжения на машинном обучении. Две ветки этой серии прямо надстраиваются над этим каркасом. Первая — исполнение на обучении с подкреплением: как только вы признаете, что импакт нелинеен, зависит от состояния и лишь частично наблюдаем, замкнутая форма исчезает, и обучение с подкреплением (начиная со статьи Nevmyvaka, Feng and Kearns 2006 года про Q-обучение) — естественный способ искать в пространстве траекторий, но каждый серьезный RL-агент исполнения сравнивается с решением AC, а часто и инициализируется из него. Вторая — нейросетевые модели ценового импакта: замена на обученный функционал от состояния стакана заявок при сохранении средне-дисперсионной логики расписания сверху. Ни одно из этих продолжений не имеет смысла без точного понимания того, что представляет собой линейно-гауссовская базовая модель и почему она имеет именно такую форму.
Что из этого вынести
Модель — это три допущения, одно разностное уравнение и одно число. Линейный постоянный импакт (навязанный запретом на манипуляцию), линейный временный импакт (аппроксимация, вокруг которой вы калибруетесь), арифметический броуновский шум. Оптимум балансирует дисперсию позиции против стоимости импакта, вся стратегия схлопывается в срочность , а TWAP выпадает как вырожденный случай — и это единственный самый полезный факт в модели, потому что он превращает "стоит ли пускать это через TWAP?" из вопроса привычки в проверяемое утверждение о неприятии риска. Калибровка — вот где настоящая работа: прост, — игра с точностью до множителя 2, — исследовательский проект, а траектория прощает все три. Реализуйте ее за один вечер; остаток месяца потратьте на диагностику регрессий.
Литература
- 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.
Авторы
Инженер торговых систем
Разработка торговых ботов с 2017 года: межбиржевой арбитраж (подключал до 30 бирж), парный арбитраж на коинтеграции между спотом и фьючерсами, скальпинг, фронтраннинг, торговля по новостям, сентиментный анализ, трендовые алгоритмы, а также алгоритмы управления и балансировки портфелей. Делает выставление ордеров до 1 мс, warehouse для big data, бэктестинг-движки, AI-агентов и интерфейсы для ботов (в т.ч. open-source profitmaker.cc). Стек: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, архитектура.