Updating the Volume Curve Intraday: Does Adaptive Forecasting Actually Help?
В статье TWAP, VWAP и POV мы напрямую сравнили все три планировщика на 90 днях воспроизведения L2 для BTCUSDT и пришли к выводу, который намеренно оставили незажившей раной: в криптовалютах слабое место конвейера - кривая объема, а не нарезка ордера. В той статье мы намеренно выпустили простую версию - медианную кривую по дню недели и времени суток, переоцениваемую раз в неделю и неизменную в течение торгового дня. Она показала, что все преимущество VWAP над TWAP связано с качеством прогноза: при учете ошибки реализованной кривой объема VWAP опережает TWAP примерно на 4 б.п. в терцили дней с лучшим прогнозом, а в худшем терциле разница растворяется в шуме.
Из этого результата следует конкретное, опровержимое продолжение. Если VWAP выигрывает только в дни, когда кривая случайно оказалась правильной, то прогнозист, который исправляет себя по мере развития дня, должен превратить часть дней из плохого терцила в хорошие. А может и не должен - и интересная версия этой статьи как раз та, где этого не происходит: плохой терциль в криптовалютах составляют дни каскадов и новостей, а именно в такие дни объем первых двух часов наблюдений меньше всего говорит о следующих двух.
В этой статье реализован внутридневной обновитель, он запускается на том же стенде, а затем приводится дельта IS относительно статической кривой - с распределением и значением для последнего квартиля, а не только со средним.
Что проверяется

Статическая кривая - это априорное распределение: фиксированный вектор ожидаемых долей объема по корзинам, оцененный офлайн. Адаптивная версия рассматривает его как априорное распределение, которое можно обновлять. После того как прошло корзин и стали известны фактические объемы , появляются два новых фрагмента информации, отбрасываемых статической кривой:
- Оценка уровня. Если априорное распределение говорит, что на корзины должна приходиться доля дневного объема, а фактически они принесли единиц, то подразумеваемый дневной итог равен . Это сигнал "сегодня будет большой объем" / "сегодня будет мертвый рынок", доступный уже с первой корзины.
- Коррекция формы. Если реализованная форма прошедших корзин систематически отклоняется от априорной, оставшиеся корзины тоже могут отклониться - но только если ошибки внутридневной кривой объема автокоррелированы внутри дня. Это отдельный эмпирический вопрос, а не допущение, которое можно бесплатно принять.
Ниже реализация полностью использует (1) и совсем не использует (2): она перенормирует нетронутый хвост априорной кривой под обновленный итог. Это консервативная версия и правильный первый эксперимент: если одно лишь обновление уровня уже захватывает большую часть доступной дельты, механизм формы будет неоправданным усложнением.
import numpy as np
from typing import Optional
class AdaptiveVolumePredictor:
"""
Bayesian-style adaptive volume profile predictor.
Combines a prior (the offline day-of-week x time-of-day curve)
with volume observed so far today to produce an updated forecast
for the remaining buckets.
"""
def __init__(self, historical_profiles: np.ndarray):
"""
Args:
historical_profiles: shape (n_days, n_buckets), each row sums to 1.0.
Use the median-based, day-of-week-conditioned curve from the
TWAP/VWAP/POV article -- a pooled mean curve is misspecified
and will make the adaptive version look better than it is by
giving it a weaker baseline to beat.
"""
self.prior_profile = np.median(historical_profiles, axis=0)
self.prior_profile /= self.prior_profile.sum()
self.prior_iqr = np.subtract(*np.percentile(historical_profiles, [75, 25], axis=0))
self.n_buckets = len(self.prior_profile)
def predict(
self,
observed_volumes: np.ndarray,
current_bucket: int,
total_volume_estimate: Optional[float] = None,
) -> np.ndarray:
"""
Predict absolute volume for every bucket: realized values for elapsed
buckets, forecasts for the remainder.
Args:
observed_volumes: actual volumes in buckets 0..current_bucket-1
current_bucket: index of the current bucket (0-based)
total_volume_estimate: external ADV estimate (optional prior on level)
"""
profile = np.zeros(self.n_buckets)
if current_bucket == 0:
base = total_volume_estimate if total_volume_estimate else 1.0
return self.prior_profile * base
profile[:current_bucket] = observed_volumes[:current_bucket]
observed_total = observed_volumes[:current_bucket].sum()
expected_fraction_so_far = self.prior_profile[:current_bucket].sum()
if expected_fraction_so_far > 0.01:
implied_total = observed_total / expected_fraction_so_far
else:
implied_total = observed_total * self.n_buckets
if total_volume_estimate:
w_obs = expected_fraction_so_far
implied_total = w_obs * implied_total + (1 - w_obs) * total_volume_estimate
remaining_prior = self.prior_profile[current_bucket:]
remaining_sum = remaining_prior.sum()
if remaining_sum > 0:
remaining_volume = max(0.0, implied_total - observed_total)
profile[current_bucket:] = remaining_prior / remaining_sum * remaining_volume
return profile
В этом коде эксперимент определяют две детали. Априорная кривая - это медианная кривая с учетом дня недели, а не объединенное среднее. Почему это важно на рынке, где один каскад ликвидаций может дать 15% дневного объема за десять минут, объясняется в разделе о кривой объема статьи про VWAP. Обновление уровня сжато в сторону внешней оценки ADV с весом, равным прошедшей априорной доле, поскольку в первой корзине implied_total - одно наблюдение, деленное на число, близкое к нулю. Обновитель без сжатия хуже всего работает именно тогда, когда до конца дня еще больше всего времени для ущерба.
Стенд

