Skip to content

Float safety: Gemini Titan normalizer (bestBid + bestAsk) / 2 parseFloat midpoint #714

@realfishsam

Description

@realfishsam

Risk Level

HIGH

Location

core/src/exchanges/gemini-titan/normalizer.ts:154-161

Code

normalizeContract(contract: GeminiRawContract, event: GeminiRawEvent): UnifiedMarket | null {
    const bestBid = contract.prices?.bestBid ? parseFloat(contract.prices.bestBid) : 0.5;
    const bestAsk = contract.prices?.bestAsk ? parseFloat(contract.prices.bestAsk) : 0.5;
    const lastPrice = contract.prices?.lastTradePrice
        ? parseFloat(contract.prices.lastTradePrice)
        : (bestBid + bestAsk) / 2;

    const yesPrice = roundPrice(Math.max(0, Math.min(1, lastPrice)));
    // ...
}

Problem

The midpoint (bestBid + bestAsk) / 2 adds two parseFloat values and divides by 2. The addition can produce a result with a set bit that doesn't exist in either operand, and division by 2 is exact in IEEE 754 — but the damage is already done. The result is then passed to roundPrice (Math.round(n * 100) / 100) which is an existing filed issue but compounds the error from the midpoint.

This is the same pattern as "Kalshi normalizer mid-price (ask + bid) / 2 parseFloat arithmetic" — the Gemini Titan venue has the identical issue and is not covered by that report.

Example Failure

bestBid = parseFloat("0.43")  // 0.42999999999999999778
bestAsk = parseFloat("0.45")  // 0.44999999999999999778

(0.42999999999999999778 + 0.44999999999999999778) / 2
= 0.87999999999999999556 / 2
= 0.43999999999999999778

roundPrice(0.43999999999999999778):
  0.43999999999999999778 * 100 = 43.99999999999999289 (not 44.0!)
  Math.round(43.99999999999999289) = 43   ← WRONG! rounds DOWN to 0.43 not 0.44

The quoted YES price becomes 0.43 instead of the correct 0.44 midpoint — a full tick error that propagates to the order router.

Suggested Fix

Compute the midpoint in a numerically stable way:

// Safe midpoint: avoids intermediate sum overflow / rounding
const lastPrice = bestBid + (bestAsk - bestBid) / 2;
// Or use Decimal.js:
const lastPrice = new Decimal(bestBid).plus(bestAsk).dividedBy(2).toNumber();

Found by automated float safety audit

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions