[object Object]

← back to Ken

Ken: fix resolver 270x portfolio over-credit — settle idempotently (UPDATE ... WHERE status='open' RETURNING, credit only on winning the flip) + re-entrancy guard so duplicate intervals (5121/9388) + slow passes can't double-count. Real bets follow paper winners, so clean rollups matter.

e7e3c8c22537fe134faec5a2df20c6651fc3226c · 2026-08-03 09:16:02 -0700 · steve@designerwallcoverings.com

Files touched

Diff

commit e7e3c8c22537fe134faec5a2df20c6651fc3226c
Author: steve@designerwallcoverings.com <steve@designerwallcoverings.com>
Date:   Mon Aug 3 09:16:02 2026 -0700

    Ken: fix resolver 270x portfolio over-credit — settle idempotently (UPDATE ... WHERE status='open' RETURNING, credit only on winning the flip) + re-entrancy guard so duplicate intervals (5121/9388) + slow passes can't double-count. Real bets follow paper winners, so clean rollups matter.
---
 kalshi-dash/server.js | 16 ++++++++++++++--
 1 file changed, 14 insertions(+), 2 deletions(-)

diff --git a/kalshi-dash/server.js b/kalshi-dash/server.js
index d2a2692..e092fed 100644
--- a/kalshi-dash/server.js
+++ b/kalshi-dash/server.js
@@ -8912,7 +8912,10 @@ async function capturePortfolioSnapshots() {
 }
 
 // Resolve portfolio trades — HYPER MODE: quick take-profit/stop-loss + aging out
+let _resolveInFlight = false;
 async function resolvePortfolioTrades() {
+  if (_resolveInFlight) return; // guard: a slow pass + duplicate intervals must not overlap
+  _resolveInFlight = true;
   try {
     // Check ALL open trades (no minimum age for hyper-trading)
     const { rows: openTrades } = await kenQ(`
@@ -8968,10 +8971,17 @@ async function resolvePortfolioTrades() {
 
       if (!status) continue; // Hold position
 
-      await kenQ(`
+      // IDEMPOTENT SETTLE (fix 2026-08-03): only THIS pass may credit the portfolio,
+      // and only if it is the one that flips the trade open→settled. `AND status='open'`
+      // + RETURNING makes the flip atomic; if a concurrent/re-entrant pass already
+      // settled this trade, rowCount is 0 and we skip crediting — preventing the
+      // ~270x portfolio over-credit that came from the same trade being counted many times.
+      const flip = await kenQ(`
         UPDATE ken_portfolio_trades SET status = $1, exit_price_cents = $2, pnl_cents = $3, resolved_at = NOW()
-        WHERE id = $4
+        WHERE id = $4 AND status = 'open'
+        RETURNING id
       `, [status, currentPrice, pnl, trade.id]);
+      if (flip.rowCount === 0) continue; // lost the race — already settled elsewhere, do NOT double-credit
 
       // Update portfolio running totals
       const streakVal = status === 'won' ? 1 : status === 'lost' ? -1 : 0;
@@ -9017,6 +9027,8 @@ async function resolvePortfolioTrades() {
     if (openTrades.length > 0) console.log(`[Ken/Portfolio] Resolved ${openTrades.length} trades across portfolios`);
   } catch (e) {
     console.error('[Ken/Portfolio] Resolution error:', e.message);
+  } finally {
+    _resolveInFlight = false;
   }
 }
 

← b9768ef Ken v2 GO-LIVE wiring: live orders follow profitable-winner  ·  back to Ken  ·  auto-save: 2026-08-03T09:22:36 (1 files) — kalshi-dash/src/L fc471b8 →