Skip to content

perps: MultiplierDecimal for ratio fields + NullableList for margin-account arrays (#407, #408)#414

Merged
TexasCoding merged 1 commit into
mainfrom
fix/perps-ratio-decimals-407-408
Jun 5, 2026
Merged

perps: MultiplierDecimal for ratio fields + NullableList for margin-account arrays (#407, #408)#414
TexasCoding merged 1 commit into
mainfrom
fix/perps-ratio-decimals-407-408

Conversation

@TexasCoding

Copy link
Copy Markdown
Owner

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 bare float, which was internally inconsistent (the perps WS ticker funding_rate and exchange.py risk params already use MultiplierDecimal) and lossy for money-adjacent math (funding_rate × notional, fee_rate × value). Switched to MultiplierDecimal (exact Decimal, string-serialized):

File Fields
markets.py leverage_estimate
margin_account.py position_leverage, account_leverage, maker_fee_rates/taker_fee_rates (dict values)
funding.py funding_rate (×3)
portfolio.py return_on_equity (roe)

#407 — NullableList for margin-account arrays

GetMarginBalanceResponse.subaccount_balances and GetMarginRiskResponse.positions were bare list[...]; switched to NullableList[...] (kept required — no default) to tolerate a server-returned null, matching sibling perps responses. NullableList is a strict superset of list (adds null→[], never rejects valid data).

The roe-should-be-required half of #407 was a verified false positive — the spec marks roe nullable: true but does not list it in required, so it stays optional (default=None).

Verification

  • Spec-checked every field's required/nullable + number/double typing against specs/perps_openapi.yaml.
  • Updated existing float assertions → Decimal; added null-array regression tests for both margin-account list fields.
  • mypy --strict + ruff clean; 1124 perps + contract-drift tests pass (drift needed no new exclusions — it checks field presence/required, not Python type).
  • CHANGELOG 3.2.0 entry updated to describe the ratio typing.

🤖 Generated with Claude Code

…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.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@claude

claude Bot commented Jun 5, 2026

Copy link
Copy Markdown

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:

  • Changes are surgical -- only targeted fields touched, no style drift
  • All changed fields are response-only models, so MultiplierDecimal string-serialization cannot break outbound requests
  • Regression tests added for both NullableList null-coercion paths
  • Float assertions updated to Decimal with isinstance checks throughout
  • Module docstrings updated in place

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant