Skip to main content

poker-equity

Rust

An equity engine for poker. Give it a game, some hands, a board and some dead cards, and it tells you what share of the pot each player wins.

Beta, and written almost entirely by AI. This is 0.1 and should be read as one. The API is not settled: names, signatures and the notation may all change before 1.0, and a few corners of the behaviour are still being decided. Pin an exact version rather than a range. The hand rankings are cross-checked against pokerkit for the games whose hand spaces are small enough to check — see Correctness — but nothing here has been used in anger yet.

Install

Python. Any CPython from 3.10 up:

pip install poker-equity

Wheels are built for Linux (x86_64 and aarch64), macOS (Intel and Apple Silicon) and Windows (x64). Anywhere else pip builds from source, which needs a Rust toolchain.

import poker_equity as pc

r = pc.chunk_from_text("holdem", ["AhKh", "QsQd"], samples=500_000)
for seat in r["players"]:
    print(f"{seat['equity']:.4f} +/- {seat['std_error']:.4f}")

More of it in Python.

Rust.

cargo add poker-equity

Then a whole program is:

use poker_equity::{odds::{equity, EquityRequest, Target}, variants::Holdem};

fn main() -> Result<(), poker_equity::error::PokerError> {
    let request = EquityRequest::from_text(Holdem, &["AhKh", "QsQd"], "", "")?;
    let result = equity(&request, Target::Samples(500_000))?;
    println!("{:.2}%", result.equities()[0].percent());   // about 46.2%
    Ok(())
}

From a clone. pip install . builds and installs the Python module with the same settings the published wheels use; for Rust, point the dependency at the clone with path = "../poker-equity".

Games

Showdowns a second, per thread: dealing, evaluating and splitting the pot, end to end. Measured heads-up on one core of an AMD Ryzen 7 9800X3D. Run cargo run --release --bin benchmark for your own machine.

Key Game Per thread
holdem Hold'em 6.86 M/s
short_deck Short Deck Hold'em — 36 cards, a flush beats a full house 7.14 M/s
omaha Omaha 2.86 M/s
omaha_five 5-Card Omaha 2.15 M/s
omaha_six 6-Card Omaha 1.67 M/s
omaha_hi_lo Omaha Hi/Lo, eight or better 1.77 M/s
omaha_five_hi_lo 5-Card Omaha Hi/Lo 1.29 M/s
courchevel Courchevel 2.26 M/s
courchevel_hi_lo Courchevel Hi/Lo 1.33 M/s
stud Seven-Card Stud 4.52 M/s
stud_hi_lo Seven-Card Stud Hi/Lo 4.12 M/s
razz Razz 4.84 M/s
deuce_seven 2-7 Lowball, single draw 10.55 M/s
five_card_draw 5-Card Draw, single draw 10.83 M/s
badugi Badugi, single draw 5.35 M/s

Threads share nothing while they sample, so multiply by however many you give it. More seats cost more: six-handed hold'em runs at 3.51 M/s a thread, and six-handed Omaha at 1.31 M/s.

A run spreads over four threads unless told otherwise, which is polite rather than greedy: several calculators may be open at once and one must not starve the others. EquityRequest::with_threads takes as many as you want to give it, and nothing needs passing to get the default.

The lowball draw games model one draw. Equity in triple draw is undefined without a drawing strategy — a made eight-low and a four-card draw are not comparable until you say how the draw resolves — so one draw is modelled and said so, rather than a number published from an invented model. 5-card draw has a single draw by its own rules, so there the one draw is the whole game.

Examples

Two games, to show that the shape of the question does not change with the game being asked about.

Hold'em: ace-king suited against a pair of queens

use poker_equity::{odds::{equity, EquityRequest, Target}, variants::Holdem};

let hero = "AhKh";        // one field per seat
let villain = "QsQd";
let board = "";           // nothing dealt yet
let dead = "";            // no cards out of play

let request = EquityRequest::from_text(Holdem, &[hero, villain], board, dead)?;
let result = equity(&request, Target::Samples(500_000))?;

for (seat, player) in result.equities().iter().enumerate() {
    println!("seat {}: {:.2}% ± {:.2}", seat, player.percent(), player.margin_percent());
}
// seat 0: 46.20% ± 0.13
// seat 1: 53.80% ± 0.13

Seats come back in the order they were passed in, so seat 0 is hero.

2-7 draw: a pat nine against a one-card draw

