MTG card price range: CatBoost quantile baseline
Magic: The Gathering is a trademark of Wizards of the Coast, LLC. This is an unofficial fan project, not produced or endorsed by Wizards of the Coast.
Source code, training pipeline and design doc: mtg_price_predict_catboost_deep_gp
Predicts an 80% price range (USD) for a Magic: The Gathering card
printing (one card in one set/treatment/finish) from 93 tabular features:
rarity, finish, frame/border treatment, mana cost, type, P/T, legalities,
booster pull rates, EDHREC rank and artist. This is the project's
"Model 1", a fast CPU-only baseline. The deep-kernel GP is
CXu0630/mtg-price-deep-gp.
Method
- CatBoost
MultiQuantileregression (p10 / p50 / p90) onlog(price_avg). Settings: depth 6,l2_leaf_reg3, learning rate 0.08, early-stopped at iteration 2,709. - "Tier A" (fundamentals-only) feature set. In an ablation, adding ~350 sparse community tag columns made coverage worse in every price bucket.
- The training split was rebalanced by capping each of 30 log-price bins at 8,000 rows, so bulk commons don't dominate the loss. Calibration and test splits keep the natural price distribution.
- Group-conditional split-conformal calibration fits a separate
additive correction to the [p10, p90] interval for each predicted-median
bucket (
<$1,$1β10,$10β100,$100+) on a held-out calibration split. set/set_nameare deliberately not features, so the model generalizes to sets it hasn't seen.
Results
Held-out test split (28,377 printings, 20%), split by oracle_id so that no
card appears in both train and test. Log-price MAE of the median;
coverage is the calibrated 80% interval.
| true price | n | MAE (log) | 80% coverage | mean width (log) |
|---|---|---|---|---|
| < $1 | 17,179 | 0.42 | 77.6% | 1.40 |
| $1β$10 | 7,641 | 0.61 | 82.9% | 2.08 |
| $10β$100 | 3,236 | 0.74 | 71.8% | 2.05 |
| $100+ | 321 | 1.14 | 52.3% | 2.03 |
| overall | 28,377 | 0.52 | 78.1% | 1.66 |
Compared with the deep GP, this model has lower median error, but its intervals under-cover expensive cards ($10β$100 and $100+). Each model was evaluated on its own split, so the comparison is approximate.
Usage
# pip install catboost huggingface_hub pandas pyarrow
import sys
from huggingface_hub import snapshot_download
path = snapshot_download("CXu0630/mtg-price-catboost")
sys.path.insert(0, path) # the repo bundles its inference code in mtg_price_prediction/
from mtg_price_prediction.catboost_model import CatBoostPredictor
model = CatBoostPredictor.from_pretrained(path)
ranges = model.predict(features_df) # -> price_low / price_mid / price_high (USD)
Or with plain CatBoost:
import json, numpy as np
from catboost import CatBoostRegressor
from huggingface_hub import snapshot_download
path = snapshot_download("CXu0630/mtg-price-catboost")
cfg = json.load(open(f"{path}/config.json"))
model = CatBoostRegressor(); model.load_model(f"{path}/model.cbm")
X = features_df[cfg["feature_cols"]].copy()
for c in cfg["cat_features"]:
X[c] = X[c].fillna("__missing__").astype(str)
p10, p50, p90 = model.predict(X).T
buckets = np.digitize(p50, np.log([1, 10, 100])) # 0:<$1 1:$1-10 2:$10-100 3:$100+
corr = np.array([cfg["calibration_corrections"][str(b)] for b in buckets])
low, mid, high = np.exp(p10 - corr), np.exp(p50), np.exp(p90 + corr)
features_df has rows shaped like the project's prepare_training_data.py
output. config.json β feature_cols lists the exact 93 columns.
Files
| file | contents |
|---|---|
model.cbm |
CatBoost model (native format) |
config.json |
feature columns, categorical columns, hyperparameters, calibration corrections, test metrics |
mtg_price_prediction/ |
inference code (CatBoostPredictor, calibration) |
Limitations
- Expensive cards are under-covered. Calibrated coverage is 52% at $100+ and 72% at $10β$100, against an 80% target. The calibration sample for $100+ is small (131 points).
- The model goes stale. It is a direct regressor on absolute log-price, trained on a ~3-month TCGplayer window ending September 2026, so it drifts with the market.
- Results are from a single seed and a single split.
- Out of scope: multi-face cards, digital-only printings, and oversized cards.
Training data
CXu0630/mtg-card-market-dataset
has one row per (printing, finish), 142,012 of them with a price. It is
built on pcwoods/mtg-card-prices
(Scryfall card data + TCGplayer price history) and joined with booster pull
rates from
CXu0630/mtg-print-distribution.
- Downloads last month
- 42