taycurry15 commited on
Commit
9bf7321
Β·
verified Β·
1 Parent(s): 967a50d

# Sports Betting AI System - Complete Implementation Guide

Browse files

## Table of Contents
1. [System Overview](#system-overview)
2. [Architecture](#architecture)
3. [Setup Instructions](#setup-instructions)
4. [Core Components](#core-components)
5. [API Integration](#api-integration)
6. [Machine Learning Pipeline](#machine-learning-pipeline)
7. [Risk Management](#risk-management)
8. [Dashboard Interface](#dashboard-interface)
9. [AI Agent Enhancements](#ai-agent-enhancements)
10. [Deployment & Operations](#deployment--operations)

---

## System Overview

A production-ready, Dockerized sports betting AI agent that:
- Ingests historical and live game/odds data via APIs
- Engineers features and trains ML models
- Runs inference for value pick selection
- Implements Kelly criterion risk management
- Sends formatted picks via Telegram
- Provides interactive web dashboard
- Self-evaluates performance with backtesting

### Key Features
- **ML Models**: XGBoost/LightGBM ensemble with calibration
- **Risk Controls**: Kelly criterion, portfolio optimization, stop-loss
- **Data Source**: The Odds API integration
- **Automation**: Scheduled pipelines via APScheduler/n8n
- **Notifications**: Telegram bot for picks and reports
- **Dashboard**: Modern web interface for monitoring

### Tech Stack
- **Backend**: Python 3.11, FastAPI, PostgreSQL, Redis
- **ML**: scikit-learn, XGBoost, LightGBM
- **Frontend**: HTML5, CSS3 (glassmorphism), JavaScript
- **Orchestration**: Docker Compose, n8n
- **Messaging**: Telegram Bot API

---

## Architecture

```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Web Dashboard β”‚
β”‚ (HTML/CSS/JS - Glassmorphism UI) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ FastAPI Backend β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚Ingestion β”‚ β”‚ ML β”‚ β”‚ Pick β”‚ β”‚
β”‚ β”‚ Service β”‚ β”‚ Pipeline β”‚ β”‚Selection β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ PostgreSQL β”‚ β”‚ Redis β”‚
β”‚ (Main DB) β”‚ β”‚ (Cache) β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ External Services β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ Odds API β”‚ β”‚ Telegram β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```

---

## Setup Instructions

### Prerequisites
- Docker & Docker Compose
- Python 3.11+
- Telegram Bot Token
- The Odds API Key

### Environment Configuration

Create `.env` file:
```bash
# API Keys
ODDS_API_KEY=327b7a158175a6f471f8e41fa0acfe5f # Your paid key
TELEGRAM_BOT_TOKEN=your_telegram_bot_token
TELEGRAM_CHAT_ID=your_chat_id

# Database
DB_HOST=postgres
DB_PORT=5432
DB_NAME=sports_betting
DB_USER=betting_user
DB_PASSWORD=secure_password_123

# Redis
REDIS_HOST=redis
REDIS_PORT=6379

# Risk Management
MAX_KELLY_FRACTION=0.25
MAX_BANKROLL_PER_BET=0.005
MIN_EDGE_THRESHOLD=0.02
MAX_PICKS_PER_DAY=10
INITIAL_BANKROLL=1000

# Time Zone
TZ=America/Kentucky/Louisville
```

### Quick Start Commands

```bash
# 1. Clone repository
git clone <repository-url>
cd sports-betting-ai

# 2. Build and start services
docker-compose up -d --build

# 3. Initialize database
docker-compose exec app python -c "from src.database import init_database; init_database()"

# 4. Backfill historical data
docker-compose exec app python scripts/backfill.py --days 30

# 5. Train initial models
docker-compose exec app python -c "from src.ml_training import ModelTrainer; ModelTrainer().train_all_models()"

# 6. Run pipeline
docker-compose exec app python scripts/daily_pipeline.py

# 7. Open dashboard
open dashboard.html
```

---

## Core Components

### 1. Docker Compose Configuration

```yaml
version: '3.8'

services:
postgres:
image: postgres:15-alpine
environment:
POSTGRES_DB: ${DB_NAME}
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
- ./scripts/setup_db.sql:/docker-entrypoint-initdb.d/01-init.sql
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"]
interval: 10s
timeout: 5s
retries: 5

redis:
image: redis:7-alpine
command: redis-server --appendonly yes
volumes:
- redis_data:/data
ports:
- "6379:6379"
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5

app:
build: .
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
env_file:
- .env
volumes:
- ./src:/app/src
- ./scripts:/app/scripts
ports:
- "8000:8000"
command: python -m src.main
restart: unless-stopped

n8n:
image: n8nio/n8n
environment:
- N8N_BASIC_AUTH_ACTIVE=true
- N8N_BASIC_AUTH_USER=admin
- N8N_BASIC_AUTH_PASSWORD=admin123
- N8N_HOST=localhost
- N8N_PORT=5678
- WEBHOOK_URL=http://app:8000
- TZ=${TZ}
volumes:
- n8n_data:/home/node/.n8n
ports:
- "5678:5678"
depends_on:
- app

volumes:
postgres_data:
redis_data:
n8n_data:
```

### 2. Database Schema

```sql
-- PostgreSQL Schema
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

CREATE TABLE leagues (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
name VARCHAR(100) NOT NULL UNIQUE,
sport VARCHAR(50) NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);

CREATE TABLE games (
id VARCHAR(100) PRIMARY KEY,
league_id UUID REFERENCES leagues(id),
home_team VARCHAR(200) NOT NULL,
away_team VARCHAR(200) NOT NULL,
commence_time TIMESTAMP NOT NULL,
completed BOOLEAN DEFAULT FALSE,
home_score INTEGER,
away_score INTEGER,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);

CREATE TABLE odds (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
game_id VARCHAR(100) REFERENCES games(id),
bookmaker VARCHAR(100) NOT NULL,
market_type VARCHAR(50) NOT NULL,
last_update TIMESTAMP NOT NULL,
home_odds DECIMAL(10,3),
away_odds DECIMAL(10,3),
created_at TIMESTAMP DEFAULT NOW()
);

CREATE TABLE features (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
game_id VARCHAR(100) REFERENCES games(id),
feature_data JSONB NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);

CREATE TABLE predictions (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
game_id VARCHAR(100) REFERENCES games(id),
model_version VARCHAR(50) NOT NULL,
prob_home DECIMAL(5,4),
prob_away DECIMAL(5,4),
confidence DECIMAL(5,4),
created_at TIMESTAMP DEFAULT NOW()
);

CREATE TABLE picks (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
game_id VARCHAR(100) REFERENCES games(id),
prediction_id UUID REFERENCES predictions(id),
pick_type VARCHAR(50) NOT NULL,
pick_value VARCHAR(100) NOT NULL,
odds DECIMAL(10,3) NOT NULL,
bookmaker VARCHAR(100),
edge DECIMAL(5,4),
kelly_fraction DECIMAL(5,4),
stake_units DECIMAL(10,4),
confidence_tier CHAR(1),
sent_at TIMESTAMP,
result VARCHAR(20),
pnl DECIMAL(10,2),
created_at TIMESTAMP DEFAULT NOW()
);

CREATE TABLE bankroll (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
balance DECIMAL(15,2) NOT NULL,
transaction_type VARCHAR(50),
amount DECIMAL(10,2),
description TEXT,
created_at TIMESTAMP DEFAULT NOW()
);

-- Indexes
CREATE INDEX idx_games_commence ON games(commence_time);
CREATE INDEX idx_odds_game ON odds(game_id, last_update DESC);
CREATE INDEX idx_predictions_game ON predictions(game_id);
CREATE INDEX idx_picks_created ON picks(created_at DESC);

-- Initial data
INSERT INTO bankroll (balance, transaction_type, description)
VALUES (1000.00, 'INITIAL', 'Starting bankroll');

INSERT INTO leagues (name, sport) VALUES
('NFL', 'American Football'),
('NBA', 'Basketball'),
('MLB', 'Baseball'),
('NHL', 'Hockey');
```

---

## API Integration

### The Odds API Client

```python
import httpx
from typing import List, Dict

class OddsAPIClient:
def __init__(self):
self.api_key = "327b7a158175a6f471f8e41fa0acfe5f"
self.base_url = "https://api.the-odds-api.com/v4"
self.client = httpx.AsyncClient(timeout=30.0)

async def get_sports(self):
"""Get list of in-season sports"""
url = f"{self.base_url}/sports"
params = {"apiKey": self.api_key}
response = await self.client.get(url, params=params)
return response.json()

async def get_odds(self, sport: str, markets: List[str] = None):
"""Get odds for upcoming games"""
url = f"{self.base_url}/sports/{sport}/odds"
params = {
"apiKey": self.api_key,
"regions": "

Files changed (2) hide show
  1. index.html +76 -8
  2. style.css +144 -18
index.html CHANGED
@@ -1,19 +1,87 @@
 
1
  <!doctype html>
2
  <html>
3
  <head>
4
  <meta charset="utf-8" />
5
  <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
  <link rel="stylesheet" href="style.css" />
 
 
8
  </head>
9
  <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  </body>
19
  </html>
 
1
+
2
  <!doctype html>
3
  <html>
4
  <head>
5
  <meta charset="utf-8" />
6
  <meta name="viewport" content="width=device-width" />
7
+ <title>Sports Betting AI Dashboard</title>
8
  <link rel="stylesheet" href="style.css" />
9
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
10
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
11
  </head>
12
  <body>
13
+ <div class="dashboard">
14
+ <header>
15
+ <h1>Sports Betting AI Dashboard</h1>
16
+ <nav>
17
+ <a href="/" class="active"><i class="fas fa-home"></i> Home</a>
18
+ <a href="/picks"><i class="fas fa-list"></i> Today's Picks</a>
19
+ <a href="/performance"><i class="fas fa-chart-line"></i> Performance</a>
20
+ <a href="/settings"><i class="fas fa-cog"></i> Settings</a>
21
+ </nav>
22
+ </header>
23
+
24
+ <div class="stats-grid">
25
+ <div class="stat-card">
26
+ <div class="stat-label">Bankroll</div>
27
+ <div class="stat-value">$1,250</div>
28
+ <div class="stat-change positive">↑ +12.5% this week</div>
29
+ </div>
30
+ <div class="stat-card">
31
+ <div class="stat-label">Win Rate</div>
32
+ <div class="stat-value">58.2%</div>
33
+ <div class="stat-change positive">↑ +3.2% from last month</div>
34
+ </div>
35
+ </div>
36
+
37
+ <div class="content-section">
38
+ <h2>Recent Picks</h2>
39
+ <table class="picks-table">
40
+ <thead>
41
+ <tr>
42
+ <th>Game</th>
43
+ <th>Pick</th>
44
+ <th>Odds</th>
45
+ <th>Edge</th>
46
+ <th>Stake</th>
47
+ <th>Result</th>
48
+ </tr>
49
+ </thead>
50
+ <tbody id="picksTableBody">
51
+ <tr>
52
+ <td>Lakers vs Warriors</td>
53
+ <td>Lakers ML</td>
54
+ <td>+120</td>
55
+ <td>3.2%</td>
56
+ <td>$25</td>
57
+ <td class="win">W (+$30)</td>
58
+ </tr>
59
+ </tbody>
60
+ </table>
61
+ </div>
62
+
63
+ <div class="content-section">
64
+ <h2>Performance Trends</h2>
65
+ <canvas id="performanceChart"></canvas>
66
+ </div>
67
  </div>
68
+
69
+ <script>
70
+ // Initialize chart
71
+ const ctx = document.getElementById('performanceChart').getContext('2d');
72
+ const performanceChart = new Chart(ctx, {
73
+ type: 'line',
74
+ data: {
75
+ labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
76
+ datasets: [{
77
+ label: 'Bankroll',
78
+ data: [1000, 1050, 1150, 1200, 1250, 1300],
79
+ borderColor: 'rgba(75, 192, 192, 1)',
80
+ backgroundColor: 'rgba(75, 192, 192, 0.2)',
81
+ tension: 0.1
82
+ }]
83
+ }
84
+ });
85
+ </script>
86
  </body>
87
  </html>
style.css CHANGED
@@ -1,28 +1,154 @@
 
 
1
  body {
2
- padding: 2rem;
3
- font-family: -apple-system, BlinkMacSystemFont, "Arial", sans-serif;
 
 
 
 
4
  }
5
 
6
- h1 {
7
- font-size: 16px;
8
- margin-top: 0;
 
9
  }
10
 
11
- p {
12
- color: rgb(107, 114, 128);
13
- font-size: 15px;
14
- margin-bottom: 10px;
15
- margin-top: 5px;
 
 
 
16
  }
17
 
18
- .card {
19
- max-width: 620px;
20
- margin: 0 auto;
21
- padding: 16px;
22
- border: 1px solid lightgray;
23
- border-radius: 16px;
24
  }
25
 
26
- .card p:last-child {
27
- margin-bottom: 0;
 
 
 
 
 
28
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ```css
2
+ /* Base Styles */
3
  body {
4
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
5
+ margin: 0;
6
+ padding: 0;
7
+ background: linear-gradient(135deg, #1a1a2e, #16213e);
8
+ color: #fff;
9
+ min-height: 100vh;
10
  }
11
 
12
+ .dashboard {
13
+ max-width: 1200px;
14
+ margin: 0 auto;
15
+ padding: 20px;
16
  }
17
 
18
+ /* Header Styles */
19
+ header {
20
+ display: flex;
21
+ justify-content: space-between;
22
+ align-items: center;
23
+ margin-bottom: 30px;
24
+ padding-bottom: 15px;
25
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
26
  }
27
 
28
+ header h1 {
29
+ margin: 0;
30
+ font-size: 28px;
 
 
 
31
  }
32
 
33
+ nav a {
34
+ color: #fff;
35
+ text-decoration: none;
36
+ margin-left: 20px;
37
+ font-size: 16px;
38
+ opacity: 0.7;
39
+ transition: opacity 0.3s;
40
  }
41
+
42
+ nav a:hover {
43
+ opacity: 1;
44
+ }
45
+
46
+ nav a.active {
47
+ opacity: 1;
48
+ font-weight: bold;
49
+ }
50
+
51
+ /* Stat Cards */
52
+ .stats-grid {
53
+ display: grid;
54
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
55
+ gap: 20px;
56
+ margin-bottom: 30px;
57
+ }
58
+
59
+ .stat-card {
60
+ background: rgba(255, 255, 255, 0.1);
61
+ backdrop-filter: blur(10px);
62
+ border-radius: 10px;
63
+ padding: 20px;
64
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
65
+ }
66
+
67
+ .stat-label {
68
+ font-size: 14px;
69
+ opacity: 0.7;
70
+ margin-bottom: 5px;
71
+ }
72
+
73
+ .stat-value {
74
+ font-size: 24px;
75
+ font-weight: bold;
76
+ margin-bottom: 5px;
77
+ }
78
+
79
+ .stat-change {
80
+ font-size: 12px;
81
+ }
82
+
83
+ .positive {
84
+ color: #4ade80;
85
+ }
86
+
87
+ .negative {
88
+ color: #f87171;
89
+ }
90
+
91
+ /* Content Sections */
92
+ .content-section {
93
+ background: rgba(255, 255, 255, 0.05);
94
+ border-radius: 10px;
95
+ padding: 20px;
96
+ margin-bottom: 30px;
97
+ }
98
+
99
+ .content-section h2 {
100
+ margin-top: 0;
101
+ font-size: 20px;
102
+ margin-bottom: 15px;
103
+ }
104
+
105
+ /* Tables */
106
+ .picks-table {
107
+ width: 100%;
108
+ border-collapse: collapse;
109
+ }
110
+
111
+ .picks-table th {
112
+ text-align: left;
113
+ padding: 10px;
114
+ font-size: 14px;
115
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
116
+ }
117
+
118
+ .picks-table td {
119
+ padding: 10px;
120
+ border-bottom: 1px solid rgba(255, 255, 255, 0.05);
121
+ }
122
+
123
+ .win {
124
+ color: #4ade80;
125
+ }
126
+
127
+ .loss {
128
+ color: #f87171;
129
+ }
130
+
131
+ /* Chart Container */
132
+ #performanceChart {
133
+ background: rgba(255, 255, 255, 0.05);
134
+ border-radius: 10px;
135
+ padding: 20px;
136
+ }
137
+
138
+ /* Responsive Design */
139
+ @media (max-width: 768px) {
140
+ header {
141
+ flex-direction: column;
142
+ align-items: flex-start;
143
+ }
144
+
145
+ nav {
146
+ margin-top: 15px;
147
+ }
148
+
149
+ nav a {
150
+ margin-left: 0;
151
+ margin-right: 15px;
152
+ }
153
+ }
154
+ ```