Como criar um bot de apostas esportivas
Dá pra construir um bot de apostas funcional com Python, scikit-learn e uma API de odds — e eu vou te mostrar como do zero ao deploy. Sem
Por que isso é importante
Como criar um bot de apostas esportivas. Dá pra construir um bot de apostas funcional com Python, scikit-learn e uma API de odds — e eu vou te mostrar como do zero ao deploy. Sem enrolação, só código que roda.
Galera, vou direto ao ponto: construir um bot de apostas com Python não é nenhum bicho de sete cabeças. Você precisa de uma fonte de odds, um modelo de previsão e uma lógica de decisão. Isso é tudo. A parte difícil não é o código — é ter um modelo que realmente funcione.
Aviso antes de começar: este conteúdo é puramente educacional e técnico. Não é recomendação de apostas nem aconselhamento financeiro. Apostas envolvem risco real de perda de capital.
Se você quiser entender a lógica matemática por trás de uma estratégia de IA para apostas, o artigo hub deste cluster cobre o fundamento teórico com profundidade. Aqui o foco é implementação.
O que é um bot de apostas e como funciona
Um bot de apostas é um programa que coleta odds de uma ou mais casas, compara com a probabilidade que ele próprio calcula e decide se aquela aposta tem valor positivo esperado (EV+). Se sim, ele pode executar a aposta automaticamente — ou apenas sinalizar para o usuário decidir.
O fluxo tem quatro etapas fixas: coleta de dados de odds em tempo real, cálculo da probabilidade real pelo modelo, comparação entre probabilidade do modelo e probabilidade implícita na odd, e decisão de apostar ou não com base no edge calculado. Simples assim na teoria, trabalhoso na prática.
O que diferencia um bot que funciona de um que não funciona
A qualidade do modelo de previsão é o que determina tudo
Sem edge real no modelo, o bot só vai automatizar perdas
Backtesting rigoroso antes de colocar dinheiro real é obrigatório
Gestão de banca com Kelly ou percentual fixo precisa estar no código
Stack técnico necessário
Python e as libs principais
Python 3.11+ é o ponto de partida. Para o modelo preditivo, você vai precisar de pandas para manipular dados históricos, scikit-learn para os algoritmos de ML e numpy para as contas. Para integrar com APIs, requests ou httpx resolvem. Para agendamento, schedule ou APScheduler.
# Criando o ambiente virtual e instalando as dependências
python -m venv .venv
source .venv/bin/activate # ou .venv\Scripts\activate no Windows
pip install pandas scikit-learn numpy requests schedule python-dotenv
pip install xgboost lightgbm # modelos mais potentes
pip install sqlalchemy psycopg2-binary # banco de dadosAPI de odds: The Odds API
The Odds API é a opção mais usada por devs. Tem plano gratuito com 500 requests por mês, cobre centenas de ligas e esportes, e a documentação é boa. Para um bot em produção, o plano pago a partir de US$ 79/mês dá 30.000 requests. Para ver as APIs de odds disponíveis com comparativo completo de preços e limites, tem um guia específico aqui no cluster.
Banco de dados para histórico
Você vai precisar guardar histórico de odds, resultados dos jogos e o registro de todas as apostas do bot. PostgreSQL é o que eu recomendo. SQLite serve pra desenvolvimento, mas em produção com consultas de séries temporais de odds o Postgres é muito mais rápido.
# Python 3.11+
# models.py — estrutura do banco com SQLAlchemy
from sqlalchemy import create_engine, Column, String, Float, DateTime, Boolean, Integer
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from datetime import datetime
class Base(DeclarativeBase):
pass
class OddsSnapshot(Base):
"""Snapshot de odds em determinado momento."""
__tablename__ = "odds_snapshots"
id: Mapped[int] = mapped_column(Integer, primary_key=True)
evento_id: Mapped[str] = mapped_column(String(100), index=True)
sport: Mapped[str] = mapped_column(String(50))
home_team: Mapped[str] = mapped_column(String(100))
away_team: Mapped[str] = mapped_column(String(100))
bookmaker: Mapped[str] = mapped_column(String(50))
market: Mapped[str] = mapped_column(String(50)) # h2h, totals, spreads
outcome: Mapped[str] = mapped_column(String(50)) # home, away, draw
odd: Mapped[float] = mapped_column(Float)
captured_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
class BetRecord(Base):
"""Registro de apostas do bot."""
__tablename__ = "bet_records"
id: Mapped[int] = mapped_column(Integer, primary_key=True)
evento_id: Mapped[str] = mapped_column(String(100))
bookmaker: Mapped[str] = mapped_column(String(50))
outcome: Mapped[str] = mapped_column(String(50))
odd: Mapped[float] = mapped_column(Float)
prob_modelo: Mapped[float] = mapped_column(Float)
edge: Mapped[float] = mapped_column(Float)
stake: Mapped[float] = mapped_column(Float)
resultado: Mapped[str | None] = mapped_column(String(10), nullable=True) # win/loss
lucro: Mapped[float | None] = mapped_column(Float, nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
# Criando as tabelas
engine = create_engine("postgresql://user:pass@localhost/betsbot")
Base.metadata.create_all(engine)Coletando dados de odds em tempo real
Aqui começa o código que realmente importa. O coletor vai chamar a API periodicamente, gravar os snapshots de odds no banco e identificar oportunidades. Você pode complementar isso com dados coletados via scraping — tem um tutorial completo sobre coletar dados via scraping das casas de apostas neste cluster.
# Python 3.11+
# odds_collector.py — coleta odds da The Odds API
import requests
import os
from datetime import datetime
from dotenv import load_dotenv
load_dotenv()
API_KEY = os.getenv("ODDS_API_KEY")
BASE_URL = "https://api.the-odds-api.com/v4"
def fetch_odds(
sport: str = "soccer_brazil_campeonato",
regions: str = "eu",
markets: str = "h2h",
bookmakers: str | None = None
) -> list[dict]:
"""
Busca odds atuais para o esporte e mercado especificados.
Returns:
Lista de dicts com dados dos eventos e odds
"""
params = {
"apiKey": API_KEY,
"regions": regions,
"markets": markets,
"oddsFormat": "decimal",
"dateFormat": "iso",
}
if bookmakers:
params["bookmakers"] = bookmakers
resp = requests.get(f"{BASE_URL}/sports/{sport}/odds", params=params, timeout=10)
resp.raise_for_status()
# Mostra quantos requests restam na cota
remaining = resp.headers.get("x-requests-remaining", "?")
print(f"Requests restantes na API: {remaining}")
return resp.json()
def parse_odds(raw_events: list[dict]) -> list[dict]:
"""Normaliza os dados brutos da API."""
rows = []
for event in raw_events:
for bookmaker in event.get("bookmakers", []):
for market in bookmaker.get("markets", []):
for outcome in market.get("outcomes", []):
rows.append({
"evento_id": event["id"],
"home_team": event["home_team"],
"away_team": event["away_team"],
"commence_time": event["commence_time"],
"bookmaker": bookmaker["key"],
"market": market["key"],
"outcome": outcome["name"],
"odd": outcome["price"],
"captured_at": datetime.utcnow().isoformat(),
})
return rows
# Teste rápido
if __name__ == "__main__":
raw = fetch_odds(sport="soccer_brazil_campeonato")
odds = parse_odds(raw)
print(f"{len(odds)} entradas de odds coletadas")
print(odds[0] if odds else "Sem dados")Treinando o modelo de previsão
Features relevantes
Quais features usar no modelo? Começa pelas básicas: aproveitamento dos times nos últimos 5 e 10 jogos, média de gols marcados e sofridos, histórico de confrontos diretos, posição na tabela, e se é jogo em casa ou fora. Depois você pode adicionar coisas mais avançadas como forma recente por posição no campo, descanso entre jogos e lesões em posições-chave.
Escolhendo o algoritmo
Para previsão de resultados esportivos, Random Forest e XGBoost costumam superar modelos mais simples. O Logistic Regression ainda é útil como baseline. O que muda mais os resultados não é o algoritmo — é a qualidade das features. Dá pra ter um Random Forest bem tunado que bate um XGBoost com features ruins.
# Python 3.11+
# model_trainer.py — treinando o modelo de previsão
import pandas as pd
import numpy as np
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.model_selection import TimeSeriesSplit, cross_val_score
from sklearn.preprocessing import LabelEncoder
from sklearn.metrics import log_loss, brier_score_loss
import pickle
def preparar_features(df: pd.DataFrame) -> tuple[pd.DataFrame, pd.Series]:
"""
Prepara features e target para o modelo.
Assume que df tem colunas de stats dos times.
"""
feature_cols = [
"home_win_rate_5", "away_win_rate_5", # últimos 5 jogos
"home_win_rate_10", "away_win_rate_10", # últimos 10 jogos
"home_goals_scored_avg", "home_goals_conceded_avg",
"away_goals_scored_avg", "away_goals_conceded_avg",
"h2h_home_win_rate", # histórico entre si
"home_position", "away_position", # tabela
"home_rest_days", "away_rest_days", # descanso
]
X = df[feature_cols].fillna(df[feature_cols].median())
# Resultado: 0=away, 1=draw, 2=home
le = LabelEncoder()
y = le.fit_transform(df["resultado"]) # W/D/L do time da casa
return X, pd.Series(y)
def treinar_modelo(X: pd.DataFrame, y: pd.Series) -> RandomForestClassifier:
"""
Treina e valida o modelo com TimeSeriesSplit.
IMPORTANTE: nunca usar k-fold padrão com dados temporais.
"""
modelo = RandomForestClassifier(
n_estimators=300,
max_depth=8,
min_samples_leaf=20, # evita overfitting
random_state=42,
n_jobs=-1
)
# Validação temporal — respeita a ordem cronológica
tscv = TimeSeriesSplit(n_splits=5)
scores_ll = cross_val_score(modelo, X, y, cv=tscv, scoring="neg_log_loss")
print(f"Log-loss médio: {-scores_ll.mean():.4f} (+/- {scores_ll.std():.4f})")
modelo.fit(X, y)
return modelo
def salvar_modelo(modelo, caminho: str = "models/predictor.pkl") -> None:
with open(caminho, "wb") as f:
pickle.dump(modelo, f)
print(f"Modelo salvo em {caminho}")
# Uso
# df_historico = pd.read_csv("dados/historico_campeonato.csv")
# X, y = preparar_features(df_historico)
# modelo = treinar_modelo(X, y)
# salvar_modelo(modelo)Implementando a lógica de apostas
O coração do bot é a função que decide se aposta ou não. Ela compara a probabilidade calculada pelo modelo com a probabilidade implícita na odd da casa. Se a diferença for maior que um threshold mínimo (o edge), a aposta tem valor positivo esperado.
# Python 3.11+
# bet_logic.py — lógica de decisão de apostas
import pickle
import numpy as np
from dataclasses import dataclass
@dataclass
class BetDecision:
apostar: bool
edge: float
prob_modelo: float
prob_implicita: float
stake_pct: float
stake_valor: float
motivo: str
def odd_para_probabilidade(odd: float) -> float:
"""Converte odd decimal para probabilidade implícita."""
return 1 / odd
def kelly_fraction(prob_win: float, odd: float, fraction: float = 0.25) -> float:
"""Kelly fracionário para calcular o stake."""
b = odd - 1
q = 1 - prob_win
kelly = (b * prob_win - q) / b
return max(kelly * fraction, 0)
def decidir_aposta(
features: dict,
odd: float,
banca: float,
modelo_path: str = "models/predictor.pkl",
edge_minimo: float = 0.03, # 3% de edge mínimo
kelly_frac: float = 0.25
) -> BetDecision:
"""
Decide se uma aposta deve ser feita.
Args:
features: dict com as features do jogo
odd: odd decimal oferecida pela casa
banca: banca atual em reais
edge_minimo: edge mínimo para considerar a aposta
"""
with open(modelo_path, "rb") as f:
modelo = pickle.load(f)
import pandas as pd
X = pd.DataFrame([features])
probs = modelo.predict_proba(X)[0]
# Assume que índice 2 = vitória do time da casa
prob_vitoria = probs[2]
prob_implicita = odd_para_probabilidade(odd)
edge = prob_vitoria - prob_implicita
if edge < edge_minimo:
return BetDecision(
apostar=False,
edge=edge,
prob_modelo=prob_vitoria,
prob_implicita=prob_implicita,
stake_pct=0,
stake_valor=0,
motivo=f"Edge insuficiente: {edge:.2%} < {edge_minimo:.2%}"
)
stake_pct = kelly_fraction(prob_vitoria, odd, kelly_frac)
stake_valor = banca * stake_pct
return BetDecision(
apostar=True,
edge=edge,
prob_modelo=prob_vitoria,
prob_implicita=prob_implicita,
stake_pct=stake_pct,
stake_valor=stake_valor,
motivo=f"Edge positivo: {edge:.2%}. Stake: R$ {stake_valor:.2f}"
)Deploy e monitoramento
Para produção, o setup mais simples é um VPS com Ubuntu (DigitalOcean, Linode ou Vultr), Python instalado, e o bot rodando via cron job ou com APScheduler dentro do próprio processo. Use supervisord para manter o processo vivo após crashes.
Checklist de deploy mínimo viável
- VPS com pelo menos 1GB RAM e 1 vCPU
- PostgreSQL configurado com backups automáticos
- Variáveis de ambiente em .env, nunca hardcoded no código
- Logging estruturado com loguru ou logging padrão
- Alerta por Telegram ou email quando o bot detecta uma aposta ou encontra erro
- Supervisord para reiniciar o processo automaticamente
- Dashboard simples com métricas de ROI e número de apostas
Resultados e backtesting
Backtesting é o que separa o bot sério da brincadeira. A regra de ouro: nunca use dados que o modelo viu no treino para medir performance. Sempre reserve pelo menos 20% do histórico para teste fora da amostra. E use TimeSeriesSplit — nunca k-fold comum, que vaza dados do futuro pro passado.
Métricas que importam no backtesting: ROI total, ROI por mês, número de apostas, drawdown máximo, yield médio por aposta e gráfico de evolução da banca. Se o drawdown passou de 30% em algum período do histórico, o modelo vai sofrer mais em produção — variância real é sempre pior que backtesting.
Falácia do backtesting perfeito
Um backtesting com ROI de 20% ao mês é quase certamente overfitting.
Backtesting bom de verdade mostra períodos longos de drawdown e recuperação.
Quanto mais simples o modelo, menos chance de overfitting — mais confiável o backtest.
Performance real costuma ser 30-50% pior que o backtesting no mesmo período.