Files
dzentra_bot/docs/migrations/build_027.md

18 KiB
Raw Blame History

Build 027 — Каноническая модель Quote и специализированные контракты

Статус

Завершён


Цель Build

Создать каноническую внутреннюю модель текущей рыночной котировки Quote и специализированные контракты подсистемы Quotes Feed.

Build должен сформировать независимую от конкретной биржи модель рыночной котировки и определить архитектурные границы между:

  • источником сырого документа котировки;
  • обработчиком документа;
  • готовым потоком котировок;
  • потребителями Market Data Acquisition.

При этом существующий legacy-контур получения и использования цен не должен изменяться.


Место в плане миграции

Build 027 является вторым этапом миграции подсистемы Quotes Feed.

Полный утверждённый план:

Build 026 — Аудит текущего контура Quotes Feed
Build 027 — Каноническая модель Quote и специализированные контракты
Build 028 — Dzengi REST quote models, parser и validation
Build 029 — Dzengi mapper и Quotes Handler
Build 030 — Quotes Feed и регистрация в Acquisition Service
Build 031 — Подключение нового REST Quotes Feed под legacy ExchangeService facade
Build 032 — Канонический Quote Store
Build 033 — Перенос MarketPriceCache на Quote Store
Build 034 — Dzengi WebSocket quote parsing и адаптер
Build 035 — Перевод market runtime на Quotes Feed
Build 036 — Перевод read-only и UI-потребителей
Build 037 — Перевод execution-потребителей
Build 038 — Удаление legacy TickerPrice и market snapshot dict layer
Build 039 — Удаление legacy quote parsing и MarketPriceCache
Build 040 — Финальная архитектурная проверка Quotes Feed

Архитектурная граница Build

Build 027 ограничен двумя задачами:

  1. создание канонической модели Quote;
  2. создание специализированных контрактов Quotes Feed.

В рамках Build не реализуются:

  • REST-запрос котировки Dzengi;
  • модели REST-ответа Dzengi;
  • parsing ответа ticker/24hr;
  • schema validation ответа Dzengi;
  • value validation полей котировки;
  • mapping модели Dzengi в Quote;
  • QuotesHandler;
  • QuotesFeed;
  • регистрация потока в Acquisition Service;
  • хранение котировок;
  • WebSocket parsing;
  • изменение ExchangeService;
  • изменение MarketPriceCache;
  • изменение market runtime;
  • перевод UI-потребителей;
  • перевод execution-потребителей.

Эти изменения относятся к следующим Build.


Изменённые файлы

src/market_data/acquisition/
├── models/
│   └── quote.py
└── protocol.py

Всего изменено:

  • 2 файла.

1. Каноническая модель Quote

Файл:

src/market_data/acquisition/models/quote.py

Создана независимая от конкретной биржи immutable-модель:

@dataclass(frozen=True, slots=True)
class Quote:
    symbol: str

    last_price: Decimal
    bid_price: Decimal
    ask_price: Decimal

    exchange_timestamp: datetime | None
    received_at: datetime

    source: str

Назначение модели

Quote представляет текущий рыночный факт о котировке одного инструмента.

Модель является внутренней моделью слоя:

Market Data
    ↓
Market Data Acquisition
    ↓
Quotes Feed
    ↓
Quote

Она не зависит от:

  • API Dzengi;
  • формата ticker/24hr;
  • WebSocket-сообщений;
  • legacy-моделей integrations/exchange;
  • UI;
  • Execution;
  • конкретного способа хранения данных.

2. Поля модели Quote

symbol

symbol: str

Каноническое обозначение инструмента.

Пример:

BTC/USD

Поле не должно содержать транспортное или биржевое представление, специфичное для конкретного API, если оно отличается от канонического обозначения Dzentra.


last_price

last_price: Decimal

Последняя известная цена инструмента, полученная от источника.


bid_price

bid_price: Decimal

Лучшая доступная цена покупки.


ask_price

ask_price: Decimal

Лучшая доступная цена продажи.


exchange_timestamp

exchange_timestamp: datetime | None

Время рыночного события на стороне источника данных.

Поле является optional, поскольку конкретный источник или endpoint может не предоставлять достоверный timestamp события.


received_at

received_at: datetime

