Две поверхности. Одно торговое решение.
Quant-трейдинговая фирма имела дело с реальной, но будничной проблемой: данные, нужные ей для одного решения, лежали в двух отдельных инструментах, которые понятия не имели о существовании друг друга.
С одной стороны — календарь отчётностей. Четыре крупных публичных источника, каждый слегка отличается — разные тикеры, разные соглашения о времени созвона, временами дубликаты, временами противоречия. Команда вручную сверяла их между собой до того, как что-либо торговалось.
С другой стороны — дилерская гамма-экспозиция по SPX. Индикатор структуры опционного рынка, который говорит, где концентрируется систематическое давление хеджирования: какие страйки становятся магнитными по мере движения рынка к ним, где дилеры будут покупать на слабости и продавать на силе, какой запас у индекса до того, как включится механическая продажа. Для этого нет поля в Bloomberg. Это выводят сами. Заново, из опционной цепочки биржи, с гаммой по Black-Scholes, вычисленной по страйкам и агрегированной с поправкой на дельту. И делают это каждые пятнадцать минут в течение торговой сессии, иначе число, которое у вас есть, уже неверно.
Бриф был конкретным: один экран, оба сигнала, синхронизированы. Наведите курсор на событие отчётности. Увидьте картину по гамме на эту дату. Готовые продукты делают либо одно, либо другое. Никто не делал оба в одном представлении.
Сплавленное представление: календарь событий сверху, гамма-экспозиция снизу.
Панель дала трейдерам фирмы календарь отчётностей, выводящий тикеры из четырёх независимых источников данных — дедуплицированные, сверенные, с индикаторами времени созвона — поверх живого графика гамма-экспозиции по SPX. Две поверхности были синхронизированы: наведите курсор на дату отчётности, и график гаммы перескакивал к картине экспозиции за эту сессию.
График гаммы показывал совокупную дилерскую экспозицию по страйкам, с поправкой на дельту, с линией чистой гаммы, делавшей переход «плюс/минус» заметным с первого взгляда. Нулевая гамма — точка, где дилерское хеджирование переключается со стабилизирующего на усиливающее — была отмечена как константа. Дневное открытие и предыдущее закрытие были опорными линиями. Кластеры страйков, где концентрировалась экспозиция, проявлялись пиками.
Календарь отчётностей показывал подтверждённые и предварительные события с индикаторами времени созвона (BMO, AMC, unconfirmed). Фильтрация на уровне тикера, выбор диапазона дат и режим списка наблюдения позволяли трейдеру ограничить календарь своим портфелем, не трогая гамма-слой. Два представления делили одну и ту же ось дат. В этом и был весь смысл.
Фронтенд работал на Next.js 16 и React 19, при этом D3.js отвечал за отрисовку гаммы, а Zustand управлял общим состоянием календаря и графика. Vercel Analytics был подключён с первой недели продакшена.
- F · 01Мультиисточниковый календарь отчётностей
- Четыре независимых источника, дедуплицированных по тикеру, дате и времени созвона. Обработка retry-and-backoff по каждому источнику мягко деградирует календарь, когда источник ломается — представление трейдера никогда не гаснет.
- F · 02Живой график гамма-экспозиции SPX
- Гамма по Black-Scholes вычисляется заново по полной опционной цепочке SPX каждые пятнадцать минут в торговые часы. Агрегируется по страйкам, корректируется на дельту. Нулевая гамма и ключевые опорные линии отмечены.
- F · 03Синхронизированная ось дат
- Наведите курсор на событие отчётности, и график гаммы перескакивает к картине экспозиции за эту сессию. Слияние — во взаимодействии, а не только в компоновке.
- F · 04Горячее хранилище только на Redis
- Никакой холодной базы данных. Оба конвейера пишут в Redis JSON с ключом по дате и тикеру. Чтение на фронтенде за доли миллисекунды. Паттерну доступа никогда не требовалось большего.
- F · 05Телеметрия по каждому источнику
- Каждый парсер отчётностей отдаёт своё состояние повторов как метрику. Сломанный парсер всплывает в панели мониторинга в пределах одного цикла обновления — до того, как кто-либо, торгующий по этим данным, это заметит.
- F · 06Двуязычный бэкенд
- Node/Express для скрейпинга и дедупликации. Python/FastAPI для quant-слоя — scipy.stats, numpy, pandas. Каждый язык владеет задачей, которой подходит. Один кэш хранит результат.
Один кэш, два конвейера, два языка.
Задача слияния данных под панелью была архитектурным выбором с ясным ответом: два независимых конвейера, ни один не блокирует другой, оба пишут в единое горячее хранилище.
Конвейер отчётностей работал на Node и Express. Сервис-скрейпер забирал данные из четырёх независимых публичных календарей каждые три часа. У каждого источника был свой парсер — изолированный, с обработкой retry-and-backoff, чтобы временный сбой одного источника не портил остальные. Дедупликация шла по тикеру, дате и времени созвона; тикер, встречающийся в трёх источниках с мелкими различиями в форматировании дат, сводился к одной канонической записи. Телеметрия по каждому источнику означала, что сломанный парсер виден в панели мониторинга раньше, чем трейдер замечал что-то неладное.
Конвейер гаммы работал на Python. FastAPI обслуживал quant-слой: свежая опционная цепочка SPX забиралась из CSV-фида биржи каждые пятнадцать минут в торговые часы, пропускалась через расчёт гаммы по Black-Scholes с использованием scipy.stats и numpy, агрегировалась по страйкам и корректировалась на дельту. Python был здесь верным инструментом — scipy.stats, научная экосистема Python и то, как quant-команда уже рассуждала о расчёте. Переписывание этого на TypeScript добавило бы трения без выигрыша.
Раз не было холодной базы данных, на которую можно опереться, каждый конвейер должен был быть корректным в момент записи. Это давление породило более чистые парсеры и более внятную историю дежурств.
Оба конвейера писали в Redis. Никакой холодной базы данных — по замыслу, а не по упущению. Телеметрия первых недель продакшена подтвердила то, что предполагала архитектура: паттерн доступа всегда был «как картина выглядит прямо сейчас», а не «как она выглядела шесть недель назад». Redis JSON с ключом по дате и тикеру давал фронтенду чтение текущего состояния за доли миллисекунды. Кэш был всем хранилищем.
Четыре года в продакшене. По-прежнему обновляется каждые пятнадцать минут.
Панель выпустила своё первое продакшен-развёртывание в 2022 году и с тех пор работает непрерывно. Двухконвейерная архитектура пережила три года перетряски селекторов на источниках отчётностей, изменения формата биржевого фида на стороне гаммы и полную пересборку фронтенда, когда состоялась миграция на React 19.
Покрытие по отчётностям держится на отметке более 5 000 тикеров из четырёх независимых источников. Движок гаммы обрабатывает более 3 000 страйков SPX за цикл экспирации. Частоты обновления не менялись: отчётности каждые три часа, гамма каждые пятнадцать минут в течение торговой сессии.
Телеметрия предохранителей по каждому источнику оказалась самой полезной в эксплуатации функцией. Парсеры источников ломаются молча — редизайн календаря или изменение API без анонса. Телеметрия помечает деградировавший источник в пределах одного цикла обновления. Представление календаря у трейдера деградирует мягко; оно не гаснет.
Фронтенд-кодовая база достигла 325 коммитов к концу 2024 года. Бэкенд перешагнул 541. Движок гаммы, изменившийся меньше всего, остановился на 59. Это соотношение — честная карта того, где на самом деле была работа: не в quant-математике — она устаканилась рано — а в непрерывной поддержке слоя данных под ней.
Мы перестали вести отдельную таблицу для этого в течение недели после запуска. Она делает сверку, которую мы делали вручную, и при этом знает, как выглядит рынок в этот момент.