Стенд идентичен запуску TWAP/VWAP/POV, поэтому числа можно сравнивать построчно:
- Инструмент и данные: бессрочный BTCUSDT, 90 дней воспроизведения L2 (верхние 20 уровней, 100 мс) плюс лента сделок. Везде UTC; временные метки funding помечены.
- Родительские ордера: 500 ордеров, горизонт ч, размер 0,75% скользящего ADV за 30 дней, сторона покупки, цена решения - mid в начале. Время случайного старта то же, что в опубликованном запуске.
- Варианты: (A) VWAP на статической кривой с еженедельной переоценкой - опубликованный baseline; (B) VWAP на том же априорном распределении с внутридневным обновлением; (C) TWAP как нижняя граница.
- Метрики: IS в б.п. цены решения с учетом комиссий относительно момента прихода - среднее, медиана, стандартное отклонение, 95-й перцентиль и IS последнего квартиля каждого родительского ордера. Проскальзывание VWAP записывается только как диагностика, а не как таблица результатов; в разделе с benchmark статьи про VWAP показан пример, где проскальзывание VWAP и IS относительно прихода ранжируют два алгоритма в противоположном порядке. Полная декомпозиция IS, включая стоимость упущенной возможности, приведена в статье implementation shortfall и TCA.
Результаты
Помогает ли это там, где нужно?
Заголовочное среднее - наименее интересное число. Опубликованный результат установил, что разрыв VWAP-TWAP монотонен относительно ошибки реализованной кривой объема, поэтому адаптивный вариант нужно оценивать так же: разделить 90 дней на терцили по расстоянию между статическим прогнозом и реализованными объемами корзин, затем отчитаться о дельте IS "адаптивный минус статический" внутри каждого терцила.
Оба исхода можно публиковать, но означают они противоположное:
- Дельта сосредоточена в хорошем терциле. Обновитель улучшает дни, которые и так были простыми. Итоговый эффект на распределение затрат косметический; хвост - именно его оплачивает родительский ордер с жестким горизонтом и альфа-сигналом - не меняется. Это означало бы, что внутридневной обновитель не исправляет слабое место, а дальше нужно смотреть в сторону коррекции формы и межактивных методов.
- Дельта сосредоточена в плохом терциле. Обновитель выполняет задачу, ради которой создан: ловит дни каскадов и новостей, когда статическая кривая ошибается сильнее всего. Такой исход оправдал бы добавление состояния в цикл исполнения.
Отрицательный результат ожидаем и его стоит изложить прямо. Механизм, из-за которого кривые объема в криптовалютах так сложны, - каскад является разрывом режима, а не сдвигом уровня, - как раз и побеждает прогнозист, обновляющий уровень: к моменту, когда прошедшие корзины сообщают, что сегодня объемный день, его самая активная часть уже может закончиться. О том, почему этот блог публикует такие результаты, см. честный отрицательный результат.
Наклон стакана: добавляет ли он что-то?

Один признак из чернового набора признаков в этом блоге еще не рассматривался: наклон стакана - насколько быстро накапливается суммарная глубина по мере удаления от лучшей цены. Подгоните по верхним уровням; большое означает, что глубина сосредоточена у лучшей цены, малое - что она тонко распределена по стакану.
def book_slope(prices: np.ndarray, volumes: np.ndarray, mid: float) -> float:
"""Slope of cumulative depth vs. distance from mid. One side only."""
distances = np.abs(prices - mid)
return float(np.polyfit(distances, np.cumsum(volumes), 1)[0])
Имеет смысл формулировать только измеренную версию этого утверждения: меняет ли добавление book_slope к существующему набору признаков прогноз объема или итоговый IS сверх baseline AR/EWMA? Статья о моделировании spread задает стандарт - сообщайте качество выше тривиального baseline с указанным горизонтом, поскольку в этих рядах доминирует persistence, а заголовочный R² в основном измеряет автокорреляцию.
Что эта статья намеренно не выводит заново

