[object Object]

← back to Bertha

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

65ed6d812c2fad4f4b6ee1d14fa865d41d38c730 · 2026-02-16 14:47:31 +0000 · DW Commit Agent

Files touched

Diff

commit 65ed6d812c2fad4f4b6ee1d14fa865d41d38c730
Author: DW Commit Agent <commit-agent@dw-agents.com>
Date:   Mon Feb 16 14:47:31 2026 +0000

    chore(alshi-dash): update 1 file (.js) [+378/-128]
---
 kalshi-dash/server.js | 506 +++++++++++++++++++++++++++++++++++++-------------
 1 file changed, 378 insertions(+), 128 deletions(-)

diff --git a/kalshi-dash/server.js b/kalshi-dash/server.js
index da5a926..73910e7 100644
--- a/kalshi-dash/server.js
+++ b/kalshi-dash/server.js
@@ -1914,15 +1914,25 @@ const routes = {
       out.categories = rows.map(r => ({ category: r.category, count: parseInt(r.cnt) }));
     } catch { out.categories = []; }
 
-    // 5. Portfolio P&L (current state)
+    // 5. Portfolio P&L (current state) — compute actual PnL from invested vs returned
     try {
-      const { rows } = await kenQ(`SELECT p.name, p.strategy, p.balance_cents, p.trades_won, p.trades_lost,
-        p.total_invested_cents, p.daily_budget_cents, p.streak,
+      const { rows } = await kenQ(`SELECT p.id, p.name, p.strategy, p.trades_won, p.trades_lost, p.trades_expired,
+        p.total_invested_cents, p.total_returned_cents, p.daily_budget_cents, p.streak,
+        p.best_trade_cents, p.worst_trade_cents, p.last_trade_at,
+        (p.total_returned_cents - p.total_invested_cents) AS pnl_cents,
         CASE WHEN (p.trades_won + p.trades_lost) > 0
           THEN ROUND(p.trades_won * 100.0 / (p.trades_won + p.trades_lost))
-          ELSE 0 END AS win_rate
-        FROM ken_portfolios p ORDER BY p.balance_cents DESC`);
-      out.portfolios = rows.map(r => ({ ...r, balance_cents: parseInt(r.balance_cents), win_rate: parseInt(r.win_rate) }));
+          ELSE 0 END AS win_rate,
+        (SELECT COUNT(*) FROM ken_portfolio_trades t WHERE t.portfolio_id = p.id) AS total_trades
+        FROM ken_portfolios p ORDER BY (p.total_returned_cents - p.total_invested_cents) DESC`);
+      out.portfolios = rows.map(r => ({
+        ...r,
+        pnl_cents: parseInt(r.pnl_cents) || 0,
+        win_rate: parseInt(r.win_rate) || 0,
+        total_trades: parseInt(r.total_trades) || 0,
+        total_invested_cents: parseInt(r.total_invested_cents) || 0,
+        total_returned_cents: parseInt(r.total_returned_cents) || 0,
+      }));
     } catch { out.portfolios = []; }
 
     // 6. Active markets count and top movers
@@ -1950,6 +1960,65 @@ const routes = {
     // 8. Gemini usage
     out.gemini = { callsToday: geminiCallCount, dailyLimit: GEMINI_DAILY_LIMIT, pctUsed: Math.round(geminiCallCount / GEMINI_DAILY_LIMIT * 100) };
 
+    // 9. Latest signals feed — most recent 15 signals for live ticker
+    try {
+      const { rows } = await kenQ(`SELECT market_id, direction, expected_edge, confidence, risk_approved, created_at,
+        SUBSTRING(reasoning FROM 1 FOR 80) AS reason_short
+        FROM ken_strategy_signals ORDER BY created_at DESC LIMIT 15`);
+      out.latestSignals = rows.map(r => ({
+        market: r.market_id, direction: r.direction,
+        edge: parseFloat(parseFloat(r.expected_edge || 0).toFixed(4)),
+        confidence: parseFloat(parseFloat(r.confidence || 0).toFixed(3)),
+        approved: r.risk_approved, reason: r.reason_short,
+        time: r.created_at,
+      }));
+    } catch { out.latestSignals = []; }
+
+    // 10. Per-market signal stats — heatmap data
+    try {
+      const { rows } = await kenQ(`SELECT market_id,
+        COUNT(*) AS total_signals,
+        AVG(expected_edge::numeric) AS avg_edge,
+        AVG(confidence::numeric) AS avg_conf,
+        COUNT(*) FILTER (WHERE risk_approved) AS approved,
+        MAX(created_at) AS last_signal
+        FROM ken_strategy_signals WHERE created_at > NOW() - INTERVAL '24 hours'
+        GROUP BY market_id ORDER BY total_signals DESC LIMIT 20`);
+      out.marketStats = rows.map(r => ({
+        market: r.market_id, signals: parseInt(r.total_signals),
+        avgEdge: parseFloat(parseFloat(r.avg_edge || 0).toFixed(4)),
+        avgConf: parseFloat(parseFloat(r.avg_conf || 0).toFixed(3)),
+        approved: parseInt(r.approved), lastSignal: r.last_signal,
+      }));
+    } catch { out.marketStats = []; }
+
+    // 11. Recent trades with details
+    try {
+      const { rows } = await kenQ(`SELECT t.id, t.portfolio_id, t.market_id, t.market_title, t.direction, t.contracts,
+        t.entry_price_cents, t.pnl_cents, t.status, 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
+        ORDER BY t.created_at DESC LIMIT 20`);
+      out.recentTrades = rows.map(r => ({
+        ...r, entry_price_cents: parseInt(r.entry_price_cents) || 0, pnl_cents: parseInt(r.pnl_cents) || 0,
+        contracts: parseInt(r.contracts) || 0, cost_cents: parseInt(r.cost_cents) || 0,
+      }));
+    } catch { out.recentTrades = []; }
+
+    // 12. Confidence distribution histogram (10 buckets from 0-1)
+    try {
+      const { rows } = await kenQ(`SELECT
+        WIDTH_BUCKET(confidence::numeric, 0, 1, 10) AS bucket,
+        COUNT(*) AS cnt
+        FROM ken_strategy_signals WHERE created_at > NOW() - INTERVAL '24 hours'
+        GROUP BY bucket ORDER BY bucket`);
+      out.confDistribution = Array.from({length: 10}, (_, i) => {
+        const found = rows.find(r => parseInt(r.bucket) === i + 1);
+        return { range: (i * 10) + '-' + ((i + 1) * 10) + '%', count: found ? parseInt(found.cnt) : 0 };
+      });
+    } catch { out.confDistribution = []; }
+
     json(res, out);
   },
 };
