Como Criar um Bot de Apostas com IA (Tutorial
Tutorial passo a passo para criar um bot de apostas esportivas com Python e machine learning. Do zero ao deploy.
Por que isso é importante
Como Criar um Bot de Apostas com IA (Tutorial. Tutorial passo a passo para criar um bot de apostas esportivas com Python e machine learning. Do zero ao deploy.
Vou te ensinar a construir um bot de apostas funcional do zero. Mas primeiro, uma conversa honesta: esse tutorial é pra quem quer aprender engenharia de software aplicada a um domínio interessante. Se o objetivo é ficar rico, leia o artigo sobre IA em apostas esportivas antes de continuar. O código funciona. Lucro consistente é outra conversa.
Aviso importante antes de começar
Apostas esportivas envolvem risco real de perda financeira. O bot que você vai construir aqui pode — e provavelmente vai — perder dinheiro real se usado com capital real sem período adequado de teste. Nunca use dinheiro que você não pode perder integralmente. Este tutorial tem propósito educacional sobre Python, ML e engenharia de software.
Arquitetura do bot (coleta → análise → decisão → execução)
Um bot de apostas bem estruturado tem quatro módulos independentes que se comunicam. Manter os módulos separados é o que vai te salvar quando precisar trocar o modelo preditivo sem quebrar a coleta de dados, ou atualizar a integração com a API da casa sem mexer na lógica de gestão de banca.
- Módulo 1 - Coleta: puxa odds e estatísticas de APIs externas, armazena em banco local
- Módulo 2 - Análise: processa dados históricos e treina/usa modelo ML pra estimar probabilidades
- Módulo 3 - Decisão: compara probabilidade estimada com odd de mercado, decide se aposta
- Módulo 4 - Execução: envia a aposta via API da casa e monitora o resultado
Stack tecnológica recomendada
Python é a escolha óbvia pelo ecossistema de ML. Pra banco de dados, SQLite pra começar (sem configuração) e PostgreSQL se escalar. Pra agendamento, APScheduler ou cron simples. Pra deploy, qualquer VPS barata — um bot desse não precisa de infra cara.
Python 3.11+
Base de tudo. Pandas pra dados, scikit-learn pra ML, requests pra APIs.
SQLite / SQLAlchemy
Banco local sem configuração. SQLAlchemy pra ORM e migração fácil pra Postgres no futuro.
APScheduler
Agendador de tarefas em Python. Roda o bot de coleta a cada hora sem precisar de cron.
Loguru
Logging com esteroides. Essencial pra debugar um bot rodando em produção às 3h da manhã.
Módulo 1: Coleta de dados e odds
O primeiro módulo é o mais simples e o mais importante. Sem dados bons, nenhum modelo vai funcionar. A ideia é coletar odds periodicamente e armazenar com timestamp, pra você ter histórico de como as odds se movem antes do jogo.
# modulo_coleta.py
import sqlite3
import requests
import json
from datetime import datetime
from loguru import logger
DATABASE = 'apostas.db'
API_KEY = 'sua_chave_the_odds_api'
def inicializar_banco():
"""Cria tabelas necessárias se não existirem."""
conn = sqlite3.connect(DATABASE)
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS jogos (
id TEXT PRIMARY KEY,
esporte TEXT,
liga TEXT,
time_casa TEXT,
time_fora TEXT,
data_jogo TEXT,
resultado TEXT DEFAULT NULL
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS odds_historico (
id INTEGER PRIMARY KEY AUTOINCREMENT,
jogo_id TEXT,
casa_aposta TEXT,
mercado TEXT,
selecao TEXT,
odd REAL,
coletado_em TEXT,
FOREIGN KEY (jogo_id) REFERENCES jogos(id)
)
''')
conn.commit()
conn.close()
logger.info('Banco inicializado com sucesso')
def coletar_e_salvar_odds(esporte: str = 'soccer_brazil_campeonato'):
"""Coleta odds da API e salva no banco local."""
url = f'https://api.the-odds-api.com/v4/sports/{esporte}/odds'
params = {
'apiKey': API_KEY,
'regions': 'eu',
'markets': 'h2h',
'oddsFormat': 'decimal'
}
try:
response = requests.get(url, params=params, timeout=10)
response.raise_for_status()
jogos = response.json()
conn = sqlite3.connect(DATABASE)
cursor = conn.cursor()
agora = datetime.utcnow().isoformat()
for jogo in jogos:
# Insere ou ignora o jogo (já pode existir de coletas anteriores)
cursor.execute('''
INSERT OR IGNORE INTO jogos (id, esporte, liga, time_casa, time_fora, data_jogo)
VALUES (?, ?, ?, ?, ?, ?)
''', (jogo['id'], esporte, jogo.get('sport_title', ''),
jogo['home_team'], jogo['away_team'], jogo['commence_time']))
# Salva snapshot das odds atuais
for bookmaker in jogo.get('bookmakers', []):
for market in bookmaker.get('markets', []):
for outcome in market.get('outcomes', []):
cursor.execute('''
INSERT INTO odds_historico
(jogo_id, casa_aposta, mercado, selecao, odd, coletado_em)
VALUES (?, ?, ?, ?, ?, ?)
''', (jogo['id'], bookmaker['key'], market['key'],
outcome['name'], outcome['price'], agora))
conn.commit()
conn.close()
logger.info(f'Coletado: {len(jogos)} jogos de {esporte}')
except requests.RequestException as e:
logger.error(f'Erro na coleta: {e}')
if __name__ == '__main__':
inicializar_banco()
coletar_e_salvar_odds()Módulo 2: Modelo preditivo simples
Pra modelo preditivo, vou usar uma abordagem clássica baseada em regressão de Poisson — a mesma usada em papers acadêmicos sérios sobre previsão de futebol. A ideia é modelar o número esperado de gols de cada time e derivar probabilidades de vitória, empate e derrota.
# modulo_modelo.py
import pandas as pd
import numpy as np
from scipy.stats import poisson
from scipy.optimize import minimize
from typing import Tuple
class ModeloPoisson:
"""
Modelo de Dixon-Coles simplificado para previsão de futebol.
Baseado no paper: 'Modelling Association Football Scores' (1997).
"""
def __init__(self):
self.params = None
self.times = None
def _log_likelihood(self, params: np.ndarray, dados: pd.DataFrame,
times_casa: list, times_fora: list) -> float:
"""Função de log-verossimilhança do modelo de Poisson."""
n_times = len(self.times)
ataque = dict(zip(self.times, params[:n_times]))
defesa = dict(zip(self.times, params[n_times:2*n_times]))
home_advantage = params[-1]
log_lik = 0
for _, row in dados.iterrows():
lambda_casa = np.exp(ataque[row['time_casa']] -
defesa[row['time_fora']] + home_advantage)
lambda_fora = np.exp(ataque[row['time_fora']] -
defesa[row['time_casa']])
log_lik += (poisson.logpmf(row['gols_casa'], lambda_casa) +
poisson.logpmf(row['gols_fora'], lambda_fora))
return -log_lik # Minimizar negativo = maximizar
def treinar(self, dados: pd.DataFrame):
"""Treina modelo com histórico de jogos."""
self.times = sorted(list(
set(dados['time_casa'].unique()) | set(dados['time_fora'].unique())
))
n_times = len(self.times)
# Parâmetros iniciais
params_iniciais = np.concatenate([
np.zeros(n_times), # ataque
np.zeros(n_times), # defesa
[0.1] # home advantage
])
resultado = minimize(
self._log_likelihood,
params_iniciais,
args=(dados, self.times, self.times),
method='L-BFGS-B'
)
self.params = resultado.x
print(f'Modelo treinado. Convergiu: {resultado.success}')
def prever(self, time_casa: str, time_fora: str,
max_gols: int = 10) -> dict:
"""Retorna probabilidades de vitória, empate e derrota."""
if self.params is None:
raise ValueError('Treine o modelo antes de prever')
n_times = len(self.times)
ataque = dict(zip(self.times, self.params[:n_times]))
defesa = dict(zip(self.times, self.params[n_times:2*n_times]))
home_adv = self.params[-1]
lambda_casa = np.exp(ataque[time_casa] - defesa[time_fora] + home_adv)
lambda_fora = np.exp(ataque[time_fora] - defesa[time_casa])
# Matriz de probabilidades de placar
prob_matrix = np.outer(
[poisson.pmf(i, lambda_casa) for i in range(max_gols)],
[poisson.pmf(i, lambda_fora) for i in range(max_gols)]
)
p_casa = np.sum(np.tril(prob_matrix, -1))
p_empate = np.sum(np.diag(prob_matrix))
p_fora = np.sum(np.triu(prob_matrix, 1))
return {
'p_casa': round(p_casa, 4),
'p_empate': round(p_empate, 4),
'p_fora': round(p_fora, 4)
}Módulo 3: Gestão de banca (Kelly Criterion)
Esse módulo é o mais ignorado e o mais importante pro longo prazo. O Critério de Kelly define quanto apostar baseado na sua estimativa de probabilidade versus a odd oferecida. Apostar tudo num jogo que parece certeiro é o caminho mais rápido pra zerar a banca. Kelly evita isso matematicamente.
# modulo_banca.py
from typing import Optional
def kelly_criterion(probabilidade_estimada: float,
odd_decimal: float,
fracao_kelly: float = 0.25) -> float:
"""
Calcula o percentual da banca a apostar usando Kelly Criterion.
Args:
probabilidade_estimada: Sua estimativa de probabilidade do evento (0-1)
odd_decimal: Odd oferecida pela casa no formato decimal
fracao_kelly: Fração do Kelly pra usar (0.25 = Kelly fracionado, mais conservador)
Returns:
Percentual da banca a apostar (0 se sem valor)
"""
# b = lucro líquido por unidade apostada
b = odd_decimal - 1
p = probabilidade_estimada
q = 1 - p # probabilidade de perder
# Formula de Kelly: f* = (bp - q) / b
kelly = (b * p - q) / b
if kelly <= 0:
return 0.0 # Sem valor, não apostar
# Aplicar fração para reduzir variância
return min(kelly * fracao_kelly, 0.05) # Máximo de 5% da banca
class GestorBanca:
"""Gerencia a banca e registra apostas."""
def __init__(self, banca_inicial: float):
self.banca_atual = banca_inicial
self.banca_inicial = banca_inicial
self.apostas = []
def calcular_aposta(self, prob_estimada: float,
odd: float) -> Optional[float]:
"""Retorna valor a apostar ou None se sem valor."""
pct_kelly = kelly_criterion(prob_estimada, odd)
if pct_kelly == 0:
return None
return round(self.banca_atual * pct_kelly, 2)
def registrar_resultado(self, valor_apostado: float,
odd: float, ganhou: bool):
"""Atualiza banca após resultado."""
if ganhou:
lucro = valor_apostado * (odd - 1)
self.banca_atual += lucro
else:
self.banca_atual -= valor_apostado
self.apostas.append({
'valor': valor_apostado,
'odd': odd,
'resultado': 'W' if ganhou else 'L',
'banca_apos': self.banca_atual
})
def relatorio(self) -> dict:
"""Resumo da performance."""
total_apostas = len(self.apostas)
vitorias = sum(1 for a in self.apostas if a['resultado'] == 'W')
retorno = (self.banca_atual - self.banca_inicial) / self.banca_inicial
return {
'banca_inicial': self.banca_inicial,
'banca_atual': self.banca_atual,
'total_apostas': total_apostas,
'win_rate': f'{vitorias/total_apostas:.1%}' if total_apostas else 'N/A',
'retorno_total': f'{retorno:.1%}'
}
# Exemplo
banca = GestorBanca(1000.0)
valor = banca.calcular_aposta(prob_estimada=0.52, odd=2.10)
print(f'Valor sugerido pra apostar: R${valor}')
print(f'Representa {valor/1000:.1%} da banca')Módulo 4: Execução e monitoramento
O módulo de execução conecta tudo e, em teoria, faz as apostas automaticamente. Na prática, eu recomendo fortemente começar em modo 'paper trading' — simula as apostas sem usar dinheiro real — por pelo menos 2-3 meses antes de considerar capital real.
# main.py — Integração de todos os módulos
from modulo_coleta import coletar_e_salvar_odds
from modulo_modelo import ModeloPoisson
from modulo_banca import GestorBanca
from apscheduler.schedulers.blocking import BlockingScheduler
from loguru import logger
import sqlite3
import pandas as pd
MIN_VALUE_EDGE = 0.05 # Só aposta se probabilidade estimada > odd em 5%
PAPER_TRADING = True # Mude pra False apenas com testes extensivos
def carregar_dados_historicos() -> pd.DataFrame:
"""Carrega jogos com resultado do banco pra treinar o modelo."""
conn = sqlite3.connect('apostas.db')
df = pd.read_sql_query('''
SELECT time_casa, time_fora, gols_casa, gols_fora
FROM jogos
WHERE resultado IS NOT NULL
ORDER BY data_jogo DESC
LIMIT 500
''', conn)
conn.close()
return df
def ciclo_principal():
"""Roda um ciclo completo: coleta → análise → decisão."""
logger.info('Iniciando ciclo de análise')
# 1. Coleta odds atuais
coletar_e_salvar_odds()
# 2. Carrega e treina modelo
historico = carregar_dados_historicos()
if len(historico) < 100:
logger.warning('Dados insuficientes pra treinar modelo confiável')
return
modelo = ModeloPoisson()
modelo.treinar(historico)
# 3. Analisa jogos disponíveis
gestor = GestorBanca(banca_inicial=1000.0)
conn = sqlite3.connect('apostas.db')
# Pega jogos com odds disponíveis que ainda não aconteceram
jogos_pendentes = pd.read_sql_query('''
SELECT DISTINCT j.id, j.time_casa, j.time_fora, j.data_jogo,
o.casa_aposta, o.selecao, o.odd
FROM jogos j
JOIN odds_historico o ON j.id = o.jogo_id
WHERE j.resultado IS NULL
AND o.mercado = 'h2h'
AND o.coletado_em = (SELECT MAX(coletado_em) FROM odds_historico)
AND o.casa_aposta = 'pinnacle' -- Casa com melhores odds como referência
''', conn)
conn.close()
for _, jogo in jogos_pendentes.iterrows():
try:
prob = modelo.prever(jogo['time_casa'], jogo['time_fora'])
except KeyError:
continue # Time sem histórico suficiente
# Checa se há value na seleção disponível
if jogo['selecao'] == jogo['time_casa']:
p_estimada = prob['p_casa']
elif jogo['selecao'] == jogo['time_fora']:
p_estimada = prob['p_fora']
else:
p_estimada = prob['p_empate']
p_implicita = 1 / jogo['odd']
edge = p_estimada - p_implicita
if edge >= MIN_VALUE_EDGE:
valor = gestor.calcular_aposta(p_estimada, jogo['odd'])
if valor and valor > 0:
modo = 'SIMULADO' if PAPER_TRADING else 'REAL'
logger.info(
f'[{modo}] Value bet encontrado: '
f'{jogo["time_casa"]} vs {jogo["time_fora"]} | '
f'Seleção: {jogo["selecao"]} | '
f'Odd: {jogo["odd"]} | '
f'Prob estimada: {p_estimada:.1%} | '
f'Edge: {edge:.1%} | '
f'Apostar: R${valor:.2f}'
)
if __name__ == '__main__':
scheduler = BlockingScheduler()
scheduler.add_job(ciclo_principal, 'interval', hours=1)
logger.info('Bot iniciado. Rodando a cada hora.')
scheduler.start()Disclaimer: por que você provavelmente vai perder dinheiro mesmo assim
O código funciona. A arquitetura é sólida. O modelo de Poisson tem base acadêmica real. E ainda assim, as chances de você lucrar consistentemente com isso são baixas. Não porque o código é ruim — mas porque o mercado de apostas é eficiente o suficiente pra que o edge real seja mínimo e a variância, enorme.
Por que o bot pode não lucrar mesmo funcionando corretamente
Variância: você precisa de centenas de apostas pra validar se tem edge real
Dados: seus dados históricos são públicos — as casas também os têm
Limitação de conta: se começar a ganhar, a casa limita você
Custos: spread e margem da casa corroem qualquer edge pequeno
Overfitting: o modelo pode estar decorando o passado, não aprendendo
Use esse projeto pra aprender Python, ML, APIs, banco de dados e arquitetura de sistemas. São habilidades que valem muito no mercado de tecnologia. Encare as apostas como custo do aprendizado, não como fonte de renda esperada.