market-data-emulator — это Python-эмулятор рыночных данных, который генерирует реалистичные воспроизводимые OHLCV-серии для одного символа или для пакета из 1..8 скоррелированных символов. Это инструмент для тестирования бэкендов, бэктестинга торговых стратегий, нагрузочного тестирования риск-движков и подготовки обучающих датасетов для ML-моделей. Здесь нет подключения к реальным биржам и нет попыток строить правдоподобный микроструктурный рынок — это контролируемый синтетический источник с известной математической моделью.
Базовая ценовая модель — Ornstein-Uhlenbeck с mean-reversion, drift и волатильностью, которые меняются по сценарному расписанию. На 1-минутном базовом таймфрейме собирается OHLCV, потом он детерминированно ресемплится в любой целевой таймфрейм (1m, 5m, 1h, 1d, 1w, 1mo, плюс произвольный <N><unit>), под конец применяются рыночные инварианты (tick/lot, OHLC-инварианты, лимиты).
Текущий релиз даёт четыре крупные поверхности, которые нужны для бэктестинга в реалистичных условиях:
- Multi-pair correlated klines — 1..8 символов в одном запросе, Cholesky-корреляции, общий режимный график, отдельный столбец
symbolв строках. - Funding profile — perpetual-swap
funding_historyплюс per-barfundingстрока. - Orderbook L2 per bar — per-bar
OrderbookSnapshotизliquidity_profile, как для симуляции матчинга, так и для фич ML. - Multi-file
jsonsetexport — каноническая раскладка(topic, symbol)сmeta.json+manifest.json, удобная для консьюмеров с раздельной индексацией.
Все четыре поверхности в проде (см. README и regression/expected_hashes.json — single-pair путь остаётся byte-identical, метки DE-029..DE-032 в Issues/проде закрыты).
Библиотека работает в трёх режимах: standalone CLI (market-emulator), Python API (from market_emulator import Emulator) и HTTP API (FastAPI + uvicorn). Все три режима проходят через один и тот же core/pipeline_orchestrator.py — единый путь гарантии, что сегодняшний pytest и завтрашний --out data.json дают одинаковый результат.
Быстрый старт
Минимальный сценарий: установить эмулятор, сгенерировать 1000 1-минутных свечей, проверить детерминизм.
pip install -r requirements.txt
pip install -e .
market-emulator generate --timeframe 1m --count 1000
market-emulator generate --timeframe 5m --duration 30d --seed 42
market-emulator generate --timeframe 1h --count 720 \
--format csv --out data.csv
Альтернативные способы (через pyproject.toml, dev-зависимости, Docker) — в §Альтернативная установка.
from market_emulator import Emulator, GenerateRequestV1, to_dataframe
from market_emulator.contracts.enums import Timeframe
from market_emulator.contracts.horizon import HorizonCount
emulator = Emulator()
request = GenerateRequestV1(
timeframe=Timeframe.FIVE_MINUTES,
horizon=HorizonCount(count=1000),
seed=42,
)
result = emulator.generate(request)
df = to_dataframe(result)
print(df.head())
print(f"Свечей: {result.meta.normalized.count}")
print(f"Fingerprint: {result.meta.fingerprint}")
Эмулятор рассчитан на Python 3.11+. Контракт GenerateRequestV1 стабильный между минорными релизами в рамках серии 0.4.x.
Что это и чем не является
Эмулятор делает:
- Генерирует реалистичные OHLCV-серии на базе Ornstein-Uhlenbeck с mean-reversion и сценарно-управляемыми drift/volatility.
- Поддерживает 1..8 скоррелированных символов в одном запросе, с Cholesky-факторизацией корреляционной матрицы и общим режимным графиком.
- Произвольный
<N><unit>таймфрейм (90m,2h,3w,3mo) — детерминированный ресемплинг с 1-минутного базового. - 71 встроенный сценарий, 11 именованных overlay-профилей, 20 composable overlay-паттернов, 8 truth-сценариев.
- Per-bar
OrderbookSnapshotL2 изliquidity_profileи per-barfundingстрока изfunding_profileдля perpetual-swap-стилей. - Deterministic-byte-equal output для трёх writer'ов: JSON, CSV, JSONL, плюс multi-file
jsonset(meta.json+manifest.json+(topic, symbol)/...json). - Truth Dataset Contract: SHA256-хеш сырых данных, sanity-чеки, сценарные инварианты, экспорт
export_truth_*сериализаторов. - CLI (
market-emulator), Python API, HTTP API (FastAPI + uvicorn). - Batch-планы в YAML для генерации серии датасетов одним вызовом.
Эмулятор не делает:
- Не подключается к реальным биржам и не тянет реальные тики. Это синтетический источник, не replay.
- Не даёт матчинг ордеров, не симулирует market-impact, не считает slippage.
OrderbookSnapshotL2 — генерируется как фича, не как результат матчинга. - Не поддерживает опционы, фьючерсы с маржой, perpetual в смысле funding-арбитража.
funding_profile— это метаданные (funding_history+ per-barfunding), а не виртуальный кошелёк. - Не выполняет бэктест стратегий. Сгенерированные свечи — это входные данные, а не order manager.
- Не подменяет CI-фреймворк. Детерминизм гарантирован для (seed, scenario, version); для интеграционных тестов он полезен, но это не утилита для assertion'ов.
Требования
| Компонент | Версия |
|---|---|
| Python | 3.11 (минимум; pyproject.toml объявляет requires-python = ">=3.11") |
| pydantic | >= 2.0 |
| numpy | >= 1.24 |
| pandas | >= 2.0 |
| fastapi | >= 0.100 |
| typer | >= 0.9 |
| pyyaml | >= 6.0 |
| uvicorn | >= 0.20 (опционально для HTTP API) |
Dev-зависимости (pip install -e ".[dev]"): pytest, pytest-xdist, pytest-cov, hypothesis, httpx, jsonschema, scikit-learn, ruff, mypy, bandit, pip-audit, build.
CI-gate: pytest --cov-fail-under=90 (минимум 90% покрытия), ruff (E/F/I — игнор только E501), mypy (advisory-baseline, не валит билд), bandit (со skip B101).
Архитектура
Эмулятор построен как pipeline с явными стадиями и изолированными RNG-потоками. Оркестратор (core/pipeline_orchestrator.py) диспатчит на req.multi_pair: single-pair путь запускается byte-identical, multi-pair путь — per-symbol OU с общими Cholesky-инновациями.
Input Request
↓
Dispatch on req.multi_pair
↓ ┌──────────────────────────────────────────────┐
├──>│ single-pair path (req.multi_pair is None) │
│ │ Time Grid (1m base) │
│ │ ↓ │
│ │ Price Process (OU) │
│ │ ↓ │
│ │ OHLCV Construction │
│ │ ↓ │
│ │ Resampling (1m → target) │
│ └──────────────────────────────────────────────┘
│ ┌──────────────────────────────────────────────┐
└──>│ multi-pair path (req.multi_pair is set) │
│ Time Grid (1m base, shared) │
│ ↓ │
│ Cholesky innovations (shared) │
│ ↓ │
│ Per-symbol OU (shared regime schedule) │
│ ↓ │
│ Per-symbol OHLCV Construction (rows │
│ carry `symbol`) │
│ ↓ │
│ Resampling (1m → target) │
└──────────────────────────────────────────────┘
↓
Optional per-bar orderbook (когда задан liquidity_profile)
↓
Optional per-bar funding (когда задан funding_profile)
↓
Constraints (tick/lot, OHLC-инварианты)
↓
Export (JSON / CSV / JSONL / jsonset)
↓
Output
Ключевые решения:
- 1-минутный базовый таймфрейм. Любая генерация идёт на 1m, потом ресемплится. Это исключает drift между long-TF и short-TF пайплайнами.
- Constraints — после ресемплинга. Tick/lot rounding и OHLC-инварианты накладываются последними, поэтому базовая модель остаётся непрерывной.
- Изолированные RNG-потоки. Каждая стадия pipeline получает свой seed, что позволяет swap одной стадии без перетасовки остальных.
- Multi-pair opt-in.
GenerationSpec.multi_pair—Noneдля single-pair. ЕслиNone, single-pair путь byte-identical (regression-хеши не двигаются).
Канонический источник архитектуры — docs/architecture.md и docs/architecture/ARCHITECTURE_MAP.md. Дизайн-решения по multi-pair — docs/adr/0006-multi-pair-derivatives-design.md.
Установка
Базовый сценарий (через requirements.txt) уже покрыт в §Быстрый старт. Здесь — альтернативные варианты и полный dev-окружение.
Через pyproject.toml
# Базовый install
pip install -e .
# Dev-зависимости (pytest, ruff, mypy, bandit, hypothesis, ...)
pip install -e ".[dev]"
Dev-окружение через requirements-dev.txt
pip install -r requirements.txt -r requirements-dev.txt
pip install -e .
Docker
Multi-stage Dockerfile на python:3.12-slim:
docker build -t market-data-emulator:local .
docker run --rm market-data-emulator:local \
market-emulator generate --timeframe 5m --count 1000 --seed 42
docker-compose.yml поднимает сервис с монтированием data/ и pre-built сценариями.
CLI
# 1000 1-минутных свечей
market-emulator generate --timeframe 1m --count 1000
# 30 дней 5-минутных
market-emulator generate --timeframe 5m --duration 30d --seed 42
# Экспорт в CSV
market-emulator generate --timeframe 1h --count 720 \
--format csv --out data.csv
# Произвольный таймфрейм <N><unit>
market-emulator generate --timeframe 90m --count 100
market-emulator generate --timeframe 2h --count 24
market-emulator generate --timeframe 3mo --count 12
# Конкретный сценарий
market-emulator generate --timeframe 5m --count 2000 \
--scenario crash_then_recover --seed 42
# Funding profile (perpetual-swap метаданные)
market-emulator generate --timeframe 1m --count 240 --seed 42 \
--scenario baseline_mix \
--config funding_profile='{"pattern": "extreme_positive", "magnitude": 0.001}'
# Liquidity profile (orderbook L2 на каждый бар)
market-emulator generate --timeframe 1m --count 240 --seed 42 \
--scenario low_liquidity_silent \
--config liquidity_profile='{"levels_per_side": 10, "base_depth_usd": 500.0, "depth_shape": "power_law", "spread_bps_mean": 20.0, "spread_bps_std": 4.0}'
# Список зарегистрированных сценариев
market-emulator generate --list-scenarios
# Batch-план (несколько датасетов из YAML)
market-emulator batch --plan examples/batch_plan.yaml \
--now 2026-01-31T00:00:00Z
funding_extreme_positive_240,funding_extreme_negative_240,correlated_xrp_btc_eth— preset-профили, зарегистрированные вsrc/market_emulator/scenarios/profile_registry.py. Compose черезMarketProfileRegistry.get_overlay(name)или--config funding_profile='{...}'— оба пути дают идентичный результат.
Python API
from market_emulator import Emulator, GenerateRequestV1, to_dataframe
from market_emulator.contracts.enums import Timeframe
from market_emulator.contracts.horizon import HorizonCount
emulator = Emulator()
request = GenerateRequestV1(
timeframe=Timeframe.FIVE_MINUTES,
horizon=HorizonCount(count=1000),
seed=42,
)
result = emulator.generate(request)
df = to_dataframe(result)
print(df.head())
print(f"Свечей: {result.meta.normalized.count}")
print(f"Сценарий: {result.meta.scenario_name}")
print(f"Fingerprint: {result.meta.fingerprint}")
Emulator — основной entry point. GenerateRequestV1 — каноническая Pydantic v2-схема входа. to_dataframe — адаптер к pandas. Метаданные результата (result.meta.*) дают доступ к fingerprint, sanity-чекам, сценарным флагам и truth-dataset-артефактам.
Multi-pair generation
1..8 скоррелированных символов в одном запросе через MultiPairSpec. Под капотом req.multi_pair маршрутизирует multi-pair-путь с Cholesky-инновациями; при None запускается single-pair байт-в-байт.
from market_emulator import Emulator, GenerateRequestV1, to_dataframe
from market_emulator.contracts.enums import Timeframe
from market_emulator.contracts.horizon import HorizonCount
from market_emulator.contracts.spec import (
CorrelationMatrix,
MultiPairSpec,
SymbolSpec,
)
emulator = Emulator()
# BTC, ETH, XRP с каноническими корреляциями (ADR-0006)
request = GenerateRequestV1(
timeframe=Timeframe.ONE_MINUTE,
horizon=HorizonCount(count=240),
seed=42,
multi_pair=MultiPairSpec(
symbols=[
SymbolSpec(symbol="BTCUSDT"),
SymbolSpec(symbol="ETHUSDT"),
SymbolSpec(symbol="XRPUSDT"),
],
correlation_matrix=CorrelationMatrix(
symbols=["BTCUSDT", "ETHUSDT", "XRPUSDT"],
matrix=[
[1.00, 0.85, 0.55],
[0.85, 1.00, 0.50],
[0.55, 0.50, 1.00],
],
),
shared_regime_schedule=True,
),
)
result = emulator.generate(request)
df = to_dataframe(result) # строки несут колонку `symbol`
mp = result.meta.multi_pair
print(f"symbols: {mp.symbols}")
print(f"bundle fingerprint: {mp.bundle_fingerprint}")
Preset correlated_xrp_btc_eth зарегистрирован как overlay-профиль в src/market_emulator/scenarios/profile_registry.py. Compose поверх любого base-сценария через MarketProfileRegistry.get_overlay("correlated_xrp_btc_eth") или инлайн через MultiPairSpec — оба пути эквивалентны.
Truth Dataset Contract
Контракт «верифицируемых датасетов»: SHA256 сырых данных, sanity-чеки, сценарные инварианты.
from market_emulator import Emulator, GenerateRequestV1
from market_emulator.contracts.enums import Timeframe
from market_emulator.contracts.horizon import HorizonCount
emulator = Emulator()
request = GenerateRequestV1(
timeframe=Timeframe.ONE_MINUTE,
horizon=HorizonCount(count=1000),
seed=42,
scenario='baseline_mix', # triggers artifacts
)
result = emulator.generate(request)
print(f"Raw hash (SHA256): {result.meta.raw_data_hash}")
print(f"Sanity checks: {result.meta.dataset_sanity_checks}")
print(f"Truth version: {result.meta.truth_dataset_version}")
# Верификация детерминизма
result2 = emulator.generate(request)
assert result.meta.raw_data_hash == result2.meta.raw_data_hash
print("✓ Determinism verified")
Канонический контракт — docs/contracts/truth-dataset.md. Экспортёры truth — src/market_emulator/exporters/truth_exporter.py (export_truth_json, export_truth_meta_json, export_truth_jsonl, export_truth_csv). Все четыре writers для одного (seed, scenario, version) дают байт-идентичный output.
Форматы вывода
| Формат | CLI | Что в файлах |
|---|---|---|
json |
--format json --out data.json |
Self-contained: данные + метаданные в одном файле |
csv |
--format csv --out data.csv |
data.csv + data.meta.json (sidecar) |
jsonl |
--format jsonl --out data.jsonl |
Streaming-friendly: data.jsonl + data.meta.json |
jsonset |
--format jsonset --out-dir <dir> |
Multi-file: meta.json + manifest.json + (topic, symbol)/<symbol>.json |
jsonset (канонический layout, shipped):
<dir>/
├── meta.json # общие метаданные
├── manifest.json # topic → file index
├── ohlcv/<symbol>.json # per-symbol OHLCV
├── funding/<symbol>.json # per-symbol funding history
├── orderbook/<symbol>.json # per-symbol orderbook L2
└── truth/<symbol>.json # truth target (только для truth сценариев)
Реализация — src/market_emulator/exporters/jsonset_exporter.py. Тест-матрица — tests/test_jsonset_cli.py, tests/test_jsonset_exporter.py, tests/test_jsonset_schema_conformance.py.
Примеры ответов
Реальные ответы на команды из examples/. Все примеры — на seed=42, --now 2026-01-31T00:00:00Z, BTC-like профиль.
JSON (--format json)
Команда:
market-emulator generate \
--timeframe 1m --count 5 --seed 42 \
--now 2026-01-31T00:00:00Z --format json \
--out data.json
data.json (один self-contained файл с метаданными и свечами):
{
"meta": {
"generation_id": "b81d494e-25da-4909-a3ac-0e54926d2a8a",
"timeframe": "1m",
"normalized": {
"start": "2026-01-30T23:55:00Z",
"end": "2026-01-31T00:00:00Z",
"count": 5,
"step_seconds": 60,
"step_kind": "fixed"
},
"seed": 42,
"profile_used": {
"symbol_profile": "btc_like",
"price_start": null,
"price_precision": null,
"tick_size": null,
"lot_size": null
},
"market_used": {
"market_regime": "mix",
"regime_schedule": [],
"market_session": "us"
},
"format": "json",
"schema": "ohlcv",
"include": ["ohlcv"],
"warnings": [],
"debug": {
"regimes_summary": {"trend_up": 5},
"price_preview": {
"first": 50000.00000000001,
"last": 50888.80646166614,
"count": 5
},
"constraints": {
"tick_size": 0.01,
"lot_size": 0.0001,
"precision": 2,
"min_notional": 5.0,
"repair": {
"high_adjusted": 0,
"low_adjusted": 0,
"low_clamped_to_tick": 0,
"high_low_swapped": 0
}
}
},
"base_timeframe": null,
"target_timeframe": null,
"resampled": false,
"dropped_partial_windows": 0,
"scenario_name": null,
"scenario_version": null,
"scenario_hash": null,
"engine_version": null,
"meta_hash": "4186f70a87513395ce4aaaf7032cd47002adc228e3e706a21ca76bc6f2afa26d",
"data_hash": "9e7479605c9fe81dbcd5e9295d9a52b9f4513a8310076b6534f70ca2db517252",
"fingerprint": "36e4dc7fd7c4435d442aa76540117858f63de1aa72b569ebc88ab77c81d3019d",
"truth_dataset_version": null,
"raw_data_hash": null,
"dataset_sanity_checks": null,
"emulator_artifacts": null,
"funding_history": null,
"orderbook_context": null,
"multi_pair": null,
"partial_last_bar": null,
"analyzer_meta_hash": null
},
"data": [
{
"ts_open": "2026-01-30T23:55:00Z",
"ts_close": "2026-01-30T23:56:00Z",
"open": 50180.28,
"high": 51138.92,
"low": 48548.44,
"close": 50000.0,
"volume": 273.9469
},
{
"ts_open": "2026-01-30T23:56:00Z",
"ts_close": "2026-01-30T23:57:00Z",
"open": 50000.0,
"high": 50792.6,
"low": 49130.56,
"close": 50524.22,
"volume": 251.9564
},
{
"ts_open": "2026-01-30T23:57:00Z",
"ts_close": "2026-01-30T23:58:00Z",
"open": 50524.22,
"high": 51719.0,
"low": 48550.17,
"close": 51405.81,
"volume": 376.1741
},
{
"ts_open": "2026-01-30T23:58:00Z",
"ts_close": "2026-01-30T23:59:00Z",
"open": 51405.81,
"high": 54167.23,
"low": 49056.3,
"close": 52610.45,
"volume": 513.139
},
{
"ts_open": "2026-01-30T23:59:00Z",
"ts_close": "2026-01-31T00:00:00Z",
"open": 52610.45,
"high": 55316.8,
"low": 50184.69,
"close": 50888.81,
"volume": 558.4578
}
]
}
meta.fingerprint — SHA256-хеш конфигурации (без generation_id и generated_at), служит ключом для кеширования и регрессионных проверок. meta.data_hash — SHA256 самих данных. meta.regimes_summary — распределение баров по режимам (trend_up: 5 означает, что все 5 баров внутри трендовой фазы).
CSV (--format csv)
Команда:
market-emulator generate \
--timeframe 5m --count 3 --seed 42 \
--now 2026-01-31T00:00:00Z --format csv \
--out data.csv
data.csv:
ts_open,ts_close,open,high,low,close,volume
2026-01-30T23:45:00Z,2026-01-30T23:50:00Z,50180.28,55316.8,48548.44,50888.81,1973.6744
2026-01-30T23:50:00Z,2026-01-30T23:55:00Z,50888.81,53366.41,47545.55,51518.33,1886.6701
2026-01-30T23:55:00Z,2026-01-31T00:00:00Z,51518.33,58363.88,49798.85,54907.27,2010.3284
data.meta.json (sidecar, та же meta структура, что и в JSON-выводе):
{
"generation_id": "65b257a0-5642-4d65-981c-51688fec8fc5",
"timeframe": "5m",
"normalized": {
"start": "2026-01-30T23:45:00Z",
"end": "2026-01-31T00:00:00Z",
"count": 3,
"step_seconds": 300,
"step_kind": "fixed"
},
"seed": 42,
"profile_used": {"symbol_profile": "btc_like"},
"market_used": {"market_regime": "mix", "market_session": "us"},
"format": "csv",
"schema": "ohlcv",
"resampled": true,
"base_timeframe": "1m",
"target_timeframe": "5m",
"data_hash": "a654cc13c7603f35784d74ddb2faf17a4e2c1703c1699eb79091ef31ecaa1746",
"meta_hash": "8505f335dbceb16517ce7f67fd743fb23e97fb6cc24b82a8ee20826dd00afb39",
"fingerprint": "09bcff37290b76955e51b5318a83ba6e16066..."
}
resampled: true означает, что 1-минутный базовый pipeline был ресемплирован в 5-минутный таймфрейм; base_timeframe / target_timeframe фиксируют это.
JSONL (--format jsonl)
Команда:
market-emulator generate \
--timeframe 5m --count 3 --seed 42 \
--now 2026-01-31T00:00:00Z --format jsonl \
--out data.jsonl
data.jsonl — по одному JSON-объекту на строку, без обёртки-массива:
{"ts_open": "2026-01-30T23:45:00Z", "ts_close": "2026-01-30T23:50:00Z", "open": 50180.28, "high": 55316.8, "low": 48548.44, "close": 50888.81, "volume": 1973.6744}
{"ts_open": "2026-01-30T23:50:00Z", "ts_close": "2026-01-30T23:55:00Z", "open": 50888.81, "high": 53366.41, "low": 47545.55, "close": 51518.33, "volume": 1886.6701}
{"ts_open": "2026-01-30T23:55:00Z", "ts_close": "2026-01-31T00:00:00Z", "open": 51518.33, "high": 58363.88, "low": 49798.85, "close": 54907.27, "volume": 2010.3284}
data.meta.json создаётся рядом. JSONL формат — для стриминга: jq -c '.close' data.jsonl проходит 100k+ баров без загрузки в память.
Python API: to_dataframe
>>> from market_emulator import Emulator, GenerateRequestV1, to_dataframe
>>> from market_emulator.contracts.enums import Timeframe
>>> from market_emulator.contracts.horizon import HorizonCount
>>>
>>> emulator = Emulator()
>>> request = GenerateRequestV1(
... timeframe=Timeframe.ONE_MINUTE,
... horizon=HorizonCount(count=5),
... seed=42,
... )
>>> result = emulator.generate(request)
>>> result.meta.normalized.model_dump()
{'start': datetime.datetime(2026, 8, 5, 11, 1, tzinfo=datetime.timezone.utc),
'end': datetime.datetime(2026, 8, 5, 11, 6, tzinfo=datetime.timezone.utc),
'count': 5,
'step_seconds': 60,
'step_kind': 'fixed'}
>>> result.meta.fingerprint
'38c4736fb1e75de585630001f1ffdaf613a6125546bed13938235d9d48841580'
>>> df = to_dataframe(result)
>>> df.head()
ts_open ts_close open high low close volume symbol orderbook funding universe is_partial_last_bar regime_label quality_flags context
0 2026-08-05 11:01:00+00:00 2026-08-05 11:02:00+00:00 50180.28 51138.92 48548.44 50000.00 273.9469 None None None None False None [] None
1 2026-08-05 11:02:00+00:00 2026-08-05 11:03:00+00:00 50000.00 50792.60 49130.56 50524.22 251.9564 None None None None False None [] None
2 2026-08-05 11:03:00+00:00 2026-08-05 11:04:00+00:00 50524.22 51719.00 48550.17 51405.81 376.1741 None None None None False None [] None
3 2026-08-05 11:04:00+00:00 2026-08-05 11:05:00+00:00 51405.81 54167.23 49056.30 52610.45 513.1390 None None None None False None [] None
4 2026-08-05 11:05:00+00:00 2026-08-05 11:06:00+00:00 52610.45 55316.80 50184.69 50888.81 558.4578 None None None None False None [] None
to_dataframe возвращает pandas DataFrame с колонками: ts_open, ts_close, open, high, low, close, volume (базовые OHLCV) плюс symbol, orderbook, funding, universe, is_partial_last_bar, regime_label, quality_flags, context (опциональные, появляются при соответствующих overlays). Колонки orderbook / funding и universe заполняются per-bar при liquidity_profile / funding_profile / multi-pair пути.
Что означают ключевые поля в meta
fingerprint— SHA256-идентификатор конфигурации (включает timeframe, seed, scenario, profile, overlays). Используется для кеширования и регрессионных проверок.data_hash— SHA256 самих свечных данных (безgenerated_at).meta_hash— SHA256 всейmetaструктуры.resampled—true, если таймфрейм целевого запроса ≠ 1m.base_timeframe/target_timeframeфиксируют путь.scenario_name/scenario_version/scenario_hash—nullесли сценарий не указан; иначе триада для трейсинга изменений сценария.normalized.start/normalized.end— финальный диапазон (UTC). При--nowсовпадает с ожидаемым пользователем; без--now— рассчитывается от текущего момента.debug.regimes_summary— distribution of bars by regime ({"trend_up": 5}— все 5 баров вtrend_upфазе).truth_dataset_version/raw_data_hash/dataset_sanity_checks— заполняются приscenario='baseline_mix'(или другом scenario) через Truth Dataset Contract.
Детерминизм
Одни и те же входы всегда дают одинаковый выход:
market-emulator generate --timeframe 1m --count 1000 --seed 42 \
--now 2026-01-31T00:00:00Z --format json > run1.json
market-emulator generate --timeframe 1m --count 1000 --seed 42 \
--now 2026-01-31T00:00:00Z --format json > run2.json
# Байт-идентично (за исключением generation_id и generated_at)
diff run1.json run2.json
Важно: --now обязателен для count и duration горизонтов — без него время отсчитывается от текущего момента, и rerun в разные секунды даёт разные timestamps. Для горизонта HorizonRange (с start/end) --now не нужен.
Регрессионный пакет tests/test_regression_pack.py фиксирует SHA256-хеши для 10 канонических сценариев. Это позволяет ловить любой непреднамеренный drift в пайплайне на CI.
Каталог сценариев
Подробный каталог — docs/scenarios.md. Здесь — компактный индекс с разбивкой по принципу деления.
Принцип деления
Все 71 встроенных сценария организованы по двум осям:
- RegimeType (
src/market_emulator/contracts/enums.py::RegimeType) — какая рыночная фаза моделируется:mix,trend_up,trend_down,range,crash,technical_pattern,wyckoff,event_driven,behavioral. Это enum, который попадает вmeta.scenario_regimeкаждого результата и читается downstream-анализаторами. - Механизм — что именно делает сценарий с ценовым процессом: чистый drift + σ, переключение фаз, multi-segment drift_segments, session-aware окна, gap (open_jump), открытые funding/liquidity overlays, attachment к TA-паттерну, attachment к Wyckoff-фазе, attachment к крипто-эвенту.
Built-in сценарии делятся на 6 исторических волн (раздел §24-profile catalogue в docs/scenarios.md):
| Волна | Регион | Сценариев | Что добавляет |
|---|---|---|---|
| Historical 18 | MIX/TREND/RANGE | 18 | Базовая линейка: baseline, trend, range, crash, stress, high-vol |
| Profile (Categories 2, 3, 5) | Всех типов | 14 | Покрытие trend × vol × liquidity × session матрицы |
| Crash-spike | CRASH-подкатегория | 7 | Liquidation, squeeze, blow-off, capitulation |
| Technical-pattern | TECHNICAL_PATTERN | 7 | H&S, треугольники, double/триple top/bottom, cup+handle |
| Wyckoff | WYCKOFF | 6 | Accumulation, distribution, spring, UTAD, SOS/SOW |
| Crypto-event | EVENT_DRIVEN | 8 | Token unlocks, listings, exploits, governance, depeg |
| Calendar + behavioral | MIX, BEHAVIORAL | 11 | Halving, options expiry, daily open/close, FOMO, panic |
Расчёт: 18 + 14 + 7 + 7 + 6 + 8 + 11 = 71. Регрессионные хеши для первых 18 в regression/expected_hashes.json неизменны — multi-pair путь, overlays и новые волны byte-identical для прошлых сценариев.
Группа A: Каноническая линейка (18)
Базовая линейка, на которой собирались регрессионные хеши. Эти сценарии — валидационный baseline: если какая-то фича ломает их хеш, ловит tests/test_regression_pack.py.
Baseline. baseline_mix — MIX, σ=0.02, normal noise. Сбалансированная смесь режимов; основной «нормальный рынок» для regression-тестов.
Trend. trend_up_soft (drift +0.0003, σ=0.015), trend_up_hard (drift +0.0008, σ=0.025), trend_down_soft (drift -0.0003, σ=0.015), trend_down_hard (drift -0.0008, σ=0.025).
Range. range_tight (drift=0, σ=0.01), range_wide (drift=0, σ=0.025).
High-volatility. high_vol_spiky — MIX, σ=0.04, Student-t (df=5) для fat tails, vol clustering.
Market events. crash_then_recover (drift -0.0002, σ=0.035, Student-t df=4), pump_then_dump (drift +0.0002, σ=0.035, Student-t df=4).
Stress & edge cases. extreme_volatility_burst (σ ramp 0.02→0.15→0.02 за 60+180 бар, volume 10x), gap_event (open_jump +5% @ bar 100, -5% @ bar 200), flash_crash_recovery (-20% за 10 бар @ bar 80, восстановление за 20).
Funding profile (требует funding_profile). funding_extreme_positive_240, funding_extreme_negative_240 — sustained funding rates, registered как overlay-профили.
Low-liquidity (требует liquidity_profile). low_liquidity_silent — RANGE + tight OHLCV + low-liquidity orderbook.
Regime transition. regime_transition_smooth — 60 бар в A, 60 бар в плавном переходе, 60 бар в B, 60 бар обратно.
Pullback / breakout. trending_with_pullbacks (аптренд + 3-4 pullback 1-3% с восстановлением), ranging_with_breakout_attempts (100 бар range, failed breakout, 100 бар range, успешный breakout).
Volume distribution. high_volume_no_price_move (200 бар, volume 5x, drift=0).
Группа B: Profile gap-fill (14)
Закрывает QA-program audit для матрицы trend × vol × liquidity × session. Каждый сценарий — параметризованный сценарий из исторических 18 + дополнительные метаданные (market_session, liq, vol).
Breakout (3): breakout_clean (BREAKOUT, medium vol, normal liq), breakout_failed (range с fake breakout, medium vol), breakout_chaotic_vol (BREAKOUT, extreme vol, thin liq).
Mean reversion (2): mean_reversion_high_vol (mean reversion, high vol, normal liq), mean_reversion_low_vol_deep (mean reversion, low vol, deep liq, asia session).
Session diversity (5): asia_uptrend (uptrend, asia), europe_downtrend (downtrend, europe), cross_session_breakout (breakout, cross, high vol), cross_session_chaotic (chaotic, extreme vol, thin liq, cross), asia_low_vol_range (range, low vol, asia).
Mixed combos (4): breakout_low_vol_normal (breakout, low vol), mean_reversion_medium_vol_thin (mean reversion, medium vol, thin), chaotic_medium_vol_normal (chaotic, medium vol), chaotic_high_vol_deep (chaotic, high vol, deep liq, cross).
Группа C: Crash / spike sub-category (7)
Подкатегория CRASH — ликвидации, шорт-сквизы, blow-off, capitulation. Все multi-phase через drift_segments + open_jumps.
liquidation_cascade— 3 последовательных падения + 2 failed relief rallies.short_squeeze— ускоряющийся аптренд (drift увеличивается per window).dead_cat_bounce— резкое падение + малый отскок + продолжение снижения.parabolic_blow_off_top— ускоряющийся аптренд + резкий разворот.capitulation_wick— single -10% open-gap @ bar 100 + V-восстановление.vol_climax_reversal— 100 бар тишины + 20-бар massive-volume reversal.black_swan— single -15% open-gap, extreme σ, без восстановления.
Группа D: Technical patterns (TECHNICAL_PATTERN, 7)
Все несут market_regime = TECHNICAL_PATTERN. Используют multi-segment drift_segments для построения TA-фигур.
head_and_shoulders_top— 7-phase: left peak → head (выше) → right peak (ниже) → пробой neckline.inverse_head_and_shoulders— зеркальный: тройное дно.ascending_triangle_breakout— растущие lows + горизонтальное сопротивление + breakout.descending_triangle_breakdown— зеркальный.cup_and_handle— U-shape + handle consolidation + breakout.double_top— два пика ≈ одна высота + пробой.double_bottom— зеркальный.
Группа E: Wyckoff (WYCKOFF, 6)
market_regime = WYCKOFF. Классические Wyckoff-фазы.
wyckoff_accumulation— PS / SC / AR / ST + Spring + markup.wyckoff_distribution— зеркальный: PSY / BC / AR / ST + UTAD + markdown.wyckoff_spring— range → false breakdown → резкое восстановление → markup.wyckoff_upthrust— range → false breakout up → reaction → markdown.wyckoff_markup_phase— SOS (sign of strength) + LPS (last point of support) + continuation.wyckoff_markdown_phase— SOW + LPSY + continuation down.
Группа F: Crypto events (EVENT_DRIVEN, 8)
market_regime = EVENT_DRIVEN. Событийные сценарии для крипто-рынка.
token_unlock_cliff— 5% cliff unlock: pre-accumulation + cliff dump + post-consolidation.token_linear_unlock— constant negative drift, 240 бар, 30-дневный linear vesting.exchange_listing_pump— pre-listing quiet + +15% open_jump + post-listing fade.exchange_delisting_dump— pre-warning + -10% open_jump + continued dump.bridge_exploit— pre-exploit + -40% open_jump (TVL drain) + post-exploit drift, t_df=3 fat tails.governance_attack— pre-vote + +20% open_jump (whale vote pass) + post-vote fade.stablecoin_peg_break— pre-depeg + -15% open_jump (USDC depeg) + partial recovery.flash_loan_manipulation— pre-attack + -15% open_jump + revert (drift +0.18 за 1 бар) + stabilization.
Группа G: Calendar + behavioral (11)
Calendar (6) — MIX regime, фокус на time-of-day / day-of-week / cyclical.
bitcoin_halving_cycle— сжатый 4-летний цикл: pre-halving rally + post-halving top + bear + capitulation + new cycle.monthly_options_expiry— pre-expiry elevated σ + max-pain pinning + post-expiry compression.daily_open_burst— opening auction: directional drift + elevated σ, бар 0-14.daily_close_lull— lull бар 210-239 с reduced σ.weekend_thin_market— tighter range, reduced base_volume.asia_session_round_trip— Asia range + EU breakout + US continuation.
Behavioral (5) — market_regime = BEHAVIORAL. Возвращают новый enum для отличия от trend/range/crash.
fomo_late_parabolic— quiet accumulation + parabolic acceleration + blow-off + crash.panic_selling_climax— sustained drop + vol expansion (Student-t df=4).hope_relief_bounce— crash + 3 progressively weaker bounces + continued decline.throw_over_pattern— accumulation + +8% open_jump + fast return + continued downtrend.capitulation_then_markup— -10% open_jump + accumulation + markup.
Overlay-профили (11)
Overlay-профили — это конфигурации существующих сценариев с заполненными overlay-полями. Регистрируются в MarketProfileRegistry через MarketProfileRegistry.get_overlay(name).
| Профиль | Базовый сценарий | Overlay-поле |
|---|---|---|
deep_book_uptrend |
trend_up_hard |
liquidity_class=deep |
deep_book_downtrend |
trend_down_hard |
liquidity_class=deep |
deep_book_breakout |
ranging_with_breakout_attempts |
liquidity_class=deep |
deep_book_chaotic |
extreme_volatility_burst |
liquidity_class=deep |
deep_book_range |
high_volume_no_price_move |
liquidity_class=deep |
funding_extreme_positive_240 |
baseline_mix |
FundingProfile(pattern="extreme_positive") |
funding_extreme_negative_240 |
baseline_mix |
FundingProfile(pattern="extreme_negative") |
funding_periodic_oscillating_240 |
baseline_mix |
FundingProfile(pattern="regime_dependent") |
multi_pair_btc_eth_correlated_up |
trend_up_hard |
MultiPairSpec(BTCUSDT, ETHUSDT, SOLUSDT) |
correlated_xrp_btc_eth |
baseline_mix |
MultiPairSpec(XRPUSDT, BTCUSDT, ETHUSDT) |
alt_rotation_jan_feb |
baseline_mix |
MultiPairSpec(8 alts) |
Composability-паттерны (20)
Это поля LiquidityProfile, FundingProfile, MultiPairSpec, которые compose-ятся поверх любого базового сценария. Не отдельные сценарии — overlay knobs.
LiquidityProfile (10): levels_per_side, base_depth_usd, spread_bps_mean, spread_bps_std, depth_shape; overlay knobs: liquidity_dry_up, orderbook_imbalance, stop_cascade, iceberg_absorption, v2 overlays: spoofing, layering, stop_hunt, twap_execution, vwap_execution, iceberg_variation.
FundingProfile (5): базовые 5 patterns (extreme_positive, extreme_negative, regime_dependent, periodic, oscillating); overlay knobs (B5-B7): funding_flip, funding_squeeze, stable_coin_depeg_shock; volatility-surface annotations: volatility_regime, term_structure, funding_rate_cap, premium_index.
MultiPairSpec (3): lead_lag, correlation_breakdown, cross_asset_contagion.
Truth-сценарии (8)
Регистрируются в TRUTH_SCENARIO_SUMMARIES, инвокация через Emulator.generate_truth(TruthGenerateRequestV1(...)). Возвращают TruthDatasetResultV1 с target массивами и truth_meta.target для QA-ассертов.
simple_test— минимальный sanity-сценарий.linear_trend/scenario_a— детерминированный линейный тренд (или piecewise черезdrift_change_points). Ground truth для slope-based моделей.markov_regime/scenario_b— Markov-режимы.hard_pattern/scenario_c— injection of patterns A и B с target-modes (multiclass/binary).periodic/scenario_d/sum_of_sinusoids— sum-of-sinusoids с известным спектром.leak_trap/scenario_e— anti-leak датасет; если модель достигает accuracy > 0.6, это баг пайплайна.change_point_injection— AR(1) с deterministic level shift @ bar 100, AR(1) mean shifts +5σ. Ground truth дляchange_point_detection.mean_reversion_oscillation— OU-process (θ=0.1, σ=1.0, 240 шагов). Ground truth дляmean_reversion.
Примеры
В examples/ собраны рабочие скрипты:
# Python quickstart
python examples/quickstart_module.py
# CLI-скрипты
bash examples/generate_csv_10d_1m.sh
bash examples/generate_json_30d_5m.sh
# Комплексный проход по всем задокументированным CLI-поверхностям
bash examples/demo_cli.sh
demo_cli.sh — это single end-to-end скрипт, который проходит каждую документированную CLI-поверхность (basic generation, date range, duration, scenarios, custom profile, export formats, multi-timeframe, batch, determinism, --now override, JSONL streaming с jq) и пишет результаты в data/.
# Batch-план
mkdir -p data
market-emulator batch --plan examples/batch_plan.yaml \
--now 2026-01-31T00:00:00Z
batch_plan.yaml документирует четыре кейса: 1m baseline, 5m trending, 1d по диапазону дат, micro-5m за неделю. Все четыре с фиксированными seed'ами дают байт-идентичный output.
Карта документации
docs/ разбит на три уровня:
User-facing references (консьюмерская документация):
docs/cli.md— полный CLI-референс.docs/module_api.md— Python API.docs/contracts.md— канонический Pydantic-интерфейс и схемы output.docs/scenarios.md— каталог сценариев (incl. truth-сценарии A, E, C).docs/determinism.md— гарантии воспроизводимости.docs/resampling.md— правила ресемплинга таймфреймов.docs/constraints.md— рыночные инварианты.
Architecture meta (внутренний дизайн):
docs/architecture.md— пайплайн и стадии.docs/architecture/ARCHITECTURE_MAP.md— cross-component map.docs/architecture/DESIGN_PRINCIPLES.md— четыре несущие правила.docs/architecture/ARCHITECT_PROJECT_CONTEXT.md— domain overlay.docs/components/— описание компонентов (api, cli, config, contracts, core, exporters, generators, scenarios, trainer, truth, utils).docs/contracts/— boundary-contracts (api-http, batch-plan, deployment-data-dir, generate-request, scenario-registry, truth-dataset).docs/contract_spec/— внешний консьюмерский контракт, compatibility rules, fixtures, machine-readable schemas.docs/adr/— принятые и планируемые архитектурные решения (ADR-0001, 0002, 0006, 0007).
Operational:
docs/ROADMAP.md— активная волна и план.docs/RUNBOOK.md— failure triage.docs/contracts/truth-dataset.md— криптографически верифицируемые артефакты.
Тестирование
# Полный прогон
pytest
# Без slow/perf
pytest -m "not slow and not perf"
# Регрессионный пакет
pytest tests/test_regression_pack.py
# Сценарии + jsonset
pytest tests/test_jsonset_cli.py tests/test_jsonset_exporter.py \
tests/test_jsonset_schema_conformance.py
pytest настроен на --cov-fail-under=90 (минимум 90% покрытия). pytest-xdist запускает тесты в 8 потоках. Маркеры slow и perf отсекаются через -m "not slow and not perf".
Регрессионный пакет проверяет SHA256-хеши для 10 канонических сценариев. Любой непреднамеренный drift в пайплайне (от рефакторинга до изменения формата) ловится этим пакетом на CI.
CONTRIBUTING.md документирует dev-окружение, code style, процесс отправки изменений.
Источники
- github.com/elriseio/market-data-emulator — MIT.
- docs/architecture.md — внутренний pipeline.
- docs/contracts.md — канонические Pydantic-контракты.
- docs/scenarios.md — каталог сценариев.
- docs/adr/0006-multi-pair-derivatives-design.md — multi-pair design rationale.
- docs/contracts/truth-dataset.md — Truth Dataset Contract.
- Pydantic v2 docs — база для
contracts/. - FastAPI docs — HTTP API.
- Typer docs — CLI.