Files
dzentra_bot/docs/migrations/build_018.md

589 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Build 018 — Переключение `get_symbol_runtime_status()`
## Статус
**COMPLETE**
---
## Цель
Перевести определение торгового состояния инструмента, используемое методом:
```python
ExchangeService.get_symbol_runtime_status()
```
на каноническую классификацию статусов из подсистемы:
```text
src/market_data/acquisition/
```
при полном сохранении существующего внешнего контракта `ExchangeRuntimeStatus`, поведения legacy-кода, UI и runtime-потребителей.
---
## Исходное состояние
До Build 018 классификация биржевых статусов инструмента находилась непосредственно в legacy exchange-слое:
```text
src/integrations/exchange/status.py
```
и основывалась на локальных наборах:
```python
OPEN_STATUSES
BREAK_STATUSES
```
Метод:
```python
build_market_status_from_symbol_status()
```
самостоятельно определял одно из состояний:
```text
OPEN
NOT_TRADABLE
BREAK
UNKNOWN
```
Это означало, что предметная классификация торгового состояния инструмента оставалась внутри legacy exchange integration layer.
---
## Архитектурное решение
Каноническая классификация статуса инструмента перенесена в:
```text
src/market_data/acquisition/models/status.py
```
Добавлены следующие сущности:
```python
InstrumentTradingState
InstrumentStatusClassification
classify_instrument_status()
```
Теперь архитектурная цепочка имеет вид:
```text
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-потребители продолжают работать без изменения публичного контракта.
---
## Добавленный файл
```text
src/market_data/acquisition/models/status.py
```
Файл содержит каноническую модель классификации торгового состояния инструмента.
Основные сущности:
```python
class InstrumentTradingState(StrEnum):
OPEN = "OPEN"
NOT_TRADABLE = "NOT_TRADABLE"
BREAK = "BREAK"
UNKNOWN = "UNKNOWN"
```
```python
@dataclass(frozen=True, slots=True)
class InstrumentStatusClassification:
state: InstrumentTradingState
normalized_status: str | None
```
Основная функция:
```python
def classify_instrument_status(
raw_status: str | None,
) -> InstrumentStatusClassification:
```
Она выполняет:
1. нормализацию входного статуса;
2. классификацию открытого рынка;
3. классификацию неторгуемого инструмента;
4. классификацию временной остановки торгов;
5. возврат `UNKNOWN` для неизвестного или отсутствующего статуса.
---
## Канонические состояния
Подсистема `market_data/acquisition` различает четыре предметных состояния:
```text
OPEN
NOT_TRADABLE
BREAK
UNKNOWN
```
### `OPEN`
Инструмент доступен для торговли.
Поддерживаются существующие legacy-статусы:
```text
TRADING
OPEN
ACTIVE
ENABLED
ONLINE
```
### `NOT_TRADABLE`
Инструмент существует, но недоступен для обычной торговли.
Поддерживаются:
```text
NOT_TRADABLE
TRADING_DISABLED
MARKET_DISABLED
UNAVAILABLE_FOR_TRADING
CLOSE_ONLY
REDUCE_ONLY
VIEW_ONLY
```
### `BREAK`
Торги временно остановлены или приостановлены.
Поддерживаются:
```text
BREAK
CLOSED
HALT
HALTED
PAUSED
SUSPENDED
DISABLED
SETTLING
POST_ONLY
```
### `UNKNOWN`
Используется для:
- неизвестного статуса;
- пустой строки;
- `None`;
- значения, отсутствующего в известных классификационных наборах.
---
## Изменение legacy exchange-слоя
Из файла:
```text
src/integrations/exchange/status.py
```
удалена собственная предметная классификация через публичные наборы:
```python
OPEN_STATUSES
BREAK_STATUSES
```
Вместо неё используются:
```python
from src.market_data.acquisition.models.status import (
InstrumentTradingState,
classify_instrument_status,
)
```
Функция:
```python
build_market_status_from_symbol_status()
```
теперь сначала вызывает:
```python
classification = classify_instrument_status(raw_status)
```
а затем преобразует каноническое состояние в существующий legacy-контракт:
```text
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 не изменяет структуру:
```python
ExchangeRuntimeStatus
```
Сохранены поля:
```text
code
is_open
is_available
is_auth_ok
title
message
ui_line
reason
symbol
raw_status
raw_error
```
Также сохранён compatibility-метод:
```python
ExchangeRuntimeStatus.as_dict()
```
Это позволяет не изменять существующие runtime-, UI- и trading-потребители.
---
## Поведение `get_symbol_runtime_status()`
Метод:
```python
ExchangeService.get_symbol_runtime_status()
```
сохранил существующий внешний контракт и последовательность обработки.
Архитектурно поток остаётся следующим:
```text
requested symbol
validate_symbol()
ExchangeSymbol
raw symbol status
build_market_status_from_symbol_status()
classify_instrument_status()
ExchangeRuntimeStatus
```
Для открытого рынка дополнительно сохраняется проверка свежести рыночного snapshot:
```text
OPEN
get_fresh_market_snapshot()
age_seconds > 60
STALE_MARKET_DATA / BREAK
```
Stale threshold сохранён:
```text
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()` продолжает использовать существующий путь:
```text
validate_symbol()
get_exchange_symbols()
existing ExchangeService cache
Instrument Acquisition path
```
Таким образом, миграция не создаёт параллельного источника Instrument Reference Data.
---
## Пустые status feed-файлы
На момент Build 018 следующие файлы существуют, но остаются пустыми:
```text
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 файл:
```text
src/market_data/acquisition/models/status.py
```
получил реализацию канонической классификации торгового состояния инструмента.
Файлы:
```text
src/market_data/acquisition/handlers/status_handler.py
src/market_data/acquisition/feeds/status_feed.py
```
в рамках Build 018 намеренно не реализуются.
Причина: текущая задача не создаёт отдельный Status Feed и не должна вводить новый источник сетевых запросов или параллельный runtime-путь.
---
## Добавленные тесты
Добавлен тестовый файл:
```text
tests/unit/market_data/acquisition/models/test_instrument_status.py
```
Он проверяет каноническую классификацию:
- все открытые статусы;
- все неторгуемые статусы;
- все break-статусы;
- регистронезависимость;
- удаление внешних пробелов;
- неизвестный статус;
- пустой статус;
- `None`;
- immutable-контракт результата.
Также расширено покрытие:
```text
tests/unit/integrations/exchange/test_status.py
```
для проверки compatibility-преобразования:
```text
InstrumentStatusClassification
ExchangeRuntimeStatus
```
Целевой regression-набор метода находится в:
```text
tests/unit/integrations/exchange/test_service_symbol_runtime_status.py
```
Дублирующий тестовый файл:
```text
tests/unit/integrations/exchange/test_service_runtime_status.py
```
удалён.
---
## Проверка целевого runtime-контракта
Выполнена команда:
```bash
python -m pytest \
tests/unit/integrations/exchange/test_service_symbol_runtime_status.py \
-q
```
Результат:
```text
34 passed in 0.09s
```
---
## Проверка компиляции
Выполнена команда:
```bash
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
```
Результат:
```text
Успешно.
Ошибок компиляции нет.
```
---
## Полный regression suite
Выполнена команда:
```bash
python -m pytest -q
```
Результат:
```text
387 passed in 0.20s
```
---
## Архитектурный результат
После Build 018 ответственность разделена следующим образом:
```text
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 не создан;
- поведение работающего бота сохранено.
---
## Итог
```text
BUILD 018 — COMPLETE
```
Build 018 завершён при полном прохождении целевых тестов, компиляции и общего regression suite:
```text
34 targeted tests passed
387 total tests passed
```