[object Object]

← 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

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 →