Время получения рыночных данных системой Dzentra.

Это позволяет независимо от наличия exchange_timestamp фиксировать момент поступления данных в систему.


source

source: str

Идентификатор источника рыночных данных.

Пример:

dzengi

Модель не фиксирует конкретный набор допустимых источников на уровне класса Quote.


3. Использование Decimal

Для канонических цен используется:

Decimal

а не:

float

Это позволяет избежать привязки новой внутренней модели к ограничениям legacy-кода и уменьшает риск потери точности при работе с денежными значениями.

Legacy-потребители при необходимости смогут получать преобразованное значение float через compatibility/facade-слой на следующих этапах миграции.


4. Immutable-модель

Модель объявлена как:

@dataclass(frozen=True, slots=True)

Это означает:

  • экземпляр Quote не изменяется после создания;
  • исключается случайная мутация рыночного факта;
  • модель имеет компактное представление через slots;
  • объект подходит для передачи между слоями системы как immutable value object.

Такой подход соответствует уже принятому направлению построения канонических моделей Market Data Acquisition.


5. Что сознательно не включено в Quote

В каноническую модель не включены поля:

is_fresh
age_seconds
freshness_status
spread_percent
runtime_key
received_monotonic

Причина: эти значения не являются исходным фактом котировки.

Они относятся к другим обязанностям системы.

Freshness

is_fresh
age_seconds
freshness_status

Это runtime-оценка актуальности данных.

Она должна вычисляться на основании времени получения или хранения котировки, а не быть частью исходного объекта Quote.

Spread

spread_percent

Это производное значение:

ask_price - bid_price

или его процентное представление.

Оно может быть вычислено отдельным processing/runtime-компонентом.

Runtime identity

runtime_key

Это идентификатор runtime-контекста, а не свойство рыночной котировки.

Monotonic clock

received_monotonic

Это внутренний технический механизм runtime/storage-слоя для измерения возраста данных.

Он не должен загрязнять каноническую модель рыночного факта.


6. Специализированные контракты Quotes Feed

В файл:

src/market_data/acquisition/protocol.py

добавлены три специализированных контракта:

QuoteDocumentSource
QuoteDocumentHandler
QuoteFeedProtocol

Архитектурная цепочка:

QuoteDocumentSource
        ↓
сырой документ
        ↓
QuoteDocumentHandler
        ↓
Quote
        ↓
QuoteFeedProtocol
        ↓
Acquisition Service

7. QuoteDocumentSource

Контракт:

@runtime_checkable
class QuoteDocumentSource(Protocol):
    def fetch_quote_document(
        self,
        symbol: str,
    ) -> object:
        """
        Получить декодированный транспортный документ текущей котировки.

        Источник не выполняет schema validation, parsing, value validation
        или mapping во внутреннюю модель Quote.
        """
        ...

Ответственность

QuoteDocumentSource отвечает только за получение сырого декодированного транспортного документа.

Он не должен:

  • проверять схему;
  • проверять значения;
  • выполнять mapping;
  • создавать Quote;
  • хранить котировку;
  • вычислять freshness;
  • обслуживать UI или Execution.

Для Dzengi конкретная реализация будет создана на следующих этапах.


8. QuoteDocumentHandler

Контракт:

@runtime_checkable
class QuoteDocumentHandler(Protocol):
    def handle_quote_document(
        self,
        document: object,
    ) -> Quote:
        """
        Преобразовать сырой документ в проверенную внутреннюю модель Quote.
        """
        ...

Ответственность

QuoteDocumentHandler определяет границу между сырым внешним документом и проверенной канонической моделью Quote.

Конкретная реализация должна организовать последовательность:

сырой документ
    ↓
schema validation
    ↓
parser
    ↓
value validation
    ↓
mapper
    ↓
Quote

Сам контракт не зависит от конкретной биржи.


9. QuoteFeedProtocol

Контракт:

@runtime_checkable
class QuoteFeedProtocol(Protocol):
    def load_quote(
        self,
        symbol: str,
    ) -> Quote:
        """
        Получить внутреннюю модель текущей котировки инструмента.
        """
        ...

Ответственность

QuoteFeedProtocol представляет готовый поток получения канонической текущей котировки для Acquisition Service.

