build 039: complete Quotes Feed migration foundation

This commit is contained in:
2026-07-14 09:58:16 +03:00
parent 26deb861bc
commit 7b62873832
443 changed files with 80452 additions and 1335 deletions

View File

@@ -0,0 +1,544 @@
# 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
```