Files
dzentra_bot/docs/migrations/build_016.md

544 lines
13 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 016 — Перевод `normalize_symbol()` / `symbol_candidates()`
## Статус
**COMPLETE**
---
## Цель Build
Перенести каноническую реализацию функций нормализации и формирования кандидатов торгового символа из legacy-подсистемы:
```text
src/integrations/exchange/symbol_utils.py
```
в новую подсистему Market Data Acquisition:
```text
src/market_data/acquisition/symbols.py
```
при этом:
- полностью сохранить существующее поведение;
- не нарушить работу legacy-кода;
- сохранить старый import path;
- исключить дублирование реализации;
- обеспечить постепенную миграцию без остановки работающего бота.
---
## Исходное состояние
До Build 016 функции:
```python
normalize_symbol()
symbol_candidates()
```
были реализованы непосредственно в:
```text
src/integrations/exchange/symbol_utils.py
```
Их использовал:
```text
src/integrations/exchange/service.py
```
В частности, функции участвовали в:
- нормализации запрошенного торгового символа;
- проверке существования инструмента;
- формировании альтернативных представлений символа;
- сопоставлении символов с данными `exchangeInfo`;
- поиске торговой комиссии для инструмента.
Legacy-зависимость выглядела следующим образом:
```text
ExchangeService
src.integrations.exchange.symbol_utils
├── normalize_symbol()
└── symbol_candidates()
```
---
## Целевая архитектура
После Build 016 каноническая реализация находится в:
```text
src/market_data/acquisition/symbols.py
```
Legacy-модуль:
```text
src/integrations/exchange/symbol_utils.py
```
сохранён как compatibility facade.
Итоговая зависимость:
```text
ExchangeService
src.integrations.exchange.symbol_utils
│ compatibility facade
src.market_data.acquisition.symbols
├── normalize_symbol()
└── symbol_candidates()
```
Это позволяет сохранить существующий production-код без массового изменения импортов и одновременно установить новую каноническую точку владения логикой.
---
## Созданный файл
Создан:
```text
src/market_data/acquisition/symbols.py
```
Он содержит единственную каноническую реализацию:
```python
def normalize_symbol(raw_symbol: str) -> str:
...
def symbol_candidates(raw_symbol: str) -> list[str]:
...
```
---
## Изменённый legacy-модуль
Файл:
```text
src/integrations/exchange/symbol_utils.py
```
больше не содержит собственной реализации алгоритмов.
Он импортирует функции непосредственно из:
```text
src.market_data.acquisition.symbols
```
и предоставляет их через прежний import path.
Таким образом:
```python
legacy_normalize_symbol is new_normalize_symbol
```
и:
```python
legacy_symbol_candidates is new_symbol_candidates
```
возвращают:
```text
True
```
Это подтверждает отсутствие копирования или дублирования функций.
---
## Зафиксированный контракт `normalize_symbol()`
Функция сохраняет существующее legacy-поведение.
### Нормализация регистра
Пример:
```text
btc/usd
```
преобразуется в:
```text
BTC/USD
```
### Удаление внешних пробелов
Пример:
```text
btc/usd
```
преобразуется в:
```text
BTC/USD
```
### Внутренние пробелы не удаляются
Функция `normalize_symbol()` не выполняет удаление внутренних пробелов.
### `%2F` не декодируется
Пример:
```text
BTC%2FUSD
```
остаётся:
```text
BTC%2FUSD
```
Декодирование разделителя выполняется только на этапе формирования кандидатов.
### Суффикс `_LEVERAGE` не добавляется автоматически
Функция не модифицирует семантику инструмента и не добавляет leverage-суффикс.
### Существующий `_LEVERAGE` сохраняется
Если суффикс уже присутствует в исходном символе, он сохраняется.
---
## Зафиксированный контракт `symbol_candidates()`
Функция формирует упорядоченный список допустимых представлений символа.
### Пустое значение
Для пустого значения возвращается:
```python
[]
```
### Первый кандидат
Первым всегда является результат:
```python
normalize_symbol(raw_symbol)
```
### Декодирование `%2F`
Если символ содержит:
```text
%2F
```
добавляется кандидат с:
```text
/
```
### Удаление обычных внутренних пробелов
После декодирования разделителя формируется вариант без обычных пробелов.
### Порядок преобразований сохраняется
Порядок кандидатов является частью compatibility-контракта:
```text
1. Нормализованное исходное значение.
2. Значение после замены %2F на /.
3. Значение после удаления обычных пробелов.
```
### Дубликаты не добавляются
Если очередное преобразование не изменило значение, новый кандидат не создаётся.
### Каждый вызов возвращает новый список
Результат не переиспользует mutable list между вызовами.
### Исходная строка не изменяется
Функция не мутирует входное значение.
### Табуляция не удаляется
Удаляются только обычные пробелы:
```text
" "
```
Внутренняя табуляция сохраняется.
### Перевод строки не удаляется
Внутренний символ новой строки сохраняется.
### `_LEVERAGE` сохраняется
Функция не изменяет существующий leverage-суффикс.
---
## Добавленные тесты
Создан:
```text
tests/unit/market_data/acquisition/test_symbols.py
```
Тесты фиксируют канонический контракт новой реализации.
Результат:
```text
29 passed in 0.02s
```
Также создан:
```text
tests/unit/integrations/exchange/test_symbol_utils.py
```
Этот набор тестов проверяет compatibility facade и эквивалентность legacy и новой реализации.
Результат:
```text
22 passed in 0.01s
```
---
## Проверка identity compatibility
Отдельно подтверждено, что legacy facade возвращает непосредственно те же функции:
```python
assert legacy_normalize_symbol is new_normalize_symbol
assert legacy_symbol_candidates is new_symbol_candidates
```
Следовательно:
- отдельной legacy-реализации больше нет;
- wrapper-функции отсутствуют;
- поведение не может разойтись из-за двух независимых реализаций.
---
## Проверка компиляции
Выполнена команда:
```bash
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
```
Результат:
```text
Успешно.
Синтаксических ошибок нет.
```
---
## Полный regression suite
Выполнена команда:
```bash
python -m pytest -q
```
Результат:
```text
271 passed in 0.17s
```
До Build 016 полный набор проекта содержал:
```text
220 passed
```
Build 016 добавил:
```text
51 test
```
Итог:
```text
220 + 51 = 271
```
Все тесты проекта проходят успешно.
---
## Финальная проверка зависимостей
Выполнена команда:
```bash
grep -RIn \
--exclude-dir="__pycache__" \
--exclude="*.pyc" \
-E "normalize_symbol|symbol_candidates|def normalize_symbol|def symbol_candidates" \
src tests
```
Проверка подтвердила:
- `normalize_symbol()` реализована только в:
```text
src/market_data/acquisition/symbols.py
```
- `symbol_candidates()` реализована только там же;
- `src/integrations/exchange/symbol_utils.py` содержит только compatibility imports и exports;
- `ExchangeService` продолжает использовать существующий стабильный legacy import path;
- дублирование реализации отсутствует.
---
## Состояние `ExchangeService`
В рамках Build 016 файл:
```text
src/integrations/exchange/service.py
```
не требовал изменения import path.
Он продолжает использовать:
```python
from src.integrations.exchange.symbol_utils import (
normalize_symbol,
symbol_candidates,
)
```
Это сделано намеренно.
Legacy import path остаётся стабильным, а фактическая реализация уже принадлежит новой подсистеме:
```text
src.market_data.acquisition
```
Такой подход соответствует принятой стратегии постепенной миграции:
```text
сначала новая каноническая реализация
затем compatibility facade
старый production-код продолжает работать
последующая миграция потребителей выполняется отдельно
```
---
## Что не изменялось
В рамках Build 016 намеренно не изменялись:
- публичный контракт `ExchangeService`;
- сигнатуры `normalize_symbol()`;
- сигнатуры `symbol_candidates()`;
- логика `validate_symbol()`;
- формат `SymbolValidationResult`;
- порядок кандидатов;
- алгоритм сопоставления символов;
- поведение mock mode;
- production-поведение работающего бота.
---
## Архитектурный результат
После Build 016 ответственность распределена следующим образом:
```text
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-поведение не изменено.
```text
BUILD 016 — COMPLETE
```