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_fightsleaves 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’sservices.rounds_data.fight_snapsrule).- 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
lagminutes of market reaction; decisions count back from the distance market’s jump at the final bell (pluslag) 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 | NoneWhen 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.DataFrameOne 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 | NoneBoth 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.DataFrameUFCStats 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.DataFrameOne 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.