From 6d121abe96b30f29dd5fc30acf1ef1572162e551 Mon Sep 17 00:00:00 2001 From: Sergey Date: Sun, 19 Jul 2026 20:12:34 +0300 Subject: [PATCH] Build 060.12: add WebSocket Trade Value Validation --- .../acquisition/validation/values.py | 39 + .../validation/test_websocket_trade_values.py | 275 ++++ docs/migrations/build_060_12.md | 1102 +++++++++++++++++ 3 files changed, 1416 insertions(+) create mode 100644 app/tests/unit/market_data/acquisition/validation/test_websocket_trade_values.py create mode 100644 docs/migrations/build_060_12.md diff --git a/app/src/market_data/acquisition/validation/values.py b/app/src/market_data/acquisition/validation/values.py index d2ff223..7d51a33 100644 --- a/app/src/market_data/acquisition/validation/values.py +++ b/app/src/market_data/acquisition/validation/values.py @@ -18,6 +18,7 @@ from src.market_data.acquisition.adapters.dzengi.models import ( DzengiTicker24hrResponse, DzengiWebSocketOhlcEvent, DzengiWebSocketQuoteResponse, + DzengiWebSocketTradeEvent, ) from src.market_data.acquisition.exceptions import ( CandleValueError, @@ -820,6 +821,44 @@ def _candle_decimal( return decimal_value +def validate_dzengi_websocket_trade_values( + event: DzengiWebSocketTradeEvent, +) -> None: + """ + Проверить допустимость значений transport-модели Dzengi WebSocket Trade. + + Функция не изменяет transport-модель, не преобразует raw numeric + значения в Decimal и не выполняет mapping во внутреннюю модель Trade. + """ + + _trade_positive_int( + event.trade_id, + path="$.payload.id", + ) + _trade_positive_decimal( + event.price, + path="$.payload.price", + ) + _trade_positive_decimal( + event.size, + path="$.payload.size", + ) + _trade_positive_int( + event.timestamp, + path="$.payload.ts", + ) + + if not event.symbol.strip(): + raise TradeValueError( + "$.payload.symbol не должен быть пустым." + ) + + if not event.order_id.strip(): + raise TradeValueError( + "$.payload.orderId не должен быть пустым." + ) + + def validate_rest_agg_trade_values( trades: tuple[DzengiRestAggTrade, ...], ) -> None: diff --git a/app/tests/unit/market_data/acquisition/validation/test_websocket_trade_values.py b/app/tests/unit/market_data/acquisition/validation/test_websocket_trade_values.py new file mode 100644 index 0000000..975d5c6 --- /dev/null +++ b/app/tests/unit/market_data/acquisition/validation/test_websocket_trade_values.py @@ -0,0 +1,275 @@ +# app/tests/unit/market_data/acquisition/validation/test_websocket_trade_values.py + +from __future__ import annotations + +import pytest + +from src.market_data.acquisition.adapters.dzengi.models import ( + DzengiWebSocketTradeEvent, +) +from src.market_data.acquisition.exceptions import TradeValueError +from src.market_data.acquisition.validation.values import ( + validate_dzengi_websocket_trade_values, +) + + +def _event( + **overrides: object, +) -> DzengiWebSocketTradeEvent: + values: dict[str, object] = { + "trade_id": 123456, + "price": "63992.50", + "size": "0.125", + "timestamp": 1784224740000, + "symbol": "BTC/USD_LEVERAGE", + "buyer": True, + "order_id": "order-123456", + } + values.update(overrides) + + return DzengiWebSocketTradeEvent( + trade_id=values["trade_id"], # type: ignore[arg-type] + price=values["price"], # type: ignore[arg-type] + size=values["size"], # type: ignore[arg-type] + timestamp=values["timestamp"], # type: ignore[arg-type] + symbol=values["symbol"], # type: ignore[arg-type] + buyer=values["buyer"], # type: ignore[arg-type] + order_id=values["order_id"], # type: ignore[arg-type] + ) + + +@pytest.mark.parametrize( + "buyer", + [ + True, + False, + ], +) +def test_validate_websocket_trade_values_accepts_buyer_values( + buyer: bool, +) -> None: + validate_dzengi_websocket_trade_values( + _event(buyer=buyer) + ) + + +@pytest.mark.parametrize( + ("field_name", "value"), + [ + ("price", "63992.50"), + ("price", 63992), + ("price", 63992.5), + ("size", "0.125"), + ("size", 1), + ("size", 0.125), + ], +) +def test_validate_websocket_trade_values_accepts_numeric_representations( + field_name: str, + value: object, +) -> None: + validate_dzengi_websocket_trade_values( + _event(**{field_name: value}) + ) + + +def test_validate_websocket_trade_values_does_not_modify_event() -> None: + event = _event() + original = event + + validate_dzengi_websocket_trade_values(event) + + assert event == original + + +@pytest.mark.parametrize( + "trade_id", + [ + 0, + -1, + -123456, + ], +) +def test_validate_websocket_trade_values_rejects_non_positive_trade_id( + trade_id: int, +) -> None: + with pytest.raises( + TradeValueError, + match=r"\$\.payload\.id должно быть целым числом больше нуля", + ): + validate_dzengi_websocket_trade_values( + _event(trade_id=trade_id) + ) + + +@pytest.mark.parametrize( + "timestamp", + [ + 0, + -1, + -1784224740000, + ], +) +def test_validate_websocket_trade_values_rejects_non_positive_timestamp( + timestamp: int, +) -> None: + with pytest.raises( + TradeValueError, + match=r"\$\.payload\.ts должно быть целым числом больше нуля", + ): + validate_dzengi_websocket_trade_values( + _event(timestamp=timestamp) + ) + + +@pytest.mark.parametrize( + "field_name", + [ + "price", + "size", + ], +) +@pytest.mark.parametrize( + "value", + [ + 0, + -1, + "-0.01", + ], +) +def test_validate_websocket_trade_values_rejects_non_positive_numeric_values( + field_name: str, + value: object, +) -> None: + path_by_field = { + "price": "price", + "size": "size", + } + + with pytest.raises( + TradeValueError, + match=( + rf"\$\.payload\.{path_by_field[field_name]} " + r"должно быть больше нуля" + ), + ): + validate_dzengi_websocket_trade_values( + _event(**{field_name: value}) + ) + + +@pytest.mark.parametrize( + "field_name", + [ + "price", + "size", + ], +) +@pytest.mark.parametrize( + "value", + [ + "NaN", + "Infinity", + "-Infinity", + float("nan"), + float("inf"), + float("-inf"), + ], +) +def test_validate_websocket_trade_values_rejects_non_finite_numeric_values( + field_name: str, + value: object, +) -> None: + path_by_field = { + "price": "price", + "size": "size", + } + + with pytest.raises( + TradeValueError, + match=( + rf"\$\.payload\.{path_by_field[field_name]} " + r"должно быть конечным числом" + ), + ): + validate_dzengi_websocket_trade_values( + _event(**{field_name: value}) + ) + + +@pytest.mark.parametrize( + "field_name", + [ + "price", + "size", + ], +) +@pytest.mark.parametrize( + "value", + [ + "", + "invalid", + "--1", + ], +) +def test_validate_websocket_trade_values_rejects_invalid_numeric_strings( + field_name: str, + value: str, +) -> None: + path_by_field = { + "price": "price", + "size": "size", + } + + with pytest.raises( + TradeValueError, + match=( + rf"\$\.payload\.{path_by_field[field_name]} " + r"должно быть корректным числом" + ), + ): + validate_dzengi_websocket_trade_values( + _event(**{field_name: value}) + ) + + +@pytest.mark.parametrize( + "symbol", + [ + "", + " ", + "\t", + "\n", + ], +) +def test_validate_websocket_trade_values_rejects_empty_symbol( + symbol: str, +) -> None: + with pytest.raises( + TradeValueError, + match=r"\$\.payload\.symbol не должен быть пустым", + ): + validate_dzengi_websocket_trade_values( + _event(symbol=symbol) + ) + + +@pytest.mark.parametrize( + "order_id", + [ + "", + " ", + "\t", + "\n", + ], +) +def test_validate_websocket_trade_values_rejects_empty_order_id( + order_id: str, +) -> None: + with pytest.raises( + TradeValueError, + match=r"\$\.payload\.orderId не должен быть пустым", + ): + validate_dzengi_websocket_trade_values( + _event(order_id=order_id) + ) \ No newline at end of file diff --git a/docs/migrations/build_060_12.md b/docs/migrations/build_060_12.md new file mode 100644 index 0000000..66535ae --- /dev/null +++ b/docs/migrations/build_060_12.md @@ -0,0 +1,1102 @@ +# Build 060.12 — WebSocket Trade Value Validation + +**Engineering Migration Report** + +--- + +# Контроль документа + +| Свойство | Значение | +|----------|----------| +| Build | 060.12 | +| Название | WebSocket Trade Value Validation | +| Статус | Completed | +| Проект | Dzentra | +| Подсистема | Market Data Acquisition | +| Компонент | Trades Feed | +| Версия | 1.0 | + +--- + +# Цель Build + +После завершения Build 060.11 система получила полностью реализованный уровень **WebSocket Trade Parser**, преобразующий структурно корректный документ + +```text +ValidatedWebSocketTradeDocument +``` + +в транспортную модель + +```text +DzengiWebSocketTradeEvent +``` + +Parser гарантирует корректное построение транспортного объекта и минимальную проверку типов, необходимую для создания модели. + +Однако после завершения Parsing транспортная модель ещё не может считаться пригодной для дальнейшего использования в системе. + +Несмотря на корректность структуры и типов, отдельные значения транспортной модели могут оставаться недопустимыми с точки зрения предметной области. + +Например: + +- цена сделки может быть равна нулю; +- объём сделки может быть отрицательным; +- timestamp может иметь недопустимое значение; +- идентификатор сделки может отсутствовать либо быть неположительным; +- строковые поля могут содержать только пробельные символы; +- числовые значения могут содержать `NaN` или бесконечность. + +Подобные ошибки уже не относятся к структуре документа и не должны обрабатываться Parser. + +Для их обработки в архитектуре Dzentra предусмотрен отдельный уровень Pipeline — **Value Validation**. + +Build 060.12 реализует данный уровень для WebSocket Trade. + +Основная задача Build — проверить корректность содержимого транспортной модели без выполнения каких-либо преобразований данных и без создания новых объектов. + +Данный Build ограничивается исключительно проверкой значений транспортной модели и не затрагивает: + +- Schema Validation; +- Parser; +- Mapper; +- Runtime; +- Routing; +- Trades Feed. + +--- + +# Предпосылки + +К началу Build архитектура подсистемы Market Data Acquisition уже содержала полноценный конвейер обработки транспортных моделей Quote, OHLC и REST Trade. + +Для каждого из них использовалось одинаковое разделение ответственности между уровнями Pipeline. + +Общая последовательность обработки выглядела следующим образом. + +```text +Raw Source + │ + ▼ +Schema Validation + │ + ▼ +Transport Model + │ + ▼ +Value Validation + │ + ▼ +Mapper + │ + ▼ +Domain Model +``` + +После завершения Build 060.11 аналогичный транспортный уровень появился и для WebSocket Trade. + +```text +ValidatedWebSocketTradeDocument + │ + ▼ +Trade Parser + │ + ▼ +DzengiWebSocketTradeEvent +``` + +Однако следующий обязательный этап — проверка корректности самих значений транспортной модели — ещё отсутствовал. + +Таким образом WebSocket-конвейер обработки сделок оставался архитектурно незавершённым. + +--- + +# Архитектурное основание + +Одним из базовых принципов архитектуры Dzentra является строгое разделение ответственности между последовательными уровнями Pipeline. + +Каждый уровень отвечает только за одну категорию задач. + +Для WebSocket Trade это разделение выглядит следующим образом. + +```text +Schema Validation +``` + +отвечает исключительно за проверку структуры документа. + +Она гарантирует: + +- наличие транспортной оболочки; +- наличие объекта `payload`; +- присутствие обязательных полей; +- соответствие ожидаемым типам транспортного документа. + +Следующий уровень — + +```text +Parser +``` + +выполняет транспортное преобразование. + +Он: + +- извлекает значения из `payload`; +- переименовывает транспортные поля; +- создаёт immutable transport model. + +После этого ответственность Parser полностью заканчивается. + +Проверка корректности самих значений транспортной модели относится уже к следующему уровню — + +```text +Value Validation +``` + +Именно Value Validation отвечает за проверку предметной допустимости данных. + +На данном уровне анализируются: + +- допустимость числовых значений; +- диапазоны значений; +- корректность строковых идентификаторов; +- невозможность использования специальных числовых значений (`NaN`, `Infinity`); +- другие ограничения транспортного контракта. + +При этом Value Validation принципиально **не выполняет**: + +- преобразование типов; +- Mapping; +- создание модели `Trade`; +- бизнес-логику; +- обработку Runtime. + +Такое разделение позволяет каждому уровню Pipeline оставаться независимым и легко тестируемым. + +--- + +# Результаты архитектурного аудита + +Перед реализацией Build был выполнен аудит существующей подсистемы проверки значений. + +В ходе анализа подтверждено наличие следующих компонентов. + +Для Quote уже реализованы: + +```text +validate_dzengi_websocket_quote_values(...) +``` + +Для OHLC реализованы: + +```text +validate_dzengi_websocket_ohlc_values(...) +``` + +Для REST Trade реализованы: + +```text +validate_rest_agg_trade_values(...) +``` + +Также подтверждено существование общего набора вспомогательных функций проверки: + +```text +_trade_positive_int(...) + +_trade_positive_decimal(...) +``` + +Указанные helper-функции уже используются существующей реализацией REST Trade и полностью соответствуют требованиям нового Build. + +Одновременно аудит подтвердил отсутствие отдельной проверки значений транспортной модели + +```text +DzengiWebSocketTradeEvent +``` + +Таким образом единственным отсутствующим элементом архитектурной цепочки являлся собственный уровень Value Validation для WebSocket Trade. + +Build 060.12 полностью закрывает данный пробел и завершает ещё один архитектурный уровень серии Build 060. + +--- + +# Архитектурное решение + +По итогам проведённого аудита было принято решение не создавать новую модель данных и не вводить дополнительный слой между Parser и Mapper. + +Вместо этого реализован тот же архитектурный шаблон, который уже используется для Quote, OHLC и REST Trade. + +В систему добавлена функция + +```text +validate_dzengi_websocket_trade_values(...) +``` + +которая принимает + +```text +DzengiWebSocketTradeEvent +``` + +и выполняет проверку корректности значений транспортной модели. + +При успешном завершении проверки объект не изменяется и продолжает использоваться последующими этапами Pipeline. + +Таким образом транспортная модель проходит дополнительный уровень контроля без создания промежуточных объектов и без нарушения существующей архитектуры. + +Конвейер WebSocket Trade принимает следующий вид. + +```text +Raw WebSocket Object + │ + ▼ +WebSocket Trade Schema Validation + │ + ▼ +ValidatedWebSocketTradeDocument + │ + ▼ +WebSocket Trade Parser + │ + ▼ +DzengiWebSocketTradeEvent + │ + ▼ +WebSocket Trade Value Validation + │ + ▼ +DzengiWebSocketTradeEvent +``` + +Build 060.12 не изменяет архитектуру ранее реализованных компонентов и лишь завершает следующий обязательный уровень транспортного Pipeline. + +# Реализованный уровень Value Validation + +В файл + +```text +src/market_data/acquisition/validation/values.py +``` + +добавлена новая функция + +```text +validate_dzengi_websocket_trade_values(...) +``` + +Функция получает транспортную модель + +```text +DzengiWebSocketTradeEvent +``` + +и подтверждает корректность всех значений, необходимых для последующего Mapping. + +После успешного завершения проверки функция не изменяет объект и не создаёт новую транспортную модель. + +Таким образом следующий уровень Pipeline получает тот же экземпляр + +```text +DzengiWebSocketTradeEvent +``` + +который ранее был создан Parser. + +--- + +# Почему Value Validation не создаёт новую модель + +Во время архитектурного проектирования отдельно рассматривался вопрос о необходимости введения дополнительной модели + +```text +ValidatedTradeTransportEvent +``` + +которая могла бы использоваться после проверки значений. + +По результатам анализа было принято решение отказаться от подобного решения. + +Основные причины: + +- транспортная модель уже является immutable; +- проверка значений не изменяет содержимое объекта; +- повторное создание объекта не приносит дополнительных архитектурных преимуществ; +- аналогичный подход уже используется для Quote, OHLC и REST Trade. + +В результате Value Validation подтверждает корректность существующего объекта и не создаёт новый экземпляр. + +Подобное решение уменьшает количество транспортных моделей в системе и делает Pipeline более простым без потери архитектурной строгости. + +--- + +# Проверяемая транспортная модель + +Проверка выполняется над объектом + +```python +@dataclass(frozen=True, slots=True) +class DzengiWebSocketTradeEvent: + trade_id: int + price: DzengiRawNumeric + size: DzengiRawNumeric + timestamp: int + symbol: str + buyer: bool + order_id: str +``` + +Value Validation рассматривает данный объект исключительно как транспортную модель. + +Никаких преобразований типов при этом не выполняется. + +--- + +# Проверка идентификатора сделки + +Поле + +```text +trade_id +``` + +обязательно должно содержать положительный целочисленный идентификатор сделки. + +Во время проверки подтверждается: + +- значение является целым числом; +- значение больше нуля. + +При нарушении любого условия генерируется + +```text +TradeValueError +``` + +с указанием пути + +```text +$.payload.id +``` + +--- + +# Проверка цены сделки + +Поле + +```text +price +``` + +может поступать в различных транспортных представлениях. + +Например: + +```text +"63992.50" + +63992 + +63992.50 +``` + +Во время проверки подтверждается: + +- возможность корректного преобразования в Decimal; +- отсутствие NaN; +- отсутствие Infinity; +- значение больше нуля. + +При этом сама транспортная модель не изменяется. + +Строковое значение остаётся строковым. + +Преобразование в Decimal будет выполняться только на этапе Mapper. + +--- + +# Проверка объёма сделки + +Поле + +```text +size +``` + +проверяется аналогично цене. + +Подтверждается: + +- корректность числового представления; +- отсутствие специальных значений; +- положительное значение. + +При нарушении любого ограничения генерируется + +```text +TradeValueError +``` + +с указанием пути + +```text +$.payload.size +``` + +--- + +# Проверка временной метки + +Поле + +```text +timestamp +``` + +должно содержать положительное целое число. + +Value Validation подтверждает: + +- корректность типа; +- значение больше нуля. + +Следует отметить, что Build 060.12 **не анализирует**, соответствует ли timestamp реальному времени. + +Подобные проверки относятся уже к предметной области и могут появиться на более высоких уровнях системы. + +--- + +# Проверка символа + +Поле + +```text +symbol +``` + +обязательно должно содержать непустую строку. + +Проверяется результат после применения + +```python +strip() +``` + +Таким образом значения + +```text +"" + +" " + +"\t" + +"\n" +``` + +считаются недопустимыми. + +--- + +# Проверка идентификатора ордера + +Аналогичная проверка выполняется для поля + +```text +order_id +``` + +После удаления пробельных символов строка должна оставаться непустой. + +В противном случае генерируется + +```text +TradeValueError +``` + +--- + +# Почему поле buyer не проверяется + +Во время архитектурного аудита отдельно анализировался вопрос дополнительной проверки поля + +```text +buyer +``` + +Было принято решение не выполнять каких-либо дополнительных ограничений. + +Причины следующие. + +Parser уже гарантирует: + +```text +bool +``` + +В транспортном контракте биржи оба значения + +```text +True + +False +``` + +являются допустимыми. + +Следовательно Value Validation не содержит никакой дополнительной логики для данного поля. + +Это полностью соответствует принципу разделения ответственности между Parser и Value Validation. + +--- + +# Повторное использование существующих helper-функций + +Build 060.12 не вводит новых механизмов проверки числовых значений. + +Вместо этого используются уже существующие функции проекта. + +```text +_trade_positive_int(...) +``` + +используется для проверки: + +- trade_id; +- timestamp. + +--- + +```text +_trade_positive_decimal(...) +``` + +используется для проверки: + +- price; +- size. + +Подобный подход обеспечивает единое поведение всех механизмов проверки Trade независимо от источника получения данных. + +--- + +# Использование TradeValueError + +Все ошибки проверки значений используют существующее исключение + +```text +TradeValueError +``` + +Build не вводит новых типов исключений. + +Это сохраняет единую архитектуру обработки ошибок Trade. + +Parser продолжает использовать + +```text +TradeParseError +``` + +а Value Validation использует исключительно + +```text +TradeValueError +``` + +Тем самым достигается чёткое разделение транспортных ошибок и ошибок корректности данных. + +--- + +# Изменённые файлы + +В рамках Build были изменены только два файла. + +## Value Validation + +```text +src/market_data/acquisition/validation/values.py +``` + +Добавлена функция + +```text +validate_dzengi_websocket_trade_values(...) +``` + +Существующая логика проверки Quote, OHLC и REST Trade не изменялась. + +--- + +## Unit-тесты + +```text +tests/unit/market_data/acquisition/validation/test_websocket_trade_values.py +``` + +Добавлен полный набор unit-тестов нового уровня Value Validation. + +--- + +# Добавленные тесты + +В рамках Build реализовано сорок семь unit-тестов, полностью покрывающих новую функциональность. + +## Проверка корректных событий + +Подтверждается успешное прохождение проверки полностью корректной транспортной модели. + +--- + +## Проверка buyer + +Подтверждается корректная работа для обоих допустимых значений: + +- `True`; +- `False`. + +--- + +## Проверка различных представлений чисел + +Отдельная серия тестов подтверждает корректную обработку: + +- строк; +- целых чисел; +- чисел с плавающей точкой. + +для полей: + +- price; +- size. + +--- + +## Проверка неположительных значений + +Реализованы параметризованные тесты для: + +- trade_id; +- timestamp; +- price; +- size. + +Подтверждается генерация + +```text +TradeValueError +``` + +при попытке использования нуля либо отрицательных значений. + +--- + +## Проверка специальных числовых значений + +Отдельная группа тестов подтверждает отклонение: + +```text +NaN + +Infinity + +-Infinity +``` + +как в строковом виде, так и после передачи соответствующих значений типа float. + +--- + +## Проверка некорректных строк + +Реализованы проверки для значений: + +```text +"" + +"invalid" + +"--1" +``` + +Подтверждается корректная генерация исключений. + +--- + +## Проверка строковых полей + +Отдельные тесты подтверждают отклонение пустых либо содержащих только пробельные символы значений: + +- symbol; +- order_id. + +--- + +## Проверка неизменяемости транспортной модели + +Отдельный тест подтверждает, что после успешной проверки объект + +```text +DzengiWebSocketTradeEvent +``` + +остаётся полностью неизменным. + +Value Validation не модифицирует транспортную модель. + +# Результаты тестирования + +После завершения реализации выполнен целевой запуск нового набора unit-тестов. + +```bash +python -m pytest \ + tests/unit/market_data/acquisition/validation/test_websocket_trade_values.py \ + -q +``` + +Результат: + +```text +47 passed in 0.05s +``` + +Все проверки новой функциональности успешно завершены. + +Новый набор тестов полностью покрывает: + +- успешную проверку корректной транспортной модели; +- обработку допустимых представлений числовых значений; +- генерацию исключений для всех типов некорректных данных; +- неизменяемость транспортной модели после успешной проверки. + +Параметризованные тесты позволили существенно сократить объём тестового кода без уменьшения покрытия и обеспечили единообразную проверку всех допустимых и недопустимых вариантов входных данных. + +--- + +# Регрессионное тестирование + +После завершения реализации выполнен полный запуск набора unit-тестов проекта. + +```bash +python -m pytest -q +``` + +Результат: + +```text +1208 passed in 2.70s +``` + +Регрессий существующей функциональности не обнаружено. + +Все ранее реализованные Build продолжают работать без каких-либо изменений. + +Это подтверждает, что добавленная функциональность полностью изолирована и не влияет на существующие конвейеры обработки Quote, OHLC, REST Trade и остальные подсистемы проекта. + +--- + +# Проверка компиляции + +После завершения реализации выполнена полная проверка компиляции проекта. + +```bash +python -m compileall src tests +``` + +Компиляция завершилась успешно. + +Ошибок синтаксиса не обнаружено. + +Все изменённые файлы успешно компилируются и не нарушают целостность проекта. + +--- + +# Проверка Git diff + +После устранения замечаний форматирования выполнена финальная проверка изменений. + +```bash +git diff --check +``` + +Результат: + +```text +без замечаний +``` + +Проверка подтвердила отсутствие: + +- trailing whitespace; +- ошибок окончания строк; +- конфликтов diff; +- нарушений форматирования. + +--- + +# Scope Build 060.12 + +В рамках данного Build реализован исключительно уровень + +```text +WebSocket Trade Value Validation +``` + +Build **не включает**: + +- WebSocket Trade Schema Validation; +- WebSocket Trade Parser; +- WebSocket Trade Mapper; +- WebSocket Trade Adapter; +- Runtime Integration; +- Unified Routing; +- Trades Feed. + +Подобное ограничение полностью соответствует принятому принципу атомарной реализации Build. + +Каждый этап дорожной карты реализует только один архитектурный уровень Pipeline. + +--- + +# Архитектурный результат + +После завершения Build система содержит полностью реализованные уровни транспортной обработки WebSocket Trade вплоть до проверки значений. + +Конвейер обработки принимает следующий вид. + +```text +Raw WebSocket Trade + │ + ▼ +WebSocket Trade Schema Validation + │ + ▼ +ValidatedWebSocketTradeDocument + │ + ▼ +WebSocket Trade Parser + │ + ▼ +DzengiWebSocketTradeEvent + │ + ▼ +WebSocket Trade Value Validation + │ + ▼ +DzengiWebSocketTradeEvent +``` + +Таким образом архитектура WebSocket Trade полностью повторяет ранее реализованные конвейеры Quote и OHLC. + +Каждый уровень Pipeline выполняет исключительно собственную задачу. + +--- + +# Состояние WebSocket Trade Pipeline + +После завершения Build 060.12 конвейер имеет следующий вид. + +```text +Raw WebSocket Trade + │ + ▼ +ValidatedWebSocketTradeDocument + │ + ▼ +DzengiWebSocketTradeEvent + │ + ▼ +Value Validation + │ + ▼ +Mapper + │ + ▼ +Trade +``` + +Статус реализации компонентов: + +| Компонент | Build | Статус | +|-----------|-------|--------| +| Canonical Trade Model | 060.1 | ✔ Completed | +| WebSocket Trade Transport Model | 060.9 | ✔ Completed | +| WebSocket Trade Schema Validation | 060.10 | ✔ Completed | +| WebSocket Trade Parser | 060.11 | ✔ Completed | +| WebSocket Trade Value Validation | 060.12 | ✔ Completed | +| WebSocket Trade Mapper | 060.13 | Pending | +| WebSocket Trade Adapter | 060.14 | Pending | +| Unified WebSocket Routing | 060.15 | Pending | + +--- + +# Соблюдение архитектурных принципов + +В рамках Build полностью сохранены архитектурные инварианты Dzentra. + +## Локальность изменений + +Изменены только: + +- `validation/values.py`; +- unit-тесты нового уровня Value Validation. + +Существующая логика Quote, OHLC и REST Trade не изменялась. + +--- + +## Повторное использование архитектуры + +Новая реализация полностью повторяет существующий архитектурный шаблон проверки значений. + +Новая архитектура не проектировалась. + +Использован уже существующий подход, применяемый для остальных типов рыночных данных. + +--- + +## Повторное использование инфраструктуры + +Для проверки числовых значений повторно использованы существующие helper-функции проекта. + +Build не вводит новых механизмов проверки и не дублирует уже реализованную функциональность. + +Это обеспечивает единообразие поведения всех уровней Value Validation. + +--- + +## Разделение ответственности + +Value Validation отвечает исключительно за проверку корректности значений транспортной модели. + +Build не выполняет: + +- преобразование транспортных данных; +- Mapping; +- создание модели Trade; +- Runtime Integration; +- бизнес-логику. + +Все перечисленные задачи остаются ответственностью последующих Build. + +--- + +## Обратная совместимость + +Существующая обработка: + +- Quote; +- OHLC; +- REST Trade; + +не изменилась. + +Добавленная функциональность полностью изолирована и не оказывает влияния на ранее реализованные компоненты системы. + +--- + +# Архитектурные решения Build (ADR) + +## ADR-060.12-001 + +**Value Validation не создаёт новую транспортную модель.** + +После успешной проверки используется тот же экземпляр `DzengiWebSocketTradeEvent`, который ранее был создан Parser. + +Создание дополнительной модели признано избыточным и не дающим архитектурных преимуществ. + +--- + +## ADR-060.12-002 + +**Value Validation не выполняет преобразование типов.** + +Проверка подтверждает только допустимость значений. + +Преобразование транспортных представлений (`str`, `int`, `float`) во внутренние типы (`Decimal`, `datetime` и другие) остаётся ответственностью Mapper. + +--- + +## ADR-060.12-003 + +**Повторное использование существующих helper-функций является обязательным архитектурным принципом.** + +Для проверки числовых значений используются существующие функции: + +```text +_trade_positive_int() + +_trade_positive_decimal() +``` + +Создание новых helper-функций признано необоснованным. + +--- + +## ADR-060.12-004 + +**Parser и Value Validation используют разные классы исключений.** + +Parser отвечает за транспортное преобразование и использует: + +```text +TradeParseError +``` + +Value Validation отвечает исключительно за корректность значений и использует: + +```text +TradeValueError +``` + +Подобное разделение обеспечивает прозрачную классификацию ошибок Pipeline. + +--- + +# Критерии завершения Build + +Build 060.12 считается завершённым, поскольку выполнены все поставленные задачи. + +- ✔ реализована функция `validate_dzengi_websocket_trade_values()`; +- ✔ реализована проверка всех обязательных полей транспортной модели; +- ✔ реализована проверка положительных числовых значений; +- ✔ реализована проверка конечности числовых значений; +- ✔ реализована проверка строковых идентификаторов; +- ✔ повторно использованы существующие helper-функции; +- ✔ используются существующие исключения `TradeValueError`; +- ✔ транспортная модель остаётся неизменяемой; +- ✔ реализовано 47 unit-тестов; +- ✔ целевой набор тестов успешно проходит; +- ✔ полное регрессионное тестирование успешно завершено; +- ✔ проект успешно компилируется; +- ✔ `git diff --check` не выявил замечаний; +- ✔ изменения полностью укладываются в согласованный scope Build. + +--- + +# Следующий этап + +Следующим этапом дорожной карты является + +```text +Build 060.13 — WebSocket Trade Mapper +``` + +Цель следующего Build: + +- преобразование `DzengiWebSocketTradeEvent` в каноническую модель `Trade`; +- преобразование транспортных числовых значений во внутренние типы; +- формирование окончательной модели предметной области; +- завершение транспортного конвейера обработки WebSocket Trade. + +--- + +# Итог + +Build 060.12 завершил реализацию уровня **Value Validation** для WebSocket Trade и сделал архитектуру транспортной обработки сделок полностью симметричной существующим конвейерам Quote, OHLC и REST Trade. + +Новая реализация основана на уже существующих архитектурных принципах Dzentra, повторно использует существующую инфраструктуру проверки значений, не создаёт дополнительных транспортных моделей и сохраняет строгое разделение ответственности между уровнями Pipeline. + +Build ограничен согласованным scope, успешно прошёл целевое и полное регрессионное тестирование, подтвердил отсутствие регрессий и создаёт необходимую основу для следующего этапа дорожной карты — **Build 060.13 — WebSocket Trade Mapper**. \ No newline at end of file