A data-driven valuation engine that prices IPL players on performance fundamentals, flags auction over/underpayment, and quantifies spend efficiency across franchises — built end-to-end using SQL, Python, Excel, and Power BI.
Every IPL auction, franchises collectively spend Rs. 500+ crore on players in a matter of days, often driven by bidding-war psychology rather than fundamentals. This project answers one core business question:
What should a player actually be worth based on performance — and which franchises are systematically over- or under-paying?
This is framed as a valuation and capital allocation problem, not a machine learning demo. The same underlying logic applies to pricing a private asset, negotiating a signing bonus, or setting a product price point.
- Data Sources
- Project Structure
- Architecture
- Feature Engineering
- Model
- Deliverables
- Key Findings
- Setup & Reproduction
- Design Decisions & Methodology Notes
| Dataset | Provider | Coverage | Records |
|---|---|---|---|
| IPL Auction Data (2013-2026) | sunnyyadav754 / Kaggle | 14 seasons, one CSV per year | 1,380 sold players |
| Ball-by-Ball Performance | jamiewelsh2 / Cricsheet | IPL 2008-2023 | 239,693 delivery rows |
No data synthesis. Every player name, price, and performance number is sourced directly from the above datasets. All auction prices are in Indian Rupees (Lakh). The 2013 dataset originally uses USD and is converted at a fixed Rs. 55/USD rate to match later seasons.
ipl/
|
+-- data/
| +-- seed_data.py # Master ingestion script - run this first
| +-- ipl_valuation.db # SQLite star schema (output)
| +-- raw/
| | +-- auction_sunny/ # Kaggle auction CSVs (2013-2026)
| | +-- ball_by_ball_ipl.csv # Cricsheet ball-by-ball data
| +-- processed/
| +-- auction_features.csv # 1,380 x 37 anti-leakage feature matrix
| +-- player_season_stats.csv # Per-player, per-season aggregates
| +-- model_features.csv # Model-ready features (scaled)
| +-- model_output.csv # Walk-forward predictions + residuals
| +-- feature_importances.csv # XGBoost feature importance scores
| +-- franchise_efficiency.csv # Per-franchise overpayment summary
|
+-- sql/
| +-- schema.sql # Star schema DDL
| +-- pre_auction_stats.sql # Anti-leakage performance aggregates
| +-- franchise_spend_summary.sql # Franchise efficiency queries
|
+-- notebooks/
| +-- 01_data_merge.ipynb # Load from SQLite, validate joins
| +-- 02_feature_engineering.ipynb # Role-aware imputation, interactions
| +-- 03_valuation_model.ipynb # XGBoost + walk-forward CV
| +-- 04_insight_generation.ipynb # Franchise efficiency, hypothesis tests
|
+-- dashboard/
| +-- generate_dashboard.py # Plotly HTML dashboard generator
| +-- generate_powerbi.py # Power BI .pbit template generator
| +-- ipl_dashboard.html # PRIMARY DELIVERABLE - open in browser
| +-- ipl_dashboard.pbit # Power BI Desktop template
|
+-- excel/
| +-- generate_excel.py # Excel workbook generator
| +-- valuation_sanity_check.xlsx # Analyst sanity-check workbook
|
+-- deliverable/
| +-- generate_pptx.py # PPTX recommendation generator
| +-- ipl_recommendation.pptx # 1-page business recommendation
| +-- charts/ # Static PNGs exported from notebooks
| +-- 01_price_distribution.png
| +-- 02_correlation_heatmap.png
| +-- 02_log_transform.png
| +-- 03_feature_importance.png
| +-- 03_model_performance.png
| +-- 04_franchise_efficiency.png
| +-- 04_market_efficiency_trend.png
| +-- 04_overpaid_underpaid_xi.png
|
+-- requirements.txt
+-----------------+
| dim_player |
| player_id (PK) |
| player_name |
| nationality |
| role |
+--------+--------+
|
+---------------+ +------+----------+ +----------------+
| dim_season | | fact_auction | | dim_team |
| season (PK) +---| player_id (FK) +---| team_id (PK) |
| year | | season (FK) | | team_name |
| auction_type | | team_id (FK) | | city |
+---------------+ | base_price | +----------------+
| final_price |
| overseas_flag |
| capped_flag |
| marquee_flag |
+-----------------+
The seed_data.py script populates this schema in a single pass, performing fuzzy name matching between the Kaggle auction data and Cricsheet player names.
Raw CSVs (Kaggle + Cricsheet)
|
v
seed_data.py ---> ipl_valuation.db ---> processed/auction_features.csv
|
01_data_merge.ipynb <------------------+
|
02_feature_engineering.ipynb
|
03_valuation_model.ipynb ---> model_output.csv
| feature_importances.csv
04_insight_generation.ipynb -> franchise_efficiency.csv
|
+------------+--------------------+
v v v
ipl_dashboard.html ipl_dashboard.pbit valuation_sanity_check.xlsx
ipl_recommendation.pptx
All features are computed with a strict anti-leakage constraint: only data from seasons strictly prior to the auction year is used. A player auctioned in 2022 can only have features derived from 2008-2021 performance.
| Category | Features | Notes |
|---|---|---|
| Career batting | batting_avg, batting_sr, runs_career, boundary_pct |
Career aggregates to auction date |
| Career bowling | bowling_avg, bowling_econ, wickets_career, dot_ball_pct |
Career aggregates to auction date |
| Recent form (batting) | recent_batting_avg, recent_batting_sr |
Last 2 seasons only — highest predictive weight |
| Recent form (bowling) | recent_bowling_econ, recent_wickets |
Last 2 seasons only |
| Context - batting | powerplay_sr, death_sr, death_runs |
Phase-specific strike rates |
| Context - bowling | powerplay_econ, death_econ, death_wickets |
Phase-specific economy rates |
| Player flags | overseas_flag, capped_flag, captaincy_flag, marquee_flag |
Binary categorical signals |
| Experience | ipl_seasons_prior, total_matches |
Number of prior IPL seasons played |
| Interaction | experience_x_recent_sr, overseas_x_recent_sr |
Nonlinear combinations — captures the overseas scarcity effect |
Players with no Cricsheet match (179 of 1,380 — first-timers or name mismatches) have performance features set to NaN and imputed using role-specific medians before modelling.
log(1 + final_price_lakh) — log-transformation stabilises the right-skewed price distribution before fitting.
XGBoost Regressor with L1 + L2 regularisation. Hyperparameters tuned via time-aware cross-validation (not random/grid search, which would violate the temporal ordering constraint).
Standard k-fold CV is inappropriate for time-series auction data: if a model trained on 2022 data predicts 2018 prices, it has implicitly used future information.
Instead, walk-forward validation is used:
Fold 1: Train 2013-2014 -> Test 2015
Fold 2: Train 2013-2015 -> Test 2016
Fold 3: Train 2013-2016 -> Test 2017
...
Fold 11: Train 2013-2024 -> Test 2025
Fold 12: Train 2013-2025 -> Test 2026
This guarantees no future data ever enters the training window.
A Ridge regression on 3 features (batting_sr, bowling_econ, overseas_flag) is computed for every fold. XGBoost must beat this baseline to be considered useful — it does, significantly, due to the nonlinear interactions between recent form, overseas status, and role.
model_output.csv contains one row per sold player per auction season with:
actual_price_lakh— what the franchise actually paidxgb_pred_lakh— XGBoost model's fair value estimateresidual_lakh—actual - predicted(positive = overpaid, negative = bargain)
Open in any browser. No server or dependencies required — fully self-contained Plotly.
Page 1 — Auction Overview
- Total spend by season (column chart)
- Spend by player role (donut chart)
- Price distribution by era: 2013-16 / 2017-20 / 2021-23 / 2024-26 (violin)
- Base price vs final bid scatter (Indian vs Overseas)
Page 2 — Valuation Deep Dive
- Actual vs XGBoost predicted price (walk-forward coloured by season)
- Top 12 most overpaid players (actual > model)
- Top 12 best bargains (actual < model)
- XGBoost feature importances (top 15)
Page 3 — Franchise Efficiency
- Total auction spend by franchise (all seasons)
- Cumulative overpayment vs model fair value per franchise
- Season-by-season overpayment heatmap
Open in Power BI Desktop. The template automatically imports data from the processed CSVs using M queries.
| Page | Visuals |
|---|---|
| Auction Overview | 5 KPI cards, column chart (spend/season), donut (spend/role), bar chart (spend/franchise), 2 slicers |
| Player Valuation | 3 KPI cards, scatter (actual vs predicted), valuation detail table, season slicer |
| Franchise Efficiency | Horizontal bar chart (overpayment), franchise efficiency table |
DAX Measures defined in the template:
[Total Spend (Cr)] = DIVIDE(SUMX(AuctionFeatures, AuctionFeatures[final_price_lakh]), 100)
[Players Bought] = COUNTROWS(AuctionFeatures)
[Avg Price (L)] = AVERAGEX(AuctionFeatures, AuctionFeatures[final_price_lakh])
[Max Price (L)] = MAXX(AuctionFeatures, AuctionFeatures[final_price_lakh])
[Total Overpayment (L)] = SUM(ModelOutput[residual_lakh])
[Model MAE (L)] = AVERAGEX(ModelOutput, ABS(ModelOutput[residual_lakh]))
[Pct Overpaid] = DIVIDE(CALCULATE(COUNTROWS(...), residual > 0), COUNTROWS(...))
If Power BI prompts for data source credentials, go to Transform Data -> Data Source Settings and verify the CSV paths point to
data/processed/.
A back-of-envelope analyst tool with 4 tabs:
| Tab | Contents |
|---|---|
| Raw Benchmarks | Rs./Run and Rs./Wicket by season — the simplest market pricing reference |
| Back-of-Envelope | Interactive formula: change yellow input cells, price updates automatically |
| Sensitivity Table | Fair value matrix across batting runs x overseas premium assumptions |
| Model vs Manual | Top residuals from XGBoost side-by-side with conditional formatting |
A single 16:9 widescreen slide structured as a McKinsey-style one-pager:
- Situation / Complication / Resolution — the business framing
- 3 data-backed findings — with real numbers embedded from
model_output.csv - 3 actionable recommendations — bid ceiling, overseas slot separation, form regression
Total IPL auction spend rose from ~Rs. 85 crore (2013) to ~Rs. 640 crore (2026). However, per-player inflation is concentrated in the top decile; the median price has risen far less. Mega-auction seasons (2018, 2022, 2025) account for a disproportionate share of total spend.
Overseas players command a statistically significant premium above their performance-implied value. The scarcity effect (maximum 4 overseas players per XI) is real but routinely overestimated in bidding wars. The premium is largest in mega-auction years when franchises are rebuilding full squads simultaneously.
XGBoost's highest-weight features are recent_batting_sr and recent_bowling_econ. The market correctly values recent form, but bids for career outlier seasons as if they will repeat — producing the largest positive residuals.
Bowlers with strong death_econ (economy rate in overs 16-20) show consistently negative residuals — they are bought below model fair value. This represents the largest persistent inefficiency in the dataset.
Rishabh Pant (2025) — actual bid: Rs. 27 crore / model fair value: ~Rs. 6 crore lower. The model does not price captaincy, franchise fan-base value, or media narrative — it prices only ball-by-ball performance. The gap is the conversation.
Python 3.10+
Anaconda (recommended) or pip
Power BI Desktop (for .pbit)
Microsoft Excel / LibreOffice (for .xlsx)
pip install -r requirements.txtrequirements.txt covers: pandas, numpy, scikit-learn, xgboost, plotly, openpyxl, python-pptx, fuzzywuzzy, python-Levenshtein, sqlite3 (stdlib).
Place the following files in data/raw/ before running the pipeline:
| File / Folder | Source |
|---|---|
data/raw/auction_sunny/IPL_Auction/ |
sunnyyadav754 Kaggle dataset — extract the ZIP, keep the per-year CSV structure |
data/raw/ball_by_ball_ipl.csv |
jamiewelsh2 Kaggle dataset |
# 1. Seed the database and build the feature matrix
python data/seed_data.py
# 2. Execute notebooks in order
jupyter nbconvert --to notebook --execute --inplace notebooks/01_data_merge.ipynb
jupyter nbconvert --to notebook --execute --inplace notebooks/02_feature_engineering.ipynb
jupyter nbconvert --to notebook --execute --inplace notebooks/03_valuation_model.ipynb
jupyter nbconvert --to notebook --execute --inplace notebooks/04_insight_generation.ipynb
# 3. Generate deliverables
python dashboard/generate_dashboard.py # -> dashboard/ipl_dashboard.html
python dashboard/generate_powerbi.py # -> dashboard/ipl_dashboard.pbit
python excel/generate_excel.py # -> excel/valuation_sanity_check.xlsx
python deliverable/generate_pptx.py # -> deliverable/ipl_recommendation.pptxTotal runtime: approximately 3-5 minutes on a standard laptop (dominated by XGBoost walk-forward training in notebook 03).
Career totals conflate early-career low-quality seasons with recent high-quality form. A player who scored 3,000 runs over 10 seasons is priced very differently from one who scored 3,000 runs in the last 3. The 2-season rolling window captures this distinction.
The relationship between overseas status and recent form is nonlinear. A domestic player with a recent SR of 160 and an overseas player with the same SR are priced very differently. XGBoost captures this interaction; a linear model cannot without manual feature engineering. Both are run — XGBoost consistently beats Ridge on MAE across all walk-forward folds.
Raw prices are highly right-skewed (a few mega-contracts dominate). Modelling log(1 + price) stabilises variance and prevents the model from over-fitting to outlier transactions. Predictions are exponentiated back before computing residuals.
The 2013 auction data uses USD prices (pre-IPL becoming a fully rupee-denominated competition). A fixed conversion rate of Rs. 55/USD is applied. This introduces a small approximation but affects fewer than 60 records.
- Captaincy value — leading a franchise has brand/sponsorship value beyond runs and wickets
- Injury risk — no injury history is available in the source data
- Fan sentiment — marquee player flags are included as binary dummies, but the magnitude of fan premium is not modelled
- Franchise retention rules — RTM (Right to Match) cards distort the auction price signal; retained players are excluded from the dataset
These omissions are documented features of the model, not oversights. The residual for a player is best interpreted as: "how much more/less than pure performance value did this franchise pay?"
- sunnyyadav754 — IPL auction dataset (Kaggle)
- jamiewelsh2 — Ball-by-ball IPL dataset (Kaggle / Cricsheet)
- Cricsheet.org — Original ball-by-ball data source