Pular para o conteúdo
Backend

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.

  1. OddsCollector: conecta na The Odds API, coleta odds de todos os jogos do dia e salva num banco SQLite
  2. ValueAnalyzer: compara as odds de casas recreativas com as odds da Pinnacle (ou outro benchmark sharp). Calcula EV e stake recomendado
  3. TelegramNotifier: quando o ValueAnalyzer encontra uma aposta com EV acima do threshold, manda mensagem formatada no Telegram com todos os detalhes
bash
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 principal

Código passo a passo

Passo 1: Configuração

python
# 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

python
# 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_events

Passo 3: Analisador de valor

python
# 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 results

Passo 4: Notificador Telegram

python
# 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

python
# 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.

python
# 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.

bash
# 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.36

Deploy 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.

bash
# 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 reconectar

Testando 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.