MMA Index
How the numbers are built

Methods

Every derived metric on the site is defined below — inputs, formula, and the rationale for the design choice. If a number on a fighter or bout page isn’t pulled directly from a raw stat (sig strikes, takedowns, etc.), it’s computed via one of the entries here.

Schedule Score

The headline opposition metric — who you fought × what you did against them, percentile-ranked across UFC history.

Every UFC opponent is scored on a 0-100 quality scale based on the opponent’s status at the time of the fight:

ScoreDefinition
100Champion in this division
90 → 78Ranked #1 → #5 in this division (linear)
75 → 60Ranked #6 → #15 in this division (linear)
78Champion in another weight class (cross-division)
45Ranked 1-15 in another weight class
65Title fight, opp had ≥8 UFC wins & ≥.70 win rate
50Title fight, no other rank signal
60Unranked long-tenured vet (≥12 UFC wins, ≥.60 win rate)
45Unranked contender-level (≥8 UFC wins, ≥.55 win rate)
30Unranked established (≥5 UFC wins, ≥.500)
18Unranked building (≥3 UFC fights, ≥.40 win rate)
8Unranked unproven
+5 / +8P4P bonus if opp was P4P top-10 / top-3 at the time

Schedule Scorecombines two components, then takes a Bayesian shrinkage step toward the UFC median so small-but-elite samples don’t outrun long elite samples on the headline:

  • Schedule Strength (65%)— recency-weighted mean of opponent quality. 3-year half-life anchored on the fighter’s most recent UFC bout.
  • Quality Wins (35%) — opponent quality × win credit (finish = 1.0, unanimous decision = 0.85, split/majority = 0.7), with losses subtracting the full opponent quality.

The composite is then anchored to a four-point landmark scale so the displayed 0–100 reads honestly. The raw composite’s real-world ceiling sits near 85 (no UFC fighter has ever faced exclusively current champions and finished every one), so a raw display would top out near 80 even for Jon Jones and Amanda Nunes — scanning as “B+” on a 0–100 scale. The displayed Schedule Score is a piecewise-linear transform of the composite that pins it to four named anchors:

ScoreDefinition
100Theoretical perfect résumé (a fighter who only faced current champions and finished every one — unreachable)
95Current GOAT tier — Jon Jones, Amanda Nunes
50Median UFC fighter (≥3 resolved bouts)
0Bottom of UFC corpus

Below median the transform is the identity (a 30 stays a 30). Between median and 80 raw the score stretches into the 50–95 band where signal lives, so champions and high-ranked contenders show clear separation. Above 80 raw the curve compresses toward the 100 cap, leaving headroom for a future résumé that surpasses Jones. The transform is monotonic — order is preserved — and lives in displayScheduleScore insrc/lib/analytics/schedule-score.ts.

Source: src/lib/analytics/schedule-score.ts · Calibration: scripts/analytics/calibrate-schedule-score.ts · Reliable above ~3 resolved UFC bouts. UFC ranking data is sparse before December 2018, so legacy-era fighters score from opponent UFC record-at-time when ranks are missing.

Opponent quality tiers

Every UFC opponent bucketed into one of eight named tiers.

On the Schedule tab of every fighter and bout page, each career opponent is sorted into one of these tiers using their rank-at-time-of-fight:

TierDefinition
championReigning champion in this weight class at fight-night
top5Ranked 1-5 in this division
top15Ranked 6-15 in this division
cross_division_championHeld a title in some OTHER weight class
cross_division_rankedRanked 1-15 in some OTHER weight class
title_challengerTitle fight, no other rank signal
establishedUnranked but ≥ 5 UFC wins at ≥ .500
unprovenUnranked, thin or debut-level resume

Tier assignment uses the same context bundle as the SoS score — same priority order, same fallback rules. The tier breakdown on each fighter page shows wins/losses split by tier so a reader can see “what kind of opponents has this person beaten / lost to.”

Divisional percentiles

Where this fighter's number falls within their division's distribution.

