Skip to content

About

Dynamic pricing engine using XGBoost, price elasticity modeling, and A/B testing to optimize revenue, with a Streamlit dashboard.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

15 Commits

Folders and files

Repository files navigation

Dynamic Pricing Engine

ML-driven dynamic pricing for e-commerce using XGBoost, price elasticity modelling, and a validated A/B test simulation.

Python XGBoost scikit-learn Streamlit


Results

Metric Value
Dataset size 500K+ transactions (bootstrap-augmented from ~173K real Kaggle rows)
Elasticity model R² (price ratio + seasonality/lag controls) 0.6011
Elasticity range across segments −0.73 to −0.79 (all 8 segments inelastic)
XGBoost MAPE (holdout, chronological split) 6.39 %
XGBoost R² 0.9903
vs Static baseline MAPE 140.70 % → 95.5 % improvement
A/B test revenue uplift +10.8 % (95% CI: 9.6–11.9 %, P(uplift>0) = 1.000)

On the A/B test design: treatment price is not an XGBoost prediction — that model reconstructs prices already historically charged, so it can't actually optimize anything. Treatment price is the profit-maximizing price from the elasticity model, searched per product within a bounded ±15% of that product's base price. The bound exists because every segment is inelastic (|ε| < 1): profit is mathematically increasing in price with no interior optimum, so an unconstrained search always walks to the edge of whatever range it's given — the bound mirrors real-world price-change guardrails and keeps predictions inside the range the model was actually fit on. Full reasoning in src/elasticity.py and src/ab_test.py.

This is a model-based counterfactual simulation, not a live experiment — it shows what the fitted elasticity model predicts, not confirmed real-world customer behavior.


Project Structure

dynamic-pricing-engine/
├── data/
│   ├── raw/                    # Kaggle dataset (gitignored)
│   └── processed/
│       ├── features.csv        # Engineered feature matrix
│       └── synthetic_500k.csv  # Augmented dataset
├── notebooks/
│   ├── 01_eda.ipynb            # Exploratory data analysis
│   ├── 02_feature_engineering.ipynb
│   ├── 03_elasticity_modeling.ipynb
│   ├── 04_xgboost_pricing.ipynb
│   └── 05_ab_test_simulation.ipynb
├── src/
│   ├── config.py               # Paths, hyperparameters, constants
│   ├── data_loader.py          # Kaggle download + synthetic augmentation
│   ├── features.py             # Feature engineering pipeline
│   ├── elasticity.py           # Log-log OLS elasticity model + bounded profit optimizer
│   ├── model.py                # XGBoost pricing model
│   └── ab_test.py              # A/B test simulation
├── streamlit_app/
│   └── app.py                  # Interactive dashboard
├── models/                     # Saved model artefacts (gitignored)
├── reports/                    # Generated plots
├── requirements.txt
└── README.md

Quickstart

1. Clone and install

git clone https://github.com/dswithmohit/dynamic-pricing-engine
cd dynamic-pricing-engine
pip install -r requirements.txt

2. Set up Kaggle credentials

# Place kaggle.json in ~/.kaggle/
chmod 600 ~/.kaggle/kaggle.json

Dataset: noopurbhatt/retail-pricing-and-demand-signals-dataset

3. Run notebooks in order

01_eda.ipynb                 → downloads data, builds 500K synthetic dataset
02_feature_engineering.ipynb → builds feature matrix
03_elasticity_modeling.ipynb → fits elasticity model (R² = 0.6011, ε ≈ −0.73 to −0.79)
04_xgboost_pricing.ipynb     → trains XGBoost (MAPE = 6.39%, R² = 0.9903)
05_ab_test_simulation.ipynb  → simulates A/B test (+10.8% revenue uplift, 95% CI 9.6–11.9%)

4. Launch Streamlit app

streamlit run streamlit_app/app.py

Key Features

Feature Engineering

Engineered features from raw transaction data:

Feature Description
price_ratio_to_base unit_price / base_price — isolates within-product price movement (promotions/discounts) from cross-product price-level differences; this is what the elasticity model regresses on
competitor_price_ratio unit_price / competitor_price. Note: competitor_price is a simulated proxy (unit_price ± 10% noise), not real market data
demand_lag_7 units_sold lagged 7 rows — short-term momentum
demand_lag_14 units_sold lagged 14 rows — medium-term trend
rolling_demand_28 28-row rolling mean demand
seasonality_index Sinusoidal month signal ∈ [0, 1] — peaks in June, troughs in December
inventory_level Stock on hand — supply constraint signal, decays with cumulative demand
price_to_avg_category Item price vs daily category average

price_ratio_to_base, competitor_price_ratio, and price_to_avg_category are all computed from unit_price and are excluded from the XGBoost feature set (see config.PRICE_LEAKAGE_COLS) — they're built for the elasticity model and reporting, not as price-prediction inputs.

Price Elasticity Modelling

Per-segment log-log OLS regression:

ln(Q) = α + ε · ln(P_ratio) + controls

where P_ratio = unit_price / base_price, and controls are seasonality_index and demand_lag_7.

  • Overall R² = 0.6011 across product segments
  • Elasticity consistently −0.73 to −0.79 across all 8 segments — every segment is inelastic, meaning demand falls slower than price rises within a realistic adjustment range
  • Because every segment is inelastic, unconstrained profit maximization has no interior optimum — optimal_price_per_row() searches within a bounded ±15% band instead, which is what drives the A/B test's treatment pricing

XGBoost Pricing Model

XGBoost regressor predicting unit_price from engineered features, after excluding columns that leak the target (see config.PRICE_LEAKAGE_COLS). Time-aware (chronological) train/test split.

  • MAPE: 6.39% on holdout, vs. a 140.70% static-baseline MAPE (95.5% improvement)
  • R²: 0.9903
  • Feature importance is dominated by base_price (~82%) and promotion_type (~11%) — unit_price rarely deviates more than ~15–20% from base_price in this dataset, so the model mainly learns a promotion-driven adjustment on a strong anchor price rather than reacting primarily to demand/inventory signals

A/B Test Simulation

Compares static (historical mean) pricing against elasticity-optimized pricing, with both arms scored through the same fitted demand curve for a fair comparison.

  • +10.8% revenue uplift over static pricing
  • 95% CI: [9.6%, 11.9%] — excludes zero; P(uplift > 0) = 1.000
  • Treatment price = profit-maximizing price per product, bounded to ±15% of base price, assuming a 40% gross margin (COST_MARGIN_FACTOR in config.py, since the dataset has no real cost column)

Tech Stack

Python · XGBoost · scikit-learn · pandas · NumPy · Streamlit · Plotly · Matplotlib


Author

Mohit · GitHub · Kaggle · LinkedIn

About

Dynamic pricing engine using XGBoost, price elasticity modeling, and A/B testing to optimize revenue, with a Streamlit dashboard.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Contributors

Languages