perps: MultiplierDecimal for ratio fields + NullableList for margin-account arrays (#407, #408)#414
Conversation
…ccount arrays Closes #408, closes #407. Pre-release polish (3.2.0 unreleased), so no breaking change for any published version. #408 — ratio fields (spec `number/double`) were bare `float`, inconsistent with the perps WS ticker + exchange surfaces which already use `MultiplierDecimal`, and lossy for money-adjacent math (funding_rate × notional, fee_rate × value). Switched to `MultiplierDecimal` (exact `Decimal`, string-serialized): - markets: `leverage_estimate` - margin_account: `position_leverage`, `account_leverage`, `maker_fee_rates`/`taker_fee_rates` (dict values) - funding: `funding_rate` (×3) - portfolio: `return_on_equity` (roe) #407 — `GetMarginBalanceResponse.subaccount_balances` and `GetMarginRiskResponse.positions` were bare `list[...]`; switched to `NullableList[...]` (kept required) to tolerate a server-returned `null` like sibling perps responses. NullableList is a strict superset of `list` (adds null→[], never rejects valid data). The `roe` "required" half of #407 was a verified false positive (spec marks it nullable, not required) — left optional. Tests: updated float assertions to `Decimal`; added null-array regression tests for both margin-account list fields. mypy strict + ruff + contract drift green.
There was a problem hiding this comment.
Code review: This PR correctly replaces float with MultiplierDecimal on ratio fields and NullableList on two margin-account arrays. One minor gap: MarginFundingHistoryEntry.funding_rate is changed but has no updated Decimal assertion in the history test. Everything else is correct and well-tested. Full review posted separately.
|
Full Code Review for PR 414 OVERVIEW: Two targeted pre-release improvements to the perps surface before v3.2.0 ships -- (1) #408 replaces bare float with MultiplierDecimal on ratio fields (leverage, funding rate, ROE, fee tiers), (2) #407 replaces bare list[T] with NullableList[T] on margin-account arrays to tolerate server-returned null. Rationale is sound: float is binary-lossy for money-adjacent math, and the WS ticker + exchange surfaces already used MultiplierDecimal. STRENGTHS:
ISSUES / SUGGESTIONS: Minor (1) -- Missing assertion for MarginFundingHistoryEntry.funding_rate: funding.py changes funding_rate in three models (MarginFundingRate, MarginFundingHistoryEntry, MarginFundingRateEstimate). Tests cover the first and third, but no assertion verifies MarginFundingHistoryEntry.funding_rate is Decimal. Adding assert isinstance(entry.funding_rate, Decimal) to the history endpoint happy-path test would close the gap. Minor (2) -- dict[str, MultiplierDecimal] coercion from JSON number: The fee-rate maps use MultiplierDecimal but the spec declares values as number/double (JSON numbers, not strings). Pydantic coerces them correctly and the fee_tiers test confirms it -- no action needed, just worth noting for readers wondering why a number/double field uses a string-serialized type. No issues with the NullableList approach: Keeping fields required (no default=None) while using NullableList is correct -- preserves spec intent while tolerating server behavior of returning null instead of []. TEST COVERAGE: Happy-path assertions updated in test_markets.py, test_portfolio.py, test_funding.py, test_margin_account.py. Null-array regression tests added for both NullableList fields. VERDICT: Approve with one suggestion -- add a Decimal assertion for MarginFundingHistoryEntry.funding_rate in the funding history test. Everything else is correct, consistent, and well-tested. |
Resolves the two pre-release modeling follow-ups from the multi-LLM review. 3.2.0 is unreleased, so these refine the perps surface before it ships — no breaking change for any published version.
#408 — Decimal-vs-float ratios
Ratio fields (spec
number/double) were barefloat, which was internally inconsistent (the perps WS tickerfunding_rateandexchange.pyrisk params already useMultiplierDecimal) and lossy for money-adjacent math (funding_rate × notional,fee_rate × value). Switched toMultiplierDecimal(exactDecimal, string-serialized):markets.pyleverage_estimatemargin_account.pyposition_leverage,account_leverage,maker_fee_rates/taker_fee_rates(dict values)funding.pyfunding_rate(×3)portfolio.pyreturn_on_equity(roe)#407 — NullableList for margin-account arrays
GetMarginBalanceResponse.subaccount_balancesandGetMarginRiskResponse.positionswere barelist[...]; switched toNullableList[...](kept required — no default) to tolerate a server-returnednull, matching sibling perps responses.NullableListis a strict superset oflist(addsnull→[], never rejects valid data).The
roe-should-be-required half of #407 was a verified false positive — the spec marksroenullable: truebut does not list it inrequired, so it stays optional (default=None).Verification
number/doubletyping againstspecs/perps_openapi.yaml.floatassertions →Decimal; added null-array regression tests for both margin-account list fields.mypy --strict+ruffclean; 1124 perps + contract-drift tests pass (drift needed no new exclusions — it checks field presence/required, not Python type).🤖 Generated with Claude Code