Skip to content

betting_combat.domain.fight

The fight clock: Kalshi’s live fight feed → the backtest’s decision points.

UFC rounds are 5:00 with 1:00 breaks. Kalshi’s feed reports current_round but its round clock stays empty until a fight ends (live session 2026-09-26), so the clock is anchored on the moment each round number is FIRST seen: the feed switches to round r+1 when round r+1 starts (fight 5: +6m13s after the start, i.e. 5:00 + the 1:00 break + feed lag). Re-anchoring every round keeps the clock honest even when the feed is a few seconds late.

A decision point is one clock_min (whole minutes since the start, breaks included). The mapping below reproduces the research backtest (ufc/model.py::decision_points) minute for minute; tests pin that parity.

Classes

ClockPosition

Where a live fight is, in the backtest’s vocabulary.

FeedPhase

Bases: StrEnum

FightClock

Round anchors for one fight. Mutable on purpose: it accumulates what the feed showed.

observe

observe(current_round: int | None, at: dt.datetime) -> bool

Record the first moment each round number is seen while the fight is live.

A round is only believed when it can have started: at most the scheduled rounds, and at least MIN_ROUND_GAP_MINUTES per round after the last round anchored (a glitching feed must not move the clock forward for good). The first round seen by a run that joined late is anchored where it is found. Returns False when the reading was not believed.

position

position(current_round: int | None, now: dt.datetime) -> ClockPosition | None

The decision point at now, or None when no decision is due (before the fight’s first full minute, in a break the feed has not closed yet, or after the scorecard window).

Phase

Bases: StrEnum

Functions

feed_phase

feed_phase(status: str | None, winner: str | None = None) -> FeedPhase

Classify one feed status. A declared winner always means the fight is over. Only the known live statuses start the clock (recorded 2026-09-26: inprogress); an unknown status is never taken for a live fight.

last_clock_min

last_clock_min(rounds: int) -> int