Change the variant, and the fields mean what that game means by them:

use poker_equity::{odds::{equity, EquityRequest, Target}, variants::DeuceSeven};

let villain = "9s7d5c4h2s";   // five cards: a made nine-low, standing pat
let hero = "8h6d4s3c";        // four cards, so one is still to come
let board = "";               // a draw game never has a board
let dead = "Kc";              // the king hero threw away

let request = EquityRequest::from_text(DeuceSeven, &[villain, hero], board, dead)?;
let result = equity(&request, Target::Samples(1_000_000))?;
// seat 0 (villain): 78.57% ± 0.00
// seat 1 (hero):    21.43% ± 0.00

Three things differ from the hold'em example, and every one of them is the game rather than the API.

A draw or stud field may be short. Five cards is a hand standing pat; four is a hand drawing one, and the card still to come is dealt. A community game may not do this. Hold'em deals both hole cards at once, so a field holding one card is a miscount rather than a hand in progress, and is an error — write A* there if you mean one known card and one unknown.

Discards are dead cards. A card thrown away is out of the deck but was seen, so it belongs in the dead field rather than nowhere.

There is no error bar, though a million deals were asked for. Only forty-two cards can arrive, so the whole question is forty-two deals wide and was walked rather than sampled: result.samples comes back as 42. Nine of those cards — the deuces, fives and sevens still in the deck — give hero a better low than a nine, and 9/42 is 21.43% exactly.

Hands may be only partly known — AKs, 2 c, A** are all valid — and the answer comes with an error bar, or none at all when the spot was small enough to walk rather than sample. To follow a long run while it goes, see Watching a long run. For how the hand rankings are checked, see Correctness.

Writing hands

A card is a rank and a suit: uppercase rank, lowercase suit on the way out, but anything on the way in. Beyond that, four patterns:

Written Means
Ah the ace of hearts
A any ace
c any club
* any card

A rank binds to a suit immediately after it, and nothing else binds. So 2c is one card and 2 c is two — a deuce and a club. Both are valid and they differ by a whole card, so the wrong reading gives a plausible wrong answer rather than an error, which is why the rule is worth knowing.

* never binds, which is what makes AA** unambiguously four cards. If that makes the count wrong, the error says so.

"AhKh"      // two named cards
"A Kh"      // any ace, and the king of hearts
"2 c"       // a deuce and a club — two cards
"2c"        // the deuce of clubs — one card
"AA**"      // two aces and two unknowns (Omaha)

What a wildcard costs

A card you name exactly gets dealt. A card you only describe -- A, c, * -- has to be found: the engine deals, checks the cards fit every seat, and deals again if they do not. The answer is the same either way; only the time changes.

It is worst in stud, where each player holds seven private cards -- four-handed stud claims twenty-eight of the fifty-two before anything is scored.

Spot Deals tried per simulation
AA vs KK, hold'em 1
AA** vs KK**, Omaha 1.2
A23 vs 456, stud 2.9
two AA** at a six-handed Omaha table 6.5
A23 any number of ways, razz 1

Where searching stops working -- below one kept deal in twenty -- the engine deals each seat from what is left instead, and weights the deal to undo the bias that introduces. Same answer, sooner, and spots that were refused outright now answer. Read effective_samples there rather than acceptance: it is what the error bar is divided by, and is_weighted says which applies. Such an average is consistent rather than exactly unbiased, by a 1/n term under the bar's 1/√n.

Razz needs no suits. It has no flushes and never looks at a suit, so A23 and Ah2h3h ask the same question -- and the engine pins the suits for you when you leave them off. Razz runs at one deal per simulation however many players are in the hand, so writing suits there buys nothing.

Everywhere else, writing the suits you actually mean is still the cheapest thing you can do. A real hand has suits; naming them is both more faithful to the spot and quicker to answer than either way of dealing it.

The underlying problem, if you want to read about it, is sampling a uniformly random bipartite matching -- one deal that satisfies every seat at once, drawn without favouring any of them.

Ranges

Where a hand holds exactly two cards, a field may be a range instead:

"AKs"       // suited ace-king, four combinations
"AKo"       // offsuit, twelve
"AK"        // either, sixteen
"22+"       // every pair
"A2s+"      // A2s through AKs
"KTs+"      // KTs, KJs, KQs
"JTs-76s"   // the suited connectors between them
"AKs, 22"   // alternatives, comma separated — ten combinations

Ranges exist only where a hand is two cards. In Omaha and stud there is no range grammar at all, which is what makes the collision between AKs the range and A Ks the two cards impossible there rather than merely unlikely.

Percentage ranges (top 15%) are not supported. There is no one ordering of starting hands by strength — it changes with the game, the table size and whose chart you trust — so naming the hands is the honest way to ask.

Boards, dead cards, and hands still being dealt

A board is how much of it you know, not which street you are on. Nought to five cards, wildcards allowed anywhere among them:

""              // nothing showing
"2c 7d 9h"      // a flop
"2c 7d 9h * Ks" // flop and river known, turn not
"2c 7d 9h Ts 4c"// finished: who won?

So "what if the turn is a heart" is "2c 7d 9h h", and a finished board is a fair question with an exact answer. Courchevel is the one game with a floor: its first board card is face up before the betting, so a Courchevel request showing nothing is refused — that spot is five-card Omaha, not Courchevel.

Write "*" there if the card has been dealt and you have not seen it. That is a fair question, and it is how to ask what the turned card was worth: run the spot with the card named, run it again with *, and the gap between them is the answer. It comes back as the five-card Omaha number, because that is what Courchevel with an unknown first card is.

Dead cards must be exact, because "a club is dead" does not say which club and every reading changes the answer. They must also be cards the game actually deals — a deuce is not dead in short deck, it is a mistake about the game, and is refused in the dead field exactly as it would be in a hand.

How many cards a field may name is three rules, not one, because the games deal differently:

A short field Why
Community refused both hole cards arrive at once
Stud allowed, equally for every seat the whole table is on the same street
Draw allowed, seat by seat a short field is the draw

Stud is the one worth care. Third street is three cards for everybody, so A23 against 2345 is not a table caught mid-deal, it is a miscount, and it is refused. A draw game is the opposite: five against four is a pat hand against a one-card draw, and saying so is the point of the notation.

In stud and the draw games, cards arrive over time, so a field says what a player holds now and whatever is missing is still to come:

EquityRequest::from_text(Stud, &["Ah2c3d", "QsQdJs"], "", "")?;
//                        three known, four still to be dealt

EquityRequest::from_text(DeuceSeven, &["7h5c4d3s", "9h8c6d5h2c"], "", "Kd")?;
//                        Hero draws one; the king he threw is dead. Villain stands pat.

A community game wants every hole card, because they are all dealt at once: there is no moment at which a hold'em player holds one card. So a short field there is a miscount and is rejected. To say "an ace and something I cannot see", write the unknown card as a wildcard — A*, not A.

Getting an answer

equity takes a target, which is how you say what "good enough" means:

Target Runs until
Target::Samples(500_000) half a million deals are done
Target::StandardError(0.001) the answer is that precise — "make this good enough to trust"
Target::Exact every possible deal has been walked, falling back to tight sampling when there are too many

StandardError is usually the one you want. A sample count is a guess at how long precision takes; a precision target just asks for the precision.

If a spot has fewer possible deals than you asked to sample, it is walked instead of sampled — AhAd against KsKc on a flop has only 990 run-outs, so asking for half a million deals gets all 990 and a std_error of exactly zero. You are never given more work than you asked for: a spot with two million possible deals is sampled, not walked, when you asked for five hundred thousand.

StandardError and Exact put no cap on the work, so they walk whatever can be walked — an exact answer beats any error bar.

Watching a long run

A run of millions of deals takes long enough that you will want to repaint a table while it goes, and to notice if the user cancelled. Pass a function to be called after each batch:

use poker_equity::odds::equity_with_progress;

let result = equity_with_progress(&request, Target::Samples(5_000_000), |progress| {
    println!("{} deals: {:.2}% (± {:.2}), keeping {:.0}% of deals",
        progress.samples,
        progress.equities[0].percent(),
        progress.equities[0].margin_percent(),
        progress.acceptance * 100.0);
})?;

|progress| { ... } is Rust's syntax for a function written inline — the names between the bars are its arguments, and the braces are its body. So that one takes a Progress and prints from it. A batch is a few milliseconds, which is a good rate to repaint at and a fine granularity to cancel at.

progress.acceptance is the share of attempted deals that could be used. It is 1.0 unless hands are competing for the same cards — several seats all wanting a five when only two are left — and a low figure is the difference between an answer that is slow and one that looks stuck.

