{"_id":"@chancemoney/bet-math","_rev":"2-39448acdf722698c3fe06bb4ab24e0fd","name":"@chancemoney/bet-math","dist-tags":{"latest":"0.2.1"},"versions":{"0.2.0":{"name":"@chancemoney/bet-math","version":"0.2.0","license":"MIT","_id":"@chancemoney/bet-math@0.2.0","maintainers":[{"name":"itsdiay","email":"davidaideyan@gmail.com"}],"dist":{"shasum":"50f73cea3290590430b3cf0e40991232441faa4a","tarball":"https://registry.npmjs.org/@chancemoney/bet-math/-/bet-math-0.2.0.tgz","fileCount":33,"integrity":"sha512-5l9BMgcYtiKv20yoIMG5arl1OGSPYI/IlHfOjfeYEkktHGI/kIcsNlkxgbseCS/JDY6DnCcQiNbtdRsThEMvKw==","signatures":[{"sig":"MEUCIQCmK9iDAuxjrXvYGrsm05kN7LWcEJk4BUyNpOYJxCwGbgIgLSKYgk/QwSBIFmwnCI6Yp9T8+q7tLUsBbuNxd0qYsCk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":146303},"main":"./dist/index.js","type":"module","types":"./src/index.ts","engines":{"node":">=20"},"exports":{".":{"types":"./src/index.ts","import":"./dist/index.js","default":"./dist/index.js"}},"gitHead":"dad0ec6acaf06d76aefbb07b3975747281170748","scripts":{"test":"../../node_modules/.bin/vitest run --passWithNoTests","build":"../../node_modules/.bin/tsc -b --force","typecheck":"../../node_modules/.bin/tsc --noEmit -p tsconfig.json"},"_npmUser":{"name":"itsdiay","email":"davidaideyan@gmail.com"},"_npmVersion":"11.3.0","description":"Pure JavaScript bet math for Chance Strike, Range, Tiered, and Shuffle modes","directories":{},"sideEffects":false,"_nodeVersion":"24.2.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/bet-math_0.2.0_1786998414767_0.5367867071507075","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@chancemoney/bet-math","version":"0.2.1","description":"JavaScript/TypeScript bet math for Chance Strike, Range, Tiered, and Shuffle modes","license":"MIT","type":"module","main":"./dist/index.js","types":"./src/index.ts","exports":{".":{"types":"./src/index.ts","import":"./dist/index.js","default":"./dist/index.js"}},"sideEffects":false,"scripts":{"build":"../../node_modules/.bin/tsc -b --force","typecheck":"../../node_modules/.bin/tsc --noEmit -p tsconfig.json","test":"../../node_modules/.bin/vitest run --passWithNoTests"},"publishConfig":{"access":"public"},"engines":{"node":">=20"},"_id":"@chancemoney/bet-math@0.2.1","gitHead":"28d8ac4bce4680b57dd1ab10670c7f1197baa836","_nodeVersion":"24.2.0","_npmVersion":"11.3.0","dist":{"integrity":"sha512-KmCKE94mWPFDbo6nV+R9dy45F1lxouPOs9ZUKM15Rpxp2+dMKOehMxH+a/xMnA5nauYWfT6Y2Zq82GiSOrj97Q==","shasum":"2acbbf8c45e0ada138990cdabec5b9de99aa3306","tarball":"https://registry.npmjs.org/@chancemoney/bet-math/-/bet-math-0.2.1.tgz","fileCount":33,"unpackedSize":146114,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIGq67Ay/smJm9eX5mLmroFEtukT4ssLzbo6/Dwr5JPgAAiEAjbvZw1tsE0iY2s6qiCGkIH/RrfWa90ItgPrL2JGcMx0="}]},"_npmUser":{"name":"itsdiay","email":"davidaideyan@gmail.com"},"directories":{},"maintainers":[{"name":"itsdiay","email":"davidaideyan@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/bet-math_0.2.1_1786999172856_0.561704249124874"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-17T20:26:54.590Z","modified":"2026-08-17T20:39:33.263Z","0.2.0":"2026-08-17T20:26:54.926Z","0.2.1":"2026-08-17T20:39:33.049Z"},"license":"MIT","description":"JavaScript/TypeScript bet math for Chance Strike, Range, Tiered, and Shuffle modes","maintainers":[{"name":"itsdiay","email":"davidaideyan@gmail.com"}],"readme":"# `@chancemoney/bet-math`\n\nJavaScript / TypeScript bet calculation for Chance modes:\n\n- **Strike** - multiple ↔ win probability, quotes, stake/multiple caps\n- **Range** - lo/hi multiples, distribution helpers, caps\n- **Tiered** - solve unlocked rung probabilities to EV, float quotes\n- **Shuffle** - rebalance unlocked card multiples to EV, float quotes\n\n## Install\n\n```bash\nyarn add @chancemoney/bet-math\n```\n\n## Protocol caps\n\nBuild `ProtocolCaps` / bankroll caps from your pool or table snapshot and pass them into quotes and stake helpers.\n\n```ts\nimport {\n  DEFAULT_EV,\n  strikeKellyCapFromBankroll,\n  maxMultiplierFromBps,\n  type ProtocolCaps,\n} from '@chancemoney/bet-math'\n\n// From your pool / table snapshot (example numbers)\nconst availableBankroll = 10_000\nconst maxBetLiabilityCap = 400 // MBL: max (W − B)\nconst maxBetCap = 500 // bankroll-relative stake ceiling\nconst maxBetAmount = 250 // absolute table limit (omit if uncapped)\nconst maxMultiplier = maxMultiplierFromBps(1_000_000) // 100×\nconst maxBetKellyCap = strikeKellyCapFromBankroll({\n  availableBankroll,\n  ev: DEFAULT_EV,\n  maxBetKellyRatioBps: 5_000, // half Kelly\n})\n\nconst caps: ProtocolCaps = {\n  ev: DEFAULT_EV,\n  maxBetLiabilityCap,\n  maxBetKellyCap,\n  maxBetCap,\n  maxBetAmount,\n  maxMultiplier,\n}\n```\n\n## Strike\n\n```ts\nimport {\n  winProbFromMultiple,\n  multipleFromWinProb,\n  quoteStrike,\n  DEFAULT_EV,\n} from '@chancemoney/bet-math'\n\nconst winProb = winProbFromMultiple({ multiple: 2, ev: DEFAULT_EV }) // 0.425\nconst multiple = multipleFromWinProb({ winProb: 0.425, ev: DEFAULT_EV }) // 2\n\nconst quote = quoteStrike({\n  stake: 100,\n  multiple: 2,\n  ...caps,\n})\n// quote.payout, quote.winProb, quote.capped, quote.capBound\n```\n\n## Range\n\n```ts\nimport {\n  rangeAlpha,\n  rangeOutcomeDistribution,\n  rangeProfitChance,\n  quoteRange,\n  DEFAULT_EV,\n} from '@chancemoney/bet-math'\n\nconst stake = 100\nconst lo = stake * 0.5\nconst hi = stake * 2\nconst mean = stake * DEFAULT_EV\n\nconst alpha = rangeAlpha(lo, hi, mean) // settle shape α, or null if invalid\nconst bins = rangeOutcomeDistribution(lo, hi, mean, 48) // histogram or null\nconst profitChance = rangeProfitChance(lo, hi, mean, stake) // P(payout ≥ stake)\n\nconst rangeQuote = quoteRange({\n  stake,\n  rminM: 0.5,\n  rmaxM: 2,\n  ...caps,\n})\n// rangeQuote.lo / .hi / .mean, .capped\n```\n\n## Tiered\n\n```ts\nimport {\n  LADDER_PRESETS,\n  ladderPresetById,\n  ladderRowsFromMultipliers,\n  unlockAllLadderRows,\n  DEFAULT_LADDER_ROWS,\n  solveLadderRungs,\n  quoteTieredBetter,\n  DEFAULT_EV,\n} from '@chancemoney/bet-math'\n\nconst preset = ladderPresetById('classic')\nconst rows = ladderRowsFromMultipliers(preset.multipliers)\n// or: LADDER_PRESETS / DEFAULT_LADDER_ROWS\n\nconst unlocked = unlockAllLadderRows(rows)\nconst solved = solveLadderRungs(unlocked, DEFAULT_EV)\n// solved.rungs, solved.ok, solved.note, solved.maxM\n\nconst tieredQuote = quoteTieredBetter({\n  stake: 100,\n  rows: unlocked,\n  ...caps,\n})\n```\n\n## Shuffle\n\n```ts\nimport {\n  isShuffleDiffId,\n  resolveShuffleDiffId,\n  shuffleDiffById,\n  shuffleRowsFromDiff,\n  shuffleRowsFromMultipliers,\n  unlockAllShuffleRows,\n  clampShuffleMultiple,\n  rebalanceShuffleRows,\n  shuffleTopMultiple,\n  shuffleDeckTopMultiple,\n  shuffleLiabilityMultipleAfterEvSnap,\n  maxShuffleStakeForOdds,\n  quoteShuffle,\n  DEFAULT_SHUFFLE_DIFF_ID,\n  DEFAULT_EV,\n} from '@chancemoney/bet-math'\n\n// SHUFFLE_DIFFICULTIES, MAX_SHUFFLE_BUCKETS, DEFAULT_SHUFFLE_ROWS also exported\nconst diffId = resolveShuffleDiffId('safe') // also maps legacy easy/medium/hard\nisShuffleDiffId(diffId) // true\nconst diff = shuffleDiffById(diffId)\n\nlet rows = shuffleRowsFromDiff(DEFAULT_SHUFFLE_DIFF_ID)\n// or: shuffleRowsFromMultipliers([0, 0.5, 1, 2])\nrows = unlockAllShuffleRows(rows)\n\nconst balanced = rebalanceShuffleRows(rows, DEFAULT_EV)\nconst topFromDiff = shuffleTopMultiple('classic')\nconst topFromDeck = shuffleDeckTopMultiple(balanced.rows)\nconst clamped = clampShuffleMultiple(12.34)\n\nconst multiples = balanced.rows.map((r) => r.m)\nconst k = 1\nconst liabM = shuffleLiabilityMultipleAfterEvSnap({ multiples, k, ev: DEFAULT_EV })\n\n// Ticket-aware Shuffle stake ceiling (top-K after EV snap). Prefer this over\n// maxStakeForMode for Shuffle - mode helper only applies the stake backstop.\nconst maxShuffleStake = maxShuffleStakeForOdds({\n  multiples,\n  k,\n  ...caps,\n})\n\nconst shuffleQuote = quoteShuffle({\n  stake: 100,\n  rows: balanced.rows,\n  pickIndexes: [0], // K = length of picks\n  ...caps,\n})\n```\n\n## Caps helpers\n\n### Constants (selected)\n\n| Export | Meaning |\n|--------|---------|\n| `BPS` | 10_000 |\n| `DEFAULT_EV` | 0.85 |\n| `DEFAULT_MAX_MULTIPLIER` | 100 |\n| `DEFAULT_MAX_BET_KELLY_RATIO_BPS` | 5_000 |\n| `STRIKE_MIN_MULTIPLE` / `STRIKE_DEFAULT_*` | Strike UI defaults |\n| `RANGE_MIN_MAX_MULTIPLE` / `RANGE_DEFAULT_*` | Range UI defaults |\n| `MAX_BET_AMOUNT_FINITE_CEIL` | Raw `maxBetAmount` at/above this → uncapped |\n\n### Parse / convert\n\n```ts\nimport {\n  finiteMaxBetAmountUnits,\n  maxMultiplierFromBps,\n  effectiveMaxMultiplierBps,\n  DEFAULT_MAX_MULTIPLIER_BPS,\n  DEFAULT_MIN_MULTIPLIER_BPS,\n} from '@chancemoney/bet-math'\n\nconst amount = finiteMaxBetAmountUnits('250000000') // bigint or null if uncapped\nconst maxM = maxMultiplierFromBps(DEFAULT_MAX_MULTIPLIER_BPS) // 100\n\nconst effectiveBps = effectiveMaxMultiplierBps({\n  maxMultiplierBps: DEFAULT_MAX_MULTIPLIER_BPS,\n  minMultiplierBps: DEFAULT_MIN_MULTIPLIER_BPS,\n  availableBankroll: 10_000,\n  pendingPayoutLiability: 2_000,\n})\n```\n\n### Liability / Kelly / optional caps\n\n```ts\nimport {\n  betLiabilityCapOrInf,\n  stakeCapOrInf,\n  absoluteMaxBetOrInf,\n  stakeBackstopOrInf,\n  strikeKellyCapFromBankroll,\n  kellyLiabilityCap,\n  kellyCapForLoMultiple,\n  netLiabilityCap,\n  effectiveLiabilityCap,\n  floorUsdCents,\n  floorMultipleTenth,\n} from '@chancemoney/bet-math'\n\nbetLiabilityCapOrInf(400) // 400; omit/≤0 → Infinity\nstakeCapOrInf(500)\nabsoluteMaxBetOrInf(250)\nstakeBackstopOrInf({ maxBetCap: 500, maxBetAmount: 250 }) // min of both\n\nconst strikeKelly = strikeKellyCapFromBankroll({\n  availableBankroll: 10_000,\n  ev: 0.85,\n  maxBetKellyRatioBps: 5_000,\n})\nkellyLiabilityCap({ stakeAmount: 100, payoutLo: 0, strikeKellyCap: strikeKelly })\nkellyCapForLoMultiple(strikeKelly, 0.5) // Range-style lo multiple of stake\nnetLiabilityCap({\n  maxBetLiabilityCap: 400,\n  maxBetKellyCap: strikeKelly,\n  payoutLoMultiple: 0,\n})\neffectiveLiabilityCap({\n  stake: 100,\n  maxBetLiabilityCap: 400,\n  maxBetKellyCap: strikeKelly,\n  maxMultiplier: 100,\n  payoutLo: 0,\n})\n\nfloorUsdCents(12.349) // 12.34\nfloorMultipleTenth(3.19) // 3.1\n```\n\n### Stake ↔ multiple from L\n\n```ts\nimport {\n  maxMultipleForStake,\n  maxStakeForMultiple,\n  maxStakeForMode,\n  maxShuffleStakeForOdds,\n  effectiveMaxBetStake,\n} from '@chancemoney/bet-math'\n\nconst maxM = maxMultipleForStake({\n  stake: 100,\n  maxBetLiabilityCap: 400,\n  maxBetKellyCap: strikeKelly,\n  maxMultiplier: 100,\n})\n\nconst maxStakeAt2x = maxStakeForMultiple({\n  multiple: 2,\n  maxBetLiabilityCap: 400,\n  maxBetKellyCap: strikeKelly,\n  maxBetCap: 500,\n  maxBetAmount: 250,\n})\n\n// Mode ceilings for Strike / Range / TieredBetter stake backstop.\n// For Shuffle tickets, use maxShuffleStakeForOdds (top-K liability), not this.\nconst modeMax = maxStakeForMode({\n  mode: 'strike', // 'strike' | 'range' | 'tieredBetter' | 'shuffle'\n  maxBetLiabilityCap: 400,\n  maxBetKellyCap: strikeKelly,\n  maxBetCap: 500,\n  maxBetAmount: 250,\n})\n\nconst uiMax = effectiveMaxBetStake({\n  poolStakeMax: modeMax,\n  walletBalance: 80,\n})\n```\n\n### Strike / Range / Tiered stake & multiple UI\n\n```ts\nimport {\n  defaultStrikeStake,\n  defaultRangeStake,\n  maxStrikeMultiple,\n  maxStrikeStake,\n  strikePayoutBoundsForStake,\n  maxRangeStake,\n  maxRangeMaxMultiple,\n  rangeHiPayoutBoundsForStake,\n  maxTieredBetterStakeForMaxMultiple,\n  clampStrikeMultiple,\n  clampRangeMaxMultiple,\n  resolveStrikeMultipleUnderCaps,\n  resolveRangeMaxMultipleUnderCaps,\n  floorStrikeDefaultStake,\n} from '@chancemoney/bet-math'\n\nconst bankrollCaps = {\n  maxBetLiabilityCap: 400,\n  maxBetKellyCap: strikeKelly,\n  maxBetCap: 500,\n  maxBetAmount: 250,\n  maxMultiplier: 100,\n}\n\ndefaultStrikeStake(bankrollCaps) // $100 or floored fallback / null\ndefaultRangeStake(bankrollCaps)\nfloorStrikeDefaultStake(0.87)\n\nmaxStrikeStake(bankrollCaps)\nmaxStrikeMultiple({ stake: 100, ...bankrollCaps })\nstrikePayoutBoundsForStake({ stake: 100, ...bankrollCaps })\n\nmaxRangeStake(bankrollCaps)\nmaxRangeMaxMultiple({ stake: 100, payoutLoMultiple: 0.5, ...bankrollCaps })\nrangeHiPayoutBoundsForStake({ stake: 100, payoutLoMultiple: 0.5, ...bankrollCaps })\n\nmaxTieredBetterStakeForMaxMultiple({\n  maxMultiple: 8,\n  payoutLoMultiple: 0,\n  ...bankrollCaps,\n})\n\nclampStrikeMultiple(1.05, 50) // ≥ STRIKE_MIN_MULTIPLE\nclampRangeMaxMultiple(1.0, 12) // ≥ RANGE_MIN_MAX_MULTIPLE\n\nresolveStrikeMultipleUnderCaps({\n  multiple: 5,\n  multipleMaxForStake: 3,\n  ...bankrollCaps,\n})\nresolveRangeMaxMultipleUnderCaps({\n  rmaxM: 4,\n  rmaxMMaxForStake: 2.5,\n  payoutLoMultiple: 0.5,\n  ...bankrollCaps,\n})\n```\n\n## Types\n\nExported types (see `src/types.ts` / caps): `ProtocolCaps`, `LadderRow`, `LadderRung`, `StrikeQuote`, `RangeQuote`, `RangeOutcomeBin`, `TieredQuote`, `ShuffleQuote`, `BetMode`, `CapBound`, `DualBankrollCaps`, `LadderPreset`, `LadderPresetId`, `ShuffleDifficulty`, `ShuffleDiffId`.\n","readmeFilename":"README.md"}