perf(core): квантизация — на границу артефакта, а не после каждой операции (C2) - #6
Merged
Merged
Conversation
…рации (C2) `core/` звал `quantize` после каждой арифметической операции: 6–12 раз на бар, каждый раз полным кругом `new Decimal(n).toDecimalPlaces(8).toFixed()` → строка → `Number`. Это самая дорогая статья профиля бара. Смысл правки — не «ускорить вопреки дизайну», а привести код к своему же дизайну. Детерминизм покупался на гранулярности бара, а наблюдаем он только на границе артефакта: `canonicalJson` квантует КАЖДОЕ число в любом случае. То есть пербарная подрезка не давала артефакту ничего, чего не даёт сериализация, — она лишь меняла последующую арифметику. Теперь симуляция считает в полной точности `Decimal`, а 8 знаков появляются один раз, при записи. Decimal-арифметика сохранена: её замена на целочисленную фиксированную точку — отдельный шаг C3, отданный на решение владельца по объёму. `ENGINE_VERSION` 0.0.0 → 0.1.0: изменилась семантика исполнения. `traceFormatVersion` остался `1` — форма трейса не тронута, а конфляция двух версий воссоздала бы проблему, которую решение (A) уже закрыло. Пакет 0.1.1 → 0.2.0. ПЕРЕМОРОЗКА ОЖИДАНИЙ ОПРАВДАНА НЕ ТЕМ, ЧТО ТЕСТЫ ПОЗЕЛЕНЕЛИ. Сдвиг измерен differential-харнессом на замороженных лентах (backtester#181), сверявшимся с состоянием ДО всей серии перф-волн и ДО этого бампа версии — чтобы отделить численный эффект от смены engineVersion в самом трейсе. Три сценария: ноль структурных расхождений, максимальный относительный сдвиг 4.49e-10, последовательность решений, состав ордеров и сделок, индексы баров и метки времени совпали. Юнит-тест на размер закрепляет теперь ОБЕ половины инварианта: внутри движка значение полное, а на границе даёт ровно то число, которое раньше подрезалось пербарно. Без второй половины тест разрешал бы молча потерять квантизацию совсем. typecheck 0; vitest 100 passed; gate:determinism clean; gate:tapes 3 tapes OK. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Шаг C2 инициативы engine-hot-path-optimization. Несёт также инженерную половину C4 — bump версии, потому что
refresh-expectationsэтого прямо требует.Что и почему
core/звалquantizeпосле каждой арифметической операции: 6–12 раз на бар, каждый раз полным кругомnew Decimal(n).toDecimalPlaces(8).toFixed()→ строка →Number. Самая дорогая статья профиля бара.Смысл правки — не «ускорить вопреки дизайну», а привести код к своему же дизайну. Детерминизм покупался на гранулярности бара, а наблюдаем он только на границе артефакта:
canonicalJson.serializeквантует каждое число в любом случае. Пербарная подрезка не давала артефакту ничего, чего не даёт сериализация, — она лишь меняла последующую арифметику.Decimal-арифметика сохранена. Её замена на целочисленную фиксированную точку — отдельный шаг C3, отданный на решение владельца по объёму.
Версии
ENGINE_VERSION0.0.0→0.1.0— изменилась семантика исполнения.traceFormatVersionостался1: форма трейса не тронута, а конфляция двух версий воссоздала бы проблему, которую решение (A) уже закрыло. Пакет0.1.1→0.2.0.Переморозка оправдана не тем, что тесты позеленели
Сдвиг измерен differential-харнессом на замороженных лентах (backtester#181), сверявшимся с состоянием до всей серии перф-волн — и до этого бампа версии, чтобы отделить численный эффект от смены
engineVersionв самом трейсе.baseline.jsonuniverse-multi.jsonvariant.jsonПоследовательность решений, состав ордеров и сделок, индексы баров и метки времени — совпали. Двинулись только величины, в десятом значащем разряде. Пример:
trades[0].size89.33037948 → 89.330379475452.Машинный вердикт харнесса: ПЕРЕМОРОЗКА РАЗРЕШЕНА (код возврата 0).
Перф
profile-runner, trusted-зонд, 20 000 баров,taskset -c 2,3, min из 6, заморозка контекста снята:×1.49, −33%. Одна чистая пара — стенд был занят почти всё время замера, и гейт из backtester#177 отклонил остальные попытки. Порядок величины согласуется с долей
decimal.jsв профиле (30.8%).Тест, который стал строже
Юнит-тест на размер закрепляет теперь обе половины инварианта: внутри движка значение полное (
9.995002498750624), а на границе даёт ровно то число, которое раньше подрезалось пербарно (9.9950025). Без второй половины тест разрешал бы молча потерять квантизацию совсем.Гейты
tsc --noEmit— 0vitest run— 100 passedgate:determinism— clean (no ambient sources insrc/)gate:tapes— 3 tape(s) OKЧто дальше
Потребитель (backtester) поднимает пин и перемораживает свои golden'ы отдельным PR — форма артефактов не изменилась, но
traceRefкаждого прогона другой.🤖 Generated with Claude Code