Stat trackers for Rainbow Six Siege look like simple profile pages: a rank, a K/D, a win rate, maybe a list of operators. Under the hood they have to agree with Ubisoft's ranked data, survive rate limits, handle divide-by-zero, and pick a win-rate denominator that two honest engineers can still argue about.
This post walks through those choices, using how the numbers are calculated on SiegeRadar as a public example. SiegeRadar is a free unofficial tracker, not affiliated with Ubisoft. The same questions show up in any similar app. For the game itself, see Ubisoft's official Rainbow Six Siege site and the Wikipedia article.
The three formulas that actually matter
Most of a player card reduces to three ratios.
K/D is kills / deaths, rounded to two decimals. The interesting branch is zero deaths. Returning Infinity, NaN or a blank cell makes a profile look broken. A clean rule is: if deaths are zero, return the kill count (so 12 kills and 0 deaths display as 12.00). It stays a real number you can sort.
Headshot percentage is headshots / kills. Guard the case where headshots exceed kills — that is almost always a data error, not a player feat — and do not compute a percentage from zero kills.
Win rate is where trackers quietly diverge. There are two legitimate denominators:
- Current ranked season card:
wins / (wins + losses) - Lifetime totals, per-mode figures and past seasons:
wins / matches_played
Those two are not always equal. "Matches played" can include results that are not a clean win or loss in the ranked board you just typed in. If one site shows 61.0% and another shows 60.4% for the same account, check the denominator before you assume either is wrong.
In Python, the core is small enough to keep on one screen:
from typing import Optional
def kd(kills: int, deaths: int) -> float:
if deaths == 0:
return float(kills)
return round(kills / deaths, 2)
def win_rate_season(wins: int, losses: int) -> Optional[float]:
denom = wins + losses
if denom == 0:
return None # render as a dash, not 0.0
return round(wins / denom * 100, 1)
def win_rate_lifetime(wins: int, matches: int) -> Optional[float]:
if matches == 0:
return None
return round(wins / matches * 100, 1)
Returning None (and showing a dash) when there is no sample is better than showing 0.0%, which readers will treat as a real losing record.
Where the data comes from, and how stale it is allowed to be
A tracker is not the game client. It asks a ranked-data API, caches the answer and renders a page. SiegeRadar's public description is that player profiles, operator stats, leaderboards and live player counts come from the Arenyze API, which serves Ubisoft's ranked data. The response is cached so the next view inside the window does not hit the source again.
Freshness is a product decision as much as a systems one. Refresh too often and you burn a data budget; refresh too rarely and a player who just finished a match thinks the site is broken. One working set of windows looks like this:
- Player profiles: at most every 15 minutes
- Operator stats: every 30 minutes (season-level operator stats every 6 hours)
- Leaderboards: every 3 hours
- Live player counts: every hour
- "Player not found" misses: remembered for 10 minutes, then tried again
- Game database pages (operators, weapons, maps, ranks): a snapshot, updated each season, not live
When the daily budget is used up, or upstream is down, serving the last saved copy with an explicit "couldn't refresh, last updated X minutes ago" is kinder than a blank page.
Other edges that will show up in bug reports
Console names must match exactly. A near miss should return "not found," not a cousin account. That feels harsh until the alternative is attaching someone else's rank to a search.
Past seasons do not share a scale. Ranked 2.0 and Ranked 3.0 use different RP ranges. Seasons before Y7S4 used MMR, not rank points, so a tracker should not invent a modern rank name for them.
Operator data is not a full career. Headshot percentage and operator time played may only cover recent seasons. Summing operator hours will usually come in under the lifetime total Ubisoft reports. Document that.
There may be no match history. Not every tracker has a replay of each lobby. If you do not have it, say so. Advertising a feature you cannot compute is worse than a shorter feature list.
Why two good trackers still disagree
Most disagreements come from four places: the win-rate denominator, the cache window, whether operator stats are treated as lifetime, and whether a missing sample is shown as zero or as empty. Get those four written down and the rest of the page is presentation. The useful test is not "does this match another site to two decimals?" It is "can I explain every number on the card, including the ones we cannot show?"



Top comments (0)