Bot de Apostas com Python e IA: Tutorial
Nesse tutorial eu mostro como criar um bot de apostas com Python que coleta odds, analisa valor com IA e manda alertas no Telegram. Código completo, passo a passo.
O que um bot realmente faz (escopo realista vs fantasia)
Vou começar desfazendo a maior ilusão: um bot de apostas NÃO é um programa que aposta sozinho e imprime dinheiro. Isso não existe. O que existe é um sistema que automatiza as partes chatas — coleta de odds, comparação entre casas, cálculo de valor — e te avisa quando encontra algo interessante.
A decisão final de apostar ou não continua sendo sua. E mesmo que você quisesse automatizar a aposta em si, as casas de apostas combatem automação com tudo que têm. Verificação por SMS, CAPTCHAs, bloqueio de IP. Não é viável de forma confiável.
O que dá pra automatizar de verdade e que gera valor real: coletar odds de várias fontes a cada 15 minutos, comparar com odds sharp (Pinnacle) pra encontrar value bets, calcular o stake ideal com Kelly, e mandar um alerta no Telegram com todos os detalhes. Aí você confere e aposta manualmente em 30 segundos.
Bot Realista
Automatiza coleta e análise, você aposta manualmente
+ Prós
- • 100% viável com APIs públicas
- • Não viola termos de serviço
- • Manutenção baixa depois de pronto
- • Você mantém controle total das apostas
− Contras
- • Precisa estar disponível pra agir nos alertas
- • Delay entre alerta e aposta pode perder oportunidades
- • Não funciona bem pra mercados ao vivo
Bot Fantasioso
Aposta automaticamente nas casas sem intervenção humana
+ Prós
- • Zero trabalho manual (em teoria)
- • Captura oportunidades instantaneamente
− Contras
- • Casas bloqueiam automação agressivamente
- • Viola termos de serviço de todas as casas
- • CAPTCHAs e 2FA impedem execução confiável
- • Risco de perder conta e saldo
- • Complexidade técnica absurda
Arquitetura do bot
O bot tem três módulos que funcionam de forma independente mas se conectam. Essa separação facilita manutenção e teste. Se a API de odds mudar, você mexe só no coletor. Se quiser trocar Telegram por Discord, mexe só no notificador.
- OddsCollector: conecta na The Odds API, coleta odds de todos os jogos do dia e salva num banco SQLite
- ValueAnalyzer: compara as odds de casas recreativas com as odds da Pinnacle (ou outro benchmark sharp). Calcula EV e stake recomendado
- TelegramNotifier: quando o ValueAnalyzer encontra uma aposta com EV acima do threshold, manda mensagem formatada no Telegram com todos os detalhes
betting-bot/
├── .env # API keys
├── requirements.txt
├── config.py # constantes e configurações
├── models/
│ └── database.py # SQLAlchemy models
├── collectors/
│ └── odds_collector.py # coleta via The Odds API
├── analyzers/
│ ├── value_analyzer.py # detecção de value bets
│ └── predictor.py # (opcional) predições com ML
├── notifiers/
│ └── telegram_bot.py # alertas via Telegram
└── main.py # orquestrador principalCódigo passo a passo
Passo 1: Configuração
# config.py
import os
from dotenv import load_dotenv
load_dotenv()
# APIs
ODDS_API_KEY = os.getenv("ODDS_API_KEY")
TELEGRAM_BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
TELEGRAM_CHAT_ID = os.getenv("TELEGRAM_CHAT_ID")
# Esportes monitorados
SPORTS = [
"soccer_brazil_serie_a",
"soccer_epl",
"basketball_nba",
"soccer_spain_la_liga",
]
# Parametros de analise
MIN_EV_PERCENT = 3.0 # EV minimo pra alertar (%)
KELLY_FRACTION = 0.25 # usar 1/4 Kelly
BANKROLL = 5000.0 # bankroll em reais
SHARP_BOOKMAKER = "Pinnacle" # referencia sharp
# Intervalo de coleta (minutos)
COLLECT_INTERVAL = 15
# Banco de dados
DB_URL = "sqlite:///bot_data.db"Passo 2: Coletor de odds
# collectors/odds_collector.py
import requests
from datetime import datetime
from config import ODDS_API_KEY
BASE_URL = "https://api.the-odds-api.com/v4/sports"
class OddsCollector:
def __init__(self):
self.remaining_requests = None
def collect(self, sport: str) -> list[dict]:
"""Coleta odds de todas as casas pra um esporte."""
url = f"{BASE_URL}/{sport}/odds"
params = {
"apiKey": ODDS_API_KEY,
"regions": "us,eu,uk,au",
"markets": "h2h,spreads,totals",
"oddsFormat": "decimal",
"dateFormat": "iso",
}
response = requests.get(url, params=params, timeout=30)
response.raise_for_status()
self.remaining_requests = response.headers.get("x-requests-remaining")
print(f"[{datetime.now():%H:%M:%S}] {sport}: {len(response.json())} eventos | Requests sobrando: {self.remaining_requests}")
return response.json()
def collect_all(self, sports: list[str]) -> dict[str, list]:
"""Coleta odds de todos os esportes configurados."""
all_events = {}
for sport in sports:
try:
all_events[sport] = self.collect(sport)
except requests.RequestException as e:
print(f"Erro coletando {sport}: {e}")
all_events[sport] = []
return all_eventsPasso 3: Analisador de valor
# analyzers/value_analyzer.py
from dataclasses import dataclass
from config import SHARP_BOOKMAKER, MIN_EV_PERCENT, KELLY_FRACTION, BANKROLL
@dataclass
class ValueBet:
sport: str
home_team: str
away_team: str
market: str
outcome: str
bookmaker: str
odds: float
sharp_odds: float
fair_prob: float
ev_percent: float
kelly_stake: float
recommended_bet: float
commence_time: str
class ValueAnalyzer:
def analyze(self, events: dict[str, list]) -> list[ValueBet]:
"""Analisa todos os eventos e retorna value bets encontradas."""
value_bets = []
for sport, sport_events in events.items():
for event in sport_events:
vbs = self._analyze_event(event, sport)
value_bets.extend(vbs)
# Ordena por EV decrescente
value_bets.sort(key=lambda x: x.ev_percent, reverse=True)
return value_bets
def _analyze_event(self, event: dict, sport: str) -> list[ValueBet]:
"""Analisa um evento individual."""
results = []
bookmakers = event.get("bookmakers", [])
# Encontra odds da sharp
sharp = next((b for b in bookmakers if b["title"] == SHARP_BOOKMAKER), None)
if not sharp:
return results
# Pra cada mercado da sharp
for sharp_market in sharp.get("markets", []):
market_key = sharp_market["key"]
# Calcula probabilidades justas removendo a margem
sharp_outcomes = {o["name"]: o["price"] for o in sharp_market["outcomes"]}
margin = sum(1 / odds for odds in sharp_outcomes.values())
fair_probs = {name: (1 / odds) / margin for name, odds in sharp_outcomes.items()}
# Compara com odds de outras casas
for bookmaker in bookmakers:
if bookmaker["title"] == SHARP_BOOKMAKER:
continue
for bk_market in bookmaker.get("markets", []):
if bk_market["key"] != market_key:
continue
for outcome in bk_market["outcomes"]:
name = outcome["name"]
odds = outcome["price"]
if name not in fair_probs:
continue
fair_prob = fair_probs[name]
implied_prob = 1 / odds
# EV = (fair_prob * (odds - 1)) - (1 - fair_prob)
ev = (fair_prob * (odds - 1)) - (1 - fair_prob)
ev_percent = ev * 100
if ev_percent >= MIN_EV_PERCENT:
# Kelly Criterion
b = odds - 1
kelly = ((b * fair_prob) - (1 - fair_prob)) / b
kelly_adjusted = kelly * KELLY_FRACTION
rec_bet = round(BANKROLL * max(kelly_adjusted, 0), 2)
results.append(ValueBet(
sport=sport,
home_team=event["home_team"],
away_team=event["away_team"],
market=market_key,
outcome=name,
bookmaker=bookmaker["title"],
odds=odds,
sharp_odds=sharp_outcomes.get(name, 0),
fair_prob=round(fair_prob, 4),
ev_percent=round(ev_percent, 2),
kelly_stake=round(kelly_adjusted * 100, 2),
recommended_bet=rec_bet,
commence_time=event["commence_time"],
))
return resultsPasso 4: Notificador Telegram
# notifiers/telegram_bot.py
import requests
from config import TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID
from analyzers.value_analyzer import ValueBet
class TelegramNotifier:
def __init__(self):
self.base_url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}"
def send_value_bet(self, vb: ValueBet):
"""Envia alerta de value bet formatado."""
message = (
f"🎯 VALUE BET ENCONTRADA\n"
f"\n"
f"⚽ {vb.home_team} vs {vb.away_team}\n"
f"📊 Mercado: {vb.market} | {vb.outcome}\n"
f"🏢 Casa: {vb.bookmaker}\n"
f"💰 Odd: {vb.odds} (sharp: {vb.sharp_odds})\n"
f"📈 EV: +{vb.ev_percent}%\n"
f"🎲 Prob. justa: {vb.fair_prob * 100:.1f}%\n"
f"💵 Aposta sugerida: R${vb.recommended_bet:.2f} ({vb.kelly_stake:.1f}% Kelly)\n"
f"⏰ Início: {vb.commence_time}\n"
)
self._send(message)
def send_summary(self, count: int, total_ev: float):
"""Envia resumo do scan."""
if count == 0:
return
message = f"📋 Scan completo: {count} value bet(s) encontrada(s) | EV médio: +{total_ev/count:.1f}%"
self._send(message)
def _send(self, text: str):
"""Envia mensagem via Telegram API."""
url = f"{self.base_url}/sendMessage"
payload = {
"chat_id": TELEGRAM_CHAT_ID,
"text": text,
"parse_mode": "HTML",
}
try:
response = requests.post(url, json=payload, timeout=10)
response.raise_for_status()
except requests.RequestException as e:
print(f"Erro enviando Telegram: {e}")Passo 5: Orquestrador principal
# main.py
import schedule
import time
from config import SPORTS, COLLECT_INTERVAL
from collectors.odds_collector import OddsCollector
from analyzers.value_analyzer import ValueAnalyzer
from notifiers.telegram_bot import TelegramNotifier
collector = OddsCollector()
analyzer = ValueAnalyzer()
notifier = TelegramNotifier()
def run_scan():
"""Pipeline principal: coleta -> analisa -> notifica."""
print("\n" + "="*50)
print("Iniciando scan...")
# 1. Coleta odds
events = collector.collect_all(SPORTS)
total_events = sum(len(e) for e in events.values())
print(f"Coletados {total_events} eventos de {len(SPORTS)} esportes")
# 2. Analisa valor
value_bets = analyzer.analyze(events)
print(f"Encontradas {len(value_bets)} value bets")
# 3. Notifica
for vb in value_bets:
notifier.send_value_bet(vb)
print(f" -> {vb.home_team} vs {vb.away_team} | {vb.bookmaker} | EV: +{vb.ev_percent}%")
total_ev = sum(vb.ev_percent for vb in value_bets)
notifier.send_summary(len(value_bets), total_ev)
print("Scan completo.")
print("="*50)
if __name__ == "__main__":
print(f"Bot iniciado. Escaneando a cada {COLLECT_INTERVAL} minutos.")
print(f"Esportes: {', '.join(SPORTS)}")
print(f"Ctrl+C pra parar.\n")
# Roda imediatamente
run_scan()
# Agenda execucoes futuras
schedule.every(COLLECT_INTERVAL).minutes.do(run_scan)
while True:
schedule.run_pending()
time.sleep(30)Adicionando IA para predições (XGBoost vs redes neurais)
Com o bot básico rodando, dá pra adicionar uma camada de predição com machine learning. A ideia: em vez de usar só as odds da Pinnacle como referência de probabilidade justa, você treina um modelo com dados históricos e usa as predições dele como fonte adicional.
Eu já testei XGBoost e redes neurais pra prever resultados de futebol. Minha conclusão: XGBoost ganha disparado em termos de praticidade. É mais rápido de treinar, mais fácil de interpretar, e os resultados são comparáveis com redes neurais pra esse tipo de dado tabular.
# analyzers/predictor.py
import pandas as pd
import xgboost as xgb
from sklearn.model_selection import train_test_split
from sklearn.metrics import log_loss, accuracy_score
import joblib
import os
class MatchPredictor:
def __init__(self, model_path: str = "models/xgb_model.json"):
self.model_path = model_path
self.model = None
self._load_model()
def _load_model(self):
"""Carrega modelo treinado se existir."""
if os.path.exists(self.model_path):
self.model = xgb.XGBClassifier()
self.model.load_model(self.model_path)
print("Modelo carregado com sucesso")
def train(self, data_path: str):
"""Treina modelo com dados historicos."""
df = pd.read_csv(data_path)
# Features: stats dos ultimos 5 jogos de cada time
feature_cols = [
"home_goals_avg_5", "home_goals_conceded_avg_5",
"home_shots_avg_5", "home_possession_avg_5",
"away_goals_avg_5", "away_goals_conceded_avg_5",
"away_shots_avg_5", "away_possession_avg_5",
"home_win_rate_5", "away_win_rate_5",
"h2h_home_wins_5", "h2h_draws_5",
"odds_home", "odds_draw", "odds_away",
]
X = df[feature_cols]
y = df["result"] # 0 = home, 1 = draw, 2 = away
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
self.model = xgb.XGBClassifier(
n_estimators=500,
max_depth=6,
learning_rate=0.05,
subsample=0.8,
colsample_bytree=0.8,
objective="multi:softprob",
num_class=3,
eval_metric="mlogloss",
)
self.model.fit(
X_train, y_train,
eval_set=[(X_test, y_test)],
verbose=50,
)
# Avaliar
y_pred = self.model.predict(X_test)
y_prob = self.model.predict_proba(X_test)
print(f"Accuracy: {accuracy_score(y_test, y_pred):.3f}")
print(f"Log Loss: {log_loss(y_test, y_prob):.3f}")
# Salvar
self.model.save_model(self.model_path)
print(f"Modelo salvo em {self.model_path}")
def predict(self, features: dict) -> dict[str, float]:
"""Retorna probabilidades previstas pro jogo."""
if self.model is None:
raise ValueError("Modelo nao treinado. Rode train() primeiro.")
df = pd.DataFrame([features])
probs = self.model.predict_proba(df)[0]
return {
"home_win": round(float(probs[0]), 4),
"draw": round(float(probs[1]), 4),
"away_win": round(float(probs[2]), 4),
}XGBoost vs Redes Neurais pra apostas
XGBoost: treinamento em minutos, interpretável (feature importance), funciona bem com poucos dados (1000+ jogos). Recomendado pra começar.
Redes Neurais: precisam de muito mais dados (10.000+ jogos), treinamento lento, difícil de interpretar. Podem capturar padrões mais complexos em teoria, mas na prática raramente superam XGBoost em dados tabulares de esportes.
Comece com XGBoost. Só migre pra deep learning se tiver uma razão específica e dados suficientes.
De onde tirar dados históricos
Pra treinar o modelo, você precisa de dados históricos de jogos com estatísticas. As melhores fontes gratuitas são football-data.co.uk (dados tabulares de ligas europeias desde 1993), FBref (stats avançadas), e a API do API-Football (plano free com 100 requests/dia).
Uma dica: seu modelo só vai ser tão bom quanto os dados que você alimenta. Features como média de gols nos últimos 5 jogos, posse de bola, chutes a gol, e forma recente são bons pontos de partida. Mas não coloque features demais — overfitting é o inimigo.
Deploy 24/7 com Railway ou VPS
O bot precisa rodar o tempo todo pra não perder oportunidades. Tem duas opções boas pra deploy: Railway (mais fácil) e VPS (mais controle).
Railway
PaaS que roda containers Docker automaticamente
+ Prós
- • Deploy em 2 minutos via GitHub
- • Plano gratuito com 500h/mês
- • Logs em tempo real no dashboard
- • Variáveis de ambiente fáceis de configurar
− Contras
- • Plano free pode não ser suficiente (500h = ~20 dias)
- • Menos controle sobre o ambiente
- • Preço sobe rápido se precisar de mais recursos
VPS (Hetzner, DigitalOcean)
Servidor virtual completo que você gerencia
+ Prós
- • Controle total do sistema operacional
- • Roda 24/7 sem limitação de horas
- • VPS básica a partir de $4/mês
- • Pode rodar múltiplos bots no mesmo servidor
− Contras
- • Precisa saber Linux básico
- • Responsável pela manutenção e segurança
- • Setup inicial mais demorado
Deploy no Railway
O jeito mais rápido: cria um Procfile na raiz do projeto com worker: python main.py, faz push pro GitHub, conecta o repo no Railway e pronto. As variáveis de ambiente você configura pelo dashboard do Railway. Em menos de 5 minutos tá rodando.
# Procfile
worker: python main.py
# requirements.txt
requests==2.32.3
python-dotenv==1.0.1
schedule==1.2.2
xgboost==2.1.3
pandas==2.2.3
scikit-learn==1.6.1
joblib==1.4.2
sqlalchemy==2.0.36Deploy numa VPS
Se prefere mais controle, uma VPS da Hetzner (CX22, 4 euros/mês) dá conta fácil. Conecta via SSH, clona o repo, instala as dependências e roda com systemd ou screen pra manter o processo vivo.
# Na VPS (Ubuntu 22.04+)
sudo apt update && sudo apt install python3-pip python3-venv -y
git clone https://github.com/seu-usuario/betting-bot.git
cd betting-bot
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
# Rodar em background com screen
screen -S bot
python main.py
# Ctrl+A, D pra desatachar
# screen -r bot pra reconectarTestando e iterando
Antes de confiar no bot, rode em modo 'paper trading' por pelo menos 2 semanas. Registre todas as value bets que ele encontra mas não aposte de verdade. Depois confira: as odds realmente caíram? O CLV foi positivo? Se sim, o bot tá funcionando.
Checklist pra colocar o bot no ar
- The Odds API key configurada no .env
- Bot do Telegram criado via @BotFather e token salvo
- Chat ID do Telegram obtido (manda mensagem pro bot e checa via API)
- Todos os módulos testados localmente
- Pipeline completo rodando sem erros por pelo menos 1 hora
- Deploy feito no Railway ou VPS
- 2 semanas de paper trading antes de apostar de verdade
- Planilha de controle criada pra registrar resultados
Evoluções futuras
O bot básico que a gente construiu aqui é o ponto de partida. Com ele rodando, você pode ir adicionando camadas: integrar o modelo de ML pra ter uma segunda opinião sobre probabilidades, adicionar mais mercados (over/under, handicap asiático), cobrir mais esportes, e criar um dashboard web com Streamlit pra visualizar o histórico.
O mais importante é começar simples. Bot funcionando, alertas chegando, e você tomando decisões com dados em vez de achismo. A complexidade você adiciona com o tempo, conforme sentir necessidade.
Se curtiu esse tutorial, dá uma olhada no artigo sobre web scraping de odds — ele complementa perfeitamente o que a gente fez aqui, mostrando como coletar dados de fontes que a API não cobre.