← back to Ken
Ken v2: profit-based ranking — sum(daily_pnl_cents), gate on net>0, rank by real $ (TK-10166)
d137b3b2aaa331a07e711a12656f7f6efb9b232c · 2026-08-03 08:34:49 -0700 · Steve Abrams
Files touched
M kalshi-dash/follow-the-winners.mjs
Diff
commit d137b3b2aaa331a07e711a12656f7f6efb9b232c
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Mon Aug 3 08:34:49 2026 -0700
Ken v2: profit-based ranking — sum(daily_pnl_cents), gate on net>0, rank by real $ (TK-10166)
---
kalshi-dash/follow-the-winners.mjs | 26 +++++++++++++++-----------
1 file changed, 15 insertions(+), 11 deletions(-)
diff --git a/kalshi-dash/follow-the-winners.mjs b/kalshi-dash/follow-the-winners.mjs
index b8cb81b..83b6f9a 100644
--- a/kalshi-dash/follow-the-winners.mjs
+++ b/kalshi-dash/follow-the-winners.mjs
@@ -24,7 +24,7 @@ async function loadWinRecord() {
SELECT p.id, p.name, p.strategy,
COALESCE(SUM(d.wins),0) AS w,
COALESCE(SUM(d.losses),0) AS l,
- COALESCE(MAX(d.cumulative_pnl_cents),0) AS net_c
+ COALESCE(SUM(d.daily_pnl_cents),0) AS net_c -- SUM(daily), not the broken cumulative col
FROM ken_portfolios p LEFT JOIN ken_daily_pnl d ON d.portfolio_id = p.id
GROUP BY p.id, p.name, p.strategy`);
const anyDaily = dp.rows.some(r => num(r.w) + num(r.l) > 0);
@@ -43,14 +43,17 @@ const { src, rows } = await loadWinRecord();
const scored = rows.map(p => {
const resolved = num(p.w) + num(p.l);
const winPct = resolved ? num(p.w) / resolved : 0;
- const netC = num(p.net_c);
- const score = (winPct - 0.5) * Math.log10(resolved + 1) * (netC >= 0 ? 1 : 0.4);
- return { ...p, resolved, winPct, netC, score };
+ const netC = num(p.net_c); // real accumulated realized P&L, cents
+ const perTrade = resolved ? netC / resolved : 0; // avg profit per resolved trade
+ // PROFIT-BASED score: dollars earned, dampened by small-sample uncertainty
+ const score = (netC / 100) * Math.log10(resolved + 1);
+ return { ...p, resolved, winPct, netC, perTrade, score };
});
+// A "winner" must be PROFITABLE (net > 0), win >= gate, over a real sample.
const winners = scored
- .filter(p => p.resolved >= MIN_RESOLVED && p.winPct >= WIN_GATE)
- .sort((a, b) => b.score - a.score)
+ .filter(p => p.resolved >= MIN_RESOLVED && p.winPct >= WIN_GATE && p.netC > 0)
+ .sort((a, b) => b.netC - a.netC) // rank by actual profit
.slice(0, TOP_K);
console.log(`\nwin-record source: ${src}`);
@@ -63,15 +66,16 @@ if (totalResolved === 0) {
await ken.end(); process.exit(0);
}
-console.log(`=== PROVEN WINNERS (win% >= ${(WIN_GATE*100)|0}, resolved >= ${MIN_RESOLVED}) ===`);
+console.log(`=== PROVEN WINNERS (PROFITABLE: net>0, win% >= ${(WIN_GATE*100)|0}, resolved >= ${MIN_RESOLVED}) ===`);
if (!winners.length) {
- console.log(` (none clear the gate yet — ${totalResolved} resolved trades on record but no portfolio`);
- console.log(` meets the bar. Do NOT bet live.)`);
+ console.log(` (none clear the bar — ${totalResolved} resolved trades on record, but no portfolio is`);
+ console.log(` BOTH >=${(WIN_GATE*100)|0}% AND net-profitable over >=${MIN_RESOLVED} trades. Do NOT bet live.)`);
await ken.end(); process.exit(0);
}
+console.log(` ${'portfolio'.padEnd(12)} ${'net$'.padStart(7)} ${'¢/trade'.padStart(7)} ${'win%'.padStart(5)} W/L`);
for (const p of winners) {
- console.log(` ${p.name.padEnd(12)} ${String(Math.round(p.winPct*100)).padStart(3)}% ` +
- `${p.w}W/${p.l}L net $${(p.netC/100).toFixed(0).padStart(5)} score ${p.score.toFixed(3)}`);
+ console.log(` ${p.name.padEnd(12)} ${('$'+(p.netC/100).toFixed(2)).padStart(7)} ${(p.perTrade.toFixed(1)+'¢').padStart(7)} ` +
+ `${String(Math.round(p.winPct*100)).padStart(4)}% ${p.w}W/${p.l}L`);
}
// --- 2) winners' current open positions → weighted consensus per market ---
← 74d4a2d Ken v2 follow-the-winners: hourly auto-check, alerts on QUIE
·
back to Ken
·
auto-save: 2026-08-03T08:52:19 (1 files) — kalshi-dash/.giti ae4735c →