← back to Bertha
chore(alshi-dash): update 1 file (.js) [+361/-4]
1d9f769b2fb361c7246cfc5196f76da0e6fbcccc · 2026-02-16 07:17:30 +0000 · DW Commit Agent
Files touched
Diff
commit 1d9f769b2fb361c7246cfc5196f76da0e6fbcccc
Author: DW Commit Agent <commit-agent@dw-agents.com>
Date: Mon Feb 16 07:17:30 2026 +0000
chore(alshi-dash): update 1 file (.js) [+361/-4]
---
kalshi-dash/server.js | 365 +++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 361 insertions(+), 4 deletions(-)
diff --git a/kalshi-dash/server.js b/kalshi-dash/server.js
index 7458969..bf22f5b 100644
--- a/kalshi-dash/server.js
+++ b/kalshi-dash/server.js
@@ -1706,10 +1706,10 @@ const routes = {
try {
const { rows: posts } = await kenQ(`
SELECT source, subreddit as area, keyword, post_title as title, post_url as url,
- score, sentiment_score, raw_text, created_at
+ score, sentiment_score, raw_text, captured_at
FROM ken_sentiment
WHERE source IN ('heating_intel', 'noaa_alert', 'noaa_forecast')
- ORDER BY created_at DESC
+ ORDER BY captured_at DESC
LIMIT 50
`);
const { rows: stats } = await kenQ(`
@@ -1718,7 +1718,7 @@ const routes = {
ROUND(AVG(score)::numeric, 0) as avg_score
FROM ken_sentiment
WHERE source IN ('heating_intel', 'noaa_alert', 'noaa_forecast')
- AND created_at > NOW() - INTERVAL '7 days'
+ AND captured_at > NOW() - INTERVAL '7 days'
GROUP BY source
`);
json(res, { posts, stats, keywords: HEATING_KEYWORDS });
@@ -1856,6 +1856,99 @@ const routes = {
})),
});
},
+
+ // ── Market Data — stock-market style data points ──
+ 'GET /api/inc/market-data': async (req, res) => {
+ const out = {};
+
+ // 1. Hourly signal volume — last 24h in 1-hour buckets
+ try {
+ const { rows } = await kenQ(`SELECT date_trunc('hour', created_at) AS hour,
+ COUNT(*) AS signals, COUNT(*) FILTER (WHERE risk_approved) AS approved,
+ AVG(ABS(expected_edge::numeric)) AS avg_edge,
+ AVG(confidence::numeric) AS avg_confidence
+ FROM ken_strategy_signals
+ WHERE created_at > NOW() - INTERVAL '24 hours'
+ GROUP BY 1 ORDER BY 1`);
+ out.signalTrend = rows.map(r => ({
+ hour: r.hour, signals: parseInt(r.signals), approved: parseInt(r.approved),
+ avgEdge: parseFloat(parseFloat(r.avg_edge || 0).toFixed(4)),
+ avgConf: parseFloat(parseFloat(r.avg_confidence || 0).toFixed(3)),
+ }));
+ } catch { out.signalTrend = []; }
+
+ // 2. Sentiment volume by hour — last 24h
+ try {
+ const { rows } = await kenQ(`SELECT date_trunc('hour', captured_at) AS hour,
+ COUNT(*) AS articles, AVG(sentiment_score) AS avg_sentiment,
+ COUNT(DISTINCT source) AS sources
+ FROM ken_sentiment
+ WHERE captured_at > NOW() - INTERVAL '24 hours'
+ GROUP BY 1 ORDER BY 1`);
+ out.sentimentTrend = rows.map(r => ({
+ hour: r.hour, articles: parseInt(r.articles),
+ avgSentiment: parseFloat(parseFloat(r.avg_sentiment || 0).toFixed(3)),
+ sources: parseInt(r.sources),
+ }));
+ } catch { out.sentimentTrend = []; }
+
+ // 3. Top sentiment sources with counts
+ try {
+ const { rows } = await kenQ(`SELECT source, COUNT(*) AS cnt,
+ AVG(sentiment_score) AS avg_sent
+ FROM ken_sentiment WHERE captured_at > NOW() - INTERVAL '24 hours'
+ GROUP BY source ORDER BY cnt DESC LIMIT 15`);
+ out.sourceMix = rows.map(r => ({
+ source: r.source, count: parseInt(r.cnt),
+ avgSentiment: parseFloat(parseFloat(r.avg_sent || 0).toFixed(3)),
+ }));
+ } catch { out.sourceMix = []; }
+
+ // 4. Top tickers / categories from sentiment
+ try {
+ const { rows } = await kenQ(`SELECT
+ COALESCE(relevance_ticker, 'general') AS category, COUNT(*) AS cnt
+ FROM ken_sentiment WHERE captured_at > NOW() - INTERVAL '24 hours'
+ AND relevance_ticker IS NOT NULL AND relevance_ticker != ''
+ GROUP BY relevance_ticker ORDER BY cnt DESC LIMIT 12`);
+ out.categories = rows.map(r => ({ category: r.category, count: parseInt(r.cnt) }));
+ } catch { out.categories = []; }
+
+ // 5. Portfolio P&L (current state)
+ try {
+ const { rows } = await kenQ(`SELECT p.name, p.strategy, p.balance_cents, p.trades_won, p.trades_lost, p.win_rate,
+ p.total_invested_cents, p.daily_budget_cents, p.budget_remaining, p.trades_today, p.streak
+ FROM ken_portfolios p ORDER BY p.balance_cents DESC`);
+ out.portfolios = rows;
+ } catch { out.portfolios = []; }
+
+ // 6. Active markets count and top movers
+ out.activeMarkets = cachedActiveMarkets.length;
+ out.momentumKeys = Object.keys(cachedMomentumMap).length;
+
+ // 7. Signal approval rate over last 6 hours vs prior 6
+ try {
+ const { rows } = await kenQ(`SELECT
+ COUNT(*) FILTER (WHERE created_at > NOW() - INTERVAL '6 hours') AS recent_total,
+ COUNT(*) FILTER (WHERE created_at > NOW() - INTERVAL '6 hours' AND risk_approved) AS recent_approved,
+ COUNT(*) FILTER (WHERE created_at BETWEEN NOW() - INTERVAL '12 hours' AND NOW() - INTERVAL '6 hours') AS prior_total,
+ COUNT(*) FILTER (WHERE created_at BETWEEN NOW() - INTERVAL '12 hours' AND NOW() - INTERVAL '6 hours' AND risk_approved) AS prior_approved
+ FROM ken_strategy_signals WHERE created_at > NOW() - INTERVAL '12 hours'`);
+ const r = rows[0] || {};
+ const recentRate = r.recent_total > 0 ? (r.recent_approved / r.recent_total * 100) : 0;
+ const priorRate = r.prior_total > 0 ? (r.prior_approved / r.prior_total * 100) : 0;
+ out.approvalTrend = {
+ current: Math.round(recentRate), prior: Math.round(priorRate),
+ change: Math.round(recentRate - priorRate),
+ recentSignals: parseInt(r.recent_total), priorSignals: parseInt(r.prior_total),
+ };
+ } catch { out.approvalTrend = { current: 0, prior: 0, change: 0 }; }
+
+ // 8. Gemini usage
+ out.gemini = { callsToday: geminiCallCount, dailyLimit: GEMINI_DAILY_LIMIT, pctUsed: Math.round(geminiCallCount / GEMINI_DAILY_LIMIT * 100) };
+
+ json(res, out);
+ },
};
// ════════════════════════════════════════
@@ -1958,6 +2051,40 @@ body::after{content:'';position:fixed;top:0;left:0;right:0;bottom:0;background-i
.clock-time{font-size:14px;font-weight:600;font-variant-numeric:tabular-nums;color:var(--text)}
.clock-date{font-size:11px;color:var(--muted)}
+/* Sparkline */
+.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)}
+.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 .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-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-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)}
+
/* Footer */
.footer{text-align:center;padding:30px;color:var(--muted);font-size:11px;letter-spacing:1px}
.footer a{color:var(--pink);text-decoration:none}
@@ -2182,6 +2309,234 @@ function PredictionsTab() {
</div>);
}
+// ── Sparkline SVG Component ──
+function Sparkline({ data, width = 120, height = 30, color = '#8b5cf6', showDots = false }) {
+ if (!data || data.length < 2) return null;
+ const max = Math.max(...data);
+ const min = Math.min(...data);
+ const range = max - min || 1;
+ const points = data.map((v, i) => {
+ const x = (i / (data.length - 1)) * width;
+ const y = height - ((v - min) / range) * (height - 4) - 2;
+ return x + ',' + y;
+ }).join(' ');
+ const areaPoints = points + ' ' + width + ',' + height + ' 0,' + height;
+ 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>
+ );
+}
+
+// ── Horizontal 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;
+ return (
+ <div className="mkt-bar-chart" style={{ height }}>
+ {data.map((d, i) => (
+ <div key={i} className="mkt-bar" style={{ height: Math.max(2, (d.value / mx) * height) + 'px', background: d.color || color, opacity: 0.7 + (d.value / mx) * 0.3 }}>
+ <div className="mkt-bar-label">{d.label}: {d.value}</div>
+ </div>
+ ))}
+ </div>
+ );
+}
+
+// ── Market Data Tab ──
+function MarketDataTab() {
+ const [mkt, setMkt] = useState(null);
+ const [tick, setTick] = useState(0);
+
+ useEffect(() => {
+ const load = () => api('/api/inc/market-data').then(setMkt).catch(e => console.error(e));
+ load();
+ const iv = setInterval(load, 10000);
+ 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>;
+
+ const signalVals = (mkt.signalTrend || []).map(s => s.signals);
+ const edgeVals = (mkt.signalTrend || []).map(s => s.avgEdge * 100);
+ const sentVals = (mkt.sentimentTrend || []).map(s => s.articles);
+ const sentScoreVals = (mkt.sentimentTrend || []).map(s => s.avgSentiment);
+ 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 changeClass = at.change > 0 ? 'up' : at.change < 0 ? 'down' : 'flat';
+
+ // Ticker items — key metrics with change indicators
+ const tickerItems = [
+ { label: 'SIGNALS/24H', 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: '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)' },
+ ];
+ const doubledTicker = tickerItems.concat(tickerItems);
+
+ return (<div>
+ {/* Ticker Strip */}
+ <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>
+ {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>}
+ </div>
+ ))}
+ </div>
+ </div>
+
+ <div className="mkt-grid">
+ {/* Signal Volume Sparkline */}
+ <div className="mkt-card">
+ <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>
+ </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>
+ </div>
+
+ {/* Sentiment Volume */}
+ <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>
+ </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) : '—'}
+ </strong></span>
+ </div>
+ </div>
+
+ {/* Approval Rate Trend */}
+ <div className="mkt-card">
+ <div className="mkt-card-header">
+ <span className="mkt-card-title" style={{color:'var(--green)'}}>Approval Rate</span>
+ <span className={'mkt-change ' + changeClass} style={{fontSize:14}}>{changeArrow} {Math.abs(at.change)}%</span>
+ </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>
+ </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>
+ </div>
+
+ {/* Edge Distribution */}
+ <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-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}%
+ </span>
+ </div>
+ <Sparkline data={edgeVals} width={300} height={50} color="#8b5cf6" showDots={true} />
+ </div>
+
+ {/* Source Mix */}
+ <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>
+ </div>
+ <div className="mkt-source-list">
+ {(mkt.sourceMix || []).slice(0, 10).map((s, i) => {
+ const maxC = (mkt.sourceMix || [])[0]?.count || 1;
+ const colors = ['#3b82f6','#8b5cf6','#ec4899','#ef4444','#f59e0b','#22c55e','#06b6d4','#14b8a6','#6366f1','#d946ef'];
+ 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}}>
+ <div className="mkt-source-bar" style={{width: (s.count / maxC * 100) + '%', background: colors[i % colors.length]}} />
+ </div>
+ <span className="mkt-source-val">{s.count.toLocaleString()}</span>
+ <span style={{fontSize:9,color: s.avgSentiment > 0 ? 'var(--green)' : s.avgSentiment < 0 ? 'var(--red)' : 'var(--muted)', minWidth:35, textAlign:'right'}}>
+ {s.avgSentiment > 0 ? '+' : ''}{s.avgSentiment.toFixed(2)}
+ </span>
+ </div>);
+ })}
+ </div>
+ </div>
+
+ {/* Gemini AI Usage */}
+ <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-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
+ </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>
+ </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>
+ </div>
+
+ {/* Category Distribution */}
+ <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>
+ </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>);
+ })}
+ </div>
+ </div>
+ </div>
+ </div>);
+}
+
function App() {
const [data, setData] = useState(null);
const [expandedAgent, setExpandedAgent] = useState(null);
@@ -2233,6 +2588,7 @@ function App() {
<div style={{display:'flex',justifyContent:'center',gap:8,marginBottom:24,flexWrap:'wrap'}}>
<button style={tabStyle('command')} onClick={()=>setTab('command')}>Command Center</button>
+ <button style={tabStyle('market')} onClick={()=>setTab('market')}>Market Data</button>
<button style={tabStyle('portfolios')} onClick={()=>setTab('portfolios')}>Portfolios (10)</button>
<button style={tabStyle('predictions')} onClick={()=>setTab('predictions')}>Predictions</button>
<button style={tabStyle('signals')} onClick={()=>setTab('signals')}>Live Signals</button>
@@ -2257,6 +2613,7 @@ function App() {
</div>
</>}
+ {tab === 'market' && <MarketDataTab />}
{tab === 'portfolios' && <PortfoliosTab />}
{tab === 'predictions' && <PredictionsTab />}
@@ -4104,7 +4461,7 @@ const RISK_LIMITS = {
maxExposurePerTheme: 15000, // cents ($150)
maxTotalExposure: 50000, // cents ($500)
maxContractsPerMarket: 25,
- minEdgeThreshold: 0.05, // 5% edge minimum to generate signal
+ minEdgeThreshold: 0.03, // 3% edge minimum (lowered from 5% — entity-specific matching yields smaller edges)
maxDailyLoss: 5000, // cents ($50)
cooldownAfterLoss: 30, // minutes
timeDecayPenalty: 0.02, // Reduce edge by 2% per hour of signal age
← 198a6d0 chore(alshi-dash): update 1 file (.js) [+1097/-2]
·
back to Bertha
·
Ken: market data refinements and portfolio query fixes df73f40 →