Синтетический рынок в контуре торговых ботов

У меня торговый бот работает в контуре, где сначала — синтетика, потом — реальный рынок. Не «или/или», а именно в этой последовательности. Раньше я делал наоборот: гонял тест на истории, деплоил, ловил тонкие случаи в продакшне. Потом понял, что половина тонких случаев — это не «что-то, что случится на рынке», а «что-то, что мой алгоритм неправильно интерпретирует». Вторую категорию на реальных данных не поймать — там нет повторяемости. Синтетика закрывает именно эту дыру.

market-data-emulator (project page) берёт на себя три задачи, на которые реальных данных не хватает.

Режимы рынка

Реальный рынок даёт мне один текущий режим — он такой, какой есть. Я не могу протестировать, как мой движок оценки рисков ведёт себя в каскаде ликвидаций, если на рынке сейчас нет такого каскада. Я не могу проверить, как стратегия по стакану обрабатывает мгновенный обвал, если мгновенный обвал не случался последние полгода. Эмулятор генерирует нужный режим по запросу: meta.regimes_summary отдаёт {"trend_up": 5, "range": 235} для 240 баров, и я знаю, что в трендовой фазе должен сработать трендовый индикатор, а в боковике — нет. Это не «реалистичные данные», это контролируемая среда. Я выбираю параметры, фиксирую seed, проверяю поведение.

Узлы анализа

В конвейере программы отдельные блоки — change_point_detector, regime_hmm, spring_detector, wyckoff_detector. Каждый надо тестировать изолированно: что он ловит, что пропускает, что ловит ложно. На реальных данных — это композиция из тестовых выборок реальных дней, и для каждого тестового случая надо искать подходящий день. На синтетике — это сценарий с заранее известным эталоном. change_point_injection сам говорит «сдвиг уровня @ бар 100, среднее +5σ», и мой детектор должен это поймать. В wyckoff_spring есть эталон-пружина, и мой детектор не должен её пропустить. Тест пишется один раз, эталон живёт в реестре сценариев.

Локальные модели

Когда я тренирую ML-фильтр на конкретный паттерн (отличить dead_cat_bounce от продолжения тренда, например), мне нужен датасет, где этих паттернов много и в разных вариациях. На реальных данных — 3-5 случаев за год, каждый в уникальном контексте, листовые метки расставляются вручную. На синтетике — 50 примеров каждого паттерна с разными drift-параметрами, разной σ, разными режимами. Модель учится на структуре, а не на конкретных исторических совпадениях. Это не «фейковые данные для обучения», это прирост датасета с контролируемой меткой.

Граница

Синтетика не заменяет реальный рынок. Бот, который прошёл только синтетические тесты, не готов к деплою — он не видел живого стакана, микроструктуры, дислокаций. Синтетика покрывает фазу, где реальный рынок либо избыточен (я знаю, что хочу протестировать), либо вреден (на реальных данных учить на каждом примере — это переобучение на историю). Реальный рынок покрывает фазу, где нужна правдоподобная картина без возможности контролировать переменные.

Поэтому порядок такой: синтетика → тесты узлов и движка оценки рисков → тест на истории реальных данных → пробная торговля → боевой режим. Эмулятор — это не инструмент замены реального рынка, это инструмент фазы, где реальный рынок либо не ловит, либо не учит.

Где не заменяет эмулятор

Реальные тики — это не воспроизведение, эмулятор не тянет исторические бары с биржи.

Сопоставление ордеров — нет движка сопоставления. OrderbookSnapshot L2 генерируется как функция, а не как результат сопоставления.

Поток данных в продакшне — не подключается к реальным биржам. Для реальной торговли нужен другой источник данных.

Историческая статистика по реальному рынку — это контролируемая выборка, не история.

Если интересно, больше информации о проекте — на странице проекта /projects/market-data-emulator/ или в репозитории github.com/elriseio/market-data-emulator.