Creates opening trees from PGN files. The tree is an SQLite database.
python3 -m venv .venv
source .venv/bin/activate
pip install uvgit clone https://github.com/isofarro/chess-opening-trees.git
cd chess-opening-trees
uv installWe build a tree from one or more PGN files. We can run the build repeatedly until all the PGN files we want to add are added. This means we can update the opening tree regularly, as new game databases are available.
./tree.py build \
--tree my_tree.tree \
--max-ply 60 \
--min-rating 2000 \
pgn/The build keeps track of files it's imported, and skips them if there are no changes (file modification date, content hash). So you can run it multiple times against a directory, and it imports any new files found in the directory. Perfect for regularly updating a tree with weekly TWIC files.
This removes positions that have been visited once, and not within a specific depth of the last position with more than one game.
./tree.py prune \
my_tree.tree \
--max-closeness 5
--batch-size 2000--max-closenessis the number of plies from a position that has more than 1 visit. Defaults to 5.--batch-sizeis the number of positions to delete in each transaction. defaults to 1000.
Each 1-game position has a game reference, so it's feasible when the tree grows to reach the leaf position to re-process the game and add in some more moves.
Query the opening tree by position
./tree.py query
my_tree.tree
--fen "r1bq1rk1/2p1bppp/p1np1n2/1p2p3/4P3/1BP2N1P/PP1P1PP1/RNBQR1K1 b - - 0 9"
--output jsonfenis the FEN string of the positionoutputis the format of the output. Defaults tojson.
The JSON output looks like this:
{
"fen": "rn1qkb1r/ppp1pppp/5nb1/4N3/3P2P1/2N4P/PPP5/R1BQKB1R b KQkq -",
"moves": [
{
"move": "e6",
"fen": "rn1qkb1r/ppp2ppp/4pnb1/4N3/3P2P1/2N4P/PPP5/R1BQKB1R w KQkq -",
"total_games": 3333,
"white_wins": 2006,
"draws": 160,
"black_wins": 1167,
"last_played_date": "2025-05-31",
"rating": 2154,
"performance": 2059
},
...
]
} ./tree.py serve \
--trees bdg-cce pgn/openings/D00-bdg-games-cce-2025-05.tree \
--port 2882 ./tree.py serve --config serve-config.jsonThe configuration file is in JSON format:
{
"port": 3000,
"trees": [
{
"name": "main",
"file": "trees/twic-2025.tree"
},
{
"name": "bdg",
"file": "pgn/openings/D00-bdg-games-cce-2025-05.tree"
}
]
}When running behind a proxy (e.g., Nginx), set baseUrl in the config. Returned links in the API responses use this public base URL instead of http://localhost:{port}.
{
"baseUrl": "https://openingtrees.example.com/api",
"port": 2882,
"trees": [
{ "name": "main", "file": "trees/twic-2025.tree" }
]
}The server still binds locally to localhost:2882; the baseUrl only affects the URLs returned by the API (e.g., tree paths).
Both methods start an HTTP server. The server supports multiple trees, each specified by name and tree file path. Command line arguments take precedence over config file settings.
For production deployment with nginx or other reverse proxies, use the WSGI application:
# Install dependencies
uv install
# Run with uvicorn directly
export OPENING_TREE_CONFIG=serve-config.json
uvicorn opening_tree.wsgi:app --host 0.0.0.0 --port 8000
# Or use the convenience script
python run_wsgi.py --config serve-config.json --port 8000 --host 0.0.0.0The WSGI application provides the same API endpoints as the HTTP server but is designed for production use with proper ASGI servers like uvicorn.
For production deployment with a domain name, use Nginx as a reverse proxy. An example configuration is provided in .
To set up:
-
Start the WSGI application:
# Run on localhost (Nginx will proxy to this) export OPENING_TREE_CONFIG=local-config.json uvicorn opening_tree.wsgi:app --host 127.0.0.1 --port 8000
-
Configure Nginx:
# Copy and modify the example config sudo cp nginx-example.conf /etc/nginx/sites-available/openingtrees # Edit the configuration sudo nano /etc/nginx/sites-available/openingtrees # - Replace 'openingtrees.example.com' with your domain # - Update SSL certificate paths # - Adjust rate limiting and other settings as needed # Enable the site sudo ln -s /etc/nginx/sites-available/openingtrees /etc/nginx/sites-enabled/ sudo nginx -t # Test configuration sudo systemctl reload nginx
-
Set up SSL certificates (using Let's Encrypt):
sudo certbot --nginx -d openingtrees.example.com
The Nginx configuration includes:
- HTTPS redirect and SSL termination
- Security headers and rate limiting
- CORS support for web frontends
- Gzip compression
- Health check endpoint
- Load balancing support (commented out)
The URL to query a position in an opening
tree is the pattern http://localhost:2882/{tree}/{fen} where:
{tree}is the name of the tree defined in theservecommand{fen}is the FEN string of the position, URL encoded.
curl http://localhost:2882/bdg-cce/rn1qkb1r%2Fpp2pppp%2F2p2n2%2F8%2F3P4%2F2N2Q1P%2FPPP3P1%2FR1B1KB1R%20w%20KQkq%20-%200%208This returns a JSON response in the same structure as the query command above.
We can get a list of trees by doing a GET on http://localhost:2882/, this returns
the payload:
[
{
"name": "main",
"path": "http://localhost:2882/main/"
},
{
"name": "bdg-cce",
"path": "http://localhost:2882/bdg-cce/"
}
]Append the URL-encoded FEN to the tree path to query a position, e.g. http://localhost:2882/main/<encoded_fen>.