[object Object]

← back to Bertha

chore(alshi-dash): update 1 file (.js) [+209/-20]

eb35535c9128bb9ddd6ca06ef6d8be172a28241c · 2026-02-16 16:17:31 +0000 · DW Commit Agent

Files touched

Diff

commit eb35535c9128bb9ddd6ca06ef6d8be172a28241c
Author: DW Commit Agent <commit-agent@dw-agents.com>
Date:   Mon Feb 16 16:17:31 2026 +0000

    chore(alshi-dash): update 1 file (.js) [+209/-20]
---
 kalshi-dash/server.js | 229 +++++++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 209 insertions(+), 20 deletions(-)

diff --git a/kalshi-dash/server.js b/kalshi-dash/server.js
index 73910e7..8562d71 100644
--- a/kalshi-dash/server.js
+++ b/kalshi-dash/server.js
@@ -1667,6 +1667,93 @@ const routes = {
     } catch(e) { json(res, { error: e.message }, 500); }
   },
 
+  // ── PortFAUXlio Dashboard API — full simulated trading view ──
+  'GET /api/portfauxlio': async (req, res) => {
+    try {
+      // 1. All portfolios with computed stats
+      const { rows: portfolios } = await kenQ(`
+        SELECT p.*,
+          (p.total_returned_cents - p.total_invested_cents) AS pnl_cents,
+          CASE WHEN p.total_invested_cents > 0
+            THEN ROUND((p.total_returned_cents - p.total_invested_cents)::numeric / p.total_invested_cents * 100, 1)
+            ELSE 0 END AS roi_pct,
+          (p.trades_won + p.trades_lost + p.trades_expired) AS resolved_trades,
+          CASE WHEN (p.trades_won + p.trades_lost) > 0
+            THEN ROUND(p.trades_won::numeric / (p.trades_won + p.trades_lost) * 100, 0)
+            ELSE 0 END AS win_rate
+        FROM ken_portfolios p ORDER BY (p.total_returned_cents - p.total_invested_cents) DESC
+      `);
+
+      // 2. All active (open) trades across all portfolios
+      const { rows: activeTrades } = await kenQ(`
+        SELECT t.id, t.portfolio_id, t.market_id, t.market_title, t.direction,
+          t.contracts, t.entry_price_cents, t.cost_cents, t.confidence, t.created_at,
+          p.name AS portfolio_name, p.strategy
+        FROM ken_portfolio_trades t
+        JOIN ken_portfolios p ON p.id = t.portfolio_id
+        WHERE t.status = 'open'
+        ORDER BY t.created_at DESC
+      `);
+
+      // 3. Trade history (all trades, most recent first)
+      const { rows: tradeHistory } = await kenQ(`
+        SELECT t.id, t.portfolio_id, t.market_id, t.market_title, t.direction,
+          t.contracts, t.entry_price_cents, t.cost_cents, t.pnl_cents, t.status,
+          t.confidence, t.created_at, t.resolved_at,
+          p.name AS portfolio_name, p.strategy
+        FROM ken_portfolio_trades t
+        JOIN ken_portfolios p ON p.id = t.portfolio_id
+        ORDER BY t.created_at DESC LIMIT 200
+      `);
+
+      // 4. Per-model trade distribution: which markets each strategy traded
+      const { rows: modelMarkets } = await kenQ(`
+        SELECT p.strategy, t.market_id,
+          COUNT(*) AS trade_count,
+          SUM(t.cost_cents) AS total_cost,
+          MAX(t.created_at) AS last_trade
+        FROM ken_portfolio_trades t
+        JOIN ken_portfolios p ON p.id = t.portfolio_id
+        GROUP BY p.strategy, t.market_id
+        ORDER BY p.strategy, trade_count DESC
+      `);
+
+      // 5. Overlap analysis: markets traded by 3+ strategies
+      const { rows: overlaps } = await kenQ(`
+        SELECT t.market_id,
+          COUNT(DISTINCT p.strategy) AS num_strategies,
+          ARRAY_AGG(DISTINCT p.name ORDER BY p.name) AS portfolios,
+          SUM(t.cost_cents) AS total_exposure
+        FROM ken_portfolio_trades t
+        JOIN ken_portfolios p ON p.id = t.portfolio_id
+        GROUP BY t.market_id
+        HAVING COUNT(DISTINCT p.strategy) >= 2
+        ORDER BY num_strategies DESC
+      `);
+
+      // 6. Summary stats
+      const totalInvested = portfolios.reduce((s, p) => s + (parseInt(p.total_invested_cents) || 0), 0);
+      const totalReturned = portfolios.reduce((s, p) => s + (parseInt(p.total_returned_cents) || 0), 0);
+      const totalTrades = tradeHistory.length;
+      const openTrades = activeTrades.length;
+      const uniqueMarkets = [...new Set(tradeHistory.map(t => t.market_id))].length;
+
+      json(res, {
+        portfolios,
+        activeTrades,
+        tradeHistory,
+        modelMarkets,
+        overlaps,
+        summary: {
+          totalInvested, totalReturned,
+          totalPnl: totalReturned - totalInvested,
+          totalTrades, openTrades, uniqueMarkets,
+          activeModels: portfolios.filter(p => parseInt(p.total_invested_cents) > 0).length,
+        }
+      });
+    } catch(e) { json(res, { error: e.message }, 500); }
+  },
+
   'GET /api/predictions-history': async (req, res) => {
     try {
       const { rows } = await kenQ(`
@@ -2199,6 +2286,62 @@ body::after{content:'';position:fixed;top:0;left:0;right:0;bottom:0;background-i
 .pnl-hero{font-size:48px;font-weight:900;font-variant-numeric:tabular-nums;letter-spacing:-1px;line-height:1}
 @keyframes pnlPulse{0%,100%{text-shadow:0 0 8px currentColor}50%{text-shadow:0 0 20px currentColor}}
 
+/* ─── PortFAUXlio Dashboard ─── */
+.pf-banner{text-align:center;padding:14px;margin-bottom:20px;background:linear-gradient(135deg,rgba(139,92,246,.08),rgba(255,105,180,.06));border:1px solid rgba(139,92,246,.2);border-radius:14px;position:relative;overflow:hidden}
+.pf-banner::before{content:'SIMULATED';position:absolute;top:-2px;right:20px;font-size:8px;letter-spacing:3px;color:var(--purple);background:var(--bg);padding:0 8px;border-radius:0 0 4px 4px;border:1px solid rgba(139,92,246,.2);border-top:none;font-weight:800}
+.pf-banner-title{font-size:22px;font-weight:900;background:linear-gradient(135deg,#8b5cf6,#ec4899);-webkit-background-clip:text;-webkit-text-fill-color:transparent;letter-spacing:1px}
+.pf-banner-sub{font-size:10px;color:var(--muted);letter-spacing:2px;text-transform:uppercase;margin-top:4px}
+
+.pf-summary-row{display:flex;gap:10px;margin-bottom:18px;flex-wrap:wrap}
+.pf-summary-card{flex:1;min-width:100px;background:var(--card);border:1px solid var(--border);border-radius:12px;padding:14px;text-align:center}
+.pf-summary-val{font-size:22px;font-weight:800;font-variant-numeric:tabular-nums}
+.pf-summary-lbl{font-size:9px;color:var(--muted);text-transform:uppercase;letter-spacing:1px;margin-top:2px}
+
+/* Active Trades Strip */
+.pf-active-strip{margin-bottom:20px}
+.pf-active-header{display:flex;align-items:center;gap:8px;margin-bottom:10px;font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;color:var(--green)}
+.pf-active-header .dot{width:8px;height:8px;border-radius:50%;background:var(--green);animation:statusPulse 2s ease infinite}
+.pf-active-scroll{display:flex;gap:8px;overflow-x:auto;padding-bottom:8px;scroll-behavior:smooth}
+.pf-active-scroll::-webkit-scrollbar{height:4px}
+.pf-active-scroll::-webkit-scrollbar-thumb{background:var(--border);border-radius:2px}
+.pf-active-card{flex-shrink:0;min-width:200px;max-width:260px;background:var(--card);border:1px solid var(--border);border-radius:10px;padding:10px 14px;transition:all .2s;border-top:2px solid var(--border)}
+.pf-active-card:hover{border-color:rgba(255,255,255,.15);transform:translateY(-2px);box-shadow:0 8px 24px rgba(0,0,0,.3)}
+
+/* Model Leaderboard Table */
+.pf-leaderboard{margin-bottom:20px;border:1px solid var(--border);border-radius:14px;overflow:hidden;background:var(--card)}
+.pf-lb-header{display:grid;grid-template-columns:32px 100px 80px 80px 60px 50px 50px 60px 80px;gap:0;padding:10px 14px;background:rgba(255,255,255,.03);border-bottom:1px solid var(--border);font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;color:var(--muted)}
+.pf-lb-row{display:grid;grid-template-columns:32px 100px 80px 80px 60px 50px 50px 60px 80px;gap:0;padding:10px 14px;border-bottom:1px solid rgba(255,255,255,.03);font-size:11px;transition:all .2s;cursor:default;align-items:center}
+.pf-lb-row:hover{background:rgba(255,255,255,.03)}
+.pf-lb-row:last-child{border-bottom:none}
+.pf-lb-rank{font-size:14px;font-weight:900;color:var(--muted)}
+.pf-lb-name{font-weight:700;display:flex;align-items:center;gap:6px;overflow:hidden}
+.pf-lb-pnl{font-weight:800;font-variant-numeric:tabular-nums}
+.pf-lb-invested{font-variant-numeric:tabular-nums;color:var(--muted)}
+.pf-lb-wr{font-weight:700;font-variant-numeric:tabular-nums}
+.pf-lb-wl{font-variant-numeric:tabular-nums;color:var(--muted)}
+.pf-lb-roi{font-weight:800;font-variant-numeric:tabular-nums;padding:2px 8px;border-radius:4px;text-align:center}
+.pf-lb-bar{height:4px;border-radius:2px;background:rgba(255,255,255,.05);overflow:hidden;margin-top:2px}
+.pf-lb-bar-fill{height:100%;border-radius:2px;transition:width .6s ease}
+
+/* Overlap Warning */
+.pf-overlap{margin-bottom:18px;background:rgba(245,158,11,.04);border:1px solid rgba(245,158,11,.15);border-radius:12px;padding:14px}
+.pf-overlap-title{font-size:11px;font-weight:700;color:var(--yellow);text-transform:uppercase;letter-spacing:.5px;margin-bottom:8px;display:flex;align-items:center;gap:6px}
+
+/* Trade History */
+.pf-history{border:1px solid var(--border);border-radius:14px;overflow:hidden;background:var(--card)}
+.pf-history-header{padding:14px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid var(--border)}
+.pf-history-list{max-height:400px;overflow-y:auto}
+.pf-history-list::-webkit-scrollbar{width:4px}
+.pf-history-list::-webkit-scrollbar-thumb{background:var(--border);border-radius:2px}
+.pf-history-row{display:flex;align-items:center;gap:8px;padding:8px 14px;font-size:11px;border-bottom:1px solid rgba(255,255,255,.02);transition:all .15s}
+.pf-history-row:hover{background:rgba(255,255,255,.03)}
+.pf-history-row:last-child{border-bottom:none}
+
+@media(max-width:900px){
+.pf-lb-header,.pf-lb-row{grid-template-columns:28px 80px 70px 70px 50px 40px 40px 50px 70px;font-size:10px;padding:8px 10px}
+.pf-active-card{min-width:180px}
+}
+
 /* Footer */
 .footer{text-align:center;padding:30px;color:var(--muted);font-size:11px;letter-spacing:1px}
 .footer a{color:var(--pink);text-decoration:none}
@@ -5987,17 +6130,54 @@ async function autonomousScan() {
 // ══════════════════════════════════════════════
 
 // Portfolio strategy filters — each personality decides differently
+// ═══ Portfolio Strategy Filters ═══
+// Each strategy is deliberately exclusive — a signal should match 1-3 portfolios, not all 10.
+// Strategies use edge/confidence/direction/article count/MC/AI to create real diversification.
 const PORTFOLIO_STRATEGIES = {
-  aggressive: (sig) => Math.abs(sig.edge) >= 0.10,
-  conservative: (sig) => sig.confidence >= 0.60 && Math.abs(sig.edge) >= 0.05,
-  weather: (sig) => /weather|temp|rain|snow|wind|hurricane|precip|storm|°f|heat|cold|frost|drought|flood|tornado/i.test(sig.reasoning || ''),
-  momentum: (sig) => /momentum|rising|dropping|delta|volume.spike/i.test(sig.reasoning || sig.signal_type || ''),
-  contrarian: (sig) => sig.direction === 'BUY_NO' || /contrarian|fading/i.test(sig.reasoning || ''),
+  // Alpha: High edge (>15%) with LOWER confidence (<70%). Big edges, less certainty — speculative.
+  aggressive: (sig) => Math.abs(sig.edge) >= 0.15 && sig.confidence < 0.70,
+
+  // Beta: High confidence (>70%) with ANY edge. The "safe" bet — trusts confidence over edge size.
+  conservative: (sig) => sig.confidence >= 0.70 && Math.abs(sig.edge) >= 0.05,
+
+  // Storm: Weather/climate markets identified by market_id patterns or reasoning keywords
+  weather: (sig) => /weather|temp|rain|snow|wind|hurricane|precip|storm|°f|heat|cold|frost|drought|flood|tornado|KXTEMP|KXRAIN|KXSNOW|KXHURR/i.test((sig.reasoning || '') + ' ' + (sig.market_id || '')),
+
+  // Momentum: Only trades when edge increased (high agreement = many articles agree).
+  // Needs 3+ articles with >65% agreement — evidence of growing consensus.
+  momentum: (sig) => (sig.agreement || 0) >= 0.65 && (sig.num_articles || 0) >= 3 && Math.abs(sig.edge) >= 0.05,
+
+  // Contrarian: ONLY BUY_NO trades. Bets against the crowd. Never buys YES.
+  contrarian: (sig) => sig.direction === 'BUY_NO',
+
+  // Nova: Only Gemini AI-enhanced signals. If AI didn't weigh in, skip.
   ai_enhanced: (sig) => sig.ai_enhanced === true,
-  mc_validated: (sig) => sig.mc_consistent === true,
-  scalper: (sig) => Math.abs(sig.edge) >= 0.03 && (sig.volume24h || 0) > 500,
-  patient: (sig) => sig.confidence >= 0.55 && sig.mc_consistent === true && (sig.num_articles || 0) >= 5,
-  random: (sig) => Math.random() < 0.33, // 1 in 3 chance
+
+  // Oracle: MC-validated WITH tight spread (stdDev < 8%) AND moderate edge. The "careful" bot.
+  mc_validated: (sig) => sig.mc_consistent === true && (sig.mc_stddev || 99) < 0.08 && Math.abs(sig.edge) >= 0.05 && Math.abs(sig.edge) < 0.20,
+
+  // Shark: Small edges (3-10%), rapid fire. Takes quantity over quality. Rejects big edges.
+  scalper: (sig) => Math.abs(sig.edge) >= 0.03 && Math.abs(sig.edge) < 0.10 && sig.confidence >= 0.30,
+
+  // Turtle: Ultra-patient. Needs EVERYTHING: 5+ articles, MC consistent, confidence >80%, moderate edge.
+  patient: (sig) => sig.confidence >= 0.80 && sig.mc_consistent === true && (sig.num_articles || 0) >= 5 && Math.abs(sig.edge) >= 0.05,
+
+  // Wildcard: Random 20% of signals — true control group. Lower chance than before to create separation.
+  random: (sig) => Math.random() < 0.20,
+};
+
+// Position sizing per strategy — differentiated risk profiles
+const PORTFOLIO_SIZING = {
+  aggressive: (edge, conf) => Math.max(2, Math.min(5, Math.round(Math.abs(edge) * 30))),     // 2-5 contracts, edge-weighted
+  conservative: (edge, conf) => Math.max(1, Math.min(3, Math.round(conf * 4))),               // 1-3 contracts, confidence-weighted
+  weather: (edge, conf) => 2,                                                                  // Fixed 2 contracts
+  momentum: (edge, conf) => Math.max(1, Math.min(4, Math.round((conf + Math.abs(edge)) * 5))),// 1-4, blended
+  contrarian: (edge, conf) => 1,                                                               // Always 1 contract (high risk)
+  ai_enhanced: (edge, conf) => Math.max(1, Math.min(3, Math.round(conf * 3))),                // 1-3, conservative
+  mc_validated: (edge, conf) => Math.max(2, Math.min(4, Math.round(conf * 5))),               // 2-4, confidence-driven
+  scalper: (edge, conf) => 1,                                                                  // Always 1 (high volume, small bets)
+  patient: (edge, conf) => Math.max(3, Math.min(5, Math.round(conf * 6))),                    // 3-5, big when confident
+  random: (edge, conf) => Math.max(1, Math.min(3, Math.ceil(Math.random() * 3))),             // 1-3 random
 };
 
 let tradeCounter = 0; // For wildcard portfolio
@@ -6028,10 +6208,13 @@ async function autoPortfolioTrade(signal) {
         direction: signal.direction,
         reasoning: signal.reasoning || '',
         signal_type: signal.signal_type || '',
+        market_id: signal.marketId || signal.market_id || '',
         ai_enhanced: signal.aiEnhanced || signal.ai_enhanced || false,
         mc_consistent: signal.mcSimulation?.consistent || signal.mc_consistent || false,
+        mc_stddev: signal.mcSimulation?.stdDev || 0.99,
         volume24h: signal.volume24h || signal.volume_24h || 0,
         num_articles: signal.num_articles || 0,
+        agreement: signal.agreement || 0,
       };
 
       if (!strategy(sigData)) continue;
@@ -6043,10 +6226,11 @@ async function autoPortfolioTrade(signal) {
       `, [pf.id, signal.marketId || signal.market_id]);
       if (dupes.length > 0) continue;
 
-      // Calculate position size: use edge and confidence to scale (1-5 contracts at ~$0.50 each)
+      // Per-strategy position sizing
       const absEdge = Math.abs(parseFloat(sigData.edge));
       const conf = parseFloat(sigData.confidence);
-      const contracts = Math.max(1, Math.min(5, Math.round(absEdge * conf * 20)));
+      const sizeFn = PORTFOLIO_SIZING[pf.strategy] || ((e, c) => Math.max(1, Math.min(3, Math.round(e * c * 15))));
+      const contracts = sizeFn(absEdge, conf);
       const entryPrice = sigData.direction === 'BUY_YES'
         ? Math.round((0.5 - absEdge / 2) * 100) // rough market mid
         : Math.round((0.5 + absEdge / 2) * 100);
@@ -6109,18 +6293,23 @@ async function resolvePortfolioTrades() {
       `, [status, currentPrice, pnl, trade.id]);
 
       // Update portfolio running totals
-      const updateCol = status === 'won' ? 'trades_won' : status === 'lost' ? 'trades_lost' : 'trades_expired';
       const streakVal = status === 'won' ? 1 : status === 'lost' ? -1 : 0;
+      // total_returned = cost_cents + pnl for winning trades (you get your money back + profit)
+      const returned = pnl > 0 ? trade.cost_cents + pnl : (pnl === 0 ? trade.cost_cents : 0);
+      const wonInc = status === 'won' ? 1 : 0;
+      const lostInc = status === 'lost' ? 1 : 0;
+      const expInc = status === 'expired' ? 1 : 0;
       await kenQ(`
         UPDATE ken_portfolios SET
-          balance_cents = balance_cents + $1,
-          total_returned_cents = total_returned_cents + GREATEST(0, $1 + $5),
-          ${updateCol} = ${updateCol} + 1,
-          streak = CASE WHEN $2 > 0 AND streak > 0 THEN streak + 1 WHEN $2 < 0 AND streak < 0 THEN streak - 1 ELSE $2 END,
-          best_trade_cents = GREATEST(best_trade_cents, $1),
-          worst_trade_cents = LEAST(worst_trade_cents, $1)
-        WHERE id = $3
-      `, [pnl, streakVal, trade.portfolio_id, updateCol, trade.cost_cents]);
+          total_returned_cents = total_returned_cents + $1,
+          trades_won = trades_won + $2,
+          trades_lost = trades_lost + $3,
+          trades_expired = trades_expired + $4,
+          streak = CASE WHEN $5 > 0 AND streak > 0 THEN streak + 1 WHEN $5 < 0 AND streak < 0 THEN streak - 1 ELSE $5 END,
+          best_trade_cents = GREATEST(best_trade_cents, $6),
+          worst_trade_cents = LEAST(worst_trade_cents, $6)
+        WHERE id = $7
+      `, [returned, wonInc, lostInc, expInc, streakVal, pnl, trade.portfolio_id]);
 
       // Update daily P&L
       const tradeDate = new Date(trade.created_at).toISOString().split('T')[0];

← 65ed6d8 chore(alshi-dash): update 1 file (.js) [+378/-128]  ·  back to Bertha  ·  chore(alshi-dash): update 1 file (.js) [+209/-56] a53e7ad →