feat: add market data architecture and complete migration through build 039

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

View File

@@ -0,0 +1,999 @@
# Build 007 — Protocol и Exceptions
**Статус:** Завершён
**Подсистема:** `market_data/acquisition`
**Область:** Instrument Reference Data
**Тип изменения:** Изолированное расширение новой архитектуры без подключения к production runtime
**Результат полного набора тестов:** `113 passed`
---
## 1. Цель Build 007
Цель Build 007 — зафиксировать минимальные публичные контракты взаимодействия между следующими слоями новой подсистемы Instrument Reference Data:
```text
Build 008 — Dzengi REST Adapter
Build 009 — Instrument Handler
Build 010 — Instrument Feed
Build 011 — Registry
Build 012 — Acquisition Service
```
Также добавлена специализированная ошибка транспортного уровня для будущего REST adapter.
После завершения Build 007 архитектурная последовательность выглядит следующим образом:
```text
InstrumentDocumentSource
InstrumentDocumentHandler
InstrumentFeedProtocol
Registry
Acquisition Service
```
Build 007 не реализует получение или обработку данных и не подключает новую подсистему к существующему `ExchangeService`.
---
## 2. Почему Build 007 выполняется именно сейчас
До начала Build 007 были завершены предыдущие этапы:
```text
Build 001 — внутренняя модель Instrument Reference Data
Build 002 — raw-модели ответа Dzengi
Build 003 — структурная валидация exchangeInfo
Build 004 — parser exchangeInfo
Build 005 — value validation
Build 006 — mapper Dzengi → Instrument
```
К началу Build 007 уже существовал полный pipeline преобразования заранее полученного JSON-документа:
```text
raw JSON document
Schema Validation
Parser
Dzengi Raw Models
Value Validation
Mapper
tuple[Instrument, ...]
```
Следующие Build должны добавить транспортный источник, handler, feed, registry и acquisition service.
Перед их реализацией необходимо было определить минимальные интерфейсы взаимодействия между этими слоями и добавить специализированную ошибку транспортного уровня.
---
## 3. Архитектурная граница Build 007
Build 007 отвечает только за:
```text
определение контракта источника сырого документа;
определение контракта обработчика сырого документа;
определение контракта источника готовых Instrument;
добавление ошибки транспортного уровня.
```
Build 007 не выполняет:
```text
HTTP-запросы;
получение exchangeInfo;
schema validation;
parsing;
value validation;
mapping;
создание конкретного Handler;
создание конкретного Feed;
создание Registry;
создание Acquisition Service;
кэширование;
изменение ExchangeService;
изменение runtime;
изменение Telegram UI;
изменение автоторговли.
```
---
## 4. Изменённые файлы
В рамках Build 007 изменены:
```text
app/src/market_data/acquisition/protocol.py
app/src/market_data/acquisition/exceptions.py
```
Создан тестовый файл:
```text
app/tests/unit/market_data/acquisition/test_protocol.py
```
Не изменялись:
```text
app/src/market_data/acquisition/adapters/dzengi/rest.py
app/src/market_data/acquisition/handlers/instrument_handler.py
app/src/market_data/acquisition/feeds/instrument_feed.py
app/src/market_data/acquisition/registry.py
app/src/market_data/acquisition/service.py
app/src/market_data/acquisition/adapters/dzengi/models.py
app/src/market_data/acquisition/adapters/dzengi/parser.py
app/src/market_data/acquisition/adapters/dzengi/mapper.py
app/src/market_data/acquisition/validation/schema.py
app/src/market_data/acquisition/validation/values.py
app/src/integrations/exchange/*
app/src/telegram/*
app/src/trading/*
```
Работающий legacy-код бота не изменён.
---
## 5. Созданные Protocol
В файле:
```text
app/src/market_data/acquisition/protocol.py
```
созданы три минимальных протокола:
```text
InstrumentDocumentSource
InstrumentDocumentHandler
InstrumentFeedProtocol
```
Все протоколы основаны на структурной типизации Python:
```python
typing.Protocol
```
и объявлены как:
```python
@runtime_checkable
```
Это позволяет использовать их как для статической типизации, так и для ограниченной runtime-проверки через `isinstance()`.
---
## 6. InstrumentDocumentSource
Контракт:
```python
@runtime_checkable
class InstrumentDocumentSource(Protocol):
def fetch_instrument_document(self) -> object:
...
```
Назначение:
```text
получить декодированный транспортный документ Instrument Reference Data.
```
Предполагаемый конкретный потребитель этого контракта появится в:
```text
Build 008 — Dzengi REST Adapter
```
Будущий REST adapter должен реализовать метод:
```python
fetch_instrument_document()
```
и вернуть сырой декодированный документ.
---
## 7. Почему InstrumentDocumentSource возвращает object
Возвращаемый тип:
```python
object
```
выбран сознательно.
Транспортный источник не должен выполнять:
```text
schema validation;
parsing;
value validation;
mapping.
```
На транспортной границе REST adapter может получить произвольное декодированное JSON-значение:
```text
dict
list
str
int
float
bool
None
```
Проверка структуры является обязанностью:
```text
Schema Validation
```
Поэтому транспортный слой не должен преждевременно утверждать, что полученный документ является корректным JSON-объектом нужной структуры.
Архитектурная граница остаётся следующей:
```text
REST Adapter
object
Schema Validation
ValidatedExchangeInfoDocument
```
---
## 8. InstrumentDocumentHandler
Контракт:
```python
@runtime_checkable
class InstrumentDocumentHandler(Protocol):
def handle_instrument_document(
self,
document: object,
) -> tuple[Instrument, ...]:
...
```
Назначение:
```text
преобразовать сырой документ в проверенные внутренние модели Instrument.
```
Конкретная реализация появится в:
```text
Build 009 — Instrument Handler
```
Handler должен объединить уже существующий pipeline:
```text
object
validate_exchange_info_schema()
ValidatedExchangeInfoDocument
parse_exchange_info()
DzengiExchangeInfoResponse
validate_exchange_info_values()
map_dzengi_exchange_info_to_instruments()
tuple[Instrument, ...]
```
При этом Protocol не знает:
```text
какая биржа является источником;
какой parser используется;
какой mapper используется;
какие source-specific raw-модели существуют.
```
---
## 9. InstrumentFeedProtocol
Контракт:
```python
@runtime_checkable
class InstrumentFeedProtocol(Protocol):
def load_instruments(self) -> tuple[Instrument, ...]:
...
```
Назначение:
```text
получить полный immutable-набор внутренних моделей Instrument.
```
Конкретный Feed появится в:
```text
Build 010 — Instrument Feed
```
Предполагаемая композиция:
```text
InstrumentFeed
├── InstrumentDocumentSource
└── InstrumentDocumentHandler
```
Рабочая последовательность:
```text
InstrumentFeed.load_instruments()
InstrumentDocumentSource.fetch_instrument_document()
object
InstrumentDocumentHandler.handle_instrument_document()
tuple[Instrument, ...]
```
---
## 10. Почему протокол называется InstrumentFeedProtocol
Будущий файл:
```text
app/src/market_data/acquisition/feeds/instrument_feed.py
```
предназначен для конкретной реализации Feed.
Чтобы избежать конфликта между интерфейсом и конкретным классом, протокол получил имя:
```text
InstrumentFeedProtocol
```
Конкретная реализация в Build 010 сможет называться:
```text
InstrumentFeed
```
Таким образом:
```text
InstrumentFeedProtocol
контракт
InstrumentFeed
конкретная реализация
```
---
## 11. Structural Typing
Реализации не обязаны наследоваться от Protocol напрямую.
Например:
```python
class StubInstrumentDocumentSource:
def fetch_instrument_document(self) -> object:
return {
"symbols": [],
}
```
Такой объект удовлетворяет контракту:
```text
InstrumentDocumentSource
```
без явного наследования:
```python
class StubInstrumentDocumentSource(InstrumentDocumentSource):
...
```
Это уменьшает связанность между конкретными реализациями и интерфейсами.
---
## 12. Runtime Checkable
Все три Protocol объявлены с:
```python
@runtime_checkable
```
Благодаря этому допустима проверка:
```python
isinstance(source, InstrumentDocumentSource)
```
Тестами подтверждено:
```text
объект с требуемым методом
→ соответствует Protocol
объект без требуемого метода
→ не соответствует Protocol
```
Важно: runtime-проверка Protocol подтверждает структурное наличие требуемых атрибутов и методов, но не выполняет полную глубокую проверку всех аннотаций типов и фактических возвращаемых значений.
---
## 13. Новая транспортная ошибка
В файл:
```text
app/src/market_data/acquisition/exceptions.py
```
добавлена:
```python
class InstrumentReferenceTransportError(
MarketDataAcquisitionError
):
pass
```
Она предназначена для будущего:
```text
Build 008 — Dzengi REST Adapter
```
---
## 14. Назначение InstrumentReferenceTransportError
Ошибка предназначена для проблем получения Instrument Reference Data от внешнего источника.
Потенциальные случаи:
```text
ошибка соединения;
timeout;
HTTP error;
ошибка JSON decoding;
непредвиденная ошибка REST-клиента.
```
Она не используется для:
```text
неверной структуры документа;
ошибки parsing;
недопустимых значений;
ошибки mapping.
```
Эти случаи уже имеют специализированные типы исключений.
---
## 15. Итоговая иерархия ошибок
После Build 007 иерархия выглядит следующим образом:
```text
MarketDataAcquisitionError
├── InstrumentReferenceTransportError
├── InstrumentReferenceSchemaError
├── InstrumentReferenceParseError
├── InstrumentReferenceValueError
└── InstrumentReferenceMappingError
```
Назначение ошибок:
| Ошибка | Ответственность |
|---|---|
| `InstrumentReferenceTransportError` | Получение данных от внешнего источника |
| `InstrumentReferenceSchemaError` | Структура исходного документа |
| `InstrumentReferenceParseError` | Преобразование проверенного документа в raw-модели |
| `InstrumentReferenceValueError` | Допустимость значений |
| `InstrumentReferenceMappingError` | Преобразование raw-модели во внутреннюю модель `Instrument` |
---
## 16. Какие дополнительные Protocol не создавались
В Build 007 сознательно не создавались отдельные Protocol для:
```text
Schema Validator
Parser
Value Validator
Mapper
Registry
Acquisition Service
```
Причины:
```text
validators, parser и mapper уже реализованы как чистые функции;
Registry и Acquisition Service пока не имеют нескольких реализаций;
дополнительные интерфейсы сейчас не используются;
создание таких контрактов было бы преждевременной абстракцией.
```
Это соответствует принципу:
```text
не создавать абстракции «на будущее» без конкретного потребителя.
```
---
## 17. Какие дополнительные Exceptions не создавались
В Build 007 сознательно не добавлялись:
```text
InstrumentReferenceHandlerError
InstrumentReferenceFeedError
InstrumentReferenceRegistryError
InstrumentReferenceServiceError
```
Причины:
```text
Handler может передавать точные ошибки Schema, Parser, Value и Mapping;
Feed может передавать точные ошибки Transport и Processing;
Registry ещё не реализован;
Acquisition Service ещё не реализован;
новые типы ошибок следует вводить только там, где появляется реальная новая категория отказа.
```
---
## 18. Реализованные тестовые сценарии
Создан файл:
```text
app/tests/unit/market_data/acquisition/test_protocol.py
```
Реализовано 7 тестов.
Проверены:
1. соответствие корректного source объекта `InstrumentDocumentSource`;
2. соответствие корректного handler объекта `InstrumentDocumentHandler`;
3. соответствие корректного feed объекта `InstrumentFeedProtocol`;
4. отклонение объектов без обязательных методов;
5. structural typing без явного наследования;
6. наследование `InstrumentReferenceTransportError` от `MarketDataAcquisitionError`;
7. наличие общего базового типа у всех ошибок Instrument Reference Data.
---
## 19. Выполненные проверки
### Проверка 1 — unit-тесты Protocol и Exceptions
Команда:
```bash
python -m pytest \
tests/unit/market_data/acquisition/test_protocol.py \
-q
```
Результат:
```text
....... [100%]
7 passed in 0.01s
```
Статус:
```text
PASSED
```
---
### Проверка 2 — Python compilation
Команда:
```bash
python -m py_compile \
src/market_data/acquisition/protocol.py \
src/market_data/acquisition/exceptions.py \
tests/unit/market_data/acquisition/test_protocol.py
```
Результат:
```text
Команда завершилась без ошибок и без вывода.
```
Статус:
```text
PASSED
```
---
### Проверка 3 — полный набор тестов проекта
Команда:
```bash
python -m pytest -q
```
Результат:
```text
................................................................................................................. [100%]
113 passed in 0.06s
```
Статус:
```text
PASSED
```
---
### Проверка 4 — отсутствие преждевременной production-интеграции
Команда:
```bash
grep -RIn \
--exclude-dir="__pycache__" \
--exclude="*.pyc" \
-E "InstrumentDocumentSource|InstrumentDocumentHandler|InstrumentFeedProtocol|InstrumentReferenceTransportError" \
src tests
```
Подтверждено:
```text
InstrumentDocumentSource
используется только в protocol.py и unit-тестах
InstrumentDocumentHandler
используется только в protocol.py и unit-тестах
InstrumentFeedProtocol
используется только в protocol.py и unit-тестах
InstrumentReferenceTransportError
объявлена в exceptions.py и используется только в unit-тестах
```
Не обнаружено подключения к:
```text
src/integrations/exchange/*
src/telegram/*
src/trading/*
```
Статус:
```text
PASSED
```
---
## 20. Архитектура после Build 007
После завершения Build 007 новая часть подсистемы имеет следующую структуру:
```text
market_data/
└── acquisition/
├── exceptions.py
│ ├── MarketDataAcquisitionError
│ ├── InstrumentReferenceTransportError
│ ├── InstrumentReferenceSchemaError
│ ├── InstrumentReferenceParseError
│ ├── InstrumentReferenceValueError
│ └── InstrumentReferenceMappingError
├── protocol.py
│ ├── InstrumentDocumentSource
│ ├── InstrumentDocumentHandler
│ └── InstrumentFeedProtocol
├── models/
│ └── instrument.py
│ └── Instrument
├── validation/
│ ├── schema.py
│ └── values.py
└── adapters/
└── dzengi/
├── models.py
├── parser.py
├── mapper.py
└── rest.py
```
На текущем этапе:
```text
rest.py
ещё не реализован
instrument_handler.py
ещё не реализован
instrument_feed.py
ещё не реализован
registry.py
ещё не реализован
service.py
ещё не реализован
```
---
## 21. Полная архитектурная цепочка после Build 007
Уже реализовано:
```text
raw JSON document
validate_exchange_info_schema()
ValidatedExchangeInfoDocument
parse_exchange_info()
DzengiExchangeInfoResponse
validate_exchange_info_values()
map_dzengi_exchange_info_to_instruments()
tuple[Instrument, ...]
```
Зафиксированы контракты для будущей orchestration-цепочки:
```text
InstrumentDocumentSource
InstrumentDocumentHandler
InstrumentFeedProtocol
Registry
Acquisition Service
```
После реализации Build 008012 предполагаемая полная цепочка будет выглядеть так:
```text
Dzengi REST API
Dzengi REST Adapter
implements InstrumentDocumentSource
object
Instrument Handler
implements InstrumentDocumentHandler
tuple[Instrument, ...]
Instrument Feed
implements InstrumentFeedProtocol
Registry
Acquisition Service
```
---
## 22. Влияние на legacy-систему
Build 007 не подключён к существующим компонентам:
```text
ExchangeService
ExchangeSymbol
SymbolValidationResult
Telegram UI
AutoTrade
Market Stream
Market Data Runner
Execution Quality
```
Не изменены:
```text
ExchangeService.get_exchange_symbols()
ExchangeService.validate_symbol()
ExchangeService.get_symbol_runtime_status()
normalize_symbol()
symbol_candidates()
```
Старый production-путь продолжает работать без изменений.
Новая подсистема развивается параллельно.
---
## 23. Обратная совместимость
Подтверждено сохранение:
```text
сигнатур существующих методов;
старых импортов;
существующего формата ошибок;
Telegram UI;
автоторговли;
runtime-поведения;
legacy ExchangeSymbol;
legacy-кэша.
```
Build 007 имеет полную обратную совместимость.
---
## 24. Классификация изменений
| Изменение | Классификация |
|---|---|
| `InstrumentDocumentSource` | Обязательное архитектурное изменение |
| `InstrumentDocumentHandler` | Обязательное архитектурное изменение |
| `InstrumentFeedProtocol` | Обязательное архитектурное изменение |
| `InstrumentReferenceTransportError` | Обязательное архитектурное изменение |
| Structural typing | Улучшение архитектурной независимости |
| `runtime_checkable` | Улучшение тестируемости и проверяемости контрактов |
| Дополнительные преждевременные Protocol | Не создавались |
| Дополнительные преждевременные Exceptions | Не создавались |
| Изменение production-поведения | Отсутствует |
---
## 25. Итог Build 007
Build 007 завершён успешно.
Реализовано:
```text
InstrumentDocumentSource
InstrumentDocumentHandler
InstrumentFeedProtocol
InstrumentReferenceTransportError
```
Подтверждено:
```text
7 protocol tests passed
113 total project tests passed
Python compilation passed
No production integration detected
Legacy bot behavior unchanged
```
Итоговый статус:
```text
BUILD 007 — COMPLETE
```
---
## 26. Следующий этап
Следующий этап утверждённого плана:
```text
Build 008 — Dzengi REST Adapter
```
Его задача — реализовать конкретный транспортный источник, соответствующий контракту:
```text
InstrumentDocumentSource
```
Будущая граница Build 008:
```text
Dzengi REST API
Dzengi REST Adapter
object
```
Build 008 не должен выполнять:
```text
schema validation;
parsing;
value validation;
mapping;
создание Instrument;
создание Handler;
создание Feed;
создание Registry;
создание Acquisition Service;
изменение ExchangeService;
production-подключение.
```