2226 lines
60 KiB
Markdown
2226 lines
60 KiB
Markdown
# Build 060.6 — REST Trade Mapper
|
||
|
||
**Engineering Migration Report**
|
||
|
||
---
|
||
|
||
# Контроль документа
|
||
|
||
| Свойство | Значение |
|
||
|---|---|
|
||
| Build | 060.6 |
|
||
| Название | REST Trade Mapper |
|
||
| Статус | Завершён |
|
||
| Проект | Dzentra |
|
||
| Подсистема | Market Data Acquisition |
|
||
| Компонент | Trades Feed / Time & Sales |
|
||
| Версия документа | 1.0 |
|
||
| Дата завершения | 2026-07-19 |
|
||
|
||
---
|
||
|
||
# Цель Build
|
||
|
||
После завершения предыдущих Build серии 060 в системе уже существовали:
|
||
|
||
- транспортная модель агрегированной сделки REST API;
|
||
- проверка структуры REST-документа;
|
||
- parser транспортной модели;
|
||
- проверка корректности значений transport-объектов.
|
||
|
||
Однако весь полученный конвейер всё ещё работал исключительно с транспортными сущностями биржи.
|
||
|
||
После окончания Build 060.5 полный pipeline выглядел следующим образом:
|
||
|
||
```text
|
||
REST JSON
|
||
|
||
│
|
||
|
||
▼
|
||
|
||
Schema Validation
|
||
|
||
│
|
||
|
||
▼
|
||
|
||
Parser
|
||
|
||
│
|
||
|
||
▼
|
||
|
||
tuple[DzengiRestAggTrade]
|
||
|
||
│
|
||
|
||
▼
|
||
|
||
Value Validation
|
||
```
|
||
|
||
Полученный результат был пригоден только для внутренних компонентов Acquisition Layer.
|
||
|
||
Остальная система Dzentra не должна зависеть от формата REST API конкретной биржи.
|
||
|
||
Следовательно необходим последний уровень преобразования, переводящий транспортную модель во внутреннюю предметную модель проекта.
|
||
|
||
Именно эту задачу решает Build 060.6.
|
||
|
||
В рамках данного Build реализуется исключительно Mapper.
|
||
|
||
Он завершает построение первого полноценного REST Pipeline для получения исторических сделок.
|
||
|
||
Build намеренно **не включает**:
|
||
|
||
- REST client;
|
||
- REST endpoint;
|
||
- REST polling;
|
||
- Feed;
|
||
- Registry;
|
||
- Runtime integration;
|
||
- Acquisition Service;
|
||
- Time & Sales Feed;
|
||
- хранение истории сделок;
|
||
- дедупликацию;
|
||
- сортировку;
|
||
- агрегацию;
|
||
- объединение REST и WebSocket Trades;
|
||
- production-интеграцию.
|
||
|
||
Все перечисленные задачи относятся к последующим Build.
|
||
|
||
---
|
||
|
||
# Архитектурный контекст
|
||
|
||
В Dzentra принята единая многоуровневая модель обработки любых внешних рыночных данных.
|
||
|
||
Независимо от источника данные проходят одинаковые стадии обработки.
|
||
|
||
```text
|
||
Transport
|
||
|
||
│
|
||
|
||
▼
|
||
|
||
Schema Validation
|
||
|
||
│
|
||
|
||
▼
|
||
|
||
Parser
|
||
|
||
│
|
||
|
||
▼
|
||
|
||
Value Validation
|
||
|
||
│
|
||
|
||
▼
|
||
|
||
Mapper
|
||
|
||
│
|
||
|
||
▼
|
||
|
||
Canonical Model
|
||
```
|
||
|
||
Такая архитектура уже используется для:
|
||
|
||
- Instrument;
|
||
- Quote;
|
||
- Candle;
|
||
- WebSocket Candle.
|
||
|
||
Build 060.6 переносит этот же принцип на REST Trades.
|
||
|
||
После его завершения REST Trade Pipeline становится полностью согласованным с остальными Acquisition Pipeline проекта.
|
||
|
||
---
|
||
|
||
# Исходное состояние
|
||
|
||
До начала Build 060.6 проект уже содержал:
|
||
|
||
```text
|
||
Canonical Trade
|
||
|
||
DzengiRestAggTrade
|
||
|
||
TradeSchemaError
|
||
|
||
TradeParseError
|
||
|
||
TradeValueError
|
||
|
||
validate_rest_agg_trades_schema()
|
||
|
||
parse_rest_agg_trades()
|
||
|
||
validate_rest_agg_trade_values()
|
||
```
|
||
|
||
Таким образом все необходимые проверки транспортного уровня уже были реализованы.
|
||
|
||
Отсутствовал только переход к внутренней модели.
|
||
|
||
Файл:
|
||
|
||
```text
|
||
app/src/market_data/acquisition/adapters/dzengi/mapper.py
|
||
```
|
||
|
||
содержал mapper для:
|
||
|
||
- Instrument;
|
||
- REST Quote;
|
||
- WebSocket Quote;
|
||
- REST Candle;
|
||
- WebSocket Candle.
|
||
|
||
Однако Trade Mapper отсутствовал полностью.
|
||
|
||
Также отсутствовало специализированное исключение уровня Mapping.
|
||
|
||
Unit-тесты не содержали проверок преобразования REST Trade в Canonical Trade.
|
||
|
||
Следовательно Build мог быть реализован как полностью additive change без изменения существующего поведения системы.
|
||
|
||
---
|
||
|
||
# Предварительный архитектурный аудит
|
||
|
||
Перед началом реализации были повторно проанализированы:
|
||
|
||
```text
|
||
app/src/market_data/acquisition/adapters/dzengi/mapper.py
|
||
|
||
app/src/market_data/acquisition/adapters/dzengi/models.py
|
||
|
||
app/src/market_data/acquisition/models/trade.py
|
||
|
||
app/src/market_data/acquisition/exceptions.py
|
||
|
||
tests/unit/market_data/acquisition/adapters/dzengi/test_mapper.py
|
||
|
||
docs/migrations/build_060_1.md
|
||
|
||
docs/migrations/build_060_2.md
|
||
|
||
docs/migrations/build_060_3.md
|
||
|
||
docs/migrations/build_060_4.md
|
||
|
||
docs/migrations/build_060_5.md
|
||
```
|
||
|
||
Кроме анализа существующего кода была повторно проверена вся архитектура Acquisition Pipeline.
|
||
|
||
Аудит подтвердил следующие выводы:
|
||
|
||
- транспортная модель уже полностью сформирована;
|
||
- parser завершён;
|
||
- value validation завершена;
|
||
- Canonical Trade уже утверждён Build 060.1;
|
||
- mapper должен стать единственной точкой создания Canonical Trade;
|
||
- mapper не должен изменять архитектуру предыдущих Build;
|
||
- mapper должен использовать существующие соглашения остальных mapper проекта;
|
||
- mapper должен возвращать immutable tuple;
|
||
- mapper должен быть полностью независимым от REST JSON.
|
||
|
||
Никакие изменения предыдущих Build не требуются.
|
||
|
||
---
|
||
|
||
# Архитектурная задача Build
|
||
|
||
Главная задача Build заключается не в преобразовании типов.
|
||
|
||
Его настоящая задача — завершить разделение двух независимых моделей данных.
|
||
|
||
До Build 060.6 система всё ещё использовала транспортную модель биржи:
|
||
|
||
```text
|
||
DzengiRestAggTrade
|
||
```
|
||
|
||
После Build система должна работать исключительно с внутренней моделью:
|
||
|
||
```text
|
||
Trade
|
||
```
|
||
|
||
Таким образом все последующие компоненты Dzentra полностью перестают зависеть от REST API Dzengi.
|
||
|
||
Это является одним из ключевых принципов архитектуры всей подсистемы Market Data Acquisition.
|
||
|
||
---
|
||
|
||
# Рассмотренные архитектурные решения
|
||
|
||
Перед реализацией были рассмотрены несколько вариантов построения последнего слоя REST Pipeline.
|
||
|
||
## Вариант 1
|
||
|
||
Использовать транспортную модель непосредственно внутри системы.
|
||
|
||
Например:
|
||
|
||
```python
|
||
DzengiRestAggTrade
|
||
```
|
||
|
||
вместо
|
||
|
||
```python
|
||
Trade
|
||
```
|
||
|
||
Данный вариант был отклонён.
|
||
|
||
Причины:
|
||
|
||
- транспортная модель описывает внешний REST API;
|
||
- любое изменение биржи приведёт к изменению внутренней модели системы;
|
||
- нарушается принцип изоляции внешнего контракта;
|
||
- остальные Acquisition Pipeline уже используют Canonical Models;
|
||
- появляется зависимость бизнес-логики от конкретной биржи.
|
||
|
||
Использование транспортной модели за пределами Acquisition Layer противоречит утверждённой архитектуре Dzentra.
|
||
|
||
---
|
||
|
||
## Вариант 2
|
||
|
||
Создавать Canonical Trade непосредственно в Parser.
|
||
|
||
Например:
|
||
|
||
```text
|
||
REST JSON
|
||
|
||
│
|
||
|
||
▼
|
||
|
||
Parser
|
||
|
||
│
|
||
|
||
▼
|
||
|
||
Trade
|
||
```
|
||
|
||
На первый взгляд такой подход уменьшает количество слоёв.
|
||
|
||
Однако он был отклонён.
|
||
|
||
Причины:
|
||
|
||
- parser начинает выполнять две независимые задачи;
|
||
- смешиваются parsing и mapping;
|
||
- невозможно независимо тестировать parser;
|
||
- parser начинает зависеть от предметной модели;
|
||
- нарушается единообразие остальных Acquisition Pipeline.
|
||
|
||
В Dzentra Parser отвечает исключительно за преобразование документа в транспортную модель.
|
||
|
||
Создание предметных объектов относится только к Mapper.
|
||
|
||
---
|
||
|
||
## Вариант 3
|
||
|
||
Объединить Value Validation и Mapper.
|
||
|
||
Например:
|
||
|
||
```text
|
||
Value Validation
|
||
|
||
│
|
||
|
||
▼
|
||
|
||
Trade
|
||
```
|
||
|
||
Вариант также был отклонён.
|
||
|
||
Причины:
|
||
|
||
Value Validation отвечает исключительно за проверку корректности значений.
|
||
|
||
Mapper отвечает исключительно за преобразование одной модели данных в другую.
|
||
|
||
Объединение этих задач привело бы к нарушению принципа Single Responsibility и существенно усложнило бы повторное использование Validation Layer.
|
||
|
||
Поэтому оба слоя остаются полностью независимыми.
|
||
|
||
# Рассмотренные архитектурные решения (продолжение)
|
||
|
||
## Назначение Mapper
|
||
|
||
Перед реализацией Build был повторно рассмотрен вопрос:
|
||
|
||
нужен ли отдельный Mapper вообще?
|
||
|
||
На первый взгляд после завершения Value Validation транспортная модель уже содержит корректные данные.
|
||
|
||
Возникает естественный вопрос:
|
||
|
||
почему не использовать её непосредственно в остальной системе?
|
||
|
||
После анализа было подтверждено, что Mapper остаётся обязательным элементом архитектуры.
|
||
|
||
Причины:
|
||
|
||
- транспортная модель принадлежит внешнему API;
|
||
- предметная модель принадлежит Dzentra;
|
||
- транспортная модель может измениться вместе с REST API;
|
||
- предметная модель должна оставаться стабильной;
|
||
- последующие уровни системы не должны знать о существовании Dzengi.
|
||
|
||
Таким образом Mapper является границей между внешним контрактом и внутренней моделью предметной области.
|
||
|
||
---
|
||
|
||
# Почему Mapper создаёт Canonical Trade
|
||
|
||
Build 060.1 ввёл единственную утверждённую модель исполненной сделки:
|
||
|
||
```text
|
||
Trade
|
||
```
|
||
|
||
Начиная с этого момента все остальные подсистемы Dzentra должны работать исключительно с ней.
|
||
|
||
После завершения Build 060.6 никакие компоненты выше Acquisition Layer больше не должны использовать:
|
||
|
||
```text
|
||
DzengiRestAggTrade
|
||
```
|
||
|
||
Тем самым достигается полная независимость внутренних модулей от конкретной биржи.
|
||
|
||
---
|
||
|
||
# Почему Mapper не выполняет Validation
|
||
|
||
Несмотря на то что Mapper повторно преобразует некоторые поля, он намеренно не занимается их проверкой.
|
||
|
||
Например:
|
||
|
||
```text
|
||
price
|
||
|
||
quantity
|
||
|
||
timestamp
|
||
```
|
||
|
||
к моменту вызова Mapper уже прошли:
|
||
|
||
- Schema Validation;
|
||
- Parser;
|
||
- Value Validation.
|
||
|
||
Следовательно Mapper получает гарантированно корректные данные.
|
||
|
||
Повторная проверка:
|
||
|
||
- увеличила бы объём кода;
|
||
- ухудшила читаемость;
|
||
- нарушила бы разделение ответственности;
|
||
- усложнила сопровождение.
|
||
|
||
Именно поэтому Build 060.6 использует уже проверенные значения без дополнительной бизнес-валидации.
|
||
|
||
---
|
||
|
||
# Почему Mapper всё же создаёт Decimal
|
||
|
||
Во время проектирования обсуждался вопрос:
|
||
|
||
если Value Validation уже проверила корректность значения,
|
||
|
||
нужно ли повторно выполнять:
|
||
|
||
```text
|
||
str
|
||
|
||
↓
|
||
|
||
Decimal
|
||
```
|
||
|
||
Ответ — да.
|
||
|
||
Причина заключается в разделении обязанностей.
|
||
|
||
Value Validation подтверждает только возможность такого преобразования.
|
||
|
||
Она не создаёт предметные объекты.
|
||
|
||
Создание экземпляров `Decimal` относится исключительно к этапу построения Canonical Model.
|
||
|
||
Поэтому преобразование выполняется именно внутри Mapper.
|
||
|
||
---
|
||
|
||
# Почему Decimal не создаётся раньше
|
||
|
||
Рассматривался альтернативный вариант.
|
||
|
||
После завершения Value Validation транспортная модель могла бы уже содержать:
|
||
|
||
```python
|
||
Decimal
|
||
```
|
||
|
||
вместо
|
||
|
||
```python
|
||
str
|
||
```
|
||
|
||
Данный вариант был отклонён.
|
||
|
||
Причины:
|
||
|
||
транспортная модель должна максимально точно отражать внешний REST API.
|
||
|
||
REST API возвращает строки.
|
||
|
||
Следовательно transport layer также обязан использовать строки.
|
||
|
||
Любое изменение типа означало бы скрытый Mapping.
|
||
|
||
Это нарушило бы архитектуру Acquisition Pipeline.
|
||
|
||
---
|
||
|
||
# Почему создаётся datetime
|
||
|
||
REST API возвращает время сделки в виде:
|
||
|
||
```text
|
||
timestamp (milliseconds)
|
||
```
|
||
|
||
Предметная модель использует:
|
||
|
||
```text
|
||
datetime
|
||
```
|
||
|
||
Следовательно именно Mapper отвечает за переход:
|
||
|
||
```text
|
||
timestamp
|
||
|
||
↓
|
||
|
||
datetime
|
||
```
|
||
|
||
Это преобразование является частью формирования внутренней модели.
|
||
|
||
Никакие предыдущие Build его не выполняют.
|
||
|
||
---
|
||
|
||
# Почему используется UTC
|
||
|
||
При проектировании обсуждались несколько вариантов представления времени.
|
||
|
||
Рассматривались:
|
||
|
||
- naive datetime;
|
||
- локальное время системы;
|
||
- UTC datetime.
|
||
|
||
Утверждён третий вариант.
|
||
|
||
Причины:
|
||
|
||
- все остальные модели проекта используют UTC;
|
||
- отсутствует зависимость от локальной временной зоны;
|
||
- упрощается дальнейшее сравнение событий;
|
||
- полностью исключаются ошибки перехода между часовыми поясами.
|
||
|
||
Следовательно Mapper всегда создаёт timezone-aware UTC datetime.
|
||
|
||
---
|
||
|
||
# Почему Mapper получает symbol параметром
|
||
|
||
Во время проектирования обсуждался ещё один вопрос.
|
||
|
||
REST endpoint вызывается примерно следующим образом:
|
||
|
||
```text
|
||
GET /api/v2/aggTrades?symbol=BTCUSDT
|
||
```
|
||
|
||
При этом внутри каждого элемента массива символ отсутствует.
|
||
|
||
Рассматривались несколько вариантов.
|
||
|
||
---
|
||
|
||
## Вариант 1
|
||
|
||
Добавить symbol в транспортную модель.
|
||
|
||
Отклонён.
|
||
|
||
Причины:
|
||
|
||
это изменило бы внешний контракт REST API;
|
||
|
||
транспортная модель перестала бы точно описывать полученный JSON.
|
||
|
||
---
|
||
|
||
## Вариант 2
|
||
|
||
Не сохранять symbol вообще.
|
||
|
||
Отклонён.
|
||
|
||
Причины:
|
||
|
||
Canonical Trade всегда должен содержать символ инструмента.
|
||
|
||
Без него сделка становится неполной.
|
||
|
||
---
|
||
|
||
## Утверждённое решение
|
||
|
||
Mapper принимает symbol отдельным параметром.
|
||
|
||
Например:
|
||
|
||
```python
|
||
map_dzengi_rest_agg_trades_to_trades(
|
||
trades,
|
||
symbol="BTC/USDT",
|
||
)
|
||
```
|
||
|
||
Такое решение позволяет одновременно:
|
||
|
||
- сохранить чистоту транспортной модели;
|
||
- корректно построить Canonical Trade.
|
||
|
||
---
|
||
|
||
# Почему выполняется strip()
|
||
|
||
Mapper использует:
|
||
|
||
```python
|
||
symbol.strip()
|
||
```
|
||
|
||
Это решение также обсуждалось отдельно.
|
||
|
||
Причины:
|
||
|
||
символ инструмента может поступать из внешнего слоя;
|
||
|
||
внешний слой потенциально способен содержать случайные пробелы;
|
||
|
||
Canonical Model должна содержать нормализованное значение.
|
||
|
||
При этом Mapper не изменяет сам идентификатор инструмента.
|
||
|
||
Удаляются только внешние пробельные символы.
|
||
|
||
---
|
||
|
||
# Почему buyer_is_maker преобразуется в TradeAggressorSide
|
||
|
||
REST API использует поле:
|
||
|
||
```text
|
||
buyer_is_maker
|
||
```
|
||
|
||
Это транспортная характеристика конкретного REST endpoint.
|
||
|
||
Предметная модель Dzentra использует другое понятие:
|
||
|
||
```text
|
||
TradeAggressorSide
|
||
```
|
||
|
||
Следовательно Mapper обязан выполнить интерпретацию транспортного признака.
|
||
|
||
Используется следующее соответствие:
|
||
|
||
```text
|
||
buyer_is_maker = False
|
||
|
||
↓
|
||
|
||
BUY
|
||
```
|
||
|
||
```text
|
||
buyer_is_maker = True
|
||
|
||
↓
|
||
|
||
SELL
|
||
```
|
||
|
||
Таким образом транспортная особенность REST API полностью исчезает после завершения Mapper.
|
||
|
||
Дальнейшие компоненты системы работают исключительно с предметной моделью.
|
||
|
||
---
|
||
|
||
# Почему Mapper не сортирует сделки
|
||
|
||
Рассматривалась возможность автоматически сортировать сделки по времени исполнения.
|
||
|
||
Вариант отклонён.
|
||
|
||
Причины:
|
||
|
||
Mapper не должен менять порядок данных;
|
||
|
||
Mapper не должен принимать бизнес-решения;
|
||
|
||
Mapper обязан сохранять последовательность объектов, полученную от предыдущего слоя.
|
||
|
||
Любая сортировка относится к более высокому уровню обработки данных.
|
||
|
||
Поэтому Build 060.6 намеренно сохраняет исходный порядок элементов.
|
||
|
||
---
|
||
|
||
# Почему Mapper не удаляет дубликаты
|
||
|
||
Аналогично обсуждалась автоматическая дедупликация сделок.
|
||
|
||
Она также была отклонена.
|
||
|
||
Причины:
|
||
|
||
Mapper не знает источник появления дубликатов;
|
||
|
||
Mapper не знает стратегию обработки повторных сделок;
|
||
|
||
REST и WebSocket могут использовать разные правила объединения данных.
|
||
|
||
Следовательно дедупликация относится исключительно к будущим компонентам Feed.
|
||
|
||
Build 060.6 не содержит подобной логики.
|
||
|
||
---
|
||
|
||
# Новый публичный API
|
||
|
||
Build добавляет новую публичную функцию:
|
||
|
||
```python
|
||
map_dzengi_rest_agg_trades_to_trades()
|
||
```
|
||
|
||
Назначение функции:
|
||
|
||
- принимает набор проверенных транспортных моделей;
|
||
- создаёт immutable набор Canonical Trade;
|
||
- сохраняет порядок входных элементов;
|
||
- не изменяет семантику данных.
|
||
|
||
Сигнатура имеет вид:
|
||
|
||
```python
|
||
tuple[DzengiRestAggTrade]
|
||
│
|
||
▼
|
||
tuple[Trade]
|
||
```
|
||
|
||
Функция является единственной публичной точкой входа Mapper.
|
||
|
||
Все остальные функции Build имеют внутренний характер и используются исключительно как вспомогательные.
|
||
|
||
---
|
||
|
||
# Новое исключение
|
||
|
||
Build 060.6 вводит новый специализированный тип исключения:
|
||
|
||
```python
|
||
TradeMappingError
|
||
```
|
||
|
||
Назначение исключения — локализовать ошибки уровня Mapping.
|
||
|
||
Таким образом полностью завершается иерархия ошибок REST Trade Pipeline:
|
||
|
||
```text
|
||
TradeSchemaError
|
||
|
||
│
|
||
|
||
TradeParseError
|
||
|
||
│
|
||
|
||
TradeValueError
|
||
|
||
│
|
||
|
||
TradeMappingError
|
||
```
|
||
|
||
Каждый этап обработки теперь имеет собственный тип исключений.
|
||
|
||
Это позволяет точно определить уровень возникновения ошибки и существенно упрощает диагностику при дальнейшем развитии системы.
|
||
|
||
# Семантика Mapper
|
||
|
||
После завершения Build 060.6 Mapper становится единственной точкой, в которой транспортная модель преобразуется во внутреннюю предметную модель Dzentra.
|
||
|
||
На вход Mapper получает исключительно полностью подготовленные объекты.
|
||
|
||
Они уже прошли:
|
||
|
||
- проверку структуры;
|
||
- parser;
|
||
- проверку обязательных полей;
|
||
- проверку типов;
|
||
- проверку диапазонов;
|
||
- проверку корректности числовых значений.
|
||
|
||
Следовательно Mapper может полностью сосредоточиться исключительно на преобразовании моделей данных.
|
||
|
||
Его работа сводится к последовательному созданию каждого экземпляра Canonical Trade.
|
||
|
||
---
|
||
|
||
# Общая схема преобразования
|
||
|
||
Для каждой транспортной модели выполняется одинаковая последовательность действий.
|
||
|
||
```text
|
||
DzengiRestAggTrade
|
||
|
||
│
|
||
|
||
├──────── aggregate_trade_id ───────► trade_id
|
||
|
||
│
|
||
|
||
├──────── price ────────────────────► Decimal
|
||
|
||
│
|
||
|
||
├──────── quantity ─────────────────► Decimal
|
||
|
||
│
|
||
|
||
├──────── timestamp ────────────────► datetime (UTC)
|
||
|
||
│
|
||
|
||
├──────── buyer_is_maker ───────────► TradeAggressorSide
|
||
|
||
│
|
||
|
||
└──────── symbol (argument) ────────► symbol
|
||
|
||
│
|
||
|
||
▼
|
||
|
||
Trade
|
||
```
|
||
|
||
После создания объекта транспортная модель больше не используется.
|
||
|
||
Вся дальнейшая работа системы выполняется исключительно с Canonical Trade.
|
||
|
||
---
|
||
|
||
# Преобразование aggregate_trade_id
|
||
|
||
Транспортная модель содержит поле:
|
||
|
||
```python
|
||
aggregate_trade_id
|
||
```
|
||
|
||
Предметная модель использует:
|
||
|
||
```python
|
||
trade_id
|
||
```
|
||
|
||
Во время проектирования обсуждался вопрос:
|
||
|
||
нужно ли сохранять первоначальное название?
|
||
|
||
Решение было отрицательным.
|
||
|
||
Причины:
|
||
|
||
Canonical Trade не должен зависеть от конкретного REST endpoint.
|
||
|
||
Внутренняя модель должна использовать единый термин:
|
||
|
||
```text
|
||
trade_id
|
||
```
|
||
|
||
Следовательно Mapper выполняет простое переименование поля.
|
||
|
||
Никаких дополнительных преобразований значения не производится.
|
||
|
||
---
|
||
|
||
# Преобразование price
|
||
|
||
REST API возвращает цену как транспортное числовое значение.
|
||
|
||
После завершения предыдущих Build уже известно, что значение:
|
||
|
||
- существует;
|
||
- корректно;
|
||
- конечно;
|
||
- может быть преобразовано в Decimal.
|
||
|
||
Mapper создаёт окончательное значение:
|
||
|
||
```python
|
||
Decimal
|
||
```
|
||
|
||
Полученный объект помещается непосредственно в Canonical Trade.
|
||
|
||
После этого транспортное представление полностью исчезает.
|
||
|
||
---
|
||
|
||
# Преобразование quantity
|
||
|
||
Поле quantity проходит абсолютно аналогичную обработку.
|
||
|
||
Mapper получает проверенное транспортное значение.
|
||
|
||
Создаётся:
|
||
|
||
```python
|
||
Decimal
|
||
```
|
||
|
||
Полученный экземпляр используется внутри Canonical Trade.
|
||
|
||
Дополнительные проверки не выполняются.
|
||
|
||
---
|
||
|
||
# Преобразование timestamp
|
||
|
||
Наиболее важным преобразованием Build является обработка времени сделки.
|
||
|
||
REST API использует миллисекундный Unix Timestamp.
|
||
|
||
Например:
|
||
|
||
```text
|
||
1783537921471
|
||
```
|
||
|
||
Внутренняя модель использует:
|
||
|
||
```python
|
||
datetime
|
||
```
|
||
|
||
Mapper выполняет преобразование:
|
||
|
||
```text
|
||
milliseconds
|
||
|
||
↓
|
||
|
||
seconds
|
||
|
||
↓
|
||
|
||
datetime
|
||
|
||
↓
|
||
|
||
UTC
|
||
```
|
||
|
||
После завершения Mapper транспортное представление времени полностью исчезает.
|
||
|
||
Все последующие компоненты работают только с datetime.
|
||
|
||
---
|
||
|
||
# Преобразование buyer_is_maker
|
||
|
||
REST API использует транспортный признак:
|
||
|
||
```text
|
||
buyer_is_maker
|
||
```
|
||
|
||
Однако для анализа рынка подобное представление неудобно.
|
||
|
||
Предметная модель использует понятие стороны агрессора.
|
||
|
||
Поэтому Mapper выполняет следующую интерпретацию.
|
||
|
||
Если:
|
||
|
||
```text
|
||
buyer_is_maker = False
|
||
```
|
||
|
||
создаётся
|
||
|
||
```text
|
||
TradeAggressorSide.BUY
|
||
```
|
||
|
||
Если:
|
||
|
||
```text
|
||
buyer_is_maker = True
|
||
```
|
||
|
||
создаётся
|
||
|
||
```text
|
||
TradeAggressorSide.SELL
|
||
```
|
||
|
||
В результате все остальные компоненты Dzentra работают уже не с транспортным булевым признаком, а с предметной характеристикой сделки.
|
||
|
||
---
|
||
|
||
# Преобразование symbol
|
||
|
||
Canonical Trade всегда содержит символ инструмента.
|
||
|
||
Поскольку REST API не включает его в элементы массива,
|
||
|
||
Mapper получает его отдельным аргументом.
|
||
|
||
Перед сохранением выполняется:
|
||
|
||
```python
|
||
strip()
|
||
```
|
||
|
||
Это гарантирует отсутствие случайных пробелов.
|
||
|
||
Никаких других преобразований идентификатора инструмента не производится.
|
||
|
||
---
|
||
|
||
# Поле source
|
||
|
||
Build 060.1 закрепил наличие поля:
|
||
|
||
```python
|
||
source
|
||
```
|
||
|
||
внутри Canonical Trade.
|
||
|
||
Mapper устанавливает значение:
|
||
|
||
```text
|
||
dzengi
|
||
```
|
||
|
||
Причины такого решения:
|
||
|
||
- источник данных известен заранее;
|
||
- он не зависит от содержимого REST ответа;
|
||
- остальные компоненты могут определить происхождение сделки без анализа transport layer.
|
||
|
||
В дальнейшем аналогичный подход позволит объединять сделки, поступающие из различных источников.
|
||
|
||
---
|
||
|
||
# Immutable-поведение
|
||
|
||
Build 060.6 полностью сохраняет принятые архитектурные принципы проекта.
|
||
|
||
Каждый созданный объект:
|
||
|
||
```python
|
||
Trade
|
||
```
|
||
|
||
остаётся immutable.
|
||
|
||
После завершения Mapper сделка не может быть изменена.
|
||
|
||
Это обеспечивает:
|
||
|
||
- предсказуемость обработки;
|
||
- отсутствие скрытых побочных эффектов;
|
||
- безопасную передачу объекта между подсистемами;
|
||
- возможность повторного использования экземпляров без копирования.
|
||
|
||
---
|
||
|
||
# Внутренние функции Mapper
|
||
|
||
Публичный API Build состоит только из одной функции.
|
||
|
||
Остальные функции являются внутренними.
|
||
|
||
---
|
||
|
||
## _map_dzengi_rest_agg_trade()
|
||
|
||
Выполняет преобразование одной транспортной модели в один Canonical Trade.
|
||
|
||
Она инкапсулирует всю логику создания предметного объекта.
|
||
|
||
Использование отдельной функции позволяет:
|
||
|
||
- упростить публичный API;
|
||
- повысить читаемость кода;
|
||
- локализовать логику преобразования одной сделки;
|
||
- упростить unit-тестирование.
|
||
|
||
---
|
||
|
||
## _required_trade_decimal()
|
||
|
||
Инкапсулирует создание объекта Decimal.
|
||
|
||
Несмотря на то что данные уже проверены,
|
||
|
||
Build намеренно использует отдельную функцию.
|
||
|
||
Причины:
|
||
|
||
- единообразие с существующими mapper проекта;
|
||
- локализация обработки ошибок Decimal;
|
||
- возможность последующего рефакторинга без изменения публичного API.
|
||
|
||
---
|
||
|
||
## _trade_timestamp_ms_to_utc_datetime()
|
||
|
||
Полностью изолирует преобразование времени.
|
||
|
||
Она отвечает исключительно за переход:
|
||
|
||
```text
|
||
timestamp (milliseconds)
|
||
|
||
↓
|
||
|
||
UTC datetime
|
||
```
|
||
|
||
Такое разделение делает Mapper проще для чтения и уменьшает связность отдельных операций.
|
||
|
||
---
|
||
|
||
## _trade_aggressor_side()
|
||
|
||
Инкапсулирует преобразование транспортного признака:
|
||
|
||
```text
|
||
buyer_is_maker
|
||
```
|
||
|
||
в предметную модель:
|
||
|
||
```text
|
||
TradeAggressorSide
|
||
```
|
||
|
||
Выделение отдельной функции позволяет:
|
||
|
||
- сделать основной Mapper более компактным;
|
||
- централизовать интерпретацию транспортного признака;
|
||
- исключить дублирование логики при дальнейшем развитии проекта.
|
||
|
||
---
|
||
|
||
# Что намеренно не делает Mapper
|
||
|
||
Build 060.6 специально не реализует никакой дополнительной бизнес-логики.
|
||
|
||
Mapper намеренно:
|
||
|
||
не сортирует сделки;
|
||
|
||
не удаляет дубликаты;
|
||
|
||
не объединяет REST и WebSocket данные;
|
||
|
||
не выполняет агрегацию;
|
||
|
||
не рассчитывает статистику;
|
||
|
||
не изменяет цену;
|
||
|
||
не изменяет количество;
|
||
|
||
не изменяет идентификаторы;
|
||
|
||
не анализирует последовательность сделок;
|
||
|
||
не определяет рыночную структуру;
|
||
|
||
не вычисляет объёмы;
|
||
|
||
не создаёт свечи;
|
||
|
||
не формирует Time & Sales Feed;
|
||
|
||
не взаимодействует с Runtime;
|
||
|
||
не сохраняет данные.
|
||
|
||
Все перечисленные задачи относятся к последующим уровням архитектуры и не входят в ответственность Mapper.
|
||
|
||
Благодаря этому Build 060.6 остаётся небольшим, полностью изолированным и строго соответствует принципу Single Responsibility.
|
||
|
||
# Изменённые файлы
|
||
|
||
В рамках Build 060.6 были изменены только три файла проекта.
|
||
|
||
```text
|
||
app/src/market_data/acquisition/adapters/dzengi/mapper.py
|
||
|
||
app/src/market_data/acquisition/exceptions.py
|
||
|
||
app/tests/unit/market_data/acquisition/adapters/dzengi/test_mapper.py
|
||
```
|
||
|
||
Кроме того, подготовлена инженерная документация Build:
|
||
|
||
```text
|
||
docs/migrations/build_060_6.md
|
||
```
|
||
|
||
Никакие другие компоненты проекта не изменялись.
|
||
|
||
Build полностью соответствует ранее утверждённому принципу минимального локального изменения (Local Additive Change).
|
||
|
||
---
|
||
|
||
# Изменения в exceptions.py
|
||
|
||
Файл:
|
||
|
||
```text
|
||
app/src/market_data/acquisition/exceptions.py
|
||
```
|
||
|
||
получил новый специализированный тип исключения:
|
||
|
||
```python
|
||
TradeMappingError
|
||
```
|
||
|
||
До Build 060.6 цепочка исключений выглядела следующим образом:
|
||
|
||
```text
|
||
TradeSchemaError
|
||
|
||
↓
|
||
|
||
TradeParseError
|
||
|
||
↓
|
||
|
||
TradeValueError
|
||
```
|
||
|
||
После завершения Build архитектура стала полностью симметричной:
|
||
|
||
```text
|
||
TradeSchemaError
|
||
|
||
↓
|
||
|
||
TradeParseError
|
||
|
||
↓
|
||
|
||
TradeValueError
|
||
|
||
↓
|
||
|
||
TradeMappingError
|
||
```
|
||
|
||
Теперь каждый слой REST Trade Pipeline имеет собственный тип ошибок.
|
||
|
||
Это значительно упрощает диагностику при сопровождении системы.
|
||
|
||
---
|
||
|
||
# Назначение TradeMappingError
|
||
|
||
Несмотря на то что Mapper работает только с уже проверенными объектами,
|
||
|
||
полностью исключить ошибки преобразования невозможно.
|
||
|
||
Например:
|
||
|
||
- невозможность создать Decimal;
|
||
- невозможность построить datetime;
|
||
- внутренние ошибки преобразования.
|
||
|
||
Во всех подобных случаях используется исключительно:
|
||
|
||
```python
|
||
TradeMappingError
|
||
```
|
||
|
||
Таким образом исключения предыдущих уровней не используются повторно.
|
||
|
||
Каждый слой отвечает только за собственные ошибки.
|
||
|
||
---
|
||
|
||
# Изменения в mapper.py
|
||
|
||
Build 060.6 расширяет существующий файл:
|
||
|
||
```text
|
||
app/src/market_data/acquisition/adapters/dzengi/mapper.py
|
||
```
|
||
|
||
Никакие существующие mapper не изменялись.
|
||
|
||
Все новые функции были добавлены дополнительно.
|
||
|
||
Таким образом Build полностью сохраняет обратную совместимость.
|
||
|
||
---
|
||
|
||
# Новый публичный API
|
||
|
||
В файл добавлена функция:
|
||
|
||
```python
|
||
map_dzengi_rest_agg_trades_to_trades()
|
||
```
|
||
|
||
Именно она становится официальной точкой входа Trade Mapper.
|
||
|
||
Она:
|
||
|
||
- принимает immutable tuple транспортных моделей;
|
||
- создаёт immutable tuple Canonical Trade;
|
||
- сохраняет порядок элементов;
|
||
- не изменяет семантику данных;
|
||
- не выполняет дополнительную validation.
|
||
|
||
После Build именно эта функция должна использоваться всеми последующими компонентами Acquisition Layer.
|
||
|
||
---
|
||
|
||
# Внутренние функции Mapper
|
||
|
||
Кроме публичного API Build добавляет четыре внутренних функции.
|
||
|
||
```python
|
||
_map_dzengi_rest_agg_trade()
|
||
|
||
_required_trade_decimal()
|
||
|
||
_trade_timestamp_ms_to_utc_datetime()
|
||
|
||
_trade_aggressor_side()
|
||
```
|
||
|
||
Каждая из них отвечает только за одну небольшую операцию.
|
||
|
||
Такое разделение полностью соответствует существующему стилю остальных mapper проекта.
|
||
|
||
---
|
||
|
||
# Почему используется несколько небольших функций
|
||
|
||
Во время проектирования обсуждалась возможность реализации всего Mapper одной функцией.
|
||
|
||
Например:
|
||
|
||
```python
|
||
map_dzengi_rest_agg_trades_to_trades(...)
|
||
```
|
||
|
||
которая сразу содержала бы всю логику.
|
||
|
||
Такой вариант был отклонён.
|
||
|
||
Причины:
|
||
|
||
- ухудшается читаемость;
|
||
- возрастает размер функции;
|
||
- усложняется unit-тестирование;
|
||
- возрастает вероятность случайных ошибок при дальнейшем сопровождении.
|
||
|
||
Использование небольших специализированных функций делает код значительно проще для сопровождения.
|
||
|
||
---
|
||
|
||
# Изменения в unit-тестах
|
||
|
||
Файл:
|
||
|
||
```text
|
||
tests/unit/market_data/acquisition/adapters/dzengi/test_mapper.py
|
||
```
|
||
|
||
получил новый раздел тестов Trade Mapper.
|
||
|
||
До Build 060.6 данный файл содержал проверки:
|
||
|
||
- Instrument Mapper;
|
||
- Quote Mapper;
|
||
- Candle Mapper.
|
||
|
||
После завершения Build в него были добавлены специализированные проверки преобразования REST Trade.
|
||
|
||
Существующие тесты не изменялись.
|
||
|
||
Build полностью additive.
|
||
|
||
---
|
||
|
||
# Проверка корректного Mapping
|
||
|
||
Наиболее важной частью Build являются тесты успешного преобразования.
|
||
|
||
Они подтверждают корректность создания:
|
||
|
||
```python
|
||
Trade
|
||
```
|
||
|
||
из:
|
||
|
||
```python
|
||
DzengiRestAggTrade
|
||
```
|
||
|
||
Проверяется:
|
||
|
||
- корректность symbol;
|
||
- корректность trade_id;
|
||
- корректность price;
|
||
- корректность quantity;
|
||
- корректность datetime;
|
||
- корректность TradeAggressorSide;
|
||
- корректность source.
|
||
|
||
Тем самым подтверждается правильность полного построения Canonical Trade.
|
||
|
||
---
|
||
|
||
# Проверка BUY и SELL
|
||
|
||
Отдельные unit-тесты подтверждают правильную интерпретацию транспортного признака:
|
||
|
||
```text
|
||
buyer_is_maker
|
||
```
|
||
|
||
Проверяются обе возможные ситуации.
|
||
|
||
```text
|
||
False
|
||
|
||
↓
|
||
|
||
BUY
|
||
```
|
||
|
||
```text
|
||
True
|
||
|
||
↓
|
||
|
||
SELL
|
||
```
|
||
|
||
Тем самым полностью исключается вероятность обратного преобразования.
|
||
|
||
---
|
||
|
||
# Проверка порядка элементов
|
||
|
||
Отдельный тест подтверждает,
|
||
|
||
что Mapper не изменяет порядок сделок.
|
||
|
||
Если транспортные модели расположены в определённой последовательности,
|
||
|
||
Canonical Trade создаются в точно таком же порядке.
|
||
|
||
Это является важной частью архитектурного контракта Mapper.
|
||
|
||
---
|
||
|
||
# Проверка пустого набора
|
||
|
||
Mapper должен корректно работать даже при отсутствии сделок.
|
||
|
||
Для этого реализована отдельная проверка.
|
||
|
||
Вход:
|
||
|
||
```python
|
||
tuple()
|
||
```
|
||
|
||
Результат:
|
||
|
||
```python
|
||
tuple()
|
||
```
|
||
|
||
Никаких специальных объектов,
|
||
|
||
исключений
|
||
|
||
или дополнительных значений Build не создаёт.
|
||
|
||
---
|
||
|
||
# Проверка нормализации symbol
|
||
|
||
Отдельный unit-тест подтверждает использование:
|
||
|
||
```python
|
||
strip()
|
||
```
|
||
|
||
Если внешний слой передал:
|
||
|
||
```text
|
||
" BTC/USDT "
|
||
```
|
||
|
||
то Canonical Trade получает:
|
||
|
||
```text
|
||
"BTC/USDT"
|
||
```
|
||
|
||
При этом внутреннее содержимое строки не изменяется.
|
||
|
||
Удаляются исключительно внешние пробелы.
|
||
|
||
---
|
||
|
||
# Проверка Decimal
|
||
|
||
Build содержит несколько отдельных тестов создания Decimal.
|
||
|
||
Проверяются различные варианты транспортных значений.
|
||
|
||
Например:
|
||
|
||
```text
|
||
str
|
||
|
||
int
|
||
|
||
float
|
||
```
|
||
|
||
Во всех случаях результатом становится корректный объект:
|
||
|
||
```python
|
||
Decimal
|
||
```
|
||
|
||
Это подтверждает независимость Mapper от конкретного представления транспортного числа.
|
||
|
||
---
|
||
|
||
# Проверка datetime
|
||
|
||
Отдельный тест подтверждает,
|
||
|
||
что миллисекундный timestamp корректно преобразуется в:
|
||
|
||
```python
|
||
UTC datetime
|
||
```
|
||
|
||
Также подтверждается,
|
||
|
||
что полученный объект содержит информацию о временной зоне.
|
||
|
||
Тем самым исключается появление naive datetime внутри Canonical Trade.
|
||
|
||
---
|
||
|
||
# Проверка immutable
|
||
|
||
Поскольку Canonical Trade является immutable,
|
||
|
||
Build содержит специальный тест,
|
||
|
||
подтверждающий невозможность изменения созданного объекта.
|
||
|
||
Это гарантирует,
|
||
|
||
что Mapper полностью сохраняет архитектурные требования Build 060.1.
|
||
|
||
---
|
||
|
||
# Проверка ошибок Mapping
|
||
|
||
Несмотря на предварительную validation,
|
||
|
||
Build содержит специальные тесты,
|
||
|
||
проверяющие работу:
|
||
|
||
```python
|
||
TradeMappingError
|
||
```
|
||
|
||
Проверяются ситуации,
|
||
|
||
при которых невозможно корректно выполнить преобразование.
|
||
|
||
Например:
|
||
|
||
- некорректный Decimal;
|
||
- бесконечность;
|
||
- NaN;
|
||
- невозможность создать datetime.
|
||
|
||
Хотя подобные ситуации практически недостижимы после Value Validation,
|
||
|
||
их наличие существенно повышает устойчивость Mapper к ошибкам при дальнейшем развитии проекта.
|
||
|
||
---
|
||
|
||
# Что намеренно не тестируется
|
||
|
||
Build 060.6 специально не тестирует:
|
||
|
||
- работу REST API;
|
||
- Schema Validation;
|
||
- Parser;
|
||
- Value Validation;
|
||
- Runtime;
|
||
- Feed;
|
||
- Registry;
|
||
- Acquisition Service;
|
||
- WebSocket Trades.
|
||
|
||
Каждый из перечисленных компонентов имеет собственные Build и собственные наборы unit-тестов.
|
||
|
||
Таким образом тесты Build 060.6 полностью сосредоточены исключительно на Mapper.
|
||
|
||
# Что намеренно не изменялось
|
||
|
||
Build 060.6 специально не изменяет:
|
||
|
||
```text
|
||
app/src/market_data/acquisition/models/trade.py
|
||
|
||
app/src/market_data/acquisition/adapters/dzengi/parser.py
|
||
|
||
app/src/market_data/acquisition/validation/schema.py
|
||
|
||
app/src/market_data/acquisition/validation/values.py
|
||
|
||
app/src/market_data/acquisition/runtime/
|
||
|
||
app/src/market_data/acquisition/feeds/
|
||
|
||
app/src/market_data/acquisition/service.py
|
||
|
||
app/src/market_data/acquisition/registry.py
|
||
```
|
||
|
||
Также Build намеренно не включает:
|
||
|
||
- REST client;
|
||
- выполнение REST-запросов;
|
||
- получение исторических сделок;
|
||
- объединение REST и WebSocket сделок;
|
||
- синхронизацию потоков данных;
|
||
- хранение истории сделок;
|
||
- построение Time & Sales Feed;
|
||
- обработку Live Feed;
|
||
- Runtime Integration;
|
||
- Production Integration.
|
||
|
||
Все перечисленные задачи реализуются отдельными Build согласно утверждённой дорожной карте.
|
||
|
||
Таким образом Build 060.6 остаётся полностью локальным и не выходит за пределы своей архитектурной ответственности.
|
||
|
||
---
|
||
|
||
# Проверка компиляции
|
||
|
||
После завершения реализации была выполнена проверка компиляции проекта.
|
||
|
||
Команда выполнялась из каталога:
|
||
|
||
```text
|
||
~/vsprojects/dzentra_bot/app
|
||
```
|
||
|
||
При активированном виртуальном окружении:
|
||
|
||
```bash
|
||
source .venv/bin/activate
|
||
```
|
||
|
||
Выполнена команда:
|
||
|
||
```bash
|
||
python -m compileall src
|
||
```
|
||
|
||
Результат:
|
||
|
||
```text
|
||
успешно
|
||
```
|
||
|
||
Все модули проекта успешно скомпилированы.
|
||
|
||
Ошибок синтаксиса не обнаружено.
|
||
|
||
Build не нарушил корректность структуры проекта.
|
||
|
||
---
|
||
|
||
# Проверка локальных unit-тестов
|
||
|
||
После завершения реализации Mapper были выполнены специализированные тесты.
|
||
|
||
Команда:
|
||
|
||
```bash
|
||
python -m pytest \
|
||
tests/unit/market_data/acquisition/adapters/dzengi/test_mapper.py \
|
||
-q
|
||
```
|
||
|
||
Результат:
|
||
|
||
```text
|
||
42 passed in 0.04s
|
||
```
|
||
|
||
Проверки подтвердили корректную работу:
|
||
|
||
- Trade Mapper;
|
||
- Instrument Mapper;
|
||
- Quote Mapper;
|
||
- Candle Mapper;
|
||
- новых тестов Build 060.6.
|
||
|
||
Ни одна существующая проверка не была нарушена.
|
||
|
||
---
|
||
|
||
# Полный regression suite
|
||
|
||
После завершения Build выполнен полный набор тестов проекта.
|
||
|
||
Команда:
|
||
|
||
```bash
|
||
python -m pytest -q
|
||
```
|
||
|
||
Результат:
|
||
|
||
```text
|
||
1092 passed in 4.39s
|
||
```
|
||
|
||
Регрессий не обнаружено.
|
||
|
||
Все ранее реализованные Build продолжают работать без изменений.
|
||
|
||
Это подтверждает, что Build 060.6 полностью соответствует принципу additive change.
|
||
|
||
---
|
||
|
||
# Проверка форматирования
|
||
|
||
После завершения работы выполнена команда:
|
||
|
||
```bash
|
||
git diff --check
|
||
```
|
||
|
||
Вывод отсутствует.
|
||
|
||
Это подтверждает отсутствие:
|
||
|
||
- trailing whitespace;
|
||
- лишних пробелов;
|
||
- нарушений форматирования;
|
||
- ошибок оформления diff.
|
||
|
||
Build соответствует принятым требованиям оформления исходного кода.
|
||
|
||
---
|
||
|
||
# Контроль размещения новой функциональности
|
||
|
||
После завершения Build вся новая функциональность сосредоточена только в предназначенных для неё компонентах.
|
||
|
||
Mapper расположен исключительно в:
|
||
|
||
```text
|
||
src/market_data/acquisition/adapters/dzengi/mapper.py
|
||
```
|
||
|
||
Новое исключение расположено исключительно в:
|
||
|
||
```text
|
||
src/market_data/acquisition/exceptions.py
|
||
```
|
||
|
||
Проверки расположены исключительно в:
|
||
|
||
```text
|
||
tests/unit/market_data/acquisition/adapters/dzengi/test_mapper.py
|
||
```
|
||
|
||
Другие подсистемы проекта Build не затрагивает.
|
||
|
||
Таким образом архитектурная изоляция полностью сохранена.
|
||
|
||
---
|
||
|
||
# Состояние Git
|
||
|
||
После завершения Build выполнена команда:
|
||
|
||
```bash
|
||
git status
|
||
```
|
||
|
||
Для Build 060.6 зафиксированы изменения:
|
||
|
||
```text
|
||
modified:
|
||
|
||
app/src/market_data/acquisition/adapters/dzengi/mapper.py
|
||
|
||
app/src/market_data/acquisition/exceptions.py
|
||
|
||
app/tests/unit/market_data/acquisition/adapters/dzengi/test_mapper.py
|
||
|
||
untracked:
|
||
|
||
docs/migrations/build_060_6.md
|
||
```
|
||
|
||
Дополнительно в рабочем каталоге присутствует архитектурная контрольная точка:
|
||
|
||
```text
|
||
docs/migrations/build_060_transition_&_architecture_checkpoint.md
|
||
```
|
||
|
||
Она не относится к реализации Build 060.6 и учитывается отдельно при формировании commit.
|
||
|
||
Ветка разработки:
|
||
|
||
```text
|
||
main
|
||
```
|
||
|
||
опережает `origin/main`.
|
||
|
||
Данное состояние не связано непосредственно с реализацией Build 060.6.
|
||
|
||
---
|
||
|
||
# Фактический diff
|
||
|
||
В Build добавлены следующие основные компоненты.
|
||
|
||
В файл:
|
||
|
||
```text
|
||
app/src/market_data/acquisition/adapters/dzengi/mapper.py
|
||
```
|
||
|
||
добавлены:
|
||
|
||
```python
|
||
map_dzengi_rest_agg_trades_to_trades()
|
||
|
||
_map_dzengi_rest_agg_trade()
|
||
|
||
_required_trade_decimal()
|
||
|
||
_trade_timestamp_ms_to_utc_datetime()
|
||
|
||
_trade_aggressor_side()
|
||
```
|
||
|
||
В файл:
|
||
|
||
```text
|
||
app/src/market_data/acquisition/exceptions.py
|
||
```
|
||
|
||
добавлено новое исключение:
|
||
|
||
```python
|
||
TradeMappingError
|
||
```
|
||
|
||
Файл:
|
||
|
||
```text
|
||
tests/unit/market_data/acquisition/adapters/dzengi/test_mapper.py
|
||
```
|
||
|
||
расширен новым набором специализированных unit-тестов.
|
||
|
||
Все изменения являются полностью additive.
|
||
|
||
Существующее поведение системы не изменялось.
|
||
|
||
---
|
||
|
||
# Архитектурный результат
|
||
|
||
После завершения Build 060.6 REST Pipeline исторических сделок впервые становится полностью завершённым.
|
||
|
||
Полная архитектура выглядит следующим образом.
|
||
|
||
```text
|
||
REST JSON
|
||
|
||
│
|
||
|
||
▼
|
||
|
||
Schema Validation
|
||
|
||
│
|
||
|
||
▼
|
||
|
||
ValidatedRestAggTradesDocument
|
||
|
||
│
|
||
|
||
▼
|
||
|
||
Parser
|
||
|
||
│
|
||
|
||
▼
|
||
|
||
tuple[DzengiRestAggTrade]
|
||
|
||
│
|
||
|
||
▼
|
||
|
||
Value Validation
|
||
|
||
│
|
||
|
||
▼
|
||
|
||
Trade Mapper
|
||
|
||
│
|
||
|
||
▼
|
||
|
||
tuple[Trade]
|
||
```
|
||
|
||
Каждый слой имеет собственную ответственность.
|
||
|
||
Каждый слой имеет собственный тип ошибок.
|
||
|
||
Каждый слой может развиваться независимо от остальных.
|
||
|
||
Таким образом архитектура REST Trades становится полностью согласованной с общей архитектурой Market Data Acquisition.
|
||
|
||
---
|
||
|
||
# Влияние на последующие Build
|
||
|
||
Build 060.6 завершает формирование базового REST Pipeline.
|
||
|
||
Все последующие Build могут работать уже исключительно с канонической моделью:
|
||
|
||
```text
|
||
Trade
|
||
```
|
||
|
||
Это позволяет реализовывать:
|
||
|
||
- REST Trade Feed;
|
||
- Time & Sales Feed;
|
||
- объединение REST и WebSocket Trade;
|
||
- кэширование сделок;
|
||
- хранение истории;
|
||
- анализ потока сделок;
|
||
- расчёт Order Flow;
|
||
- Footprint;
|
||
- Delta;
|
||
- Volume Profile;
|
||
- дальнейшие компоненты Market Intelligence.
|
||
|
||
При этом ни один из этих компонентов больше не зависит от формата REST API Dzengi.
|
||
|
||
---
|
||
|
||
# Критерии завершения
|
||
|
||
Build 060.6 считается полностью завершённым, поскольку:
|
||
|
||
- проведён архитектурный аудит;
|
||
- реализован отдельный Mapper;
|
||
- реализовано специализированное исключение Mapping Layer;
|
||
- завершено разделение Transport Model и Canonical Model;
|
||
- реализовано преобразование всех полей Trade;
|
||
- реализовано создание Decimal;
|
||
- реализовано создание UTC datetime;
|
||
- реализовано преобразование buyer_is_maker в TradeAggressorSide;
|
||
- реализована нормализация symbol;
|
||
- сохранён immutable-подход;
|
||
- сохранён порядок сделок;
|
||
- отсутствует скрытая бизнес-логика;
|
||
- отсутствует сортировка;
|
||
- отсутствует дедупликация;
|
||
- отсутствует изменение транспортной модели;
|
||
- compile проверка успешно пройдена;
|
||
- локальные unit-тесты успешно пройдены;
|
||
- полный regression suite успешно пройден;
|
||
- проверка форматирования успешно пройдена;
|
||
- scope Build не расширен.
|
||
|
||
---
|
||
|
||
# Архитектурные инварианты
|
||
|
||
После завершения Build 060.6 следующие свойства подсистемы считаются архитектурным контрактом и не должны изменяться последующими Build без отдельного архитектурного решения.
|
||
|
||
## Инвариант 1
|
||
|
||
Transport Layer остаётся полностью независимым от предметной модели.
|
||
|
||
Транспортные модели не должны использовать:
|
||
|
||
- Trade;
|
||
- TradeAggressorSide;
|
||
- Decimal;
|
||
- datetime;
|
||
- любую бизнес-логику.
|
||
|
||
---
|
||
|
||
## Инвариант 2
|
||
|
||
Schema Validation отвечает исключительно за проверку структуры документа.
|
||
|
||
Она не выполняет:
|
||
|
||
- parsing;
|
||
- mapping;
|
||
- преобразование типов;
|
||
- бизнес-валидацию.
|
||
|
||
---
|
||
|
||
## Инвариант 3
|
||
|
||
Parser отвечает исключительно за построение transport-моделей.
|
||
|
||
Parser не создаёт:
|
||
|
||
- Canonical Models;
|
||
- Decimal;
|
||
- datetime;
|
||
- бизнес-объекты.
|
||
|
||
---
|
||
|
||
## Инвариант 4
|
||
|
||
Value Validation отвечает исключительно за корректность значений.
|
||
|
||
Она не изменяет транспортные модели и не создаёт предметные объекты.
|
||
|
||
---
|
||
|
||
## Инвариант 5
|
||
|
||
Mapper остаётся единственной точкой построения Canonical Trade.
|
||
|
||
Никакие другие компоненты не должны создавать объект Trade из REST transport-модели.
|
||
|
||
---
|
||
|
||
## Инвариант 6
|
||
|
||
Все последующие компоненты системы работают исключительно с Canonical Trade.
|
||
|
||
Использование DzengiRestAggTrade за пределами Acquisition Layer не допускается.
|
||
|
||
---
|
||
|
||
## Инвариант 7
|
||
|
||
Mapper не выполняет:
|
||
|
||
- сортировку;
|
||
- дедупликацию;
|
||
- агрегацию;
|
||
- расчёт аналитики;
|
||
- бизнес-решения.
|
||
|
||
---
|
||
|
||
## Инвариант 8
|
||
|
||
Trade остаётся immutable.
|
||
|
||
Последующие Build не должны нарушать свойства:
|
||
|
||
- frozen;
|
||
- slots;
|
||
- неизменяемость объекта.
|
||
|
||
---
|
||
|
||
## Инвариант 9
|
||
|
||
Все даты внутри Canonical Trade представлены только в UTC.
|
||
|
||
Использование naive datetime не допускается.
|
||
|
||
---
|
||
|
||
## Инвариант 10
|
||
|
||
Каждый уровень Acquisition Pipeline имеет собственный тип исключений.
|
||
|
||
Объединение нескольких уровней обработки под одним типом исключений не допускается.
|
||
|
||
---
|
||
|
||
# Итог
|
||
|
||
**Build 060.6 завершён успешно.**
|
||
|
||
Текущее состояние REST Trade Pipeline:
|
||
|
||
```text
|
||
Transport Model — реализована
|
||
|
||
Schema Validation — реализована
|
||
|
||
Parser — реализован
|
||
|
||
Value Validation — реализована
|
||
|
||
Mapper — реализован
|
||
|
||
Canonical Trade — используется
|
||
|
||
TradeMappingError — реализован
|
||
|
||
Compile check — успешно
|
||
|
||
Target tests — 42 passed
|
||
|
||
Full regression suite — 1092 passed
|
||
|
||
Whitespace check — успешно
|
||
|
||
Production Integration — намеренно не выполнялась
|
||
```
|
||
|
||
После завершения Build 060.6 подсистема получения исторических сделок впервые получила полностью законченный Acquisition Pipeline.
|
||
|
||
Теперь все последующие компоненты проекта могут работать исключительно с канонической моделью `Trade`, полностью изолированной от внешнего REST API биржи.
|
||
|
||
---
|
||
|
||
# Следующий этап
|
||
|
||
Следующим этапом развития серии Build 060 становится интеграция построенного Pipeline в рабочую подсистему получения данных.
|
||
|
||
Наиболее логичным продолжением является:
|
||
|
||
```text
|
||
Build 060.7 — REST Trades Client
|
||
```
|
||
|
||
На этом этапе будет реализован специализированный клиент получения агрегированных сделок через REST API Dzengi, использующий полностью сформированный Pipeline:
|
||
|
||
```text
|
||
REST Request
|
||
|
||
↓
|
||
|
||
REST Response
|
||
|
||
↓
|
||
|
||
Schema Validation
|
||
|
||
↓
|
||
|
||
Parser
|
||
|
||
↓
|
||
|
||
Value Validation
|
||
|
||
↓
|
||
|
||
Mapper
|
||
|
||
↓
|
||
|
||
tuple[Trade]
|
||
```
|
||
|
||
Build 060.7 станет первым этапом, на котором сформированный Pipeline начнёт использоваться в реальном процессе получения исторических сделок. |