Build 060.30: finalize Market Data Acquisition documentation

This commit is contained in:
2026-08-03 23:30:41 +03:00
parent 8c98de9acc
commit 64a5bdd04c
61 changed files with 13980 additions and 1873 deletions

View File

@@ -6,8 +6,8 @@
|---|---|
| Тип | Target Architecture |
| Статус | Active Baseline |
| Версия | 1.0 |
| Дата | 2026-07-31 |
| Версия | 1.1 |
| Дата | 2026-08-03 |
| Проект | Dzentra |
---
@@ -51,6 +51,12 @@ Dzentra является работающим modular monolith, который
Любое изменение ownership или направления зависимостей выполняется
отдельным Build после анализа фактических consumers.
Вертикаль Trades Feed принята от Production Runtime до persistent
Storage, Startup Recovery, Historical Access и deterministic Replay в
Builds 060.25060.29. Build 060.30 актуализировал документацию этого
состояния. Завершение одной вертикали не означает готовность остальных
Market Data feeds или всей торговой платформы.
---
## 3. Верхнеуровневая карта
@@ -150,7 +156,8 @@ app/src/market_data/acquisition/
```
**Текущий активный результат:** Production Runtime для Trades Feed
завершён в Build 060.25.
завершён в Build 060.25, а network/fault/stress/soak/live verification —
в Build 060.26.
### 4.2. Validation, Canonicalization and Data Quality
@@ -195,6 +202,10 @@ Missing Data Recovery не должны смешиваться в один ун
`TradeStreamState` принадлежит Consistency Layer, а не общему Market
Data Storage и не WebSocket Transport.
Подтверждённая durable точка восстановления хранится отдельно как
Persistent Checkpoint. Она восстанавливает operational state после
перезапуска, но не заменяет долговременную историю рынка.
### 4.4. Persistent Market Data Storage
**Назначение:** долговременно хранить Canonical Market Data для:
@@ -206,31 +217,106 @@ Data Storage и не WebSocket Transport.
- backtesting;
- аудита качества данных.
Хранилище должно определить:
Build 060.27 реализовал два явно разных слоя:
- raw и canonical retention policy;
- ключи идемпотентности;
- порядок и уникальность событий;
- партиционирование по типу данных, символу и времени;
- правила исправления и повторной загрузки;
- provenance и версию Canonical schema.
```text
app/src/storage/
общий/исторический PostgreSQL foundation, pool и migrations
Существующий `app/src/storage/` нельзя автоматически считать готовым
Market Data Storage. Его фактические контракты проверяются в
Build 060.27.
app/src/market_data/storage/
канонические contracts и repositories Market Data
```
### 4.5. Historical Access and Replay
Канонический слой содержит idempotent PostgreSQL repositories для
Trades, Quotes и Candle revisions, provenance, schema versions,
write-only `MarketDataStorage`, Partition Manager и Retention Service.
Production writer wiring подключён только для Trades. Существующие REST
Quotes/Candles обслуживают Trading/UI, но не записываются этими
persistent repositories автоматически.
**Назначение:**
Физическое partitioning реализовано отдельным parent/default family для
каждого типа данных:
- исторические запросы;
- последовательное воспроизведение Canonical Market Data;
- управляемые виртуальные часы;
- одинаковые контракты данных для production и replay consumers;
- основа backtesting и воспроизводимой диагностики.
```text
market_data.trades
RANGE (executed_at)
├── trades_default
└── trades_YYYY_MM
Replay должен воспроизводить порядок событий и не обходить Canonical
Models или validation guarantees.
market_data.quotes
RANGE (received_at)
├── quotes_default
└── quotes_YYYY_MM
market_data.candle_revisions
RANGE (open_time)
├── candle_revisions_default
└── candle_revisions_YYYY_MM
```
`venue` и `symbol` участвуют в identity и queries, но не являются
уровнями физического partitioning. Месячный child имеет UTC-границы для
всех venue/symbol соответствующего parent. Partition Manager вызывается
явно, сериализует создание advisory lock, переносит подходящие строки из
default partition и регистрирует управляемый child в
`market_data.partition_registry` в одной транзакции.
Retention также запускается только явно. Он не является Scheduler-задачей
Production Runtime. Raw storage, automatic partition creation,
historical backfill и гарантия полноты истории пока не реализованы.
### 4.5. Persistent Checkpoint and Startup Recovery
Build 060.28 реализовал постоянный operational checkpoint Trades:
- identity checkpoint — `(venue, symbol)`;
- checkpoint ссылается на точную durable Trade;
- Trade и checkpoint продвигаются в одной PostgreSQL-транзакции;
- optimistic CAS revision защищает конкурентное продвижение;
- in-memory state изменяется только после durable commit;
- Hydration восстанавливает checkpoint и bounded deduplication tail;
- Startup Recovery заполняет разрыв при закрытом Live gate;
- buffered live data выпускаются только после успешного Recovery.
Checkpoint существует только для Trades. Он не является второй копией
истории, не доказывает полноту Storage и не имеет отдельного feature
flag: persistent checkpoint включается вместе с Market Data Storage.
### 4.6. Historical Access
Build 060.29 создал отдельный synchronous read-side:
```text
app/src/market_data/access/
```
Trade, Quote и Candle revision history читаются forward keyset pages.
Каждый вызов Historical query читает одну текущую committed page;
единый snapshot между последовательными страницами не гарантируется.
Access не изменяет durable data или operational checkpoint, вызывается
явным consumer и не запускается из Bootstrap автоматически.
### 4.7. Deterministic Replay
Replay использует те же Canonical Models, что live processing, и не
обходит validation guarantees. Migration 9 добавила общий immutable
`replay_sequence` для Trades, Quotes и Candle revisions. Миграция
блокирующая и перед большой production-базой требует maintenance window,
backup и предварительного замера длительности.
Replay Plan Builder материализует bounded snapshot в транзакции
`READ ONLY REPEATABLE READ`. Превышение `max_records` завершается явной
ошибкой. Смешанный порядок событий равен
`(replay_at, replay_sequence)`.
`DeterministicReplayClock` принимает timezone-aware `datetime`,
канонизирует его в UTC, допускает равное время и запрещает движение
назад; naive `datetime` запрещён. `ReplaySession` — caller-owned one-shot
lifecycle с новым графом зависимостей для каждого запуска.
Автоматический Replay startup, default consumer, отдельный Replay
endpoint, hidden task и historical backfill не реализованы. Backtesting
может использовать этот фундамент, но сам не входит в Build 060.29.
---
@@ -436,6 +522,11 @@ Backtesting является отдельной capability, но переисп
Отдельная копия торговой логики только для backtest не допускается.
Persistent Storage, Historical Access, Replay Plan, deterministic clock
и Replay Session уже создают необходимые data/time prerequisites.
Backtesting engine, exchange/OMS simulator и автоматическая composition
этой capability пока не реализованы.
---
## 8. Правила зависимостей
@@ -485,7 +576,10 @@ order, account и runtime events, а не через обратные импор
| Market Data Acquisition | `app/src/market_data/acquisition/` |
| Application Runtime Composition | `app/src/bootstrap/` |
| Runtime Events | `app/src/runtime_events/` и локальные runtime events |
| Storage foundation | `app/src/storage/` |
| Общий/исторический Storage foundation | `app/src/storage/` |
| Canonical Market Data Storage | `app/src/market_data/storage/` |
| Historical Market Data Access | `app/src/market_data/access/` |
| Deterministic Replay | `app/src/market_data/replay/` |
| Market Intelligence | `app/src/trading/market_intelligence/` |
| Trading Decision | `app/src/trading/decision/` |
| Execution | `app/src/trading/execution/` |
@@ -501,9 +595,11 @@ order, account и runtime events, а не через обратные импор
4. подготовлен отдельный migration plan;
5. проверено направление импортов.
Предложенные имена вроде `analytics/features`,
`market_data/normalization` или `market_data/storage` являются
логическими ориентирами, а не заранее утверждённой файловой структурой.
Предложенные имена вроде `analytics/features` или
`market_data/normalization` являются логическими ориентирами, а не
заранее утверждённой файловой структурой. Каталоги
`market_data/storage`, `market_data/access` и `market_data/replay` уже
являются принятыми фактическими boundaries.
---
@@ -511,18 +607,19 @@ order, account и runtime events, а не через обратные импор
| Область | Состояние |
|---|---|
| Market Data Acquisition foundation | Реализуется Build-by-Build |
| Market Data Acquisition foundation | Trades vertical slice завершён; остальные feeds развиваются Build-by-Build |
| Trades Feed production runtime | Completed — Build 060.25 |
| Runtime integration/stress verification | Next — Build 060.26 |
| Persistent Market Data Storage | Planned — Build 060.27 |
| Persistent Checkpoint / Startup Recovery | Planned — Build 060.28 |
| Historical Access / Replay | Planned — Build 060.29 |
| Runtime integration/stress/live verification | Completed — Build 060.26 |
| Persistent Market Data Storage | Completed — Build 060.27 |
| Persistent Checkpoint / Startup Recovery | Completed — Build 060.28 |
| Historical Access / Replay | Completed — Build 060.29 |
| Market Data Acquisition Final Documentation | Completed — Build 060.30 |
| Validation ownership audit/refactoring | Planned — Build 061.00 |
| Market Intelligence | Существующая активная подсистема |
| Decision / Execution / Position | Существующие подсистемы, требуют будущих boundary audits |
| OMS / Reconciliation | Целевая ответственность, отдельный план не утверждён |
| Backtesting | Целевая capability после Storage и Replay |
| Operations / Deployment | Целевая cross-cutting программа |
| Backtesting | Storage/Replay prerequisites готовы; capability не реализована |
| Operations / Deployment | Docker hardening принят; deployment остаётся отдельной cross-cutting программой |
`Completed` означает завершение утверждённого Build, а не окончательную
сертификацию всей подсистемы для любой production-нагрузки.

View File

@@ -1,11 +1,117 @@
# Architecture Overview
# Обзор архитектуры Dzentra
Проект строится как modular monolith с разделением по слоям:
**Статус:** Current; Build 060.30.3
- `telegram` — меню, handlers, routers
- `bootstrap` — сборка приложения
- `core` — конфигурация и базовые сущности
- `trading` — бизнес-логика торговли
- `storage` — доступ к данным
- `integrations` — внешние API
- `shared` — общие утилиты
Dzentra развивается как модульный монолит: приложение собирается одним
Composition Root, но ответственность разделена между пакетами и
связывается через явные контракты.
Каноническое подробное описание готовой Market Data вертикали находится
в [архитектуре Trades Feed](trades_feed.md). Эта страница служит
верхнеуровневой картой всего приложения и не дублирует lifecycle и
recovery-сценарии.
## Composition Root и текущие связи
`src.bootstrap` загружает настройки, выполняет базовый `init_schema()` с
PostgreSQL I/O, собирает опциональный Market Data pool/repository graph
без I/O и управляет общим lifecycle Telegram, Trade Runtime и Market
Data Storage.
Схема запуска и основных взаимодействий:
```text
main.py → bootstrap
├→ init_schema() → src.storage → PostgreSQL
├→ Telegram polling → trading / integrations
└→ optional Trade Runtime
├→ market_data/acquisition → Consistency / Recovery
└→ optional market_data/storage → PostgreSQL
trading / integrations / notifications / runtime_events
→ существующие связи модульного монолита
```
Это карта исполнения, а не утверждение о полностью однонаправленном
import graph. В исторических пакетах сохраняются перекрёстные связи между
`trading`, `integrations` и `notifications`. Новая Market Data вертикаль
имеет более строгие protocol/composition boundaries. Пакеты не должны
самостоятельно запускать общий Application lifecycle.
## Основные пакеты `app/src`
| Пакет | Ответственность |
|---|---|
| `bootstrap` | Composition Root, startup, graceful shutdown и feature-flag wiring |
| `core` | Settings, logging и общие базовые компоненты приложения |
| `telegram` | Routers, handlers, keyboards и Telegram UI |
| `trading` | Торговая бизнес-логика, журнал и существующие runtime-компоненты |
| `integrations` | Клиенты и адаптеры внешних API вне канонической Market Data вертикали |
| `market_data` | Acquisition, persistent Storage, Historical Access и deterministic Replay |
| `runtime_events` | Внутренние runtime-события и их публикация |
| `notifications` | Доставка и маршрутизация уведомлений |
| `storage` | Смешанный общий/исторический PostgreSQL foundation, migrations и in-memory caches |
| `shared` | Общие вспомогательные компоненты без владения lifecycle |
## Market Data
Пакет `market_data` разделён на четыре независимые области:
| Область | Назначение |
|---|---|
| `acquisition` | WebSocket/REST ingestion, validation, Consistency, Recovery и Production Runtime Trades |
| `storage` | Канонические Trade/Quote/Candle repositories, persistent Trade checkpoint, partitions и Retention |
| `access` | Read-only Historical Access к сохранённым данным |
| `replay` | Ограниченный deterministic Replay через явно создаваемую one-shot session |
WebSocket Runtime, Consistency/Recovery и persistence wiring завершены
только для Trades. Существующие REST-потоки Quotes/Candles продолжают
обслуживать Trading/UI. Quote и Candle repositories/readers реализованы,
но persistent writer consumers к ним пока не подключены.
Historical Access, Replay, Retention и создание месячных партиций не
являются фоновыми задачами приложения и выполняются только явным
вызывающим кодом.
## Две Storage-границы
Историческое имя `src/storage` охватывает несколько разных обязанностей:
- `session.py`, `schema.py` и repositories журнала/balance snapshots
обслуживают базовую схему приложения;
- `instrument_store.py` и `quote_store.py` предоставляют in-memory caches;
- `postgres_pool.py` и `migrations.py` являются общей
PostgreSQL-инфраструктурой, которую использует новая Market Data
вертикаль.
`src/market_data/storage` владеет каноническими persistent Market Data
contracts и repositories. Поэтому `src/storage` нельзя считать целиком
устаревшим или неиспользуемым. Подробная карта ownership приведена в
[Storage README](../../app/src/storage/README.md).
## Runtime и данные
Краткая цепочка принятой Trades-вертикали:
```text
Dzengi WebSocket + REST Recovery
Canonical Trade → Consistency
optional atomic Trade + checkpoint write
next startup: Hydration → Startup Recovery → buffered live processing
explicit caller → Historical Access / Replay
```
PostgreSQL доступен обычному Bootstrap даже при выключенных Trade Stream
и Market Data Storage, поскольку базовые таблицы журнала и balance
snapshots инициализируются всегда.
## Связанные документы
- [Структура проекта](project_structure.md)
- [Текущая архитектура Trades Feed](trades_feed.md)
- [Эксплуатация Trade Stream Runtime](../operations/trades_feed_runtime.md)
- [Архитектура Build 060.30](../migrations/build_060_30_architecture.md)

View File

@@ -1,15 +1,108 @@
# Project Structure
# Структура проекта Dzentra
## Корневые папки
- `app/` — код приложения
- `docs/` — документация
- `infra/` — Docker и compose
**Статус:** Current; Build 060.30.3
## Внутри `app/src`
- `bootstrap/`
- `core/`
- `telegram/`
- `trading/`
- `storage/`
- `integrations/`
- `shared/`
Документ показывает назначение верхних уровней текущего checkout. Это
навигационная карта, а не полный перечень файлов.
## Корень репозитория
```text
dzentra_bot/
├── app/ Python-приложение, зависимости и тесты
├── docs/ архитектура, runbook, roadmap и история Builds
├── infra/ Dockerfile и Docker Compose
├── scripts/ общие quality-gate и вспомогательные scripts
├── pyrightconfig.json единая конфигурация статической проверки
├── README.md входная страница существующего checkout
└── bootstrap_project.py исторический генератор первоначального каркаса
```
`bootstrap_project.py` не является командой установки или запуска
текущего проекта.
## Каталог `app`
| Путь | Назначение |
|---|---|
| `src/` | Production-код модульного монолита |
| `tests/unit/` | Unit-тесты default offline regression |
| `tests/static/` | Обязательный Pyright gate внутри default pytest |
| `tests/integration/` | Opt-in локальные network/PostgreSQL сценарии |
| `tests/stress/` | Fixed stress и opt-in soak проверки |
| `tests/live/` | Только явно разрешённые проверки внешних endpoints |
| `tests/support/` | Общие test harness и безопасные opt-in helpers |
| `scripts/` | Ручные диагностические scripts |
| `tools/` | Исследовательские Dzengi probes и сохранённые samples |
| `.env.example` | Полный безопасный пример поддерживаемых настроек |
| `requirements.txt` | Прямые Production dependencies |
| `requirements.lock` | Полный hash-locked набор Production image |
| `requirements-dev.txt` | Production dependencies плюс pytest и Pyright |
Диагностические `app/scripts` и `app/tools` не запускаются автоматически
вместе с Production Runtime.
## Пакеты `app/src`
```text
src/
├── bootstrap/ Composition Root и общий lifecycle
├── core/ settings, logging и общие компоненты
├── integrations/ внешние API вне Market Data vertical slice
├── market_data/ acquisition, storage, access и replay
├── notifications/ доставка и маршрутизация уведомлений
├── runtime_events/ внутренняя Runtime Event система
├── shared/ общие вспомогательные компоненты
├── storage/ общий/исторический Storage foundation
├── telegram/ Telegram UI и handlers
├── trading/ торговая бизнес-логика и runtime-компоненты
└── main.py async entry point приложения
```
## Пакет `market_data`
```text
market_data/
├── acquisition/ Canonical models, protocols, validation, Recovery и Runtime
├── storage/ persistent repositories, checkpoint, partitions и Retention
├── access/ Historical Access read-side
└── replay/ Replay plan, deterministic clock и one-shot session
```
Фактические WebSocket Runtime, Consistency/Recovery и persistent writer
consumer существуют только для Trades. REST-потоки Quotes/Candles
продолжают использоваться Trading/UI; их persistence/read-side API
реализованы отдельно.
## Storage ownership
`src/storage` и `src/market_data/storage` не являются взаимозаменяемыми:
| Путь | Фактическая роль |
|---|---|
| `src/storage/session.py`, `schema.py`, `repositories/` | Базовая PostgreSQL-схема журнала и balance snapshots |
| `src/storage/instrument_store.py`, `quote_store.py` | In-memory caches существующих consumers |
| `src/storage/postgres_pool.py`, `migrations.py` | Общая инфраструктура pool/migrations, используемая Market Data Storage |
| `src/market_data/storage/` | Канонические persistent Market Data contracts и repositories |
| `src/market_data/access/` | Отдельный read-side сохранённых данных |
| `src/market_data/replay/` | Отдельная deterministic Replay-вертикаль |
Подробности приведены в [README пакета Storage](../../app/src/storage/README.md).
## Документация
| Каталог | Назначение |
|---|---|
| `docs/architecture/` | Current и target architecture |
| `docs/operations/` | Актуальные эксплуатационные руководства |
| `docs/migrations/` | Architecture/report документов отдельных Builds |
| `docs/roadmap/` | План развития и исторические stage roadmaps |
| `docs/stages/`, `docs/decisions/` | Исторические этапы и решения |
| `docs/market_intelligence/` | Отдельная развиваемая Market Intelligence область |
## Связанные документы
- [Обзор архитектуры](overview.md)
- [Текущая архитектура Trades Feed](trades_feed.md)
- [Эксплуатация Trade Stream Runtime](../operations/trades_feed_runtime.md)
- [Инструкция приложения](../../app/README.md)

View File

@@ -0,0 +1,585 @@
# Trades Feed (Time & Sales) — текущая архитектура
**Статус:** Current; accepted in Build 060.30.1
**Область:** Production vertical slice Canonical Trades
**Версия документа:** 1.0
---
## Связанные документы
- [Архитектура Build 060.30](../migrations/build_060_30_architecture.md)
- [Эксплуатация Trade Stream Runtime](../operations/trades_feed_runtime.md)
- [Build 060.27 — Persistent Market Data Storage](../migrations/build_060_27.md)
- [Build 060.28 — Persistent Checkpoint](../migrations/build_060_28.md)
- [Build 060.29 — Market Data Access and Replay](../migrations/build_060_29.md)
---
## 1. Назначение и фактический scope
Trades Feed получает сделки Dzengi, преобразует их в Canonical `Trade`,
проверяет порядок и дубликаты, восстанавливает разрывы через REST и при
включённом Storage атомарно сохраняет сделку вместе с persistent
checkpoint.
После перезапуска Runtime восстанавливает Consistency state из
PostgreSQL, выполняет Startup Recovery и только затем пропускает
накопленные live-сообщения. Сохранённые Canonical Market Data доступны
через отдельные Historical Access и deterministic Replay API.
Краткая итоговая цепочка:
```text
Dzengi WebSocket → Transport → validation/mapping → Canonical Trade ─┐
├→ Consistency
Dzengi REST Recovery → validation/mapping → Canonical Trade ─────────┘
optional PostgreSQL Trade + checkpoint
├→ next persistent startup
│ → Hydration
│ → Consistency state
│ → Startup Recovery
├→ explicit caller
│ → Historical Access
└→ explicit caller
→ Replay Plan
→ Replay Session → consumer
```
Production ingestion и persistence в этой цепочке реализованы только
для Trades. Наличие Quote/Candle моделей и repositories не означает,
что Quotes и Candles уже подключены как Production feeds.
---
## 2. Матрица готовности Market Data
| Возможность | Trades | Quotes | Candle revisions |
|---|---:|---:|---:|
| Canonical model и Dzengi adapters | Да | Да | Да |
| Production WebSocket Runtime consumer | Да | Нет | Нет |
| Live/Recovery Consistency | Да | Нет | Нет |
| Production persistence wiring | Да | Нет | Нет |
| PostgreSQL repository и schema | Да | Да | Да |
| Persistent operational checkpoint | Да | Нет | Нет |
| Historical reader | Да | Да | Да |
| Bounded deterministic Replay | Да | Да | Да |
Другие acquisition feeds проекта не входят в завершённую production
вертикаль Trades Feed.
---
## 3. Режимы по feature flags
| Trade Stream | Market Data Storage | Фактическое поведение |
|---|---|---|
| выключен | выключен | Trade Runtime и Market Data pool не создаются |
| включён | выключен | Live, in-memory Consistency и reconnect Recovery без restart persistence |
| включён | включён | Durable Trades, checkpoint, Hydration и Startup Recovery |
| выключен | включён | Недопустимая конфигурация; settings завершаются ошибкой |
Оба флага по умолчанию выключены. При включённом Trade Stream требуются
явные WebSocket URL, REST base URL и symbols. Скрытых URL или symbol
fallback нет.
Без Storage первоначальный startup подписывается без persistent
Hydration, ожидания startup ACK и Startup REST Recovery. Reconnect
Recovery во время уже работающего процесса остаётся доступным.
---
## 4. Карта компонентов
| Граница | Ответственность | Основные компоненты |
|---|---|---|
| Transport | WebSocket lifecycle, send/receive, Ping/Pong probe | `DzengiWebSocketTransport`, `WebSocketSession` |
| Protocol | Команды, события, subscription и control routing | `AcquisitionRuntimeService`, `WebSocketSubscriptionManager` |
| Adapter | JSON/schema/value validation и Canonical mapping | `DzengiUnifiedWebSocketAdapter`, REST adapter |
| Runtime | Startup, receive loop, buffering, tasks и shutdown | `TradeStreamProductionRuntime` |
| Consistency | Порядок, deduplication и per-symbol state | `TradeStreamConsistencyController`, `TradeStreamStateStore` |
| Recovery | REST windows и повторная подача в Consistency | `RuntimeRecoveryCoordinator`, `TradeRecoveryController` |
| Persistence | Canonical write, provenance и checkpoint | `TradeStorageObservationSink`, `PostgresTradeRepository` |
| Startup | Hydration durable tail и Startup Recovery | `TradeStreamStateHydrator`, `RuntimeStartupRecoveryCoordinator` |
| Read-side | Historical pages | `MarketDataHistoricalAccess`, PostgreSQL readers |
| Replay | Snapshot, virtual clock и one-shot playback | `PostgresReplayPlanBuilder`, `ReplaySessionFactory`, `ReplaySession` |
| Composition | Concrete dependency graph и root lifecycle | `bootstrap`, `ApplicationComposition` |
Transport не знает Canonical models, Recovery или PostgreSQL. Bootstrap
является внешним composition root и создаёт concrete adapters.
---
## 5. Canonical Trade и Consistency
Один `TradeStreamState` принадлежит одному нормализованному symbol.
Состояние хранит:
- последнюю принятую сделку;
- in-memory checkpoint;
- ограниченное окно Trade ID для deduplication;
- Canonical payload уже наблюдавшихся сделок.
Размер deduplication tail по умолчанию равен 10 000 Trades.
Trade ID соответствует signed 32-bit контракту. Сравнение учитывает
rollover:
- `INT32_MAX → INT32_MIN` является продвижением;
- `-1 → 0` является продвижением;
- расстояние ровно в половину 32-битного цикла неоднозначно и
отклоняется.
Две доставки считаются одним биржевым фактом, если совпадают symbol,
Trade ID, цена, количество, execution time и aggressor side. Поле
`source` описывает путь доставки WebSocket/REST и не участвует в этом
сравнении. При этом `source` сохраняется как часть Canonical payload и
provenance.
Конфликтующий дубликат и нарушение rollover-aware порядка являются
ошибками, а не молча отбрасываемыми данными.
Исходники:
- [TradeStreamState](../../app/src/market_data/acquisition/consistency/trade_stream_state.py)
- [Consistency Controller](../../app/src/market_data/acquisition/consistency/trade_stream_consistency_controller.py)
- [Trade ID sequence](../../app/src/market_data/acquisition/trade_id_sequence.py)
---
## 6. Live processing
Точная последовательность одного live-документа:
```text
WebSocket receive
→ зафиксировать transport activity
→ опубликовать Runtime Event
→ JSON decode
→ войти в LiveProcessingGate
→ разделить control и market document
→ Dzengi adapter
→ Canonical Trade
→ общий Consistency Controller
→ optional durable sink
→ продвинуть in-memory state
```
Blocking adapter/Consistency/Persistence path выполняется через
`asyncio.to_thread`. Runtime владеет worker-задачей и при cancellation
дожидается уже начатой обработки, чтобы не оставить неизвестный
результат записи.
При выключенном Storage Consistency продвигает только память процесса.
При включённом Storage durable callback завершается до изменения
in-memory checkpoint.
Runtime events доставляются in-process последовательно. Publisher не
имеет фоновой очереди или отдельной root task; production composition
регистрирует logging consumer.
Исходники:
- [Production Runtime](../../app/src/market_data/acquisition/runtime/trade_stream_production_runtime.py)
- [Acquisition Service](../../app/src/market_data/acquisition/trade_stream_acquisition_service.py)
- [Runtime Event Publisher](../../app/src/market_data/acquisition/runtime/acquisition_runtime_event_publisher.py)
---
## 7. Durable write и persistent checkpoint
Для новой принятой сделки гарантия имеет следующий порядок:
```text
BEGIN
→ insert либо validate identical Trade
→ compare-and-set persistent checkpoint
→ COMMIT
→ advance in-memory state
```
Trade и checkpoint записываются одним `PostgresTradeRepository` и одной
транзакцией. Ошибка вставки, payload conflict или checkpoint conflict
откатывает операцию и не продвигает in-memory state.
Полный дубликат обрабатывается отдельно:
```text
validate stored Trade
→ optional provenance update
→ persistent checkpoint не двигается
→ in-memory checkpoint не двигается
```
Идентичность строки Trade:
```text
venue + symbol + trade_id + executed_at
```
Persistent checkpoint имеет один ключ `venue + symbol` и ссылается на
точную durable Trade identity через `DEFERRABLE INITIALLY DEFERRED`
foreign key с `NO ACTION`.
Parent table Trades partitioned по диапазону `executed_at`; Quotes — по
`received_at`, Candle revisions — по `open_time`. Миграции всегда
создают default partition. Конкретная месячная UTC-partition появляется
только после явного вызова Partition Manager. Symbol и venue являются
identity/query scope, но не отдельными физическими уровнями
partitioning.
Исходники:
- [Trade Storage Sink](../../app/src/market_data/storage/trade_storage_observation_sink.py)
- [PostgreSQL Trade Repository](../../app/src/market_data/storage/postgres_trade_repository.py)
- [Storage migrations](../../app/src/storage/migrations.py)
---
## 8. Startup с persistent state
Application сначала открывает Market Data pool и применяет migrations.
Только после успешного Storage startup запускаются Telegram polling и
Trade Runtime tasks.
Persistent Trade Runtime выполняет:
```text
load checkpoint и bounded durable tail
→ либо атомарно принять последнюю durable Trade при первом запуске
→ опубликовать восстановленный StateStore целиком
→ WebSocket connect
→ отправить subscriptions
→ дождаться соответствующего ACK
→ сохранить ранние market documents в bounded FIFO
→ REST Recovery до одной зафиксированной границы времени
→ обработать FIFO в исходном порядке
→ запустить Supervisor, receive loop и Scheduler
```
Если для symbol нет checkpoint и durable Trades, state создаётся пустым;
исторический backfill с биржи до начала доступного REST-окна не
выполняется.
Если durable Trades есть, а checkpoint отсутствует, Hydrator может
атомарно принять последнюю durable Trade как начальную точку. Corrupt,
orphan или несовместимый checkpoint является фатальной ошибкой. Тихого
сброса checkpoint нет.
Startup buffer по умолчанию ограничен 10 000 documents. Negative ACK,
ACK timeout, overflow, Hydration error и Recovery error не открывают
Live gate.
Исходники:
- [State Hydrator](../../app/src/market_data/acquisition/checkpoint/trade_stream_state_hydrator.py)
- [Startup Recovery Coordinator](../../app/src/market_data/acquisition/runtime/runtime_startup_recovery_coordinator.py)
- [Application lifecycle](../../app/src/bootstrap/application.py)
---
## 9. Reconnect и Recovery
Transport receive error и Heartbeat timeout используют одну
generation-aware single-flight операцию:
```text
закрыть общий Live gate
→ Disconnect
→ Connect
→ переотправить desired subscriptions
→ зафиксировать recovery_end_time
→ последовательно выполнить REST Recovery для symbols
→ тот же Consistency Controller и durable sink
→ открыть Live gate
```
Вторая причина reconnect для того же connection generation
присоединяется к уже выполняющейся операции. Старое transport error не
запускает новый reconnect после смены generation.
Важные границы:
- Reconnect выполняет одну попытку; retry loop и backoff отсутствуют.
- Restore subscriptions означает успешную повторную отправку; отдельное
ожидание ACK перед reconnect Recovery не реализовано.
- Recovery использует REST `/api/v1/aggTrades` через настроенный base URL.
- Окна ограничены `TRADE_STREAM_RECOVERY_WINDOW_MS`, по умолчанию
3 599 999 ms.
- Возможный повтор на границе окон удаляет общий Consistency layer.
- Если state/checkpoint отсутствует, Recovery не придумывает начальную
историю и возвращает пустой результат.
- Ошибка reconnect или Recovery переводит gate в terminal failure и
распространяется в Application.
Recovery гарантирует упорядоченную обработку фактически полученных REST
данных, но не может гарантировать полноту данных, которых нет в ответе
биржи.
Исходники:
- [Reconnect Coordinator](../../app/src/market_data/acquisition/runtime/reconnect.py)
- [Reconnect Recovery Coordinator](../../app/src/market_data/acquisition/runtime/runtime_reconnect_recovery_coordinator.py)
- [Runtime Recovery Coordinator](../../app/src/market_data/acquisition/runtime/runtime_recovery_coordinator.py)
- [Trade Recovery Controller](../../app/src/market_data/acquisition/recovery/trade_recovery_controller.py)
---
## 10. Liveness, Heartbeat и Scheduler
Встроенный WebSocket keepalive библиотеки отключён. Источником
transport liveness является явный Ping/Pong probe.
Scheduler периодически:
1. вызывает transport probe;
2. при положительном ответе обновляет Heartbeat activity;
3. проверяет Heartbeat timeout;
4. передаёт подтверждённый timeout Supervisor.
Любое успешно полученное WebSocket-сообщение также считается activity.
Отрицательный probe сам по себе не запускает reconnect немедленно:
решение принимается по Heartbeat timeout. Supervisor не допускает
параллельный второй timeout reconnect.
Scheduler не владеет своей asyncio task. Task принадлежит Production
Runtime, который предварительно `claim()`-ит Scheduler и освобождает его
после остановки.
Исходники:
- [Heartbeat](../../app/src/market_data/acquisition/runtime/heartbeat.py)
- [Scheduler](../../app/src/market_data/acquisition/runtime/scheduler.py)
- [Supervisor](../../app/src/market_data/acquisition/runtime/supervisor.py)
---
## 11. Historical Access
Historical Access является отдельной synchronous read-side границей и
не расширяет write-only Storage facade.
Поддерживаются:
- Trade History;
- Quote History;
- Candle Revision History.
Общие свойства:
- timezone-aware полуоткрытый диапазон `[start_time, end_time)`;
- forward-only keyset pagination;
- строгий cursor scope;
- Canonical validation прочитанных PostgreSQL rows;
- limit по умолчанию 500, максимум 1 000.
Порядок страниц:
| Тип | Порядок |
|---|---|
| Trade | `executed_at`, затем `replay_sequence` |
| Quote | `received_at`, затем `replay_sequence` |
| Candle revision | `open_time`, затем `replay_sequence` |
Каждая страница выполняет отдельный запрос. Между страницами не
удерживается одна `REPEATABLE READ` транзакция, поэтому concurrent writes
или Retention могут изменить dataset следующего запроса.
Исходники:
- [Historical Access facade](../../app/src/market_data/access/market_data_historical_access.py)
- [Historical models](../../app/src/market_data/access/models.py)
---
## 12. Deterministic Replay
Replay отделён от live Runtime:
```text
caller
→ ReplaySessionFactory.prepare_session()
→ PostgresReplayPlanBuilder
→ READ ONLY REPEATABLE READ snapshot
→ bounded immutable ReplayPlan
→ закрыть transaction и connection
→ fresh Clock + fresh consumer + fresh ReplaySession
→ caller await session.run()
```
ReplayPlan ограничен максимум 100 000 events. Превышение limit приводит
к явной ошибке без частичного плана.
Глобальный порядок задаётся парой:
```text
replay_at + replay_sequence
```
| Тип | `replay_at` |
|---|---|
| Trade | `executed_at` |
| Quote | `received_at` |
| Candle revision | `observed_at` |
`replay_sequence` выдаётся одним PostgreSQL sequence для всех трёх
семейств и остаётся неизменяемым.
Clock начинается в `request.time_range.start_time`, разрешает равное
время и запрещает движение назад. Session является one-shot:
```text
CREATED → RUNNING → COMPLETED | FAILED | CANCELLED
```
Повторный `run()` запрещён. Отдельного `ReplayEngine`, default consumer,
background task, automatic startup и wall-clock pacing нет. Вызвавший
код предоставляет `ReplayConsumerFactoryProtocol`. Затем
`ReplaySessionFactory.prepare_session()` создаёт fresh consumer, Clock и
Session; caller владеет полученной Session и её `run()`.
Исходники:
- [Replay Plan Builder](../../app/src/market_data/replay/postgres_replay_plan_builder.py)
- [Replay Session Factory](../../app/src/market_data/replay/replay_session_factory.py)
- [Replay Session](../../app/src/market_data/replay/replay_session.py)
---
## 13. Ownership и lifecycle
| Ресурс | Владелец |
|---|---|
| PostgreSQL pool и migrations | Application через `MarketDataStorageLifecycle` |
| Telegram polling и root Runtime task | `run_application()` |
| startup, receive, scheduler и market-processing tasks | `TradeStreamProductionRuntime` |
| Heartbeat state | `RuntimeSupervisor` |
| reconnect/recovery single-flight | `RuntimeReconnectRecoveryCoordinator` |
| Hydration/Startup Recovery worker | Runtime через `RuntimeStartupRecoveryCoordinator` |
| WebSocket connection | `DzengiWebSocketTransport` через Session |
| Runtime Event delivery | caller `publish()`; фонового owner нет |
| Historical connection/cursor | один repository call |
| Replay snapshot transaction | `PostgresReplayPlanBuilder.create_plan()` |
| Replay execution | caller `session.run()` |
Application shutdown:
```text
отменить Telegram polling
→ runtime.stop()
→ завершить startup worker, если он выполняется
→ остановить и дождаться Scheduler
→ остановить Supervisor
→ отменить и дождаться receive loop
→ остановить WebSocket Session
→ очистить subscriptions
→ дождаться root Runtime task
→ закрыть PostgreSQL pool
→ закрыть bot HTTP session
```
Ошибка включённого Runtime или persistent write считается фатальной для
всего приложения. Degraded fallback к in-memory режиму не выполняется.
---
## 14. Направление зависимостей
```text
bootstrap
concrete Dzengi adapters + Runtime composition + PostgreSQL adapters
acquisition → Canonical models + own Protocol boundaries
→ narrow Storage checkpoint contracts/exceptions
Dzengi REST adapter → integrations.exchange REST client
storage → Canonical models
access → Canonical models + read contracts
replay → Canonical models + access row materialization
```
Подтверждённые границы:
- `market_data` не импортирует Telegram, Trading или Bootstrap;
- Bootstrap импортирует concrete adapters как composition root;
- Acquisition не зависит от Historical Access или Replay;
- Production Runtime не импортирует concrete PostgreSQL repository;
- Replay не зависит от Live Runtime и не вызывает Consistency;
- Dzengi-specific код преимущественно находится в
`acquisition/adapters/dzengi`, но provider subscription document и
часть validation/handlers физически размещены в соседних acquisition
пакетах.
Canonical models сейчас физически находятся в
`market_data/acquisition/models`. Это фактическое расположение, а не
утверждение, что общая Domain package уже выделена.
---
## 15. Failure policy
| Ошибка | Поведение |
|---|---|
| Неверные settings | Fail fast до запуска Runtime |
| Storage pool или migration | Application startup завершается ошибкой |
| Corrupt/orphan checkpoint | Startup завершается ошибкой |
| Subscription ACK timeout/negative ACK при persistent startup | Live gate остаётся закрытым, Runtime завершается |
| Startup buffer overflow | Runtime завершается |
| WebSocket receive failure | Одна generation-aware reconnect/recovery попытка |
| Reconnect или REST Recovery failure | Terminal gate failure и завершение Application |
| Canonical conflict/order error | Не принимается и распространяется как ошибка |
| Persistent write/checkpoint conflict | Transaction rollback, in-memory state не двигается |
| Replay consumer failure | Session переходит в `FAILED` |
| Replay cancellation | Session переходит в `CANCELLED` |
Ошибки не превращаются в молчаливое продолжение с потенциально
несогласованным состоянием.
---
## 16. Доступный период и ограничения
Доступная история начинается не с момента существования инструмента на
бирже, а с наиболее ранней Canonical записи, которая фактически
находится в PostgreSQL. Обычно это момент успешного включения Storage,
но история может также включать ранее импортированные durable rows.
Граница может сдвигаться из-за явно применённой Retention Policy.
Система не предоставляет:
- initial exchange historical backfill;
- completeness metadata и доказательство полной биржевой истории;
- storage raw exchange documents;
- automatic monthly partition creation;
- automatic Retention Scheduler;
- Production persistence Quotes/Candles;
- общий snapshot между Historical pages;
- восстановление исходного порядка сетевых пакетов;
- Replay HTTP/CLI/UI endpoint;
- automatic Replay startup;
- infinite reconnect retry/backoff;
- HA, leader election, distributed lease или multi-instance ownership;
- автоматические backup, metrics и alerting.
Default PostgreSQL partitions принимают строки без заранее созданной
месячной partition. Явный Partition Manager позднее может атомарно
перенести соответствующие строки.
Migration 9 выполняет блокирующий backfill `replay_sequence`. Для
крупной базы нужны backup, измерение на сопоставимом объёме и отдельное
maintenance window.
---
## 17. Безопасная итоговая формулировка
Trades Feed реализует единый Canonical Trade pipeline с live-получением,
in-process consistency, generation-aware reconnect, REST gap recovery,
опциональной durable-записью и persistent startup recovery. Historical
Access и deterministic Replay работают над фактически сохранёнными
Canonical Market Data. Полнота истории ограничена моментом включения
persistence, доступностью REST Recovery и применяемой Retention Policy.