export function ciExpectancy(rValues) { const n = rValues.length; if (n === 0) return { mean: 0, lo: 0, hi: 0, n: 0 }; const mean = rValues.reduce((a, b) => a + b, 0) / n; if (n === 1) return { mean, lo: mean, hi: mean, n }; const variance = rValues.reduce((a, b) => a + Math.pow(b - mean, 2), 0) / (n - 1); const sd = Math.sqrt(variance); const se = sd / Math.sqrt(n); return { mean, lo: mean - 1.96 * se, hi: mean + 1.96 * se, n }; } export function generateResultsTable(testResults) { const grades = ['A', 'B', 'C', 'D']; const grouped = { A: { wins: 0, losses: 0, scratches: 0, rVals: [] }, B: { wins: 0, losses: 0, scratches: 0, rVals: [] }, C: { wins: 0, losses: 0, scratches: 0, rVals: [] }, D: { wins: 0, losses: 0, scratches: 0, rVals: [] } }; for (const r of testResults) { if (grouped[r.grade]) { grouped[r.grade].rVals.push(r.rMultiple); if (r.outcome === 'WIN') grouped[r.grade].wins++; else if (r.outcome === 'LOSS') grouped[r.grade].losses++; else grouped[r.grade].scratches++; } } console.log(`\n=== OUT-OF-SAMPLE TEST RESULTS (EXPECTANCY) ===`); console.log(`Grade\tExp(R)\t\t95% CI (Exp)\t\tWin%\tLoss%\tScratch%\tn`); console.log(`-----------------------------------------------------------------------------------------`); for (const g of grades) { const st = grouped[g]; const n = st.rVals.length; if (n === 0) continue; const pWin = (st.wins / n) * 100; const pLoss = (st.losses / n) * 100; const pScratch = (st.scratches / n) * 100; const ci = ciExpectancy(st.rVals); const meanFmt = `${ci.mean > 0 ? '+' : ''}${ci.mean.toFixed(3)}R`; const ciFmt = `[${ci.lo.toFixed(3)}, ${ci.hi.toFixed(3)}]`; console.log(`${g}\t${meanFmt}\t\t${ciFmt}\t\t${pWin.toFixed(1)}%\t${pLoss.toFixed(1)}%\t${pScratch.toFixed(1)}%\t\t${n}`); } // Random Control = The entire test set const allR = testResults.map(r => r.rMultiple); const totalN = allR.length; if (totalN > 0) { const totalWins = testResults.filter(r => r.outcome === 'WIN').length; const totalLosses = testResults.filter(r => r.outcome === 'LOSS').length; const totalScratches = testResults.filter(r => r.outcome === 'SCRATCH').length; const ciRand = ciExpectancy(allR); const meanFmt = `${ciRand.mean > 0 ? '+' : ''}${ciRand.mean.toFixed(3)}R`; const ciFmt = `[${ciRand.lo.toFixed(3)}, ${ciRand.hi.toFixed(3)}]`; const pWin = (totalWins / totalN) * 100; const pLoss = (totalLosses / totalN) * 100; const pScratch = (totalScratches / totalN) * 100; console.log(`Rand\t${meanFmt}\t\t${ciFmt}\t\t${pWin.toFixed(1)}%\t${pLoss.toFixed(1)}%\t${pScratch.toFixed(1)}%\t\t${totalN}`); } console.log(`-----------------------------------------------------------------------------------------`); }