institutional-trader/backend/src/backtest/report/resultsTable.js

76 lines
2.8 KiB
JavaScript

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(`-----------------------------------------------------------------------------------------`);
}