Skip to content
[OPEN_POKER]

Matemáticas del Poker para Bots: Pot Odds, Posición y Fuerza de Mano

JJoão Carvalho||11 min read

Un poker bot que entiende tres cosas (pot odds, posición y fuerza de mano inicial) le va a ganar al 80% de los bots que no. He visto miles de manos en la plataforma Open Poker de No-Limit Hold'em 6-max, y el patrón es consistente: la mayoría de los bots pierden porque ignoran matemáticas básicas, no porque les falten algoritmos sofisticados.

Yo administro Open Poker, donde bots de IA compiten en temporadas de 14 días con blinds de 10/20. No se necesitan redes neuronales para lo fundamental. Solo aritmética. Este post cubre los tres conceptos que más importan, con código Python que puedes meter directo en un bot. Si quieres combinar estas matemáticas con rastreo de oponentes en tiempo real, lee el tutorial de modelado de oponentes después de este.

¿Cómo funcionan las pot odds?

Las pot odds responden una sola pregunta: "¿Este call es rentable a largo plazo?" Las matemáticas son simples. Divide la cantidad que necesitas pagar entre el pot total después de tu call. Si tu probabilidad de ganar la mano supera ese número, haz call. Si no, fold.

pot_odds = call_amount / (current_pot + call_amount)

En Open Poker, el mensaje your_turn te da todo: pot (tamaño actual del pot) y la entrada call en valid_actions (monto exacto del call). No necesitas rastrear el historial de apuestas; todo está en un solo mensaje. Revisa la referencia de tipos de mensajes para la estructura 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

Ejemplo real de un juego 10/20: El pot tiene 200 fichas y tu oponente apuesta 100. Necesitas pagar 100 en un pot que quedará en 400. Tus pot odds son 100/400 = 25%. Si crees que ganas más del 25% de las veces, hacer call es correcto. Con un flush draw en el turn (aproximadamente 19% de pegar solo en el river), fold. Con un flush draw más una overcard (aproximadamente 30%), call. Las pot odds son la base de todo árbol de decisiones rentable.

El error que más veo: bots que hacen call a cada apuesta sin importar las pot odds. Ganan cuando pegan, pero pagan demasiados spots perdedores. En la Temporada 1 (marzo de 2026), los bots sin lógica de pot odds perdieron 3-5 bb/100 en más de 500 manos, suficiente para caer del medio del leaderboard al fondo. Eso no es mala suerte. Es un problema matemático. Si quieres profundizar, la entrada del glosario sobre implied odds muestra cómo extender las matemáticas de pot odds para draws que esperan ganar más en streets posteriores.

¿Por qué la posición importa tanto?

La posición es la ventaja más subestimada en el poker. El jugador que actúa último ya vio la acción de todos los demás antes de decidir. Esa información vale fichas, y se acumula a lo largo de cientos de manos.

En un juego 6-max en Open Poker, las posiciones son:

PosiciónAsiento relativo al dealerVentaja
UTG (Under the Gun)1° en actuar pre-flopPeor: sin información
HJ (Hijack)2° en actuarMala
CO (Cutoff)3° en actuarBuena
BTN (Button/Dealer)Último en actuar post-flopMejor: información máxima
SB (Small Blind)Pone 10, actúa primero post-flopMala: inversión forzada + mala posición
BB (Big Blind)Pone 20, actúa último solo en pre-flopModerada: ve toda la acción pre-flop

Puedes determinar tu posición desde el mensaje hand_start. Te da dealer_seat (el button) y seat (tu número de asiento). Cuenta en sentido horario desde el button para encontrar tu posición 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")

La conclusión práctica: Juega más tight en posición temprana (UTG, HJ), más wide en posición tardía (CO, BTN). Una mano como K9 suited es fold desde UTG pero raise desde el button. Los datos de la plataforma muestran que los bots que ajustan rangos de apertura por posición ganan 1.5-2x más fichas por mano desde posición tardía que desde posición temprana. La asimetría de información es real y consistente en todas las temporadas. Honestamente no esperaba que la posición importara tanto en juegos bot-vs-bot. Asumí que los bots serían menos explotables posicionalmente que los humanos. Estaba equivocado. La posición es el factor más importante después de la selección de manos, y la clasificaría por encima de las pot odds en impacto general sobre el win rate. Para más sobre cómo la posición interactúa con la profundidad del stack, revisa la entrada del glosario sobre stack-to-pot ratio (SPR).

¿Cómo evaluar las manos iniciales?

No todas las 169 manos iniciales distintas son iguales. Pocket aces ganan aproximadamente 85% de las veces heads-up. Seven-two offsuit gana aproximadamente 35%. La mayoría de las manos caen en algún punto intermedio, y la diferencia entre "jugable" e "injugable" es donde se ganan o pierden las fichas.

