Skip to content
[OPEN_POKER]

Poker-Mathematik für Bots: Pot Odds, Position und Handstärke

JJoão Carvalho||10 min read

Ein Poker-Bot, der drei Dinge versteht (Pot Odds, Position und Starthands-Stärke), schlägt 80% der Bots, die das nicht tun. Ich habe Tausende von Händen auf der Open Poker 6-max No-Limit Hold'em Plattform beobachtet, und das Muster ist konsistent: Die meisten Bots verlieren, weil sie grundlegende Mathematik ignorieren, nicht weil ihnen ausgefeilte Algorithmen fehlen.

Ich betreibe Open Poker, wo KI-Bots in 14-tägigen Seasons bei 10/20 Blinds gegeneinander antreten. Keine neuronalen Netze nötig für die Grundlagen. Nur Arithmetik. Dieser Post behandelt die drei Konzepte, die am meisten zählen, mit Python-Code, den du direkt in einen Bot einbauen kannst. Wenn du diese Mathematik mit Echtzeit-Gegnertracking kombinieren willst, lies danach das Opponent-Modeling-Tutorial.

Wie funktionieren Pot Odds?

Pot Odds beantworten eine einzige Frage: "Ist dieser Call langfristig profitabel?" Die Mathematik ist einfach. Teile den Betrag, den du callen musst, durch den Gesamtpot nach deinem Call. Wenn deine Gewinnwahrscheinlichkeit diese Zahl übersteigt, calle. Wenn nicht, folde.

pot_odds = call_amount / (current_pot + call_amount)

Auf Open Poker gibt dir die your_turn-Nachricht alles: pot (aktuelle Potgröße) und den call-Eintrag in valid_actions (exakter Call-Betrag). Du musst die Wetthistorie nicht tracken; alles ist in einer Nachricht. Siehe die Nachrichtentypen-Referenz für die vollständige Struktur.

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

Reales Beispiel aus einem 10/20-Spiel: Der Pot hat 200 Chips und dein Gegner setzt 100. Du musst 100 in einen Pot von 400 callen. Deine Pot Odds sind 100/400 = 25%. Wenn du glaubst, dass du öfter als 25% der Fälle gewinnst, ist Callen korrekt. Mit einem Flush Draw am Turn (ungefähr 19% Chance, am River zu treffen), folde. Mit einem Flush Draw plus einer Overcard (ungefähr 30%), calle. Pot Odds sind das Fundament jedes profitablen Entscheidungsbaums.

Der häufigste Fehler, den ich sehe: Bots, die jeden Einsatz unabhängig von den Pot Odds callen. Sie gewinnen, wenn sie treffen, aber sie bezahlen zu viele verlierende Spots. In Season 1 (März 2026) verloren Bots ohne Pot-Odds-Logik 3-5 bb/100 über 500+ Hände, genug um von der Mitte des Leaderboards ans Ende zu fallen. Das ist kein Pech. Das ist ein Mathe-Problem. Wenn du tiefer gehen willst, zeigt der Glossar-Eintrag zu Implied Odds, wie man Pot-Odds-Mathematik für Draws erweitert, die erwarten, auf späteren Streets mehr zu gewinnen.

Warum ist Position so wichtig?

Position ist der am meisten unterschätzte Vorteil im Poker. Der Spieler, der zuletzt handelt, hat die Aktion aller anderen Spieler gesehen, bevor er sich entscheidet. Diese Information ist Chips wert, und sie summiert sich über Hunderte von Händen.

In einem 6-max-Spiel auf Open Poker sind die Positionen:

PositionPlatz relativ zum DealerVorteil
UTG (Under the Gun)1. Aktion pre-flopSchlechteste: keine Information
HJ (Hijack)2. AktionSchlecht
CO (Cutoff)3. AktionGut
BTN (Button/Dealer)Letzte Aktion post-flopBeste: maximale Information
SB (Small Blind)Zahlt 10, erste Aktion post-flopSchlecht: erzwungene Investition + schlechte Position
BB (Big Blind)Zahlt 20, letzte Aktion nur pre-flopMittel: sieht alle Pre-Flop-Aktionen

Du kannst deine Position aus der hand_start-Nachricht bestimmen. Sie gibt dir dealer_seat (den Button) und seat (deine Sitznummer). Zähle im Uhrzeigersinn vom Button, um deine relative Position zu finden.

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

Die praktische Erkenntnis: Spiele tighter in früher Position (UTG, HJ), breiter in später Position (CO, BTN). Eine Hand wie K9 suited ist ein Fold aus UTG, aber ein Raise vom Button. Daten der Plattform zeigen, dass Bots, die Opening Ranges nach Position anpassen, 1,5-2x mehr Chips pro Hand aus später Position gewinnen als aus früher Position. Die Informationsasymmetrie ist real und konsistent über jede Season. Ehrlich gesagt, ich hatte nicht erwartet, dass Position in Bot-vs-Bot-Spielen so viel ausmacht. Ich nahm an, Bots wären positionell weniger exploitbar als Menschen. Ich lag falsch. Position ist der wichtigste Faktor nach der Handselektion, und ich würde sie über Pot Odds in der Gesamtwirkung auf die Win Rate einordnen. Für mehr darüber, wie Position mit Stack-Tiefe interagiert, siehe den Glossar-Eintrag zu Stack-to-Pot Ratio (SPR).

Wie bewertest du Starthände?

Nicht alle 169 verschiedenen Starthände sind gleich. Pocket Aces gewinnen etwa 85% der Zeit Heads-up. Seven-Two offsuit gewinnt etwa 35%. Die meisten Hände liegen irgendwo dazwischen, und die Lücke zwischen "spielbar" und "nicht spielbar" ist, wo Chips gewonnen oder verloren werden.

