Files
dzentra_bot/docs/migrations/build_018.md

14 KiB
Raw Blame History

Build 018 — Переключение get_symbol_runtime_status()

Статус

COMPLETE


Цель

Перевести определение торгового состояния инструмента, используемое методом:

ExchangeService.get_symbol_runtime_status()

на каноническую классификацию статусов из подсистемы:

src/market_data/acquisition/

при полном сохранении существующего внешнего контракта ExchangeRuntimeStatus, поведения legacy-кода, UI и runtime-потребителей.


Исходное состояние

До Build 018 классификация биржевых статусов инструмента находилась непосредственно в legacy exchange-слое:

src/integrations/exchange/status.py

и основывалась на локальных наборах:

OPEN_STATUSES
BREAK_STATUSES

Метод:

build_market_status_from_symbol_status()

самостоятельно определял одно из состояний:

OPEN
NOT_TRADABLE
BREAK
UNKNOWN

Это означало, что предметная классификация торгового состояния инструмента оставалась внутри legacy exchange integration layer.


Архитектурное решение

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

src/market_data/acquisition/models/status.py

Добавлены следующие сущности:

InstrumentTradingState
InstrumentStatusClassification
classify_instrument_status()

Теперь архитектурная цепочка имеет вид:

raw exchange status
        ↓
classify_instrument_status()
        ↓
InstrumentStatusClassification
        ↓
build_market_status_from_symbol_status()
        ↓
ExchangeRuntimeStatus
        ↓
legacy runtime / UI / trading consumers

Таким образом:

  • market_data/acquisition отвечает за предметную классификацию состояния инструмента;
  • integrations/exchange/status.py сохраняет compatibility-функцию преобразования результата в существующий ExchangeRuntimeStatus;
  • существующие runtime-потребители продолжают работать без изменения публичного контракта.

Добавленный файл

src/market_data/acquisition/models/status.py

Файл содержит каноническую модель классификации торгового состояния инструмента.

Основные сущности:

class InstrumentTradingState(StrEnum):
    OPEN = "OPEN"
    NOT_TRADABLE = "NOT_TRADABLE"
    BREAK = "BREAK"
    UNKNOWN = "UNKNOWN"
@dataclass(frozen=True, slots=True)
class InstrumentStatusClassification:
    state: InstrumentTradingState
    normalized_status: str | None

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

def classify_instrument_status(
    raw_status: str | None,
) -> InstrumentStatusClassification:

Она выполняет:

  1. нормализацию входного статуса;
  2. классификацию открытого рынка;
  3. классификацию неторгуемого инструмента;
  4. классификацию временной остановки торгов;
  5. возврат UNKNOWN для неизвестного или отсутствующего статуса.

Канонические состояния

Подсистема market_data/acquisition различает четыре предметных состояния:

OPEN
NOT_TRADABLE
BREAK
UNKNOWN

OPEN

Инструмент доступен для торговли.

Поддерживаются существующие legacy-статусы:

TRADING
OPEN
ACTIVE
ENABLED
ONLINE

NOT_TRADABLE

Инструмент существует, но недоступен для обычной торговли.

Поддерживаются:

NOT_TRADABLE
TRADING_DISABLED
MARKET_DISABLED
UNAVAILABLE_FOR_TRADING
CLOSE_ONLY
REDUCE_ONLY
VIEW_ONLY

BREAK

Торги временно остановлены или приостановлены.

Поддерживаются:

BREAK
CLOSED
HALT
HALTED
PAUSED
SUSPENDED
DISABLED
SETTLING
POST_ONLY

UNKNOWN

Используется для:

  • неизвестного статуса;
  • пустой строки;
  • None;
  • значения, отсутствующего в известных классификационных наборах.

Изменение legacy exchange-слоя

Из файла:

src/integrations/exchange/status.py

удалена собственная предметная классификация через публичные наборы:

OPEN_STATUSES
BREAK_STATUSES

Вместо неё используются:

from src.market_data.acquisition.models.status import (
    InstrumentTradingState,
    classify_instrument_status,
)

Функция:

build_market_status_from_symbol_status()

теперь сначала вызывает:

classification = classify_instrument_status(raw_status)

а затем преобразует каноническое состояние в существующий legacy-контракт:

InstrumentTradingState.OPEN
        ↓
ExchangeRuntimeStatus(code=OPEN)

InstrumentTradingState.NOT_TRADABLE
        ↓
ExchangeRuntimeStatus(code=BREAK, reason=market_not_tradable)

InstrumentTradingState.BREAK
        ↓
ExchangeRuntimeStatus(code=BREAK, reason=market_break)

InstrumentTradingState.UNKNOWN
        ↓
ExchangeRuntimeStatus(code=UNKNOWN, reason=market_status_unknown)

Сохранённый публичный контракт

Build 018 не изменяет структуру:

ExchangeRuntimeStatus

Сохранены поля:

code
is_open
is_available
is_auth_ok
title
message
ui_line
reason
symbol
raw_status
raw_error

Также сохранён compatibility-метод:

ExchangeRuntimeStatus.as_dict()

Это позволяет не изменять существующие runtime-, UI- и trading-потребители.


Поведение get_symbol_runtime_status()