progress.effective_samples is what the run is worth so far — equal to progress.samples unless the deals were weighted, and what the error bar is divided by.

Driving the loop yourself

If you want to own the loop — because cancelling, or threading, or merging results across machines is your business rather than the library's — the layer underneath is a single batch that returns sums:

use poker_equity::odds::{run_chunk, ChunkResult};

let mut total = ChunkResult::empty(2);
for seed in 0..10 {
    total.merge(&run_chunk(&request, 50_000, seed)?);
    if user_cancelled() { break; }
}
// 500,000 deals, or fewer if the user stopped it

run_chunk is pure and stateless: no callbacks, no cancellation token, no shared state. Everything in ChunkResult is a sum, so batches merge by addition, and the sums of squares are carried too — which is what makes the error bar free rather than something to compute separately. The deals' weights are summed the same way, so effective_samples merges as cleanly as the rest.

What comes back

pub struct PlayerEquity {
    pub equity: f64,      // share of the pot — the number that matters
    pub win: f64,         // taken outright
    pub tie: f64,         // shared
    pub low_equity: f64,  // the low half alone, in split games
    pub scoop: f64,       // both halves
    pub std_error: f64,   // zero when the answer was enumerated
}

Equity leads because it is the answer — it is what the money does over time. Wins and ties are colour. percent() and margin_percent() give the two numbers a table usually shows: the equity, and the half-width of a 95% interval around it.

The mask API

Text is a convenience. Underneath, a card is a u8 and a set of cards is a u64 with one bit per card, and that is what the engine speaks. A caller that already holds masks can skip the parser entirely:

use poker_equity::{cards::{Card, CardSet, Rank, Suit}, notation::HandSpec,
                       odds::EquityRequest, variants::Holdem};

assert_eq!(Card::new(Suit::Heart, Rank::Ace).index(), 50);   // rank * 4 + suit

let queen_of_spades = CardSet::from_cards(&["Qs".parse::<Card>()?]);

let request = EquityRequest::from_masks(
    Holdem,
    &[
        // any ace and any club
        HandSpec::from_slots(&[CardSet::of_rank(Rank::Ace), CardSet::of_suit(Suit::Club)]),
        // the queen of spades and anything
        HandSpec::from_slots(&[queen_of_spades, CardSet::FULL_DECK]),
    ],
    &[],                    // no board yet
    CardSet::EMPTY,         // nothing dead
)?;

A named card is a mask with one bit; "any ace" is a mask with four; "any card" is all fifty-two. The sampler never asks which kind it was handed, which is why a wildcard costs nothing on the fast path.

The layout is worth knowing if you are building masks yourself: index is rank * 4 + suit, ranks 0..13 running 23456789TJQKA and suits 0..4 running cdhs. So a rank is four adjacent bits and a suit is every fourth bit:

of_rank(Ace)  = 0b1111 << 48
of_suit(Club) = 0x1_1111_1111_1111
FULL_DECK     = (1 << 52) - 1

Both APIs are the same request — from_text parses into exactly what from_masks takes, once, before any card is dealt. A test runs both from the same seed and compares the raw sums.

Speed

The per-game figures are in Games above.

Underneath, a hand is scored by two array reads:

Hands/s
score — reads the lookup tables 99,726,346
evaluate_hand — names the hand 1,956,053

Both are kept. score is what the sampling loop compares; evaluate_hand is what tells a player they have two pair, and is the independent second implementation the tables are checked against.

The tables total 1,104 KB — four rankings sharing one perfect hash — and are generated by build.rs into OUT_DIR. Nothing generated is committed.

Correctness

Hand ranking is checked three ways. None of it stands in for use: the library is new and has not yet been run in earnest by anyone.

Against a reference evaluator, exhaustively. Every lookup table is swept against the readable evaluator beside it — all 2,598,960 five-card hands per ranking, plus seven-card hands. cargo test --release -- --ignored.

Against an outside implementation. The two above were both written here, from one reading of the rules, so they catch a mistake in either but not a rule misunderstood in both. validation/ checks the rankings against pokerkit, as far as each game allows: every five-card hand of every ranking and every four-card badugi holding are walked exhaustively, while seven-card hands are sampled and Omaha — 4.6 × 10¹¹ deals — cannot be walked at all. Everything checked agrees. See validation/README.md.