Striking, Grappling, and Outcomes tabs label every metric with a divisional context tag (“TOP 11% OF DIVISION”, “BELOW DIVISION AVERAGE”). These come from a per-metric distribution baseline computed across every current UFC fighter in that division with a meaningful sample (typically ≥ 3 UFC bouts).

  • Top 10% of the division = “Elite — top of division.”
  • Top 25% = “Top quartile.”
  • Middle 50% = “Mid-pack.”
  • Bottom 25% = “Below division average.”
  • Bottom 10% = “Well below average.”

The division mean is rendered as a vertical tick on the comparison bars, so “past the tick” reads as “above average” at a glance. Bouts at a contracted catchweight inherit the home division of fighter A (the page anchor).

Source: src/lib/analytics/division-context.ts.

Time-weighted recency

Why old fights count less, and exactly how much less.

Several metrics on the site weight recent bouts more heavily than older ones — most prominently the SoS score. The weighting uses an exponential decay with a 3-year half-lifeanchored on the fighter’s most-recent UFC bout (not on “today”):

weight(bout) = 0.5 ^ (yearsBeforeAnchor / 3)

// e.g. for an anchor of the most-recent bout:
//   yearsBack = 0 → weight = 1.00
//   yearsBack = 1.5 → weight ≈ 0.71
//   yearsBack = 3   → weight = 0.50
//   yearsBack = 6   → weight = 0.25

Why not anchor on “today”? Because a fighter who’s been inactive for two years would see every bout exponentially decayed, even though the relativeweighting between their recent and early-career bouts is what we care about. Anchoring on the most-recent bout keeps the headline number tracking “what level were they fighting when active.”

Scorecards · per-judge scoring

Per-round judge scores plus three derived flags.

For ingested decision bouts, every judge’s round-by-round 10-point-must scores are stored individually. Three flags are computed on read:

  • 10-8 round. Exactly one fighter scored 10, the other ≤ 8. Surfaces a clear dominance round.
  • Minority card.A judge whose verdict differed from the majority of judges on the bout. With 3 judges, this is the “1” in a 2-1 split.
  • Swing round. A round where the judges did NOT agree on the winner of that round (one or more dissented). A swing round on a unanimous card is a card that COULD have gone the other way.

The bout page’s “Consensus” banner summarises these into one of five buckets: clean, close, majority, split, or draw.

Rolled up per fighter, these feed a scorecard-luckread on the profile page’s Judging section — how the cards have treated a fighter when the result was contestable:

  • Coin-flip decisions.The fighter’s win-loss record in split and majority decisions — bouts the judges themselves did not call cleanly. A positive net is the outcome-level signal that close calls have fallen their way.
  • Swing-round share. Across every round the judges disagreed on, the share the majorityof judges awarded this fighter. Rounds with no majority lean (an even split) aren’t attributable, so they’re excluded. The round-level signal beneath the decision outcomes.

Source: src/lib/scorecards.ts (per-bout) and src/lib/fighter-judging-stats.ts (per-fighter rollup) · raw scores from MMA Decisions, ingested per event.

Finish rate

Share of a fighter's UFC wins that ended early.

On the Outcomes tab:

finishRate = (wins by KO/TKO + wins by submission) / total wins

// "Finish" excludes decisions of any kind (unanimous, split,
// majority, technical) and excludes DQ wins.

KO-loss rate and submission-loss rate are computed similarly over total resolved bouts (not wins), since they describe how the fighter gets stopped, not how they win.

UFC Finisher Index

Speed-weighted finishing RATE across UFC wins — how fast and how often a fighter ends bouts when they win.

Scope is UFC wins only. Pride, RIZIN, ONE, and boxing finishes are out of frame — the metric reads the UFCStats-rooted corpus and nothing else.

Every UFC win contributes a per-round credit based on when the fight ended. Decisions earn zero — the index is about stopping people, not winning narrowly:

CreditDefinition
1.00R1 finish (KO/TKO, submission, technical submission)
0.75R2 finish
0.55R3 finish
0.40R4 finish
0.30R5 finish
0.00Win by decision (any flavor) or DQ

The empirical rate is then Bayesian-shrunk toward the corpus mean so a 6-fight 100%-R1 finisher doesn’t outrun a 17-fight career finisher with one decision win:

rate*       = (Σ credit + K · prior_credit) / (n + K)
finisherIdx = rate* × 100

// K = 5 (phantom wins of prior weight)
// prior_credit = 0.43 (measured corpus mean credit-per-win at ≥5 UFC wins)
// n = total UFC wins

