Skip to content

Fix/pricing correctness - #55

Open
Shawnchee wants to merge 8 commits into
mainfrom
fix/pricing-correctness
Open

Shawnchee wants to merge 8 commits into
mainfrom
fix/pricing-correctness

Conversation

@Shawnchee

Copy link
Copy Markdown
Collaborator

Summary

Correctness fixes to the pricing/sizing paths shipped in 0.3.0. Every item corrects a
wrong-number-or-throw bug that reaches users or agents. No API surface is added and no
already-correct behavior changes — vanilla condors, WETH/USDC sizing, and the
non-negative pricing paths are byte-identical to before.

  • Multi-leg quote clamping — getSpreadPricing / getCondorPricing /
    getButterflyPricing clamp the final bid/ask to [0, width/spot] (deep-ITM spreads
    no longer quote above max payout, or negative).
  • Iron-condor credit pricing — the type: 'iron' branch now uses correct
    short-inner / long-outer weights instead of vanilla-condor weights (was flooring bid
    to $0 with a negative ask). Vanilla condors unchanged.
  • getPositionPricing — routes USD-magnitude values through string-exact toBigInt
    (was throwing for every BTC option and any structure wider than ~$9,007); native-
    collateral premiums no longer overstated by ~spot×.
  • calculateMaxContracts — discriminates base-vs-quote collateral by token identity,
    fixing ~1000× cbBTC call mis-sizing. WETH/USDC unchanged.
  • utils.fromBigInt — extracts the sign before formatting, so negative sub-unit
    values render correctly (-500n,6 → "-0.0005", was "0.00-5").
  • calculateNumContracts — returns 0 for degenerate strikes (was tradeAmount);
    MCP calculate_num_contracts schema constrains strikes (minItems: 1,
    exclusiveMinimum: 0).

package.json and CHANGELOG.md are both bumped to 0.3.1 — the branch is
release-ready (merge → cut a v0.3.1 GitHub Release → publish.yml publishes to npm).

Test plan

  • npm run typecheck — clean
  • npm run lint — clean
  • npm run build — ESM + CJS + types build clean
  • npm test — live mainnet integration suite: skipped. All changes are pure
    off-chain math + one MCP JSON-schema constraint; no on-chain read/write surface
    changed. Coverage is via dedicated property suites below.
  • Manual smoke test — six property suites, all green: test:mmpricing 9/9,
    test:ironcondor 4/4, test:positionpricing 6/6, test:maxcontracts 6/6,
    test:frombigint 7/7, test:numcontracts 8/8.
  • For ABI changes: N/A — no ABI files touched.

Breaking changes

None. Every change is a behavior correction; no method signatures or return shapes
changed. Structures that were already priced/sized correctly are byte-identical.

Codex review

Not required by the trigger (no ABI, payable, or zero-address changes — pure pricing
math + a schema constraint). The fixes were adversarially cross-verified across two
models during development.

Checklist

  • CHANGELOG.md updated under the next planned version (0.3.1)
  • package.json version bumped to 0.3.1 (matches CHANGELOG)
  • Migration guide updated if breaking — N/A (no breaking changes)
  • GitBook docs updated if user-facing surface changed — N/A (behavior corrections
    only, no new/changed public surface)
  • No Co-Authored-By trailers (verified across all 8 commits)

Shawnchee added 8 commits July 9, 2026 17:18
Multi-leg quotes composed from per-leg MM feeds could exceed the structural
max payout for deep-ITM strikes (e.g. $70.94 quoted on a $50-wide spread).
Clamp final ask/bid (after FEE_MULTIPLIER) to [0, widthUsd/spot] in
getSpreadPricing, getCondorPricing, getButterflyPricing; the ask side also
gains the zero floor it lacked (negative asks on wrong-order strikes)
getCondorPricing applied vanilla (debit) condor weights to iron condors
unconditionally, so the credit came out sign-flipped: a real +$22 credit
priced as raw bid -$25.66 / ask -$22.22 and displayed as Bid $0.00 —
every iron condor was untradeable.

Add a type-aware branch per IRON_CONDOR_PRICING_FIX.md: for type 'iron'
(legs [PutK1, PutK2, CallK3, CallK4]),
  sellNet = bid(K2) + bid(K3) - ask(K1) - ask(K4)
  buyNet  = ask(K2) + ask(K3) - bid(K1) - bid(K4)
matching the SDK's own iron_condor payout/collateral orientation in
utils.ts. Vanilla call/put condors are byte-identical (verified to 1e-12
over 400 randomized cases).

Adds tests/properties/iron-condor-pricing.test.ts (npm run test:ironcondor):
worked example credit ~$20.94 bid / ~$27.22 ask, genuine-floor case,
vanilla byte-identical regression guards.
…Int; native-collateral premium units

- getPositionPricing: spot price and collateral-per-contract now scale via toBigInt(value.toFixed(12), 12) instead of floatToBigInt, which throws for values >= ~9007 (TNU-AUDIT-0056 guard) — every BTC option threw before
- getPositionPricing: premium/collateralCost convert to USD only when the collateral IS USD; WETH/cbBTC premiums stay in native underlying terms (was overstated by ~spot-price×, e.g. 100 WETH instead of 0.05 WETH)
- getSpreadPricing/getCondorPricing/getButterflyPricing: widthUsd scaling migrated the same way — BTC structures with width >= ~$9,007 threw (reproduced live in the dapp RFQ form with listed 48000/58000 strikes)
…lateMaxContracts

cbBTC is 8-decimal BASE collateral for inverse calls, but the old
collateralDecimals >= 18 heuristic routed it into the USDC/linear
strike-division branch, under-counting max fill size ~1000x (live on
24 real Base book orders, under-counted 662-803x). Discriminate by
token identity instead: USDC is the only quote collateral; everything
else sizes 1 contract per whole token, rescaled from native decimals
to 6-dec numContracts
Negative bigints under one whole unit rendered garbage strings
(-500n @ 6dp -> "0.00-5" instead of "-0.0005") because the minus sign
was padded and sliced as if it were a digit. formatAmount/formatPrice
inherited the corruption. Sign is now extracted first, digits formatted
from the absolute value, and "-" re-prefixed; positive and decimals-0
outputs are byte-identical to before
…hema (for the above problem)

calculateNumContracts' PUT/LINEAR_CALL/PHYSICAL_PUT fallback returned
tradeAmount on missing/zero strike (e.g. 2000 USDC -> 2000 contracts) while
calculateCollateralRequired returns 0 for the same input — an internally
inconsistent "2000 contracts for $0 collateral" answer. Return 0 to match every other product's guard

Also add minItems:1 + exclusiveMinimum:0 to the MCP calculate_num_contracts strikes schema so agent callers can't reach the degenerate path directly
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant