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
Summary
_decimal_to_str(value: Decimal) -> strreturnsstr(value). Python'sDecimal.__str__emits scientific notation whenever the adjusted exponent is>0or<-6:The Kalshi API documents
<dollars_string>and fixed-point count wire formats with positional digits (examples:"0.5600","100.00"). AnyDecimalwhose 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:FixedPointCounton large batch orders (count_fp,volume_fpaccumulated cost in big positions)a.scaleb,a * bwith mixed exponents)Location
kalshi/types.py:31-33Evidence
Recommended fix
Format positionally:
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 → positionalDecimal('0.5600')round-trip preserves trailing zeros (existing behavior)Document the positional guarantee in CHANGELOG.
Severity & category
high / correctness