diff --git a/app/src/market_data/acquisition/adapters/dzengi/models.py b/app/src/market_data/acquisition/adapters/dzengi/models.py index 0a1b527..b8af757 100644 --- a/app/src/market_data/acquisition/adapters/dzengi/models.py +++ b/app/src/market_data/acquisition/adapters/dzengi/models.py @@ -150,6 +150,17 @@ class DzengiKlinesResponse: items: tuple[DzengiKline, ...] +# Транспортное представление одной агрегированной сделки +# Dzengi GET /api/v2/aggTrades. +@dataclass(frozen=True, slots=True) +class DzengiRestAggTrade: + aggregate_trade_id: int + price: DzengiRawNumeric + quantity: DzengiRawNumeric + timestamp: int + buyer_is_maker: bool + + # Транспортное представление одного события Dzengi WebSocket ohlc.event. # # Событие содержит завершённую OHLC-свечу без объёма и поэтому не является diff --git a/app/tests/unit/market_data/acquisition/adapters/dzengi/test_models.py b/app/tests/unit/market_data/acquisition/adapters/dzengi/test_models.py index 0d92010..f22dc44 100644 --- a/app/tests/unit/market_data/acquisition/adapters/dzengi/test_models.py +++ b/app/tests/unit/market_data/acquisition/adapters/dzengi/test_models.py @@ -13,6 +13,7 @@ from src.market_data.acquisition.adapters.dzengi.models import ( DzengiLotSizeFilter, DzengiMinNotionalFilter, DzengiRateLimit, + DzengiRestAggTrade, DzengiUnknownFilter, ) @@ -203,4 +204,49 @@ def test_raw_models_are_immutable() -> None: response = DzengiExchangeInfoResponse(payload=payload) with pytest.raises(FrozenInstanceError): - response.status = "OK" # type: ignore[misc] \ No newline at end of file + response.status = "OK" # type: ignore[misc] + +def test_rest_agg_trade_stores_complete_transport_contract() -> None: + trade = DzengiRestAggTrade( + aggregate_trade_id=2134857062, + price="64497.25", + quantity="0.005", + timestamp=1784218066823, + buyer_is_maker=False, + ) + + assert trade.aggregate_trade_id == 2134857062 + assert trade.price == "64497.25" + assert trade.quantity == "0.005" + assert trade.timestamp == 1784218066823 + assert trade.buyer_is_maker is False + + +def test_rest_agg_trade_preserves_raw_numeric_values() -> None: + trade = DzengiRestAggTrade( + aggregate_trade_id=2134846831, + price=64555.55, + quantity=2, + timestamp=1784218012030, + buyer_is_maker=True, + ) + + assert trade.price == 64555.55 + assert isinstance(trade.price, float) + assert trade.quantity == 2 + assert isinstance(trade.quantity, int) + + +def test_rest_agg_trade_is_immutable_and_uses_slots() -> None: + trade = DzengiRestAggTrade( + aggregate_trade_id=2134857062, + price="64497.25", + quantity="0.005", + timestamp=1784218066823, + buyer_is_maker=False, + ) + + assert not hasattr(trade, "__dict__") + + with pytest.raises(FrozenInstanceError): + trade.price = "1.00" # type: ignore[misc] diff --git a/docs/migrations/build_060_2.md b/docs/migrations/build_060_2.md new file mode 100644 index 0000000..0044bd3 --- /dev/null +++ b/docs/migrations/build_060_2.md @@ -0,0 +1,1401 @@ +# 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`. \ No newline at end of file