← back to Bertha
chore(alshi-dash): update 1 file (.js) [+1097/-2]
198a6d07544ce2b745d30b9432e73c8db30d0cfc · 2026-02-16 06:47:30 +0000 · DW Commit Agent
Files touched
Diff
commit 198a6d07544ce2b745d30b9432e73c8db30d0cfc
Author: DW Commit Agent <commit-agent@dw-agents.com>
Date: Mon Feb 16 06:47:30 2026 +0000
chore(alshi-dash): update 1 file (.js) [+1097/-2]
---
kalshi-dash/server.js | 1099 ++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 1097 insertions(+), 2 deletions(-)
diff --git a/kalshi-dash/server.js b/kalshi-dash/server.js
index f21d1ec..7458969 100644
--- a/kalshi-dash/server.js
+++ b/kalshi-dash/server.js
@@ -1626,8 +1626,658 @@ const routes = {
},
});
},
+
+ // ── Portfolios API ──
+ 'GET /api/portfolios': async (req, res) => {
+ try {
+ const { rows: portfolios } = await kenQ(`SELECT * FROM ken_portfolios ORDER BY id`);
+ // Get today's trade counts per portfolio
+ for (const pf of portfolios) {
+ const { rows: today } = await kenQ(`
+ SELECT COUNT(*) as trades_today, COALESCE(SUM(cost_cents),0) as spent_today
+ FROM ken_portfolio_trades WHERE portfolio_id = $1 AND created_at::date = CURRENT_DATE
+ `, [pf.id]);
+ pf.trades_today = parseInt(today[0]?.trades_today) || 0;
+ pf.spent_today = parseInt(today[0]?.spent_today) || 0;
+ pf.budget_remaining = pf.daily_budget_cents - pf.spent_today;
+
+ // Win rate
+ const totalResolved = pf.trades_won + pf.trades_lost + pf.trades_expired;
+ pf.win_rate = totalResolved > 0 ? Math.round(pf.trades_won / totalResolved * 100) : 0;
+ pf.total_resolved = totalResolved;
+ }
+ json(res, portfolios);
+ } catch(e) { json(res, { error: e.message }, 500); }
+ },
+
+ 'GET /api/portfolio-trades': async (req, res) => {
+ try {
+ const url = new URL(req.url, 'http://x');
+ const pfId = url.searchParams.get('portfolio_id');
+ const status = url.searchParams.get('status');
+ let q = `SELECT * FROM ken_portfolio_trades`;
+ const params = [];
+ const conds = [];
+ if (pfId) { conds.push(`portfolio_id = $${params.length+1}`); params.push(pfId); }
+ if (status) { conds.push(`status = $${params.length+1}`); params.push(status); }
+ if (conds.length) q += ' WHERE ' + conds.join(' AND ');
+ q += ' ORDER BY created_at DESC LIMIT 200';
+ const { rows } = await kenQ(q, params);
+ json(res, rows);
+ } catch(e) { json(res, { error: e.message }, 500); }
+ },
+
+ 'GET /api/predictions-history': async (req, res) => {
+ try {
+ const { rows } = await kenQ(`
+ SELECT id, ticker, title, signal_type, side, confidence, entry_price, exit_price,
+ edge_bps, outcome, pnl_cents, reasoning, created_at, resolved_at
+ FROM ken_predictions
+ WHERE outcome != 'pending'
+ ORDER BY resolved_at DESC NULLS LAST
+ LIMIT 100
+ `);
+ // Also get stats
+ const { rows: stats } = await kenQ(`
+ SELECT outcome, COUNT(*) as cnt, ROUND(AVG(pnl_cents),1) as avg_pnl,
+ ROUND(AVG(confidence),1) as avg_conf
+ FROM ken_predictions WHERE outcome != 'pending'
+ GROUP BY outcome
+ `);
+ json(res, { predictions: rows, stats });
+ } catch(e) { json(res, { error: e.message }, 500); }
+ },
+
+ 'GET /api/daily-pnl': async (req, res) => {
+ try {
+ const { rows } = await kenQ(`
+ SELECT dp.*, p.name as portfolio_name
+ FROM ken_daily_pnl dp
+ JOIN ken_portfolios p ON p.id = dp.portfolio_id
+ ORDER BY dp.date DESC, dp.portfolio_id
+ LIMIT 200
+ `);
+ json(res, rows);
+ } catch(e) { json(res, { error: e.message }, 500); }
+ },
+
+ // ── Heating & Weather Intelligence API ──
+ 'GET /api/heating-intel': async (req, res) => {
+ 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
+ FROM ken_sentiment
+ WHERE source IN ('heating_intel', 'noaa_alert', 'noaa_forecast')
+ ORDER BY created_at DESC
+ LIMIT 50
+ `);
+ const { rows: stats } = await kenQ(`
+ SELECT source, COUNT(*) as cnt,
+ ROUND(AVG(sentiment_score)::numeric, 2) as avg_sentiment,
+ 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'
+ GROUP BY source
+ `);
+ json(res, { posts, stats, keywords: HEATING_KEYWORDS });
+ } catch(e) { json(res, { error: e.message }, 500); }
+ },
+
+ // ── Ken Inc Command Center API ──
+ 'GET /api/inc': async (req, res) => {
+ const mem = process.memoryUsage();
+ // Get recent signals
+ let recentSignals = [];
+ try {
+ const { rows } = await kenQ(`SELECT market_id, direction, expected_edge, confidence, reasoning, risk_approved, created_at
+ FROM ken_strategy_signals ORDER BY created_at DESC LIMIT 20`);
+ recentSignals = rows;
+ } catch(e) {}
+
+ // Get pipeline stats
+ let pipelineStats = {};
+ try {
+ const { rows } = await kenQ(`SELECT COUNT(*) as total_signals,
+ COUNT(*) FILTER (WHERE risk_approved = true) as approved,
+ COUNT(*) FILTER (WHERE created_at > NOW() - INTERVAL '1 hour') as last_hour,
+ AVG(ABS(expected_edge::numeric)) as avg_edge,
+ MAX(confidence::numeric) as max_confidence
+ FROM ken_strategy_signals WHERE created_at > NOW() - INTERVAL '24 hours'`);
+ pipelineStats = rows[0] || {};
+ } catch(e) {}
+
+ // Sentiment stats
+ let sentimentStats = {};
+ try {
+ const { rows } = await kenQ(`SELECT COUNT(*) as total_articles,
+ COUNT(DISTINCT source) as unique_sources,
+ AVG(sentiment_score) as avg_sentiment
+ FROM ken_sentiment WHERE captured_at > NOW() - INTERVAL '24 hours'`);
+ sentimentStats = rows[0] || {};
+ } catch(e) {}
+
+ // Market snapshot stats
+ let marketStats = {};
+ try {
+ const { rows } = await kenQ(`SELECT COUNT(DISTINCT ticker) as tracked_markets
+ FROM ken_market_snapshots WHERE captured_at > NOW() - INTERVAL '24 hours'`);
+ marketStats = rows[0] || {};
+ } catch(e) {}
+
+ // Build sub-agent status
+ const subAgents = [
+ {
+ id: 'scout', name: 'Scout', role: 'Intelligence Gatherer', emoji: '🔍',
+ color: '#3B82F6', status: 'active',
+ desc: 'Sweeps 77 subreddits, 71 RSS feeds, HN, YouTube, Bluesky, Mastodon, Discord',
+ stats: { articles24h: parseInt(sentimentStats.total_articles) || 0, sources: parseInt(sentimentStats.unique_sources) || 0 },
+ skills: ['Reddit Scraping', 'RSS Feeds', 'Social Media', 'GDELT TV', 'YouTube', 'Lemmy', 'Lobsters'],
+ },
+ {
+ id: 'oracle', name: 'Oracle', role: 'Monte Carlo Engine', emoji: '🎲',
+ color: '#8B5CF6', status: 'active',
+ desc: 'Runs 15,000 iteration Monte Carlo simulations on every signal for probability validation',
+ stats: { iterations: 15000, cores: os.cpus().length, momentumTracked: Object.keys(cachedMomentumMap).length },
+ skills: ['Monte Carlo Simulation', 'Bayesian Inference', 'Ensemble Probability', 'Confidence Intervals'],
+ },
+ {
+ id: 'gemini', name: 'Nova', role: 'AI Analyst', emoji: '🧠',
+ color: '#EC4899', status: geminiCallCount < GEMINI_DAILY_LIMIT ? 'active' : 'rate_limited',
+ desc: 'Gemini 2.0 Flash analyzes high-edge signals with natural language reasoning',
+ stats: { callsToday: geminiCallCount, dailyBudget: GEMINI_DAILY_LIMIT, model: GEMINI_MODEL },
+ skills: ['Signal Analysis', 'News Interpretation', 'Risk Assessment', 'Market Reasoning'],
+ },
+ {
+ id: 'pulse', name: 'Pulse', role: 'Sentiment Analyst', emoji: '💓',
+ color: '#EF4444', status: 'active',
+ desc: '75 event-specific rules across 7 categories with certainty modifiers and source diversity scoring',
+ stats: { rules: 75, categories: 7, avgSentiment: parseFloat(sentimentStats.avg_sentiment || 0).toFixed(3) },
+ skills: ['Event Sentiment', 'Certainty Detection', 'Multi-source Agreement', 'Category Weighting'],
+ },
+ {
+ id: 'hawk', name: 'Hawk', role: 'Market Watcher', emoji: '🦅',
+ color: '#F59E0B', status: 'active',
+ desc: 'Tracks momentum, volume spikes, spreads, and price action across all active markets',
+ stats: { activeMarkets: cachedActiveMarkets.length, trackedMarkets: parseInt(marketStats.tracked_markets) || 0 },
+ skills: ['Momentum Tracking', 'Volume Analysis', 'Spread Detection', 'Price Action'],
+ },
+ {
+ id: 'arbiter', name: 'Arbiter', role: 'Cross-Market Arbitrage', emoji: '⚖️',
+ color: '#14B8A6', status: 'active',
+ desc: 'Compares Kalshi prices vs Polymarket, PredictIt, Manifold, Metaculus for arbitrage',
+ stats: { platforms: 5 },
+ skills: ['Polymarket', 'PredictIt', 'Manifold', 'Metaculus', 'Fuzzy Matching'],
+ },
+ {
+ id: 'shield', name: 'Shield', role: 'Risk Manager', emoji: '🛡️',
+ color: '#22C55E', status: 'active',
+ desc: 'Enforces exposure limits, edge thresholds, time decay, and liquidity requirements',
+ stats: {
+ signalsApproved: parseInt(pipelineStats.approved) || 0,
+ totalSignals: parseInt(pipelineStats.total_signals) || 0,
+ maxConf: parseFloat(pipelineStats.max_confidence || 0).toFixed(2),
+ },
+ skills: ['Position Sizing', 'Exposure Limits', 'Edge Threshold', 'Time Decay', 'Liquidity Check'],
+ },
+ {
+ id: 'storm', name: 'Storm', role: 'Weather Predictor', emoji: '⛈️',
+ color: '#06B6D4', status: 'active',
+ desc: 'NWS forecast data + Bayesian model for 11 US cities, climatological priors by season',
+ stats: { cities: 11 },
+ skills: ['NWS Forecasts', 'Bayesian Weather', 'Climatological Priors', 'Temperature/Precip/Wind'],
+ },
+ ];
+
+ json(res, {
+ ceo: { name: 'Ken', title: 'CEO, Ken Inc.', tagline: 'Trading Intelligence Corporation', color: '#FF69B4' },
+ agents: subAgents,
+ pipeline: {
+ signalsLast24h: parseInt(pipelineStats.total_signals) || 0,
+ approvedLast24h: parseInt(pipelineStats.approved) || 0,
+ lastHour: parseInt(pipelineStats.last_hour) || 0,
+ avgEdge: parseFloat(pipelineStats.avg_edge || 0).toFixed(3),
+ },
+ system: {
+ memory_mb: Math.round(mem.rss / 1024 / 1024),
+ uptime_hours: Math.round(process.uptime() / 3600 * 10) / 10,
+ cpuCores: os.cpus().length,
+ pid: process.pid,
+ },
+ recentSignals: recentSignals.map(s => ({
+ market: s.market_id,
+ direction: s.direction,
+ edge: parseFloat(s.expected_edge || 0),
+ confidence: parseFloat(s.confidence || 0),
+ approved: s.risk_approved,
+ reasoning: s.reasoning?.substring(0, 200),
+ time: s.created_at,
+ })),
+ });
+ },
};
+// ════════════════════════════════════════
+// KEN INC COMMAND CENTER PAGE
+// ════════════════════════════════════════
+const KEN_INC_HTML = `<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Ken Inc. | Trading Intelligence Command Center</title>
+<script src="https://unpkg.com/react@18/umd/react.production.min.js" crossorigin></script>
+<script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js" crossorigin></script>
+<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
+<style>
+*{margin:0;padding:0;box-sizing:border-box}
+:root{--bg:#050510;--card:rgba(15,15,35,0.85);--border:rgba(255,255,255,0.06);--text:#e2e8f0;--muted:#64748b;--pink:#ff69b4;--blue:#3b82f6;--purple:#8b5cf6;--green:#22c55e;--red:#ef4444;--yellow:#f59e0b;--cyan:#06b6d4;--teal:#14b8a6}
+body{font-family:'SF Pro Display',-apple-system,BlinkMacSystemFont,sans-serif;background:var(--bg);color:var(--text);min-height:100vh;overflow-x:hidden}
+
+/* Animated gradient background */
+body::before{content:'';position:fixed;top:0;left:0;right:0;bottom:0;background:radial-gradient(ellipse at 20% 50%,rgba(139,92,246,0.08) 0%,transparent 50%),radial-gradient(ellipse at 80% 20%,rgba(255,105,180,0.06) 0%,transparent 50%),radial-gradient(ellipse at 50% 80%,rgba(59,130,246,0.06) 0%,transparent 50%);z-index:-1;animation:bgPulse 8s ease-in-out infinite alternate}
+@keyframes bgPulse{0%{opacity:0.6}100%{opacity:1}}
+
+/* Grid lines overlay */
+body::after{content:'';position:fixed;top:0;left:0;right:0;bottom:0;background-image:linear-gradient(rgba(255,255,255,0.015) 1px,transparent 1px),linear-gradient(90deg,rgba(255,255,255,0.015) 1px,transparent 1px);background-size:60px 60px;z-index:-1;animation:gridScroll 20s linear infinite}
+@keyframes gridScroll{to{background-position:60px 60px}}
+
+.container{max-width:1400px;margin:0 auto;padding:20px}
+
+/* Header */
+.header{text-align:center;padding:30px 20px;position:relative}
+.header-glow{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:400px;height:400px;background:radial-gradient(circle,rgba(255,105,180,0.15) 0%,transparent 70%);border-radius:50%;filter:blur(60px);animation:glowPulse 4s ease-in-out infinite alternate;pointer-events:none}
+@keyframes glowPulse{0%{opacity:0.5;transform:translate(-50%,-50%) scale(0.9)}100%{opacity:1;transform:translate(-50%,-50%) scale(1.1)}}
+.ceo-avatar{width:80px;height:80px;border-radius:50%;background:linear-gradient(135deg,#ff69b4,#8b5cf6);display:flex;align-items:center;justify-content:center;font-size:36px;margin:0 auto 12px;box-shadow:0 0 30px rgba(255,105,180,0.4),0 0 60px rgba(139,92,246,0.2);animation:avatarFloat 3s ease-in-out infinite;position:relative;z-index:2}
+@keyframes avatarFloat{0%,100%{transform:translateY(0)}50%{transform:translateY(-8px)}}
+.ceo-name{font-size:32px;font-weight:800;background:linear-gradient(135deg,#ff69b4,#c084fc,#818cf8);-webkit-background-clip:text;-webkit-text-fill-color:transparent;position:relative;z-index:2}
+.ceo-title{color:var(--muted);font-size:14px;letter-spacing:2px;text-transform:uppercase;margin-top:4px;position:relative;z-index:2}
+.ceo-tagline{color:rgba(255,105,180,0.6);font-size:11px;letter-spacing:4px;text-transform:uppercase;margin-top:8px;position:relative;z-index:2}
+
+/* Stats bar */
+.stats-bar{display:flex;justify-content:center;gap:30px;margin:24px 0;flex-wrap:wrap}
+.stat-item{text-align:center;padding:12px 24px;background:var(--card);border:1px solid var(--border);border-radius:12px;backdrop-filter:blur(10px);min-width:120px}
+.stat-val{font-size:24px;font-weight:700;font-variant-numeric:tabular-nums}
+.stat-label{font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:1px;margin-top:2px}
+
+/* Agent Network */
+.network{position:relative;margin:30px auto;max-width:1200px}
+.agents-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:16px}
+@media(max-width:1024px){.agents-grid{grid-template-columns:repeat(2,1fr)}}
+@media(max-width:600px){.agents-grid{grid-template-columns:1fr}}
+
+/* Agent Card */
+.agent-card{background:var(--card);border:1px solid var(--border);border-radius:16px;padding:20px;backdrop-filter:blur(10px);transition:all 0.3s cubic-bezier(0.4,0,0.2,1);position:relative;overflow:hidden;cursor:pointer}
+.agent-card::before{content:'';position:absolute;top:0;left:0;right:0;height:3px;border-radius:16px 16px 0 0;opacity:0;transition:opacity 0.3s}
+.agent-card:hover{transform:translateY(-4px);border-color:rgba(255,255,255,0.12);box-shadow:0 20px 40px rgba(0,0,0,0.3)}
+.agent-card:hover::before{opacity:1}
+.agent-card.expanded{grid-column:span 2}
+.agent-header{display:flex;align-items:center;gap:12px}
+.agent-emoji{font-size:28px;width:48px;height:48px;display:flex;align-items:center;justify-content:center;border-radius:12px;flex-shrink:0}
+.agent-name{font-size:16px;font-weight:700}
+.agent-role{font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:0.5px}
+.status-indicator{width:8px;height:8px;border-radius:50%;position:absolute;top:16px;right:16px;animation:statusPulse 2s ease-in-out infinite}
+@keyframes statusPulse{0%,100%{opacity:1;box-shadow:0 0 4px currentColor}50%{opacity:0.6;box-shadow:0 0 12px currentColor}}
+.agent-desc{font-size:12px;color:var(--muted);line-height:1.5;margin-top:10px}
+.agent-stats{display:flex;flex-wrap:wrap;gap:6px;margin-top:12px}
+.agent-stat{padding:3px 10px;background:rgba(255,255,255,0.04);border:1px solid var(--border);border-radius:6px;font-size:11px;font-variant-numeric:tabular-nums}
+.agent-stat strong{color:var(--text);margin-right:3px}
+.skills-wrap{margin-top:12px;display:flex;flex-wrap:wrap;gap:4px}
+.skill-tag{padding:2px 8px;border-radius:4px;font-size:10px;font-weight:600;letter-spacing:0.3px}
+
+/* Signal Feed */
+.signal-feed{margin-top:30px}
+.signal-feed h2{font-size:16px;font-weight:600;margin-bottom:12px;display:flex;align-items:center;gap:8px}
+.signal-feed h2 .live-dot{width:8px;height:8px;border-radius:50%;background:var(--red);animation:livePulse 1s ease-in-out infinite}
+@keyframes livePulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:0.5;transform:scale(0.8)}}
+.signal-list{display:flex;flex-direction:column;gap:6px}
+.signal-item{display:flex;align-items:center;gap:12px;padding:10px 16px;background:var(--card);border:1px solid var(--border);border-radius:10px;font-size:12px;backdrop-filter:blur(10px);transition:border-color 0.2s}
+.signal-item:hover{border-color:rgba(255,255,255,0.12)}
+.signal-dir{padding:3px 8px;border-radius:4px;font-weight:700;font-size:11px;min-width:60px;text-align:center}
+.signal-dir.buy-yes{background:rgba(34,197,94,0.15);color:var(--green)}
+.signal-dir.buy-no{background:rgba(239,68,68,0.15);color:var(--red)}
+.signal-market{flex:1;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
+.signal-edge{font-weight:700;font-variant-numeric:tabular-nums;min-width:50px;text-align:right}
+.signal-conf{color:var(--muted);font-variant-numeric:tabular-nums;min-width:40px;text-align:right}
+.signal-time{color:var(--muted);font-size:11px;min-width:65px;text-align:right}
+.signal-approved{width:18px;height:18px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:10px;flex-shrink:0}
+
+/* Connection Lines Animation */
+.connections-svg{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:0}
+.data-flow{stroke-dasharray:8 4;animation:flowDash 1s linear infinite}
+@keyframes flowDash{to{stroke-dashoffset:-12}}
+
+/* Particle effect */
+.particles{position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:0}
+.particle{position:absolute;width:2px;height:2px;border-radius:50%;opacity:0;animation:particleFloat linear infinite}
+@keyframes particleFloat{0%{opacity:0;transform:translateY(0)}10%{opacity:0.6}90%{opacity:0.6}100%{opacity:0;transform:translateY(-100vh)}}
+
+/* Clock */
+.clock{position:fixed;top:16px;right:20px;text-align:right;z-index:10}
+.clock-time{font-size:14px;font-weight:600;font-variant-numeric:tabular-nums;color:var(--text)}
+.clock-date{font-size:11px;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}
+</style>
+</head>
+<body>
+<div id="root"></div>
+<script type="text/babel">
+const { useState, useEffect, useRef } = React;
+
+const AUTH = btoa('admin:DWSecure2024!');
+const api = (url) => fetch(url, { headers: { 'Authorization': 'Basic ' + AUTH } }).then(r => r.json());
+
+function Particles() {
+ const particles = Array.from({ length: 30 }, (_, i) => ({
+ id: i,
+ left: Math.random() * 100 + '%',
+ delay: Math.random() * 10 + 's',
+ duration: 8 + Math.random() * 12 + 's',
+ color: ['#ff69b4','#8b5cf6','#3b82f6','#22c55e','#f59e0b','#06b6d4'][Math.floor(Math.random()*6)],
+ size: 1 + Math.random() * 2 + 'px',
+ }));
+ return (
+ <div className="particles">
+ {particles.map(p => (
+ <div key={p.id} className="particle" style={{
+ left: p.left, bottom: 0, animationDelay: p.delay, animationDuration: p.duration,
+ background: p.color, width: p.size, height: p.size, boxShadow: '0 0 6px ' + p.color,
+ }} />
+ ))}
+ </div>
+ );
+}
+
+function Clock() {
+ const [now, setNow] = useState(new Date());
+ useEffect(() => { const iv = setInterval(() => setNow(new Date()), 1000); return () => clearInterval(iv); }, []);
+ const pt = now.toLocaleString('en-US', { timeZone: 'America/Los_Angeles', hour: 'numeric', minute: '2-digit', second: '2-digit', hour12: true });
+ const dateStr = now.toLocaleDateString('en-US', { timeZone: 'America/Los_Angeles', weekday: 'short', month: 'short', day: 'numeric' });
+ return <div className="clock"><div className="clock-time">{pt} PT</div><div className="clock-date">{dateStr}</div></div>;
+}
+
+function AgentCard({ agent, expanded, onToggle }) {
+ const bgColor = agent.color + '15';
+ return (
+ <div className={'agent-card' + (expanded ? ' expanded' : '')} onClick={onToggle}
+ style={{ '--agent-color': agent.color }}>
+ <div className="status-indicator" style={{ background: agent.status === 'active' ? 'var(--green)' : 'var(--yellow)', color: agent.status === 'active' ? 'var(--green)' : 'var(--yellow)' }} />
+ <div style={{ position: 'absolute', top: 0, left: 0, right: 0, height: 3, borderRadius: '16px 16px 0 0', background: agent.color, opacity: 0.7 }} />
+ <div className="agent-header">
+ <div className="agent-emoji" style={{ background: bgColor }}>{agent.emoji}</div>
+ <div>
+ <div className="agent-name" style={{ color: agent.color }}>{agent.name}</div>
+ <div className="agent-role">{agent.role}</div>
+ </div>
+ </div>
+ <div className="agent-desc">{agent.desc}</div>
+ <div className="agent-stats">
+ {Object.entries(agent.stats || {}).map(([k, v]) => (
+ <div key={k} className="agent-stat"><strong>{typeof v === 'number' ? v.toLocaleString() : v}</strong> {k.replace(/([A-Z])/g, ' $1').toLowerCase()}</div>
+ ))}
+ </div>
+ {expanded && (
+ <div className="skills-wrap">
+ {(agent.skills || []).map(s => (
+ <span key={s} className="skill-tag" style={{ background: agent.color + '20', color: agent.color, border: '1px solid ' + agent.color + '30' }}>{s}</span>
+ ))}
+ </div>
+ )}
+ </div>
+ );
+}
+
+function SignalFeed({ signals }) {
+ if (!signals?.length) return <div style={{ textAlign: 'center', color: 'var(--muted)', padding: 30, fontSize: 13 }}>Waiting for signals...</div>;
+
+ function timeAgo(ts) {
+ const ms = Date.now() - new Date(ts).getTime();
+ if (ms < 60000) return Math.floor(ms/1000) + 's ago';
+ if (ms < 3600000) return Math.floor(ms/60000) + 'm ago';
+ return Math.floor(ms/3600000) + 'h ago';
+ }
+
+ return (
+ <div className="signal-list">
+ {signals.slice(0, 12).map((s, i) => (
+ <div key={i} className="signal-item">
+ <div className={'signal-dir ' + (s.direction === 'BUY_YES' ? 'buy-yes' : 'buy-no')}>
+ {s.direction === 'BUY_YES' ? 'BUY YES' : 'BUY NO'}
+ </div>
+ <div className="signal-market" title={s.market}>{s.market}</div>
+ <div className="signal-edge" style={{ color: Math.abs(s.edge) > 0.1 ? 'var(--green)' : 'var(--muted)' }}>
+ {(s.edge * 100).toFixed(1)}%
+ </div>
+ <div className="signal-conf">{(s.confidence * 100).toFixed(0)}%</div>
+ <div className="signal-approved" style={{ background: s.approved ? 'rgba(34,197,94,0.15)' : 'rgba(239,68,68,0.1)', color: s.approved ? 'var(--green)' : 'var(--red)' }}>
+ {s.approved ? '\\u2713' : '\\u2717'}
+ </div>
+ <div className="signal-time">{timeAgo(s.time)}</div>
+ </div>
+ ))}
+ </div>
+ );
+}
+
+// ── PORTFOLIOS TAB ──
+function PortfoliosTab() {
+ const [portfolios, setPortfolios] = useState([]);
+ const [trades, setTrades] = useState([]);
+ const [selectedPf, setSelectedPf] = useState(null);
+ useEffect(() => {
+ api('/api/portfolios').then(setPortfolios).catch(()=>{});
+ api('/api/portfolio-trades').then(setTrades).catch(()=>{});
+ const iv = setInterval(() => {
+ api('/api/portfolios').then(setPortfolios).catch(()=>{});
+ if (selectedPf) api('/api/portfolio-trades?portfolio_id='+selectedPf).then(setTrades).catch(()=>{});
+ }, 20000);
+ return () => clearInterval(iv);
+ }, [selectedPf]);
+
+ const personalities = { aggressive:'Swings big on high-edge', conservative:'Quality over quantity', weather:'Weather markets only', momentum:'Follows the trend', contrarian:'Bets against the crowd', ai_enhanced:'Gemini AI picks only', mc_validated:'Monte Carlo confirmed', scalper:'Tiny edges, high volume', patient:'Waits for perfect setup', random:'Control group (luck test)' };
+ const emojis = { aggressive:'🔥', conservative:'🏛️', weather:'⛈️', momentum:'🚀', contrarian:'🔄', ai_enhanced:'🧠', mc_validated:'🎲', scalper:'⚡', patient:'🐢', random:'🎰' };
+ const colors = { aggressive:'#ef4444', conservative:'#3b82f6', weather:'#06b6d4', momentum:'#f59e0b', contrarian:'#8b5cf6', ai_enhanced:'#ec4899', mc_validated:'#a855f7', scalper:'#eab308', patient:'#22c55e', random:'#64748b' };
+
+ return (<div>
+ <div style={{display:'grid',gridTemplateColumns:'repeat(auto-fill,minmax(280px,1fr))',gap:12,marginBottom:20}}>
+ {portfolios.map(pf => {
+ const c = colors[pf.strategy]||'var(--muted)';
+ return (<div key={pf.id} onClick={()=>{setSelectedPf(pf.id);api('/api/portfolio-trades?portfolio_id='+pf.id).then(setTrades);}}
+ style={{background:'var(--card)',border:selectedPf===pf.id?'1px solid '+c:'1px solid var(--border)',borderRadius:14,padding:16,cursor:'pointer',transition:'all .2s',position:'relative',overflow:'hidden'}}>
+ <div style={{position:'absolute',top:0,left:0,right:0,height:3,background:c,opacity:.7}}/>
+ <div style={{display:'flex',alignItems:'center',gap:10,marginBottom:8}}>
+ <span style={{fontSize:24}}>{emojis[pf.strategy]||'📊'}</span>
+ <div>
+ <div style={{fontSize:15,fontWeight:700,color:c}}>{pf.name}</div>
+ <div style={{fontSize:10,color:'var(--muted)',textTransform:'uppercase',letterSpacing:.5}}>{pf.strategy}</div>
+ </div>
+ <div style={{marginLeft:'auto',textAlign:'right'}}>
+ <div style={{fontSize:18,fontWeight:700,color:pf.balance_cents>=0?'var(--green)':'var(--red)',fontVariantNumeric:'tabular-nums'}}>
+ {pf.balance_cents>=0?'+':''}\${(pf.balance_cents/100).toFixed(2)}
+ </div>
+ <div style={{fontSize:10,color:'var(--muted)'}}>P&L</div>
+ </div>
+ </div>
+ <div style={{fontSize:11,color:'var(--muted)',marginBottom:8,lineHeight:1.4}}>{personalities[pf.strategy]}</div>
+ <div style={{display:'flex',gap:6,flexWrap:'wrap'}}>
+ <span style={{padding:'2px 8px',borderRadius:4,fontSize:10,background:'rgba(34,197,94,.12)',color:'var(--green)'}}>W:{pf.trades_won}</span>
+ <span style={{padding:'2px 8px',borderRadius:4,fontSize:10,background:'rgba(239,68,68,.12)',color:'var(--red)'}}>L:{pf.trades_lost}</span>
+ <span style={{padding:'2px 8px',borderRadius:4,fontSize:10,background:'rgba(255,255,255,.05)',color:'var(--muted)'}}>WR:{pf.win_rate}%</span>
+ <span style={{padding:'2px 8px',borderRadius:4,fontSize:10,background:'rgba(255,255,255,.05)',color:'var(--muted)'}}>Today:{pf.trades_today}</span>
+ <span style={{padding:'2px 8px',borderRadius:4,fontSize:10,background:pf.budget_remaining>0?'rgba(34,197,94,.08)':'rgba(239,68,68,.08)',color:pf.budget_remaining>0?'var(--green)':'var(--red)'}}>{'$'}{(pf.budget_remaining/100).toFixed(2)} left</span>
+ {pf.streak!==0&&<span style={{padding:'2px 8px',borderRadius:4,fontSize:10,background:pf.streak>0?'rgba(34,197,94,.15)':'rgba(239,68,68,.15)',color:pf.streak>0?'var(--green)':'var(--red)'}}>{pf.streak>0?'🔥':'❄️'}{Math.abs(pf.streak)} streak</span>}
+ </div>
+ </div>);
+ })}
+ </div>
+ {selectedPf && trades.length > 0 && (<div>
+ <h3 style={{fontSize:14,fontWeight:600,marginBottom:10,color:'var(--muted)'}}>Recent Trades — {portfolios.find(p=>p.id===selectedPf)?.name}</h3>
+ <div style={{display:'flex',flexDirection:'column',gap:4}}>
+ {trades.slice(0,20).map((t,i) => (<div key={i} style={{display:'flex',alignItems:'center',gap:10,padding:'8px 14px',background:'var(--card)',border:'1px solid var(--border)',borderRadius:8,fontSize:12}}>
+ <span style={{padding:'2px 8px',borderRadius:4,fontWeight:700,fontSize:11,background:t.direction==='BUY_YES'?'rgba(34,197,94,.15)':'rgba(239,68,68,.15)',color:t.direction==='BUY_YES'?'var(--green)':'var(--red)',minWidth:55,textAlign:'center'}}>{t.direction==='BUY_YES'?'YES':'NO'}</span>
+ <span style={{flex:1,overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{t.market_title||t.market_id}</span>
+ <span style={{fontVariantNumeric:'tabular-nums',color:'var(--muted)'}}>{t.entry_price_cents}¢ x{t.contracts}</span>
+ <span style={{fontWeight:700,fontVariantNumeric:'tabular-nums',color:t.status==='won'?'var(--green)':t.status==='lost'?'var(--red)':'var(--muted)',minWidth:50,textAlign:'right'}}>
+ {t.status==='open'?'OPEN':t.pnl_cents>0?'+'+t.pnl_cents+'¢':t.pnl_cents+'¢'}
+ </span>
+ <span style={{fontSize:10,color:'var(--muted)',minWidth:60,textAlign:'right'}}>{new Date(t.created_at).toLocaleTimeString('en-US',{hour:'numeric',minute:'2-digit',hour12:true})}</span>
+ </div>))}
+ </div>
+ </div>)}
+ {(!selectedPf || trades.length === 0) && <div style={{textAlign:'center',padding:30,color:'var(--muted)',fontSize:13}}>Click a portfolio to see its trades. Portfolios auto-trade every 2 minutes from approved signals.</div>}
+ </div>);
+}
+
+// ── PREDICTIONS TAB ──
+function PredictionsTab() {
+ const [data, setData] = useState(null);
+ useEffect(() => {
+ api('/api/predictions-history').then(setData).catch(()=>{});
+ const iv = setInterval(() => api('/api/predictions-history').then(setData).catch(()=>{}), 30000);
+ return () => clearInterval(iv);
+ }, []);
+ if (!data) return <div style={{textAlign:'center',padding:40,color:'var(--muted)'}}>Loading predictions...</div>;
+
+ const statsMap = {};
+ (data.stats||[]).forEach(s => { statsMap[s.outcome] = s; });
+
+ return (<div>
+ <div style={{display:'flex',gap:12,marginBottom:20,flexWrap:'wrap'}}>
+ {['win','loss','expired'].map(o => {
+ const s = statsMap[o]||{cnt:0,avg_pnl:0,avg_conf:0};
+ const c = o==='win'?'var(--green)':o==='loss'?'var(--red)':'var(--muted)';
+ return (<div key={o} style={{background:'var(--card)',border:'1px solid var(--border)',borderRadius:12,padding:16,flex:1,minWidth:140,textAlign:'center'}}>
+ <div style={{fontSize:28,fontWeight:700,color:c}}>{s.cnt}</div>
+ <div style={{fontSize:10,color:'var(--muted)',textTransform:'uppercase',letterSpacing:1,marginTop:2}}>{o==='win'?'Wins':o==='loss'?'Losses':'Expired'}</div>
+ <div style={{fontSize:11,color:'var(--muted)',marginTop:4}}>Avg P&L: <strong style={{color:c}}>{parseFloat(s.avg_pnl||0)>0?'+':''}{s.avg_pnl||0}¢</strong></div>
+ <div style={{fontSize:11,color:'var(--muted)'}}>Avg Conf: {s.avg_conf||0}%</div>
+ </div>);
+ })}
+ <div style={{background:'var(--card)',border:'1px solid var(--border)',borderRadius:12,padding:16,flex:1,minWidth:140,textAlign:'center'}}>
+ <div style={{fontSize:28,fontWeight:700,color:'var(--blue)'}}>{statsMap.win?Math.round(parseInt(statsMap.win.cnt)/(parseInt(statsMap.win.cnt)+parseInt(statsMap.loss?.cnt||0))*100):0}%</div>
+ <div style={{fontSize:10,color:'var(--muted)',textTransform:'uppercase',letterSpacing:1,marginTop:2}}>Win Rate</div>
+ <div style={{fontSize:11,color:'var(--muted)',marginTop:4}}>W/L only (excl. expired)</div>
+ </div>
+ </div>
+
+ <h3 style={{fontSize:14,fontWeight:600,marginBottom:10,display:'flex',alignItems:'center',gap:8}}>Resolved Predictions <span style={{fontSize:11,color:'var(--muted)',fontWeight:400}}>— what actually happened</span></h3>
+ <div style={{display:'flex',flexDirection:'column',gap:4}}>
+ {(data.predictions||[]).slice(0,30).map((p,i) => {
+ const oc = p.outcome==='win'?'var(--green)':p.outcome==='loss'?'var(--red)':'var(--muted)';
+ return (<div key={i} style={{display:'flex',alignItems:'center',gap:10,padding:'8px 14px',background:'var(--card)',border:'1px solid var(--border)',borderRadius:8,fontSize:12}}>
+ <span style={{padding:'2px 8px',borderRadius:4,fontWeight:700,fontSize:10,background:p.outcome==='win'?'rgba(34,197,94,.15)':p.outcome==='loss'?'rgba(239,68,68,.15)':'rgba(255,255,255,.05)',color:oc,minWidth:48,textAlign:'center',textTransform:'uppercase'}}>{p.outcome}</span>
+ <span style={{padding:'2px 8px',borderRadius:4,fontSize:10,fontWeight:600,background:p.side==='YES'||p.side==='yes'?'rgba(34,197,94,.1)':'rgba(239,68,68,.1)',color:p.side==='YES'||p.side==='yes'?'var(--green)':'var(--red)'}}>{p.side}</span>
+ <span style={{flex:1,overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}} title={p.title}>{p.title||p.ticker}</span>
+ <span style={{fontVariantNumeric:'tabular-nums',color:'var(--muted)',fontSize:11}}>Conf:{p.confidence}%</span>
+ <span style={{fontVariantNumeric:'tabular-nums',color:'var(--muted)',fontSize:11}}>{p.entry_price}¢→{p.exit_price||'?'}¢</span>
+ <span style={{fontWeight:700,fontVariantNumeric:'tabular-nums',color:oc,minWidth:45,textAlign:'right'}}>{p.pnl_cents>0?'+':''}{p.pnl_cents||0}¢</span>
+ <span style={{fontSize:10,color:'var(--muted)',minWidth:70,textAlign:'right'}}>{p.resolved_at?new Date(p.resolved_at).toLocaleDateString('en-US',{month:'short',day:'numeric'}):''}</span>
+ </div>);
+ })}
+ </div>
+ </div>);
+}
+
+function App() {
+ const [data, setData] = useState(null);
+ const [expandedAgent, setExpandedAgent] = useState(null);
+ const [tab, setTab] = useState('command');
+ const [loading, setLoading] = useState(true);
+
+ useEffect(() => {
+ const load = async () => {
+ try {
+ const d = await api('/api/inc');
+ setData(d);
+ setLoading(false);
+ } catch(e) { console.error(e); setLoading(false); }
+ };
+ load();
+ const iv = setInterval(load, 15000);
+ return () => clearInterval(iv);
+ }, []);
+
+ if (loading) return (
+ <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100vh', flexDirection: 'column', gap: 16 }}>
+ <div style={{ width: 60, height: 60, borderRadius: '50%', background: 'linear-gradient(135deg,#ff69b4,#8b5cf6)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 28, animation: 'avatarFloat 2s ease-in-out infinite' }}>K</div>
+ <div style={{ color: 'var(--muted)', fontSize: 14, letterSpacing: 2 }}>INITIALIZING KEN INC...</div>
+ </div>
+ );
+
+ const d = data;
+ const approvalRate = d.pipeline.signalsLast24h > 0
+ ? Math.round(d.pipeline.approvedLast24h / d.pipeline.signalsLast24h * 100) : 0;
+
+ const tabStyle = (t) => ({
+ padding: '10px 24px', fontSize: 13, fontWeight: 600, border: '1px solid ' + (tab===t?'rgba(255,255,255,.2)':'var(--border)'),
+ borderRadius: 10, background: tab===t?'rgba(255,255,255,.08)':'transparent', color: tab===t?'var(--text)':'var(--muted)',
+ cursor: 'pointer', transition: 'all .2s', letterSpacing: .3,
+ });
+
+ return (
+ <div className="container">
+ <Particles />
+ <Clock />
+
+ <div className="header">
+ <div className="header-glow" />
+ <div className="ceo-avatar">K</div>
+ <div className="ceo-name">Ken</div>
+ <div className="ceo-title">Chief Executive Officer</div>
+ <div className="ceo-tagline">Trading Intelligence Corporation</div>
+ </div>
+
+ <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('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>
+ </div>
+
+ {tab === 'command' && <>
+ <div className="stats-bar">
+ <div className="stat-item"><div className="stat-val" style={{color:'var(--pink)'}}>{d.agents.length}</div><div className="stat-label">Sub-Agents</div></div>
+ <div className="stat-item"><div className="stat-val" style={{color:'var(--blue)'}}>{d.pipeline.signalsLast24h}</div><div className="stat-label">Signals / 24h</div></div>
+ <div className="stat-item"><div className="stat-val" style={{color:'var(--green)'}}>{approvalRate}%</div><div className="stat-label">Approval Rate</div></div>
+ <div className="stat-item"><div className="stat-val" style={{color:'var(--purple)'}}>{d.pipeline.lastHour}</div><div className="stat-label">Last Hour</div></div>
+ <div className="stat-item"><div className="stat-val" style={{color:'var(--cyan)'}}>{(parseFloat(d.pipeline.avgEdge)*100).toFixed(1)}%</div><div className="stat-label">Avg Edge</div></div>
+ <div className="stat-item"><div className="stat-val" style={{color:'var(--yellow)'}}>{d.system.memory_mb}MB</div><div className="stat-label">Memory</div></div>
+ <div className="stat-item"><div className="stat-val">{d.system.cpuCores}</div><div className="stat-label">CPU Cores</div></div>
+ </div>
+ <div className="network">
+ <div className="agents-grid">
+ {d.agents.map(a => (
+ <AgentCard key={a.id} agent={a} expanded={expandedAgent===a.id} onToggle={()=>setExpandedAgent(expandedAgent===a.id?null:a.id)} />
+ ))}
+ </div>
+ </div>
+ </>}
+
+ {tab === 'portfolios' && <PortfoliosTab />}
+ {tab === 'predictions' && <PredictionsTab />}
+
+ {tab === 'signals' && <div className="signal-feed">
+ <h2><span className="live-dot" /> Live Signal Feed</h2>
+ <SignalFeed signals={d.recentSignals} />
+ </div>}
+
+ <div className="footer">
+ Ken Inc. Trading Intelligence • {d.system.cpuCores} cores • {d.system.uptime_hours}h uptime • 10 paper portfolios •
+ <a href="/"> Back to Dashboard</a>
+ </div>
+ </div>
+ );
+}
+
+ReactDOM.createRoot(document.getElementById('root')).render(<App />);
+</script>
+</body>
+</html>`;
+
// ════════════════════════════════════════
// HTTP SERVER
// ════════════════════════════════════════
@@ -1680,6 +2330,13 @@ const server = http.createServer(async (req, res) => {
return json(res, { error: `Unknown API endpoint: ${req.method} ${urlPath}` }, 404);
}
+ // ── Ken Inc. Command Center ──
+ if (urlPath === '/inc' || urlPath === '/inc/') {
+ res.writeHead(200, { 'Content-Type': 'text/html', 'Cache-Control': 'no-cache' });
+ res.end(KEN_INC_HTML);
+ return;
+ }
+
// ── Serve Static Files / SPA Routing ──
let filePath = path.join(DIST, urlPath === '/' ? 'index.html' : urlPath);
@@ -2660,6 +3317,10 @@ async function redditIntelligenceLoop() {
'Kalshi', 'Polymarket', 'PredictionMarket', 'sportsbook',
// ── Weather (hurricane/temp/storm markets) ──
'TropicalWeather', 'weather', 'tornado', 'hurricane', 'climate',
+ 'NaturalGas', 'energy', 'propane', 'HomeImprovement',
+ // ── Heating Bills & Utility Costs ──
+ 'personalfinance', 'Frugal', 'povertyfinance', 'homeowners',
+ 'HVAC', 'utilities', 'electricvehicles',
// ── Politics & Elections ──
'politics', 'Conservative', 'Liberal', 'NeutralPolitics', 'PoliticalDiscussion',
'fivethirtyeight', 'elections', 'supremecourt', 'moderatepolitics', 'law',
@@ -3659,6 +4320,15 @@ async function signalPipeline(activeMarkets) {
if (risk.approved && confidence > 0.7) {
console.log(`[Ken] SIGNAL: ${direction} ${ticker} — edge ${(mcEdge * 100).toFixed(1)}%, conf ${(confidence * 100).toFixed(0)}%, MC:${mcResult.edgeConsistent ? 'consistent' : 'mixed'}${signal.aiEnhanced ? ', AI-enhanced' : ''}, size ${risk.sizeRecommendation}`);
}
+
+ // Auto-trade across all 10 paper portfolios
+ if (risk.approved) {
+ autoPortfolioTrade({
+ ...signal, marketId: ticker, market_title: market.title,
+ num_articles: numArticles, signal_type: signals[0]?.category,
+ reasoning, expected_edge: mcEdge,
+ }).catch(e => console.error('[Ken/Portfolio] Error:', e.message));
+ }
}
}
@@ -4633,6 +5303,416 @@ async function autonomousScan() {
}
}
+// ══════════════════════════════════════════════
+// AUTO-PORTFOLIO TRADING ENGINE — 10 personalities, $20/day each
+// ══════════════════════════════════════════════
+
+// Portfolio strategy filters — each personality decides differently
+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 || ''),
+ 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
+};
+
+let tradeCounter = 0; // For wildcard portfolio
+
+async function autoPortfolioTrade(signal) {
+ try {
+ const { rows: portfolios } = await kenQ('SELECT * FROM ken_portfolios');
+ const today = new Date().toISOString().split('T')[0];
+
+ for (const pf of portfolios) {
+ const strategy = PORTFOLIO_STRATEGIES[pf.strategy];
+ if (!strategy) continue;
+
+ // Check daily budget: how much did this portfolio spend today?
+ const { rows: todaySpend } = await kenQ(`
+ SELECT COALESCE(SUM(cost_cents), 0) as spent
+ FROM ken_portfolio_trades
+ WHERE portfolio_id = $1 AND created_at::date = CURRENT_DATE AND status = 'open'
+ `, [pf.id]);
+ const spentToday = parseInt(todaySpend[0]?.spent) || 0;
+
+ if (spentToday >= pf.daily_budget_cents) continue; // Budget exhausted
+
+ // Check if strategy accepts this signal
+ const sigData = {
+ edge: signal.expectedEdge || signal.expected_edge || 0,
+ confidence: signal.confidence || 0,
+ direction: signal.direction,
+ reasoning: signal.reasoning || '',
+ signal_type: signal.signal_type || '',
+ ai_enhanced: signal.aiEnhanced || signal.ai_enhanced || false,
+ mc_consistent: signal.mcSimulation?.consistent || signal.mc_consistent || false,
+ volume24h: signal.volume24h || signal.volume_24h || 0,
+ num_articles: signal.num_articles || 0,
+ };
+
+ if (!strategy(sigData)) continue;
+
+ // Skip dupes: don't trade same market twice in same portfolio today
+ const { rows: dupes } = await kenQ(`
+ SELECT id FROM ken_portfolio_trades
+ WHERE portfolio_id = $1 AND market_id = $2 AND created_at::date = CURRENT_DATE
+ `, [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)
+ 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 entryPrice = sigData.direction === 'BUY_YES'
+ ? Math.round((0.5 - absEdge / 2) * 100) // rough market mid
+ : Math.round((0.5 + absEdge / 2) * 100);
+ const clampedPrice = Math.max(5, Math.min(95, entryPrice));
+ const cost = clampedPrice * contracts;
+
+ if (spentToday + cost > pf.daily_budget_cents) continue; // Would exceed budget
+
+ await kenQ(`
+ INSERT INTO ken_portfolio_trades (portfolio_id, signal_id, market_id, market_title, direction, entry_price_cents, contracts, cost_cents, reasoning, confidence, ai_enhanced, mc_consistent)
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
+ `, [pf.id, signal.id || null, signal.marketId || signal.market_id, signal.market_title || signal.marketTitle || '',
+ sigData.direction, clampedPrice, contracts, cost, (sigData.reasoning || '').substring(0, 500),
+ conf, sigData.ai_enhanced, sigData.mc_consistent]);
+
+ // Update portfolio stats
+ await kenQ(`UPDATE ken_portfolios SET total_invested_cents = total_invested_cents + $1, last_trade_at = NOW() WHERE id = $2`, [cost, pf.id]);
+
+ console.log(`[Ken/Portfolio] ${pf.name} (${pf.strategy}): ${sigData.direction} ${(signal.marketId || signal.market_id || '').substring(0, 30)} @ ${clampedPrice}¢ x${contracts} = $${(cost/100).toFixed(2)}`);
+ }
+ } catch (e) {
+ console.error('[Ken/Portfolio] Auto-trade error:', e.message);
+ }
+}
+
+// Resolve portfolio trades — check if markets settled
+async function resolvePortfolioTrades() {
+ try {
+ const { rows: openTrades } = await kenQ(`
+ SELECT pt.*, p.name as portfolio_name
+ FROM ken_portfolio_trades pt
+ JOIN ken_portfolios p ON p.id = pt.portfolio_id
+ WHERE pt.status = 'open' AND pt.created_at < NOW() - INTERVAL '4 hours'
+ LIMIT 100
+ `);
+
+ for (const trade of openTrades) {
+ // Get latest price from snapshots
+ const { rows: snaps } = await kenQ(`
+ SELECT last_price FROM ken_market_snapshots
+ WHERE ticker = $1 ORDER BY captured_at DESC LIMIT 1
+ `, [trade.market_id]);
+
+ if (snaps.length === 0) continue;
+ const currentPrice = parseInt(snaps[0].last_price);
+
+ let pnl = 0;
+ let status = 'expired';
+ if (trade.direction === 'BUY_YES') {
+ pnl = (currentPrice - trade.entry_price_cents) * trade.contracts;
+ status = pnl > 0 ? 'won' : pnl < 0 ? 'lost' : 'expired';
+ } else {
+ pnl = (trade.entry_price_cents - currentPrice) * trade.contracts;
+ status = pnl > 0 ? 'won' : pnl < 0 ? 'lost' : 'expired';
+ }
+
+ await kenQ(`
+ UPDATE ken_portfolio_trades SET status = $1, exit_price_cents = $2, pnl_cents = $3, resolved_at = NOW()
+ WHERE id = $4
+ `, [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;
+ 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]);
+
+ // Update daily P&L
+ const tradeDate = new Date(trade.created_at).toISOString().split('T')[0];
+ await kenQ(`
+ INSERT INTO ken_daily_pnl (portfolio_id, date, closed_trades, wins, losses, daily_pnl_cents, cumulative_pnl_cents)
+ VALUES ($1, $2, 1, $3, $4, $5, $5)
+ ON CONFLICT (portfolio_id, date) DO UPDATE SET
+ closed_trades = ken_daily_pnl.closed_trades + 1,
+ wins = ken_daily_pnl.wins + $3,
+ losses = ken_daily_pnl.losses + $4,
+ daily_pnl_cents = ken_daily_pnl.daily_pnl_cents + $5
+ `, [trade.portfolio_id, tradeDate, status === 'won' ? 1 : 0, status === 'lost' ? 1 : 0, pnl]);
+
+ // Update learning table
+ const category = extractSignalCategory(trade.reasoning || '');
+ await kenQ(`
+ INSERT INTO ken_learning (category, signal_type, win_rate, avg_pnl_cents, total_trades, last_updated)
+ VALUES ($1, $2, $3, $4, 1, NOW())
+ ON CONFLICT DO NOTHING
+ `, [category, trade.direction, status === 'won' ? 1 : 0, pnl]);
+ }
+
+ if (openTrades.length > 0) console.log(`[Ken/Portfolio] Resolved ${openTrades.length} trades across portfolios`);
+ } catch (e) {
+ console.error('[Ken/Portfolio] Resolution error:', e.message);
+ }
+}
+
+function extractSignalCategory(reasoning) {
+ const r = reasoning.toLowerCase();
+ if (/heating|gas bill|utility bill|electric bill|energy cost|propane|furnace|hvac/.test(r)) return 'heating_energy';
+ if (/weather|temp|rain|snow|wind|hurricane|polar vortex|cold snap|freeze|frost/.test(r)) return 'weather';
+ if (/crypto|bitcoin|btc|eth/.test(r)) return 'crypto';
+ if (/politic|elect|congress|senate|trump|biden/.test(r)) return 'politics';
+ if (/econom|rate|inflation|fed|gdp|job/.test(r)) return 'economics';
+ if (/momentum|volume|spike|spread/.test(r)) return 'momentum';
+ if (/sport|game|nfl|nba|mlb/.test(r)) return 'sports';
+ if (/lng|natural gas|oil|commodity|corn|wheat|soy|cattle|crop/.test(r)) return 'commodities';
+ return 'general';
+}
+
+// ══════════════════════════════════════════════
+// LNG & AGRICULTURAL COMMODITY INTELLIGENCE
+// ══════════════════════════════════════════════
+const COMMODITY_FEEDS = [
+ // ── LNG & Natural Gas (Government) ──
+ { url: 'https://www.eia.gov/rss/naturalgas_rss.xml', source: 'EIA Natural Gas', category: 'lng' },
+ { url: 'https://www.eia.gov/rss/petroleum_rss.xml', source: 'EIA Petroleum', category: 'oil' },
+ { url: 'https://www.eia.gov/rss/todayinenergy_rss.xml', source: 'EIA Today in Energy', category: 'energy' },
+ { url: 'https://www.eia.gov/rss/electricity_rss.xml', source: 'EIA Electricity', category: 'electricity' },
+ // ── Agriculture (Government) ──
+ { url: 'https://www.usda.gov/rss/home.xml', source: 'USDA', category: 'agriculture' },
+ { url: 'https://www.nass.usda.gov/rss/index.php', source: 'USDA NASS', category: 'agriculture' },
+ // ── NOAA Weather (Government) ──
+ { url: 'https://alerts.weather.gov/cap/us.php?x=0', source: 'NWS National Alerts', category: 'weather' },
+ { url: 'https://www.weather.gov/rss_page.php?site_name=nws', source: 'NWS News', category: 'weather' },
+ { url: 'https://www.cpc.ncep.noaa.gov/products/stratosphere/strat-trop/rss.xml', source: 'NOAA CPC', category: 'weather' },
+ // ── FERC & Energy Regulation (Government) ──
+ { url: 'https://www.ferc.gov/rss/news-releases.xml', source: 'FERC News', category: 'energy_regulation' },
+ // ── Weather Risk ──
+ { url: 'https://rfrx.rfrz.io/weatherrisk', source: 'Weather Risk', category: 'weather_commodity' },
+];
+
+// Government Open APIs for Energy & Weather Data
+const GOV_API_ENDPOINTS = {
+ // EIA Open Data API (no key needed for RSS, key for JSON)
+ eia_ng_spot: 'https://api.eia.gov/v2/natural-gas/pri/fut/data/?frequency=daily&data[0]=value&sort[0][column]=period&sort[0][direction]=desc&length=5&api_key=',
+ eia_elec_price: 'https://api.eia.gov/v2/electricity/retail-sales/data/?frequency=monthly&data[0]=price&sort[0][column]=period&sort[0][direction]=desc&length=10&api_key=',
+ // NOAA Weather API (free, no key)
+ noaa_alerts: 'https://api.weather.gov/alerts/active?status=actual&message_type=alert',
+ noaa_forecast_nyc: 'https://api.weather.gov/gridpoints/OKX/33,37/forecast',
+ noaa_forecast_chi: 'https://api.weather.gov/gridpoints/LOT/75,72/forecast',
+ noaa_forecast_dal: 'https://api.weather.gov/gridpoints/FWD/87,108/forecast',
+ noaa_forecast_den: 'https://api.weather.gov/gridpoints/BOU/62,60/forecast',
+ noaa_forecast_msp: 'https://api.weather.gov/gridpoints/MPX/107,71/forecast',
+ // FRED Economic Data (key needed but some RSS free)
+ fred_natgas: 'https://fred.stlouisfed.org/graph/fredgraph.csv?id=DHHNGSP',
+};
+
+// Fetch NOAA weather alerts for extreme cold/heat events
+async function fetchNOAAWeatherIntel() {
+ let found = 0;
+ try {
+ const controller = new AbortController();
+ const timeout = setTimeout(() => controller.abort(), 10000);
+ const res = await fetch(GOV_API_ENDPOINTS.noaa_alerts, {
+ signal: controller.signal,
+ headers: { 'User-Agent': 'KenInc/1.0 Weather Intelligence', Accept: 'application/geo+json' },
+ });
+ clearTimeout(timeout);
+ if (!res.ok) return found;
+ const data = await res.json();
+
+ const heatingKeywords = /cold|freeze|frost|ice|blizzard|winter storm|wind chill|extreme cold|heating|arctic|polar vortex|below zero|record low|dangerous cold/i;
+ const energyKeywords = /power outage|grid|electricity|utility|energy emergency|rolling blackout|brownout|load shed/i;
+
+ for (const feature of (data.features || []).slice(0, 50)) {
+ const props = feature.properties || {};
+ const headline = props.headline || props.event || '';
+ const desc = props.description || '';
+ const combined = headline + ' ' + desc;
+
+ if (heatingKeywords.test(combined) || energyKeywords.test(combined)) {
+ const areas = (props.areaDesc || '').substring(0, 200);
+ const severity = props.severity || 'Unknown';
+ const certainty = props.certainty || 'Unknown';
+
+ await kenQ(`
+ INSERT INTO ken_sentiment (source, subreddit, keyword, post_title, post_url, score, num_comments, sentiment_score, raw_text)
+ VALUES ('noaa_alert', $1, 'weather_energy', $2, $3, $4, 0, $5, $6)
+ ON CONFLICT DO NOTHING
+ `, [
+ areas,
+ headline.substring(0, 500),
+ props.id || `noaa-${Date.now()}`,
+ severity === 'Extreme' ? 100 : severity === 'Severe' ? 80 : severity === 'Moderate' ? 50 : 20,
+ severity === 'Extreme' ? -0.9 : severity === 'Severe' ? -0.7 : -0.4,
+ `[${severity}/${certainty}] ${areas}: ${desc.substring(0, 400)}`
+ ]);
+ found++;
+ }
+ }
+ } catch (e) {
+ // NOAA API can be slow, don't log every failure
+ }
+
+ // Fetch regional forecasts for major metro areas (heating demand proxy)
+ const metros = [
+ { name: 'NYC', url: GOV_API_ENDPOINTS.noaa_forecast_nyc },
+ { name: 'Chicago', url: GOV_API_ENDPOINTS.noaa_forecast_chi },
+ { name: 'Dallas', url: GOV_API_ENDPOINTS.noaa_forecast_dal },
+ { name: 'Denver', url: GOV_API_ENDPOINTS.noaa_forecast_den },
+ { name: 'Minneapolis', url: GOV_API_ENDPOINTS.noaa_forecast_msp },
+ ];
+
+ for (const metro of metros) {
+ try {
+ const controller = new AbortController();
+ const timeout = setTimeout(() => controller.abort(), 8000);
+ const res = await fetch(metro.url, {
+ signal: controller.signal,
+ headers: { 'User-Agent': 'KenInc/1.0', Accept: 'application/geo+json' },
+ });
+ clearTimeout(timeout);
+ if (!res.ok) continue;
+ const data = await res.json();
+
+ for (const period of (data.properties?.periods || []).slice(0, 4)) {
+ const tempF = period.temperature;
+ const wind = period.windSpeed || '';
+ const forecast = period.detailedForecast || period.shortForecast || '';
+
+ // Flag extreme cold (below 20F) or heat waves (above 100F) — heating demand signals
+ if (tempF <= 20 || tempF >= 100) {
+ await kenQ(`
+ INSERT INTO ken_sentiment (source, subreddit, keyword, post_title, post_url, score, num_comments, sentiment_score, raw_text)
+ VALUES ('noaa_forecast', $1, 'extreme_temp', $2, $3, $4, 0, $5, $6)
+ ON CONFLICT DO NOTHING
+ `, [
+ metro.name,
+ `${metro.name}: ${tempF}°F ${period.name} — ${period.shortForecast}`,
+ `noaa-forecast-${metro.name}-${period.number}-${new Date().toISOString().split('T')[0]}`,
+ Math.abs(tempF <= 20 ? (20 - tempF) * 5 : (tempF - 100) * 5),
+ tempF <= 20 ? -0.8 : -0.6,
+ `${metro.name} ${period.name}: ${tempF}°F, Wind ${wind}. ${forecast.substring(0, 300)}`
+ ]);
+ found++;
+ }
+ }
+ await new Promise(r => setTimeout(r, 500));
+ } catch (e) { /* NOAA timeout */ }
+ }
+
+ if (found > 0) console.log(`[Ken/NOAA] ${found} weather/energy alerts from government APIs`);
+ return found;
+}
+
+// Additional weather-impacted commodity keywords for market scanning
+const COMMODITY_KEYWORDS = [
+ 'natural gas', 'lng', 'henry hub', 'gas price', 'heating degree',
+ 'corn', 'wheat', 'soybeans', 'soybean', 'cotton', 'cattle', 'hogs',
+ 'crop', 'harvest', 'drought', 'frost', 'freeze', 'growing season',
+ 'el nino', 'la nina', 'commodity', 'futures', 'agriculture',
+ 'ethanol', 'fertilizer', 'grain', 'livestock', 'usda', 'crop report',
+];
+
+// ── HEATING BILL & ENERGY COST INTELLIGENCE ──
+const HEATING_KEYWORDS = [
+ 'heating bill', 'gas bill', 'electric bill', 'utility bill', 'energy bill',
+ 'heating cost', 'energy cost', 'propane', 'furnace', 'thermostat',
+ 'polar vortex', 'cold snap', 'wind chill', 'below zero', 'record cold',
+ 'heating oil', 'natural gas price', 'electricity price', 'rate hike',
+ 'winter storm', 'power outage', 'grid strain', 'rolling blackout',
+ 'hvac', 'insulation', 'heat pump', 'space heater', 'energy assistance',
+];
+
+const HEATING_SUBS = [
+ 'personalfinance', 'Frugal', 'povertyfinance', 'homeowners',
+ 'HVAC', 'energy', 'NaturalGas', 'HomeImprovement', 'utilities',
+ 'weather', 'climate', 'TropicalWeather', 'preppers',
+];
+
+async function fetchHeatingBillIntel() {
+ let found = 0;
+ const heatingRegex = new RegExp(HEATING_KEYWORDS.join('|'), 'i');
+
+ for (let i = 0; i < HEATING_SUBS.length; i += 3) {
+ const batch = HEATING_SUBS.slice(i, i + 3);
+ const results = await Promise.allSettled(batch.map(sub => scrapeSubredditHot(sub, 15)));
+ for (let j = 0; j < results.length; j++) {
+ if (results[j].status !== 'fulfilled') continue;
+ const sub = batch[j];
+ for (const post of results[j].value) {
+ const text = (post.title + ' ' + (post.selftext || '')).toLowerCase();
+ if (heatingRegex.test(text) && post.score > 10) {
+ const sentiment = scoreSentiment(post.title);
+ await kenQ(`
+ INSERT INTO ken_sentiment (source, subreddit, keyword, post_title, post_url, score, num_comments, sentiment_score, raw_text)
+ VALUES ('heating_intel', $1, 'heating', $2, $3, $4, $5, $6, $7)
+ ON CONFLICT DO NOTHING
+ `, [sub, post.title, post.url, post.score, post.num_comments, sentiment, text.substring(0, 500)]);
+ found++;
+ }
+ }
+ }
+ await new Promise(r => setTimeout(r, 2000));
+ }
+
+ if (found > 0) console.log(`[Ken/Heating] Found ${found} heating bill/energy cost discussions`);
+ return found;
+}
+
+async function fetchCommodityIntel() {
+ let total = 0;
+ for (const feed of COMMODITY_FEEDS) {
+ try {
+ const controller = new AbortController();
+ const timeout = setTimeout(() => controller.abort(), 8000);
+ const res = await fetch(feed.url, {
+ signal: controller.signal,
+ headers: { 'User-Agent': 'KenInc/1.0 Commodity Intelligence' },
+ });
+ clearTimeout(timeout);
+ if (!res.ok) continue;
+ const text = await res.text();
+
+ // Extract items from RSS
+ const items = text.match(/<item>[\s\S]*?<\/item>/gi) || [];
+ for (const item of items.slice(0, 15)) {
+ const title = (item.match(/<title><!\[CDATA\[(.*?)\]\]>|<title>(.*?)<\/title>/)?.[1] || item.match(/<title>(.*?)<\/title>/)?.[1] || '').trim();
+ const link = (item.match(/<link>(.*?)<\/link>/)?.[1] || '').trim();
+ if (!title) continue;
+
+ // Score commodity relevance
+ const score = scoreEventSentiment(title);
+
+ try {
+ await kenQ(`
+ INSERT INTO ken_sentiment (source, keyword, post_title, post_url, sentiment_score, captured_at)
+ VALUES ($1, $2, $3, $4, $5, NOW())
+ ON CONFLICT DO NOTHING
+ `, [feed.source, feed.category, title.substring(0, 500), link.substring(0, 500), score.sentiment || 0]);
+ total++;
+ } catch(e) {} // skip dupes
+ }
+ } catch(e) {}
+ }
+ if (total > 0) console.log(`[Ken/Commodities] ${total} articles from ${COMMODITY_FEEDS.length} commodity feeds`);
+ return total;
+}
+
// ══════════════════════════════════════════════
// PERFORMANCE TRACKER — resolve old predictions
// ══════════════════════════════════════════════
@@ -4702,7 +5782,8 @@ server.listen(PORT, '0.0.0.0', () => {
console.log(`[Ken] CPU cores: ${os.cpus().length} | MC iterations: 15,000 per signal`);
console.log(`[Ken] Gemini AI: ${GEMINI_MODEL} (daily budget: ${GEMINI_DAILY_LIMIT} calls)`);
console.log(`[Ken] Slack alerts: every 4 hours (high-conf bypass)`);
- console.log(`[Ken] Data sources: Kalshi | Reddit (70+) | ${NEWS_FEEDS.length} news feeds | HN | GDELT TV | YouTube | Manifold | r/Kalshi | r/Polymarket | Lemmy | Lobsters | Tildes | Bluesky | Mastodon | Metaculus | Polymarket | PredictIt | FRED Economic | NWS Alerts | GovTrack | Twitter/Nitter | Discord`);
+ console.log(`[Ken] Data sources: Kalshi | Reddit (80+) | ${NEWS_FEEDS.length} news feeds | HN | GDELT TV | YouTube | Manifold | r/Kalshi | r/Polymarket | Lemmy | Lobsters | Tildes | Bluesky | Mastodon | Metaculus | Polymarket | PredictIt | FRED Economic | NWS Alerts | NOAA Forecasts | EIA Energy | FERC | GovTrack | USDA | Twitter/Nitter | Discord`);
+ console.log(`[Ken] Heating intel: ${HEATING_SUBS.length} subreddits | ${HEATING_KEYWORDS.length} keywords | NOAA 5 metro forecasts | EIA electricity/gas prices`);
console.log(`[Ken] ═══════════════════════════════════════`);
// Mutex: prevent concurrent sweeps from overlapping and doubling memory
@@ -4745,11 +5826,25 @@ server.listen(PORT, '0.0.0.0', () => {
}
}, SIGNAL_PIPELINE_MS);
- // Resolve old predictions every hour
+ // Resolve old predictions + portfolio trades every hour
setInterval(() => {
resolveOldPredictions().catch(e => console.error('[Ken] Resolution error:', e.message));
+ resolvePortfolioTrades().catch(e => console.error('[Ken] Portfolio resolution error:', e.message));
}, 60 * 60 * 1000);
+ // Commodity + heating bill + NOAA intelligence every 20 minutes
+ setInterval(() => {
+ fetchCommodityIntel().catch(e => console.error('[Ken] Commodity error:', e.message));
+ fetchHeatingBillIntel().catch(e => console.error('[Ken] Heating intel error:', e.message));
+ fetchNOAAWeatherIntel().catch(e => console.error('[Ken] NOAA intel error:', e.message));
+ }, 20 * 60 * 1000);
+ // Initial commodity + heating + NOAA fetch
+ setTimeout(() => {
+ fetchCommodityIntel().catch(() => {});
+ fetchHeatingBillIntel().catch(() => {});
+ fetchNOAAWeatherIntel().catch(() => {});
+ }, 15000);
+
// Cleanup old data every 24 hours
setInterval(() => {
cleanupOldData().catch(e => console.error('[Ken] Cleanup error:', e.message));
← aa3548c chore(alshi-dash): update 1 file (.js) [+249/-22]
·
back to Bertha
·
chore(alshi-dash): update 1 file (.js) [+361/-4] 1d9f769 →