With this shrinkage, nobody saturates at 100 — top of the league reads ~78 (small-sample R1 specialists) down to ~70 (large-sample career finishers). Min 5 UFC wins to qualify, keeping the floor honest while letting the prior absorb small-sample distortion above the floor.

Source: getFinisherIndexLeaderboard in src/lib/stats.ts. The Outcomes-tab finish rate above is a simpler, unweighted version (raw % of wins by finish) and reads against a different scale.

Finisher Count

Career UFC stoppages, speed-weighted by round — a volume metric that complements the rate-based UFC Finisher Index.

Where UFC Finisher Index measures how efficientlya fighter finishes their wins, Finisher Count measures how many people they’ve stopped. Same per-round credits (R1 = 1.00, R2 = .75, R3 = .55, R4 = .40, R5 = .30), but the sum is NOTnormalized by total wins. So Jim Miller’s 20 UFC finishes (mostly subs) and Charles Oliveira’s 21 (17 of them subs) anchor the top — names the rate-based index buries because of decision wins dragging their finish percentage down.

finisherCount = Σ credit_for_round

// e.g., 5 R1 KOs        = 5.00
//       3 R1 + 2 R3 KOs = 3.00 + 1.10 = 4.10

No Bayesian shrinkage — this is a count, not a rate, so small samples don’t distort the ordering. Three R1 KOs scores 3.00 and that’s exactly what it is. Min 3 career UFC finishes to qualify so the leaderboard isn’t padded by single-finish careers.

Source: getFinisherCountLeaderboard in src/lib/stats.ts · Scope is UFC bouts only; non-UFC promotions are not visible to the corpus.

UFC Durability

Resistance to being hurt across a UFC career — finishes count fully, knockdowns received count as ¼ each, shrunk toward the corpus mean so sample size matters.

Scope is UFC bouts only.A fighter’s results in other promotions (Pride, RIZIN, ONE, professional boxing, etc.) are out of frame for this metric — they don’t exist in our UFCStats-rooted corpus. Take a high UFC Durability score as a precise statement about the fighter’s UFC tenure, not a verdict on their durability across every cage and ring they’ve stepped into.

Every UFC bout the fighter has been part of contributes a durability-event count:

EventDefinition
1.00Finish loss (KO/TKO, submission, technical submission)
0.25Each knockdown received (even if the fighter survived the bout)
0.00Clean bout — no finish loss, no knockdown received

Knockdowns stack within a fight: three knockdowns survived in one bout penalize three times a single knockdown. A finish-loss bout still counts at 1.00 even if the fighter was knocked down earlier in the same fight — the finish is the dominant event and we don’t double-count the stoppage round’s KD.

The empirical event rate is then Bayesian-shrunk toward the UFC corpus mean so a clean 5-bout career doesn’t outrun a clean 20-bout career on the headline:

rate*       = (Σ event + K · prior_rate) / (n + K)
durability  = max(0, 1 − rate*) × 100

// K = 5 (phantom bouts of prior weight)
// prior_rate = 0.30 (measured corpus mean event rate at ≥5 UFC bouts)
// Σ event sums finishes (1.0 each) and KDs received (0.25 each).

Reading: a fighter who has never been finished AND never been knocked down across 23 UFC bouts scores ~95. The same clean record over 13 bouts reads ~92, and over 5 bouts reads ~85. Nobody ever displays 100 — the prior keeps a ceiling that credits long, clean UFC careers proportionally to their actual exposure.

The α = 0.25 KD weight was chosen so a KO ≈ 4 knockdowns: heavy enough that being dropped meaningfully moves the score, light enough that the finish stays the dominant signal. Minimum 5 UFC bouts to qualify, so single-fight outliers don’t dominate the leaderboard.

Source: getDurabilityLeaderboard in src/lib/stats.ts · Per-bout knockdown counts come from UFCStats canonical per-round data. Bouts without per-round stats (≈5% of the legacy corpus) contribute total bouts but 0 knockdowns received.

Finish timing

Every UFC finish a fighter has been part of — win or loss — plotted on a shared fight clock split into rounds, so a reader can see at a glance WHEN their fights end and whether they're on the winning or losing end of it.

