Skip to content

Instantly share code, notes, and snippets.

@dleshem
Last active October 4, 2019 13:06
Show Gist options
  • Save dleshem/2697034d082ebd9e213b111eae1b2ffe to your computer and use it in GitHub Desktop.
Save dleshem/2697034d082ebd9e213b111eae1b2ffe to your computer and use it in GitHub Desktop.
Smashing PokerStars All-In Cash Out for Fun and Profit
const d3 = require('d3-array');
const Combinatorics = require('js-combinatorics');
const OddsCalculator = require('cardplayer-odds-calculator');
const PromiseThrottle = require('promise-throttle');
const oddsCalculator = new OddsCalculator({timeout: 30000});
const odds = async (holes, dead = []) => {
switch(holes[0].length) {
case 2: return oddsCalculator.texasHoldem({holes, dead});
case 4: return oddsCalculator.omahaHoldem({holes, dead});
default: throw new Error(`${holes[0].length} hole cards are not supported`);
}
};
const promiseThrottle = new PromiseThrottle({
requestsPerSecond: 3,
promiseImplementation: Promise
});
const calcEv2 = ({win, lose, tie}) => (win + tie/2) / (win + lose + tie);
const stringifyHole = hole => hole.join('');
const sample = async (deck, holeCards, players) => {
d3.shuffle(deck);
const indices = [...Array(players)].map((_, i) => i);
const holes = indices.map(i => deck.slice(i*holeCards, (i+1)*holeCards));
const options = [];
let playersPart;
const playersCmb = Combinatorics.combination(indices, 2);
while (playersPart = playersCmb.next()) {
const twoHoles = [];
const dead = [];
for (let i = 0; i < players; ++i) {
if ((i === playersPart[0]) || (i === playersPart[1])) {
twoHoles.push(holes[i]);
} else {
dead.push(holes[i][0]);
dead.push(holes[i][1]);
}
}
options.push({
index1: playersPart[0],
index2: playersPart[1],
twoHoles,
futureAssumedOdds: promiseThrottle.add(odds.bind(this, twoHoles)),
futureRealOdds: promiseThrottle.add(odds.bind(this, twoHoles, dead))
});
}
const results = [];
const best = {edge: 0};
for (let i = 0; i < options.length; ++i) {
const option = options[i];
const assumedEV = calcEv2((await option.futureAssumedOdds)[0]);
const realEV = calcEv2((await option.futureRealOdds)[0]);
const edge = Math.round(10000*Math.abs(assumedEV - realEV))/10000;
results.push({
index1: option.index1,
index2: option.index2,
twoHoles: option.twoHoles,
assumedEV,
realEV,
edge
});
if (edge > best.edge) {
best.index1 = option.index1,
best.index2 = option.index2,
best.edge = edge;
best.twoHoles = option.twoHoles;
best.assumedEV = assumedEV;
best.realEV = realEV;
}
}
// Print
console.log(holes);
results.forEach(({twoHoles, assumedEV, realEV, edge}) => {
console.log(`${stringifyHole(twoHoles[0])} vs ${stringifyHole(twoHoles[1])}: assumed: ${assumedEV}, actual: ${realEV}}, edge: ${edge}`);
});
console.log('------------');
console.log(`${stringifyHole(best.twoHoles[0])} vs ${stringifyHole(best.twoHoles[1])} has best edge: ${best.edge}`);
return best;
};
////////////////////////////////////////
const players = 10;
const holeCards = 2;
const deck = Combinatorics.cartesianProduct(['2', '3', '4', '5', '6', '7', '8', '9', 'T', 'J', 'Q', 'K', 'A'], ['s', 'c', 'd', 'h']).map(x => x.join(''));
(async () => {
try {
await sample(deck, holeCards, players);
} catch (e) {}
})();
const d3 = require('d3-array');
const Combinatorics = require('js-combinatorics');
const OddsCalculator = require('cardplayer-odds-calculator');
const PromiseThrottle = require('promise-throttle');
const oddsCalculator = new OddsCalculator({timeout: 30000});
const odds = async (holes, dead = []) => {
switch(holes[0].length) {
case 2: return oddsCalculator.texasHoldem({holes, dead});
case 4: return oddsCalculator.omahaHoldem({holes, dead});
default: throw new Error(`${holes[0].length} hole cards are not supported`);
}
};
const promiseThrottle = new PromiseThrottle({
requestsPerSecond: 3,
promiseImplementation: Promise
});
const calcEv2 = ({win, lose, tie}) => (win + tie/2) / (win + lose + tie);
const stringifyHole = hole => hole.join('');
const sample = async (deck, holeCards, players) => {
d3.shuffle(deck);
const indices = [...Array(players)].map((_, i) => i);
const holes = indices.map(i => deck.slice(i*holeCards, (i+1)*holeCards));
const options = [];
let playersPart;
const playersCmb = Combinatorics.combination(indices, 2);
while (playersPart = playersCmb.next()) {
const twoHoles = [];
const dead = [];
for (let i = 0; i < players; ++i) {
if ((i === playersPart[0]) || (i === playersPart[1])) {
twoHoles.push(holes[i]);
} else {
dead.push(holes[i][0]);
dead.push(holes[i][1]);
}
}
options.push({
index1: playersPart[0],
index2: playersPart[1],
twoHoles,
futureAssumedOdds: promiseThrottle.add(odds.bind(this, twoHoles)),
futureRealOdds: promiseThrottle.add(odds.bind(this, twoHoles, dead))
});
}
const results = [];
const best = {edge: 0};
for (let i = 0; i < options.length; ++i) {
const option = options[i];
const assumedEV = calcEv2((await option.futureAssumedOdds)[0]);
const realEV = calcEv2((await option.futureRealOdds)[0]);
const edge = Math.round(10000*Math.abs(assumedEV - realEV))/10000;
results.push({
index1: option.index1,
index2: option.index2,
twoHoles: option.twoHoles,
assumedEV,
realEV,
edge
});
if (edge > best.edge) {
best.index1 = option.index1,
best.index2 = option.index2,
best.edge = edge;
best.twoHoles = option.twoHoles;
best.assumedEV = assumedEV;
best.realEV = realEV;
}
}
// Print
console.log(holes);
results.forEach(({twoHoles, assumedEV, realEV, edge}) => {
console.log(`${stringifyHole(twoHoles[0])} vs ${stringifyHole(twoHoles[1])}: assumed: ${assumedEV}, actual: ${realEV}}, edge: ${edge}`);
});
console.log('------------');
console.log(`${stringifyHole(best.twoHoles[0])} vs ${stringifyHole(best.twoHoles[1])} has best edge: ${best.edge}`);
return best;
};
////////////////////////////////////////
// see https://www.pokerstars.com/poker/room/rake/
const rake = pot => Math.min(pot * 0.05, 2.5);
const players = 10;
const holeCards = 2;
const deck = Combinatorics.cartesianProduct(['2', '3', '4', '5', '6', '7', '8', '9', 'T', 'J', 'Q', 'K', 'A'], ['s', 'c', 'd', 'h']).map(x => x.join(''));
const stacks = [];
for (let i = 0; i < players; ++i) {
stacks[i] = 0;
}
let bankroll = 0;
(async () => {
let hands = 0;
while (true) {
console.log(`hands = ${hands}`);
++hands;
console.log(`Bankroll = ${bankroll}`);
console.log(stacks);
let total = bankroll;
for (let i = 0; i < players; ++i) {
total += stacks[i];
const reload = Math.max(100 - stacks[i], 0);
bankroll -= reload;
stacks[i] += reload;
}
console.log(`total = ${total}`);
let best;
while (true) {
try {
best = await sample(deck, holeCards, players);
break;
} catch (e) {}
}
let allinIndex, cashoutIndex, cashoutOdds, allinOdds;
if (best.realEV > best.assumedEV) {
allinIndex = best.index1;
cashoutIndex = best.index2;
cashoutOdds = 1 - best.assumedEV;
allinOdds = best.realEV;
} else {
allinIndex = best.index2;
cashoutIndex = best.index1;
cashoutOdds = best.assumedEV;
allinOdds = 1 - best.realEV;
}
const effectiveAllin = Math.min(stacks[allinIndex], stacks[cashoutIndex]);
stacks[allinIndex] -= effectiveAllin;
stacks[cashoutIndex] -= effectiveAllin;
const pot = (2 * effectiveAllin) - rake(2 * effectiveAllin);
stacks[cashoutIndex] += pot * 0.99 * cashoutOdds;
if (Math.random() < allinOdds) {
stacks[allinIndex] += pot;
}
}
})();
Hand 1 Hand 2 PokerStars Real Edge
J♠Q♣ A♣9♦ 42.65% 39.74% 2.92%
J♠Q♣ K♠9♣ 42.13% 43.59% 1.46%
A♣9♦ K♠9♣ 74.42% 80.20% 5.78%
J♠Q♣ T♠7♦ 67.63% 62.05% 5.58%
A♣9♦ T♠7♦ 61.56% 57.88% 3.68%
K♠9♣ T♠7♦ 62.61% 54.35% 8.26%
J♠Q♣ 3♥Q♠ 73.32% 70.02% 3.30%
A♣9♦ 3♥Q♠ 63.48% 64.03% 0.55%
K♠9♣ 3♥Q♠ 64.81% 61.10% 3.70%
T♠7♦ 3♥Q♠ 42.74% 48.73% 5.99%
J♠Q♣ K♦4♣ 44.48% 43.97% 0.51%
A♣9♦ K♦4♣ 63.83% 64.90% 1.07%
K♠9♣ K♦4♣ 69.93% 65.45% 4.48%
T♠7♦ K♦4♣ 42.55% 48.56% 6.01%
3♥Q♠ K♦4♣ 36.12% 36.52% 0.40%
╔═══════════╦════════════╦════════╦════════╗
║ Combo ║ PokerStars ║ Real ║ Edge ║
╠═══════════╬════════════╬════════╬════════╣
║ J♠Q♣|A♣9♦ ║ 42.65% ║ 39.74% ║ 2.92% ║
║ J♠Q♣|K♠9♣ ║ 42.13% ║ 43.59% ║ 1.46% ║
║ A♣9♦|K♠9♣ ║ 74.42% ║ 80.20% ║ 5.78% ║
║ J♠Q♣|T♠7♦ ║ 67.63% ║ 62.05% ║ 5.58% ║
║ A♣9♦|T♠7♦ ║ 61.56% ║ 57.88% ║ 3.68% ║
║ K♠9♣|T♠7♦ ║ 62.61% ║ 54.35% ║ 8.26% ║ ← information edge
║ J♠Q♣|3♥Q♠ ║ 73.32% ║ 70.02% ║ 3.30% ║
║ A♣9♦|3♥Q♠ ║ 63.48% ║ 64.03% ║ 0.55% ║
║ K♠9♣|3♥Q♠ ║ 64.81% ║ 61.10% ║ 3.70% ║
║ T♠7♦|3♥Q♠ ║ 42.74% ║ 48.73% ║ 5.99% ║
║ J♠Q♣|K♦4♣ ║ 44.48% ║ 43.97% ║ 0.51% ║
║ A♣9♦|K♦4♣ ║ 63.83% ║ 64.90% ║ 1.07% ║
║ K♠9♣|K♦4♣ ║ 69.93% ║ 65.45% ║ 4.48% ║
║ T♠7♦|K♦4♣ ║ 42.55% ║ 48.56% ║ 6.01% ║
║ 3♥Q♠|K♦4♣ ║ 36.12% ║ 36.52% ║ 0.40% ║
╚═══════════╩════════════╩════════╩════════╝
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment