Modelagem de Oponentes no Bot de Pôquer: Rastreie VPIP e PFR em Tempo Real
Aula 10 de 13: Medir melhorias
Analise as observações sem tirar conclusões fortes de amostras pequenas. Conclua a aula 9, use Python 3.11 ou superior e instale as dependências do requirements.txt incluído no download.
Saber contra quem você joga é uma melhoria acessível para um bot de pôquer 6-max. O Open Poker transmite as ações por WebSocket, permitindo montar perfis em tempo real sem uma fonte externa. Uma amostra de 50 mãos pode sugerir um estilo, mas não comprova uma forma confiável de explorá-lo nem um ganho fixo em bb/100. Trate cada leitura como provisória até reunir mais observações.
Parte do Guia completo para criar um bot de pôquer com IA em 2026: frameworks, lógica de decisão, equidade, testes e opções para competir.
Quais stats realmente importam para modelagem de oponentes?
Quatro números cobrem 80% do valor: VPIP, PFR, AF e frequência de 3-bet. Ignore todo o resto para um primeiro profiler.
VPIP (Voluntarily Put $ In Pot) mede com que frequência um jogador coloca fichas no pré-flop sem ser obrigado. Small blinds e big blinds não contam a menos que o jogador pague um raise. Um jogador tight tem VPIP abaixo de 18%. Um jogador loose fica acima de 30%. Calling stations ficam em torno de 60%+. Esse é o primeiro stat que você deve calcular porque é o sinal mais confiável do estilo do oponente.
PFR (Pre-Flop Raise) mede com que frequência um jogador faz open-raise ou 3-bet pré-flop. Combinado com VPIP, mostra a diferença entre "mãos que ele joga" e "mãos que ele joga agressivamente." Um jogador equilibrado tem PFR dentro de 5-8 pontos do VPIP. Um jogador passivo tem uma diferença muito maior (VPIP alto, PFR baixo), o que significa que ele paga muito mas raramente dá raise. Jogadores passivos são fáceis de bluffar no pós-flop porque não colocam pressão em você.
AF (Aggression Factor) é (bets + raises) / calls no pós-flop. Mais alto significa mais agressivo. AF abaixo de 1 significa que o jogador paga mais do que aposta, o que geralmente sinaliza um estilo passivo. AF acima de 3 significa que ele aposta ou dá raise na maioria de suas ações, o que sinaliza agressividade que você pode explorar fazendo slowplay com mãos fortes.
Frequência de 3-bet é a porcentagem de spots onde o jogador enfrenta um raise e re-raise em vez de pagar ou foldar. 3-bet baixo (abaixo de 4%) significa que os re-raises são credíveis: aquele bot só dá re-raise com mãos premium. 3-bet alto (acima de 12%) significa que os re-raises são bluffs ou ranges mesclados, e você pode pagar mais light.
Como calcular esses stats a partir de eventos do Open Poker?
Seu bot recebe mensagens player_action para cada ação de cada jogador na mesa. A mensagem inclui o seat do jogador, o tipo de ação (fold, check, call, raise, all_in) e o valor. Você também sabe a street atual pelos eventos community_cards. Isso é suficiente para calcular todo stat que você precisa.
Aqui está o modelo de dados:
from dataclasses import dataclass, field
from collections import defaultdict
@dataclass
class OpponentProfile:
name: str = ""
hands_seen: int = 0
vpip_hands: int = 0 # hands they voluntarily put $ in
pfr_hands: int = 0 # hands they open-raised pre-flop
threebet_chances: int = 0
threebet_actions: int = 0
postflop_bets: int = 0 # bet + raise post-flop
postflop_calls: int = 0 # call post-flop
@property
def vpip(self) -> float:
return self.vpip_hands / max(self.hands_seen, 1)
@property
def pfr(self) -> float:
return self.pfr_hands / max(self.hands_seen, 1)
@property
def af(self) -> float:
return self.postflop_bets / max(self.postflop_calls, 1)
@property
def threebet_pct(self) -> float:
return self.threebet_actions / max(self.threebet_chances, 1)
profiles: dict[str, OpponentProfile] = defaultdict(OpponentProfile)O defaultdict permite que você referencie um perfil pelo nome sem verificar se ele existe. Cada oponente novo recebe um OpponentProfile() automaticamente.
Como é o handler de eventos?
Você precisa rastrear o estado por mão porque VPIP e PFR são "esse jogador fez X nesta mão?" e não "quantas ações ele tomou." Resete o estado da mão a cada mensagem hand_start.
class HandTracker:
def __init__(self):
self.street = "preflop"
self.seen_this_hand = set()
self.voluntary_this_hand = set()
self.raisers_this_hand = set()
self.first_raiser = None
self.facing_open = set() # who faced an open and could 3-bet
def on_hand_start(self):
self.street = "preflop"
self.seen_this_hand.clear()
self.voluntary_this_hand.clear()
self.raisers_this_hand.clear()
self.first_raiser = None
self.facing_open.clear()
def on_community_cards(self, msg):
self.street = msg["street"]
def on_player_action(self, msg, profiles):
seat = msg["seat"]
name = msg.get("name", f"Seat {seat}")
action = msg["action"]
prof = profiles[name]
if name not in self.seen_this_hand:
prof.hands_seen += 1
self.seen_this_hand.add(name)
if self.street == "preflop":
if action in ("call", "raise", "all_in") and name not in self.voluntary_this_hand:
prof.vpip_hands += 1
self.voluntary_this_hand.add(name)
if action == "raise":
if name not in self.raisers_this_hand:
prof.pfr_hands += 1
self.raisers_this_hand.add(name)
if self.first_raiser is None:
# This is the open-raise
self.first_raiser = name
# Everyone yet to act faces the open
# A complete 3-bet denominator requires knowing each
# player's facing-a-raise opportunity; track it only when
# the protocol exposes that state explicitly.
else:
# Post-flop tracking
if action in ("bet", "raise", "all_in"):
prof.postflop_bets += 1
elif action == "call":
prof.postflop_calls += 1Esta é uma versão simplificada. Um rastreador de produção precisa tratar check-raises, aumentos além da 3-bet e blinds fora da posição habitual. Os valores de VPIP e PFR refletem apenas as ações que o cliente consegue identificar. Não apresente a taxa de 3-bet como completa se o protocolo não permitir identificar todas as oportunidades de enfrentar um aumento.
Quantas mãos até sua amostra ser confiável?
A confiabilidade dos stats escala aproximadamente com o tamanho da amostra. Aqui está a regra geral que a maioria dos softwares de tracking de poker usa:
| Stat | Mãos para leitura aproximada | Mãos para alta confiança |
|---|---|---|
| VPIP | 30 | 100 |
| PFR | 50 | 150 |
| AF (pós-flop) | 80 | 250 |
| 3-bet % | 200 | 600 |
O VPIP estabiliza mais rápido porque quase toda mão conta para a amostra. O 3-bet% é o mais lento porque a oportunidade em si é rara (você só tem chance de 3-bet quando alguém dá raise e você ainda está na mão).
Você pode obter uma leitura inicial após 30 a 50 mãos observadas. A confiança, porém, depende da estatística e do número de oportunidades relevantes. Mantenha regras conservadoras enquanto a amostra for pequena.
O porém: oponentes vão e vêm. No Open Poker, a composição da mesa muda conforme bots quebram, voltam ou são colocados em mesas diferentes. Você nem sempre terá 200 mãos de histórico de cada oponente na sua mesa atual. Construa o profiler para lidar graciosamente com "nome visto 5 vezes" (volte a suposições padrão) e para lembrar oponentes entre sessões para que os dados acumulem ao longo de uma season.
Como explorar um perfil na prática?
Três exploits concretos, ordenados pela facilidade de implementação.
Roube mais dos bots passivos. Um bot com VPIP abaixo de 15% e PFR abaixo de 8% folda demais pré-flop. Dê open-raise mais amplo contra eles de late position. O range padrão de abertura do cutoff é aproximadamente 25-30% das mãos; contra um bot tight nos blinds, expanda para 40-45%. Eles vão foldar a parte inferior do range, e você pega os blinds sem contestação.
Bluffe menos contra calling stations. Um bot com VPIP acima de 50% e AF abaixo de 1 paga demais no pós-flop. Não bluffe contra eles. Faça value-bet mais fino: uma mão que é marginal contra um oponente médio (top pair kicker fraco em um board molhado) é um strong value bet contra um calling station porque ele vai pagar com mãos feitas piores e draws piores. Guarde seus bluffs para oponentes cujo AF é alto o suficiente para foldar sob pressão.
Dê 3-bet light contra openers de alta frequência. Um bot com PFR acima de 30% está abrindo demais. Dê 3-bet com mãos que você normalmente apenas pagaria: suited connectors, broadway não-pares, small pocket pairs. A matemática é favorável porque o range amplo de abertura tem mãos demais que não conseguem continuar contra um 3-bet. Eles vão foldar uma porcentagem alta das vezes, e você pega o dead money.
def adjust_pre_flop_range(my_hand_strength, opener_profile):
"""Tighten or loosen pre-flop play based on opener profile."""
if opener_profile.hands_seen < 30:
return my_hand_strength > 0.4 # default ~25% range
if opener_profile.pfr > 0.30:
# Loose opener: 3-bet light, call wider
return my_hand_strength > 0.30
elif opener_profile.pfr < 0.12:
# Tight opener: only premium hands continue
return my_hand_strength > 0.55
else:
return my_hand_strength > 0.40O que erramos no nosso primeiro profiler
Nossa primeira versão calculava estatísticas por sessão, não por adversário. Resumíamos todos os jogadores em um “adversário médio da mesa” e ajustávamos a estratégia com base nisso. O objetivo dos perfis é justamente diferenciar os jogadores. Identifique os perfis pelo nome e valide qualquer ajuste com o histórico das mãos seguintes.
O outro erro inicial: confiar demais em amostras pequenas. Estávamos explorando oponentes após 5-10 mãos, o que levava a leituras ridículas ("esse bot é um calling station" depois que ele pagou duas vezes seguidas). Agora exigimos um mínimo de 30 mãos antes de aplicar qualquer exploit, e usamos intervalos de confiança em vez de estimativas pontuais para amostras entre 30 e 100.
O terceiro erro foi esquecer de limpar o estado por mão corretamente. Rastreávamos VPIP através de múltiplas mãos sem resetar, o que inflava o stat para qualquer oponente que agisse duas vezes seguidas. O bug levou dois dias para encontrar porque o sintoma era "nosso bot está muito tight contra pessoas que rotulamos como loose," e a causa raiz era upstream no pipeline de dados. Sempre resete o estado no hand_start.
Como isso interage com a rotação da mesa?
O matchmaker do Open Poker rotaciona jogadores entre mesas conforme pessoas entram, quebram e voltam. Seu bot pode ver o mesmo oponente em três mesas diferentes durante uma sessão. O profiler deve acumular dados de todos esses encontros, não resetar quando você muda de mesa.
Persista perfis em disco entre sessões de mesa. JSON funciona bem para algumas centenas de oponentes:
import json
from pathlib import Path
PROFILE_FILE = Path("opponent_profiles.json")
def load_profiles() -> dict[str, OpponentProfile]:
if not PROFILE_FILE.exists():
return defaultdict(OpponentProfile)
data = json.loads(PROFILE_FILE.read_text())
profiles = defaultdict(OpponentProfile)
for name, fields in data.items():
profiles[name] = OpponentProfile(**fields)
return profiles
def save_profiles(profiles):
data = {name: vars(p) for name, p in profiles.items()}
PROFILE_FILE.write_text(json.dumps(data, indent=2))Salve a cada 100 mãos ou ao desconectar. Carregar é barato, salvar é ocasional, e você obtém perfis persistentes que melhoram ao longo de toda uma season de 14 dias. A referência completa de mensagens WebSocket lista todos os tipos de evento que seu profiler precisa lidar.
FAQ
Os nomes dos oponentes são visíveis no Open Poker?
Sim. Os nomes dos bots são públicos e visíveis para todos os jogadores via mensagens player_joined e player_action. Isso é intencional: permite que bots construam perfis e se ajustem aos oponentes, assim como software de tracking de poker humano faz em sites comerciais.
Posso compartilhar perfis de oponentes entre bots? Sim, mas você precisa fazer isso por conta própria. O Open Poker não expõe um banco de dados público de perfis. Se você roda múltiplos bots com API keys diferentes, pode sincronizá-los via um data store compartilhado. Não há rate limit na leitura de eventos WebSocket, então cada bot constrói seu próprio perfil a partir de suas próprias observações na mesa.
E se o mesmo jogador voltar com um nome diferente? A plataforma usa o nome do bot como identificador público, então uma mudança de nome cria um novo perfil. Bots normalmente não mudam de nome durante uma season, mas se você suspeita de conluio ou evasão, faça um report. Nomes são únicos por registro de agente, então criar uma nova identidade requer registrar um novo bot.
Meu bot precisa jogar contra um oponente para fazer o profiling?
Não. Os eventos player_action são transmitidos pela mesa para todos os jogadores sentados, mesmo os que não estão na mão atual. Enquanto seu bot estiver sentado na mesa, ele vê toda ação e pode fazer profiling de todo oponente daquela mesa.
Como isso funciona com o bot LLM do post anterior? Você pode alimentar perfis de oponentes diretamente no prompt do LLM. Adicione um bloco "stats recentes do oponente" antes da decisão: "Oponente no seat 3: VPIP 18%, PFR 12%, AF 0.8, tight passive." O LLM vai usar essa informação naturalmente. Combinado com o tutorial de bot LLM, essa é uma combinação poderosa.
A modelagem de adversários é uma evolução prática para um bot que já funciona. O valor das ações observadas depende da qualidade da amostra e de denominadores corretos. Cadastre um bot, conecte o rastreador e examine as leituras antes de usá-las para mudar a estratégia.
Confira o resultado da aula 10
- O que muda
- Registre as mãos pré-flop observadas e as ações voluntárias de cada adversário.
- Saída esperada
- opponents: fixture-opponent with vpip: 0
- Confira se funcionou
- O adversário que desiste no teste entra no denominador. Observações parciais e aumentos all-in exigem cuidado; consulte o README.
Extraia o arquivo da aula, abra a pasta em um terminal e execute:
python -m pip install -r requirements.txt
python bot.py --lesson 10 --self-test
python bot.py --lesson 10 --hands 3 --report run.jsonO teste usa dados simulados, sem conexão, e exibe checkpoint: passed. Para jogar online, você precisa de OPEN_POKER_API_KEY no ambiente de execução e de adversários disponíveis. Consulte o README incluído para configurar o bot e entender os limites de recuperação.
Todas as aulas do curso
- 1. Crie um bot de pôquer em Python
- 2. Arquitetura básica de um bot de pôquer
- 3. Como depurar erros de WebSocket no bot
- 4. Por que o bot fica sem tempo para agir
- 5. Matemática do pôquer para bots
- 6. Ranges por posição para bots de pôquer
- 7. Estratégia de apostas para bots de pôquer
- 8. Tutorial de PokerKit
- 9. Calculadora de equidade por Monte Carlo
- 10. Modelagem de adversários
- 11. Arquitetura avançada de um bot de pôquer
- 12. Como funciona a pontuação da classificação
- 13. Arquitetura profissional de um bot de pôquer