Skip to main content

Bayesian inverse-variance fusion of FIDE, Chess.com and Lichess ratings into a single FIDE-scale rating.

Project description

unirating

Python License: MIT

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) → raises InvalidRatingError.
  • Out-of-range ratings → raises InvalidRatingError (configurable bounds).
  • Mixed time controls → only one TimeControl per 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

unirating-0.1.0.tar.gz (25.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

unirating-0.1.0-py3-none-any.whl (15.0 kB view details)

Uploaded Python 3

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

Hashes for unirating-0.1.0.tar.gz
Algorithm Hash digest
SHA256 355745941974e2abe40b14cdfb26e112b4fde2f706d1d62eb06f1b3ebc8c70f0
MD5 ef729b05977393e0c3d7ddb977b20fbe
BLAKE2b-256 17be8760c889157d412724cb762e8b54c838345b245089ec45867002c2075db6

See more details on using hashes here.

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

Hashes for unirating-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1aa4573011f6b21f169be242c1be6ad9d6055c07ff1acf55f88612f7429020bd
MD5 4f192a1b8c566e1b36e2dcebf97cf83e
BLAKE2b-256 aa298a17d269f29a401d73916389cea75161b2c92c6150131b6967e6a799edae

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page