@@ -2058,35 +2127,77 @@ body::after{content:'';position:fixed;top:0;left:0;right:0;bottom:0;background-i
 .sparkline-wrap{display:inline-block;vertical-align:middle}
 .sparkline-svg{display:block}
 
-/* Market Data Panel */
-.mkt-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(340px,1fr));gap:14px;margin-top:20px}
-.mkt-card{background:var(--card);border:1px solid var(--border);border-radius:14px;padding:18px 20px;backdrop-filter:blur(10px);transition:all .3s}
-.mkt-card:hover{border-color:rgba(255,255,255,.1);transform:translateY(-2px)}
+/* ─── Market Data Terminal ─── */
+.mkt-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(340px,1fr));gap:14px;margin-top:16px}
+.mkt-card{background:var(--card);border:1px solid var(--border);border-radius:14px;padding:18px 20px;backdrop-filter:blur(10px);transition:all .3s;position:relative;overflow:hidden}
+.mkt-card::after{content:'';position:absolute;top:0;left:0;right:0;height:2px;background:linear-gradient(90deg,transparent,var(--border),transparent);opacity:0;transition:opacity .3s}
+.mkt-card:hover{border-color:rgba(255,255,255,.12);transform:translateY(-2px);box-shadow:0 12px 40px rgba(0,0,0,.3)}
+.mkt-card:hover::after{opacity:1}
 .mkt-card-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}
 .mkt-card-title{font-size:13px;font-weight:700;text-transform:uppercase;letter-spacing:.5px}
 .mkt-card-badge{padding:3px 8px;border-radius:6px;font-size:10px;font-weight:700;font-variant-numeric:tabular-nums}
 .mkt-bar-chart{display:flex;align-items:flex-end;gap:3px;height:80px;padding-top:4px}
 .mkt-bar{flex:1;border-radius:3px 3px 0 0;min-width:4px;transition:height .5s ease;position:relative}
-.mkt-bar:hover{opacity:.8}
-.mkt-bar-label{position:absolute;top:-14px;left:50%;transform:translateX(-50%);font-size:8px;color:var(--muted);white-space:nowrap;display:none}
+.mkt-bar:hover{opacity:1!important;filter:brightness(1.3)}
+.mkt-bar-label{position:absolute;top:-16px;left:50%;transform:translateX(-50%);font-size:8px;color:var(--muted);white-space:nowrap;display:none;background:var(--card);padding:1px 4px;border-radius:3px;border:1px solid var(--border)}
 .mkt-bar:hover .mkt-bar-label{display:block}
-.mkt-source-list{display:flex;flex-direction:column;gap:4px}
-.mkt-source-row{display:flex;align-items:center;gap:8px;font-size:11px}
-.mkt-source-bar{height:6px;border-radius:3px;transition:width .5s}
-.mkt-source-name{min-width:80px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
-.mkt-source-val{font-variant-numeric:tabular-nums;color:var(--muted);min-width:40px;text-align:right}
-.mkt-gauge{position:relative;width:100%;height:12px;background:rgba(255,255,255,.05);border-radius:6px;overflow:hidden;margin-top:8px}
-.mkt-gauge-fill{height:100%;border-radius:6px;transition:width .5s}
-.mkt-gauge-label{position:absolute;top:50%;right:8px;transform:translateY(-50%);font-size:9px;font-weight:700;color:var(--text)}
-.mkt-ticker-strip{display:flex;gap:20px;overflow:hidden;padding:10px 0;border-top:1px solid var(--border);border-bottom:1px solid var(--border);margin:16px 0}
-.mkt-ticker-track{display:flex;gap:20px;animation:tickerScroll 30s linear infinite;width:max-content}
+.mkt-source-list{display:flex;flex-direction:column;gap:5px}
+.mkt-source-row{display:flex;align-items:center;gap:8px;font-size:11px;padding:2px 0;transition:background .2s;border-radius:4px}
+.mkt-source-row:hover{background:rgba(255,255,255,.03)}
+.mkt-source-bar{height:7px;border-radius:4px;transition:width .6s cubic-bezier(.4,0,.2,1)}
+.mkt-source-name{min-width:85px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
+.mkt-source-val{font-variant-numeric:tabular-nums;color:var(--muted);min-width:45px;text-align:right;font-weight:600}
+.mkt-gauge{position:relative;width:100%;height:14px;background:rgba(255,255,255,.05);border-radius:7px;overflow:hidden;margin-top:8px}
+.mkt-gauge-fill{height:100%;border-radius:7px;transition:width .6s cubic-bezier(.4,0,.2,1)}
+.mkt-gauge-label{position:absolute;top:50%;right:8px;transform:translateY(-50%);font-size:9px;font-weight:700;color:var(--text);text-shadow:0 1px 2px rgba(0,0,0,.5)}
+/* Scrolling ticker */
+.mkt-ticker-strip{overflow:hidden;padding:12px 0;border-top:1px solid rgba(255,255,255,.06);border-bottom:1px solid rgba(255,255,255,.06);margin:16px 0;background:linear-gradient(90deg,rgba(5,5,16,.8),transparent 5%,transparent 95%,rgba(5,5,16,.8))}
+.mkt-ticker-track{display:flex;gap:24px;animation:tickerScroll 40s linear infinite;width:max-content}
 .mkt-ticker-track:hover{animation-play-state:paused}
 @keyframes tickerScroll{0%{transform:translateX(0)}100%{transform:translateX(-50%)}}
-.mkt-ticker-item{display:flex;align-items:center;gap:6px;font-size:12px;font-weight:600;white-space:nowrap;font-variant-numeric:tabular-nums}
+.mkt-ticker-item{display:flex;align-items:center;gap:8px;font-size:12px;font-weight:600;white-space:nowrap;font-variant-numeric:tabular-nums;padding:4px 12px;border-radius:6px;background:rgba(255,255,255,.02);border:1px solid rgba(255,255,255,.04)}
 .mkt-change{font-size:11px;font-weight:700}
 .mkt-change.up{color:var(--green)}
 .mkt-change.down{color:var(--red)}
 .mkt-change.flat{color:var(--muted)}
+/* Signal feed */
+.signal-feed{display:flex;flex-direction:column;gap:4px;max-height:300px;overflow-y:auto}
+.signal-feed::-webkit-scrollbar{width:4px}
+.signal-feed::-webkit-scrollbar-thumb{background:var(--border);border-radius:2px}
+.signal-row{display:flex;align-items:center;gap:8px;padding:6px 10px;border-radius:8px;font-size:11px;transition:all .2s;border:1px solid transparent;animation:signalIn .3s ease}
+.signal-row:hover{background:rgba(255,255,255,.04);border-color:var(--border)}
+@keyframes signalIn{from{opacity:0;transform:translateX(-8px)}to{opacity:1;transform:none}}
+.signal-dir{padding:2px 8px;border-radius:4px;font-weight:800;font-size:10px;text-transform:uppercase;letter-spacing:.5px}
+.signal-dir.yes{background:rgba(34,197,94,.15);color:var(--green)}
+.signal-dir.no{background:rgba(239,68,68,.15);color:var(--red)}
+.signal-market{font-weight:600;min-width:160px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
+.signal-edge{font-variant-numeric:tabular-nums;font-weight:700;min-width:50px;text-align:right}
+.signal-conf{font-variant-numeric:tabular-nums;min-width:40px;text-align:right;color:var(--muted)}
+.signal-time{font-size:9px;color:var(--muted);margin-left:auto;white-space:nowrap}
+.signal-approved{width:6px;height:6px;border-radius:50%;flex-shrink:0}
+/* Heatmap grid */
+.heatmap-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:6px}
+.heatmap-cell{padding:8px 10px;border-radius:8px;border:1px solid var(--border);transition:all .2s;cursor:default;text-align:center}
+.heatmap-cell:hover{transform:scale(1.03);z-index:1;box-shadow:0 4px 16px rgba(0,0,0,.4)}
+.heatmap-market{font-size:10px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;margin-bottom:2px}
+.heatmap-edge{font-size:16px;font-weight:800;font-variant-numeric:tabular-nums}
+.heatmap-meta{font-size:8px;color:var(--muted);margin-top:2px}
+/* Histogram */
+.histogram{display:flex;align-items:flex-end;gap:2px;height:80px}
+.hist-bar{flex:1;border-radius:3px 3px 0 0;transition:height .5s;position:relative;min-width:0}
+.hist-bar:hover{filter:brightness(1.3)}
+.hist-label{position:absolute;bottom:-16px;left:50%;transform:translateX(-50%);font-size:7px;color:var(--muted);white-space:nowrap}
+/* Trade log */
+.trade-log{display:flex;flex-direction:column;gap:3px;max-height:260px;overflow-y:auto}
+.trade-log::-webkit-scrollbar{width:4px}
+.trade-log::-webkit-scrollbar-thumb{background:var(--border);border-radius:2px}
+.trade-row{display:flex;align-items:center;gap:6px;padding:5px 10px;border-radius:6px;font-size:10px;border:1px solid transparent;transition:all .2s}
+.trade-row:hover{background:rgba(255,255,255,.03);border-color:var(--border)}
+.trade-portfolio{font-weight:600;min-width:70px}
+.trade-pnl{font-weight:800;font-variant-numeric:tabular-nums;min-width:55px;text-align:right}
+/* P&L big number */
+.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}}
 
 /* Footer */
 .footer{text-align:center;padding:30px;color:var(--muted);font-size:11px;letter-spacing:1px}
