Files
dzentra_bot/docs/migrations/build_005.md

22 KiB
Raw Permalink Blame History

Build 005 — Value Validation для Instrument Reference Data

Статус: Завершён
Подсистема: market_data/acquisition
Область: Instrument Reference Data
Тип изменения: Изолированное расширение новой архитектуры без подключения к production runtime
Результат полного набора тестов: 84 passed


1. Цель Build 005

Цель Build 005 — реализовать отдельный слой проверки допустимости значений, полученных после:

  1. проверки структуры исходного документа exchangeInfo;
  2. преобразования проверенного документа в типизированные 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

Если значение присутствует, оно должно:

  1. корректно преобразовываться в Decimal;
  2. быть конечным;
  3. быть строго больше нуля.

Дополнительно проверяется отношение:

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 до завершения и проверки нового пути.