diff --git a/README.md b/README.md index 0dee543..da8d22c 100644 --- a/README.md +++ b/README.md @@ -114,40 +114,51 @@ Trial Results └─────────┴───────┴─────────┴───────┘ Final Balance Summary -┌─────────┬────────┐ -│ (index) │ Values │ -├─────────┼────────┤ -│ min │ 416 │ -│ p1 │ 416 │ -│ p5 │ 416 │ -│ p10 │ 416 │ -│ p25 │ 416 │ -│ p50 │ 416 │ -│ p75 │ 614 │ -│ p90 │ 614 │ -│ p95 │ 614 │ -│ p99 │ 614 │ -│ max │ 614 │ -└─────────┴────────┘ +┌─────────┬────────┬────────┐ +│ (index) │ stat │ value │ +├─────────┼────────┼────────┤ +│ 0 │ min │ 416 │ +│ 1 │ p1 │ 416 │ +│ 2 │ p5 │ 416 │ +│ 3 │ p10 │ 416 │ +│ 4 │ p25 │ 416 │ +│ 5 │ p50 │ 416 │ +│ 6 │ mean │ 515 │ +│ 7 │ p75 │ 614 │ +│ 8 │ p90 │ 614 │ +│ 9 │ p95 │ 614 │ +│ 10 │ p99 │ 614 │ +│ 11 │ max │ 614 │ +└─────────┴────────┴────────┘ +stdDev: 140.01 +95% CI: [320.96, 709.04] Roll Count Summary -┌─────────┬────────┐ -│ (index) │ Values │ -├─────────┼────────┤ -│ min │ 25 │ -│ p1 │ 25 │ -│ p5 │ 25 │ -│ p10 │ 25 │ -│ p25 │ 25 │ -│ p50 │ 25 │ -│ p75 │ 65 │ -│ p90 │ 65 │ -│ p95 │ 65 │ -│ p99 │ 65 │ -│ max │ 65 │ -└─────────┴────────┘ +┌─────────┬────────┬────────┐ +│ (index) │ stat │ value │ +├─────────┼────────┼────────┤ +│ 0 │ min │ 25 │ +│ 1 │ p1 │ 25 │ +│ 2 │ p5 │ 25 │ +│ 3 │ p10 │ 25 │ +│ 4 │ p25 │ 25 │ +│ 5 │ p50 │ 25 │ +│ 6 │ mean │ 45 │ +│ 7 │ p75 │ 65 │ +│ 8 │ p90 │ 65 │ +│ 9 │ p95 │ 65 │ +│ 10 │ p99 │ 65 │ +│ 11 │ max │ 65 │ +└─────────┴────────┴────────┘ +stdDev: 28.28 +95% CI: [5.80, 84.20] ``` +The standard deviation shows how widely the trials vary around the mean. +Roughly 68% of results will fall within one standard deviation, 95% within +two, and 99.7% within three. The 95% confidence interval is a range that is +expected to contain the true mean in 95% of repeated samples. + ## table rules `playHand` accepts a `rules` object that controls minimum bets and odds limits. diff --git a/monte-carlo.js b/monte-carlo.js index 5275be9..ecfe796 100644 --- a/monte-carlo.js +++ b/monte-carlo.js @@ -25,8 +25,21 @@ function percentile (sorted, p) { function summary (arr) { const sorted = [...arr].sort((a, b) => a - b) + const mean = arr.reduce((m, v) => m + v, 0) / arr.length + const variance = arr.length > 1 + ? arr.reduce((m, v) => m + Math.pow(v - mean, 2), 0) / (arr.length - 1) + : 0 + const stdDev = Math.sqrt(variance) + const stdErr = stdDev / Math.sqrt(arr.length) + const ci95Low = mean - (1.96 * stdErr) + const ci95High = mean + (1.96 * stdErr) + return { min: sorted[0], + mean, + stdDev, + ci95Low, + ci95High, max: sorted[sorted.length - 1], p1: percentile(sorted, 1), p5: percentile(sorted, 5), @@ -42,8 +55,29 @@ function summary (arr) { function summaryTable (arr) { const obj = summary(arr) - const order = ['min', 'p1', 'p5', 'p10', 'p25', 'p50', 'p75', 'p90', 'p95', 'p99', 'max'] - return order.map(k => ({ stat: k, value: obj[k] })) + const order = [ + 'min', + 'p1', + 'p5', + 'p10', + 'p25', + 'p50', + 'mean', + 'p75', + 'p90', + 'p95', + 'p99', + 'max' + ] + const table = order + .map(k => ({ stat: k, value: obj[k] })) + .sort((a, b) => a.value - b.value || order.indexOf(a.stat) - order.indexOf(b.stat)) + return { + table, + stdDev: obj.stdDev, + ci95Low: obj.ci95Low, + ci95High: obj.ci95High + } } function simulateTrial ({ handsPerTrial, startingBankroll, rules }) { @@ -73,10 +107,16 @@ function printResults (results) { console.table(results.map((r, i) => ({ trial: i + 1, balance: r.balance, rolls: r.rolls }))) console.log('\nFinal Balance Summary') - console.table(summaryTable(results.map(r => r.balance))) + const balanceSummary = summaryTable(results.map(r => r.balance)) + console.table(balanceSummary.table) + console.log(`stdDev: ${balanceSummary.stdDev.toFixed(2)}`) + console.log(`95% CI: [${balanceSummary.ci95Low.toFixed(2)}, ${balanceSummary.ci95High.toFixed(2)}]`) console.log('\nRoll Count Summary') - console.table(summaryTable(results.map(r => r.rolls))) + const rollSummary = summaryTable(results.map(r => r.rolls)) + console.table(rollSummary.table) + console.log(`stdDev: ${rollSummary.stdDev.toFixed(2)}`) + console.log(`95% CI: [${rollSummary.ci95Low.toFixed(2)}, ${rollSummary.ci95High.toFixed(2)}]`) } if (require.main === module) {