Skip to content
[OPEN_POKER]

Monte Carlo Poker Equity Calculator in Python

JJoão Carvalho||9 min read

A Monte Carlo poker equity calculator is the first math upgrade that changes real decisions. Instead of saying "I have a draw, maybe call," your bot can estimate how often it wins, compare that number to pot odds, and fold the expensive guesses.

Part of: The Complete Guide to Building an AI Poker Bot in 2026 - the full pillar covering frameworks, decision logic, equity, testing, and where to compete.

Key Takeaways

  • Monte Carlo equity estimates win probability by sampling unknown opponent cards and future board cards.
  • Open Poker gives your bot the exact pot and call amount in your_turn, so equity can flow straight into pot-odds decisions.
  • Start with 2,000 to 5,000 trials per decision. More is cleaner, but action latency matters.

What does Monte Carlo equity solve?

Monte Carlo equity solves the gap between made-hand labels and future-card reality. On Open Poker, a bot has 120 seconds to act and receives the pot, board, valid actions, and turn token in each your_turn message (Open Poker actions docs). That is enough to estimate whether a call is profitable before sending the action.

The calculator asks: if we deal the unknown cards many times, how often does my hand win or split? That number is equity.

If the pot is 300 and the call is 100, your pot odds are:

100 / (300 + 100) = 25%

If your sampled equity is 36%, calling is profitable before future betting effects. If your equity is 18%, folding saves chips. The number won't be perfect, but it is much better than hard-coding "call any flush draw."

What inputs do you need from Open Poker?

You need four values: hero hole cards, community cards, pot size, and call amount. Open Poker sends community cards and pot in your_turn; your bot should cache hole cards from the private hole_cards message. The WebSocket protocol is JSON-based and any language can use it (WebSocket docs).

def call_amount(valid_actions):
    for action in valid_actions:
        if action["action"] == "call":
            return action["amount"]
    return 0
 
 
def pot_odds(pot, call):
    if call <= 0:
        return 0
    return call / (pot + call)

Do not estimate the pot yourself if the server gives it to you. Bots get into weird bugs when they reconstruct state from earlier player_action events and miss a reconnect, rejected action, or side-pot detail. Let the server own state. Let your bot own decisions.

How do you compare final hands?

Use PokerKit's StandardHighHand.from_game() to compare final Hold'em hands. PokerKit supports standard high-hand evaluation and normal comparison operators, with examples in its hand-evaluation docs (PokerKit hand evaluation).

from pokerkit import StandardHighHand
 
RANKS = "23456789TJQKA"
SUITS = "cdhs"
DECK = [r + s for r in RANKS for s in SUITS]
 
 
def compact(cards):
    return "".join(cards)
 
 
def best_hand(hole, board):
    return StandardHighHand.from_game(compact(hole), compact(board))

That small wrapper keeps the rest of the calculator readable. Open Poker cards already look like As, Td, and 7c, so you don't need a custom converter unless your bot stores cards differently.

If performance becomes a problem, cache parsed cards. PokerKit's docs warn that repeated string parsing is expensive. For a first bot, direct strings are fine. For a bot running 10,000 trials per action across multiple hosted bots, profiling becomes worth it.

How do you estimate equity against one opponent?

Sample the missing board cards and one opponent hand, then compare final hands. At 5,000 trials, a simple Python calculator is usually stable enough for live Open Poker decisions. PokerKit's own benchmark reports more than one million standard high-hand evaluations per second on one laptop CPU, so the evaluator is not the first bottleneck (PokerKit docs).

from random import sample
 
 
def estimate_equity(hero, board=None, opponents=1, trials=5000):
    board = board or []
    known = set(hero + board)
    missing_board = 5 - len(board)
    equity = 0.0
 
    for _ in range(trials):
        deck = [card for card in DECK if card not in known]
        draw_count = opponents * 2 + missing_board
        draw = sample(deck, draw_count)
 
        opp_holes = [
            draw[i * 2 : i * 2 + 2]
            for i in range(opponents)
        ]
        runout = board + draw[opponents * 2 :]
 
        hero_hand = best_hand(hero, runout)
        all_hands = [hero_hand] + [
            best_hand(opp, runout)
            for opp in opp_holes
        ]
 
        winning_hand = max(all_hands)
        winners = [hand for hand in all_hands if hand == winning_hand]
 
        if hero_hand == winning_hand:
            equity += 1 / len(winners)
 
    return equity / trials

This function gives split pots partial credit. If your hand ties with one opponent, you get half a win. If it ties three ways, you get one third. That matters more often than people expect on paired boards and chopped straight boards.

How do you turn equity into an action?

Compare equity to pot odds, then add a small safety margin. Open Poker games are 6-max with fixed 10/20 blinds and table buy-ins from 1,000 to 5,000 chips (season docs), so one bad call can still cost a meaningful share of your stack.

def equity_decision(msg, hole_cards, opponents=1):
    actions = {a["action"]: a for a in msg["valid_actions"]}
 
    if "check" in actions:
        return {"action": "check"}
 
    if "call" not in actions:
        return {"action": "fold"}
 
    call = actions["call"]["amount"]
    pot = msg["pot"]
    board = msg.get("community_cards", [])
 
    equity = estimate_equity(
        hero=hole_cards,
        board=board,
        opponents=opponents,
        trials=3000,
    )
 
    required = pot_odds(pot, call)
    margin = 0.03
 
    if equity > required + margin:
        return {"action": "call"}
    return {"action": "fold"}

That policy is still passive. It calls profitable spots but doesn't raise value hands or semi-bluff strong draws. Pair it with poker bot betting strategy once the calculator works.

How many trials should a live bot run?

Start with 3,000 trials on flop and turn, then lower it if latency matters. Open Poker allows 20 WebSocket messages per second per connection and gives 120 seconds per action, but your bot should act quickly anyway. Slow bots miss more edge when they block the event loop.

Use different trial counts by street:

StreetSuggested trialsWhy
Preflop1,000Good enough for rough range checks
Flop5,000Two cards remain, variance is high
Turn3,000One card remains, estimates stabilize faster
River0No simulation needed, evaluate made hands

Preflop equity against random hands is not enough by itself. You also need position ranges because opponents are not playing random cards. Use poker bot position ranges to narrow which hands your bot opens before the flop.

What are the traps?

The biggest trap is treating "equity now" as "profit always." Equity ignores future betting. A flush draw might have 35% equity on the flop, but if the turn misses and your opponent jams, you may have to fold before realizing that equity.

The second trap is opponent count. Equity against one random hand is not equity against five active players. Multiway pots crush weak draws because someone else is more likely to improve. Increase the opponents argument when more players remain in the hand.

The third trap is table texture. If your bot is against very loose opponents, random-hand assumptions are closer. Against tight bots, their calling range is stronger than random, and your equity estimate is too optimistic. That's where opponent modeling starts paying for itself.

How do you keep the calculator fast?

Keep it fast by doing less work before the river. A live bot does not need lab-grade precision on every marginal call. It needs the right answer often enough, quickly enough, without blocking its WebSocket loop. That means caching, street-based trial counts, and early exits.

Use three simple rules:

OptimizationWhy it helps
Skip river simulationAll five board cards are known
Lower trials in tiny potsA 20-chip mistake is not worth 10,000 trials
Cache repeated inputsSame hole and board can appear in retries or resyncs
equity_cache = {}
 
 
def cached_equity(hero, board, opponents, trials):
    key = (tuple(sorted(hero)), tuple(board), opponents, trials)
    if key not in equity_cache:
        equity_cache[key] = estimate_equity(hero, board, opponents, trials)
    return equity_cache[key]

The cache does not need to live forever. Clear it between hands or cap it at a few hundred entries. The goal is to avoid accidental repeated work, not build a database of every possible Hold'em state.

If your bot uses an LLM for decisions, run equity before the LLM call and pass the percentage into the prompt. Numbers make LLM poker decisions less vague.

FAQ

Is Monte Carlo equity exact?

No. It is a sampled estimate. Exact enumeration is possible for many spots, but Monte Carlo is easier to wire into a live bot and fast enough for Open Poker's 120-second action timeout.

Should my bot run equity preflop?

Only as a secondary signal. Preflop decisions should start from position ranges because A9o on the button and A9o under the gun are not the same hand. Equity against random cards misses that context.

Can I use this without PokerKit?

Yes, but then you need another hand evaluator. PokerKit saves time because it supports Hold'em hand construction and comparison directly. Writing your own evaluator is a fun project and a bad first production decision.

How do I test whether this improves my bot?

Run two versions for at least a few hundred hands: one with your old call logic and one with equity-gated calls. Track net chips, hands played, and busted sessions through the Open Poker season stats and leaderboard.

Weiterlesen