UNPKG

@idealic/poker-engine

Version:

Poker game engine and hand evaluator

139 lines (85 loc) 6.87 kB
# Poker.Stats Namespace Complete reference for the `Poker.Stats` namespace, providing tools for poker analytics and statistical processing. ```typescript import * as Poker from '@idealic/poker-engine'; ``` ## Main Function **`Poker.Stats(source: Game | Hand | StreetStat[]): StreetStat[]`** Extracts raw street-by-street, player-by-player statistics from a `Game` or `Hand` object. If an array of `StreetStat` is passed, it returns it unchanged. This is the primary function for accessing detailed statistical data. ## Core Methods **`Poker.Stats.aggregate(source: Game | Hand | StreetStat[], groups: ('player' | 'street' | 'venue')[]): AggregatedStats`** Aggregates statistics based on specified grouping criteria. It can group stats by player, street, or venue to provide summarized views of the data. **`Poker.Stats.forPlayer(game: Game, playerIdentifier: PlayerIdentifier): StreetStat[]`** Filters and returns all statistics for a specific player from a game. **`Poker.Stats.forPlayerStreet(game: Game, playerIdentifier: PlayerIdentifier, street: Street): StreetStat | undefined`** Finds and returns the statistics for a specific player on a specific street. **`Poker.Stats.getRows(source: Game | Hand | StreetStat[]): any[][]`** Returns the statistics as an array of rows, where each row is an array of stat values. Useful for exporting to CSV or other row-based formats. **`Poker.Stats.getColumnNames(): string[]`** Returns an array of all available statistic names in snake_case format, corresponding to the columns for `getRows`. ## Usage Examples ```typescript import * as Poker from '@idealic/poker-engine'; const game = Poker.Game({ /* ... */ }); // Get stats for a specific player const playerStats = Poker.Stats.forPlayer(game, 'Alice'); // Get stats for a player on a specific street const flopStats = Poker.Stats.forPlayerStreet(game, 'Alice', 'flop'); ``` ## Statistics Philosophy The stats engine distinguishes between **Stats** and **Metrics**: - **Stats**: These are the raw, fundamental counts tracked in real-time during a hand. They record basic events like `three_bet_ip_attempts` or `cbet_oop_challenges`. The system is optimized to capture these efficiently. - **Metrics**: These are derived, contextual calculations computed from one or more base stats. Metrics provide meaningful insights into player behavior, such as `three_bet_frequency` (calculated from `three_bet_attempts` and `three_bet_opportunities`). This separation allows for a lean, high-performance tracking system at the core, while enabling powerful, flexible analysis on top. ## Tracked Tactical Maneuvers The engine tracks a comprehensive set of common poker maneuvers to build detailed player profiles. For a full breakdown of the logic for identifying each maneuver and determining positional advantage (IP/OOP), please refer to the [Positional Stat Tracking Strategy](./APPENDIX_STATS_POSITIONING.md) document. ### Stat Breakdown Each maneuver is broken down into a set of granular stats and derived metrics for both the **aggressor** (the player making the move) and the **defender** (the player facing the move). This allows for a precise analysis of how a player acts and reacts in specific situations. **For the Aggressor:** _Base Stats:_ - **`{maneuver}_{ip/oop}_opportunities`**: How many times the player had a chance to make the move, either In Position (IP) or Out of Position (OOP). - **`{maneuver}_{ip/oop}_attempts`**: How many times the player actually made the move. - **`{maneuver}_{ip/oop}_takedowns`**: How many times the player's attempt resulted in them winning the pot without a showdown on that street. _Derived Metrics:_ - **`{maneuver}_{ip/oop}_frequency`**: The percentage of time a player makes the move when given the opportunity (`attempts` / `opportunities`). - **`{maneuver}_{ip/oop}_takedown_frequency`**: The success rate of the move; the percentage of time an attempt results in winning the pot (`takedowns` / `attempts`). **For the Defender:** _Base Stats:_ - **`{maneuver}_{ip/oop}_challenges`**: How many times the player faced this move. - **`{maneuver}_{ip/oop}_continues`**: How many times the player defended against the move by calling or raising. - **`{maneuver}_{ip/oop}_folds`**: How many times the player folded to the move. _Derived Metrics:_ - **`{maneuver}_{ip/oop}_continue_frequency`**: The percentage of time a player defends against the move (`continues` / `challenges`). - **`{maneuver}_{ip/oop}_fold_frequency`**: The percentage of time a player folds when facing the move (`folds` / `challenges`). ### List of Tracked Maneuvers **Pre-flop:** - Limp - 3-Bet - Squeeze (a 3-bet variant) - 4-Bet - 5-Bet - Steal Attempt - Open Shove **Post-flop:** - Continuation Bet (C-Bet) - Delayed C-Bet - Double Barrel / Triple Barrel - Probe Bet - Float Bet - Donk Bet - Check-Raise - Shove ## Data Aggregation and Warehousing The raw statistical events captured by the poker engine are written to a TimescaleDB hypertable named `poker_stats`. To enable high-performance analytics, these raw stats are then processed through a sophisticated, multi-layered aggregation pipeline. Instead of storing pre-calculated, aggregated numbers (like total bets or average VPIP), the system uses specialized statistical digests from the TimescaleDB Toolkit, such as `statssummary1d` and `uddsketch`. The pipeline works as follows: 1. **Ingestion**: Raw stats from the `poker_stats` table are fed into an `input_agg` function. This function converts numeric values (like bet amounts) into statistical digests. For example, a series of bet amounts becomes a single `statssummary1d` object that efficiently stores sum, count, min, max, etc. 2. **Hierarchical Rollups**: A series of TimescaleDB Continuous Aggregates automatically and efficiently roll up these digests into hierarchical time buckets: - **`poker_stats_hourly`**: Raw stats are aggregated into hourly summaries. - **`poker_stats_daily`**: Hourly digests are rolled up into daily digests. - **`poker_stats_monthly`**: Daily digests are rolled up into monthly digests. This hierarchical structure means that querying for a month's worth of data is incredibly fast, as the database only needs to combine ~30 daily digest rows instead of processing millions of raw event rows. 3. **Querying (Egress)**: When you query the data, a final accessor function (`output_agg`) is used. This function unpacks the digests back into human-readable numbers (e.g., `public.sum(bets_digest)`) and calculates all the derived metrics (like frequencies and factors) on the fly. This approach provides the best of both worlds: extreme performance and storage efficiency through digest-based rollups, and the flexibility to compute a vast array of complex metrics at query time without being constrained by pre-aggregated data.