Bot de Arbitragem em Python: Tutorial Completo

James Whitfield
Arbitragem em apostas significa cobrir todos os resultados de um evento em casas de apostas diferentes cujas odds discordam o suficiente para você lucrar seja qual for o desfecho. Encontrar essas janelas na mão é impossível. Elas aparecem e somem em minutos. Então vamos construir um bot que as encontra, calcula os stakes e avisa você, em cerca de 100 linhas de Python.
Se você quer os fundamentos primeiro, leia nosso tutorial de scanner de arbitragem. Aquele post cobre a teoria e um script de escanear e imprimir em profundidade. Este aqui é o ângulo da automação: um bot de longa duração com deduplicação, divisão de stakes e alertas.
O que você precisa
- Uma chave de API gratuita da Odds-API.io (100 requisições/hora, sem cartão de crédito)
- Python 3.10+ e a biblioteca requests
- Contas em duas ou mais casas de apostas, como Betano e Bet365, se você pretende realmente apostar
pip install requestsA matemática da arbitragem de dois resultados
Para um mercado de dois resultados (vencedor de partida de tênis, handicap asiático, totais), some o inverso das melhores odds disponíveis de cada lado:
margin = 1/odds_a + 1/odds_bSe a soma fica abaixo de 1.0, existe uma arbitragem. Digamos que a Casa A tem o mandante a 2.15 e a Casa B tem o visitante a 2.10:
1/2.15 + 1/2.10 = 0.4651 + 0.4762 = 0.9413Isso é uma vantagem de 5,87% antes de limites e taxas. Para travar o lucro, você divide o stake total na proporção da probabilidade implícita de cada lado. Com R$ 100, ficaria assim:
def split_stakes(total, odds_a, odds_b):
inv_a, inv_b = 1 / odds_a, 1 / odds_b
s = inv_a + inv_b
stake_a = total * inv_a / s
stake_b = total * inv_b / s
payout = stake_a * odds_a # equal on both sides
return round(stake_a, 2), round(stake_b, 2), round(payout - total, 2)
print(split_stakes(100, 2.15, 2.10))
# (49.41, 50.59, 6.24) -> bet 49.41 on A, 50.59 on B, ~6.24 profit either wayO caminho fácil: o endpoint /arbitrage-bets
Você poderia puxar as odds brutas de cada evento e rodar essa matemática em cada par de casas por conta própria. A Odds-API.io também faz isso no servidor. O endpoint /arbitrage-bets escaneia as casas que você especificar e retorna oportunidades ao vivo com os stakes ótimos já calculados:
import requests
API_KEY = "YOUR_API_KEY"
BASE = "https://api.odds-api.io/v3"
resp = requests.get(f"{BASE}/arbitrage-bets", params={
"apiKey": API_KEY,
"bookmakers": "Betano,Bet365,SingBet",
"includeEventDetails": "true",
"limit": 50,
})
resp.raise_for_status()
for arb in resp.json():
ev = arb.get("event", {})
print(f"{ev.get('home')} vs {ev.get('away')} | {arb['market']['name']} "
f"| margin {arb['profitMargin']:.2%}")
for leg, stake in zip(arb["legs"], arb["optimalStakes"]):
print(f" {leg['side']:>4} @ {leg['odds']} on {leg['bookmaker']} "
f"-> stake {stake['stake']}")Cada oportunidade inclui as pernas, a casa de apostas de cada lado, a margem de lucro, os stakes ótimos para uma banca de 100 unidades (escale linearmente para a sua) e links diretos para o mercado exato em cada casa.
Verificando com odds brutas
Antes de apostar dinheiro de verdade, confira os preços você mesmo. As odds se movem, e os segundos entre o escaneamento e a sua aposta importam. Puxe as odds atuais do evento com /odds:
def verify(event_id, bookmakers):
resp = requests.get(f"{BASE}/odds", params={
"apiKey": API_KEY,
"eventId": event_id,
"bookmakers": ",".join(bookmakers),
})
resp.raise_for_status()
data = resp.json()
for book, markets in data["bookmakers"].items():
for market in markets:
if market["name"] == "ML":
print(book, market["odds"], "updated", market["updatedAt"])
verify(123456, ["Betano", "Bet365"])Recalcule a margem com o que você vê. Se ela passou de 1.0, a janela fechou. Siga em frente, o bot vai achar a próxima.
Montando o bot
Aqui está o loop completo: consultar, deduplicar para você não ser alertado duas vezes pela mesma oportunidade, filtrar por margem mínima e notificar via Telegram.
import time
import requests
API_KEY = "YOUR_API_KEY"
BASE = "https://api.odds-api.io/v3"
BOOKMAKERS = "Betano,Bet365,SingBet"
MIN_MARGIN = 0.01 # ignore arbs under 1%
BANKROLL = 200.0
POLL_SECONDS = 120 # 30 polls/hour, well inside the free tier
TG_TOKEN = "YOUR_TELEGRAM_BOT_TOKEN"
TG_CHAT = "YOUR_CHAT_ID"
seen = {}
def notify(text):
requests.post(
f"https://api.telegram.org/bot{TG_TOKEN}/sendMessage",
json={"chat_id": TG_CHAT, "text": text},
)
def fetch_arbs():
resp = requests.get(f"{BASE}/arbitrage-bets", params={
"apiKey": API_KEY,
"bookmakers": BOOKMAKERS,
"includeEventDetails": "true",
})
if resp.status_code == 429:
reset = resp.headers.get("x-ratelimit-reset", "")
print("rate limited until", reset)
time.sleep(300)
return []
resp.raise_for_status()
return resp.json()
def run():
while True:
for arb in fetch_arbs():
if arb["profitMargin"] < MIN_MARGIN or arb["id"] in seen:
continue
seen[arb["id"]] = time.time()
ev = arb.get("event", {})
scale = BANKROLL / arb["totalStake"]
lines = [
f"ARB {arb['profitMargin']:.2%} | "
f"{ev.get('home')} vs {ev.get('away')} | {arb['market']['name']}"
]
for leg, stake in zip(arb["legs"], arb["optimalStakes"]):
lines.append(
f"{leg['side']}: {stake['stake'] * scale:.2f} "
f"@ {leg['odds']} ({leg['bookmaker']})"
)
notify("\n".join(lines))
# drop dedupe entries older than an hour
cutoff = time.time() - 3600
for key in [k for k, t in seen.items() if t < cutoff]:
del seen[key]
time.sleep(POLL_SECONDS)
if __name__ == "__main__":
run()Execute com:
python arb_bot.pyPolling vs WebSocket
Consultar a cada dois minutos funciona bem para arbitragens pré-jogo, que costumam durar vários minutos. Para arbitragem ao vivo as janelas fecham em segundos, e o polling perde a corrida. Os planos pagos incluem o feed WebSocket (wss://api.odds-api.io/v3/ws), que envia as mudanças de odds para você em cerca de um segundo. A lógica do bot continua a mesma, você só recalcula as margens a cada atualização recebida em vez de usar um timer.
Ressalvas realistas
- Limites de requisição. O plano gratuito é 100 requisições/hora, os pagos são 5.000/hora. Observe o header x-ratelimit-remaining e recue ao receber um 429.
- Latência mata arbitragens marginais. Uma arbitragem de 1% que levou 90 segundos para ser apostada costuma virar uma arbitragem de 0%. Verifique os preços imediatamente antes de apostar.
- Restrições de conta são o limitador real. Casas recreativas limitam ou fecham contas que pegam preços de arbitragem com frequência, e isso vale também para as casas licenciadas no Brasil. Casas sharp e exchanges toleram vencedores; leve isso em conta ao escolher quais casas escanear.
- Arredondamento de stakes. Apostar R$ 49,41 e R$ 50,59 parece exatamente arbitragem. Muitos arbitradores arredondam para valores naturais e aceitam um lucro levemente desigual.
- Uma perna pode ser anulada ou rejeitada, deixando você exposto na outra. Tenha um plano (hedge em uma exchange) antes que isso aconteça.
Próximos passos
Pegue uma chave de API gratuita, rode o bot contra odds reais e leia o tutorial do scanner para a teoria mais profunda, mais tipos de mercado e o código completo da versão focada em escaneamento. Os dois scripts compartilham a mesma matemática e se complementam bem.