@@ -2313,34 +2424,43 @@ function PredictionsTab() {
 }
 
 // ── Sparkline SVG Component ──
-function Sparkline({ data, width = 120, height = 30, color = '#8b5cf6', showDots = false }) {
+function Sparkline({ data, width = 120, height = 30, color = '#8b5cf6', showDots = false, showArea = true, label }) {
   if (!data || data.length < 2) return null;
   const max = Math.max(...data);
   const min = Math.min(...data);
   const range = max - min || 1;
+  const gradId = 'grad-' + color.replace('#','') + '-' + width;
   const points = data.map((v, i) => {
     const x = (i / (data.length - 1)) * width;
-    const y = height - ((v - min) / range) * (height - 4) - 2;
+    const y = height - ((v - min) / range) * (height - 6) - 3;
     return x + ',' + y;
   }).join(' ');
   const areaPoints = points + ' ' + width + ',' + height + ' 0,' + height;
+  const lastVal = data[data.length - 1];
+  const prevVal = data[data.length - 2];
+  const trending = lastVal >= prevVal;
   return (
-    <svg className="sparkline-svg" width={width} height={height} viewBox={'0 0 ' + width + ' ' + height}>
-      <defs><linearGradient id={'grad-' + color.replace('#','')} x1="0" y1="0" x2="0" y2="1">
-        <stop offset="0%" stopColor={color} stopOpacity="0.3"/><stop offset="100%" stopColor={color} stopOpacity="0.02"/>
-      </linearGradient></defs>
-      <polygon points={areaPoints} fill={'url(#grad-' + color.replace('#','') + ')'} />
-      <polyline points={points} fill="none" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
-      {showDots && data.map((v, i) => {
-        const x = (i / (data.length - 1)) * width;
-        const y = height - ((v - min) / range) * (height - 4) - 2;
-        return <circle key={i} cx={x} cy={y} r="2" fill={color} opacity={i === data.length - 1 ? 1 : 0.4} />;
-      })}
-    </svg>
+    <div style={{position:'relative'}}>
+      <svg className="sparkline-svg" width={width} height={height} viewBox={'0 0 ' + width + ' ' + height}>
+        <defs><linearGradient id={gradId} x1="0" y1="0" x2="0" y2="1">
+          <stop offset="0%" stopColor={color} stopOpacity="0.25"/><stop offset="100%" stopColor={color} stopOpacity="0.01"/>
+        </linearGradient></defs>
+        {showArea && <polygon points={areaPoints} fill={'url(#' + gradId + ')'} />}
+        <polyline points={points} fill="none" stroke={color} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
+        {showDots && data.map((v, i) => {
+          const x = (i / (data.length - 1)) * width;
+          const y = height - ((v - min) / range) * (height - 6) - 3;
+          return <circle key={i} cx={x} cy={y} r={i === data.length - 1 ? 3 : 1.5} fill={i === data.length - 1 ? color : 'transparent'} stroke={color} strokeWidth={i === data.length - 1 ? 0 : 0.5} opacity={i === data.length - 1 ? 1 : 0.5} />;
+        })}
+      </svg>
+      {label && <div style={{position:'absolute',top:2,right:4,fontSize:9,color:trending?'var(--green)':'var(--red)',fontWeight:700}}>
+        {trending?'\\u25B2':'\\u25BC'} {lastVal.toLocaleString()}
+      </div>}
+    </div>
   );
 }
 
-// ── Horizontal Bar Chart ──
+// ── Vertical Bar Chart ──
 function BarChart({ data, maxVal, height = 80, color = '#8b5cf6' }) {
   if (!data || data.length === 0) return null;
   const mx = maxVal || Math.max(...data.map(d => d.value)) || 1;
@@ -2355,7 +2475,37 @@ function BarChart({ data, maxVal, height = 80, color = '#8b5cf6' }) {
   );
 }
 
-// ── Market Data Tab ──
+// ── Confidence Histogram ──
+function ConfHistogram({ data }) {
+  if (!data || data.length === 0) return null;
+  const max = Math.max(...data.map(d => d.count)) || 1;
+  const colors = ['#ef4444','#f97316','#f59e0b','#eab308','#84cc16','#22c55e','#14b8a6','#06b6d4','#3b82f6','#8b5cf6'];
+  return (
+    <div className="histogram">
+      {data.map((d, i) => (
+        <div key={i} className="hist-bar" style={{
+          height: Math.max(2, (d.count / max) * 70) + 'px',
+          background: colors[i] || 'var(--muted)',
+          opacity: d.count > 0 ? 0.8 : 0.2,
+        }}>
+          <div className="hist-label">{d.range}</div>
+        </div>
+      ))}
+    </div>
+  );
+}
+
+// ── Time ago helper ──
+function timeAgo(ts) {
+  if (!ts) return '';
+  const diff = Date.now() - new Date(ts).getTime();
+  if (diff < 60000) return Math.floor(diff / 1000) + 's ago';
+  if (diff < 3600000) return Math.floor(diff / 60000) + 'm ago';
+  if (diff < 86400000) return Math.floor(diff / 3600000) + 'h ago';
+  return Math.floor(diff / 86400000) + 'd ago';
+}
+
+// ── Market Data Tab — Bloomberg-style terminal ──
 function MarketDataTab() {
   const [mkt, setMkt] = useState(null);
   const [tick, setTick] = useState(0);
@@ -2363,12 +2513,14 @@ function MarketDataTab() {
   useEffect(() => {
     const load = () => api('/api/inc/market-data').then(setMkt).catch(e => console.error(e));
     load();
-    const iv = setInterval(load, 10000);
+    const iv = setInterval(load, 8000);
     return () => clearInterval(iv);
   }, []);
   useEffect(() => { const iv = setInterval(() => setTick(t => t + 1), 1000); return () => clearInterval(iv); }, []);
 
-  if (!mkt) return <div style={{textAlign:'center',padding:60,color:'var(--muted)'}}>Loading market data...</div>;
+  if (!mkt) return <div style={{textAlign:'center',padding:60,color:'var(--muted)'}}>
+    <div style={{fontSize:24,marginBottom:8,animation:'avatarFloat 2s ease-in-out infinite'}}>Loading market data...</div>
+  </div>;
 
   const signalVals = (mkt.signalTrend || []).map(s => s.signals);
   const edgeVals = (mkt.signalTrend || []).map(s => s.avgEdge * 100);
@@ -2377,30 +2529,50 @@ function MarketDataTab() {
   const totalSent24h = sentVals.reduce((a, b) => a + b, 0);
   const totalSignals = signalVals.reduce((a, b) => a + b, 0);
   const at = mkt.approvalTrend || {};
-  const changeArrow = at.change > 0 ? '\\u25B2' : at.change < 0 ? '\\u25BC' : '—';
+  const changeArrow = at.change > 0 ? '\\u25B2' : at.change < 0 ? '\\u25BC' : '\\u2014';
   const changeClass = at.change > 0 ? 'up' : at.change < 0 ? 'down' : 'flat';
+  const totalPnl = (mkt.portfolios || []).reduce((s, p) => s + (p.pnl_cents || 0), 0);
+  const totalTrades = (mkt.portfolios || []).reduce((s, p) => s + (p.total_trades || 0), 0);
+  const avgEdge = edgeVals.length > 0 ? (edgeVals.reduce((a,b)=>a+b,0) / edgeVals.length) : 0;
 
-  // Ticker items — key metrics with change indicators
+  // Ticker items
   const tickerItems = [
-    { label: 'SIGNALS/24H', value: totalSignals, color: 'var(--blue)' },
+    { label: 'P&L', value: (totalPnl >= 0 ? '+' : '') + (totalPnl / 100).toFixed(2), color: totalPnl >= 0 ? 'var(--green)' : 'var(--red)' },
+    { label: 'SIGNALS', value: totalSignals, color: 'var(--blue)' },
     { label: 'APPROVAL', value: at.current + '%', change: at.change, color: 'var(--green)' },
     { label: 'SENTIMENT', value: totalSent24h.toLocaleString(), color: 'var(--pink)' },
-    { label: 'AVG EDGE', value: edgeVals.length > 0 ? (edgeVals.reduce((a,b)=>a+b,0) / edgeVals.length).toFixed(2) + '%' : '—', color: 'var(--purple)' },
+    { label: 'AVG EDGE', value: avgEdge.toFixed(1) + '%', color: 'var(--purple)' },
+    { label: 'TRADES', value: totalTrades, color: 'var(--yellow)' },
     { label: 'MARKETS', value: mkt.activeMarkets, color: 'var(--cyan)' },
-    { label: 'MOMENTUM', value: mkt.momentumKeys, color: 'var(--yellow)' },
-    { label: 'GEMINI', value: mkt.gemini.pctUsed + '%', color: 'var(--teal)' },
-    { label: 'SOURCES', value: (mkt.sourceMix || []).length, color: 'var(--blue)' },
+    { label: 'GEMINI', value: mkt.gemini.callsToday + '/' + mkt.gemini.dailyLimit, color: 'var(--teal)' },
   ];
   const doubledTicker = tickerItems.concat(tickerItems);
 
+  const stratColors = {aggressive:'#ef4444',conservative:'#3b82f6',weather:'#06b6d4',momentum:'#f59e0b',contrarian:'#8b5cf6',ai_enhanced:'#ec4899',mc_validated:'#a855f7',scalper:'#eab308',patient:'#22c55e',random:'#64748b'};
+  const stratEmojis = {aggressive:'\\uD83D\\uDD25',conservative:'\\uD83C\\uDFDB\\uFE0F',weather:'\\u26C8\\uFE0F',momentum:'\\uD83D\\uDE80',contrarian:'\\uD83D\\uDD04',ai_enhanced:'\\uD83E\\uDDE0',mc_validated:'\\uD83C\\uDFB2',scalper:'\\u26A1',patient:'\\uD83D\\uDC22',random:'\\uD83C\\uDFB0'};
+
   return (<div>
-    {/* Ticker Strip */}
+    {/* ═══ Hero P&L Banner ═══ */}
+    <div style={{textAlign:'center',padding:'20px 0 8px',borderBottom:'1px solid var(--border)'}}>
+      <div style={{fontSize:10,color:'var(--muted)',textTransform:'uppercase',letterSpacing:2,marginBottom:4}}>Total Portfolio P&L</div>
+      <div className="pnl-hero" style={{color: totalPnl >= 0 ? 'var(--green)' : 'var(--red)', animation: totalPnl !== 0 ? 'pnlPulse 3s ease-in-out infinite' : 'none'}}>
+        {totalPnl >= 0 ? '+' : ''}{(totalPnl / 100).toFixed(2)}
+      </div>
+      <div style={{display:'flex',justifyContent:'center',gap:24,marginTop:8,fontSize:11,color:'var(--muted)'}}>
+        <span>{totalTrades} trades</span>
+        <span>{(mkt.portfolios||[]).filter(p => p.pnl_cents > 0).length} winning</span>
+        <span>{(mkt.portfolios||[]).filter(p => p.pnl_cents < 0).length} losing</span>
+        <span>{avgEdge.toFixed(1)}% avg edge</span>
+      </div>
+    </div>
+
+    {/* ═══ Scrolling Ticker ═══ */}
     <div className="mkt-ticker-strip">
       <div className="mkt-ticker-track">
         {doubledTicker.map((t, i) => (
           <div key={i} className="mkt-ticker-item">
-            <span style={{color:'var(--muted)',fontSize:10}}>{t.label}</span>
-            <span style={{color:t.color}}>{t.value}</span>
+            <span style={{color:'var(--muted)',fontSize:9,textTransform:'uppercase',letterSpacing:.5}}>{t.label}</span>
+            <span style={{color:t.color,fontWeight:800}}>{t.value}</span>
             {t.change !== undefined && <span className={'mkt-change ' + (t.change > 0 ? 'up' : t.change < 0 ? 'down' : 'flat')}>
               {t.change > 0 ? '\\u25B2' : t.change < 0 ? '\\u25BC' : ''}{Math.abs(t.change)}%
             </span>}
@@ -2410,37 +2582,62 @@ function MarketDataTab() {
     </div>
 
     <div className="mkt-grid">
-      {/* Signal Volume Sparkline */}
-      <div className="mkt-card">
+      {/* ═══ Live Signal Feed ═══ */}
+      <div className="mkt-card" style={{gridColumn:'span 2'}}>
         <div className="mkt-card-header">
-          <span className="mkt-card-title" style={{color:'var(--blue)'}}>Signal Volume (24h)</span>
-          <span className="mkt-card-badge" style={{background:'rgba(59,130,246,.15)',color:'var(--blue)'}}>{totalSignals} signals</span>
+          <span className="mkt-card-title" style={{color:'var(--blue)'}}>
+            <span style={{display:'inline-block',width:6,height:6,borderRadius:'50%',background:'var(--green)',marginRight:6,animation:'statusPulse 2s ease infinite'}}></span>
+            Live Signal Feed
+          </span>
+          <span className="mkt-card-badge" style={{background:'rgba(59,130,246,.15)',color:'var(--blue)'}}>{(mkt.latestSignals||[]).length} latest</span>
         </div>
-        <Sparkline data={signalVals} width={300} height={50} color="#3b82f6" showDots={true} />
-        <BarChart data={(mkt.signalTrend || []).map((s, i) => ({
-          value: s.signals, label: new Date(s.hour).toLocaleTimeString('en-US', {hour:'numeric',hour12:true}),
-          color: s.approved > 0 ? 'rgba(34,197,94,.6)' : 'rgba(59,130,246,.4)',
-        }))} height={50} />
-        <div style={{display:'flex',gap:12,marginTop:8,fontSize:10,color:'var(--muted)'}}>
-          <span>\\u2588 Green = Approved</span><span>\\u2588 Blue = Total</span>
+        <div className="signal-feed">
+          {(mkt.latestSignals || []).map((sig, i) => (
+            <div key={i} className="signal-row">
+              <div className="signal-approved" style={{background: sig.approved ? 'var(--green)' : 'var(--red)'}} title={sig.approved ? 'Approved' : 'Rejected'} />
+              <span className={'signal-dir ' + sig.direction}>{sig.direction}</span>
+              <span className="signal-market" title={sig.market}>{sig.market}</span>
+              <span className="signal-edge" style={{color: sig.edge > 0 ? 'var(--green)' : 'var(--red)'}}>
+                {sig.edge > 0 ? '+' : ''}{(sig.edge * 100).toFixed(1)}%
+              </span>
+              <span className="signal-conf" title="Confidence">{(sig.confidence * 100).toFixed(0)}%</span>
+              <span className="signal-time">{timeAgo(sig.time)}</span>
+            </div>
+          ))}
+          {(!mkt.latestSignals || mkt.latestSignals.length === 0) && <div style={{padding:20,textAlign:'center',color:'var(--muted)',fontSize:12}}>No recent signals</div>}
         </div>
       </div>
 
-      {/* Sentiment Volume */}
+      {/* ═══ Signal Volume Sparkline ═══ */}
       <div className="mkt-card">
         <div className="mkt-card-header">
-          <span className="mkt-card-title" style={{color:'var(--pink)'}}>Sentiment Flow (24h)</span>
-          <span className="mkt-card-badge" style={{background:'rgba(236,72,153,.15)',color:'var(--pink)'}}>{totalSent24h.toLocaleString()} articles</span>
+          <span className="mkt-card-title" style={{color:'var(--blue)'}}>Signal Volume</span>
+          <span className="mkt-card-badge" style={{background:'rgba(59,130,246,.15)',color:'var(--blue)'}}>{totalSignals}</span>
         </div>
-        <Sparkline data={sentVals} width={300} height={50} color="#ec4899" />
-        <div style={{display:'flex',gap:8,marginTop:8,fontSize:10,color:'var(--muted)'}}>
-          <span>Avg Sentiment: <strong style={{color: sentScoreVals.length > 0 && sentScoreVals.reduce((a,b)=>a+b,0)/sentScoreVals.length > 0 ? 'var(--green)' : 'var(--red)'}}>
-            {sentScoreVals.length > 0 ? (sentScoreVals.reduce((a,b)=>a+b,0)/sentScoreVals.length).toFixed(3) : '—'}
+        <Sparkline data={signalVals} width={300} height={50} color="#3b82f6" showDots={true} label={true} />
+        <BarChart data={(mkt.signalTrend || []).map((s) => ({
+          value: s.signals,
+          label: new Date(s.hour).toLocaleTimeString('en-US', {hour:'numeric',hour12:true}),
+          color: s.approved > s.signals * 0.5 ? 'rgba(34,197,94,.6)' : 'rgba(239,68,68,.4)',
+        }))} height={45} />
+      </div>
+
+      {/* ═══ Sentiment Flow ═══ */}
+      <div className="mkt-card">
+        <div className="mkt-card-header">
+          <span className="mkt-card-title" style={{color:'var(--pink)'}}>Sentiment Flow</span>
+          <span className="mkt-card-badge" style={{background:'rgba(236,72,153,.15)',color:'var(--pink)'}}>{totalSent24h.toLocaleString()}</span>
+        </div>
+        <Sparkline data={sentVals} width={300} height={50} color="#ec4899" label={true} />
+        <div style={{display:'flex',justifyContent:'space-between',marginTop:8,fontSize:10,color:'var(--muted)'}}>
+          <span>Avg: <strong style={{color: sentScoreVals.length > 0 && sentScoreVals.reduce((a,b)=>a+b,0)/sentScoreVals.length > 0 ? 'var(--green)' : 'var(--red)'}}>
+            {sentScoreVals.length > 0 ? (sentScoreVals.reduce((a,b)=>a+b,0)/sentScoreVals.length).toFixed(3) : '\\u2014'}
           </strong></span>
+          <span>{(mkt.sourceMix||[]).length} sources</span>
         </div>
       </div>
 
-      {/* Approval Rate Trend */}
+      {/* ═══ Approval Rate ═══ */}
       <div className="mkt-card">
         <div className="mkt-card-header">
           <span className="mkt-card-title" style={{color:'var(--green)'}}>Approval Rate</span>
@@ -2448,38 +2645,71 @@ function MarketDataTab() {
         </div>
         <div style={{display:'flex',alignItems:'baseline',gap:12,marginBottom:8}}>
           <span style={{fontSize:42,fontWeight:800,color:'var(--green)',fontVariantNumeric:'tabular-nums'}}>{at.current}%</span>
-          <span style={{fontSize:12,color:'var(--muted)'}}>vs {at.prior}% prior 6h</span>
+          <span style={{fontSize:12,color:'var(--muted)'}}>vs {at.prior}% prior</span>
         </div>
         <div className="mkt-gauge">
           <div className="mkt-gauge-fill" style={{width: at.current + '%', background:'linear-gradient(90deg,var(--green),var(--teal))'}} />
-          <div className="mkt-gauge-label">{at.current}%</div>
-        </div>
-        <div style={{display:'flex',justifyContent:'space-between',fontSize:10,color:'var(--muted)',marginTop:4}}>
-          <span>Recent: {at.recentSignals} signals</span><span>Prior: {at.priorSignals} signals</span>
+          <div className="mkt-gauge-label">{at.recentSignals} signals</div>
         </div>
       </div>
 
-      {/* Edge Distribution */}
+      {/* ═══ Edge Trend ═══ */}
       <div className="mkt-card">
         <div className="mkt-card-header">
-          <span className="mkt-card-title" style={{color:'var(--purple)'}}>Edge Trend (24h)</span>
+          <span className="mkt-card-title" style={{color:'var(--purple)'}}>Edge Trend</span>
           <span className="mkt-card-badge" style={{background:'rgba(139,92,246,.15)',color:'var(--purple)'}}>
-            Avg: {edgeVals.length > 0 ? (edgeVals.reduce((a,b)=>a+b,0)/edgeVals.length).toFixed(2) : 0}%
+            {avgEdge.toFixed(1)}% avg
           </span>
         </div>
-        <Sparkline data={edgeVals} width={300} height={50} color="#8b5cf6" showDots={true} />
+        <Sparkline data={edgeVals} width={300} height={50} color="#8b5cf6" showDots={true} label={true} />
       </div>
 
-      {/* Source Mix */}
+      {/* ═══ Market Heatmap ═══ */}
+      {(mkt.marketStats || []).length > 0 && <div className="mkt-card" style={{gridColumn:'span 2'}}>
+        <div className="mkt-card-header">
+          <span className="mkt-card-title" style={{color:'var(--yellow)'}}>Market Heatmap (24h)</span>
+          <span className="mkt-card-badge" style={{background:'rgba(245,158,11,.15)',color:'var(--yellow)'}}>{(mkt.marketStats||[]).length} markets</span>
+        </div>
+        <div className="heatmap-grid">
+          {(mkt.marketStats || []).map((m, i) => {
+            const edge = m.avgEdge * 100;
+            const intensity = Math.min(1, Math.abs(edge) / 25);
+            const bg = edge >= 0
+              ? 'rgba(34,197,94,' + (0.08 + intensity * 0.2) + ')'
+              : 'rgba(239,68,68,' + (0.08 + intensity * 0.2) + ')';
+            const borderC = edge >= 0 ? 'rgba(34,197,94,' + (0.2 + intensity * 0.3) + ')' : 'rgba(239,68,68,' + (0.2 + intensity * 0.3) + ')';
+            return (<div key={i} className="heatmap-cell" style={{background: bg, borderColor: borderC}}>
+              <div className="heatmap-market">{m.market}</div>
+              <div className="heatmap-edge" style={{color: edge >= 0 ? 'var(--green)' : 'var(--red)'}}>
+                {edge >= 0 ? '+' : ''}{edge.toFixed(1)}%
+              </div>
+              <div className="heatmap-meta">{m.signals} sig | {m.approved} appr | {(m.avgConf * 100).toFixed(0)}% conf</div>
+            </div>);
+          })}
+        </div>
+      </div>}
+
+      {/* ═══ Confidence Distribution ═══ */}
+      {(mkt.confDistribution || []).length > 0 && <div className="mkt-card">
+        <div className="mkt-card-header">
+          <span className="mkt-card-title" style={{color:'var(--cyan)'}}>Confidence Distribution</span>
+        </div>
+        <ConfHistogram data={mkt.confDistribution} />
+        <div style={{marginTop:18,display:'flex',justifyContent:'space-between',fontSize:9,color:'var(--muted)'}}>
+          <span>Low conf</span><span>High conf</span>
+        </div>
+      </div>}
+
+      {/* ═══ Intelligence Sources ═══ */}
       <div className="mkt-card">
         <div className="mkt-card-header">
           <span className="mkt-card-title" style={{color:'var(--cyan)'}}>Intelligence Sources</span>
-          <span className="mkt-card-badge" style={{background:'rgba(6,182,212,.15)',color:'var(--cyan)'}}>{(mkt.sourceMix||[]).length} active</span>
+          <span className="mkt-card-badge" style={{background:'rgba(6,182,212,.15)',color:'var(--cyan)'}}>{(mkt.sourceMix||[]).length}</span>
         </div>
         <div className="mkt-source-list">
-          {(mkt.sourceMix || []).slice(0, 10).map((s, i) => {
+          {(mkt.sourceMix || []).slice(0, 12).map((s, i) => {
             const maxC = (mkt.sourceMix || [])[0]?.count || 1;
-            const colors = ['#3b82f6','#8b5cf6','#ec4899','#ef4444','#f59e0b','#22c55e','#06b6d4','#14b8a6','#6366f1','#d946ef'];
+            const colors = ['#3b82f6','#8b5cf6','#ec4899','#ef4444','#f59e0b','#22c55e','#06b6d4','#14b8a6','#6366f1','#d946ef','#f97316','#a855f7'];
             return (<div key={i} className="mkt-source-row">
               <span className="mkt-source-name">{s.source}</span>
               <div style={{flex:1,display:'flex',alignItems:'center',gap:4}}>
@@ -2494,79 +2724,99 @@ function MarketDataTab() {
         </div>
       </div>
 
-      {/* Gemini AI Usage */}
+      {/* ═══ Gemini + Categories row ═══ */}
       <div className="mkt-card">
         <div className="mkt-card-header">
-          <span className="mkt-card-title" style={{color:'var(--teal)'}}>Gemini AI Budget</span>
+          <span className="mkt-card-title" style={{color:'var(--teal)'}}>Gemini AI</span>
           <span className="mkt-card-badge" style={{background: mkt.gemini.pctUsed > 80 ? 'rgba(239,68,68,.15)' : 'rgba(20,184,166,.15)', color: mkt.gemini.pctUsed > 80 ? 'var(--red)' : 'var(--teal)'}}>
-            {mkt.gemini.pctUsed}% used
+            {mkt.gemini.pctUsed}%
           </span>
         </div>
         <div style={{display:'flex',alignItems:'baseline',gap:8,marginBottom:8}}>
-          <span style={{fontSize:36,fontWeight:800,color:'var(--teal)',fontVariantNumeric:'tabular-nums'}}>{mkt.gemini.callsToday}</span>
-          <span style={{fontSize:12,color:'var(--muted)'}}>/ {mkt.gemini.dailyLimit} daily limit</span>
+          <span style={{fontSize:32,fontWeight:800,color:'var(--teal)',fontVariantNumeric:'tabular-nums'}}>{mkt.gemini.callsToday}</span>
+          <span style={{fontSize:11,color:'var(--muted)'}}>/ {mkt.gemini.dailyLimit}</span>
         </div>
         <div className="mkt-gauge">
-          <div className="mkt-gauge-fill" style={{
-            width: mkt.gemini.pctUsed + '%',
-            background: mkt.gemini.pctUsed > 80 ? 'linear-gradient(90deg,var(--yellow),var(--red))' : 'linear-gradient(90deg,var(--teal),var(--cyan))'
-          }} />
-          <div className="mkt-gauge-label">{mkt.gemini.callsToday}/{mkt.gemini.dailyLimit}</div>
+          <div className="mkt-gauge-fill" style={{width: mkt.gemini.pctUsed + '%', background: mkt.gemini.pctUsed > 80 ? 'linear-gradient(90deg,var(--yellow),var(--red))' : 'linear-gradient(90deg,var(--teal),var(--cyan))'}} />
+          <div className="mkt-gauge-label">{mkt.gemini.pctUsed}%</div>
+        </div>
+        {/* Categories inline */}
+        <div style={{marginTop:14,display:'flex',flexWrap:'wrap',gap:4}}>
+          {(mkt.categories || []).slice(0, 8).map((c, i) => {
+            const colors = ['#f59e0b','#3b82f6','#ec4899','#22c55e','#8b5cf6','#ef4444','#06b6d4','#14b8a6'];
+            return (<div key={i} style={{padding:'3px 10px',borderRadius:6,background:'rgba(255,255,255,.03)',border:'1px solid var(--border)',fontSize:9,display:'flex',alignItems:'center',gap:4}}>
+              <div style={{width:4,height:4,borderRadius:'50%',background:colors[i % colors.length]}} />
+              <span style={{fontWeight:600}}>{c.category}</span>
+              <span style={{color:'var(--muted)'}}>{c.count}</span>
+            </div>);
+          })}
         </div>
       </div>
 
-      {/* Category Distribution */}
-      <div className="mkt-card" style={{gridColumn:'span 2'}}>
+      {/* ═══ Recent Trades ═══ */}
+      {(mkt.recentTrades || []).length > 0 && <div className="mkt-card" style={{gridColumn:'span 2'}}>
         <div className="mkt-card-header">
-          <span className="mkt-card-title" style={{color:'var(--yellow)'}}>Signal Categories (24h)</span>
+          <span className="mkt-card-title" style={{color:'var(--yellow)'}}>Recent Trades</span>
+          <span className="mkt-card-badge" style={{background:'rgba(245,158,11,.15)',color:'var(--yellow)'}}>{totalTrades} total</span>
         </div>
-        <div style={{display:'flex',flexWrap:'wrap',gap:6}}>
-          {(mkt.categories || []).map((c, i) => {
-            const maxC = (mkt.categories || [])[0]?.count || 1;
-            const pct = (c.count / maxC * 100);
-            const colors = ['#f59e0b','#3b82f6','#ec4899','#22c55e','#8b5cf6','#ef4444','#06b6d4','#14b8a6','#6366f1','#d946ef','#f97316','#64748b'];
-            return (<div key={i} style={{
-              padding:'6px 14px',borderRadius:8,background:'var(--card)',border:'1px solid var(--border)',
-              fontSize:11,display:'flex',alignItems:'center',gap:6,minWidth:120,transition:'all .2s',
-            }}>
-              <div style={{width:6,height:6,borderRadius:'50%',background:colors[i % colors.length],flexShrink:0}} />
-              <span style={{fontWeight:600}}>{c.category}</span>
-              <span style={{color:'var(--muted)',marginLeft:'auto',fontVariantNumeric:'tabular-nums'}}>{c.count.toLocaleString()}</span>
+        <div className="trade-log">
+          {(mkt.recentTrades || []).map((t, i) => {
+            const c = stratColors[t.strategy] || 'var(--muted)';
+            return (<div key={i} className="trade-row">
+              <span className="trade-portfolio" style={{color:c}}>{t.portfolio_name}</span>
+              <span className={'signal-dir ' + t.direction} style={{fontSize:9}}>{t.direction}</span>
+              <span style={{flex:1,overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap',color:'var(--muted)'}}>{t.market_id}</span>
+              <span style={{fontSize:10,color:'var(--muted)'}}>{t.contracts}x @ {t.entry_price_cents}\\u00A2</span>
+              <span className="trade-pnl" style={{color: t.pnl_cents >= 0 ? 'var(--green)' : 'var(--red)'}}>
+                {t.pnl_cents >= 0 ? '+' : ''}{(t.pnl_cents / 100).toFixed(2)}
+              </span>
+              <span className="signal-time">{timeAgo(t.created_at)}</span>
             </div>);
           })}
         </div>
-      </div>
+      </div>}
 
-      {/* Portfolio Leaderboard */}
+      {/* ═══ Portfolio Leaderboard ═══ */}
       {(mkt.portfolios || []).length > 0 && <div className="mkt-card" style={{gridColumn:'span 2'}}>
         <div className="mkt-card-header">
           <span className="mkt-card-title" style={{color:'var(--pink)'}}>Portfolio Leaderboard</span>
-          <span className="mkt-card-badge" style={{background:'rgba(255,105,180,.15)',color:'var(--pink)'}}>{(mkt.portfolios||[]).length} portfolios</span>
+          <span className="mkt-card-badge" style={{background:'rgba(255,105,180,.15)',color:'var(--pink)'}}>{totalTrades} trades</span>
         </div>
-        <div style={{display:'grid',gridTemplateColumns:'repeat(auto-fill,minmax(200px,1fr))',gap:8}}>
+        <div style={{display:'grid',gridTemplateColumns:'repeat(auto-fill,minmax(220px,1fr))',gap:8}}>
           {(mkt.portfolios || []).map((p, i) => {
-            const stratColors = {aggressive:'#ef4444',conservative:'#3b82f6',weather:'#06b6d4',momentum:'#f59e0b',contrarian:'#8b5cf6',ai_enhanced:'#ec4899',mc_validated:'#a855f7',scalper:'#eab308',patient:'#22c55e',random:'#64748b'};
-            const stratEmojis = {aggressive:'🔥',conservative:'🏛️',weather:'⛈️',momentum:'🚀',contrarian:'🔄',ai_enhanced:'🧠',mc_validated:'🎲',scalper:'⚡',patient:'🐢',random:'🎰'};
             const c = stratColors[p.strategy] || 'var(--muted)';
-            const pnl = parseInt(p.balance_cents) || 0;
-            return (<div key={i} style={{padding:'10px 14px',background:'rgba(255,255,255,.02)',border:'1px solid var(--border)',borderRadius:10,transition:'all .2s',borderLeftColor:c,borderLeftWidth:3}}>
-              <div style={{display:'flex',alignItems:'center',gap:8,marginBottom:4}}>
-                <span style={{fontSize:18}}>{stratEmojis[p.strategy]||'📊'}</span>
-                <div>
-                  <div style={{fontSize:12,fontWeight:700,color:c}}>{p.name}</div>
-                  <div style={{fontSize:9,color:'var(--muted)',textTransform:'uppercase'}}>{p.strategy}</div>
+            const emoji = stratEmojis[p.strategy] || '';
+            const pnl = p.pnl_cents || 0;
+            const invested = p.total_invested_cents || 0;
+            const roi = invested > 0 ? ((pnl / invested) * 100).toFixed(1) : '0.0';
+            return (<div key={i} style={{padding:'12px 14px',background:'rgba(255,255,255,.02)',border:'1px solid var(--border)',borderRadius:10,transition:'all .3s',borderLeftColor:c,borderLeftWidth:3,position:'relative',overflow:'hidden'}}>
+              {/* Rank badge */}
+              <div style={{position:'absolute',top:6,right:8,fontSize:8,color:'var(--muted)',fontWeight:800}}>#{i+1}</div>
+              <div style={{display:'flex',alignItems:'center',gap:8,marginBottom:6}}>
+                <span style={{fontSize:20}}>{emoji}</span>
+                <div style={{flex:1}}>
+                  <div style={{fontSize:13,fontWeight:700,color:c}}>{p.name}</div>
+                  <div style={{fontSize:9,color:'var(--muted)',textTransform:'uppercase',letterSpacing:.3}}>{p.strategy}</div>
                 </div>
-                <div style={{marginLeft:'auto',textAlign:'right'}}>
-                  <div style={{fontSize:16,fontWeight:800,color:pnl>=0?'var(--green)':'var(--red)',fontVariantNumeric:'tabular-nums'}}>
-                    {pnl>=0?'+':''}{(pnl/100).toFixed(2)}
+                <div style={{textAlign:'right'}}>
+                  <div style={{fontSize:18,fontWeight:900,color: pnl >= 0 ? 'var(--green)' : 'var(--red)',fontVariantNumeric:'tabular-nums'}}>
+                    {pnl >= 0 ? '+' : ''}{(pnl / 100).toFixed(2)}
                   </div>
+                  <div style={{fontSize:9,color:'var(--muted)'}}>ROI: {roi}%</div>
                 </div>
               </div>
-              <div style={{display:'flex',gap:4,fontSize:9}}>
-                <span style={{padding:'1px 6px',borderRadius:3,background:'rgba(34,197,94,.1)',color:'var(--green)'}}>W:{p.trades_won}</span>
-                <span style={{padding:'1px 6px',borderRadius:3,background:'rgba(239,68,68,.1)',color:'var(--red)'}}>L:{p.trades_lost}</span>
-                <span style={{padding:'1px 6px',borderRadius:3,background:'rgba(255,255,255,.04)',color:'var(--muted)'}}>WR:{p.win_rate}%</span>
+              <div style={{display:'flex',gap:4,fontSize:9,flexWrap:'wrap'}}>
+                <span style={{padding:'2px 7px',borderRadius:4,background:'rgba(34,197,94,.1)',color:'var(--green)'}}>W:{p.trades_won}</span>
+                <span style={{padding:'2px 7px',borderRadius:4,background:'rgba(239,68,68,.1)',color:'var(--red)'}}>L:{p.trades_lost}</span>
+                <span style={{padding:'2px 7px',borderRadius:4,background:'rgba(255,255,255,.04)',color:'var(--muted)'}}>WR:{p.win_rate}%</span>
+                <span style={{padding:'2px 7px',borderRadius:4,background:'rgba(255,255,255,.04)',color:'var(--muted)'}}>{p.total_trades} trades</span>
+                {p.streak !== 0 && <span style={{padding:'2px 7px',borderRadius:4,background: p.streak > 0 ? 'rgba(34,197,94,.1)' : 'rgba(239,68,68,.1)',color: p.streak > 0 ? 'var(--green)' : 'var(--red)'}}>{p.streak > 0 ? '+' : ''}{p.streak} streak</span>}
               </div>
+              {invested > 0 && <div style={{marginTop:6}}>
+                <div style={{height:3,borderRadius:2,background:'rgba(255,255,255,.05)',overflow:'hidden'}}>
+                  <div style={{height:'100%',borderRadius:2,width: Math.min(100, Math.abs(parseFloat(roi))) + '%',background: pnl >= 0 ? 'var(--green)' : 'var(--red)',transition:'width .5s'}} />
+                </div>
+              </div>}
             </div>);
           })}
         </div>

← 4d47305 Ken: add probability shift dampening for realistic signal ed  ·  back to Bertha  ·  chore(alshi-dash): update 1 file (.js) [+209/-20] eb35535 →