22 KiB
Build 005 — Value Validation для Instrument Reference Data
Статус: Завершён
Подсистема: market_data/acquisition
Область: Instrument Reference Data
Тип изменения: Изолированное расширение новой архитектуры без подключения к production runtime
Результат полного набора тестов: 84 passed
1. Цель Build 005
Цель Build 005 — реализовать отдельный слой проверки допустимости значений, полученных после:
- проверки структуры исходного документа
exchangeInfo; - преобразования проверенного документа в типизированные raw-модели адаптера Dzengi.
Build 005 добавляет третий этап обработки Instrument Reference Data:
raw JSON
↓
schema validation
↓
parser
↓
value validation
Value validation проверяет не структуру JSON и не типы полей исходного документа, а предметную допустимость уже распарсенных значений.
Например:
tickSize = 0
может быть корректным числовым значением с точки зрения JSON и parser layer, но недопустимым значением для шага цены торгового инструмента.
2. Почему Build 005 реализован именно на этом этапе
До Build 005 были завершены следующие этапы миграции:
Build 001
↓
Каноническая source-independent модель Instrument
Build 002
↓
Raw transport models адаптера Dzengi
Build 003
↓
Schema validation исходного exchangeInfo
Build 004
↓
Parser: validated document → Dzengi raw models
Build 005
↓
Value validation Dzengi raw models
Такой порядок позволяет строго разделить ответственность слоёв.
Schema validation отвечает за:
Есть ли ожидаемые JSON-объекты?
Есть ли массив symbols?
Являются ли элементы symbols объектами?
Является ли filters массивом?
Parser отвечает за:
Как преобразовать проверенный JSON-документ
в типизированные raw-модели Dzengi?
Value validation отвечает за:
Допустимы ли конкретные значения полей
с точки зрения контракта Instrument Reference Data?
Это исключает смешивание:
- проверки JSON-структуры;
- транспортного parsing;
- проверки предметных значений;
- source-independent mapping.
3. Изменённые файлы
В рамках Build 005 изменены:
app/src/market_data/acquisition/exceptions.py
app/src/market_data/acquisition/validation/values.py
Добавлен тестовый файл:
app/tests/unit/market_data/acquisition/validation/test_values.py
4. Новая типизированная ошибка
В файл:
app/src/market_data/acquisition/exceptions.py
добавлена ошибка:
class InstrumentReferenceValueError(MarketDataAcquisitionError):
pass
Иерархия ошибок Instrument Reference Data теперь выглядит так:
MarketDataAcquisitionError
├── InstrumentReferenceSchemaError
├── InstrumentReferenceParseError
└── InstrumentReferenceValueError
Каждый этап обработки имеет собственную категорию ошибки:
| Этап | Ошибка |
|---|---|
| Schema validation | InstrumentReferenceSchemaError |
| Parsing | InstrumentReferenceParseError |
| Value validation | InstrumentReferenceValueError |
Это позволяет будущему orchestration/service layer точно определять, на каком этапе обработки произошла ошибка.
5. Главная функция Build 005
В файл:
app/src/market_data/acquisition/validation/values.py
добавлена функция:
def validate_exchange_info_values(
response: DzengiExchangeInfoResponse,
) -> None:
Функция принимает:
DzengiExchangeInfoResponse
то есть результат работы parser layer из Build 004.
При корректных значениях функция возвращает:
None
При обнаружении недопустимого значения выбрасывается:
InstrumentReferenceValueError
6. Проверяемые обязательные строки инструмента
Для каждого DzengiExchangeInfoSymbol проверяются следующие обязательные строковые поля:
symbol
name
status
baseAsset
quoteAsset
marketType
Они не должны быть пустыми или состоять только из пробелов.
Пример недопустимого значения:
symbol=" "
Результат:
InstrumentReferenceValueError:
$.payload.symbols[0].symbol не должен быть пустым.
7. Проверка orderTypes и marketModes
Каждый элемент следующих последовательностей проверяется на непустое строковое значение:
orderTypes
marketModes
Например:
order_types=("LIMIT", " ")
отклоняется с ошибкой:
$.payload.symbols[0].orderTypes[1] не должен быть пустым.
Аналогично:
market_modes=("REGULAR", "")
отклоняется как недопустимое значение.
8. Проверка integer-полей
Следующие optional integer-поля не могут быть отрицательными:
baseAssetPrecision
quotePrecision
swapChargeInterval
Допустимо:
None
0
1
2
...
Недопустимо:
-1
При нарушении выбрасывается:
InstrumentReferenceValueError
9. Проверка tickSize
Для:
tickSize
применяется правило:
tickSize > 0
Допустимы только положительные конечные числа.
Отклоняются:
0
отрицательные значения
NaN
+Infinity
-Infinity
Это правило подтверждено анализом реального sample Dzengi:
tickSize:
present=51
min=1E-8
max=1
zero=0
negative=0
invalid=[]
10. Проверка конечности числовых значений
Для optional numeric-полей проверяется, что значение является конечным числом.
Проверяются:
tickValue
tradingFee
exchangeFee
longRate
shortRate
minSLGap
maxSLGap
minTPGap
maxTPGap
Отклоняются:
NaN
+Infinity
-Infinity
При этом нулевые значения не запрещаются автоматически.
11. Проверка LOT_SIZE
Для:
DzengiLotSizeFilter
проверяются:
minQty
maxQty
stepSize
Если значение присутствует, оно должно:
- корректно преобразовываться в
Decimal; - быть конечным;
- быть строго больше нуля.
Дополнительно проверяется отношение:
minQty <= maxQty
Если:
minQty > maxQty
выбрасывается:
InstrumentReferenceValueError
12. Проверка MIN_NOTIONAL
Для:
DzengiMinNotionalFilter
проверяется:
minNotional >= 0
Нулевое значение разрешено.
Отрицательное значение отклоняется.
13. Использование Decimal
Строковые и числовые значения ограничений торгового инструмента преобразуются для проверки через:
Decimal(str(value))
Это позволяет избежать ненужной потери точности при обработке таких значений, как:
0.0001
0.00000001
6.9E-7
Именно такой подход соответствует уже принятому контракту канонической модели Instrument, где торговые числовые ограничения представлены через Decimal.
14. Особенность полей country, sector и industry
В ходе первой проверки Build 005 была обнаружена ошибка первоначальной реализации.
Изначально к полям:
country
sector
industry
применялось правило:
если значение присутствует, строка не должна быть пустой
Однако анализ реального ответа Dzengi и уже созданных raw-моделей показал, что Dzengi штатно возвращает:
country=""
sector=""
industry=""
Поэтому пустая строка для этих полей является допустимым транспортным состоянием.
После исправления Build 005:
country=""
sector=""
industry=""
не считаются ошибкой.
Это соответствует реальному контракту Dzengi и не выполняет преждевременную нормализацию transport-specific данных.
15. Проверка отрицательных longRate и shortRate
Отрицательные значения:
longRate
shortRate
являются допустимыми.
Это подтверждено анализом реального sample Dzengi:
longRate:
present=51
min=-0.0684932
max=0.1389493
negative=43
shortRate:
present=51
min=-0.1608693
max=0.01
negative=38
Поэтому value validation проверяет только:
значение является корректным конечным числом
но не требует:
value >= 0
16. Проверка допустимости нулевых optional numeric values
Нулевые значения разрешены для полей, для которых реальный контракт Dzengi допускает 0.
В частности:
tickValue
tradingFee
exchangeFee
minSLGap
maxSLGap
minTPGap
maxTPGap
Это соответствует анализу реального sample:
tickValue:
zero=1
tradingFee:
zero=40
minSLGap:
zero=51
minTPGap:
zero=51
Value validation не вводит ограничений, которые не подтверждены фактическими данными или контрактом источника.
17. Проверка неизвестных instrument filters
Для неизвестного фильтра инструмента:
DzengiUnknownFilter
поле:
filterType
должно быть непустым.
Это позволяет сохранить forward compatibility с новыми типами фильтров Dzengi, одновременно предотвращая попадание полностью неопределённых instrument filters в дальнейшую обработку.
18. Особенность global exchange filters
Для:
exchangeFilters
сохранено более мягкое поведение.
Реальный transport contract может содержать global filter без meaningful filterType, поэтому пустое значение не отклоняется автоматически.
Таким образом, Build 005 различает:
instrument-level filters
и:
global exchange filters
и не навязывает им одинаковые ограничения без подтверждения реального контракта.
19. Результаты анализа реального sample Dzengi
Перед реализацией Build 005 был выполнен анализ реального exchangeInfo sample.
Получены следующие результаты.
Обязательные строки
Пустые значения отсутствуют для:
symbol
name
status
baseAsset
quoteAsset
marketType
Precision
baseAssetPrecision:
count=51
min=2
max=8
invalid=[]
quotePrecision:
count=51
min=2
max=8
invalid=[]
Числовые диапазоны
tickSize:
present=51
min=1E-8
max=1
zero=0
negative=0
invalid=[]
tickValue:
present=39
min=0
max=50665.5
zero=1
negative=0
invalid=[]
minQty:
present=51
min=0.0001
max=1
zero=0
negative=0
invalid=[]
maxQty:
present=51
min=100
max=10000000
zero=0
negative=0
invalid=[]
stepSize:
present=51
min=0.0001
max=1
zero=0
negative=0
invalid=[]
minNotional:
present=39
min=6.9E-7
max=507
zero=0
negative=0
invalid=[]
tradingFee:
present=51
min=0
max=0.075
zero=40
negative=0
invalid=[]
exchangeFee:
present=0
min=None
max=None
zero=0
negative=0
invalid=[]
longRate:
present=51
min=-0.0684932
max=0.1389493
zero=0
negative=43
invalid=[]
shortRate:
present=51
min=-0.1608693
max=0.01
zero=0
negative=38
invalid=[]
minSLGap:
present=51
min=0
max=0
zero=51
negative=0
invalid=[]
maxSLGap:
present=51
min=20.0
max=50.0
zero=0
negative=0
invalid=[]
minTPGap:
present=51
min=0
max=0
zero=51
negative=0
invalid=[]
maxTPGap:
present=51
min=20.0
max=50.0
zero=0
negative=0
invalid=[]
Проверка отношения minQty и maxQty
Не обнаружено ни одного случая:
minQty > maxQty
Результат:
[]
20. Реализованные тесты
Добавлен файл:
app/tests/unit/market_data/acquisition/validation/test_values.py
В нём реализовано:
34 теста
Тестами покрыты:
- корректный полный
exchangeInfo; - пустые обязательные строки;
- пустой
orderType; - пустой
marketMode; - отрицательные precision values;
- отрицательный
swapChargeInterval; - нулевой
tickSize; - отрицательный
tickSize; NaN;+Infinity;-Infinity;- нечисловой
minQty; - неположительный
minQty; - неположительный
maxQty; - неположительный
stepSize; minQty > maxQty;- отрицательный
minNotional; - нулевой
minNotional; - отрицательные
longRate; - отрицательные
shortRate; - допустимые нулевые optional numeric values;
- rate limits;
- неизвестные instrument filters;
- global exchange filters.
21. Выполненные проверки
Проверка 1 — unit-тесты Build 005
Команда:
python -m pytest \
tests/unit/market_data/acquisition/validation/test_values.py \
-q
Результат:
34 passed in 0.02s
Проверка 2 — Python compilation
Команда:
python -m py_compile \
src/market_data/acquisition/exceptions.py \
src/market_data/acquisition/validation/values.py \
tests/unit/market_data/acquisition/validation/test_values.py
Результат:
успешно, без ошибок
Проверка 3 — полная ручная цепочка обработки
Проверена последовательность:
raw JSON
↓
validate_exchange_info_schema()
↓
parse_exchange_info()
↓
validate_exchange_info_values()
Результат:
Validation result: None
Symbol: BTC/USD_LEVERAGE
Filters: (
DzengiLotSizeFilter(
filter_type='LOT_SIZE',
min_qty='0.0001',
max_qty='1000',
step_size='0.0001'
),
DzengiMinNotionalFilter(
filter_type='MIN_NOTIONAL',
min_notional='1'
)
)
Это подтверждает совместимость Build 003, Build 004 и Build 005.
Проверка 4 — полный набор тестов проекта
Команда:
python -m pytest -q
Результат:
84 passed in 0.06s
Регрессий не обнаружено.
Проверка 5 — контроль области использования
Выполнен поиск:
grep -RIn \
--exclude-dir="__pycache__" \
--exclude="*.pyc" \
-E "validate_exchange_info_values|InstrumentReferenceValueError" \
src tests
Подтверждено:
validate_exchange_info_values()используется только в собственном модуле и unit-тестах;InstrumentReferenceValueErrorиспользуется только вexceptions.py,values.pyи unit-тестах;- новый validator ещё не подключён к legacy
ExchangeService; - UI не изменён;
- runtime не изменён;
- автоторговля не изменена;
- production-поведение работающего бота не изменено.
22. Архитектурная граница Build 005
Build 005 намеренно не реализует:
REST client новой подсистемы
mapping Dzengi raw models → Instrument
instrument feed
instrument handler
registry
service orchestration
cache
legacy compatibility adapter
переключение production consumers
удаление старого ExchangeSymbol
Эти задачи относятся к следующим этапам миграции.
Build 005 отвечает только за:
проверку допустимости значений
в уже распарсенных raw-моделях Dzengi exchangeInfo
23. Влияние на работающего legacy-бота
Build 005 не изменяет production-поведение существующего бота.
Старый runtime продолжает использовать существующие компоненты:
src/integrations/exchange/models.py
src/integrations/exchange/service.py
src/integrations/exchange/status.py
src/integrations/exchange/symbol_utils.py
Новая реализация находится отдельно:
src/market_data/acquisition/
На момент завершения Build 005 она ещё не подключена к production consumers.
Это соответствует принятой стратегии миграции:
сначала построить и протестировать новый путь параллельно,
затем переключать consumers поэтапно,
не ломая работающего бота
24. Итог Build 005
Build 005 успешно завершён.
Реализовано:
InstrumentReferenceValueError
validate_exchange_info_values()
валидация обязательных строк
валидация orderTypes
валидация marketModes
валидация precision
валидация tickSize
валидация finite numeric values
валидация LOT_SIZE
валидация MIN_NOTIONAL
валидация rate limits
поддержка отрицательных longRate/shortRate
поддержка допустимых нулевых значений
поддержка пустых country/sector/industry
проверка unknown filters
34 unit-теста
Финальный результат:
84 passed in 0.06s
Регрессий не обнаружено.
Legacy production runtime не изменён.
25. Текущее состояние pipeline после Build 005
На момент завершения Build 005 построена следующая часть новой подсистемы:
Dzengi exchangeInfo raw JSON
↓
validate_exchange_info_schema()
↓
ValidatedExchangeInfoDocument
↓
parse_exchange_info()
↓
DzengiExchangeInfoResponse
↓
validate_exchange_info_values()
↓
validated Dzengi raw models
Следующий архитектурный шаг должен продолжить pipeline от проверенных source-specific raw-моделей к канонической source-independent модели:
validated Dzengi raw models
↓
mapper
↓
Instrument
Таким образом, естественным следующим этапом является реализация mapping layer:
DzengiExchangeInfoSymbol → Instrument
при сохранении изоляции от legacy production runtime до завершения и проверки нового пути.