Files
dzentra_bot/docs/migrations/build_060_18_architecture.md

2726 lines
64 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Build 060.18 — Trade Stream Consistency Controller
**Статус:** Architecture Specification
**Build:** 060.18
**Ветка:** Trades Feed (Time & Sales)
**Документ:** `build_060_18_architecture.md`
**Связанные документы:** `build_060_18.md` — план реализации данного Build.
---
# Назначение документа
Настоящий документ является официальной архитектурной спецификацией Build 060.18 и определяет построение подсистемы обеспечения согласованности потока сделок (Trade Stream Consistency).
Документ фиксирует все архитектурные решения, принятые до начала реализации, и служит единственным источником истины (Single Source of Truth) при разработке данного Build.
Все решения, описанные ниже, считаются утвержденными до начала реализации и не должны изменяться в процессе написания кода без подготовки нового ADR.
---
# Статус Build
Build 060.18 является продолжением серии Build 060, посвящённой построению новой подсистемы получения рыночных данных (Market Data Acquisition).
К моменту начала данного Build в проекте уже существуют:
- каноническая модель Trade;
- транспортные Parser;
- Mapper;
- Value Validation;
- REST и WebSocket интеграции;
- Trades Feed;
- Trades Handler.
Получаемые сделки уже приводятся к единому каноническому виду независимо от источника данных.
Однако на текущем этапе отсутствует механизм, обеспечивающий согласованность самого потока сделок.
Build 060.18 закрывает именно эту архитектурную задачу.
---
# Контекст
После завершения Build 060.17 система умеет получать сделки одновременно из различных транспортных источников.
Например:
- REST Backfill;
- WebSocket Trade Stream;
- будущие источники исторических данных.
Все эти источники после прохождения Parser, Validation и Mapper возвращают одинаковый объект:
```python
Trade
```
Однако наличие канонической модели ещё не означает существование канонического потока.
Например, поток может содержать:
```text
100
101
100
```
или
```text
100
101
99
```
или
```text
500
501
500 (с другой ценой)
```
Каждый из этих случаев нарушает различные инварианты системы.
Следовательно, между построением объекта Trade и публикацией сделки потребителю должен существовать отдельный уровень контроля согласованности.
Именно этот уровень реализуется Build 060.18.
---
# Предпосылки
Настоящий Build опирается на архитектурные решения, принятые ранее.
## Build 057
Определены базовые принципы Market Data Acquisition.
Разделены транспортный и доменный уровни.
---
## Build 060.1
Построена каноническая модель Trade.
Все источники данных приводятся к единому объекту.
---
## Build 060.2
Завершена унификация транспортных моделей.
---
## Build 060.4
Завершено разделение Parser и Mapper.
Parser отвечает только за транспорт.
Mapper отвечает только за построение Domain Model.
---
## Build 060.9
Построена система Value Validation.
Корректность отдельных значений гарантируется до появления объекта Trade.
---
## Build 060.11
Определены транспортные адаптеры REST и WebSocket.
---
## Build 060.17
Построен Trades Feed.
Получение сделок полностью функционирует.
На этом этапе поток состоит из независимых объектов Trade.
Build 060.17 сознательно не занимается проверкой согласованности последовательности сделок.
---
# Проблема
Build 060.17 гарантирует корректность каждой отдельной сделки.
Однако система ещё не гарантирует корректность последовательности сделок.
Например:
```text
Trade
Trade
Trade
```
не обязательно образуют корректный поток.
В частности отсутствуют гарантии:
- монотонности;
- отсутствия повторов;
- отсутствия конфликтующих дублей;
- целостности опубликованного потока.
Следовательно, потребитель Trade не может считать получаемый поток достоверным.
Build 060.18 устраняет именно эту проблему.
---
# Основная идея Build
Главная идея Build заключается в разделении двух понятий.
До настоящего момента существовала только каноническая модель Trade.
После завершения Build появляются две независимые сущности.
Первая:
```text
Canonical Trade
```
Вторая:
```text
Canonical Trade Stream
```
Это принципиально разные понятия.
Trade представляет собой корректный объект предметной области.
Trade Stream представляет собой последовательность объектов Trade, удовлетворяющую дополнительным инвариантам.
Следовательно, появление объекта Trade ещё не означает его публикацию в поток.
Перед публикацией Trade обязан пройти проверку согласованности.
---
# Цель Build
Build обязан обеспечить существование единственного канонического потока сделок.
После завершения Build система должна гарантировать следующие свойства.
Каждая опубликованная сделка:
- имеет корректную каноническую модель;
- не нарушает порядок потока;
- не является конфликтующим повтором;
- публикуется не более одного раза.
Все источники данных должны использовать одинаковый механизм проверки согласованности.
Источник происхождения сделки не должен влиять на работу системы.
---
# Что НЕ входит в Scope Build
Настоящий Build сознательно НЕ реализует:
- восстановление пропущенных сделок;
- обнаружение разрывов последовательности;
- повторную синхронизацию через REST;
- сохранение состояния между перезапусками процесса;
- журналирование истории потока;
- долговременное хранение дедупликационного окна;
- диагностику качества соединения;
- обработку потери WebSocket.
Все перечисленные задачи относятся к следующим Build серии 060.
Build 060.18 отвечает исключительно за согласованность уже поступающих сделок.
---
# Архитектурные принципы
При реализации Build используются следующие фундаментальные принципы.
## 1. Canonical First
Любая транспортная информация должна быть преобразована в каноническую модель до начала проверки согласованности.
Контроллер никогда не работает с транспортными структурами.
---
## 2. Single Domain Model
Во всей системе существует единственная модель Trade.
REST и WebSocket не имеют собственных моделей после этапа Mapper.
---
## 3. Stream Before Publication
Ни одна сделка не может быть опубликована потребителю до завершения проверки согласованности.
---
## 4. Immutable Domain Objects
Объект Trade никогда не изменяется после создания.
Контроллер может принять или отклонить сделку, но не имеет права изменять её содержимое.
---
## 5. One Source Of Truth
Единственным владельцем состояния потока является компонент Stream Consistency.
Никакие другие части Acquisition Pipeline не должны хранить собственную историю уже обработанных сделок.
---
## 6. Stateless Acquisition
Parser, Mapper, Validation и Handler остаются полностью stateless.
Первой stateful-компонентой Acquisition становится только Stream Consistency Controller.
---
## 7. Separation Of Responsibilities
Trade описывает предметную область.
TradeStreamState хранит состояние.
TradeStreamConsistencyController принимает доменные решения.
Ни один из этих компонентов не должен брать на себя ответственность другого.
---
---
## 8. Unique File Naming
Во всём проекте Dzentra не допускается существование нескольких файлов с одинаковым именем независимо от их расположения в каталогах.
Единственным исключением является служебный файл:
```text
__init__.py
```
Имя каждого файла должно однозначно отражать его назначение и позволять определить его содержимое без открытия файла.
Например:
```text
trade_stream_consistency_controller.py
trade_stream_state.py
trade_stream_protocol.py
trade_stream_exceptions.py
```
Не допускается использование общих имён файлов, таких как:
```text
controller.py
state.py
protocol.py
exceptions.py
```
---
# Canonical Trade Stream
## Введение
Главной архитектурной задачей Build 060.18 является переход от понятия **отдельной канонической сделки** к понятию **канонического потока сделок**.
До настоящего Build система гарантировала корректность каждого объекта `Trade` независимо.
После завершения Build система начинает гарантировать корректность всей последовательности опубликованных сделок.
Именно поток, а не отдельная сделка, становится новой доменной сущностью Acquisition Layer.
---
# Определение Canonical Trade Stream
**Canonical Trade Stream** — это последовательность канонических объектов `Trade`, удовлетворяющая всем инвариантам согласованности потока.
Именно эта последовательность считается единственным допустимым источником сделок для всех последующих компонентов системы.
Все последующие модули Dzentra должны считать, что получаемый ими поток уже является согласованным.
Повторная проверка порядка, дедупликации или целостности не допускается.
---
# Отличие Canonical Trade от Canonical Trade Stream
Canonical Trade отвечает на вопрос:
> Является ли данный объект корректной сделкой?
Canonical Trade Stream отвечает на вопрос:
> Может ли данная сделка стать частью уже существующего потока?
Это принципиально разные уровни проверки.
Корректная сделка вполне может быть отвергнута контроллером потока.
Например:
```text
Trade #100
Trade #101
Trade #99
```
Все три объекта являются корректными `Trade`.
Однако третья сделка нарушает согласованность потока.
Следовательно, она никогда не становится частью Canonical Trade Stream.
---
# Граница формирования потока
До прохождения проверки согласованности существует только набор независимых объектов Trade.
```text
REST
WebSocket
Parser
Validation
Mapper
Trade
```
После прохождения Stream Consistency появляется новая сущность.
```text
Trade
TradeStreamConsistencyController
Canonical Trade Stream
Consumer
```
Именно `TradeStreamConsistencyController` создаёт канонический поток.
Никакой другой компонент системы не имеет такого права.
---
# Инварианты Canonical Trade Stream
Любой поток, публикуемый системой, обязан удовлетворять следующим инвариантам.
---
## Инвариант №1
Поток состоит исключительно из объектов `Trade`.
Транспортные модели никогда не публикуются.
---
## Инвариант №2
Каждая опубликованная сделка успешно прошла Value Validation.
---
## Инвариант №3
Каждая опубликованная сделка успешно прошла проверку Stream Consistency.
---
## Инвариант №4
Для каждого символа порядок `trade_id` никогда не уменьшается.
---
## Инвариант №5
Каждая биржевая сделка публикуется не более одного раза.
---
## Инвариант №6
Конфликтующие повторы никогда не публикуются.
---
## Инвариант №7
После публикации сделка считается неизменяемой.
---
## Инвариант №8
Каждый символ имеет полностью независимую историю потока.
---
## Инвариант №9
Проверка согласованности выполняется до публикации сделки.
---
## Инвариант №10
После публикации сделка никогда повторно не проверяется.
---
# Identity Model
## Назначение
Для обеспечения дедупликации необходимо определить каноническую идентичность сделки.
Без формального определения идентичности невозможно определить:
- повтор;
- конфликтующий повтор;
- новую сделку.
---
# Рассматриваемые варианты
Во время проектирования были рассмотрены несколько вариантов.
---
## Вариант 1
```text
trade_id
```
Отклонён.
Причина:
Архитектура не должна предполагать глобальную уникальность `trade_id`.
Разные торговые инструменты могут использовать одинаковые идентификаторы.
---
## Вариант 2
```text
(symbol, trade_id)
```
Принят.
Данная пара однозначно определяет одну сделку внутри канонического потока.
---
## Вариант 3
```text
(symbol, trade_id, source)
```
Отклонён.
После Mapper источник происхождения сделки перестаёт иметь доменное значение.
REST и WebSocket обязаны порождать одну и ту же каноническую сущность.
---
## Вариант 4
```text
(symbol, trade_id, timestamp)
```
Отклонён.
Время исполнения является атрибутом сделки, а не частью её идентичности.
---
# Официальная модель идентичности
Во всей системе Build 060.18 официальной идентичностью сделки считается:
```text
(symbol, trade_id)
```
Никакие другие поля не участвуют в определении идентичности.
---
# Ordering Model
## Назначение
После определения идентичности необходимо определить правило упорядочивания потока.
---
# Рассматриваемые варианты
---
## Ordering по timestamp
Отклонён.
Причины:
- различные источники могут получать данные с различной задержкой;
- время может совпадать;
- транспортная задержка не должна влиять на доменную последовательность.
---
## Ordering по executed_at
Отклонён.
`executed_at` является характеристикой сделки, но не механизмом восстановления порядка.
---
## Ordering по (timestamp, trade_id)
Отклонён.
Избыточно.
Усложняет систему без появления дополнительных гарантий.
---
## Ordering по trade_id
Принят.
Биржа уже определяет последовательность исполнения сделок.
Следовательно, система должна использовать именно её.
---
# Официальное правило Ordering
Для каждого символа поток обязан удовлетворять условию:
```text
trade_id(new) >= trade_id(last)
```
При этом допускаются разрывы последовательности.
Например:
```text
100
101
103
120
```
является корректным потоком.
Build 060.18 не занимается анализом пропущенных идентификаторов.
Эта задача относится к Build 060.19.
---
# Нарушение порядка
Если новая сделка имеет меньший `trade_id`, чем последняя опубликованная сделка данного символа, поток считается нарушенным.
Пример:
```text
100
101
98
```
В этом случае сделка отвергается.
Контроллер генерирует `TradeOrderingError`.
Состояние потока при этом не изменяется.
---
# Deduplication Model
## Назначение
После проверки порядка необходимо определить правила обработки повторных сделок.
---
# Определение дубликата
Дубликатом считается сделка, имеющая ту же идентичность:
```text
(symbol, trade_id)
```
что и уже зарегистрированная сделка.
---
# Полный дубликат
Если все канонические поля совпадают, повтор считается корректным.
Например:
```text
BTC
trade_id = 150
price = 100
quantity = 5
```
и повтор:
```text
BTC
trade_id = 150
price = 100
quantity = 5
```
представляют одну и ту же сделку.
Контроллер не публикует её повторно.
Ошибки не возникает.
---
# Конфликтующий дубликат
Если идентичность совпадает, но хотя бы одно бизнес-поле отличается:
- price;
- quantity;
- executed_at;
- aggressor_side;
поток считается противоречивым.
Например:
```text
trade_id = 500
price = 100
```
позже:
```text
trade_id = 500
price = 101
```
Такая ситуация невозможна внутри корректного канонического потока.
Контроллер обязан немедленно завершить обработку ошибкой.
Публикация сделки запрещается.
---
# Что не считается дубликатом
Следующие сделки никогда не считаются повтором:
```text
trade_id = 500
trade_id = 501
```
Даже если совпадают:
- цена;
- объём;
- время исполнения.
Идентичность определяется исключительно парой:
```text
(symbol, trade_id)
```
---
# Итоговая модель обработки сделки
Каждая поступающая сделка проходит последовательность проверок.
```text
Trade
Определение символа
Получение состояния потока
Проверка существования trade_id
├─────────────── Да ───────────────┐
│ │
▼ ▼
Проверка совпадения Полный дубликат
бизнес-полей │
│ ▼
Не публиковать
Конфликт?
├──── Да ───► TradeConsistencyError
Нет
Проверка порядка
├──── Нарушение ─► TradeOrderingError
Регистрация сделки
Публикация
```
---
# Архитектурный результат
После завершения Build 060.18 в системе появляется новый уровень доменной модели.
До Build:
```text
Trade
```
После Build:
```text
Trade
Canonical Trade Stream
```
Именно канонический поток становится единственным допустимым источником данных для всех последующих компонентов Market Intelligence Pipeline.
---
# Архитектура компонентов
После определения модели Canonical Trade Stream необходимо определить архитектурные компоненты, обеспечивающие его существование.
Build 060.18 вводит в Acquisition Layer первую stateful-подсистему.
До настоящего момента все компоненты Acquisition являлись stateless.
Появление Stream Consistency является первой точкой, где система начинает хранить собственное состояние.
Именно поэтому данный Build вводит строго определённые границы ответственности.
---
# Архитектура подсистемы
Подсистема Stream Consistency состоит из двух компонентов.
```text
TradeStreamConsistencyController
TradeStreamState
```
Оба компонента являются внутренними компонентами Acquisition Layer.
Никакие другие части системы не имеют права изменять их состояние.
---
# TradeStreamConsistencyController
## Назначение
TradeStreamConsistencyController является единственной точкой формирования Canonical Trade Stream.
Никакой другой компонент системы не имеет права принимать решение о публикации сделки.
Контроллер определяет:
- может ли сделка стать частью потока;
- нарушает ли она инварианты;
- является ли она повтором;
- должна ли она быть опубликована.
---
# Ответственность Controller
Контроллер отвечает исключительно за доменные решения.
К ним относятся:
- маршрутизация по символам;
- проверка порядка;
- определение повторов;
- обнаружение конфликтующих дублей;
- формирование доменных исключений;
- публикация только корректных сделок.
---
# Controller НЕ отвечает
Контроллер сознательно не отвечает за:
- хранение данных;
- структуру дедупликационного окна;
- алгоритм удаления старых записей;
- реализацию FIFO;
- внутреннее устройство состояния.
Все перечисленные задачи принадлежат исключительно TradeStreamState.
---
# Главный принцип Controller
Controller принимает решения.
State хранит данные.
Это фундаментальное архитектурное правило Build 060.18.
---
# TradeStreamState
## Назначение
TradeStreamState представляет собой внутреннюю модель состояния одного потока сделок.
Каждый экземпляр состояния соответствует ровно одному торговому символу.
Например:
```text
BTCUSDT
TradeStreamState
```
или
```text
ETHUSDT
TradeStreamState
```
Состояние различных символов никогда не смешивается.
---
# Почему состояние существует отдельно
Во время проектирования рассматривалась возможность хранения всех данных непосредственно внутри Controller.
Этот вариант был отклонён.
Причина:
Controller должен выражать бизнес-правила.
State должен выражать состояние предметной области.
Разделение этих двух ролей существенно упрощает дальнейшее развитие системы.
---
# Инварианты TradeStreamState
Каждый экземпляр состояния обязан удовлетворять следующим требованиям.
---
## Инвариант №1
Состояние обслуживает ровно один symbol.
---
## Инвариант №2
Состояние никогда не принимает Trade другого symbol.
Даже если Controller ошибётся.
Это является дополнительной защитой целостности системы.
---
## Инвариант №3
Состояние хранит только уже опубликованные сделки.
Непринятые сделки никогда не изменяют состояние.
---
## Инвариант №4
Состояние никогда самостоятельно не принимает бизнес-решения.
Оно лишь предоставляет операции хранения.
---
## Инвариант №5
Состояние полностью скрывает собственную реализацию.
Controller не знает:
- используется ли OrderedDict;
- используется ли deque;
- используется ли Ring Buffer;
- используется ли специализированная структура данных.
---
# Внутреннее состояние
Минимальная модель состояния включает:
```text
symbol
last_trade_id
recent_trades
```
Этого достаточно для реализации всех инвариантов Build 060.18.
---
# symbol
Каждое состояние хранит собственный символ.
Например:
```text
BTCUSDT
```
Это необходимо по двум причинам.
Во-первых,
для проверки принадлежности входящей сделки.
Во-вторых,
для полноценной диагностики ошибок.
---
# last_trade_id
last_trade_id означает:
> максимальный trade_id,
> успешно опубликованный системой
> для данного символа.
Это очень важно.
last_trade_id НЕ означает:
> последний увиденный trade_id.
Например:
```text
100
101
95
```
После возникновения OrderingError состояние остаётся:
```text
last_trade_id = 101
```
Никаких изменений не происходит.
---
# recent_trades
recent_trades представляет собой окно уже опубликованных сделок.
Окно необходимо исключительно для проверки повторов.
После выхода сделки из окна
она перестаёт участвовать в дедупликации.
---
# Почему используется окно
Полная история сделок потенциально бесконечна.
Следовательно,
невозможно хранить все Trade.
Используется ограниченное окно фиксированного размера.
Размер окна определяется конфигурацией.
---
# Требования к окну
Окно обязано обеспечивать:
поиск
```text
O(1)
```
вставку
```text
O(1)
```
удаление самой старой записи
```text
O(1)
```
Эти требования являются обязательными.
---
# FIFO Window
Во время проектирования рассматривались различные структуры хранения.
---
## Полная история
Отклонена.
Причина:
неограниченный рост памяти.
---
## HashSet
Отклонён.
Причина:
невозможно проверить конфликтующий повтор.
---
## LRU Cache
Отклонён.
Причина:
LRU ориентирован на обращения.
Trade Stream ориентирован на порядок появления.
---
## FIFO Window
Принят.
FIFO полностью соответствует природе потока сделок.
Самые старые сделки постепенно забываются.
Новые добавляются в конец окна.
---
# Внутренний ключ
Поскольку один экземпляр TradeStreamState обслуживает только один symbol,
ключом окна становится исключительно:
```text
trade_id
```
Полная идентичность
```text
(symbol, trade_id)
```
используется только на уровне Controller.
Это уменьшает объём памяти
и упрощает внутреннюю структуру состояния.
---
# Жизненный цикл состояния
Build 060.18 определяет простой жизненный цикл.
---
## Создание
Состояние создаётся лениво.
Первое появление сделки данного символа приводит к созданию нового TradeStreamState.
---
## Использование
После создания состояние используется всеми последующими сделками данного символа.
---
## Уничтожение
Build 060.18 не удаляет состояния.
Они существуют до завершения процесса.
Управление жизненным циклом относится к Runtime Layer и будет рассматриваться отдельно.
---
# Публичный контракт Controller
Controller предоставляет единственную публичную операцию.
```python
accept(trade: Trade) -> Trade | None
```
Других публичных методов Build 060.18 не вводит.
---
# Семантика accept()
Если сделка успешно прошла проверку,
Controller возвращает исходный объект Trade.
Если поступил полный повтор,
возвращается:
```python
None
```
Если обнаружено нарушение инвариантов,
генерируется соответствующее исключение.
---
# Почему возвращается Trade
Во время проектирования рассматривались альтернативы.
---
## bool
Отклонён.
Причина:
вызывающий код вынужден хранить исходный объект отдельно.
---
## Result Object
Отклонён.
Причина:
избыточен для Build 060.18.
---
## Исключение для дубликатов
Отклонено.
Повтор является нормальной ситуацией.
Он не считается ошибкой.
---
## Trade | None
Принят.
Контракт минимален,
естественен
и легко расширяется в будущем.
---
# Исключения Controller
Build вводит только два новых доменных исключения.
---
## TradeOrderingError
Возникает,
если сделка нарушает монотонность потока.
Например:
```text
100
101
95
```
---
## TradeConsistencyError
Возникает,
если найден конфликтующий повтор.
Например:
```text
trade_id = 500
price = 100
```
позже
```text
trade_id = 500
price = 101
```
Такой поток считается внутренне противоречивым.
---
# Взаимодействие Controller и State
Важнейшим архитектурным принципом Build является инкапсуляция состояния.
Controller никогда не обращается к внутренним структурам данных напрямую.
Вместо этого он взаимодействует со State исключительно через его операции.
Концептуально взаимодействие выглядит следующим образом.
```text
Controller
TradeStreamState
├── получить последнюю сделку
├── получить зарегистрированную сделку
├── зарегистрировать новую сделку
└── поддерживать размер окна
```
Таким образом:
- Controller ничего не знает о реализации хранения;
- State ничего не знает о бизнес-правилах проверки согласованности.
Именно это разделение делает подсистему устойчивой к дальнейшему развитию.
---
# Алгоритм работы TradeStreamConsistencyController
После определения архитектуры компонентов необходимо формально определить алгоритм обработки каждой сделки.
Build 060.18 рассматривает Controller как **детерминированный автомат** (Deterministic State Machine).
Это означает, что результат обработки полностью определяется двумя величинами:
```text
Текущее состояние
+
Входящая Trade
Новое состояние
+
Результат обработки
```
Никакие внешние факторы не влияют на принятие решения.
---
# Почему выбран детерминированный автомат
Во время проектирования рассматривались несколько моделей.
---
## Процедурный алгоритм
Обычная последовательность условий.
```text
if
if
if
if
```
Работает.
Но по мере развития Build начинает быстро усложняться.
---
## Таблица правил
Возможна.
Однако становится плохо читаемой.
---
## Deterministic State Machine
Принята.
Причины:
- полностью предсказуемое поведение;
- простое тестирование;
- возможность восстановления состояния;
- одинаковое поведение REST и WebSocket;
- естественное расширение для Recovery Build.
---
# Формальная модель
Для каждой входящей сделки существует единственный возможный результат.
```text
TradeStreamState
+
Trade
TradeStreamState'
+
Result
```
где
Result представляет собой одно из следующих состояний:
```text
Accepted
Duplicate
TradeOrderingError
TradeConsistencyError
```
Других исходов Build 060.18 не предусматривает.
---
# Последовательность обработки
Каждая входящая сделка проходит одинаковую последовательность шагов.
```text
Trade
Получить состояние символа
Поиск trade_id
Определение дубликата
Проверка порядка
Регистрация
Публикация
```
Порядок этапов является обязательным.
Изменение последовательности проверок запрещается.
---
# Почему сначала проверяется дубликат
Во время проектирования рассматривался альтернативный вариант.
Сначала Ordering.
Потом Deduplication.
Например:
```text
100
101
100
```
Ordering немедленно сообщил бы,
что сделка старая.
Но на самом деле это корректный повтор.
Следовательно,
Ordering не должен выполняться первым.
---
# Официальная последовательность
Контроллер всегда выполняет проверки в следующем порядке.
---
## Шаг 1
Получение состояния символа.
Если состояние отсутствует,
оно создаётся.
---
## Шаг 2
Поиск существующей сделки.
Если trade_id найден,
Controller обязан сравнить всю каноническую модель.
---
## Шаг 3
Определение типа повтора.
Если совпадают все бизнес-поля,
сделка считается полным дубликатом.
Возвращается:
```python
None
```
---
Если найдено хотя бы одно различие,
генерируется
```text
TradeConsistencyError
```
После этого обработка завершается.
---
## Шаг 4
Если сделка новая,
выполняется проверка порядка.
Если
```text
trade_id < last_trade_id
```
генерируется
```text
TradeOrderingError
```
Состояние не изменяется.
---
## Шаг 5
Если все проверки успешно завершены,
сделка регистрируется.
---
## Шаг 6
После регистрации
Trade публикуется вызывающему компоненту.
---
# Почему регистрация выполняется перед публикацией
Это принципиальное решение Build.
Рассмотрим последовательность.
Плохой вариант.
```text
Trade
Publication
Registration
```
Если между публикацией и регистрацией произойдёт исключение,
система потеряет согласованность.
Потребитель уже увидел сделку,
а внутреннее состояние ещё нет.
---
Правильный вариант.
```text
Trade
Registration
Publication
```
После регистрации
Controller уже находится в согласованном состоянии.
---
# Неизменяемость состояния при ошибках
Любая ошибка обязана обладать свойством атомарности.
Если возникает:
- TradeOrderingError;
- TradeConsistencyError;
состояние Controller не изменяется.
Это означает:
- last_trade_id остаётся прежним;
- окно дедупликации не изменяется;
- история публикаций остаётся неизменной.
---
# Архитектурная диаграмма
Полный путь обработки сделки выглядит следующим образом.
```text
REST / WebSocket
Parser
Value Validation
Mapper
Trade
TradeStreamConsistencyController
Canonical Trade Stream
TradesFeed Consumer
```
После Build 060.18 именно эта схема становится официальной архитектурой Trades Feed.
---
# Интеграция в Acquisition Pipeline
## Основной принцип
Build 060.18 не изменяет существующий Acquisition Pipeline.
Он лишь добавляет новый этап.
До Build:
```text
Handler
Consumer
```
После Build:
```text
Handler
TradeStreamConsistencyController
Consumer
```
Никакие существующие компоненты не меняют собственную ответственность.
---
# Integration Boundary
TradeStreamConsistencyController располагается между Handler и Consumer.
Он получает уже канонический Trade.
И публикует только Canonical Trade Stream.
Это официальная граница между:
```text
Canonical Trade
Canonical Trade Stream
```
---
# Dependency Injection
Controller создаётся в Composition Root.
Никакой компонент не создаёт Controller самостоятельно.
Итоговая схема выглядит следующим образом.
```text
Composition Root
├──────────────┐
▼ ▼
TradesHandler TradeStreamConsistencyController
│ │
└──────┬───────┘
TradesFeed
```
Таким образом:
- Feed ничего не знает о реализации Controller;
- Controller ничего не знает о Feed;
- Handler ничего не знает о Controller.
Каждый компонент получает только необходимые зависимости.
---
# План изменений файлов
Build 060.18 вводит новую внутреннюю подсистему.
Предлагаемая структура.
```text
market_data/
└── acquisition/
└── consistency/
├── __init__.py
├── trade_stream_consistency_controller.py
├── trade_stream_state.py
├── trade_stream_protocol.py
└── trade_stream_exceptions.py
```
---
## trade_stream_consistency_controller.py
Содержит:
```text
TradeStreamConsistencyController
```
Отвечает исключительно за доменные решения.
---
## trade_stream_state.py
Содержит:
```text
TradeStreamState
```
Отвечает исключительно за хранение состояния.
---
## trade_stream_exceptions.py
Содержит:
```text
TradeOrderingError
TradeConsistencyError
```
---
## trade_stream_protocol.py
Содержит публичный контракт подсистемы Stream Consistency.
Все внешние компоненты должны зависеть от данного Protocol, а не от конкретной реализации Controller.
---
# Соглашение об именовании файлов
Build 060.18 закрепляет правило уникальности имён файлов в проекте Dzentra.
Все новые файлы должны иметь уникальные имена в пределах всего репозитория.
Имя файла должно отражать его содержимое, а не только роль внутри текущего каталога.
Например:
```text
trade_stream_consistency_controller.py
trade_stream_state.py
trade_stream_protocol.py
trade_stream_exceptions.py
```
Данное соглашение является обязательным архитектурным стандартом для всех последующих Build.
# Изменяемые файлы
Build 060.18 должен минимально затронуть существующий код.
Изменения предполагаются только в точках интеграции:
- Composition Root (создание Controller);
- TradesFeed (внедрение зависимости и последовательность вызовов).
Все остальные изменения должны быть локализованы внутри новой подсистемы `consistency`.
---
# Architectural Decision Records
## ADR-060.18-01 — Canonical Trade Identity
**Решение**
Идентичность сделки определяется парой:
```text
(symbol, trade_id)
```
Другие поля в идентичность не входят.
---
## ADR-060.18-02 — Stream State Ownership
**Решение**
Единственным владельцем состояния потока является `TradeStreamConsistencyController`, который управляет набором `TradeStreamState` по символам.
---
## ADR-060.18-03 — Deterministic Stream State Machine
**Решение**
Контроллер рассматривается как детерминированный автомат:
```text
State + Trade → New State + Result
```
Поведение полностью определяется текущим состоянием и входящей сделкой.
---
## ADR-060.18-04 — Encapsulated Stream State
**Решение**
`TradeStreamState` инкапсулирует все детали хранения.
Контроллер взаимодействует только через его публичные операции.
---
## ADR-060.18-05 — Domain State Ownership
**Решение**
Разделение ответственности фиксируется следующим образом:
- `TradeStreamConsistencyController` — бизнес-правила и доменные решения;
- `TradeStreamState` — состояние и структурные инварианты.
---
## ADR-060.18-06 — Stream Boundary
**Решение**
`TradeStreamConsistencyController` является единственной точкой формирования Canonical Trade Stream.
До него существует только Canonical Trade.
После него существует только Canonical Trade Stream.
Никакой другой компонент не имеет права изменять или повторно проверять согласованность потока.
---
# Граница Build 060.18
После завершения данного Build система получает:
- каноническую модель Trade;
- канонический поток Trade;
- гарантии порядка;
- гарантии дедупликации;
- гарантии неизменяемости опубликованного потока.
На этом ответственность Build заканчивается.
Следующие задачи сознательно оставлены за пределами Build:
- обнаружение пропусков (`Gap Detection`);
- восстановление последовательности (`Recovery`);
- синхронизация через REST Backfill;
- сохранение состояния между перезапусками процесса;
- диагностика качества соединения;
- телеметрия и метрики работы контроллера.
Все перечисленные функции относятся к следующим Build серии 060.
---
# Стратегия тестирования
Build 060.18 вводит первый stateful-компонент Acquisition Layer.
Поэтому целью тестирования становится не только проверка отдельных методов, но и доказательство соблюдения всех архитектурных инвариантов, определённых настоящей спецификацией.
Тестирование должно подтверждать корректность поведения системы независимо от источника данных.
---
# Основные принципы тестирования
При проектировании тестов используются следующие принципы.
---
## Проверяются инварианты
Основной объект тестирования —
не отдельные методы,
а инварианты Canonical Trade Stream.
---
## Тестируются только публичные контракты
Unit-тесты не должны зависеть от внутренней реализации:
- FIFO;
- OrderedDict;
- структуры хранения;
- внутренних коллекций.
Все проверки выполняются исключительно через публичный API.
---
## Один тест — одна причина отказа
Каждый негативный сценарий проверяет только одно нарушение инварианта.
Это существенно упрощает анализ ошибок.
---
## Детерминированность
Каждый тест обязан быть полностью воспроизводимым.
Никакие случайные значения,
генераторы,
таймеры,
или текущее время
не должны влиять на результат.
---
# Уровни тестирования
Build 060.18 вводит три уровня проверки.
---
# Unit Tests
Проверяются:
- TradeStreamState
- TradeStreamConsistencyController
изоляционно.
Все внешние зависимости заменяются тестовыми объектами.
---
# Integration Tests
Проверяется полный Pipeline.
```text
Trade
TradeStreamConsistencyController
Consumer
```
Основная задача —
доказать,
что Controller корректно интегрирован в Acquisition.
---
# Regression Tests
Проверяется,
что Build 060.18
не изменил поведение уже существующих Build.
В частности:
- Parser;
- Mapper;
- Validation;
- Handler.
---
# Матрица тестирования
## 1. Создание состояния
### Проверяется
Первое появление нового symbol.
### Ожидаемый результат
Создаётся новый TradeStreamState.
---
## 2. Повторное использование состояния
### Проверяется
Несколько сделок одного symbol.
### Ожидаемый результат
Используется существующее состояние.
Новое не создаётся.
---
## 3. Независимость символов
### Проверяется
BTC
ETH
BTC
ETH
### Ожидаемый результат
Состояния полностью независимы.
---
## 4. Первая сделка
### Проверяется
Пустой поток.
### Ожидаемый результат
Trade публикуется.
---
## 5. Возрастающий trade_id
```text
100
101
102
```
### Ожидаемый результат
Все сделки публикуются.
---
## 6. Разрыв последовательности
```text
100
105
150
```
### Ожидаемый результат
Ошибок нет.
---
## 7. Уменьшение trade_id
```text
100
101
95
```
### Проверяется
Ordering.
### Ожидаемый результат
TradeOrderingError.
---
## 8. Полный дубликат
```text
100
100
```
### Проверяется
Deduplication.
### Ожидаемый результат
Возвращается
```python
None
```
---
## 9. Конфликтующий повтор
```text
trade_id = 500
price = 100
```
позже
```text
trade_id = 500
price = 101
```
### Проверяется
Consistency.
### Ожидаемый результат
TradeConsistencyError.
---
## 10. Неизменяемость состояния после ошибки
После возникновения исключения проверяется:
- last_trade_id;
- окно дедупликации;
- количество опубликованных сделок.
Ничего не должно измениться.
---
## 11. Работа FIFO
Заполняется окно.
Добавляется ещё одна сделка.
Проверяется корректное удаление самой старой записи.
---
## 12. Проверка границы окна
После удаления старой записи
её повтор
не должен считаться известной сделкой.
---
## 13. Проверка symbol isolation
BTC
не должен влиять на
ETH.
Даже при совпадающих trade_id.
---
## 14. Проверка большого потока
Несколько тысяч последовательных сделок.
Проверяется:
- отсутствие деградации;
- корректность состояния;
- отсутствие ошибок порядка.
---
# Проверка производительности
Build 060.18 не вводит отдельного Benchmark Build.
Однако контроллер обязан удовлетворять следующим требованиям.
---
## Поиск сделки
Средняя сложность
```text
O(1)
```
---
## Добавление сделки
Средняя сложность
```text
O(1)
```
---
## Удаление старой записи
Средняя сложность
```text
O(1)
```
---
## Использование памяти
Размер памяти ограничивается размером окна дедупликации.
Не допускается бесконечный рост.
---
# Матрица архитектурных инвариантов
| Инвариант | Проверка |
|-----------|----------|
| Immutable Trade | Unit |
| Canonical Identity | Unit |
| Ordering | Unit |
| Deduplication | Unit |
| Conflicting Duplicate | Unit |
| State Isolation | Unit |
| FIFO Window | Unit |
| Integration Pipeline | Integration |
| Stateless Handler | Integration |
| Controller Boundary | Integration |
---
# Definition of Done
Build считается завершённым только после выполнения всех перечисленных условий.
---
## Архитектура
- Все ADR реализованы без отклонений.
- Не нарушены границы ответственности компонентов.
- Controller остаётся Domain Service.
- State остаётся Domain State.
---
## Код
- Не изменена модель Trade.
- Не изменены Parser.
- Не изменены Mapper.
- Не изменена Validation.
- Handler остаётся stateless.
---
## Функциональность
Поддерживаются:
- Ordering;
- Deduplication;
- Conflict Detection;
- Symbol Isolation.
---
## Производительность
Все операции соответствуют требованиям:
```text
O(1)
```
---
## Тестирование
Все Unit Tests проходят.
Все Integration Tests проходят.
Regression Tests проходят без изменений.
---
## Документация
Обновлены:
- Build Documentation;
- ADR;
- Architecture Diagram;
- File Plan.
---
# Пошаговый план реализации
Реализация Build должна выполняться строго по следующему порядку.
---
## Шаг 1
Создание структуры каталога
consistency/
---
## Шаг 2
Создание файла trade_stream_exceptions.py.
---
## Шаг 3
Создание файла trade_stream_state.py.
---
## Шаг 4
Реализация FIFO Window.
---
## Шаг 5
Создание файла trade_stream_consistency_controller.py.
---
## Шаг 6
Создание файла trade_stream_protocol.py.
---
## Шаг 7
Интеграция через Composition Root.
---
## Шаг 8
Интеграция с TradesFeed.
---
## Шаг 9
Unit Tests.
---
## Шаг 10
Integration Tests.
---
## Шаг 11
Regression Tests.
---
# Build Boundary
После завершения Build 060.18 подсистема получения сделок считается архитектурно завершённой с точки зрения согласованности потока.
Следующим этапом развития становится Build 060.19.
Его задачами являются:
- обнаружение пропусков последовательности;
- восстановление пропущенных сделок;
- интеграция REST Backfill;
- повторная синхронизация потока;
- подготовка к непрерывной работе Trade Feed.
Build 060.18 сознательно не реализует перечисленные функции.
Он создаёт архитектурный фундамент, на который будут последовательно опираться все последующие Build серии 060.
---
# Заключение
Build 060.18 завершает формирование базовой архитектуры **Trades Feed (Time & Sales)**.
Если предыдущие Build определяли, **что такое каноническая сделка (`Trade`)**, то Build 060.18 впервые формализует, **что такое канонический поток сделок (`Canonical Trade Stream`)**.
Ключевым результатом является введение `TradeStreamConsistencyController` — единственной точки формирования согласованного потока, и `TradeStreamState` — единственного владельца состояния этого потока.
Зафиксированные в документе инварианты, контракты и ADR обеспечивают:
- единое поведение независимо от источника данных (REST или WebSocket);
- строгую дедупликацию и контроль порядка;
- неизменяемость опубликованного потока;
- чистое разделение ответственности между компонентами;
- возможность дальнейшего развития без изменения уже принятой архитектуры.
Таким образом, Build 060.18 становится фундаментом для следующих этапов серии 060, включая Gap Detection, Recovery, REST Backfill и построение полностью отказоустойчивого Trade Feed.