Files
dzentra_bot/docs/migrations/build_060_2.md

1401 lines
34 KiB
Markdown
Raw Permalink 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.2 — REST Trade Transport Model
**Engineering Migration Report**
---
# Контроль документа
| Свойство | Значение |
|---|---|
| Build | 060.2 |
| Название | REST Trade Transport Model |
| Статус | Завершён |
| Проект | Dzentra |
| Подсистема | Market Data Acquisition |
| Компонент | Trades Feed / Time & Sales |
| Версия документа | 1.0 |
| Дата завершения | 2026-07-18 |
---
# Цель Build
После завершения Build 060.1 в системе появился первый канонический контракт исполненной сделки:
```text
Trade
```
Следующим этапом необходимо было начать построение REST-пайплайна получения исторических сделок.
Первым слоем этого пайплайна является транспортная модель, описывающая один элемент ответа REST API Dzengi.
Build 060.2 вводит такую модель.
Она должна описывать внешний контракт REST API максимально точно и при этом не выполнять никаких преобразований данных.
Build ограничен исключительно модельным уровнем.
В рамках Build намеренно **не реализуются**:
- REST document source;
- REST schema validation;
- REST parser;
- REST value validation;
- REST mapper;
- Canonical Trade mapping;
- REST client;
- feed;
- registry;
- protocol;
- acquisition service;
- runtime;
- production-интеграция.
---
# Архитектурный контекст
В Dzentra используется многоуровневая модель обработки рыночных данных.
Каждый внешний источник проходит одинаковую цепочку преобразований:
```text
Transport
Schema Validation
Parser
Value Validation
Mapper
Canonical Model
```
Такой подход уже используется для остальных типов рыночных данных и теперь переносится на Trades Feed.
Build 060.1 создал внутреннюю предметную модель:
```text
Trade
```
Однако REST API биржи использует собственный внешний контракт.
Следовательно, непосредственное создание `Trade` из REST JSON нарушило бы архитектурный принцип разделения ответственности.
Перед появлением parser и mapper необходим отдельный слой, описывающий исключительно внешний формат данных.
Именно этот слой реализуется в Build 060.2.
---
# Исходное состояние
До начала Build 060.2 в проекте уже существовали транспортные модели:
```text
ExchangeInfo
Kline
WebSocket OHLC
```
Однако транспортной модели агрегированной сделки REST API ещё не существовало.
Файл:
```text
app/src/market_data/acquisition/adapters/dzengi/models.py
```
содержал модели:
- ExchangeInfo;
- Instrument Filters;
- Kline;
- WebSocket OHLC.
Контракт REST AggTrades отсутствовал.
Unit-тесты также не содержали проверок транспортной модели сделки.
Таким образом Build мог быть реализован как полностью additive change без изменения существующего поведения системы.
---
# Предварительный архитектурный аудит
Перед реализацией были проанализированы:
```text
app/src/market_data/acquisition/adapters/dzengi/models.py
app/tests/unit/market_data/acquisition/adapters/dzengi/test_models.py
app/src/market_data/acquisition/models/trade.py
docs/migrations/build_060_1.md
```
Кроме анализа существующего кода была повторно проверена спецификация REST API агрегированных сделок.
Используемый REST элемент имеет следующий вид:
```json
{
"a": 2134857062,
"p": "64497.25",
"q": "0.005",
"T": 1784218066823,
"m": false
}
```
Аудит подтвердил следующие выводы:
- транспортная модель должна описывать один элемент массива;
- транспортная модель не должна выполнять parsing;
- транспортная модель не должна выполнять validation;
- транспортная модель не должна выполнять mapping;
- транспортная модель должна использовать существующий тип `DzengiRawNumeric`;
- timestamp должен оставаться типом `int`;
- transport boolean должен сохраняться без интерпретации;
- модель должна использовать `@dataclass(frozen=True, slots=True)`.
Никаких изменений существующих моделей Build не требует.
---
# Рассмотренные архитектурные решения
Перед реализацией были рассмотрены несколько вариантов представления транспортной модели.
## Вариант 1
Использовать сразу Canonical Trade.
Например:
```python
Trade(
...
)
```
Данный вариант был отклонён.
Причины:
- нарушается разделение transport и domain;
- parser начинает зависеть от внутренней модели;
- mapper становится ненужным;
- невозможно отделить ошибки транспорта от ошибок бизнес-преобразования.
Использование Canonical Trade непосредственно на транспортном уровне противоречит архитектуре Acquisition Pipeline.
---
## Вариант 2
Использовать словарь (`dict`).
Например:
```python
dict[str, Any]
```
Вариант отклонён.
Причины:
- отсутствует строгий контракт;
- отсутствует типизация;
- снижается читаемость parser;
- невозможно использовать dataclass conventions проекта;
- увеличивается вероятность ошибок при дальнейшем mapping.
Использование dataclass полностью соответствует существующей архитектуре проекта.
---
## Вариант 3
Создать отдельную immutable transport model.
```python
DzengiRestAggTrade
```
Именно этот вариант утверждён.
Причины:
- полностью отделяет transport layer;
- не смешивает внешний API с предметной моделью;
- сохраняет единый стиль существующих моделей;
- обеспечивает строгую типизацию;
- позволяет независимо развивать parser и mapper.
---
# Почему используется отдельная транспортная модель
REST API является внешним контрактом биржи.
Любое изменение этого API не должно автоматически изменять внутренние предметные модели Dzentra.
Поэтому транспортная модель выполняет только одну задачу:
точно описывает внешний формат данных.
Она ничего не знает о:
- Canonical Trade;
- Decimal;
- datetime;
- TradeAggressorSide;
- предметной логике;
- runtime.
Все последующие преобразования выполняются отдельными Build.
# Рассмотренные архитектурные решения (продолжение)
## Название модели
Рассматривались несколько вариантов.
### Вариант 1
```text
AggTrade
```
Вариант отклонён.
Причины:
- название не указывает источник данных;
- в дальнейшем появится WebSocket Transport Model;
- становится невозможным различить REST и WebSocket transport-контракты только по имени класса.
---
### Вариант 2
```text
RestTrade
```
Вариант также отклонён.
Причины:
- не отражает, что используется именно endpoint `aggTrades`;
- в будущем могут появиться другие REST-модели сделок;
- название становится слишком общим.
---
### Вариант 3
```text
DzengiRestAggTrade
```
Именно этот вариант утверждён.
Причины:
- явно указывает биржу;
- явно указывает transport;
- явно указывает используемый REST endpoint;
- согласуется с существующими моделями Dzengi;
- исключает неоднозначность при появлении WebSocket Transport Model.
---
# Название идентификатора сделки
Рассматривались следующие варианты.
```text
id
trade_id
aggregate_trade_id
```
---
## Вариант `id`
Отклонён.
Причины:
- слишком общее имя;
- не отражает предметную семантику;
- легко спутать с внутренними идентификаторами других объектов.
---
## Вариант `trade_id`
Отклонён.
Причины:
В Build 060.1 уже утверждено поле
```text
trade_id
```
для Canonical Trade.
Использование того же имени на транспортном уровне создавало бы ложное ощущение, что REST уже предоставляет канонический идентификатор сделки.
Фактически REST возвращает идентификатор агрегированной сделки конкретного endpoint.
Следовательно транспортная модель должна сохранить эту семантику.
---
## Утверждённое решение
Используется
```python
aggregate_trade_id
```
Причины:
- отражает фактическое назначение поля;
- полностью соответствует REST API;
- не смешивается с Canonical Trade;
- позволяет Mapper самостоятельно принять решение о дальнейшем использовании этого значения.
---
# Название цены
Рассматривались:
```text
price
execution_price
trade_price
```
Выбрано:
```text
price
```
Причины:
- полностью соответствует существующим моделям проекта;
- название предметное;
- не требует дополнительных уточнений;
- одинаково подходит для REST и WebSocket transport.
---
# Название количества
Рассматривались:
```text
quantity
size
volume
```
---
## size
Отклонено.
Причины:
- это WebSocket transport-термин;
- Build 060.2 описывает REST контракт;
- Canonical Trade уже использует quantity.
---
## volume
Отклонено.
Причины:
- термин неоднозначен;
- volume чаще относится к агрегированному объёму свечи;
- REST API использует количество конкретной сделки.
---
## Утверждённое решение
Используется
```text
quantity
```
Причины:
- совпадает с предметной терминологией;
- соответствует Canonical Trade;
- одинаково понятно независимо от transport.
---
# Представление времени сделки
Наиболее серьёзное обсуждение вызвал timestamp.
Рассматривались несколько вариантов.
```text
timestamp
executed_at
datetime
trade_time
```
---
## Использование datetime
Вариант отклонён.
Причины:
Build 060.2 описывает транспортный слой.
REST API возвращает миллисекундный timestamp.
Преобразование его в datetime является задачей Mapper.
Следовательно транспортная модель не должна менять тип данных.
---
## Использование executed_at
Также отклонено.
Причины:
Поле
```text
executed_at
```
уже является частью Canonical Trade.
Оно описывает предметное время исполнения.
REST же возвращает только транспортный timestamp.
До этапа Mapper это ещё не предметное время.
---
## Использование timestamp
Утверждено.
Поле имеет вид
```python
timestamp: int
```
Причины:
- полностью отражает транспортный контракт;
- отсутствует скрытая логика преобразования;
- parser получает исходные данные;
- Mapper самостоятельно создаёт datetime.
---
# Представление стороны сделки
REST API содержит поле
```json
"m": false
```
которое означает
```text
buyer_is_maker
```
Build 060.2 должен решить, как представить эту информацию.
---
## Вариант Enum
Например
```python
TradeAggressorSide
```
отклонён.
Причины:
это уже предметная модель Build 060.1.
Transport layer не должен знать о внутреннем Enum.
---
## Вариант bool с именем m
Например
```python
m: bool
```
отклонён.
Причины:
- имя ничего не говорит разработчику;
- требуется знание документации REST API;
- ухудшается читаемость parser.
---
## Утверждённое решение
Используется
```python
buyer_is_maker: bool
```
Причины:
- семантика REST полностью сохранена;
- отсутствует неоднозначность;
- Mapper получает всю необходимую информацию;
- транспортная модель остаётся максимально близкой к внешнему контракту.
---
# Почему используется DzengiRawNumeric
REST API возвращает цену и количество строками.
Однако существующие transport-модели Dzentra уже используют общий тип:
```python
DzengiRawNumeric
```
который допускает:
```text
str
int
float
```
Использование существующего alias обеспечивает единообразие transport-моделей проекта.
Кроме того, Build 060.2 не должен заниматься проверкой корректности числовых значений.
Эта задача полностью переносится на Build 060.5.
---
# Окончательно утверждённый контракт
После завершения обсуждения был утверждён следующий dataclass.
```python
@dataclass(frozen=True, slots=True)
class DzengiRestAggTrade:
aggregate_trade_id: int
price: DzengiRawNumeric
quantity: DzengiRawNumeric
timestamp: int
buyer_is_maker: bool
```
Контракт является минимальным.
Он содержит только те поля, которые реально присутствуют внутри одного элемента REST `aggTrades`.
Никакие дополнительные сведения Build 060.2 не добавляет.
# Семантика транспортной модели
Транспортная модель описывает один элемент массива, возвращаемого REST endpoint:
```text
GET /api/v2/aggTrades
```
Каждое поле модели соответствует одному полю внешнего REST API.
Модель не интерпретирует значения и не выполняет их преобразование.
---
## aggregate_trade_id
```python
aggregate_trade_id: int
```
Идентификатор агрегированной сделки, возвращаемый REST API.
Соответствует JSON-полю:
```text
a
```
Build 060.2 не делает предположений относительно:
- глобальной уникальности значения;
- возможности его использования в качестве Canonical Trade ID;
- совпадения с WebSocket Trade ID.
Все подобные решения относятся исключительно к будущему Mapper.
---
## price
```python
price: DzengiRawNumeric
```
Исходное значение цены сделки.
Соответствует JSON-полю:
```text
p
```
Build 060.2 намеренно не выполняет:
- преобразование в Decimal;
- проверку формата строки;
- проверку положительности;
- проверку точности.
Все проверки выполняются позже.
---
## quantity
```python
quantity: DzengiRawNumeric
```
Количество исполненного инструмента.
Соответствует JSON-полю:
```text
q
```
Модель не проверяет:
- минимальный размер сделки;
- допустимую точность;
- знак значения.
Эти проверки относятся к Value Validation.
---
## timestamp
```python
timestamp: int
```
Миллисекундная временная метка сделки.
Соответствует JSON-полю:
```text
T
```
Build 060.2 специально не выполняет:
```text
int
datetime
```
Подобное преобразование относится к обязанностям Mapper.
Таким образом transport layer всегда содержит исходное значение API.
---
## buyer_is_maker
```python
buyer_is_maker: bool
```
Соответствует JSON-полю:
```text
m
```
Поле хранится без каких-либо преобразований.
Build 060.2 намеренно не вычисляет:
```text
TradeAggressorSide
```
Причина проста.
REST API предоставляет transport boolean.
Canonical Model использует предметный Enum.
Преобразование одного представления в другое является обязанностью Mapper.
---
# Поля, намеренно не включённые в модель
## symbol
Поле отсутствует.
Причины:
В REST endpoint:
```text
GET /api/v2/aggTrades
```
символ инструмента передаётся параметром запроса.
Внутри элемента массива его нет.
Добавление поля
```text
symbol
```
искусственно изменило бы внешний контракт.
Транспортная модель должна описывать исключительно полученный JSON.
---
## trade_id
Не добавлено.
Причины:
Build 060.1 уже закрепил:
```text
Trade.trade_id
```
как часть Canonical Model.
На транспортном уровне имеется только:
```text
aggregate_trade_id
```
Смешивание этих понятий нарушило бы разделение уровней архитектуры.
---
## executed_at
Не добавлено.
Причины:
REST API возвращает:
```text
timestamp
```
Преобразование в предметное время исполнения должно происходить позже.
---
## Decimal
Не используется.
Причины:
Transport layer не выполняет преобразование типов.
Использование Decimal означало бы скрытый parser.
Это противоречит принятой архитектуре.
---
## datetime
Не используется.
Причины:
Transport layer не занимается интерпретацией времени.
Datetime появляется только после Mapper.
---
## TradeAggressorSide
Не используется.
Причины:
Transport layer ничего не знает о предметной модели.
Булево значение REST API должно сохраняться в исходном виде.
---
# Изменённые файлы
В рамках Build изменены только:
```text
app/src/market_data/acquisition/adapters/dzengi/models.py
app/tests/unit/market_data/acquisition/adapters/dzengi/test_models.py
docs/migrations/build_060_2.md
```
Другие компоненты системы не изменялись.
---
# Изменения в models.py
Файл
```text
app/src/market_data/acquisition/adapters/dzengi/models.py
```
получил новый immutable dataclass:
```python
DzengiRestAggTrade
```
Никакие существующие модели не изменялись.
Build добавляет исключительно новый транспортный контракт.
Модель использует уже существующий alias:
```python
DzengiRawNumeric
```
что обеспечивает единообразие transport layer.
---
# Изменения в unit-тестах
Файл
```text
app/tests/unit/market_data/acquisition/adapters/dzengi/test_models.py
```
расширен тремя новыми тестами.
Они проверяют:
- корректное сохранение полного REST transport contract;
- отсутствие скрытого преобразования числовых значений;
- immutable-поведение;
- использование `slots=True`.
Build не изменяет существующие тесты ExchangeInfo и других моделей.
Все новые проверки являются полностью additive.
---
# Что намеренно не изменялось
Build 060.2 специально не изменяет:
```text
app/src/market_data/acquisition/models/trade.py
app/src/market_data/acquisition/models/__init__.py
app/src/market_data/acquisition/runtime/
app/src/market_data/acquisition/validation/
app/src/market_data/acquisition/feeds/
app/src/market_data/acquisition/protocol.py
app/src/market_data/acquisition/service.py
app/src/market_data/acquisition/registry.py
```
Также Build не включает:
- REST client;
- document source;
- parser;
- schema validation;
- value validation;
- mapper;
- runtime integration;
- feed integration;
- production integration;
- WebSocket изменения;
- изменение Canonical Trade.
Все перечисленные компоненты реализуются отдельными Build согласно утверждённой дорожной карте.
# Проверка компиляции
Команды выполнялись из каталога:
```text
~/vsprojects/dzentra_bot/app
```
Активировано виртуальное окружение:
```bash
source .venv/bin/activate
```
Выполнена команда:
```bash
python -m compileall src
```
Результат:
```text
успешно
```
Все каталоги проекта были успешно обработаны.
Ошибок компиляции не обнаружено.
---
# Проверка локальных unit-тестов
После реализации транспортной модели были выполнены специализированные тесты:
```bash
python -m pytest \
tests/unit/market_data/acquisition/adapters/dzengi/test_models.py \
-q
```
Результат:
```text
9 passed in 0.03s
```
Это подтверждает корректность:
- новой транспортной модели;
- существующих моделей ExchangeInfo;
- существующих транспортных типов;
- новых unit-тестов Build 060.2.
---
# Полный regression suite
После завершения Build выполнен полный набор тестов проекта.
Команда:
```bash
python -m pytest -q
```
Результат:
```text
993 passed in 3.00s
```
Регрессий не обнаружено.
Добавление новой транспортной модели не повлияло на существующее поведение системы.
---
# Проверка форматирования
Выполнена команда:
```bash
git diff --check
```
Вывод отсутствует.
Это подтверждает отсутствие:
- trailing whitespace;
- ошибочных пустых строк;
- нарушений форматирования diff.
---
# Контроль размещения новой модели
После завершения Build транспортная модель агрегированной сделки располагается исключительно в предусмотренном месте:
```text
src/market_data/acquisition/adapters/dzengi/models.py
```
Её использование подтверждено только специализированными unit-тестами:
```text
tests/unit/market_data/acquisition/adapters/dzengi/test_models.py
```
Другие компоненты системы Build 060.2 не затрагивает.
Таким образом транспортный контракт остаётся полностью изолированным.
---
# Состояние Git
После завершения Build выполнена команда:
```bash
git status
```
Для Build 060.2 зафиксированы изменения:
```text
modified:
app/src/market_data/acquisition/adapters/dzengi/models.py
modified:
app/tests/unit/market_data/acquisition/adapters/dzengi/test_models.py
untracked:
docs/migrations/build_060_2.md
```
Дополнительно в рабочем каталоге присутствует архитектурная контрольная точка:
```text
docs/migrations/build_060_transition_&_architecture_checkpoint.md
```
Она не относится к реализации Build 060.2 и учитывается отдельно при формировании commit.
Ветка разработки:
```text
main
```
опережает `origin/main`.
Данное состояние не связано с реализацией транспортной модели и не изменялось в рамках Build.
---
# Фактический diff модели
В файл:
```text
app/src/market_data/acquisition/adapters/dzengi/models.py
```
добавлен новый dataclass:
```python
@dataclass(frozen=True, slots=True)
class DzengiRestAggTrade:
aggregate_trade_id: int
price: DzengiRawNumeric
quantity: DzengiRawNumeric
timestamp: int
buyer_is_maker: bool
```
Существующие модели:
- ExchangeInfo;
- Instrument Filters;
- Kline;
- WebSocket OHLC;
не изменялись.
Build является полностью additive.
---
# Фактический diff unit-тестов
В файл:
```text
tests/unit/market_data/acquisition/adapters/dzengi/test_models.py
```
добавлены три новых теста.
Они подтверждают:
- корректность хранения полного транспортного контракта;
- сохранение исходных числовых типов;
- immutable-поведение;
- использование `slots=True`.
Все ранее существовавшие тесты продолжают выполняться без изменений.
---
# Архитектурный результат
После завершения Build 060.2 в подсистеме Market Data Acquisition появился первый транспортный контракт агрегированной сделки REST API.
Архитектура REST-пайплайна приобрела следующий вид:
```text
REST API
DzengiRestAggTrade
Schema Validation
Parser
Value Validation
Mapper
Trade
```
Таким образом Build окончательно разделил:
- внешний REST API;
- внутреннюю предметную модель;
- будущие этапы обработки.
Каждый уровень теперь имеет собственную ответственность.
---
# Влияние на последующие Build
Build 060.2 создаёт фундамент для следующих этапов серии 060.
Прежде всего он позволяет реализовать:
```text
Build 060.3
REST Trade Schema Validation
```
Новая транспортная модель станет входным контрактом валидатора схемы.
Далее на её основе будут построены:
```text
Build 060.4
REST Trade Parser
Build 060.5
REST Trade Value Validation
Build 060.6
REST Trade Mapper
```
До появления Build 060.2 реализация этих компонентов была невозможна без нарушения архитектурных принципов.
---
# Критерии завершения
Build 060.2 считается завершённым, поскольку:
- проведён предварительный архитектурный аудит;
- подтверждены существующие conventions transport layer;
- утверждён минимальный REST Transport Contract;
- создан immutable dataclass;
- используется `slots=True`;
- используется существующий тип `DzengiRawNumeric`;
- timestamp сохраняется как `int`;
- transport boolean сохраняется без преобразования;
- `Decimal` намеренно не используется;
- `datetime` намеренно не используется;
- `TradeAggressorSide` намеренно не используется;
- `symbol` намеренно отсутствует;
- parser не реализован;
- validation не реализована;
- mapper не реализован;
- runtime не изменён;
- compile проверка успешно пройдена;
- локальные unit-тесты успешно пройдены;
- полный regression suite успешно пройден;
- `git diff --check` не выявил ошибок;
- scope Build не расширен.
---
# Итог
**Build 060.2 завершён успешно.**
Текущее состояние:
```text
DzengiRestAggTrade — реализован
Immutable contract — подтверждён
slots=True — подтверждён
DzengiRawNumeric — используется
timestamp остаётся int
transport boolean сохранён
Parser — отсутствует
Schema Validation — отсутствует
Value Validation — отсутствует
Mapper — отсутствует
Compile check — успешно
Target tests — 9 passed
Full regression suite — 993 passed
Whitespace check — чисто
Production integration — намеренно не выполнялась
```
Build создал полноценный транспортный контракт агрегированной сделки REST API.
Полученная модель полностью изолирована от внутренней предметной модели и готова к использованию следующими слоями Acquisition Pipeline.
---
# Следующий этап
Следующий Build должен оставаться минимальным и additive.
Его scope должен быть ограничен исключительно проверкой структуры транспортного документа.
Наиболее логичное продолжение серии:
```text
Build 060.3 — REST Trade Schema Validation
```
На этом этапе будет реализована проверка корректности структуры одного элемента `DzengiRestAggTrade` без выполнения parsing, преобразования типов и построения канонической модели `Trade`.