Files
dzentra_bot/docs/migrations/build_060_4.md

13 KiB
Raw Blame History

Build 060.4 — REST Trade Parser

Статус

Завершён


Цель Build

После завершения Build 060.3 система уже умела принимать REST-ответ endpoint /api/v2/aggTrades, проверять его структуру и преобразовывать в immutable-контракт:

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:

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:

parse_rest_agg_trades(
    document: ValidatedRestAggTradesDocument,
) -> tuple[DzengiRestAggTrade, ...]

Parser работает со всем REST-документом целиком и не принимает произвольный dict или list. Его входной контракт уже подтверждает, что документ прошёл Schema Validation.

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 добавлено исключение:

class TradeParseError(MarketDataAcquisitionError):
    pass

Оно используется только для ошибок преобразования структурно проверенного REST Trade документа в transport-модели Dzengi.

Build сознательно не добавляет TradeValueError, TradeMappingError, TradeTransportError и TradeFeedRegistryError. Каждое исключение должно появляться только в Build, реализующем соответствующий слой pipeline.


Входной контракт

Parser принимает:

ValidatedRestAggTradesDocument

Контракт был добавлен в Build 060.3:

@dataclass(frozen=True, slots=True)
class ValidatedRestAggTradesDocument:
    items: tuple[Mapping[str, object], ...]

Каждый элемент items уже является mapping, содержит обязательные поля, имеет строковые ключи и защищён от изменения через MappingProxyType. Parser не повторяет Schema Validation.


Выходной контракт

Parser возвращает:

tuple[DzengiRestAggTrade, ...]

Каждый элемент является immutable transport-моделью:

@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

Основная функция:

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;
  • pstr | int | float, но не bool;
  • qstr | 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 формирует путь к каждому полю с учётом индекса элемента.

Например, ошибка цены во второй сделке будет представлена как:

$[1].p должен быть строкой или числом

Это позволяет точно определить индекс некорректной сделки и имя поля.


Отдельные Trade helper-функции

В Build добавлены специализированные helper-функции:

_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:

46 passed

Полная регрессия проекта:

1038 passed

Дополнительно успешно выполнены:

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 не затрагивает:

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 выглядит так:

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.

Система получила строго типизированное преобразование:

ValidatedRestAggTradesDocument
            ↓
DzengiRestAggTrade

При этом сохранены архитектурные границы:

  • Schema Validation проверяет структуру;
  • Parser проверяет transport-типы;
  • Value Validation будет проверять допустимость значений;
  • Mapper будет строить canonical Trade.

Следующим этапом серии должен стать Build 060.5 — REST Trade Value Validation.