Glicko-Boost Rating System¶
See How to choose a rating system for a decision guide across every system, or Glicko for the system Glicko-Boost extends.
Overview¶
Glicko-Boost is Mark Glickman’s extension of Glicko, written for the Deloitte/FIDE Chess Rating Challenge and described in Glicko-Boost. It adds four things to Glicko: an advantage for playing white, a second pass that re-rates every player against the opponents’ first-pass ratings, an RD “boost” for players whose results were exceptional, and a rating-dependent model for how RD grows between periods.
It is the first system in Elote whose published update is defined over a rating period rather than over one game. The two-pass structure cannot be expressed as a stream of pairwise updates – a player’s rating change depends on how their opponents did in the same period – so GlickoBoostCompetitor overrides apply_rating_period() and does the whole population at once. The pairwise API is unchanged: beat/lost_to/tied run the same algorithm over a one-game period.
How It Works¶
Every step is built on Glicko updating with a white-advantage term. For a player with rating \(r\) and rating deviation \(RD\) who played \(J\) games with scores \(s_j\) against opponents \((r_j, RD_j)\):
Here \(w_j\) is \(+1\) when the player had white in game \(j\), \(-1\) when black, and \(\eta\) is the rating advantage of playing white. With \(\eta = 0\) the term disappears and this is ordinary Glicko updating.
The period update¶
Given every player’s pre-period rating and RD, one period is five steps:
Update every player from the pre-period ratings and RDs.
Update every player again from the pre-period ratings and RDs, but against the opponents’ step 1 ratings and RDs. A player who lost to opponents who all had a strong month is now judged against those better ratings.
Compute each player’s performance z-score and boost their pre-period RD if it is large (below).
Repeat step 1 with the boosted RDs.
Repeat step 2 on the step 4 results. These are the period’s final ratings and RDs.
If no RD was boosted in step 3, steps 4 and 5 reproduce steps 1 and 2 exactly, and Elote skips them.
The RD boost¶
The boost catches an improving player whose RD is too small for their rating to move fast enough. The z-score measures how far the period’s results ran ahead of what the player’s pre-period rating predicted, against the step 2 population:
\(k = 1.96\) is the point beyond which a z-score would occur by chance 2.5% of the time, so on an ordinary month nobody is boosted and steps 4 and 5 never run.
RD increase over time¶
Instead of Glicko’s constant \(c\), the increase depends on both the RD and the rating, so that higher-rated players – whose strength is more stable – gain slightly less uncertainty per idle period:
Elote applies this when a competitor next takes part, for the rating periods it sat out, the way GlickoCompetitor handles inactivity. Results that share a period end therefore never inflate each other’s RD.
Prediction¶
expected_score uses the paper’s own approximation, which combines the two rating deviations rather than using only the opponent’s:
With the default \(\eta = 0\) this is exactly complementary: a.expected_score(b) + b.expected_score(a) == 1.
The colour convention¶
Elote’s result API carries no colour argument, and Glicko-Boost does not get one added for it. Instead argument order is the colour: in a rating-period row (a, b, outcome, scores), and in a.beat(b), a is white and b is black.
Callers who have no colour information should leave _eta at its default of 0.0, which is why that – rather than the paper’s 30.0 – is Elote’s default. The update then depends only on rating differences and is invariant to the order in which each row is written.
Argument order is preserved through matchup() (and therefore tournament()) and rating_period(): a loss is dispatched as the caller’s lost_to, which reverses internally, so a row written white-first stays that way. The same holds for train_arena_with_dataset() and process_history(), which dispatch a loss in caller order too.
Key Parameters¶
All of these are class-level, so they are settable through configure_class and reachable from walk_forward() and tune() through competitor_params.
Parameter |
Elote default |
Meaning (Glickman’s optimized value) |
|---|---|---|
|
0.0 |
Rating advantage for playing white (30.0) |
|
0.20139 |
RD boost multiplicative factor (0.20139) |
|
17.5 |
RD boost additive factor (17.5) |
|
1.96 |
z-score threshold above which the RD is boosted (1.96) |
|
5.83733, -1.75374e-04, -7.080124e-05, 0.001733792, 0.00026706 |
RD-increase-over-time coefficients (same values) |
|
250.0 |
RD cap, \(RD_{unr}\) (250.0) |
|
30.0 |
Days in one rating period, used to turn elapsed time into RD increases |
initial_rating (default 1500) and initial_rd (default 250) are constructor arguments, so they travel through base_competitor_kwargs rather than competitor_params. Glickman’s default rating for an unrated player is 1946.25, which is specific to the FIDE population the system was fitted on; Elote keeps its house default of 1500.
How It Differs From Glicko and Glicko-2¶
Glicko |
Glicko-2 |
Glicko-Boost |
|
|---|---|---|---|
Update granularity |
One game (Elote replays a period pairwise) |
One game |
The whole period, twice over |
Colour |
Not modelled |
Not modelled |
\(\eta\) inside \(E()\), from argument order |
Reacting to a surprising month |
Nothing beyond the ordinary update |
A per-player volatility \(\sigma\) updated every period |
A one-off RD boost when \(Z > k\), then a re-rate |
RD growth while idle |
\(\sqrt{RD^2 + c^2 t}\) |
Driven by volatility |
\(\exp\) of a rating-and-RD-dependent polynomial |
Expected score |
\(g(RD_B)\) only |
Transformed scale |
\(g\!\left(\sqrt{RD_A^2 + RD_B^2}\right)\), so it is complementary |
Glicko-2’s volatility is a persistent per-player parameter that is re-estimated every period; Glicko-Boost’s boost is a one-shot widening of the prior for the period being rated, discarded afterwards. Glicko-Boost is therefore cheaper and has no iterative solve, at the cost of not tracking volatility over time.
Implementation in Elote¶
from datetime import datetime
from elote import GlickoBoostCompetitor
# The paper's white advantage; leave it at 0.0 if your data has no colour.
GlickoBoostCompetitor.configure_class(eta=30.0)
alice = GlickoBoostCompetitor(initial_rating=2300, initial_rd=140)
bob = GlickoBoostCompetitor(initial_rating=2295, initial_rd=80)
cara = GlickoBoostCompetitor(initial_rating=2280, initial_rd=150)
# One rating period, applied to the whole population at once. The first
# competitor of each row had white.
GlickoBoostCompetitor.apply_rating_period(
[
(alice, bob, 0.0, None),
(cara, alice, 1.0, None),
(bob, cara, 0.5, None),
],
period_end=datetime(2026, 1, 31),
)
print(alice.rating, alice.rd)
print(alice.expected_score(bob))
Through an arena, the same period is one call:
from elote import LambdaArena, GlickoBoostCompetitor
arena = LambdaArena(lambda a, b: True, base_competitor=GlickoBoostCompetitor)
arena.rating_period(
[("alice", "bob", 0.0, None), ("cara", "alice", 1.0, None)],
period_end=datetime(2026, 1, 31),
)
print(arena.leaderboard())
beat, lost_to and tied are supported and run the same algorithm over a one-game period, so a single result is never a different formula from a batch. Because of the two-pass structure a lone pairwise call is not identical to a Glicko update, and applying a period one row at a time does not give the period’s ratings – group results into periods when you have them.
Advantages¶
Period-native: rates a whole month the way the published system does, with no within-period ordering effect
Opponent-aware: the second pass judges a player against how their opponents actually did, not just their pre-period ratings
Catches improvers: the RD boost widens the prior for a player whose results were far ahead of their rating, so the re-rate can move them further
Colour: models the advantage of moving first, at no cost to the uniform API
Flexible RD growth: uncertainty grows at a rate that depends on the player’s rating and current RD
Limitations¶
Needs periods: the value is in the batch; a stream of one-game periods loses the second pass’s information
Many parameters: twelve system constants, fitted by Glickman on FIDE data and not necessarily right for another domain
No volatility tracking: unlike Glicko-2, nothing persists between periods about how erratic a player is
Colour is positional: rows have to be written white-first for \(\eta\) to mean anything
Cost: four Glicko passes over the population per period instead of one
Numeric reference¶
tests/test_GlickoBoostCompetitor_known_values.py checks this implementation against the worked example in Section 4 of Glickman’s paper. Two of that table’s columns are not reproducible from the paper’s own formulas – the module’s docstring records the measured deviations and the sweep that establishes it – so they are pinned as regression values while the rest of the table is asserted as the published oracle.