Skip to content

polish(types): retype count/size/volume fields to FixedPointCount (#90)#140

Merged
TexasCoding merged 1 commit into
mainfrom
polish/issue-90-type-drift
May 17, 2026
Merged

polish(types): retype count/size/volume fields to FixedPointCount (#90)#140
TexasCoding merged 1 commit into
mainfrom
polish/issue-90-type-drift

Conversation

@TexasCoding

Copy link
Copy Markdown
Owner

Summary

Wave 5 #90 — swap the type annotation for 17 count/size/volume fields across 9 response models from DollarDecimal to FixedPointCount. The runtime behavior is byte-identical (both validators wrap the wire _fp/_dollars int and yield a Decimal), but the annotation now communicates the right semantics: these fields represent integer share/contract counts, not dollar amounts.

Fields swapped

Class Field(s)
Market yes_bid_size, yes_ask_size, no_bid_size, no_ask_size, volume, volume_24h, open_interest
Candlestick volume, open_interest
OrderbookLevel quantity
Fill count
Trade count
MarketPosition position
EventPosition total_cost_shares
Settlement yes_count, no_count
Series volume

CHANGELOG [Unreleased] → Changed entry added.

Bugs uncovered

None. _to_decimal_dollars and _to_decimal_fp are byte-identical, so no wire-format-mocked test fixtures needed updating.

Out of scope

  • OrderbookDeltaPayload.side: strLiteral["yes", "no"] lives in kalshi/ws/models/orderbook_delta.py (Wave 3/4 territory). Noted in commit body.
  • Consolidating _to_decimal_dollars / _to_decimal_fp into a single callable was a refactor suggestion in the issue but not required by the title — deferring as easy follow-up.

Closes #90

Test plan

  • Suite: 1654 passed, 48 skipped (matches baseline)
  • uv run ruff check . clean
  • uv run mypy kalshi/ clean (strict)

Several response-model fields used DollarDecimal even though their wire
alias is `_fp` (FixedPointCount). The annotation falsely signaled "dollar
amount" for what are semantically share/contract counts. Both validators
are byte-identical today — runtime behavior unchanged — but if either
parser ever diverges (per-family scale factor, different precision rule)
every mistyped field silently breaks.

Swapped:
- Market: yes_bid_size, yes_ask_size, no_bid_size, no_ask_size, volume,
  volume_24h, open_interest
- Candlestick: volume, open_interest
- OrderbookLevel: quantity
- Fill: count
- Trade: count
- MarketPosition: position
- EventPosition: total_cost_shares
- Settlement: yes_count, no_count
- Series: volume

Both DollarDecimal and FixedPointCount resolve to `Decimal` at the type
level, so user code handling these fields as Decimal is unaffected.
Drift tests pass (the spec doesn't constrain SDK-side type names, only
schema names + presence).

The orderbook_delta.py `side: str → Literal["yes", "no"]` fix mentioned
in the issue is in kalshi/ws/ — out of scope for this branch (Wave 3/4
territory). Filed/tracked separately.

Closes #90

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@claude

claude Bot commented May 17, 2026

Copy link
Copy Markdown

Code Review — PR #140: polish(types): retype count/size/volume fields to FixedPointCount

Overview

Clean, surgical semantic fix. All 17 fields correctly retyped across 9 models with no unrelated changes. The diff traces directly to the stated goal. No issues with correctness or safety.


Positives

  • Precise scope. Every changed line is a direct consequence of the retypings — no formatting cleanup, no opportunistic refactors.
  • series.py import correctly cleaned up. DollarDecimal was the only usage in that file; removing it from the import is right and avoids a stale import lint warning.
  • OrderbookLevel docstring updated. The old comment said "Quantity is DollarDecimal to support fractional contracts" — that rationalization was wrong (the real reason is wire semantics, not fractional support). The new wording is accurate.
  • Test suite unaffected. Since _to_decimal_dollars and _to_decimal_fp are byte-identical, existing fixtures exercise the same code paths. No test churn needed.

Suggestions

1. No regression guard for the type annotations themselves (minor)

Since both validators are byte-identical, a future developer who accidentally reverts a field back to DollarDecimal (or adds a new count field with the wrong type) would see all tests pass. Consider adding a lightweight annotation test:

# tests/test_type_annotations.py
from kalshi.models.markets import Market, OrderbookLevel, Candlestick
from kalshi.models.portfolio import MarketPosition, EventPosition, Settlement
from kalshi.models.orders import Fill
from kalshi.models.historical import Trade
from kalshi.models.series import Series
from kalshi.types import FixedPointCount

def _is_fpc(model, field: str) -> bool:
    """Check that a field's inner annotation is FixedPointCount."""
    metadata = model.model_fields[field].metadata
    # FixedPointCount is Annotated[Decimal, ...]; metadata carries the validators
    return FixedPointCount.__metadata__ == tuple(metadata)

def test_count_size_volume_fields_are_fixed_point_count():
    cases = [
        (Market, ["yes_bid_size", "yes_ask_size", "no_bid_size", "no_ask_size",
                  "volume", "volume_24h", "open_interest"]),
        (Candlestick, ["volume", "open_interest"]),
        (Fill, ["count"]),
        (Trade, ["count"]),
        (MarketPosition, ["position"]),
        (EventPosition, ["total_cost_shares"]),
        (Settlement, ["yes_count", "no_count"]),
        (Series, ["volume"]),
    ]
    for model, fields in cases:
        for f in fields:
            assert _is_fpc(model, f), f"{model.__name__}.{f} should be FixedPointCount"

This makes the semantic intent part of the test contract and would fail loudly if a future wave accidentally reintroduces DollarDecimal on one of these fields.

2. _to_decimal_dollars and _to_decimal_fp are byte-identical — noted, but worth a comment (nit)

The PR correctly calls this out as a deferred refactor. The only risk is divergence: if someone updates _to_decimal_dollars (e.g., to handle a new edge case) without touching _to_decimal_fp, the two types silently behave differently. A one-line comment in types.py beside each function noting the intentional duplication and the plan would prevent a future reader from "fixing" one and missing the other:

# Intentionally parallel to _to_decimal_dollars — see #90 for planned consolidation
def _to_decimal_fp(value: Any) -> Decimal:

Low priority, but cheap to add.

3. Is FixedPointCount exported from the public kalshi namespace? (question)

Users who want to annotate their own variables or functions with the correct type (e.g., volume: FixedPointCount) need it accessible via from kalshi import FixedPointCount. If it isn't exported yet, this PR is a natural time to add it since it's now the annotated type on many public fields.


Verdict

Approve with the above as suggestions, not blockers. The core change is correct and the test plan is solid. The annotation regression guard (#1) would be a nice addition if you have appetite for it; the other two are genuinely optional polish.

@TexasCoding
TexasCoding merged commit 9e08d91 into main May 17, 2026
4 checks passed
@TexasCoding
TexasCoding deleted the polish/issue-90-type-drift branch May 17, 2026 18:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Type-annotation drift: size/count fields use DollarDecimal instead of FixedPointCount

1 participant