Files
dzentra_bot/docs/migrations/build_003.md

19 KiB
Raw Permalink Blame History

Dzentra — Instrument Reference Data Migration

Build 003 — структурная валидация exchangeInfo

Статус: Завершён
Подсистема: Market Data Acquisition
Область: Instrument Reference Data
Проект: Dzentra
Язык реализации: Python 3.12


1. Цель Build 003

Цель Build 003 — создать отдельный слой структурной валидации сырого JSON-документа exchangeInfo до его преобразования в raw-модели Dzengi.

После завершения Build 003 формируется следующий архитектурный поток:

JSON response
    ↓
Schema validation
    ↓
Parser
    ↓
Dzengi Raw Models

В рамках этого Build реализована только проверка формы и структуры входных данных.

Schema validation не выполняет:

  • преобразование JSON в raw-модели Dzengi;
  • преобразование camelCase в snake_case;
  • преобразование строковых чисел в Decimal, float или int;
  • нормализацию символов;
  • проверку допустимости числовых значений;
  • проверку торговой семантики статусов;
  • создание внутренней модели Instrument;
  • фильтрацию или отбрасывание инструментов.

2. Почему Build 003 выполняется перед parser

После Build 002 уже существуют типизированные raw-модели ответа Dzengi.

Следующий этап — parser, однако parser не должен одновременно:

  • определять wrapped или unwrapped формат ответа;
  • проверять тип корневого объекта;
  • проверять наличие symbols;
  • проверять тип symbols;
  • проверять структуру элементов symbols;
  • проверять структуру вложенных коллекций;
  • создавать raw-модели.

Поэтому перед parser был выделен отдельный слой:

validation/schema.py

Разделение ответственности теперь выглядит следующим образом:

schema.py
    Проверяет форму и структуру данных.

parser.py
    Преобразует структурно корректные данные в raw-модели Dzengi.

values.py
    Проверяет допустимость предметных значений.

mapper.py
    Преобразует raw-модели Dzengi во внутреннюю модель Instrument.

Это является обязательным архитектурным изменением для утверждённой структуры Dzentra.


3. Изменённые и созданные файлы

В рамках Build 003 были изменены или созданы только следующие файлы:

app/src/market_data/acquisition/exceptions.py
app/src/market_data/acquisition/validation/schema.py
app/tests/unit/market_data/acquisition/validation/test_schema.py

Другие файлы проекта не изменялись.


4. Реализованные исключения

В файле:

app/src/market_data/acquisition/exceptions.py

добавлены:

class MarketDataAcquisitionError(Exception):
    pass

и:

class InstrumentReferenceSchemaError(MarketDataAcquisitionError):
    pass

Иерархия ошибок:

Exception
    ↓
MarketDataAcquisitionError
    ↓
InstrumentReferenceSchemaError

InstrumentReferenceSchemaError используется для явного обозначения ошибки структуры документа Instrument Reference Data.

Это позволяет в последующих Build отличать структурную ошибку от:

  • сетевой ошибки;
  • ошибки HTTP;
  • ошибки JSON-декодирования;
  • ошибки значений;
  • ошибки parser;
  • ошибки mapper.

Исключение создано не «на будущее»: оно непосредственно используется schema validation в Build 003.


5. Реализованный контракт schema validation

В файле:

app/src/market_data/acquisition/validation/schema.py

создана модель:

@dataclass(frozen=True, slots=True)
class ValidatedExchangeInfoDocument:
    payload: Mapping[str, object]
    is_wrapped: bool
    status: object | None
    correlation_id: object | None

Она представляет структурно проверенный документ exchangeInfo.

Модель содержит:

Поле Назначение
payload Проверенный payload с данными exchangeInfo
is_wrapped Признак wrapped/unwrapped формата
status Верхнеуровневый статус wrapped-ответа
correlation_id Верхнеуровневый correlationId wrapped-ответа

Основная функция:

validate_exchange_info_schema(document: object) -> ValidatedExchangeInfoDocument

принимает произвольный объект и либо:

  • возвращает ValidatedExchangeInfoDocument;
  • либо выбрасывает InstrumentReferenceSchemaError.

6. Поддерживаемые форматы ответа

6.1. Unwrapped-формат

Поддерживается непосредственный payload:

{
  "timezone": "UTC",
  "serverTime": 1783537921471,
  "symbols": []
}

Результат:

is_wrapped = False
status = None
correlation_id = None

6.2. Wrapped-формат

Поддерживается ответ с вложенным payload:

{
  "status": "OK",
  "correlationId": "2",
  "payload": {
    "timezone": "UTC",
    "serverTime": 1783537921471,
    "symbols": []
  }
}

Результат:

