Poker Bot Opponent Modeling: VPIP और PFR को Live Track करें
6-max bot poker में सबसे सस्ता edge यह जानना है कि आप किसके खिलाफ खेल रहे हैं। Open Poker हर player action को WebSocket पर broadcast करता है, जिसका मतलब है कि आपका bot बिना किसी external data source के real time में opponent profile बना सकता है। 50 hands का sample obvious tight-passive bots और aggressive bluffers को spot करने के लिए काफी है, और typical fields के against यह 2-3 bb/100 worth है।
Opponent modeling के लिए कौन से stats actually matter करते हैं?
चार numbers 80% value cover करते हैं: VPIP, PFR, AF, और 3-bet frequency। पहले profiler के लिए बाकी सब ignore करें।
VPIP (Voluntarily Put $ In Pot) measure करता है कि player कितनी बार बिना force हुए pre-flop में chips डालता है। Small blinds और big blinds count नहीं होते जब तक player raise call नहीं करता। Tight player का VPIP 18% से नीचे होता है। Loose player 30% से ऊपर होता है। Calling stations 60%+ के आसपास होते हैं। यह पहला stat है जो आपको calculate करना चाहिए क्योंकि यह opponent style का सबसे reliable signal है।
PFR (Pre-Flop Raise) measure करता है कि player कितनी बार pre-flop open-raise या 3-bet करता है। VPIP के साथ combine करने पर, यह "जो hands वो play करते हैं" और "जो hands वो aggressively play करते हैं" के बीच का gap बताता है। Balanced player का PFR VPIP के 5-8 points के अंदर होता है। Passive player का gap बहुत ज्यादा होता है (high VPIP, low PFR), मतलब वो बहुत call करते हैं लेकिन rarely raise करते हैं। Passive players को post-flop bluff करना easy है क्योंकि वो आप पर pressure नहीं डालते।
AF (Aggression Factor) post-flop (bets + raises) / calls है। Higher मतलब ज्यादा aggressive। AF 1 से नीचे मतलब player bet से ज्यादा call करता है, जो usually passive style signal करता है। AF 3 से ऊपर मतलब वो ज्यादातर actions पर bet या raise करता है, जो aggression signal करता है जिसे आप strong hands से trap करके exploit कर सकते हैं।
3-bet frequency उन spots का percentage है जहां player raise face करता है और call या fold करने की बजाय re-raise करता है। Low 3-bet (4% से नीचे) मतलब re-raises credible हैं: वो bot सिर्फ premium hands से re-raise करता है। High 3-bet (12% से ऊपर) मतलब re-raises bluffs या merged ranges हैं, और आप lighter call कर सकते हैं।
Open Poker events से ये stats कैसे compute करें?
आपका bot table पर हर player की हर action के लिए player_action messages receive करता है। Message में player का seat, उनका action type (fold, check, call, raise, all_in), और amount include होता है। आप community_cards events से current street भी जानते हैं। यह हर stat compute करने के लिए enough है।
यहां data model है:
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)defaultdict आपको name से profile reference करने देता है बिना check किए कि वो exist करता है या नहीं। हर new opponent को automatically fresh OpponentProfile() मिलता है।
Event handler कैसा दिखता है?
आपको per-hand state track करना होगा क्योंकि VPIP और PFR "क्या इस player ने इस hand में X किया?" है, न कि "उन्होंने कितनी actions ली।" हर hand_start message पर hand state reset करें।
class HandTracker:
def __init__(self):
self.street = "preflop"
self.players_acted_pf = set() # who's voluntarily acted pre-flop
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.players_acted_pf.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 self.street == "preflop":
if action in ("call", "raise", "all_in"):
if name not in self.players_acted_pf:
prof.hands_seen += 1 # count hand once
prof.vpip_hands += 1
self.players_acted_pf.add(name)
if action == "raise":
if self.first_raiser is None:
# This is the open-raise
self.first_raiser = name
prof.pfr_hands += 1
# Everyone yet to act faces the open
self.facing_open.update(
n for n in self.players_acted_pf if n != name
)
elif name in self.facing_open:
# 3-bet opportunity → 3-bet action
prof.threebet_chances += 1
prof.threebet_actions += 1
self.facing_open.discard(name)
elif action in ("call", "fold") and name in self.facing_open:
# 3-bet opportunity → declined
prof.threebet_chances += 1
self.facing_open.discard(name)
else:
# Post-flop tracking
if action in ("bet", "raise", "all_in"):
prof.postflop_bets += 1
elif action == "call":
prof.postflop_calls += 1यह simplified version है। Production tracker check-raises, 3-bet से आगे के re-raises, और players जो out of position blinds post करते हैं जैसे edge cases handle करेगा। पहले profiler के लिए, simple version 90% signal capture करता है।
आपका sample कितनी hands में reliable हो जाता है?
Stat reliability roughly sample size के साथ scale करती है। यहां वो rule of thumb है जो ज्यादातर poker tracking software use करते हैं:
| Stat | Rough read के लिए hands | High confidence के लिए hands |
|---|---|---|
| VPIP | 30 | 100 |
| PFR | 50 | 150 |
| AF (post-flop) | 80 | 250 |
| 3-bet % | 200 | 600 |
VPIP सबसे तेज़ stabilize होता है क्योंकि लगभग हर hand sample में count होती है। 3-bet% सबसे slow है क्योंकि opportunity itself rare है (आपको 3-bet chance तभी मिलता है जब कोई और raise करता है और आप अभी hand में हैं)।
Implication: आपका bot पहली 30-50 hands में opponents पर useful reads पा लेता है। Hand 100 तक आपके पास confident profile होता है। Hand 200 तक आपके पास high-confidence exploits के लिए सब कुछ होता है।
Catch: opponents आते-जाते रहते हैं। Open Poker पर, table composition बदलती रहती है जैसे bots bust, rejoin, या different tables पर match होते हैं। आपके पास हमेशा current table पर हर opponent के 200 hands का history नहीं होगा। Profiler को "name 5 बार देखा" gracefully handle करने के लिए बनाएं (default assumptions पर fall back करें) और sessions के बीच opponents को remember करें ताकि data पूरे season में accumulate हो।
Profile को actually कैसे exploit करें?
तीन concrete exploits, implementation की आसानी के order में।
Passive bots से ज्यादा steal करें। VPIP 15% से कम और PFR 8% से कम वाला bot pre-flop बहुत ज्यादा fold करता है। Late position से उनके against wider open-raise करें। Cutoff से default opening range roughly 25-30% hands है; blinds में tight bot के against, 40-45% तक expand करें। वो अपने range का bottom fold कर देंगे, और आप blinds बिना contest के ले लेंगे।
Calling stations के against कम bluff करें। VPIP 50% से ज्यादा और AF 1 से कम वाला bot post-flop बहुत ज्यादा call करता है। उन्हें bluff मत करें। इसके बजाय thinner value-bet करें: एक hand जो average opponent के against marginal है (wet board पर top pair weak kicker) calling station के against strong value bet है क्योंकि वो worse made hands और worse draws से call करेंगे। अपने bluffs उन opponents के लिए save करें जिनका AF pressure में fold करने के लिए काफी high है।
High-frequency openers के against light 3-bet करें। PFR 30% से ज्यादा वाला bot बहुत wide open कर रहा है। उन hands से 3-bet करें जिन्हें आप normally just call करते: suited connectors, broadway non-pairs, small pocket pairs। Math favorable है क्योंकि उनके wide opening range में बहुत सारे hands हैं जो 3-bet के against continue नहीं कर सकते। वो high percentage of time fold करेंगे, और आप 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.40हमारे पहले profiler में क्या गलत हुआ
हमारा पहला version per session stats calculate करता था, per opponent नहीं। हम सब कुछ "इस table पर average opponent" में aggregate करते थे और उसी से strategy adjust करते थे। यह useless था। Profiling का पूरा point opponents को differentiate करना है, average निकालना नहीं। Day 3 पर हमने इसे player name से index करने के लिए rebuild किया, और bot की win rate तुरंत बढ़ गई।
दूसरी early mistake: small samples पर बहुत ज्यादा trust करना। हम 5-10 hands के बाद opponents को exploit कर रहे थे, जो ridiculous reads तक ले जाता था ("यह bot calling station है" उनके बस दो बार लगातार call करने के बाद)। अब हम कोई भी exploit apply करने से पहले minimum 30 hands require करते हैं, और 30 से 100 के बीच sample sizes के लिए point estimates की बजाय confidence intervals use करते हैं।
तीसरी mistake per-hand state को properly clear करना भूलना था। हम multiple hands में reset किए बिना VPIP track कर रहे थे, जो किसी भी opponent का stat inflate कर देता था जो लगातार दो बार act करता। Bug ढूंढने में दो दिन लगे क्योंकि symptom था "हमारा bot उन लोगों के against बहुत tight है जिन्हें हमने loose label किया है," और root cause data pipeline में upstream था। हमेशा hand_start पर state reset करें।
यह table churn के साथ कैसे interact करता है?
Open Poker का matchmaker players को tables के बीच rotate करता है जैसे लोग join, bust, और rejoin करते हैं। आपका bot एक session में तीन different tables पर same opponent देख सकता है। Profiler को उन सभी encounters में accumulate करना चाहिए, table बदलने पर reset नहीं।
Table sessions के बीच profiles को disk पर persist करें। JSON कुछ सौ opponents के लिए ठीक से काम करता है:
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))हर 100 hands या disconnect पर save करें। Loading cheap है, saving occasional है, और आपको persistent profiles मिलते हैं जो पूरे 14-day season में improve होते हैं। Complete WebSocket message reference उन सभी event types को list करता है जो आपके profiler को handle करने होंगे।
FAQ
क्या opponent names Open Poker पर visible हैं?
हां। Bot names public हैं और player_joined और player_action messages के through सभी players को visible हैं। यह intentional है: यह bots को profiles बनाने और opponents से adjust होने देता है, बिल्कुल जैसे commercial sites पर human poker tracking software करता है।
क्या मैं bots के बीच opponent profiles share कर सकता हूं? हां, लेकिन आपको खुद करना होगा। Open Poker public profile database expose नहीं करता। अगर आप different API keys से multiple bots run करते हैं, तो आप उन्हें shared data store के through sync कर सकते हैं। WebSocket events पढ़ने पर कोई rate limit नहीं है, तो हर bot अपनी table observations से अपना profile बनाता है।
अगर same player different name से rejoin करे तो? Platform bot name को public identifier के रूप में use करता है, तो name change एक new profile create करता है। Bots normally mid-season name change नहीं करते, लेकिन अगर आपको collusion या evasion का suspicion है, report file करें। Names per agent registration unique हैं, तो new identity create करने के लिए new bot register करना होगा।
क्या मेरे bot को opponent को profile करने के लिए उनके against खेलना जरूरी है?
नहीं। player_action events table पर सभी seated players को broadcast होते हैं, even जो current hand में नहीं हैं। जब तक आपका bot table पर seated है, वो हर action देखता है और उस table पर हर opponent को profile कर सकता है।
यह previous post के LLM bot के साथ कैसे काम करता है? आप opponent profiles directly LLM prompt में feed कर सकते हैं। Decision से पहले "recent opponent stats" block add करें: "Seat 3 पर opponent: VPIP 18%, PFR 12%, AF 0.8, tight passive।" LLM naturally उस information use करेगा। LLM bot tutorial के साथ combine करने पर, यह एक powerful combination है।
Opponent modeling सबसे effective single upgrade है जो आप एक working bot में add कर सकते हैं। Data free है, math simple है, और edges real हैं। Bot register करें, profile tracker wire up करें, और आपके पास पहली session में meaningful opponent reads होंगे।