The finish-rate metrics above answer how often a fighter finishes. The finish-timing chart answers when, and which way. Each fighter gets a lane laid over a shared fight clock — round 1, round 2, round 3 (and 4–5 for five-rounders) drawn left to right — and every UFC finish becomes a marker placed at the exact moment on the clock it happened:

clockPosition = (round − 1) × 5:00 + time into the round

// a KO at 2:14 of round 1 → 2:14 on the clock
// a submission at 1:30 of round 2 → 6:30 on the clock

Reading the lane. Three independent channels carry the read, so no legend-hunting is needed for the headline:

ChannelDefinition
Horizontal positionWhen on the fight clock the finish landed — left = early, right = late
Above the line, greenA finish WIN
Below the line, redA finish LOSS (got knocked out or submitted)
CircleKO / TKO
DiamondSubmission (incl. technical submission)

So a first-round finisher reads as a cluster of marks jammed into the R1 band — and because losses are drawn too, a fighter who both wins andloses early shows it: marks above and below the line, all bunched on the left. That “win or lose, this fight ends early” signal is exactly what the finish-rate rows can’t express. A caption in the lane header carries the average finish-win time and the finish W–L tally; the round bands and a minute axis beneath both lanes anchor the clock.

Decisions are deliberately absent — a decision has no finish, so it has no place on the clock. Both fighters share one axis (sized to the deepest finish, clamped to 3–5 rounds) so the two timing profiles are read against the same clock.

Scope matches the finish-rate rows. Only completed UFC bouts with a recorded winner are counted, and Contender Series bouts are excluded — the identical filter behind the win/loss-by-method counts shown alongside, so the timeline reconciles exactly with the displayed finish tallies rather than telling a slightly different story. Other promotions are out of frame for the same UFCStats-corpus reason as every metric above.

Source: getFighterFinishTimeline in src/lib/analytics/finish-timing.ts · clock math and the average-finish figure from src/lib/analytics/finishes.ts.

Pace & fade

Round-by-round work rate and whether a fighter slows down — built on the per-round data, with a denominator designed so a fast finisher doesn't read as a gasser and a grappler's wrestling round isn't read as idle.

The Pace tab charts each fighter’s output per minuteas an area-filled curve, one point per round, over every UFC bout that reached each round. In a matchup the two fighters’ curves are overlaid on one axis, so a fade reads literally — the line slopes downfrom left to right when a fighter slows, and holds flat or climbs when they don’t, with the gap between the two curves showing who carries more late. Each fighter carries a one-word badge — Builds, Holds, or Fades — derived from the headline number, the fade index: round-3 output as a share of round-1 output.

fadeIndex = (R3 output / min) / (R1 output / min)

// 100% = holds round-1 work rate into round 3
//  <100% = slows down (fades)
//  >100% = builds (rare)

Output is a work rate, not a striking rate.Counting only significant strikes would brand every grappler a gasser’s opposite — or worse. A chain-wrestler who spends round 1 shooting takedowns and grinding from top control lands almost no strikes that round, so a strikes-only metric reads his round 1 as a near-idle start — and then, because his striking picks up once the fight untangles, scores him as if he builthis pace two- or three-fold into the later rounds. That’s nonsense as a cardio signal: he wasn’t resting in round 1, he was doing the hardest work in the sport. So output blends three per-round actions into one work-unit total, putting a grappling round and a kickboxing round on the same scale:

output = (sig strikes landed)
       + 3 × (takedown attempts)
       + (control-time seconds ÷ 15)

// 1 unit  per significant strike landed   — striking baseline
// 3 units per takedown ATTEMPT            — a shot/scramble is an
//                                           explosive burst of work,
//                                           landed or not; effort is
//                                           what cardio measures
// 1 unit  per 15s of control              — grinding is continuous
//                                           output; a full 5:00 control
//                                           round ≈ 20 units (~4/min),
//                                           on par with a busy striking
//                                           round