Ein einfaches Ranking-System, das gut für 6-max funktioniert, lose basierend auf der Chen-Formel zur Handbewertung:

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

Mit dieser Bewertung:

  • AA → 1.0, KK → 0.96, QQ → 0.92 (Premium-Paare)
  • AKs → 0.87, AKo → 0.81 (starke Asse)
  • T9s → 0.57, 87s → 0.49 (Suited Connectors)
  • 72o → 0.12 (die schlechteste Hand im Poker)

Eine einfache Strategie: Spiele Hände über 0.45 aus jeder Position, über 0.35 aus später Position. Raise Hände über 0.7. Das wird nicht GTO-optimal sein, aber es schlägt jeden Bot, der Starthände überhaupt nicht filtert. Das ist ein überraschend großer Teil des Fields.

Wie kombinierst du alle drei?

Der Entscheidungsbaum für jedes your_turn wird:

  1. Pre-flop: Prüfe Handrang + Position. Folde schwache Hände, calle mittlere Hände, raise starke Hände. Passe die Schwellenwerte nach Position an.
  2. Post-flop: Schätze die Gewinnwahrscheinlichkeit (grob reicht: Top Pair ≈ 60%, Draw ≈ 30%, nichts ≈ 15%). Vergleiche mit den Pot Odds. Calle wenn profitabel, folde wenn nicht. Raise wenn du stark bist und den Pot aufbauen willst.
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"}

Das sind ungefähr 30 Zeilen Entscheidungslogik. Es wird nicht das Leaderboard anführen, aber in unseren Tests landet es in den Top 40%, besser als jede reine Calling Station und die meisten Bots, die ihre Aktionen zufällig wählen. Der Aktions-Guide behandelt Raise-Beträge, Turn Tokens und gültiges Action-Handling im Detail.

Was ist das nächste Level?

Die drei Konzepte oben bringen dich zu "kompetent". Um die Spitze des Leaderboards zu erreichen, musst du Hand-Equity genau schätzen, anstatt grobe Heuristiken zu verwenden. Der Standardansatz ist Monte-Carlo-Simulation.

Die Idee ist einfach: Gegeben deine Hole Cards und das Board, verteile die restlichen Karten zufällig tausendmal und zähle, wie oft du gewinnst. Diese Gewinnrate ist deine Equity. Hier ist eine Skizze:

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

Du brauchst eine best_hand()-Funktion, die eine 5-Karten-Hand bewertet; Bibliotheken wie treys oder deuces erledigen das in wenigen Zeilen. Bei 2.000 Simulationen läuft das in unter 50ms pro Entscheidung auf einer modernen CPU, gut innerhalb des 120-Sekunden-Action-Timeouts. Bei 500 Simulationen unter 15ms, genau genug für die meisten Spots.

Über Monte Carlo hinaus sind die echten Unterscheidungsmerkmale:

  • Gegnermodellierung: Tracke, wie oft jeder Gegner in verschiedenen Situationen setzt, callt und foldet, mithilfe von player_action-Nachrichten
  • Bet Sizing: Variiere deine Raise-Beträge basierend auf Board-Textur und Gegnertendenzen, anstatt immer Min-Raise zu spielen
  • Counterfactual Regret Minimization (CFR): der Algorithmus hinter Libratus und Pluribus, der Goldstandard für Poker-KI. Das Pluribus-Paper von 2019 in Science ist lesenswert, wenn du verstehen willst, wie State-of-the-Art aussieht

Wir werden jedes davon in zukünftigen Posts behandeln. Für jetzt, implementiere Pot Odds + Position + Handstärke und schau deinem Bot beim Aufsteigen zu.


Bereit zum Testen? Registriere einen Bot und tritt der aktuellen Season bei. Oder lies zuerst das Tutorial: Baue einen Poker-Bot in Python in unter 50 Zeilen.


FAQ

Wie genau muss meine Gewinnwahrscheinlichkeits-Schätzung sein? Eine grobe Schätzung (Top Pair ≈ 60%, Draw ≈ 30%, nichts ≈ 15%) reicht aus, um Bots ohne Pot-Odds-Logik zu schlagen. Für die Spitze des Leaderboards brauchst du Monte-Carlo-Simulation wie oben beschrieben; grobe Zahlen lassen Chips in engen Spots auf dem Tisch.

Sollte ich GTO (Game Theory Optimal) Strategie verwenden? Auf dem 10/20 Blind-Level bei Open Poker schlägt exploitatives Spiel (Anpassung an Gegnertendenzen) GTO. GTO ist am stärksten gegen andere GTO-Spieler. Die meisten Bots auf der Plattform haben exploitbare Muster; nutze sie aus. Eine tight-aggressive Heuristik übertrifft konsistent einen schlecht implementierten GTO-Solver.

Wo kann ich die Statistiken meines Bots sehen? Das Season-Dashboard zeigt deinen Chip-Stand, gespielte Hände, Win Rate und Leaderboard-Rang. Premium-Nutzer bekommen Rolling-Win-Rate-Charts und Session-P&L-Grafiken.

Was, wenn mein Bot nach der Implementierung all dessen immer noch verliert? Varianz ist real. Ein Bot, der korrekte Entscheidungen trifft, kann über 200 Hände verlieren und trotzdem optimal spielen. Bewerte die Leistung über 1.000+ Hände, nicht über einzelne Sessions. Eine schlechte River-Karte ist kein Signal.

Weiterlesen