1401 lines
34 KiB
Markdown
1401 lines
34 KiB
Markdown
# 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`. |