Skip to content

DollarDecimal serialization can emit scientific notation (Kalshi may reject) #191

Closed

Description

@TexasCoding

Summary

_decimal_to_str(value: Decimal) -> str returns str(value). Python's Decimal.__str__ emits scientific notation whenever the adjusted exponent is >0 or <-6:

>>> str(Decimal('1E+10'))   # '1E+10'
>>> str(Decimal('0.0000001'))  # '1E-7'
>>> str(Decimal('100').scaleb(20))  # '1.00E+22'

The Kalshi API documents <dollars_string> and fixed-point count wire formats with positional digits (examples: "0.5600", "100.00"). Any Decimal whose representation lands in scientific form will be sent to the server as e.g. "1E-2", either rejected with a 400 or — worse — accepted by a permissive parser at a different numeric value.

The risk surface is small for current price ranges (typical Decimals '0.0001'–'1.0000' stay in positional form), but real for:

  • FixedPointCount on large batch orders (count_fp, volume_fp accumulated cost in big positions)
  • Any arithmetic the user performs on Decimals before passing them in (a.scaleb, a * b with mixed exponents)
  • Future high-precision fields the spec may add

Location

kalshi/types.py:31-33

Evidence

# kalshi/types.py
def _decimal_to_str(value: Decimal) -> str:
    """Serialize Decimal back to string for API requests."""
    return str(value)

Recommended fix

Format positionally:

def _decimal_to_str(value: Decimal) -> str:
    return f'{value:f}'  # forces fixed-point, never scientific

Decimal.__format__('f') handles all finite Decimals correctly and is the documented positional format. Add unit tests covering:

  • Decimal('1E+10') → '10000000000'
  • Decimal('1E-7') → '0.0000001'
  • Decimal('0').scaleb(20) → '0E+20'-like input → positional
  • Decimal('0.5600') round-trip preserves trailing zeros (existing behavior)

Document the positional guarantee in CHANGELOG.

Severity & category

high / correctness

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions