Files
dzentra_bot/docs/migrations/build_060_4.md

395 lines
13 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.4 — REST Trade Parser
## Статус
✅ Завершён
---
# Цель Build
После завершения Build 060.3 система уже умела принимать REST-ответ endpoint `/api/v2/aggTrades`, проверять его структуру и преобразовывать в immutable-контракт:
```text
REST JSON
validate_rest_agg_trades_schema()
ValidatedRestAggTradesDocument
```
Однако структурно проверенный документ ещё не преобразовывался в transport-модели адаптера Dzengi.
Цель Build 060.4 — добавить отдельный REST Trade Parser, который:
- принимает только `ValidatedRestAggTradesDocument`;
- проверяет типы полей каждой сделки;
- создаёт `DzengiRestAggTrade`;
- возвращает immutable-последовательность transport-моделей;
- не выполняет value validation и canonical mapping.
Итоговый участок pipeline после Build 060.4:
```text
REST JSON
Schema Validation
ValidatedRestAggTradesDocument
REST Trade Parser
tuple[DzengiRestAggTrade, ...]
```
---
# Причины появления Build
Schema Validation отвечает только за структуру внешнего документа. После Build 060.3 она гарантирует, что корень документа является JSON-массивом, каждый элемент является JSON-объектом, обязательные поля `a`, `p`, `q`, `T`, `m` присутствуют, ключи объектов являются строками, а документ переведён в immutable-представление.
Но Schema Validation намеренно не определяет, являются ли значения полей допустимыми транспортными типами. Именно Parser обязан отклонить такой документ и не допустить создание некорректной transport-модели.
---
# Архитектурное решение
В Build реализован отдельный parser-функционал для REST Trades:
```python
parse_rest_agg_trades(
document: ValidatedRestAggTradesDocument,
) -> tuple[DzengiRestAggTrade, ...]
```
Parser работает со всем REST-документом целиком и не принимает произвольный `dict` или `list`. Его входной контракт уже подтверждает, что документ прошёл Schema Validation.
```text
ValidatedRestAggTradesDocument
parse_rest_agg_trades()
tuple[DzengiRestAggTrade, ...]
```
---
# Граница ответственности Parser
REST Trade Parser отвечает за:
- проверку типов полей;
- исключение недопустимого использования `bool` как числа;
- создание `DzengiRestAggTrade`;
- сохранение исходных raw numeric значений;
- формирование immutable `tuple`;
- диагностический путь с индексом элемента.
Parser не отвечает за:
- проверку положительности цены и количества;
- проверку диапазона timestamp;
- проверку неотрицательности aggregate trade id;
- преобразование `str | int | float` в `Decimal`;
- построение canonical `Trade`;
- определение стороны сделки;
- REST transport, feed registry и runtime orchestration.
---
# Новое исключение
В Build добавлено исключение:
```python
class TradeParseError(MarketDataAcquisitionError):
pass
```
Оно используется только для ошибок преобразования структурно проверенного REST Trade документа в transport-модели Dzengi.
Build сознательно не добавляет `TradeValueError`, `TradeMappingError`, `TradeTransportError` и `TradeFeedRegistryError`. Каждое исключение должно появляться только в Build, реализующем соответствующий слой pipeline.
---
# Входной контракт
Parser принимает:
```python
ValidatedRestAggTradesDocument
```
Контракт был добавлен в Build 060.3:
```python
@dataclass(frozen=True, slots=True)
class ValidatedRestAggTradesDocument:
items: tuple[Mapping[str, object], ...]
```
Каждый элемент `items` уже является mapping, содержит обязательные поля, имеет строковые ключи и защищён от изменения через `MappingProxyType`. Parser не повторяет Schema Validation.
---
# Выходной контракт
Parser возвращает:
```python
tuple[DzengiRestAggTrade, ...]
```
Каждый элемент является immutable transport-моделью:
```python
@dataclass(frozen=True, slots=True)
class DzengiRestAggTrade:
aggregate_trade_id: int
price: DzengiRawNumeric
quantity: DzengiRawNumeric
timestamp: int
buyer_is_maker: bool
```
После Build 060.4 REST-ответ уже представлен типизированными transport-объектами, но ещё не преобразован во внутреннюю canonical-модель Dzentra.
---
# Реализация Parser
Основная функция:
```python
def parse_rest_agg_trades(
document: ValidatedRestAggTradesDocument,
) -> tuple[DzengiRestAggTrade, ...]:
return tuple(
_parse_rest_agg_trade_item(
item,
path=f"$[{index}]",
)
for index, item in enumerate(document.items)
)
```
Функция проходит по всем элементам документа, сохраняет исходный порядок сделок, передаёт индекс элемента в diagnostic path и создаёт immutable `tuple`.
Пустой документ преобразуется в `()` и не считается ошибкой.
---
# Преобразование отдельной сделки
Каждый элемент преобразуется функцией `_parse_rest_agg_trade_item()`.
Соответствие внешних полей transport-модели:
| REST поле | Transport поле |
|---|---|
| `a` | `aggregate_trade_id` |
| `p` | `price` |
| `q` | `quantity` |
| `T` | `timestamp` |
| `m` | `buyer_is_maker` |
Parser не изменяет семантику значений.
---
# Контракт типов
Build фиксирует следующий transport type contract:
- `a` — строго `int`, но не `bool`;
- `p``str | int | float`, но не `bool`;
- `q``str | int | float`, но не `bool`;
- `T` — строго `int`, но не `bool`;
- `m` — строго `bool`.
Проверка диапазонов и смысловой допустимости значений в этот Build не входит.
---
# Почему `bool` исключается из числовых полей
В Python выражение `isinstance(True, int)` возвращает `True`. Без специальной проверки значения `True` и `False` могли бы быть ошибочно приняты как идентификатор сделки, timestamp, цена или количество.
Поэтому числовые helper-функции сначала проверяют `isinstance(value, bool)` и отклоняют такое значение до общей проверки числа. Это является частью transport contract, а не value validation.
---
# Raw numeric preservation
Поля `price` и `quantity` используют тип `DzengiRawNumeric`.
Parser не преобразует значения в `Decimal` и не нормализует их:
- строка `"1.2300"` остаётся строкой `"1.2300"`;
- целое число `5` остаётся `5`;
- число `1.25` остаётся `1.25`.
Это сохраняет исходное представление данных биржи до отдельного этапа Value Validation и Normalization.
---
# Diagnostic path
Parser формирует путь к каждому полю с учётом индекса элемента.
Например, ошибка цены во второй сделке будет представлена как:
```text
$[1].p должен быть строкой или числом
```
Это позволяет точно определить индекс некорректной сделки и имя поля.
---
# Отдельные Trade helper-функции
В Build добавлены специализированные helper-функции:
```python
_trade_required_int()
_trade_required_raw_numeric()
_trade_required_bool()
```
Они следуют общей parser-инфраструктуре проекта, но возбуждают именно `TradeParseError`. Переиспользование helper-функций другого feed было бы некорректным, поскольку они используют другие error contracts.
---
# Unit-тесты
В Build добавлен отдельный набор unit-тестов REST Trade Parser.
Проверяются:
- корректное преобразование нескольких сделок;
- сохранение порядка элементов;
- пустой документ;
- immutable `tuple`;
- создание `DzengiRestAggTrade`;
- сохранение raw numeric значений;
- отклонение неверных типов `a`, `T`, `p`, `q`, `m`;
- отдельное отклонение `bool` в числовых полях;
- корректный индекс элемента в сообщении об ошибке.
---
# Результаты проверки
Target tests:
```text
46 passed
```
Полная регрессия проекта:
```text
1038 passed
```
Дополнительно успешно выполнены:
```bash
python -m compileall src
git diff --check
```
Файл unit-тестов содержит корректный завершающий перевод строки.
---
# Изменённые файлы
В рамках Build изменены только три файла.
## `src/market_data/acquisition/exceptions.py`
Добавлено `TradeParseError`.
## `src/market_data/acquisition/adapters/dzengi/parser.py`
Добавлены:
- `parse_rest_agg_trades()`;
- `_parse_rest_agg_trade_item()`;
- `_trade_required_int()`;
- `_trade_required_raw_numeric()`;
- `_trade_required_bool()`;
- необходимые импорты.
## `tests/unit/market_data/acquisition/adapters/dzengi/test_parser.py`
Добавлены helper создания validated-документа и полный набор позитивных и негативных parser-тестов.
Другие части проекта не изменялись.
---
# Что не изменялось
Build не затрагивает:
```text
src/market_data/acquisition/adapters/dzengi/models.py
src/market_data/acquisition/adapters/dzengi/mapper.py
src/market_data/acquisition/validation/schema.py
src/market_data/acquisition/validation/values.py
src/market_data/acquisition/feeds/
src/market_data/acquisition/runtime/
```
Также не изменялись существующие parser-функции для ExchangeInfo, Quotes, Candles, WebSocket Quote и WebSocket OHLC.
---
# Архитектурный результат
После Build 060.4 REST Trades pipeline выглядит так:
```text
REST JSON
validate_rest_agg_trades_schema()
ValidatedRestAggTradesDocument
parse_rest_agg_trades()
tuple[DzengiRestAggTrade, ...]
```
Теперь внешний REST-документ структурно проверен, типизирован, преобразован в transport-модели, защищён от изменения и готов к Value Validation.
---
# Итог
Build 060.4 завершает parser-этап REST Trades Acquisition Pipeline.
Система получила строго типизированное преобразование:
```text
ValidatedRestAggTradesDocument
DzengiRestAggTrade
```
При этом сохранены архитектурные границы:
- Schema Validation проверяет структуру;
- Parser проверяет transport-типы;
- Value Validation будет проверять допустимость значений;
- Mapper будет строить canonical `Trade`.
Следующим этапом серии должен стать **Build 060.5 — REST Trade Value Validation**.