Skip to content

betting_combat.rounds.market.timing

The historical fight clock: when each Kalshi-listed fight started and when its result became known, recovered from Kalshi’s 1-minute candles and the UFCStats result.

Ported from the research (ufc/inplay.py: load_ufcstats, build_fights, minute_frame, fair_and_activity, snap_time, fight_start; ufc/final_assessment.py: _distance_jump, use_timing with Cfg(timing='consistent', lag=0.5)). The research switched its builders to the consistent clock by replacing inplay.fight_start globally; here the clock is an explicit object, :class:FightClock, that every builder is handed. The numbers are identical.

  • snap: the first minute the eventual winner’s price (the mean of A’s mid and 1 - B’s mid) is at 97c or more and never falls below 90c again. A snap on the first minute both books quote means the result was priced before the candles begin: the real snap, and any clock anchored on it, is unknown, and the fight is untimed (:func:timed_fights leaves it out, so it has no Group 6 values, no Set 3 / Set 4 rows and no candidate bets; research fixed 2026-09-30, the data layer’s services.rounds_data.fight_snaps rule).
  • template start (the research’s “original” timing): a finish counts back from the snap by the official finish time (5-minute rounds, 1-minute breaks); a decision fits the round/break activity template.
  • consistent start: finishes count back from the snap by the official time plus lag minutes of market reaction; decisions count back from the distance market’s jump at the final bell (plus lag) when that lands within 15 minutes of the template, else keep the template.

Classes

FightClock

The consistent fight clock (research use_timing(Cfg()): timing ‘consistent’, lag 0.5 minutes).

Args: distance_minutes: the distance markets’ 1-minute candles by ticker (kalshi_distance_minutes.json). lag: minutes of market reaction subtracted from every anchor.

distance_jump

distance_jump(key: str) -> pd.Timestamp | None

When KXUFCDISTANCE-<key>-DIST jumped to the result (the final bell of a decision); None without candles or a jump.

start

start(fight: Any, frame: pd.DataFrame, snap: pd.Timestamp) -> tuple[pd.Timestamp, str] | tuple[None, None]

(fight start, how it was anchored): finish_anchor, bell_anchor or break_template; (None, None) when the method has no anchor. fight needs event_ticker, method, rounds, finish_round, finish_secs.

Functions

build_fights

build_fights(kalshi_fights: pd.DataFrame, markets: Iterable[Mapping[str, Any]], ufcstats: pd.DataFrame) -> pd.DataFrame

One row per Kalshi fight matched to exactly one UFCStats bout (card date +/- 1 day, both names) and listing exactly two winner markets (ticker_a < ticker_b).

Args: kalshi_fights: fights_kalshi.csv as read by pandas. markets: the Kalshi winner markets (kalshi_markets.json), each with ticker. ufcstats: :func:load_ufcstats.

fair_and_activity

fair_and_activity(ticker_a: str, ticker_b: str, minutes: Mapping[str, list[dict[str, Any]]]) -> pd.DataFrame | None

Both winner books on one minute clock (columns ('a', field), ('b', field), every column carried forward), fair = P(A wins) = (A’s mid + 1 - B’s mid) / 2 and act = its absolute minute change. None when either market has no candles.

Carrying every column forward includes vol: past one market’s last candle its last minute’s volume repeats (research behaviour, kept for parity).

load_ufcstats

load_ufcstats(events: pd.DataFrame, results: pd.DataFrame) -> pd.DataFrame

UFCStats bouts with their date, fighters, method, finish time and scheduled rounds.

Args: events: ufcstats_ufc_event_details.csv as read by pandas. results: ufcstats_ufc_fight_results.csv as read by pandas.

minute_frame

minute_frame(candles: Iterable[Mapping[str, Any]]) -> pd.DataFrame

One row per minute from a market’s first candle to its last: bid, ask (YES close, dollars; close_dollars or the older close), vol, mid. A minute without a candle had no change: bid/ask carry forward, vol 0. Empty (no columns) without candles.

template_start

template_start(fight: Any, frame: pd.DataFrame, snap: pd.Timestamp) -> tuple[pd.Timestamp, str] | tuple[None, None]

The research’s original estimate (inplay.fight_start): a finish anchored on the snap minus the official time, a decision fitted by the round/break template. fight needs method, rounds, finish_round, finish_secs.

timed_fights

timed_fights(fights: pd.DataFrame, minutes: Mapping[str, list[dict[str, Any]]], clock: FightClock) -> Iterator[tuple[Any, pd.DataFrame, pd.Timestamp, pd.Timestamp, str]]

(fight row, winner frame, snap, start, how) for every fight whose winner books exist, whose result snapped after the books’ first quoted minute, and whose start can be anchored, in fights order.

Args: fights: :func:build_fights rows (winner is 'a' or 'b'). minutes: the winner markets’ 1-minute candles by ticker. clock: the fight clock.