refactor(core): единая точка прохода для денежной арифметики - #7
Merged
Conversation
До этого `new Decimal(...)` стоял россыпью в 37 местах пяти файлов, и правила денежного счёта не были записаны нигде — они держались на том, что кто-то их помнит. Любая правка по деньгам начиналась с археологии, а смена представления означала 37 правок и 37 шансов ошибиться. ЗНАЧЕНИЯ НЕ ДВИНУЛИСЬ. Это извлечение, а не оптимизация: golden-tape тест на байт-идентичность замороженных ожиданий проходит без переморозки, 112 тестов зелёные, gate:determinism и gate:tapes чистые. Именно поэтому шаг сделан ОТДЕЛЬНО от правок по производительности — после него они становятся правкой одного файла с готовым доказательством. ГЛАВНОЕ ПРАВИЛО МОДУЛЯ: одна функция — одно целое выражение. Это не стилистика, а требование точности, и оно неочевидно: `Decimal(cash).plus(gross).minus(fee).toNumber()` выходит во float64 ровно один раз, а сборка из примитивов `sub(add(cash, gross), fee)` — два раза, и промежуточный результат округляется до ближайшего double раньше времени. Поэтому универсальных примитивов, из которых можно собрать что угодно, здесь нет; есть составные операции под конкретные выражения движка. Само правило закреплено тестом, который показывает расхождение на живом примере. Переведены portfolio.ts (20 мест), risk.ts (4), protection.ts (5). Не переведены execution.ts и funding.ts: они передают Decimal между функциями и наружу, поэтому их перевод — это смена публичных сигнатур пакета, а не извлечение арифметики. Причина записана в заголовке модуля. Брендированные типы Price/Qty/Cash объявлены, но не применены: их протаскивание через OpenPosition, OpenFill и Trade затрагивает публичные типы контракта. Отдельная правка, чисто типовая, с нулевым эффектом в рантайме (control-center docs/analysis/22). Шкала вынесена в SCALE как параметр, а не константа по коду, — чтобы переход на per-instrument tick size был правкой этого файла. 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.
Точка прохода для денежной арифметики — то, о чём владелец спросил: «не получится ли, что потом опять надо учитывать нюансы; хотелось постоянный механизм».
Проблема
new Decimal(...)стоял россыпью в 37 местах пяти файлов. Правила денежного счёта не были записаны нигде — они держались на том, что кто-то их помнит. Поэтому любая правка по деньгам начиналась с археологии, а смена представления означала 37 правок и 37 шансов ошибиться.Значения не двинулись
Это извлечение, а не оптимизация.
golden-tape.test.ts— байт-идентичность замороженных ожиданий — проходит без переморозки. 112 тестов зелёные,gate:determinismиgate:tapesчистые.Именно поэтому шаг сделан отдельно от правок по производительности: после него они становятся правкой одного файла с уже готовым доказательством, что рефакторинг ничего не сдвинул.
Главное правило: одна функция — одно целое выражение
Это не стилистика, а требование точности, и оно неочевидно. Выражение
держит полную десятичную точность на всей цепочке и выходит во
float64ровно один раз. Сборка из примитивов —sub(add(cash, gross), fee)— даёт два выхода, и промежуточный результат округляется до ближайшего double раньше времени.Поэтому здесь нет универсальных
add/sub, из которых можно собрать что угодно: каждая функция повторяет одно конкретное выражение движка целиком. Само правило закреплено тестом, который показывает расхождение на живом примере — если оно когда-нибудь исчезнет, тест об этом скажет.Что переведено, что нет
portfolio.tsprotection.tsrisk.tsexecution.tsfunding.tsexecution.tsиfunding.tsне просто считают — они передаютDecimalмежду своими функциями и наружу:fillPriceвозвращаетDecimal, аcomputeOpenFillделит на него в полной точности;computeBarFundingтоже отдаётDecimal. Перевести их, не сдвинув значений, можно только вместе со сменой публичных сигнатур, то есть это правка контракта пакета. Смешивать её с этим шагом значит потерять его главное свойство. Причина записана в заголовке модуля, а не только здесь.Заготовлено на будущее
Брендированные типы
Price/Qty/Cashобъявлены, но не применены: их протаскивание черезOpenPosition,OpenFill,Tradeзатрагивает публичные типы контракта. Отдельная правка, чисто типовая, с нулевым эффектом в рантайме — типобезопасность Nautilus без аллокации, которой избавлялась ethers.js (разбор — control-centerdocs/analysis/22).Шкала вынесена в
SCALEкак параметр, а не рассыпана константой по коду, — чтобы переход на per-instrument tick size (precisionMode: TICK_SIZEу ccxt) был правкой этого файла, а не вторым заходом по всем местам.Гейты
tsc --noEmit— 0vitest run— 112 passed (было 100; +12 тестов на правила модуля)gate:determinism— cleangate:tapes— 3 tape(s) OK🤖 Generated with Claude Code