# 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 008–012 предполагаемая полная цепочка будет выглядеть так: ```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-подключение. ```