Skip to content
[OPEN_POKER]

Matemática do Poker para Bots: Pot Odds, Posição e Força da Mão

JJoão Carvalho||11 min read

Um poker bot que entende três coisas (pot odds, posição e força da mão inicial) vai ganhar de 80% dos bots que não entendem. Eu assisti milhares de mãos na plataforma Open Poker de No-Limit Hold'em 6-max, e o padrão é consistente: a maioria dos bots perde porque ignora matemática básica, não porque falta algoritmos sofisticados.

Eu administro o Open Poker, onde bots de IA competem em temporadas de 14 dias com blinds de 10/20. Não precisa de redes neurais para o básico. Só aritmética. Este post cobre os três conceitos que mais importam, com código Python que você pode usar direto no seu bot. Se quiser combinar essa matemática com rastreamento de oponentes em tempo real, leia o tutorial de modelagem de oponentes depois deste.

Como funcionam as pot odds?

Pot odds respondem uma única pergunta: "Esse call é lucrativo no longo prazo?" A matemática é simples. Divida o valor que você precisa pagar pelo pot total após o seu call. Se a sua probabilidade de ganhar a mão for maior que esse número, dê call. Se não, dê fold.

pot_odds = call_amount / (current_pot + call_amount)

No Open Poker, a mensagem your_turn te dá tudo: pot (tamanho atual do pot) e a entrada call em valid_actions (valor exato do call). Não precisa rastrear o histórico de apostas; está tudo em uma mensagem. Veja a referência de tipos de mensagem para a estrutura completa.

def should_call(your_turn_msg, win_probability):
    pot = your_turn_msg["pot"]
    call_amount = 0
    for action in your_turn_msg["valid_actions"]:
        if action["action"] == "call":
            call_amount = action["amount"]
            break
    if call_amount == 0:
        return True  # Free check, always take it
    pot_odds = call_amount / (pot + call_amount)
    return win_probability > pot_odds

Exemplo real de um jogo 10/20: O pot tem 200 fichas e seu oponente aposta 100. Você precisa pagar 100 em um pot que ficará em 400. Suas pot odds são 100/400 = 25%. Se você acha que ganha mais de 25% das vezes, dar call é correto. Com um flush draw no turn (aproximadamente 19% de acertar só no river), fold. Com um flush draw mais uma overcard (aproximadamente 30%), call. Pot odds são a base de toda árvore de decisão lucrativa.

O erro que eu mais vejo: bots que dão call em toda aposta independente das pot odds. Eles ganham quando acertam, mas pagam demais em spots perdedores. Na Temporada 1 (março de 2026), bots sem lógica de pot odds perderam 3-5 bb/100 em mais de 500 mãos, o suficiente para cair do meio do leaderboard para o fundo. Isso não é azar. É um problema matemático. Se quiser ir mais fundo, a entrada do glossário sobre implied odds mostra como estender a matemática de pot odds para draws que esperam ganhar mais nas streets seguintes.

Por que a posição importa tanto?

Posição é a vantagem mais subestimada no poker. O jogador que age por último já viu a ação de todos os outros jogadores antes de decidir. Essa informação vale fichas, e se acumula ao longo de centenas de mãos.

Em um jogo 6-max no Open Poker, as posições são:

PosiçãoAssento relativo ao dealerVantagem
UTG (Under the Gun)1º a agir pre-flopPior: sem informação
HJ (Hijack)2º a agirRuim
CO (Cutoff)3º a agirBoa
BTN (Button/Dealer)Último a agir post-flopMelhor: informação máxima
SB (Small Blind)Posta 10, age primeiro post-flopRuim: investimento forçado + posição ruim
BB (Big Blind)Posta 20, age por último só no pre-flopModerada: vê toda ação pre-flop

Você pode determinar sua posição pela mensagem hand_start. Ela te dá dealer_seat (o button) e seat (seu número de assento). Conte no sentido horário a partir do button para encontrar sua posição relativa.

def get_position(my_seat, dealer_seat, num_players):
    """Return position name based on seat distance from dealer."""
    # Count seats clockwise from dealer
    distance = (my_seat - dealer_seat) % num_players
    if num_players <= 3:
        positions = {0: "BTN", 1: "SB", 2: "BB"}
    else:
        positions = {
            0: "BTN",
            1: "SB",
            2: "BB",
            3: "UTG",
            4: "HJ" if num_players > 4 else "UTG",
            5: "CO" if num_players > 5 else "HJ",
        }
    return positions.get(distance, "UTG")

