Where does a player stand nationally?

Someone asked, after the NE Open rating-changes writeup: how did you check where players were with respect to other players in the country? Short answer: the AGA publishes the whole national rating list, so it is a sort. This page is the long answer, with the code.

1. Get the list

The TDList is the AGA's list of every rated player - the same file OpenGotha downloads with its "Update AGA rating list" button. About 17,000 rows, ~1 MB, free and public. Three variants: A (tab-separated, chapter short code), B (chapter full name), N (fixed width). Everything here uses A.

The AGA's stable handle is https://www.usgo.org/TDListA. Two things will trip up a script:

The list is a snapshot: it changes whenever the AGA runs ratings. So take one the night before your tournament and another after it is rated, keep both, and every "what changed" question answers itself later. The rating-changes page is exactly that diff, joined on AGA ID.

2. What is in a row

Nine tab-separated columns, no header row:

#ColumnExample
1name (Last, First)Chiu, Jeremy
2AGA id17531
3membership typeAdult Full - Lifetime
4rating7.98574
5membership expires5/14/2067
6chapter codePENG
7stateCA
8sigma (rating uncertainty)0.16805
9date of last rated game9/5/2026

Columns 4 and 9 are the two that matter here: the rating answers "where", and the last-game date answers "compared to whom". Column 9 carries the start date of the last event the player was rated for - every NE Open player reads 9/5/2026, the date on the results file.

A blank rating means the player joined but has never played a rated game - 1,736 of the 17,159 rows in the September 7 list. Those are not players in this sense; drop them and you are left with 15,423 rated players.

3. Rating to rank

The AGA scale is continuous, and it skips the open interval (-1, 1) - there is no zero. Positive is dan, negative is kyu, and the rank truncates toward zero:

RatingRankNote
7.985747dnot 8d - truncation is the rule
4.988114d4.99 is emphatically a 4d
-3.992093ka 3k by eight thousandths

One consequence worth knowing if you subtract two ratings: a 1k → 1d promotion is a raw change of about +2 for what is really a bit of movement across one boundary. To compare differences, shift dan ratings down by 2 so ranks index continuously (2k = -2, 1k = -1, 1d = 0, 2d = 1). For the position question on this page it does not matter - sorting inside one band never crosses the gap.

4. Position inside the band

Filter the list to one rank, sort by rating, find the player. That is the entire method - the only real decision is who is in the pool (next section). Two results from the NE Open, against everyone who has played a rated game in the last five years:

$ python3 aga-rank.py tdlist.tsv "Steinberg, Micah"
Steinberg, Micah (30588)  -3.99209  3k   [pool: played in the last 5 years, 3449 players]
   #2 of 149 3k players from the bottom (#148 from the top)
   stronger than 1944 of 3449 (56.4%)

$ python3 aga-rank.py tdlist.tsv 17531
Chiu, Jeremy (17531)  7.98574  7d   [pool: played in the last 5 years, 3449 players]
   #52 of 52 7d players from the bottom (#1 from the top)
   stronger than 3416 of 3449 (99.0%)

Micah Steinberg was promoted by 0.06 of a point and is the second-weakest active 3k in the country - which is what a promotion looks like the day it happens. Jeremy Chiu crossed the same kind of boundary going the other way, on a change of -0.046, and came out the strongest active 7d in the country.

5. Who counts as a player?

"The country" sounds like a fixed number and is not. The list holds 15,423 rated players - everyone the AGA has ever rated. 6,967 of them last played before 2010, and 2,399 before 2000. Ranking someone against all of them compares them to a mostly dormant population, and it inflates every denominator.

Column 9 settles it without much guesswork: played a rated game in the last five years. That is 3,449 players, and it is the default here.

PlayerBandPlayed <5 yrs
(3,449)
Ever rated
(15,423)
Micah Steinberg3k#2 weakest of 149 #3 weakest of 562
Jeremy Chiu7dstrongest of 52 #3 of 260

Both are true; they answer different questions, and the shape of the answer survives either. Say which pool you used - that is the part that matters. The five-year pool is the one used in the rating-changes writeup, because "where does this player stand" means among people you could actually meet at a tournament.

Membership expiry (column 5) would work as a proxy - current or lapsed within five years is 4,515 players - but there is no reason to reach for it. It is strictly looser: every one of the 3,449 who played recently is also a recent member, and the extra 1,067 are people who pay dues without playing. The activity date is right there in column 9; use it.

6. The script

aga-rank.py - Python 3.9+, standard library only, nothing to install. It downloads the list and answers the question:

python3 aga-rank.py tdlist.tsv --fetch      # download today's list
python3 aga-rank.py tdlist.tsv "Steinberg"  # by name (substring; prints every match)
python3 aga-rank.py tdlist.tsv 17531        # by AGA id
python3 aga-rank.py tdlist.tsv 17531 --years 10   # widen the activity window
python3 aga-rank.py tdlist.tsv 17531 --pool all   # everyone the AGA has ever rated

The script, and the rest of the tournament toolkit it came from, now live in gotrevor/baduk-tools - publishers that turn a desktop OpenGotha into the live pairings and cross-tab pages this tournament ran on, plus the AGA rating-list tools. Apache 2.0; patches from other TDs welcome.

git clone https://github.com/gotrevor/baduk-tools
python3 baduk-tools/bin/aga-rank.py tdlist.tsv --fetch