Метод:

ExchangeService.get_symbol_runtime_status()

сохранил существующий внешний контракт и последовательность обработки.

Архитектурно поток остаётся следующим:

requested symbol
        ↓
validate_symbol()
        ↓
ExchangeSymbol
        ↓
raw symbol status
        ↓
build_market_status_from_symbol_status()
        ↓
classify_instrument_status()
        ↓
ExchangeRuntimeStatus

Для открытого рынка дополнительно сохраняется проверка свежести рыночного snapshot:

OPEN
  ↓
get_fresh_market_snapshot()
  ↓
age_seconds > 60
  ↓
STALE_MARKET_DATA / BREAK

Stale threshold сохранён:

60 секунд

Проверка stale market data выполняется только для рынка, первоначально классифицированного как OPEN.


Сохранённое поведение

Build 018 сохраняет следующие legacy-сценарии:

  • mock exchange;
  • явный symbol;
  • использование default_symbol, если аргумент равен None;
  • invalid symbol;
  • ошибка при validation;
  • OPEN;
  • NOT_TRADABLE;
  • BREAK;
  • UNKNOWN;
  • stale market data;
  • отсутствие age_seconds;
  • ошибка получения snapshot;
  • нормализованный matched symbol;
  • существующий ExchangeRuntimeStatus;
  • существующие reason;
  • существующие UI-тексты;
  • существующие raw_status.

Кэширование и сетевые запросы

Build 018 не добавляет:

  • новый cache;
  • новый REST-запрос;
  • дополнительную загрузку exchangeInfo;
  • дополнительный acquisition service;
  • отдельный status feed runtime.

get_symbol_runtime_status() продолжает использовать существующий путь:

validate_symbol()
        ↓
get_exchange_symbols()
        ↓
existing ExchangeService cache
        ↓
Instrument Acquisition path

Таким образом, миграция не создаёт параллельного источника Instrument Reference Data.


Пустые status feed-файлы

На момент Build 018 следующие файлы существуют, но остаются пустыми:

src/market_data/acquisition/models/status.py
src/market_data/acquisition/handlers/status_handler.py
src/market_data/acquisition/feeds/status_feed.py

После Build 018 файл:

src/market_data/acquisition/models/status.py

получил реализацию канонической классификации торгового состояния инструмента.

Файлы:

src/market_data/acquisition/handlers/status_handler.py
src/market_data/acquisition/feeds/status_feed.py

в рамках Build 018 намеренно не реализуются.

Причина: текущая задача не создаёт отдельный Status Feed и не должна вводить новый источник сетевых запросов или параллельный runtime-путь.


Добавленные тесты

Добавлен тестовый файл:

tests/unit/market_data/acquisition/models/test_instrument_status.py

Он проверяет каноническую классификацию:

  • все открытые статусы;
  • все неторгуемые статусы;
  • все break-статусы;
  • регистронезависимость;
  • удаление внешних пробелов;
  • неизвестный статус;
  • пустой статус;
  • None;
  • immutable-контракт результата.

Также расширено покрытие:

tests/unit/integrations/exchange/test_status.py

для проверки compatibility-преобразования:

InstrumentStatusClassification
        ↓
ExchangeRuntimeStatus

Целевой regression-набор метода находится в:

tests/unit/integrations/exchange/test_service_symbol_runtime_status.py

Дублирующий тестовый файл:

tests/unit/integrations/exchange/test_service_runtime_status.py

удалён.


Проверка целевого runtime-контракта

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

python -m pytest \
  tests/unit/integrations/exchange/test_service_symbol_runtime_status.py \
  -q

Результат:

34 passed in 0.09s

Проверка компиляции

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

python -m py_compile \
  src/market_data/acquisition/models/status.py \
  src/integrations/exchange/status.py \
  src/integrations/exchange/service.py \
  tests/unit/market_data/acquisition/models/test_instrument_status.py \
  tests/unit/integrations/exchange/test_status.py \
  tests/unit/integrations/exchange/test_service_symbol_runtime_status.py

Результат:

Успешно.
Ошибок компиляции нет.

Полный regression suite

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

python -m pytest -q

Результат:

387 passed in 0.20s

Архитектурный результат

После Build 018 ответственность разделена следующим образом:

market_data/acquisition/models/status.py
    ↓
каноническая предметная классификация статуса инструмента

integrations/exchange/status.py
    ↓
compatibility mapping в legacy ExchangeRuntimeStatus

integrations/exchange/service.py
    ↓
runtime orchestration, validation и stale market data check

runtime / UI / trading consumers
    ↓
продолжают использовать существующий ExchangeRuntimeStatus

В результате:

  • предметная классификация статуса инструмента больше не принадлежит legacy exchange-слою;
  • дублирование OPEN_STATUSES / BREAK_STATUSES устранено;
  • ExchangeRuntimeStatus сохранён как compatibility boundary;
  • существующие потребители не требуют массового рефакторинга;
  • новый REST-путь не создан;
  • новый cache не создан;
  • поведение работающего бота сохранено.

Итог

BUILD 018 — COMPLETE

Build 018 завершён при полном прохождении целевых тестов, компиляции и общего regression suite:

34 targeted tests passed
387 total tests passed