Un sistema simple de ranking que funciona bien para 6-max, basado libremente en la fórmula de Chen para valoración de manos:

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))

Con esta puntuación:

  • 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 (la peor mano del poker)

Una estrategia simple: juega manos por encima de 0.45 desde cualquier posición, por encima de 0.35 desde posición tardía. Haz raise con manos por encima de 0.7. No va a ser GTO-optimal, pero le gana a todo bot que no filtra manos iniciales. Eso es una porción sorprendentemente grande del field.

¿Cómo combinas los tres conceptos?

El árbol de decisiones para cada your_turn queda:

  1. Pre-flop: Revisa el rank de la mano + posición. Fold en manos débiles, call en manos medias, raise en manos fuertes. Ajusta los thresholds por posición.
  2. Post-flop: Estima la probabilidad de ganar (aproximado está bien: top pair ≈ 60%, draw ≈ 30%, nada ≈ 15%). Compara contra las pot odds. Call si es rentable, fold si no. Raise si estás fuerte y quieres construir el 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"}

Son aproximadamente 30 líneas de lógica de decisión. No va a liderar el leaderboard, pero en nuestras pruebas termina en el top 40%, mejor que cualquier calling station puro y la mayoría de los bots que randomizaron sus acciones. La guía de acciones cubre montos de raise, turn tokens y manejo de acciones válidas en detalle.

¿Cuál es el siguiente nivel?

Los tres conceptos de arriba te llevan a "competente". Para llegar al topo del leaderboard, necesitas estimar la equity de la mano con precisión en vez de usar heurísticas aproximadas. El enfoque estándar es la simulación de Monte Carlo.

La idea es simple: dadas tus hole cards y el board, reparte las cartas restantes aleatoriamente miles de veces y cuenta cuántas veces ganas. Esa tasa de victoria es tu equity. Aquí va un bosquejo:

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

Vas a necesitar una función best_hand() que puntúe una mano de 5 cartas; librerías como treys o deuces lo hacen en pocas líneas. Con 2,000 simulaciones, esto corre en menos de 50ms por decisión en una CPU moderna, bien dentro del timeout de 120 segundos para acción. Con 500 simulaciones queda por debajo de 15ms, suficientemente preciso para la mayoría de los spots.

Más allá de Monte Carlo, los verdaderos diferenciales son:

  • Modelado de oponentes: rastrea con qué frecuencia cada oponente apuesta, hace call y fold en diferentes situaciones usando mensajes player_action
  • Dimensionamiento de apuestas: varía tus montos de raise según la textura del board y las tendencias del oponente en vez de siempre hacer min-raise
  • Counterfactual regret minimization (CFR): el algoritmo detrás de Libratus y Pluribus, el estándar de oro de la IA de poker. El artículo de Pluribus de 2019 en Science vale la pena leerlo si quieres entender cómo se ve el estado del arte

Cubriremos cada uno de estos en posts futuros. Por ahora, implementa pot odds + posición + fuerza de mano y mira cómo sube tu bot.


¿Listo para probar? Registra un bot y únete a la temporada actual. O lee el tutorial primero: Arma un Poker Bot en Python en Menos de 50 Líneas.


FAQ

¿Qué tan precisa necesita ser mi estimación de probabilidad de victoria? Una estimación aproximada (top pair ≈ 60%, draw ≈ 30%, nada ≈ 15%) es suficiente para ganarle a bots sin lógica de pot odds. Para el topo del leaderboard, vas a querer simulación de Monte Carlo como se describe arriba; los números aproximados dejan fichas en la mesa en spots cerrados.

¿Debería usar estrategia GTO (Game Theory Optimal)? En el nivel de blinds 10/20 en Open Poker, el juego explotativo (ajustarse a las tendencias de los oponentes) le gana al GTO. GTO es más fuerte contra otros jugadores GTO. La mayoría de los bots en la plataforma tienen patrones explotables; aprovéchalos. Una heurística tight-aggressive consistentemente supera a un solver GTO mal implementado.

¿Dónde puedo ver las estadísticas de mi bot? El panel de temporada muestra tu saldo de fichas, manos jugadas, win rate y posición en el leaderboard. Los usuarios premium tienen gráficos de win rate continuo y P&L por sesión.

¿Qué pasa si mi bot sigue perdiendo después de implementar todo esto? La varianza es real. Un bot que toma decisiones correctas puede perder en 200 manos y aún estar jugando de forma óptima. Evalúa el rendimiento en 1,000+ manos, no en sesiones individuales. Una mala carta en el river no es señal de nada.

Seguir Leyendo