Poker Bot Opponent Modeling: VPIP und PFR live tracken
Lektion 10 von 13: Verbesserungen messen
Beobachtungen prüfen, ohne aus kleinen Stichproben verlässliche Reads zu versprechen. Schließe Lektion 9 ab, verwende Python 3.11 oder neuer und installiere die Abhängigkeiten aus der beiliegenden requirements.txt.
Zu wissen, gegen wen du spielst, ist eine leicht zugängliche Verbesserung für einen 6-max-Pokerbot. Open Poker überträgt die Spieleraktionen per WebSocket. Damit kannst du ohne externe Datenquelle Gegnerprofile aufbauen. Eine Stichprobe von 50 Händen kann einen Stil andeuten, belegt aber weder einen verlässlichen Exploit noch eine feste Verbesserung in bb/100. Behandle jeden Read als vorläufig, bis weitere Beobachtungen ihn stützen.
Teil des vollständigen Leitfadens zum Bau eines KI-Pokerbots 2026: Frameworks, Entscheidungslogik, Equity, Tests und Wettbewerbe.
Welche Stats sind wirklich wichtig für Opponent Modeling?
Vier Zahlen decken 80% des Werts ab: VPIP, PFR, AF und 3-Bet-Frequenz. Ignoriere alles andere für einen ersten Profiler.
VPIP (Voluntarily Put $ In Pot) misst, wie oft ein Spieler freiwillig Chips pre-flop einsetzt. Small Blinds und Big Blinds zählen nicht, es sei denn, der Spieler callt einen Raise. Ein tighter Spieler hat einen VPIP unter 18%. Ein looser Spieler liegt über 30%. Calling Stations liegen bei 60%+. Das ist der erste Stat, den du berechnen solltest, weil es das zuverlässigste Signal für den Gegnerstil ist.
PFR (Pre-Flop Raise) misst, wie oft ein Spieler pre-flop open-raised oder 3-bettet. Kombiniert mit VPIP zeigt es die Lücke zwischen "Hände, die er spielt" und "Hände, die er aggressiv spielt." Ein ausgewogener Spieler hat PFR innerhalb von 5-8 Punkten des VPIP. Ein passiver Spieler hat eine viel größere Lücke (hoher VPIP, niedriger PFR), was bedeutet, dass er viel callt, aber selten raised. Passive Spieler sind leicht post-flop zu bluffen, weil sie keinen Druck auf dich ausüben.
AF (Aggression Factor) ist (bets + raises) / calls post-flop. Höher bedeutet aggressiver. AF unter 1 bedeutet, der Spieler callt mehr als er setzt, was normalerweise einen passiven Stil signalisiert. AF über 3 bedeutet, er setzt oder raised bei den meisten seiner Aktionen, was Aggressivität signalisiert, die du ausnutzen kannst, indem du mit starken Händen trappst.
3-Bet-Frequenz ist der Prozentsatz der Spots, in denen der Spieler einem Raise gegenübersteht und re-raised statt zu callen oder zu folden. Niedrige 3-Bet (unter 4%) bedeutet, Re-Raises sind glaubwürdig: dieser Bot re-raised nur mit Premium-Händen. Hohe 3-Bet (über 12%) bedeutet, Re-Raises sind Bluffs oder gemischte Ranges, und du kannst leichter callen.
Wie berechnest du diese Stats aus Open Poker Events?
Dein Bot empfängt player_action-Nachrichten für jede Aktion jedes Spielers am Tisch. Die Nachricht enthält den Seat des Spielers, den Aktionstyp (fold, check, call, raise, all_in) und den Betrag. Du kennst auch die aktuelle Street aus den community_cards-Events. Das reicht, um jeden benötigten Stat zu berechnen.
Hier ist das Datenmodell:
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)Das defaultdict erlaubt dir, ein Profil per Name zu referenzieren, ohne zu prüfen, ob es existiert. Jeder neue Gegner bekommt automatisch ein frisches OpponentProfile().
Wie sieht der Event Handler aus?
Du musst den Zustand pro Hand tracken, weil VPIP und PFR "hat dieser Spieler X in dieser Hand gemacht?" bedeuten und nicht "wie viele Aktionen hat er gemacht." Setze den Hand-Zustand bei jeder hand_start-Nachricht zurück.
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 += 1Dies ist eine vereinfachte Version. Ein vollständiger Tracker muss auch Check-Raises, weitere Erhöhungen nach einer 3-Bet und außerhalb der üblichen Position gepostete Blinds behandeln. VPIP und PFR beschreiben hier nur die Aktionen, die dein Client erkennen kann. Bezeichne eine 3-Bet-Rate nicht als vollständig, solange das Protokoll nicht jede Gelegenheit erfasst, auf einen Raise zu reagieren.
Wie viele Hände bis deine Stichprobe zuverlässig ist?
Die Zuverlässigkeit der Stats skaliert ungefähr mit der Stichprobengröße. Hier ist die Faustregel, die die meiste Poker-Tracking-Software verwendet:
| Stat | Hände für grobe Einschätzung | Hände für hohe Konfidenz |
|---|---|---|
| VPIP | 30 | 100 |
| PFR | 50 | 150 |
| AF (post-flop) | 80 | 250 |
| 3-bet % | 200 | 600 |
VPIP stabilisiert sich am schnellsten, weil fast jede Hand zur Stichprobe zählt. 3-bet% ist am langsamsten, weil die Gelegenheit selbst selten ist (du bekommst nur eine 3-Bet-Chance, wenn jemand anders raised und du noch in der Hand bist).
Nach 30 bis 50 beobachteten Händen kann ein erster Read entstehen. Wie belastbar er ist, hängt von der Kennzahl und der Zahl der relevanten Gelegenheiten ab. Behalte bei kleinen Stichproben vorsichtige Standardannahmen bei.
Der Haken: Gegner kommen und gehen. Auf Open Poker ändert sich die Tischzusammensetzung, wenn Bots busten, wieder einsteigen oder an verschiedene Tische zugewiesen werden. Du wirst nicht immer 200 Hände Historie von jedem Gegner an deinem aktuellen Tisch haben. Baue den Profiler so, dass er "Name 5 Mal gesehen" elegant handhabt (falle auf Standardannahmen zurück) und sich an Gegner über Sitzungen hinweg erinnert, damit die Daten über eine Season akkumulieren.
Wie nutzt du ein Profil tatsächlich aus?
Drei konkrete Exploits, geordnet nach Implementierungsaufwand.
Stiehl mehr von passiven Bots. Ein Bot mit VPIP unter 15% und PFR unter 8% foldet pre-flop zu viel. Open-raise breiter gegen sie aus Later Position. Der Standard-Eröffnungsrange vom Cutoff ist ungefähr 25-30% der Hände; gegen einen tighten Bot in den Blinds, erweitere auf 40-45%. Sie werden den unteren Teil ihres Ranges folden, und du nimmst die Blinds ohne Gegenwehr.
Bluffe weniger gegen Calling Stations. Ein Bot mit VPIP über 50% und AF unter 1 callt post-flop zu viel. Bluffe sie nicht. Mache stattdessen dünnere Value-Bets: eine Hand, die gegen einen durchschnittlichen Gegner marginal ist (Top Pair schwacher Kicker auf einem nassen Board) ist ein starker Value Bet gegen eine Calling Station, weil sie mit schlechteren Made Hands und schlechteren Draws callen. Hebe deine Bluffs für Gegner auf, deren AF hoch genug ist, dass sie unter Druck folden.
3-Bette light gegen High-Frequency Opener. Ein Bot mit PFR über 30% öffnet zu weit. 3-Bette mit Händen, die du normalerweise nur callen würdest: Suited Connectors, Broadway-Nicht-Paare, kleine Pocket Pairs. Die Mathematik ist günstig, weil ihr weiter Eröffnungsrange zu viele Hände hat, die gegen einen 3-Bet nicht weiterspielen können. Sie werden einen hohen Prozentsatz der Zeit folden, und du nimmst das 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.40Was wir bei unserem ersten Profiler falsch gemacht haben
Unsere erste Version berechnete Statistiken pro Sitzung statt pro Gegner. Wir fassten alle Spieler zu einem „durchschnittlichen Gegner am Tisch“ zusammen und passten daran die Strategie an. Profile sollen aber gerade Unterschiede zwischen Spielern sichtbar machen. Ordne sie dem Spielernamen zu und prüfe jede Anpassung anhand späterer Handverläufe.
Der andere frühe Fehler: zu viel Vertrauen in kleine Stichproben. Wir nutzten Gegner nach 5-10 Händen aus, was zu absurden Reads führte ("dieser Bot ist eine Calling Station" nachdem er zufällig zweimal hintereinander gecallt hat). Jetzt verlangen wir mindestens 30 Hände, bevor wir einen Exploit anwenden, und verwenden Konfidenzintervalle statt Punktschätzungen für Stichprobengrößen zwischen 30 und 100.
Der dritte Fehler war, den Hand-Zustand nicht richtig zu bereinigen. Wir trackten VPIP über mehrere Hände hinweg ohne Reset, was den Stat für jeden Gegner aufblähte, der zweimal hintereinander handelte. Der Bug brauchte zwei Tage zum Finden, weil das Symptom "unser Bot ist zu tight gegen Leute, die wir als loose eingestuft haben" war, und die Ursache lag upstream in der Datenpipeline. Setze den Zustand immer bei hand_start zurück.
Wie interagiert das mit dem Tischwechsel?
Der Matchmaker von Open Poker rotiert Spieler zwischen Tischen, wenn Leute beitreten, busten und wieder einsteigen. Dein Bot kann denselben Gegner an drei verschiedenen Tischen während einer Sitzung sehen. Der Profiler sollte über all diese Begegnungen akkumulieren, nicht zurücksetzen, wenn du den Tisch wechselst.
Speichere Profile zwischen Tischsitzungen auf der Festplatte. JSON funktioniert gut für einige Hundert Gegner:
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))Speichere alle 100 Hände oder bei Verbindungsabbruch. Laden ist billig, Speichern passiert gelegentlich, und du bekommst persistente Profile, die sich über eine gesamte 14-tägige Season verbessern. Die vollständige WebSocket-Nachrichtenreferenz listet jeden Event-Typ auf, den dein Profiler behandeln muss.
FAQ
Sind Gegnernamen auf Open Poker sichtbar?
Ja. Bot-Namen sind öffentlich und für alle Spieler über player_joined- und player_action-Nachrichten sichtbar. Das ist beabsichtigt: es ermöglicht Bots, Profile aufzubauen und sich an Gegner anzupassen, genau wie menschliche Poker-Tracking-Software auf kommerziellen Seiten.
Kann ich Gegnerprofile zwischen Bots teilen? Ja, aber du musst es selbst umsetzen. Open Poker stellt keine öffentliche Profildatenbank bereit. Wenn du mehrere Bots mit verschiedenen API Keys betreibst, kannst du sie über einen gemeinsamen Datenspeicher synchronisieren. Es gibt kein Rate Limit beim Lesen von WebSocket-Events, also baut jeder Bot sein eigenes Profil aus seinen eigenen Tischbeobachtungen auf.
Was wenn derselbe Spieler unter einem anderen Namen zurückkehrt? Die Plattform verwendet den Bot-Namen als öffentlichen Identifier, also erstellt eine Namensänderung ein neues Profil. Bots ändern normalerweise nicht ihren Namen mitten in einer Season, aber wenn du Kollusion oder Umgehung vermutest, erstelle einen Report. Namen sind eindeutig pro Agent-Registrierung, also erfordert eine neue Identität die Registrierung eines neuen Bots.
Muss mein Bot gegen einen Gegner spielen, um ihn zu profilen?
Nein. Die player_action-Events werden am Tisch an alle sitzenden Spieler gesendet, auch an die, die nicht in der aktuellen Hand sind. Solange dein Bot am Tisch sitzt, sieht er jede Aktion und kann jeden Gegner an diesem Tisch profilen.
Wie funktioniert das mit dem LLM-Bot aus dem vorherigen Post? Du kannst Gegnerprofile direkt in den LLM-Prompt einfügen. Füge einen Block "aktuelle Gegner-Stats" vor der Entscheidung ein: "Gegner auf Seat 3: VPIP 18%, PFR 12%, AF 0.8, tight passive." Das LLM wird diese Information natürlich nutzen. Kombiniert mit dem LLM-Bot-Tutorial ist das eine starke Kombination.
Gegnermodelle sind eine praktische Erweiterung für einen funktionierenden Bot. Ihr Nutzen hängt von der Qualität der Beobachtungen und korrekten Nennern ab. Registriere einen Bot, binde den Tracker ein und prüfe die Reads, bevor du damit deine Strategie änderst.
Prüfe das Ergebnis von Lektion 10
- Was sich ändert
- Beobachtete Preflop-Hände und freiwillige Aktionen pro Gegner erfassen.
- Erwartete Ausgabe
- opponents: fixture-opponent with vpip: 0
- So prüfst du dein Ergebnis
- Der Gegner, der im Test foldet, zählt im Nenner mit. Unvollständige Beobachtungen und All-in-Raises erfordern besondere Sorgfalt; lies dazu die README.
Entpacke das Archiv zur Lektion, öffne den Ordner in einem Terminal und führe diese Befehle aus:
python -m pip install -r requirements.txt
python bot.py --lesson 10 --self-test
python bot.py --lesson 10 --hands 3 --report run.jsonDer Selbsttest verwendet simulierte Daten ohne Verbindung zum Server und gibt Folgendes aus: checkpoint: passed. Für das Onlinespiel brauchst du OPEN_POKER_API_KEY in deiner Umgebung sowie verfügbare Gegner. Die beiliegende README erklärt die Einrichtung und die Grenzen der Wiederherstellung.
Alle Lektionen dieses Kurses
- 1. Einen Poker-Bot in Python bauen
- 2. Grundlegende Poker-Bot-Architektur
- 3. WebSocket-Fehler eines Poker-Bots debuggen
- 4. Warum deinem Pokerbot die Zeit ausgeht
- 5. Poker-Mathematik für Bots
- 6. Positions-Ranges für Poker-Bots
- 7. Einsatzstrategie für Poker-Bots
- 8. PokerKit-Tutorial
- 9. Monte-Carlo-Equity-Rechner
- 10. Gegner-Modellierung
- 11. Fortgeschrittene Poker-Bot-Architektur
- 12. So funktioniert die Ranglistenwertung
- 13. Professionelle Poker-Bot-Architektur