19 KiB
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 должен:
- принимать структурно проверенный
ValidatedExchangeInfoDocument; - преобразовывать payload в raw-модели Dzengi, созданные в Build 002;
- не выполнять повторную schema validation;
- не создавать внутреннюю модель
Instrument; - не подключаться к
ExchangeService; - не менять production-поведение работающего бота.