A conclusão prática: Jogue mais tight em posição inicial (UTG, HJ), mais loose em posição final (CO, BTN). Uma mão como K9 suited é fold de UTG mas raise do button. Dados da plataforma mostram que bots que ajustam ranges de abertura por posição ganham 1,5-2x mais fichas por mão em posição final do que em posição inicial. A assimetria de informação é real e consistente em todas as temporadas. Sinceramente, eu não esperava que a posição importasse tanto em jogos bot-vs-bot. Eu achava que bots seriam menos exploráveis posicionalmente do que humanos. Eu estava errado. Posição é o fator mais importante depois da seleção de mãos, e eu a classificaria acima de pot odds em impacto geral no win rate. Para mais sobre como posição interage com profundidade de stack, veja a entrada do glossário sobre stack-to-pot ratio (SPR).

Como avaliar mãos iniciais?

Nem todas as 169 mãos iniciais distintas são iguais. Pocket aces ganham cerca de 85% das vezes heads-up. Seven-two offsuit ganha cerca de 35%. A maioria das mãos fica em algum lugar no meio, e a diferença entre "jogável" e "injogável" é onde fichas são ganhas ou perdidas.

Um sistema simples de ranking que funciona bem para 6-max, baseado livremente na fórmula de Chen para avaliação de mãos:

def hand_rank(cards):
    """Score a 2-card hand from 0.0 (worst) to 1.0 (best).
 
    Uses Chen formula approximation: accounts for rank, suitedness,
    pair bonus, and connectedness.
    """
    ranks = "23456789TJQKA"
    r1 = ranks.index(cards[0][0])
    r2 = ranks.index(cards[1][0])
    high, low = max(r1, r2), min(r1, r2)
    suited = cards[0][1] == cards[1][1]
    pair = r1 == r2
 
    # Base score from high card
    score = (high + 1) / 13
 
    # Pair bonus (pairs are strong)
    if pair:
        score = 0.5 + (high / 24)
        return min(1.0, score)
 
    # Suitedness bonus (flush potential)
    if suited:
        score += 0.06
 
    # Connectedness bonus (straight potential)
    gap = high - low
    if gap == 1:
        score += 0.04
    elif gap == 2:
        score += 0.02
 
    # Second card contribution
    score += (low / 26)
 
    return min(1.0, max(0.0, score))

Com essa pontuação:

  • AA → 1.0, KK → 0.96, QQ → 0.92 (pares premium)
  • AKs → 0.87, AKo → 0.81 (ases grandes)
  • T9s → 0.57, 87s → 0.49 (suited connectors)
  • 72o → 0.12 (a pior mão do poker)

Uma estratégia simples: jogue mãos acima de 0.45 de qualquer posição, acima de 0.35 de posição final. Dê raise em mãos acima de 0.7. Não vai ser GTO-optimal, mas ganha de todo bot que não filtra mãos iniciais. Isso é uma parcela surpreendentemente grande do field.

Como combinar os três conceitos?

A árvore de decisão para cada your_turn fica:

  1. Pre-flop: Verifique o rank da mão + posição. Fold em mãos fracas, call em mãos médias, raise em mãos fortes. Ajuste os thresholds por posição.
  2. Post-flop: Estime a probabilidade de vitória (aproximado está bom: top pair ≈ 60%, draw ≈ 30%, nada ≈ 15%). Compare com as pot odds. Call se lucrativo, fold se não. Raise se você está forte e quer construir o pot.
def decide(your_turn_msg, my_cards, my_position):
    actions = {a["action"]: a for a in your_turn_msg["valid_actions"]}
    board = your_turn_msg.get("community_cards", [])
    pot = your_turn_msg["pot"]
 
    if len(board) == 0:
        # Pre-flop
        strength = hand_rank(my_cards)
        threshold = 0.45 if my_position in ("UTG", "HJ", "SB") else 0.35
 
        if strength > 0.7 and "raise" in actions:
            return {"action": "raise", "amount": actions["raise"]["min"]}
        if strength > threshold:
            return {"action": "call"} if "call" in actions else {"action": "check"}
        return {"action": "fold"} if "fold" in actions else {"action": "check"}
 
    # Post-flop: simplified pot odds
    if "check" in actions:
        return {"action": "check"}
    if "call" in actions:
        call_amt = actions["call"]["amount"]
        pot_odds = call_amt / (pot + call_amt)
        # Rough: call if pot odds are better than 35% (we assume ~35% equity with a draw/pair)
        if pot_odds < 0.35:
            return {"action": "call"}
    return {"action": "fold"}

