Everything you need to install, configure, and use the NBA Shot Strategy application.
Interactive web application applying probabilistic methods from financial mathematics to basketball analytics. Users can select NBA matchups, adjust shot strategies interactively, and run Monte Carlo simulations to find optimal 2PT/3PT ratios.
cd /opt/predicrionai
docker compose up --build
# Open http://localhost:8000
# Configure .env
cp .env.example .env
nano .env # Add CF_API_EMAIL, CF_API_KEY, DOMAIN
# Launch
docker compose up -d --build
# Access:
# https://monte.kz-saas.com → Web App
# https://jupyter.monte.kz-saas.com → Jupyter
# https://research.monte.kz-saas.com → Research Paper
# https://traefik.monte.kz-saas.com → Dashboard
# Clone repository
git clone <repo-url>
cd predicrionai
# Launch
docker compose up -d --build
Automatically handles:
# Backend
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
uvicorn webapp.main:app --reload --port 8000
# Frontend (separate terminal)
cd frontend
npm install
npm run dev
# Open http://localhost:5173
Add 4 A records:
| Name | Content | Proxy |
|---|---|---|
| monte | YOUR_SERVER_IP | ✅ Proxied |
| jupyter.monte | YOUR_SERVER_IP | ✅ Proxied |
| research.monte | YOUR_SERVER_IP | ✅ Proxied |
| traefik.monte | YOUR_SERVER_IP | ❌ DNS only |
cp .env.example .env
nano .env
Fill in:
# Cloudflare API Token
CF_API_EMAIL=your-email@cloudflare.com
CF_API_KEY=your_cloudflare_api_token
# Domain
DOMAIN=monte.yourdomain.com
# Application
NBA_API_RATE_LIMIT=0.6
LOG_LEVEL=INFO
# Match cache (Redis, TTL in seconds, default 12 hours)
MATCH_CACHE_TTL_SECONDS=43200
# Start all services
docker compose up -d --build
# Check status
docker ps
# Follow logs (certificate issuance)
docker logs traefik -f
# Look for: "Server responded with a certificate"
curl -I https://monte.yourdomain.com # 200
curl -I https://research.monte.yourdomain.com # 200
curl -I https://jupyter.monte.yourdomain.com # 302
ESPN, nba.com or Synthetic (fallback)Matches are fetched from the first available source (in priority order):
| Priority | Source | Provides | source |
|---|---|---|---|
| 1 | ESPN Scoreboard API | Real games: last 7 days + next 7 days | espn |
| 2 | stats.nba.com (nba_api) | Historical games from season 2023-24 | nba_api |
| 3 | SyntheticProvider | Deterministic synthetic pairings (seed 42) — always works | synthetic |
Responses are cached in Redis (key matches:{mode}:{count}, TTL 12 hours by default). If Redis is unavailable an in-memory cache is used — behavior stays the same.
# Start Jupyter
docker compose up jupyter
# Open https://jupyter.monte.kz-saas.com
# Password in CREDENTIALS.txt
Example code:
from src.simulation import MonteCarloSimulation
sim = MonteCarloSimulation(
two_point_fg_pct=0.52,
three_point_fg_pct=0.36,
num_iterations=10000
)
best, all = sim.optimize_shot_distribution()
import matplotlib.pyplot as plt
plt.plot([r.three_pt_ratio for r in all],
[r.mean_score for r in all])
plt.show()
predicrionai/
├── webapp/ # FastAPI backend
│ ├── main.py # Entry point (+ cache warm-up on startup)
│ ├── api.py # REST routes
│ ├── providers.py # Data sources: ESPN → nba_api → synthetic
│ ├── cache.py # Redis match cache (in-memory fallback)
│ ├── services.py # Business logic
│ └── static/ # Built frontend
├── frontend/ # React + Vite
│ ├── src/
│ │ ├── pages/ # MatchSelect, Analysis
│ │ └── components/ # TeamCard, Charts
│ └── vite.config.js
├── src/ # Simulation engine
│ ├── simulation/ # MonteCarlo
│ ├── analysis/ # FourFactors, HotHand
│ └── data/ # NBAClient
├── research-site/ # Static site
└── docker-compose.yml # Orchestration (traefik, web, redis, jupyter, research)
| Component | Technology | Version |
|---|---|---|
| Backend | FastAPI + Uvicorn | 0.115+ |
| Frontend | React + Vite | 18 / 5 |
| Charts | Chart.js | 4.4 |
| Simulation | NumPy + SciPy | 1.24+ |
| Cache | Redis | 7.x |
| Containers | Docker + Compose | v2 |
| Proxy | Traefik + Let's Encrypt | 3.7.10 |
Get list of all NBA teams.
curl http://localhost:8000/api/teams
Get team profile (shooting stats, Four Factors).
curl http://localhost:8000/api/teams/1610612747
Run Monte Carlo simulation.
{
"home_team_id": 1610612747,
"away_team_id": 1610612744,
"home_three_ratio": 0.38,
"away_three_ratio": 0.42,
"home_fg3_pct": 0.36,
"away_fg3_pct": 0.38,
"hot_hand": false,
"iterations": 5000
}
Health check endpoint.
curl http://localhost:8000/api/health
List of matches. For upcoming/history data is fetched from ESPN (fallback: stats.nba.com, then synthetic) and cached in Redis.
curl "http://localhost:8000/api/matches?mode=history&count=5"
Each match has a source field — espn, nba_api or synthetic:
[{
"match_id": "ESP-401585000",
"date": "2026-08-04",
"home_team_id": 1610612747,
"away_team_id": 1610612738,
"home_score": 112,
"away_score": 98,
"status": "final",
"source": "espn",
"label": "Los Angeles Lakers vs Boston Celtics"
}]
Full API docs: https://monte.kz-saas.com/docs
# Find process
lsof -i :8000
# Stop
kill -9 <PID>
# Or change port in docker-compose.yml
ports:
- "8001:8000"
# Rebuild
cd frontend
npm run build
# Check files
ls -la ../webapp/static/
Check password hash in docker-compose.yml — all $ must be doubled:
--NotebookApp.password='argon2:$$argon2id$$v=19$$...'
CREDENTIALS.txttraefik.monte.yourdomain.com is in DNS-only mode (not proxied)# Check logs
docker logs traefik | grep -i error
# Common causes:
# - Invalid CF_API_KEY
# - Token lacks "Zone DNS Edit" permission
# - DNS records missing
Not a problem! Match data is fetched through the ESPN → stats.nba.com → Synthetic chain. If all real sources are down (or it's off-season with no games), the synthetic fallback kicks in automatically — you'll see the "Synthetic" badge. If stats.nba.com blocks requests, ESPN keeps working, and vice versa.
The app works without Redis too: the cache automatically switches to in-memory mode (single process). To restore Redis: docker compose up -d redis.
Research Concentration under Professor Hesam Oveys
New York University | August 2026