Against arithmetic. Sampling is checked against exact enumeration within four standard errors, which is the only test that catches a sampler that is uniform over the wrong thing. Equities sum to one; permuting seats permutes the answer; a hand against its own mirror splits exactly; a wildcard narrowed to a single card equals naming that card.

Weighted dealing needs this most, since a wrong weight moves the answer without moving anything a reader would notice. Where a spot is too large to walk it is checked by symmetry: seats asking for the same thing must be given the same equity.

Python

Four ways in — two that parse the notation, two that take masks. Building the module is under Install.

Call Does
chunk_from_text(variant, hands, board="", dead="", samples=100_000, seed=0, threads=0) samples that many deals
exact_from_text(variant, hands, board="", dead="") walks every deal, or returns None when there are too many
chunk(variant, hands, board, dead, samples, seed=0, threads=0) the same by mask
exact(variant, hands, board, dead) likewise
import poker_equity as pc

r = pc.exact_from_text("holdem", ["AhAd", "KsKc"], "2c 7d 9h")
print(r["players"][0]["equity"])        # 0.916161...
print(r["exact"], r["samples"])         # True 990

# or masks, skipping the parser: hands[seat][alternative][slot]
hands = [[[1 << pc.card_index("Ah"), 1 << pc.card_index("Kh")]],
         [[1 << pc.card_index("Qs"), 1 << pc.card_index("Qd")]]]
r = pc.chunk("holdem", hands, [], 0, 200_000, seed=3)

Every seat in r["players"] carries equity, win, tie, low_equity, scoop and std_error. Beside them sit the run's own totals — samples, weight_sum, share_sum, acceptance, effective_samples, weighted — which are sums rather than averages, so calling again with another seed and adding them is how you sample further. There is no precision target here as there is in Rust: ask for a count.

abi3-py310, so one wheel per platform covers CPython 3.10 upward. The GIL is released around sampling, so a long batch does not block the interpreter. pc.variants() lists every game with how it deals, so a caller needs no table of its own, and pc.parse_hand_field hands back masks so another parser can be checked against this one.

Averaging the sums yourself: divide by weight_sum, not samples. The two are equal for almost every request, so the wrong one costs nothing until a spot is weighted, then returns equities hundreds of times too small.

r = pc.chunk_from_text("stud", ["A23", "456", "789", "TJQ"], samples=200_000)
equity = [total / r["weight_sum"] for total in r["share_sum"]]   # not / samples

spread = (r["share_square_sum"][0]
          - 2 * equity[0] * r["square_weight_share_sum"][0]
          + equity[0] ** 2 * r["weight_square_sum"])
std_error = max(spread, 0.0) ** 0.5 / r["weight_sum"]

r["weighted"] and r["effective_samples"] are that run's own figures; tests/python/test_binding.py checks this rebuild against the engine's.

Building

cargo test                              # 234 tests, about ten seconds
cargo test --release -- --ignored       # the exhaustive sweeps
cargo run --release --bin benchmark     # speed, per game
cargo bench                             # the paths a refactor could slow

benches/engine.rs runs locally as ordinary criterion timings. CI runs the same definitions under callgrind, where what is counted is instructions rather than seconds, so a pull request's figures can be compared with the branch it came from without either number depending on the machine that produced it.

The tests spread across every core, which is not always what you want on the machine you are also using. scripts/quiet runs the same commands pinned to four cores at the lowest priority, and costs about fifteen percent of wall time:

scripts/quiet test
QUIET_CORES=2 scripts/quiet run --release --bin benchmark

Tests build at opt-level = 2, because the equity tests run a hundred thousand Monte Carlo deals apiece and that is minutes unoptimised against seconds optimised.

build.rs reports what it generated — how full the perfect hash is, how many distinct values each kernel has, what the tables weigh — when asked:

POKER_EQUITY_BUILD_STATS=1 cargo build

tests/fixtures/notation.tsv is the grammar's source of truth — a plain table of input and expected output, with a row for every rule above. It is a flat file rather than Rust so that another implementation of the notation can be checked against the same rows.

Releasing

A release goes to PyPI and crates.io together, from a version tag, and only from a tag: pushing to main tests and benchmarks but never publishes.

  1. Bump version in Cargo.toml — the Python package reads it from there — and merge that to main.

  2. Tag the merge and push the tag:

    git tag vX.Y.Z && git push origin vX.Y.Z
    
  3. .github/workflows/release.yml checks the tag matches Cargo.toml and sits on main, builds a wheel for every platform, installs each one and runs tests/python/test_binding.py against it, and packs the crate.

  4. Nothing is published until the release environment is approved in the Actions tab. Then both registries are uploaded to, each by trusted publishing, so there is no token stored anywhere.

