Skip to content

Float safety: Gemini Titan buildOrder params.price.toFixed(2) incorrect rounding #666

@realfishsam

Description

@realfishsam

Risk Level

HIGH

Location

core/src/exchanges/gemini-titan/index.ts:134

Code

const payload: Record<string, unknown> = {
    symbol: instrumentSymbol,
    orderType: 'limit',
    side: params.side,
    quantity: String(params.amount),
    price: params.price !== undefined ? params.price.toFixed(2) : '0.50',  // ← line 134
    outcome: side,
    timeInForce: params.type === 'market' ? 'immediate-or-cancel' : 'good-til-cancel',
};

Problem

.toFixed(2) on a JavaScript number does not guarantee correct decimal rounding. .toFixed() is implemented using float arithmetic internally and will produce wrong results for any price that is not exactly representable in IEEE 754. The resulting string is sent directly in the order payload to the Gemini Titan API as the limit price.

The classic example: (1.005).toFixed(2) returns "1.00" in V8 (not "1.01") because 1.005 is stored as 1.00499999... in IEEE 754. Any prediction market price with the digit 5 in the third decimal place is at risk.

Example Failure

// Prices that round incorrectly with .toFixed(2):
(0.005).toFixed(2)  = "0.00"  // should be "0.01" — order at wrong cent
(0.015).toFixed(2)  = "0.01"  // should be "0.02"
(0.025).toFixed(2)  = "0.02"  // should be "0.03"
(0.575).toFixed(2)  = "0.57"  // should be "0.58" — 1 cent off in a 57¢ market
(0.995).toFixed(2)  = "0.99"  // should be "1.00"

// The pattern: any x.xx5 price is at risk
// Gemini Titan tick size is 0.01, so 2dp is the correct scale,
// but the rounding step itself is wrong for ~half of all .xx5 prices

A 1-cent error in a limit price means the order sits on the wrong side of a spread or matches at the wrong level.

Suggested Fix

import Decimal from 'decimal.js';
price: params.price !== undefined
    ? new Decimal(params.price).toDecimalPlaces(2, Decimal.ROUND_HALF_UP).toFixed(2)
    : '0.50',

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