Fast Texas Hold'em equity calculator with precomputed preflop lookup table
Project description
py-poker-equity
Fast Texas Hold'em equity calculator for Python. Computes win/loss/tie percentages for heads-up matchups at any stage of a hand.
- Preflop: Instant lookup from a precomputed table (all 169x169 canonical matchups)
- Flop/Turn/River: Exact enumeration of remaining board cards (no Monte Carlo, no approximation)
- Zero dependencies: Pure Python, no C/Rust compilation, nothing to break
Installation
pip install py-poker-equity
Or install from source:
git clone https://github.com/mitchtabian/py-poker-equity.git
cd py-poker-equity
pip install -e .
Quick Start
from py_poker_equity import get_equity
# Preflop: AK suited vs pocket queens
result = get_equity(["Ah", "Kh"], ["Qd", "Qs"])
# {'a_win': 46.15, 'b_win': 53.15, 'tie': 0.70}
# Flop: AK suited vs pocket queens on a friendly board
result = get_equity(["Ah", "Kh"], ["Qd", "Qs"], board=["7h", "4h", "2c"])
# {'a_win': 54.55, 'b_win': 43.94, 'tie': 1.52}
# Turn
result = get_equity(["Ah", "Kh"], ["Qd", "Qs"], board=["7h", "4h", "2c", "Jd"])
# {'a_win': 43.18, 'b_win': 56.82, 'tie': 0.0}
# River (deterministic — one player wins or it's a tie)
result = get_equity(["Ah", "Kh"], ["Qd", "Qs"], board=["7h", "4h", "2c", "Jd", "Ac"])
# {'a_win': 100.0, 'b_win': 0.0, 'tie': 0.0}
API Reference
Card Notation
Cards are strings with rank + suit:
- Ranks:
2,3,4,5,6,7,8,9,10,J,Q,K,A - Suits:
h(hearts),d(diamonds),c(clubs),s(spades)
Examples: "Ah" (ace of hearts), "10s" (ten of spades), "2c" (two of clubs)
get_equity(hand_a, hand_b, board=None)
Calculate win/loss/tie percentages for a heads-up matchup.
Parameters:
hand_a— list of 2 card strings (player A's hole cards)hand_b— list of 2 card strings (player B's hole cards)board— list of 0, 3, 4, or 5 card strings (community cards). DefaultNone= preflop.
Returns: dict with keys a_win, b_win, tie (percentages 0-100, rounded to 2 decimal places)
Raises: ValueError if duplicate cards are detected or board has invalid number of cards.
from py_poker_equity import get_equity
# Preflop (uses lookup table — instant)
get_equity(["Ah", "Kh"], ["Qd", "Qs"])
# Flop (enumerates ~946 remaining board combos)
get_equity(["Ah", "Kh"], ["Qd", "Qs"], board=["7h", "4h", "2c"])
# Turn (enumerates ~44 remaining cards)
get_equity(["Ah", "Kh"], ["Qd", "Qs"], board=["7h", "4h", "2c", "Jd"])
# River (just evaluates — one comparison)
get_equity(["Ah", "Kh"], ["Qd", "Qs"], board=["7h", "4h", "2c", "Jd", "Ac"])
get_preflop_equity(hand_a, hand_b)
Look up preflop equity directly from the precomputed table. This is called automatically by get_equity when no board is provided, but you can call it directly if you want.
Parameters:
hand_a— list of 2 card stringshand_b— list of 2 card strings
Returns: dict with keys a_win, b_win, tie
from py_poker_equity import get_preflop_equity
get_preflop_equity(["Ah", "Kh"], ["Qd", "Qs"])
# {'a_win': 46.15, 'b_win': 53.15, 'tie': 0.70}
evaluate_hand(hole_cards, board)
Evaluate the best 5-card poker hand from hole cards + board.
Parameters:
hole_cards— list of 2 card stringsboard— list of 3-5 card strings
Returns: HandResult namedtuple with:
hand_rank— int (1 = Royal Flush, 10 = High Card)hand_name— string (e.g. "Full House", "Pair")best_five— list of the 5Cardnamedtuples that make the best handtiebreaker— tuple used for comparing hands of the same rank
from py_poker_equity import evaluate_hand
result = evaluate_hand(["Ah", "Kh"], ["Qh", "Jh", "10h", "2c", "3d"])
print(result.hand_name) # "Royal Flush"
print(result.hand_rank) # 1
result = evaluate_hand(["7s", "7d"], ["7c", "Ks", "Kh", "2c", "3d"])
print(result.hand_name) # "Full House"
print(result.hand_rank) # 4
Hand rank reference:
| Rank | Hand |
|---|---|
| 1 | Royal Flush |
| 2 | Straight Flush |
| 3 | Four of a Kind |
| 4 | Full House |
| 5 | Flush |
| 6 | Straight |
| 7 | Three of a Kind |
| 8 | Two Pair |
| 9 | Pair |
| 10 | High Card |
compute_winner(hand_a, hand_b)
Determine the winner between two HandResult objects.
Parameters:
hand_a—HandResultfromevaluate_handhand_b—HandResultfromevaluate_hand
Returns: The winning HandResult object, or None if it's a tie.
from py_poker_equity import evaluate_hand, compute_winner
board = ["9c", "7s", "5h", "2c", "3d"]
aces = evaluate_hand(["Ah", "Ad"], board)
kings = evaluate_hand(["Kh", "Kd"], board)
winner = compute_winner(aces, kings)
if winner is aces:
print("Aces win!")
elif winner is kings:
print("Kings win!")
else:
print("It's a tie!")
# Output: Aces win!
Preflop Table
The preflop lookup table ships with the package (752 KB). It contains exact equity for all 14,365 canonical hand matchups, sourced from the oscar6echo/Poker2 dataset which was computed via exhaustive enumeration of all possible boards.
If the table gets deleted or you want to regenerate it:
python -m py_poker_equity
This converts the reference CSV (included in the test fixtures) into the JSON lookup format. Takes about 2 seconds.
How It Works
Preflop: There are 169 canonical starting hand types in Texas Hold'em (13 pairs + 78 suited + 78 offsuit). The preflop table stores the exact equity for every possible matchup between these hand types. At runtime, hole cards are mapped to their canonical type and the result is looked up — no computation needed.
Post-flop: With 3+ community cards dealt, the remaining unknown cards are few enough to enumerate exhaustively:
- Flop (2 cards to come): ~946 possible board completions
- Turn (1 card to come): ~44 possible river cards
- River (board complete): Just evaluate and compare
For each possible board completion, both hands are evaluated and the winner is tallied. This gives exact results — no simulation, no approximation.
Performance
| Stage | Method | Speed |
|---|---|---|
| Preflop | Lookup table | Instant |
| Flop | Enumerate ~946 boards | ~50ms |
| Turn | Enumerate ~44 cards | ~2ms |
| River | Single evaluation | <1ms |
Limitations
This library is heads-up only (2 players). It does not support multi-way equity calculations (3+ players).
Why? The preflop lookup table is the constraint. For 2 players there are ~14,000 canonical matchups — a few hundred KB. For 3 players it's 169^3 = ~4.8 million matchups (~115 MB). For 4 players it's ~815 million. It blows up exponentially and becomes impractical to ship as a pip package.
Post-flop multi-way is still feasible using the evaluate_hand and compute_winner functions directly. The enumeration logic is the same — you just evaluate 3+ hands per board runout instead of 2. On the flop that's ~946 board completions, which is fast regardless of player count. If you need multi-way equity post-flop, you can write a small wrapper around evaluate_hand:
from itertools import combinations
from py_poker_equity.evaluator import evaluate_hand, compute_winner, parse_card
def get_multiway_equity(hands, board):
"""
hands: list of hole card lists, e.g. [["Ah", "Kh"], ["Qd", "Qs"], ["Jc", "10c"]]
board: list of 3-5 board cards
"""
all_known = [c for h in hands for c in h] + board
known_set = set((parse_card(c).rank, parse_card(c).suit) for c in all_known)
RANKS = ['2','3','4','5','6','7','8','9','10','J','Q','K','A']
SUITS = ['h','d','c','s']
remaining = [f"{r}{s}" for r in RANKS for s in SUITS
if (parse_card(f"{r}{s}").rank, parse_card(f"{r}{s}").suit) not in known_set]
cards_needed = 5 - len(board)
wins = [0] * len(hands)
ties = 0
for extra in combinations(remaining, cards_needed):
full_board = board + list(extra)
results = [evaluate_hand(h, full_board) for h in hands]
# Find the best hand
best = results[0]
best_indices = [0]
for i in range(1, len(results)):
w = compute_winner(best, results[i])
if w is results[i]:
best = results[i]
best_indices = [i]
elif w is None:
best_indices.append(i)
if len(best_indices) == 1:
wins[best_indices[0]] += 1
else:
ties += 1
total = sum(wins) + ties
return {
'wins': [round(w / total * 100, 2) for w in wins],
'tie': round(ties / total * 100, 2),
}
The only thing this library can't do for multi-way is preflop — because there's no lookup table for 3+ players. If you know the stage of the all-in (flop, turn, or river), multi-way works fine.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file py_poker_equity-0.1.2.tar.gz.
File metadata
- Download URL: py_poker_equity-0.1.2.tar.gz
- Upload date:
- Size: 189.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
eb51d4315d723dbc318a68312fe71fc63e41d34332789c7ba2b3cef5c4d8c18e
|
|
| MD5 |
843385e027f5172cb9e84f2a663971ff
|
|
| BLAKE2b-256 |
c2dc40ab95e0d96a1cbda9a14fe1bae3f3f5256f040444d6322f2fea87cd8040
|
File details
Details for the file py_poker_equity-0.1.2-py3-none-any.whl.
File metadata
- Download URL: py_poker_equity-0.1.2-py3-none-any.whl
- Upload date:
- Size: 157.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c828469f66652e275a1e97719fac995c0994ed7969d66911968de8e087f64227
|
|
| MD5 |
b019077d44ecc9167522d7ee3a2c38b0
|
|
| BLAKE2b-256 |
598afae1a9ad1cf27556af432626cd0e8da0938461c39a645475698c527b1d43
|