refactor(core): execution и funding — через money.ts, Decimal уходит из сигнатур (E2) - #9
Merged
Merged
Conversation
…из сигнатур (E2) Восемь оставшихся мест из тридцати семи. Их не перевели вместе с остальными не по недосмотру: они ПЕРЕДАВАЛИ `Decimal` между функциями и наружу, и покомпонентный перевод сдвинул бы значения. Приватный `fillPrice` отдавал полную цену исполнения, а `computeOpenFill` делил на неё нотионал; замени его на `div(notional, shiftBps(...))` — и цена вышла бы во float64 ПЕРЕД делением, а она в общем случае непредставима точно. Решение — не дробить выражения, а назвать их целиком, ровно по правилу money.ts «одна функция — одно целое выражение»: sizeAtShiftedPrice notional / (base × (1 ± bps/1e4)) feeOnShiftedNotional base × (1 ± bps/1e4) × size × feeBps/1e4 fundingCost (rate8h / (ih·60)) × barMinutes × (size × mark) × sign Порядок множителей в каждой повторяет прежний дословно: умножение коммутативно в математике, но не в decimal.js — он округляет до рабочей точности на каждой операции, поэтому перестановка была бы сдвигом значений. `computeBarFunding` теперь возвращает `number`. Единственный вызывающий в движке и раньше немедленно звал `.toNumber()`. `perMinuteFundingFraction` СОЗНАТЕЛЬНО оставлен на `Decimal` — это единственное исключение, и причина записана в коде: внутри пакета его никто не зовёт, он существует для исследовательского контура backtester'а, который накапливает им дробь по всему окну удержания. Отдай он `number` — каждое слагаемое округлялось бы до сложения, и значения поехали бы у потребителя. А этот шаг значений не двигает по определению. Значения проверены, а не заявлены: рядом с новой арифметикой лежит дословная копия прежней, и обе сверяются ПОБИТОВО (`toBe`, не `toBeCloseTo`) на 15 000 случайных наборов — открывающий филл, закрывающий филл, фандинг. Плюс сохранён порядок проверок: непокрытый бар возвращает ноль ДО проверки интервала. Гейты: pnpm typecheck → 0; pnpm test → 120 passed / 0 failed; golden-tape (ref трасс против expected-traces.json) зелёный; gate:determinism и gate:tapes чистые.
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.
Шаг E2 волны E. Значений не двигает, меняет публичные сигнатуры.
Почему эти восемь мест не перевели вместе с остальными 29
Они передавали
Decimalмежду функциями и наружу. ПриватныйfillPriceотдавал полную цену исполнения, аcomputeOpenFillделил на неё нотионал. Замени его наdiv(notional, shiftBps(...))— и цена вышла бы во float64 перед делением, а она в общем случае непредставима точно: размер поехал бы в последнем разряде.Решение — назвать выражения целиком, а не дробить
Ровно по правилу
money.ts«одна функция — одно целое выражение»:sizeAtShiftedPricenotional / (base × (1 ± bps/1e4))feeOnShiftedNotionalbase × (1 ± bps/1e4) × size × feeBps/1e4fundingCost(rate8h / (ih·60)) × barMinutes × (size × mark) × signПорядок множителей в каждой повторяет прежний дословно: умножение коммутативно в математике, но не в
decimal.js— он округляет до рабочей точности на каждой операции, поэтому перестановка была бы сдвигом значений.Одно сознательное исключение
perMinuteFundingFractionостаётся наDecimal, и причина записана в коде: внутри пакета его никто не зовёт, он существует для исследовательского контура backtester'а (computeFundingPaidFraction), который накапливает им дробь по всему окну удержания, складывая сотни слагаемых. Отдай онnumber— каждое слагаемое округлялось бы до сложения, и отчёт о разрыве реализма поехал бы у потребителя. Шаг, который «не двигает значений», не может двигать их через границу пакета.Доказательство, а не заявление
Рядом с новой арифметикой лежит дословная копия прежней, и обе сверяются побитово (
toBe, неtoBeCloseTo) на 15 000 случайных наборов: открывающий филл (цена, комиссия, размер), закрывающий филл (цена, комиссия от нотионала закрытия), фандинг за бар. Отдельно закреплён порядок проверок: непокрытый бар возвращает ноль до проверки интервала.Что понадобится потребителю при bump'е пина
computeBarFundingтеперь возвращаетnumber. В backtester'е это два механических места —runner.ts(.toNumber()сразу после вызова) иtest/funding.test.ts. Значения там те же: вызывающий и раньше немедленно схлопывалDecimalв число. Правка принадлежит шагу bump'а пина, как это было с волной C (bt#183), а не этому PR.Гейты
pnpm typecheck→ 0pnpm test→ 120 passed / 0 failed (+4 новых)golden-tape(ref трасс противexpected-traces.json) — зелёный ⇒ трассы не сдвинулисьpnpm gate:determinism→ clean;pnpm gate:tapes→ 3 tape(s) OK