Bayesian inverse-variance fusion of FIDE, Chess.com and Lichess ratings into a single FIDE-scale rating.
Project description
unirating
Author: Sourav Sahu (@souravsahums)
Bayesian inverse-variance fusion of a player's FIDE, Chess.com and Lichess ratings into a single number on the FIDE scale, with proper handling of missing ratings, calibrated per time control.
The estimator is the maximum-likelihood / Best Linear Unbiased Estimator (Gauss–Markov) when all sources are present, and the posterior mean of a Gaussian–Gaussian Bayesian model when one or more sources are missing — so a player with no ratings at all gracefully degrades to the population prior (the "base rating") rather than crashing.
Full mathematical derivation with proof: docs/derivation.md. Citations: docs/citations.md.
Install
pip install unirating
Zero runtime dependencies. Python 3.9+.
Quick start
from unirating import Rating, TimeControl, fuse
result = fuse(
fide = Rating(value=1820, sigma=60), # FIDE Rapid, settled
chesscom = Rating(value=1950, sigma=55), # Chess.com Rapid, RD=55
lichess = Rating(value=2100, sigma=50), # Lichess Rapid, RD=50
time_control = TimeControl.RAPID,
)
print(result.rating) # 1838.95 (FIDE scale)
print(result.sigma) # 31.36 (1-sigma uncertainty)
print(result.ci95) # (1777.5, 1900.4)
print(result.contributions) # per-source weight share, sums to 1.0
A player with no ratings at all:
from unirating import fuse, TimeControl
result = fuse(time_control=TimeControl.RAPID)
print(result.rating) # 1500.0 (prior mean — the "base rating")
print(result.sigma) # 350.0 (prior std-dev — full uncertainty)
print(result.is_prior_only) # True
Lichess gives you the RD directly via API — pass it in. If you only have the rating number, the package will use a sensible default $\sigma$ but you should treat the result as approximate:
from unirating import Rating, TimeControl, fuse
result = fuse(
lichess = Rating(value=1850), # sigma=None → default used + warning
time_control = TimeControl.RAPID,
)
What's in the box
| Module | Purpose |
|---|---|
unirating.fuse |
The main entry point — call this. |
unirating.Rating |
Immutable (value, sigma) measurement. |
unirating.Prior |
Gaussian prior over latent skill. Defaults to $\mathcal N(1500, 350^2)$. |
unirating.Calibration |
Affine map source ↔ FIDE. Defaults per time control. |
unirating.TimeControl |
BULLET, BLITZ, RAPID, CLASSICAL. |
unirating.FusionResult |
(rating, sigma, ci95, contributions, used_sources, …). |
unirating (CLI) |
One-shot fusion from the shell. |
CLI
unirating \
--fide 1820 --fide-sigma 60 \
--chesscom 1950 --chesscom-rd 55 \
--lichess 2100 --lichess-rd 50 \
--time-control rapid
Output:
Unified rating (FIDE scale): 1839 ± 31
95% CI: [1778, 1900]
Sources used: fide, chesscom, lichess
Contributions: prior=1% fide=27% chesscom=33% lichess=39%
How the math works (one paragraph)
Each rating is treated as a noisy linear measurement $R_i = \alpha_i + \beta_i \theta + \varepsilon_i$, $\varepsilon_i \sim \mathcal N(0, \sigma_i^2)$, of the latent FIDE-equivalent skill $\theta$. After inverting each measurement to a FIDE-scale estimate $\hat\theta_i$ with variance $\tau_i^2 = \sigma_i^2/\beta_i^2$, the posterior mean of $\theta$ given a Gaussian prior $\mathcal N(\mu_0, \sigma_0^2)$ is the precision-weighted average
$$ \hat\theta = \frac{\mu_0/\sigma_0^2 + \sum_{i \in S} \hat\theta_i/\tau_i^2}{1/\sigma_0^2 + \sum_{i \in S} 1/\tau_i^2} $$
over whatever subset $S$ of sources is present. With no sources, the formula collapses to $\mu_0$. The full proof (MLE + Gauss–Markov + Bayes), the choice of constants, and a worked example are in docs/derivation.md.
Calibration constants (defaults)
| Time control | Chess.com $\alpha,\beta$ | Lichess $\alpha,\beta$ |
|---|---|---|
BULLET |
150, 1.0 | 400, 1.0 |
BLITZ |
120, 1.0 | 350, 1.0 |
RAPID |
100, 1.0 | 250, 1.0 |
CLASSICAL |
50, 1.0 | 200, 1.0 |
Source: published community regressions (see docs/citations.md). You can override per call:
from unirating import Calibration, fuse
custom = Calibration(chesscom_alpha=80, lichess_alpha=300)
result = fuse(..., calibration=custom)
Edge cases handled
- No ratings → returns prior,
is_prior_only=True. - One rating → posterior shrinks toward prior; weight reported.
- Missing $\sigma_i$ → fills with conservative default, emits
MissingSigmaWarning. - Provisional rating (large RD, Lichess
?) → naturally down-weighted; warns if RD > 110. - Zero / negative $\sigma_i$ → raises
InvalidRatingError. - Non-finite values (
nan,inf) → raisesInvalidRatingError. - Out-of-range ratings → raises
InvalidRatingError(configurable bounds). - Mixed time controls → only one
TimeControlper call; the calibration enforces consistency. - Custom prior → pass any
Prior(mu, sigma)(e.g. for juniors, titled players). - Numerical stability → all sums computed in precision-space then inverted at the end.
See tests/ for the formal coverage.
Citation
If you use this package in academic work, please cite:
@software{sahu_unirating_2026,
author = {Sahu, Sourav},
title = {unirating: Bayesian inverse-variance fusion of FIDE,
Chess.com and Lichess ratings},
year = {2026},
url = {https://github.com/souravsahums/unirating},
version = {0.1.0},
}
Plain text: Sahu, S. (2026). unirating: Bayesian inverse-variance fusion of FIDE, Chess.com and Lichess ratings (v0.1.0) [Computer software]. https://github.com/souravsahums/unirating
License
MIT — see LICENSE. Copyright © 2026 Sourav Sahu.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file unirating-0.1.0.tar.gz.
File metadata
- Download URL: unirating-0.1.0.tar.gz
- Upload date:
- Size: 25.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
355745941974e2abe40b14cdfb26e112b4fde2f706d1d62eb06f1b3ebc8c70f0
|
|
| MD5 |
ef729b05977393e0c3d7ddb977b20fbe
|
|
| BLAKE2b-256 |
17be8760c889157d412724cb762e8b54c838345b245089ec45867002c2075db6
|
File details
Details for the file unirating-0.1.0-py3-none-any.whl.
File metadata
- Download URL: unirating-0.1.0-py3-none-any.whl
- Upload date:
- Size: 15.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1aa4573011f6b21f169be242c1be6ad9d6055c07ff1acf55f88612f7429020bd
|
|
| MD5 |
4f192a1b8c566e1b36e2dcebf97cf83e
|
|
| BLAKE2b-256 |
aa298a17d269f29a401d73916389cea75161b2c92c6150131b6967e6a799edae
|