is_wrapped = True
status = "OK"
correlation_id = "2"

7. Что проверяет schema validation

Build 003 проверяет следующие структурные свойства документа.

7.1. Корень документа

Корень должен быть JSON-объектом.

Отклоняются:

[]
null
"invalid"
123

7.2. Wrapped payload

Если присутствует ключ:

payload

его значение должно быть JSON-объектом.

Например, отклоняется:

{
  "status": "OK",
  "payload": []
}

7.3. Наличие symbols

Payload должен содержать:

symbols

Отсутствие symbols считается ошибкой структуры.


7.4. Тип symbols

symbols должен быть JSON-массивом.

Отклоняется:

{
  "symbols": {}
}

7.5. Элементы symbols

Каждый элемент symbols должен быть JSON-объектом.

Отклоняется:

{
  "symbols": [
    "BTC/USD"
  ]
}

7.6. filters

Если у инструмента присутствует:

filters

то:

  • filters должен быть JSON-массивом;
  • каждый элемент filters должен быть JSON-объектом.

Отклоняется:

{
  "symbols": [
    {
      "symbol": "BTC/USD",
      "filters": {}
    }
  ]
}

Также отклоняется:

{
  "symbols": [
    {
      "symbol": "BTC/USD",
      "filters": [
        "LOT_SIZE"
      ]
    }
  ]
}

7.7. marketModes

Если присутствует:

marketModes

то:

  • значение должно быть JSON-массивом;
  • каждый элемент должен быть строкой.

7.8. orderTypes

Если присутствует:

orderTypes

то:

  • значение должно быть JSON-массивом;
  • каждый элемент должен быть строкой.

7.9. rateLimits

Если присутствует:

rateLimits

то:

  • значение должно быть JSON-массивом;
  • каждый элемент должен быть JSON-объектом.

7.10. exchangeFilters

Если присутствует:

exchangeFilters

то:

  • значение должно быть JSON-массивом;
  • каждый элемент должен быть JSON-объектом.

8. Что намеренно не проверяется в Build 003

Build 003 не проверяет допустимость значений.

Следующие примеры не относятся к schema validation:

{
  "tickSize": -1
}
{
  "baseAssetPrecision": -5
}
{
  "status": ""
}
{
  "minQty": "not-a-number"
}

Такие проверки относятся к:

app/src/market_data/acquisition/validation/values.py

и должны реализовываться отдельно, без смешения структурной и предметной валидации.


9. Защита проверенного payload

Поле:

payload: Mapping[str, object]

возвращается через:

MappingProxyType

Это предотвращает случайное изменение корневого payload последующим parser.

Таким образом, schema validation передаёт следующему слою проверенное read-only представление данных.


10. Диагностические пути ошибок

Schema validation формирует ошибки с указанием пути до проблемного значения.

Примеры:

$ должен быть JSON-объектом, получен list.
$.payload.symbols должен быть JSON-массивом, получен dict.
$.payload.symbols[0] должен быть JSON-объектом, получен str.
$.payload.symbols[0].filters должен быть JSON-массивом, получен dict.

На текущем этапе для symbols используется унифицированный диагностический путь:

$.payload.symbols

как для wrapped-, так и для unwrapped-формата.

Это сознательное упрощение текущего контракта и не влияет на production-поведение.


11. Реализованные тесты

Создан файл:

app/tests/unit/market_data/acquisition/validation/test_schema.py

Он проверяет:

  • корректный unwrapped-документ;
  • корректный wrapped-документ;
  • отклонение не-объекта в корне;
  • отклонение некорректного wrapped payload;
  • отсутствие symbols;
  • некорректный тип symbols;
  • некорректный элемент symbols;
  • некорректный тип filters;
  • некорректный элемент filters;
  • некорректный тип marketModes;
  • некорректный тип orderTypes;
  • нестроковый элемент marketModes;
  • нестроковый элемент orderTypes;
  • некорректный тип rateLimits;
  • некорректный элемент rateLimits;
  • некорректный тип exchangeFilters;
  • некорректный элемент exchangeFilters.

Итог:

20 passed

12. Выполненные проверки

Проверка 1 — unit-тесты Build 003

Команда:

python -m pytest \
  tests/unit/market_data/acquisition/validation/test_schema.py \
  -q

Результат:

.................... [100%]
20 passed in 0.01s

Статус:

PASSED

Проверка 2 — компиляция файлов Build 003

Команда:

python -m py_compile \
  src/market_data/acquisition/exceptions.py \
  src/market_data/acquisition/validation/schema.py \
  tests/unit/market_data/acquisition/validation/test_schema.py

Результат:

Ошибок нет.

Статус:

PASSED