Потребитель этого контракта не должен знать:

  • какая биржа является источником;
  • используется REST или другой транспорт;
  • как устроен внешний payload;
  • как выполняется parsing;
  • как выполняется validation;
  • как выполняется mapping.

Для потребителя существует только операция:

symbol → Quote

10. Соответствие паттерну Instrument Reference Data

Build 027 продолжает архитектурный подход, уже реализованный для Instrument Reference Data.

Instrument Reference Data

InstrumentDocumentSource
        ↓
InstrumentDocumentHandler
        ↓
InstrumentFeedProtocol
        ↓
Instrument

Quotes Feed

QuoteDocumentSource
        ↓
QuoteDocumentHandler
        ↓
QuoteFeedProtocol
        ↓
Quote

Таким образом, новая вертикаль Quotes Feed строится в соответствии с уже принятой архитектурой Market Data Acquisition, без создания альтернативного или параллельного архитектурного подхода.


11. Legacy-контур

Build 027 не изменяет существующие legacy-компоненты:

src/integrations/exchange/models.py
src/integrations/exchange/service.py
src/integrations/exchange/market_cache.py
src/integrations/exchange/market_stream.py
src/integrations/exchange/market_data_runner.py
src/integrations/exchange/ws_client.py

Продолжают работать без изменений:

TickerPrice
ExecutionPriceSnapshot
MarketPriceSnapshot
MarketPriceCache
ExchangeService.get_price()
ExchangeService.get_market_snapshot()
ExchangeService.get_execution_snapshot()
ExchangeService.get_fresh_market_snapshot()

На данном этапе новая модель Quote существует параллельно legacy-контуру и ещё не используется работающим ботом.

Это соответствует утверждённой стратегии безопасной миграции:

создать новый контур
    ↓
проверить новый контур
    ↓
подключить его под legacy facade
    ↓
поэтапно перевести потребителей
    ↓
удалить legacy только после полного переключения

12. Обратная совместимость

Build 027 полностью обратно совместим с существующим ботом.

Не изменены:

  • публичные методы ExchangeService;
  • форматы legacy snapshot;
  • MarketPriceCache;
  • market runtime;
  • Telegram UI;
  • trading strategies;
  • Execution;
  • существующие модели интеграционного слоя.

Новая модель и контракты пока не участвуют в runtime работающего приложения.


13. Проверка синтаксиса

Выполнена команда:

python -m py_compile \
  src/market_data/acquisition/models/quote.py \
  src/market_data/acquisition/protocol.py

Результат:

Успешно.
Ошибок синтаксиса и импортов не обнаружено.

14. Полная регрессия

Выполнена команда:

python -m pytest -q

Результат:

423 passed in 0.24s

Все существующие тесты проекта проходят.

Регрессий не обнаружено.


15. Критерии завершения

Build 027 считается завершённым, поскольку выполнены все его критерии:

  • создана каноническая модель Quote;
  • модель не зависит от Dzengi;
  • цены представлены через Decimal;
  • модель immutable;
  • разделены exchange_timestamp и received_at;
  • runtime-поля не включены в каноническую модель;
  • создан QuoteDocumentSource;
  • создан QuoteDocumentHandler;
  • создан QuoteFeedProtocol;
  • сохранён архитектурный паттерн существующей вертикали Instrument;
  • legacy-контур не изменён;
  • синтаксическая проверка проходит;
  • полная регрессия проходит;
  • 423 теста проходят успешно.

Итог

В рамках Build 027 создан фундамент канонической вертикали Quotes Feed.

Теперь архитектура содержит независимое представление текущей рыночной котировки:

Quote

и три специализированных контракта:

QuoteDocumentSource
QuoteDocumentHandler
QuoteFeedProtocol

Целевая архитектурная цепочка сформирована как:

Внешний источник
        ↓
QuoteDocumentSource
        ↓
сырой транспортный документ
        ↓
QuoteDocumentHandler
        ↓
schema validation
        ↓
parser
        ↓
value validation
        ↓
mapper
        ↓
Quote
        ↓
QuoteFeedProtocol
        ↓
Acquisition Service

Build 027 завершён без изменения поведения работающего бота и без преждевременного вмешательства в legacy-контур.

Следующий этап:

Build 028 — Dzengi REST quote models, parser и validation