Files
dzentra_bot/docs/migrations/build_016.md

13 KiB
Raw Blame History

Build 016 — Перевод normalize_symbol() / symbol_candidates()

Статус

COMPLETE


Цель Build

Перенести каноническую реализацию функций нормализации и формирования кандидатов торгового символа из legacy-подсистемы:

src/integrations/exchange/symbol_utils.py

в новую подсистему Market Data Acquisition:

src/market_data/acquisition/symbols.py

при этом:

  • полностью сохранить существующее поведение;
  • не нарушить работу legacy-кода;
  • сохранить старый import path;
  • исключить дублирование реализации;
  • обеспечить постепенную миграцию без остановки работающего бота.

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

До Build 016 функции:

normalize_symbol()
symbol_candidates()

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

src/integrations/exchange/symbol_utils.py

Их использовал:

src/integrations/exchange/service.py

В частности, функции участвовали в:

  • нормализации запрошенного торгового символа;
  • проверке существования инструмента;
  • формировании альтернативных представлений символа;
  • сопоставлении символов с данными exchangeInfo;
  • поиске торговой комиссии для инструмента.

Legacy-зависимость выглядела следующим образом:

ExchangeService
    │
    ▼
src.integrations.exchange.symbol_utils
    │
    ├── normalize_symbol()
    └── symbol_candidates()

Целевая архитектура

После Build 016 каноническая реализация находится в:

src/market_data/acquisition/symbols.py

Legacy-модуль:

src/integrations/exchange/symbol_utils.py

сохранён как compatibility facade.

Итоговая зависимость:

ExchangeService
    │
    ▼
src.integrations.exchange.symbol_utils
    │
    │ compatibility facade
    ▼
src.market_data.acquisition.symbols
    │
    ├── normalize_symbol()
    └── symbol_candidates()

Это позволяет сохранить существующий production-код без массового изменения импортов и одновременно установить новую каноническую точку владения логикой.


Созданный файл

Создан:

src/market_data/acquisition/symbols.py

Он содержит единственную каноническую реализацию:

def normalize_symbol(raw_symbol: str) -> str:
    ...


def symbol_candidates(raw_symbol: str) -> list[str]:
    ...

Изменённый legacy-модуль

Файл:

src/integrations/exchange/symbol_utils.py

больше не содержит собственной реализации алгоритмов.

Он импортирует функции непосредственно из:

src.market_data.acquisition.symbols

и предоставляет их через прежний import path.

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

legacy_normalize_symbol is new_normalize_symbol

и:

legacy_symbol_candidates is new_symbol_candidates

возвращают:

True

Это подтверждает отсутствие копирования или дублирования функций.


Зафиксированный контракт normalize_symbol()

Функция сохраняет существующее legacy-поведение.

Нормализация регистра

Пример:

btc/usd

преобразуется в:

BTC/USD

Удаление внешних пробелов

Пример:

  btc/usd

преобразуется в:

BTC/USD

Внутренние пробелы не удаляются

Функция normalize_symbol() не выполняет удаление внутренних пробелов.

%2F не декодируется

Пример:

BTC%2FUSD

остаётся:

BTC%2FUSD

Декодирование разделителя выполняется только на этапе формирования кандидатов.

Суффикс _LEVERAGE не добавляется автоматически

Функция не модифицирует семантику инструмента и не добавляет leverage-суффикс.

Существующий _LEVERAGE сохраняется

Если суффикс уже присутствует в исходном символе, он сохраняется.


Зафиксированный контракт symbol_candidates()

Функция формирует упорядоченный список допустимых представлений символа.

Пустое значение

Для пустого значения возвращается:

[]

Первый кандидат

Первым всегда является результат:

normalize_symbol(raw_symbol)

Декодирование %2F

Если символ содержит:

%2F

добавляется кандидат с:

/

Удаление обычных внутренних пробелов

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

Порядок преобразований сохраняется

Порядок кандидатов является частью compatibility-контракта:

1. Нормализованное исходное значение.
2. Значение после замены %2F на /.
3. Значение после удаления обычных пробелов.

Дубликаты не добавляются

Если очередное преобразование не изменило значение, новый кандидат не создаётся.

Каждый вызов возвращает новый список

Результат не переиспользует mutable list между вызовами.

Исходная строка не изменяется

Функция не мутирует входное значение.

Табуляция не удаляется

Удаляются только обычные пробелы:

" "

Внутренняя табуляция сохраняется.

Перевод строки не удаляется

Внутренний символ новой строки сохраняется.

_LEVERAGE сохраняется

Функция не изменяет существующий leverage-суффикс.


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

Создан:

tests/unit/market_data/acquisition/test_symbols.py

Тесты фиксируют канонический контракт новой реализации.

Результат:

29 passed in 0.02s

Также создан:

tests/unit/integrations/exchange/test_symbol_utils.py

Этот набор тестов проверяет compatibility facade и эквивалентность legacy и новой реализации.

Результат:

22 passed in 0.01s

Проверка identity compatibility

Отдельно подтверждено, что legacy facade возвращает непосредственно те же функции:

assert legacy_normalize_symbol is new_normalize_symbol
assert legacy_symbol_candidates is new_symbol_candidates

Следовательно:

  • отдельной legacy-реализации больше нет;
  • wrapper-функции отсутствуют;
  • поведение не может разойтись из-за двух независимых реализаций.

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

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

python -m py_compile \
  src/market_data/acquisition/symbols.py \
  src/integrations/exchange/symbol_utils.py \
  tests/unit/market_data/acquisition/test_symbols.py \
  tests/unit/integrations/exchange/test_symbol_utils.py

Результат:

Успешно.
Синтаксических ошибок нет.

Полный regression suite

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

python -m pytest -q

Результат:

271 passed in 0.17s

До Build 016 полный набор проекта содержал:

220 passed

Build 016 добавил:

51 test

Итог:

220 + 51 = 271

Все тесты проекта проходят успешно.


Финальная проверка зависимостей

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

grep -RIn \
  --exclude-dir="__pycache__" \
  --exclude="*.pyc" \
  -E "normalize_symbol|symbol_candidates|def normalize_symbol|def symbol_candidates" \
  src tests

Проверка подтвердила:

  • normalize_symbol() реализована только в:
src/market_data/acquisition/symbols.py
  • symbol_candidates() реализована только там же;
  • src/integrations/exchange/symbol_utils.py содержит только compatibility imports и exports;
  • ExchangeService продолжает использовать существующий стабильный legacy import path;
  • дублирование реализации отсутствует.

Состояние ExchangeService

В рамках Build 016 файл:

src/integrations/exchange/service.py

не требовал изменения import path.

Он продолжает использовать:

from src.integrations.exchange.symbol_utils import (
    normalize_symbol,
    symbol_candidates,
)

Это сделано намеренно.

Legacy import path остаётся стабильным, а фактическая реализация уже принадлежит новой подсистеме:

src.market_data.acquisition

Такой подход соответствует принятой стратегии постепенной миграции:

сначала новая каноническая реализация
        ↓
затем compatibility facade
        ↓
старый production-код продолжает работать
        ↓
последующая миграция потребителей выполняется отдельно

Что не изменялось

В рамках Build 016 намеренно не изменялись:

  • публичный контракт ExchangeService;
  • сигнатуры normalize_symbol();
  • сигнатуры symbol_candidates();
  • логика validate_symbol();
  • формат SymbolValidationResult;
  • порядок кандидатов;
  • алгоритм сопоставления символов;
  • поведение mock mode;
  • production-поведение работающего бота.

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

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

market_data/acquisition/symbols.py
    │
    └── каноническая логика нормализации
        и формирования кандидатов символа

integrations/exchange/symbol_utils.py
    │
    └── compatibility facade для legacy-кода

integrations/exchange/service.py
    │
    └── существующий production consumer

Таким образом, новая подсистема получила владение логикой идентификации торговых символов без нарушения существующих зависимостей.


Итоговый статус

Все критерии Build 016 выполнены:

  • каноническая реализация перенесена в market_data;
  • legacy import path сохранён;
  • дублирование реализации устранено;
  • существующий контракт зафиксирован тестами;
  • compatibility facade проверен;
  • identity функций подтверждена;
  • компиляция успешна;
  • полный regression suite успешен;
  • production-поведение не изменено.
BUILD 016 — COMPLETE