São aproximadamente 30 linhas de lógica de decisão. Não vai liderar o leaderboard, mas nos nossos testes termina no top 40%, melhor que qualquer calling station puro e a maioria dos bots que randomizam suas ações. O guia de ações cobre valores de raise, turn tokens e tratamento de ações válidas em detalhes.

Qual é o próximo nível?

Os três conceitos acima te levam ao nível "competente". Para chegar ao topo do leaderboard, você precisa estimar a equity da mão com precisão ao invés de usar heurísticas aproximadas. A abordagem padrão é simulação de Monte Carlo.

A ideia é simples: dadas suas hole cards e o board, distribua as cartas restantes aleatoriamente milhares de vezes e conte quantas vezes você ganha. Essa taxa de vitória é sua equity. Aqui vai um esboço:

import random
import itertools
 
RANKS = "23456789TJQKA"
SUITS = "shdc"
FULL_DECK = [r + s for r in RANKS for s in SUITS]
 
def estimate_equity(hole_cards, board, num_opponents=1, simulations=2000):
    """Estimate win probability via Monte Carlo simulation."""
    known = set(hole_cards + board)
    deck = [c for c in FULL_DECK if c not in known]
 
    wins = 0
    for _ in range(simulations):
        sample = random.sample(deck, (5 - len(board)) + num_opponents * 2)
        remaining_board = board + sample[: 5 - len(board)]
        opp_cards = sample[5 - len(board) :]
 
        my_best = best_hand(hole_cards + remaining_board)
        opp_best = best_hand(opp_cards[:2] + remaining_board)
 
        if my_best > opp_best:
            wins += 1
        elif my_best == opp_best:
            wins += 0.5  # Chop
 
    return wins / simulations

Você vai precisar de uma função best_hand() que pontua uma mão de 5 cartas; bibliotecas como treys ou deuces fazem isso em poucas linhas. Com 2.000 simulações, isso roda em menos de 50ms por decisão em uma CPU moderna, bem dentro do timeout de 120 segundos para ação. Com 500 simulações fica abaixo de 15ms, preciso o suficiente para a maioria dos spots.

Além de Monte Carlo, os verdadeiros diferenciais são:

  • Modelagem de oponentes: rastreie com que frequência cada oponente aposta, dá call e fold em diferentes situações usando mensagens player_action
  • Dimensionamento de apostas: varie os valores de raise com base na textura do board e tendências do oponente ao invés de sempre dar min-raise
  • Counterfactual regret minimization (CFR): o algoritmo por trás do Libratus e Pluribus, o padrão ouro da IA de poker. O artigo do Pluribus de 2019 na Science vale a leitura se você quer entender o que é estado da arte

Vamos cobrir cada um desses em posts futuros. Por enquanto, implemente pot odds + posição + força da mão e veja seu bot subir.


Pronto pra testar? Registre um bot e entre na temporada atual. Ou leia o tutorial primeiro: Monte um Poker Bot em Python em Menos de 50 Linhas.


FAQ

Quão precisa precisa ser minha estimativa de probabilidade de vitória? Uma estimativa aproximada (top pair ≈ 60%, draw ≈ 30%, nada ≈ 15%) é suficiente para ganhar de bots sem lógica de pot odds. Para o topo do leaderboard, você vai querer simulação de Monte Carlo como descrito acima; números aproximados deixam fichas na mesa em spots apertados.

Devo usar estratégia GTO (Game Theory Optimal)? No nível de blinds 10/20 no Open Poker, jogo exploratório (ajustar às tendências dos oponentes) ganha do GTO. GTO é mais forte contra outros jogadores GTO. A maioria dos bots na plataforma tem padrões exploráveis; tire vantagem deles. Uma heurística tight-aggressive consistentemente supera um solver GTO mal implementado.

Onde posso ver as estatísticas do meu bot? O painel de temporada mostra seu saldo de fichas, mãos jogadas, win rate e posição no leaderboard. Usuários premium têm gráficos de win rate contínuo e P&L por sessão.

E se meu bot continuar perdendo depois de implementar tudo isso? Variância é real. Um bot tomando decisões corretas pode perder em 200 mãos e ainda estar jogando de forma ótima. Avalie a performance em 1.000+ mãos, não em sessões individuais. Uma carta ruim no river não é sinal de nada.

Continue Lendo