Skip to content

Polish bundle: model small items (AwareDatetime in WS, V1 Literal, default=None, timestamp typing, Decimal NaN) #270

Closed

Description

@TexasCoding

Summary

Small model / type quality improvements bundled for a single PR.

Item 1 — WS user_orders and communications payloads use stdlib datetime, not AwareDatetime

#234 standardised REST models on AwareDatetime so a naive RFC3339-without-offset string is rejected. WS payloads use stdlib datetime, which accepts naive values. Pure consistency drift introduced by the partial #234 rollout.

Location: kalshi/ws/models/user_orders.py:3,55-58, kalshi/ws/models/communications.py:3,21,50,82,117,153.

Fix: Replace from datetime import datetime with from pydantic import AwareDatetime and retype the affected fields.

Item 2 — V1 CreateOrderRequest leaves enum-style fields as bare str, V2 uses Literal

# V1
side: str
action: str
time_in_force: str | None = None
self_trade_prevention_type: str | None = None
# V2
side: BookSideLiteral
time_in_force: TimeInForceLiteral
self_trade_prevention_type: SelfTradePreventionTypeLiteral

Users constructing a V1 request directly can pass side="foo" and only fail server-side; V2 callers fail at construction. With V1 deprecation set for no earlier than May 2026, the inconsistency is fresh enough to align before V1 sunset.

Location: kalshi/models/orders.py:163-176 (V1), :459-465 (V2).

Fix: Tighten V1 to Literals (safer call given the spec is stable today).

Item 3 — Optional response fields declared as T | None without default=None

T | None = Field(validation_alias=...) without an explicit default=None makes the field required-but-nullable: silent server omission raises ValidationError. The same modules use default=None on other optional fields, so the omission here looks accidental.

Location: kalshi/models/portfolio.py:67-86 (MarketPosition.total_traded/position), kalshi/models/historical.py:25-35 (Trade.count).

Fix: Audit against OpenAPI spec: where the field is required: true (even if nullable), keep as-is and add a docstring note. Where it isn't required, add default=None.

Item 4 — Timestamp typing inconsistent (int Unix seconds vs AwareDatetime)

Within a single resource, the same conceptual field is int on Balance/Deposit/Withdrawal and AwareDatetime on MarketPosition/Settlement. Users iterating positions and balances side-by-side have to special-case the type.

Location: kalshi/models/portfolio.py:46-49,81,142-153, kalshi/models/subaccounts.py:39-50.

Fix: Introduce a UnixSecondsTimestamp = Annotated[int, ...] alias and use it everywhere a server int64 timestamp lands, or document the convention class-by-class. Greppable wire shape either way.

Item 5 — Decimal NaN survives serialization

f"{Decimal('NaN'):f}" returns "NaN", and _coerce_decimal happily accepts Decimal("NaN") (it short-circuits at the isinstance(value, Decimal) check). A caller doing arithmetic that produces NaN and then constructing CreateOrderRequest(yes_price=that_nan) ships "NaN" on the wire to a real-money order placement endpoint.

Location: kalshi/types.py:36-38.

Fix: In _coerce_decimal, after coercion, call value.is_finite() and raise on NaN/Infinity. One branch protects every Dollar/FP field at parse and at serialise.

Item 6 — MarketLifecyclePayload timestamps lack the _ts_ms naming used elsewhere in v0.14+ backfill

open_ts: int | None = None
close_ts: int | None = None
determination_ts: int | None = None
settled_ts: int | None = None

If these are Unix-ms (consistent with sibling channels) the name is misleading; if they are seconds it's a third unit convention. Worth confirming against AsyncAPI and either renaming or annotating.

Location: kalshi/ws/models/market_lifecycle.py:25-31.

Fix: Verify spec units. If ms, rename to open_ts_ms etc. with back-compat aliases. If seconds, add a short docstring noting the unit.

Severity & category

low / polish

Metadata

Metadata

Assignees

No one assigned

    Labels

    polishCode-quality and DX improvements; non-functional

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions