13 KiB
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