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