A version cannot be published twice to either registry — only yanked — so the tag is the point of no return. To rehearse, run the workflow by hand from the Actions tab: nowhere builds and checks every wheel, and testpypi goes on to publish them to TestPyPI.

Metadata

Release files for poker-equity 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for poker-equity 0.1.0
File Size Uploaded
poker_equity-0.1.0.tar.gz 202.6 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for poker-equity 0.1.0
File
poker_equity-0.1.0-cp310-abi3-win_amd64.whl CPython 3.10 abi3 Windows x86-64 Details
poker_equity-0.1.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.10 abi3 Linux glibc 2.17+ x86-64 Details
poker_equity-0.1.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.10 abi3 Linux glibc 2.17+ ARM64 Details
poker_equity-0.1.0-cp310-abi3-macosx_11_0_arm64.whl CPython 3.10 abi3 macOS 11.0+ ARM64 Details
poker_equity-0.1.0-cp310-abi3-macosx_10_12_x86_64.whl CPython 3.10 abi3 macOS 10.12+ x86-64 Details

Total release size: 6.0 MB

Release files / poker_equity-0.1.0.tar.gz

Download URL poker_equity-0.1.0.tar.gz
Size 202.6 kB
Tags Source
SHA-256 checksum
How to use checksums
1333c86d9fb2257538f0f42ceea23b00f2aee133f8f173db9137863a0e291391
BLAKE2b-256 checksum
How to use checksums
214e95583a63354b3bba498611ec047344256adcd190e6048744d2248f14d545
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 19, 2026.

Transparency log

Release files / poker_equity-0.1.0-cp310-abi3-win_amd64.whl

Download URL poker_equity-0.1.0-cp310-abi3-win_amd64.whl
Size 1.0 MB
Tags CPython 3.10 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
f2cc19da37d3d3233194a05c7c307f5f588afd2617c26fe10b5b5f12a02f26ee
BLAKE2b-256 checksum
How to use checksums
b2135a191715f8f8a2c0c9c9e72c5f1e3f839f39f2e416597b9f97435a5beda1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 19, 2026.

Transparency log

Release files / poker_equity-0.1.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL poker_equity-0.1.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 1.2 MB
Tags CPython 3.10 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
5accb5204b2af41c71624670e2f69f1b95a2e86e5f6d41eace5cfaff6301cf10
BLAKE2b-256 checksum
How to use checksums
c69fd34e68df17d3d4d26c07f947af3bcaf7061635e63f1d5130d3372288d4f0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 19, 2026.

Transparency log

Release files / poker_equity-0.1.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL poker_equity-0.1.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 1.2 MB
Tags CPython 3.10 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
d39ce2ad927fa4da3a96e28c9bd2901cbe1d2f88c4a598c2e8471012f93159e5
BLAKE2b-256 checksum
How to use checksums
ac076fdd89ffd775d538b1057a12f232e42cc65258e7117d54b16df3fee630f5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 19, 2026.

Transparency log

Release files / poker_equity-0.1.0-cp310-abi3-macosx_11_0_arm64.whl

Download URL poker_equity-0.1.0-cp310-abi3-macosx_11_0_arm64.whl
Size 1.2 MB
Tags CPython 3.10 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
063192fb66935b8a11ec86f25bf9bb93e7f76d4068a1fff21c03b852d16666f1
BLAKE2b-256 checksum
How to use checksums
ce2eb3a1d9710c45ca0e29edf5ace5be580a553d39ecae759789d495f583f94e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 19, 2026.

Transparency log

Release files / poker_equity-0.1.0-cp310-abi3-macosx_10_12_x86_64.whl

Download URL poker_equity-0.1.0-cp310-abi3-macosx_10_12_x86_64.whl
Size 1.2 MB
Tags CPython 3.10 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
9056bafe4461495ccc6f7b44fa13deec1985e79e957e5f15d8953581c06999f8
BLAKE2b-256 checksum
How to use checksums
8501b109172868eb0ac465b394bacc3623ca4b26908295c4579687585d19e899
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 19, 2026.

Transparency log

Release history Release notifications | RSS feed

0.1.1

6 release files

This release

0.1.0 This release

6 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page