Все перечисленное ниже уже подробно разобрано в блоге, а повторное объяснение здесь создало бы лишь более слабую вторую версию:
- Измерение ликвидности. Оценка Roll (с исправлением signed-root и ловушкой "единицы цены против доходностей") и lambda Кайла: моделирование spread с помощью машинного обучения. Lambda Кайла также откалибрована на реальных Binance aggTrades как коэффициент постоянного воздействия в статье Almgren-Chriss. Стоимость sweep по стакану: модели проскальзывания и затрат.
- Признаки стакана. Взвешенный mid (который не является microprice Стоикова - тот скорректирован под мартингальность), многоуровневый дисбаланс стакана, OFI и полная таксономия признаков LOB: DeepLOB и моделирование spread.
- Внутридневные паттерны. U-образная форма акций - неверная модель для рынка без закрытия; криптовалютная структура сессий, funding и недели, которая ее заменяет, вместе с сеткой "день недели × время суток" приведена в статье про VWAP. Циклическое кодирование времени суток есть в таблице признаков статьи о spread.
- Запланированные события. Экспирации Deribit в 08:00 UTC, макроданные США в 12:30/14:00 UTC, расчеты CME - рассматривайте их как dummy-признаки, а не позволяйте им загрязнять baseline-кривую; конкретный криптокалендарь приведен в статье про VWAP. Приведенный выше адаптивный обновитель - не обработчик событий, и не следует требовать от него быть таковым.
- Модели. Gradient boosting с purged и embargoed walk-forward CV (обычный
TimeSeriesSplitпротекает через перекрывающуюся цель forward-window) описан в моделировании spread; семейство CNN-LSTM, входной тензор 40 признаков/10 уровней, LOBFrame и обсуждение кризиса воспроизводимости - в DeepLOB. Важно: ось уровней нельзя схлопывать до рекуррентного слоя. - Решение по дочернему ордеру. Passive против aggressive - это вычисляемая точка безубыточности , а не жестко заданный порог spread: тактики исполнения дочерних ордеров. Target по доле участия эндогенен - ваши собственные fills печатаются в ленте, а исправление приведено в статье про VWAP.
- Фрагментация площадок. Умная маршрутизация ордеров в криптовалютах, с TCA-таблицей по каждой площадке в статье implementation shortfall.
- Задержка в production. Раздел DeepLOB о production; обратите внимание, что статья о spread уже уточняет заявления о времени inference - LightGBM с 2000 раундами предсказывает из Python за десятки микросекунд, а однозначные микросекунды дает только скомпилированный predictor.
Adversarial dynamics заслуживают одного абзаца, а не отдельного раздела. Отображаемая глубина отчасти постановочна, а механика обнаружения - скорость отмен, скорость наращивания стены, поведение при приближении цены, layering - разобрана с конкретным методом на основе PIQ в статье анализ позиции в очереди и стен. Единственная добавка этой статьи могла бы состоять в использовании их как входов прогнозиста: отношение отмен к сделкам, среднее время нахождения у лучшей цены и доля глубины, отменяемая в течение 100 мс после появления, подаются в модель объема вместе с сигналом прошедшей корзины. Добавляют ли они качество поверх существующего набора признаков - открытый вопрос измерения, а не утверждение.
Заключение

Проверяемое утверждение узкое, и блог уже создал условия для его опровержения: статья про VWAP установила, что преимущество VWAP полностью определяется качеством прогноза, а прогноз терпит неудачу в самые дорогие дни. Внутридневной обновитель либо исправляет это, либо нет, и ответом будет число в плохом терциле воспроизведения 500 родительских ордеров.
Эта статья не будет приписывать production-системе значение в б.п. без воспроизведения. Утверждения вида "хорошо настроенное прогнозирование ликвидности экономит 2-5 б.п., то есть меняет Sharpe с 1,5 до 2,0" - именно тот жанр, который дефлированный Sharpe и multiple testing призван разоблачать: без источника, без условий и без указания разброса. Важное здесь число - IS последнего квартиля в худшем терциле дней, и оно либо измерено, либо не приводится.
Авторы
Инженер торговых систем
Разработка торговых ботов с 2017 года: межбиржевой арбитраж (подключал до 30 бирж), парный арбитраж на коинтеграции между спотом и фьючерсами, скальпинг, фронтраннинг, торговля по новостям, сентиментный анализ, трендовые алгоритмы, а также алгоритмы управления и балансировки портфелей. Делает выставление ордеров до 1 мс, warehouse для big data, бэктестинг-движки, AI-агентов и интерфейсы для ботов (в т.ч. open-source profitmaker.cc). Стек: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, архитектура.