Проверка 3 — ручная проверка wrapped/unwrapped контрактов

Получен результат:

Unwrapped:
  is_wrapped: False
  status: None
  correlation_id: None
  symbols: []

Wrapped:
  is_wrapped: True
  status: OK
  correlation_id: 2
  symbols: []

Подтверждено:

  • unwrapped-формат определяется корректно;
  • wrapped-формат определяется корректно;
  • status сохраняется;
  • correlationId сохраняется как correlation_id;
  • symbols доступны через проверенный payload.

Статус:

PASSED

Проверка 4 — типизированные ошибки

Проверены четыре некорректных документа.

Получен результат:

1: InstrumentReferenceSchemaError: $ должен быть JSON-объектом, получен list.
2: InstrumentReferenceSchemaError: $.payload.symbols должен быть JSON-массивом, получен dict.
3: InstrumentReferenceSchemaError: $.payload.symbols[0] должен быть JSON-объектом, получен str.
4: InstrumentReferenceSchemaError: $.payload.symbols[0].filters должен быть JSON-массивом, получен dict.

Ни один ошибочный документ не был принят.

Статус:

PASSED

Проверка 5 — полный набор тестов проекта

Команда:

python -m pytest -q

Результат:

............................... [100%]
31 passed in 0.04s

Статус:

PASSED

Проверка 6 — отсутствие подключения к production-коду

Выполнен поиск:

grep -RIn \
  --exclude-dir="__pycache__" \
  --exclude="*.pyc" \
  -E "validate_exchange_info_schema|ValidatedExchangeInfoDocument|InstrumentReferenceSchemaError" \
  src tests

Подтверждено, что новые сущности Build 003 используются только в:

src/market_data/acquisition/exceptions.py
src/market_data/acquisition/validation/schema.py
tests/unit/market_data/acquisition/validation/test_schema.py

Они пока не подключены к:

ExchangeService
legacy exchangeInfo
Telegram UI
runtime
автоторговле
market stream
production parser
production mapper

Статус:

PASSED

13. Обратная совместимость

Build 003 не изменяет:

ExchangeService.get_exchange_symbols()
ExchangeService.validate_symbol()
ExchangeService.get_symbol_runtime_status()
ExchangeService.get_symbol_market_status()

Не изменены:

ExchangeSymbol
SymbolValidationResult
normalize_symbol()
symbol_candidates()

Не изменено поведение:

  • Telegram UI;
  • автоторговли;
  • runtime-проверок символа;
  • legacy-кэша инструментов;
  • получения цен;
  • market stream;
  • существующего exchangeInfo.

Новый schema validation пока изолирован от production-кода.


14. Точки обратной совместимости

После Build 003 продолжают действовать прежние точки совместимости:

ExchangeService.get_exchange_symbols()
    → list[ExchangeSymbol]

ExchangeService.validate_symbol()
    → SymbolValidationResult

ExchangeService.get_symbol_runtime_status()
    → ExchangeRuntimeStatus

ExchangeService.get_symbol_market_status()
    → dict[str, object]

Ни одна из этих сигнатур не изменена.


15. Классификация изменений

Изменение Классификация
Создание schema validation Обязательное архитектурное изменение
Создание MarketDataAcquisitionError Обязательное архитектурное изменение
Создание InstrumentReferenceSchemaError Обязательное архитектурное изменение
Поддержка wrapped/unwrapped форматов Улучшение надёжности
Явные структурные ошибки Улучшение надёжности
Read-only представление проверенного payload Улучшение надёжности
Изменение production-поведения Отсутствует
Изменение публичных legacy-интерфейсов Отсутствует

16. Условие завершения Build 003

Build 003 считается завершённым, потому что выполнены все условия:

  • создан отдельный schema validation layer;
  • поддержан wrapped-формат;
  • поддержан unwrapped-формат;
  • проверяется обязательная структура symbols;
  • проверяются вложенные коллекции;
  • ошибки типизированы;
  • создан read-only контракт для передачи данных parser;
  • написаны unit-тесты;
  • все тесты Build проходят;
  • полный набор тестов проекта проходит;
  • production-код не изменён;
  • обратная совместимость сохранена.

17. Итоговый статус

Build 003 — COMPLETED

Итоговый набор тестов проекта:

31 passed in 0.04s

Следующий этап:

Build 004 — Parser exchangeInfo

На Build 004 новый parser должен:

  1. принимать структурно проверенный ValidatedExchangeInfoDocument;
  2. преобразовывать payload в raw-модели Dzengi, созданные в Build 002;
  3. не выполнять повторную schema validation;
  4. не создавать внутреннюю модель Instrument;
  5. не подключаться к ExchangeService;
  6. не менять production-поведение работающего бота.