The finisher-looks-like-a-gasser problem.The naïve way to build this — average everyone’s round-1 pace, average everyone’s round-3 pace, divide — quietly punishes the most dangerous fighters on the roster. A knockout artist who ends most of their fights in the first round has almost no round-3 sample, and the handful of bouts that didreach the third round are by definition their off-nights — the ones where the finish didn’t come. Pool those and the finisher’s round-3 average collapses, so the math brands a first-round killer a cardio liability. That’s backwards. We fix it two ways. First, the fade index is population-consistent: round 1 and round 3 are compared only across the same set of bouts— the ones that actually reached round 3 — so a fighter is never measured against rounds they never fought. Second, the final round of a bout that ended early is pro-rated by its real length: a knockout at 2:30 counts as 2.5 minutes of work, not a full five. A fighter who lands 20 significant strikes and then finishes at the halfway mark was throwing at 8/min, not 4/min, and the denominator reflects that. Without the partial-round denominator, every early finish would deflate the finisher’s per-minute pace and — again — make the most decisive fighters look like the gassers. The combination is the point: it’s why a fade number on this site can be trusted to mean cardio, not finishing ability wearing a cardio costume.

Per-round rates use UFCStats canonical per-round data, available for roughly 95% of bouts since 2008. A fighter needs at least three bouts that reached round 3 before a fade index is shown — below that the sample is too thin to separate a pattern from a single bad night, and the chip reads “—” instead.

What drives the pace. Because output is a blend, two fighters can post identical pace bars for opposite reasons — one climbing on strike volume, the other on takedown attempts and control time. The detail panel breaks the single composite total back into its three raw components, each charted as its own per-round bars:

ComponentDefinition
Sig. strikes landedSignificant strikes landed per minute — the striking dimension on its own.
Takedown attemptsTakedown attempts per minute — the wrestling-pressure dimension, attempts not just landed.
Control timeShare of the round (%) spent in control position — the grinding dimension.

These are the same per-round numbers the composite is built from, just shown unblended so a reader can see whichkind of work a fighter’s pace is made of, and whether the thing that fades (or builds) is the striking, the wrestling, or the control. The breakdown shows only when a fighter has at least two rounds of data, and matchup views overlay both fighters’ curves on every component — the same line treatment as the headline output.

Power & chin proxies.On the Outcomes tab’s KO-history card, two rates normalise raw knockdown totals by head-strike volume:

powerProxy = knockdowns dealt    / head sig strikes landed   × 100
chinProxy  = knockdowns absorbed / head sig strikes absorbed × 100

// "per 100 clean head strikes" — how often a landed head
// strike actually drops someone (or drops THEM, for the chin).

Raw knockdown counts reward busy, high-volume strikers — throw enough and some land flush. Dividing by head significant strikes (knockdowns come almost entirely from head contact) asks the sharper question: of the clean head shots that land, what share put someone down? The chin proxy is the mirror — how often a clean head strike drops thisfighter. Neither is a synthesized rating; both carry their underlying fraction in the caption so the reader can see exactly what went into the rate. Null (“—”) when the fighter has no head-strike volume on record.

Source: src/lib/analytics/pace.ts (fade index, per-round curve) and src/lib/ko-history.ts (power/chin proxies). Both read the per-round bout_stats rows directly.

Common-opponents edge

Who beat the shared opponents more convincingly?

On the matchup Schedule tab, each shared opponent gets an edgetag based on the two fighters’ records against that opponent:

  • Edge · X — fighter X has the better record vs that opponent.
  • Both won — both undefeated vs that opp.
  • Both lost — both winless vs that opp.
  • Mixed — equal records, both have wins.
  • Tied — equal records, neither has a win, or sample too thin.

With single bouts on each side it’s directional, not a verdict. With rematches it reflects net wins. The tag is intentionally modest — common-opponents data is suggestive, not predictive.

Data sources behind the derived layer

What gets mirrored vs what gets computed.

Every number on the site falls into one of two buckets:

  • Mirrored. Raw bout outcomes, per-round stats, fighter records, current rankings. These come straight from the upstream sources documented at /sources — UFCStats, UFC.com, Sherdog, BestFightOdds.
  • Derived. Everything documented on this page — SoS score, opponent quality tiers, divisional percentiles, consensus flags, finish rate. Computed from the mirrored layer by the helpers in src/lib/analytics/.

The derived layer is where MMA Index goes beyond “cleaner display of UFCStats.” If you want to verify how any number on the site was built, the source-of-truth files are linked in each section above.