Skip to content

WS: count/size/timestamp payload fields typed as raw str (should be Decimal/datetime) #198

Description

@TexasCoding

Summary

WebSocket payload models silently regress to plain str for count/size/volume/timestamp fields that REST models type as FixedPointCount / DollarDecimal / datetime. The same logical quantity (e.g. count, volume, created_ts) round-trips as Decimal/datetime on REST and as raw string on WS.

Consumers driving limits, hedging, P&L, or cross-stream correlation off WS must Decimal(...) / datetime.fromisoformat(...) every field by hand. Any oversight is a silent type error:

  • str + int → TypeError
  • str * 2 → doubled string (not doubled number)
  • mixing REST Decimal positions with WS str fills → comparison silently false
  • Decimal('5.00') != '5.00' quietly

Even within kalshi/ws/models/, types are inconsistent across closely-related payloads.

Affected fields

Count/size/volume (should be FixedPointCount per CHANGELOG v2.0 retyping)

  • kalshi/ws/models/ticker.py:34-50volume, open_interest, yes_bid_size, yes_ask_size, last_trade_size
  • kalshi/ws/models/trade.py:21-26count
  • kalshi/ws/models/fill.py:25-32count, post_position
  • kalshi/ws/models/user_orders.py:33-43fill_count, remaining_count, initial_count
  • kalshi/ws/models/market_positions.py:19-37position, volume
  • kalshi/ws/models/orderbook_delta.py:18-26OrderbookSnapshotPayload.yes / .no is list[tuple[str, str]]

Note the internal inconsistency: OrderbookDeltaPayload (same module) correctly uses DollarDecimal/FixedPointCount. So the same logical pair (price, qty) is typed differently on snapshot vs delta.

RFQ/Quote contracts typed inconsistently

  • kalshi/ws/models/communications.py:31-34RfqCreatedPayload.contracts: str | None
  • kalshi/ws/models/communications.py:64-68RfqDeletedPayload.contracts: FixedPointCount | None ← already correct
  • kalshi/ws/models/communications.py:117-120QuoteAcceptedPayload.contracts_accepted: str | None

Timestamps (should be datetime / pydantic.AwareDatetime per REST convention)

  • kalshi/ws/models/user_orders.py:57-59created_time, last_update_time, expiration_time (all str)
  • kalshi/ws/models/communications.py:30, 52, 79, 114, 158, 163created_ts, deleted_ts, executed_ts, etc. (with an inline TODO(v0.15.1) comment acknowledging the drift)

REST equivalent: kalshi/models/orders.py:88-89 created_time: datetime | None.

Recommended fix

Replace str with FixedPointCount / DollarDecimal on every _fp / _dollars-aliased field, and with datetime (or pydantic.AwareDatetime to force tz-awareness) on every ISO-8601 timestamp field. Pydantic handles both coercions automatically via the existing BeforeValidator and built-in datetime parser.

For OrderbookSnapshotPayload.yes/.no, change list[tuple[str, str]] to list[tuple[DollarDecimal, FixedPointCount]] (Pydantic coerces per element). Drop the manual Decimal(...) coercion in kalshi/ws/orderbook.py:79-80.

Update fixtures in tests/ws/test_models.py to match — the wire format does not change.

Add a parametrized contract test that asserts type symmetry across REST and WS for shared logical fields (count, volume, created_time).

Severity & category

high / consistency, ws

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingwsWebSocket-related

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions