← back to Bertha
kalshi-dash/src/pages/Pulse.jsx
749 lines
import React, { useState, useEffect, useMemo } from 'react';
import { api } from '../api';
// ── Pulse Category Grid Configuration ──
const PULSE_CATEGORIES = [
{
name: 'Economy',
icon: '\u{1F4B0}',
subcategories: ['GDP', 'Inflation', 'Jobs', 'Fed Rate', 'Recession', 'Trade', 'Debt', 'Markets'],
keywords: ['gdp', 'inflation', 'cpi', 'unemployment', 'jobs', 'payroll', 'fed', 'interest rate', 'recession', 'stock', 'sp500', 'nasdaq', 'dow', 'treasury', 'bond', 'debt', 'trade', 'tariff', 'deficit'],
cssColor: 'var(--green)',
dimColor: 'var(--green-dim)',
rgbColor: [0, 195, 137],
},
{
name: 'Politics',
icon: '\u{1F3DB}',
subcategories: ['White House', 'Congress', 'Elections', 'SCOTUS', 'Cabinet', 'Polls', 'Policy', 'Foreign'],
keywords: ['trump', 'president', 'impeach', 'congress', 'senate', 'house', 'governor', 'election', 'democrat', 'republican', 'vote', 'biden', 'vance', 'cabinet', 'scotus', 'supreme', 'pardon', 'approval', 'poll', 'secretary', 'defense', 'attorney'],
cssColor: 'var(--blue)',
dimColor: 'var(--blue-dim)',
rgbColor: [59, 130, 246],
},
{
name: 'Weather',
icon: '\u{26C8}',
subcategories: ['Temperature', 'Storms', 'Snow', 'Rain', 'Hurricanes', 'Heat', 'Wind', 'Climate'],
keywords: ['temperature', 'rain', 'snow', 'hurricane', 'weather', 'tornado', 'flood', 'heat', 'cold', 'storm', 'wind', 'precipitation', 'climate'],
cssColor: 'var(--cyan)',
dimColor: 'var(--cyan-dim)',
rgbColor: [0, 240, 255],
},
{
name: 'Technology',
icon: '\u{1F4BB}',
subcategories: ['AI', 'SpaceX', 'Tesla', 'Big Tech', 'Startups', 'Chips', 'Cyber', 'Social'],
keywords: ['ai', 'spacex', 'tesla', 'apple', 'google', 'meta', 'openai', 'chatgpt', 'tech', 'rocket', 'launch'],
cssColor: 'var(--cyan)',
dimColor: 'var(--cyan-dim)',
rgbColor: [0, 240, 255],
},
{
name: 'Crypto',
icon: '\u{1FA99}',
subcategories: ['Bitcoin', 'Ethereum', 'DeFi', 'NFTs', 'Regulation', 'Mining', 'Stablecoins', 'Altcoins'],
keywords: ['bitcoin', 'btc', 'ethereum', 'eth', 'crypto', 'token', 'blockchain', 'defi'],
cssColor: 'var(--orange)',
dimColor: 'var(--orange-dim)',
rgbColor: [247, 147, 26],
},
{
name: 'Sports',
icon: '\u{1F3C8}',
subcategories: ['NFL', 'NBA', 'MLB', 'Soccer', 'Tennis', 'UFC', 'Olympics', 'Playoffs'],
keywords: ['nfl', 'nba', 'mlb', 'nhl', 'soccer', 'tennis', 'super bowl', 'championship', 'game', 'match', 'playoff', 'world series'],
cssColor: 'var(--purple)',
dimColor: 'var(--purple-dim)',
rgbColor: [168, 85, 247],
},
{
name: 'Energy',
icon: '\u{26A1}',
subcategories: ['Oil', 'Gas', 'Solar', 'Nuclear', 'OPEC', 'EVs', 'Grid', 'Coal'],
keywords: ['oil', 'gas', 'energy', 'solar', 'opec', 'crude', 'coal', 'renewable'],
cssColor: 'var(--orange)',
dimColor: 'var(--orange-dim)',
rgbColor: [247, 147, 26],
},
{
name: 'Culture',
icon: '\u{1F3AC}',
subcategories: ['Movies', 'Music', 'Awards', 'TV', 'Social Media', 'Trends', 'Celebrity', 'Gaming'],
keywords: ['oscar', 'movie', 'tv', 'celebrity', 'grammy', 'award', 'music', 'entertainment'],
cssColor: 'var(--orange)',
dimColor: 'var(--orange-dim)',
rgbColor: [247, 147, 26],
},
];
// Keep original baseColor arrays for data logic compatibility
PULSE_CATEGORIES.forEach((cat, i) => {
cat.baseColor = cat.rgbColor;
});
function getHeatColor(intensity, baseColor) {
const [r, g, b] = baseColor;
const alpha = Math.max(0.05, Math.min(0.9, intensity));
return `rgba(${r},${g},${b},${alpha})`;
}
function getHeatBorder(intensity, baseColor) {
const [r, g, b] = baseColor;
const alpha = Math.max(0.1, Math.min(0.6, intensity * 0.8));
return `rgba(${r},${g},${b},${alpha})`;
}
function getTextColor(intensity, baseColor) {
const [r, g, b] = baseColor;
if (intensity > 0.5) return `rgb(${Math.min(255, r + 60)},${Math.min(255, g + 60)},${Math.min(255, b + 60)})`;
if (intensity > 0.2) return `rgb(${r},${g},${b})`;
return 'var(--text-muted)';
}
// ── Pulse Cell Component ──
function PulseCell({ subcategory, intensity, signalCount, sentiment, baseColor, onClick, isSelected }) {
const bg = getHeatColor(intensity, baseColor);
const border = getHeatBorder(intensity, baseColor);
const textColor = getTextColor(intensity, baseColor);
const scanLines = intensity > 0.3;
return (
<div
onClick={onClick}
className={`relative overflow-hidden rounded-lg cursor-pointer group ${isSelected ? 'z-10' : ''}`}
style={{
backgroundColor: bg,
border: isSelected
? '2px solid var(--cyan)'
: `1px solid ${border}`,
minHeight: 70,
transform: isSelected ? 'scale(1.05)' : undefined,
transition: 'all 200ms ease',
boxShadow: isSelected ? '0 0 16px rgba(0,240,255,0.2)' : undefined,
}}
>
{/* Thermal scan lines effect */}
{scanLines && (
<div
className="absolute inset-0 opacity-10 pointer-events-none"
style={{
backgroundImage: `repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(255,255,255,0.03) 2px, rgba(255,255,255,0.03) 4px)`,
}}
/>
)}
{/* Pulse animation for high intensity */}
{intensity > 0.6 && (
<div
className="absolute inset-0 animate-pulse opacity-30 pointer-events-none"
style={{
background: `radial-gradient(circle at center, rgba(${baseColor.join(',')},0.4), transparent 70%)`,
animationDuration: `${3 - intensity * 2}s`,
}}
/>
)}
{/* Hover glow */}
<div
className="absolute inset-0 opacity-0 group-hover:opacity-100 pointer-events-none"
style={{
background: `radial-gradient(circle at center, rgba(${baseColor.join(',')},0.08), transparent)`,
transition: 'opacity 150ms ease',
}}
/>
<div className="relative p-2.5 h-full flex flex-col justify-between z-10">
<div>
<p className="text-[10px] font-bold uppercase tracking-wider" style={{ color: textColor }}>
{subcategory}
</p>
</div>
<div className="flex items-end justify-between mt-1">
<div>
{signalCount > 0 ? (
<p className="text-lg font-bold mono" style={{ color: textColor }}>
{signalCount}
</p>
) : (
<p className="text-sm" style={{ color: 'var(--text-muted)' }}>--</p>
)}
</div>
{sentiment !== 0 && (
<div
className="text-[9px] mono font-bold px-1.5 py-0.5 rounded"
style={
sentiment > 0
? { background: 'var(--green-dim)', color: 'var(--green)' }
: { background: 'var(--red-dim)', color: 'var(--red)' }
}
>
{sentiment > 0 ? '+' : ''}{sentiment.toFixed(2)}
</div>
)}
</div>
</div>
{/* Intensity bar at bottom */}
<div
className="absolute bottom-0 left-0 right-0 h-0.5"
style={{
background: `linear-gradient(90deg, transparent, rgba(${baseColor.join(',')},${intensity}), transparent)`,
}}
/>
</div>
);
}
// ── Category Row ──
function CategoryRow({ category, subcategoryData, onCellClick, selectedCell }) {
const totalSignals = subcategoryData.reduce((s, d) => s + d.signalCount, 0);
const maxIntensity = Math.max(...subcategoryData.map(d => d.intensity), 0);
const avgSentiment = subcategoryData.reduce((s, d) => s + d.sentiment, 0) / subcategoryData.length;
return (
<div className="space-y-2">
{/* Category Header */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="text-lg">{category.icon}</span>
<h3 className="text-sm font-bold heading" style={{ color: category.cssColor }}>
{category.name}
</h3>
<span className="text-[10px]" style={{ color: 'var(--text-muted)' }}>{totalSignals} signals</span>
</div>
<div className="flex items-center gap-3 text-[10px]">
<span style={{ color: 'var(--text-muted)' }}>
Peak:{' '}
<span className="font-bold" style={{ color: category.cssColor }}>
{(maxIntensity * 100).toFixed(0)}%
</span>
</span>
<span
className="mono"
style={{ color: avgSentiment > 0 ? 'var(--green)' : avgSentiment < 0 ? 'var(--red)' : 'var(--text-muted)' }}
>
Sentiment: {avgSentiment > 0 ? '+' : ''}{avgSentiment.toFixed(3)}
</span>
</div>
</div>
{/* Subcategory Grid */}
<div className="grid grid-cols-8 gap-2">
{subcategoryData.map(sub => (
<PulseCell
key={sub.name}
subcategory={sub.name}
intensity={sub.intensity}
signalCount={sub.signalCount}
sentiment={sub.sentiment}
baseColor={category.baseColor}
onClick={() => onCellClick({ category: category.name, subcategory: sub.name, ...sub })}
isSelected={selectedCell?.category === category.name && selectedCell?.subcategory === sub.name}
/>
))}
</div>
</div>
);
}
// ── Intensity Scale ──
function IntensityScale() {
const steps = 10;
return (
<div className="flex items-center gap-2">
<span className="stat-label">Low</span>
<div className="flex gap-px">
{Array.from({ length: steps }).map((_, i) => (
<div
key={i}
className="w-5 h-3 rounded-sm"
style={{
backgroundColor: `rgba(0,240,255,${(i + 1) / steps * 0.7 + 0.05})`,
}}
/>
))}
</div>
<span className="stat-label">High</span>
</div>
);
}
// ── Cell Detail Panel ──
function CellDetail({ cell, signals, onClose }) {
if (!cell) return null;
const cellSignals = signals.filter(s => {
const lower = (s.reasoning || s.ticker || '').toLowerCase();
return cell.matchingKeywords?.some(kw => lower.includes(kw));
}).slice(0, 10);
return (
<div className="card-glow fade-in">
<div className="flex items-start justify-between mb-3">
<div>
<div className="flex items-center gap-2 mb-1">
<span className="text-lg">{cell.categoryIcon}</span>
<h3 className="heading text-sm font-bold" style={{ color: 'var(--text)' }}>
{cell.category} / {cell.subcategory}
</h3>
</div>
<p className="text-xs" style={{ color: 'var(--text-muted)' }}>
{cell.signalCount} signals | Intensity: {(cell.intensity * 100).toFixed(0)}% | Sentiment:{' '}
<span style={{ color: cell.sentiment > 0 ? 'var(--green)' : cell.sentiment < 0 ? 'var(--red)' : 'var(--text-dim)' }}>
{cell.sentiment > 0 ? '+' : ''}{cell.sentiment.toFixed(3)}
</span>
</p>
</div>
<button onClick={onClose} className="btn btn-ghost text-lg leading-none" style={{ padding: '0 6px' }}>
×
</button>
</div>
{cellSignals.length > 0 ? (
<div className="space-y-2">
<p className="stat-label mb-1">Related Signals</p>
{cellSignals.map((sig, i) => (
<div
key={i}
className="flex items-center gap-3 text-xs rounded-lg px-3 py-2"
style={{ background: 'var(--surface-el)', border: '1px solid var(--border)' }}
>
<span
className="badge"
style={
sig.direction === 'BUY_YES'
? { background: 'var(--green-dim)', color: 'var(--green)' }
: { background: 'var(--red-dim)', color: 'var(--red)' }
}
>
{sig.direction}
</span>
<span className="mono truncate flex-1" style={{ color: 'var(--text-dim)' }}>{sig.ticker}</span>
<span className="mono font-bold" style={{ color: 'var(--orange)' }}>
{(parseFloat(sig.expected_edge) * 100).toFixed(1)}%
</span>
<span className="mono" style={{ color: 'var(--blue)' }}>
{(parseFloat(sig.confidence) * 100).toFixed(0)}%
</span>
{sig.risk_approved ? (
<span className="text-[9px] font-bold" style={{ color: 'var(--green)' }}>OK</span>
) : (
<span className="text-[9px] font-bold" style={{ color: 'var(--red)' }}>BLOCKED</span>
)}
</div>
))}
</div>
) : (
<p className="text-sm text-center py-4" style={{ color: 'var(--text-muted)' }}>
No matching signals for this subcategory
</p>
)}
</div>
);
}
// ── National Overview Bar ──
function NationalOverview({ categoryStats }) {
const maxSignals = Math.max(...categoryStats.map(c => c.totalSignals), 1);
return (
<div className="card">
<div className="flex items-center gap-2 mb-4">
<h2 className="section-title">National Activity Overview</h2>
<span className="section-subtitle">Signal density across categories</span>
</div>
<div className="space-y-2">
{categoryStats.sort((a, b) => b.totalSignals - a.totalSignals).map(cat => {
const pct = (cat.totalSignals / maxSignals) * 100;
return (
<div key={cat.name} className="flex items-center gap-3">
<span className="text-sm w-5">{cat.icon}</span>
<span className="text-xs font-medium w-20 heading" style={{ color: cat.cssColor || `rgb(${cat.baseColor.join(',')})` }}>
{cat.name}
</span>
<div
className="flex-1 h-5 rounded-full overflow-hidden relative"
style={{ background: 'var(--surface-el)' }}
>
<div
className="h-full rounded-full transition-all duration-1000 relative overflow-hidden"
style={{
width: `${Math.max(pct, 2)}%`,
background: `linear-gradient(90deg, rgba(${cat.baseColor.join(',')},0.3), rgba(${cat.baseColor.join(',')},0.6))`,
}}
>
{/* Animated shimmer */}
<div
className="absolute inset-0"
style={{
background: 'linear-gradient(90deg, transparent 0%, rgba(255,255,255,0.1) 50%, transparent 100%)',
animation: 'shimmer 2s infinite',
}}
/>
</div>
<span
className="absolute right-2 top-0 bottom-0 flex items-center text-[10px] mono"
style={{ color: 'var(--text-dim)' }}
>
{cat.totalSignals}
</span>
</div>
<span
className="text-[10px] mono w-12 text-right"
style={{ color: cat.avgSentiment > 0 ? 'var(--green)' : cat.avgSentiment < 0 ? 'var(--red)' : 'var(--text-muted)' }}
>
{cat.avgSentiment > 0 ? '+' : ''}{cat.avgSentiment.toFixed(2)}
</span>
</div>
);
})}
</div>
</div>
);
}
// ── Main Pulse Page ──
export default function Pulse() {
const [data, setData] = useState(null);
const [signals, setSignals] = useState(null);
const [signalsDash, setSignalsDash] = useState(null);
const [loading, setLoading] = useState(true);
const [selectedCell, setSelectedCell] = useState(null);
const [timeWindow, setTimeWindow] = useState('24h');
useEffect(() => {
load();
const i = setInterval(load, 30000);
return () => clearInterval(i);
}, []);
async function load() {
try {
const [d, s, sd] = await Promise.all([
api('/dashboard'),
api('/signals'),
api('/signals/dashboard'),
]);
setData(d);
setSignals(s?.signals || []);
setSignalsDash(sd);
} catch {}
setLoading(false);
}
const { categoryData, categoryStats, overallStats } = useMemo(() => {
if (!signals) return { categoryData: [], categoryStats: [], overallStats: {} };
const allSignals = signals || [];
const maxSubSignals = Math.max(
...PULSE_CATEGORIES.flatMap(cat =>
cat.subcategories.map(sub => {
const keywords = cat.keywords.filter(kw => sub.toLowerCase().includes(kw.split(' ')[0]) || kw.includes(sub.toLowerCase()));
const matching = allSignals.filter(s => {
const lower = (s.reasoning || s.ticker || '').toLowerCase();
return cat.keywords.some(kw => lower.includes(kw));
});
return matching.length;
})
),
1
);
const catData = PULSE_CATEGORIES.map(category => {
const categorySignals = allSignals.filter(s => {
const lower = (s.reasoning || s.ticker || '').toLowerCase();
return category.keywords.some(kw => lower.includes(kw));
});
const totalCatSignals = categorySignals.length;
const avgCatSentiment = categorySignals.length > 0
? categorySignals.reduce((s, sig) => {
const sentSum = (sig.sources || []).reduce((ss, src) => ss + (src.sentiment || 0), 0);
return s + sentSum;
}, 0) / Math.max(categorySignals.length, 1)
: 0;
const subcategoryData = category.subcategories.map(sub => {
const subKeywords = category.keywords.filter(kw => {
const subLower = sub.toLowerCase();
return kw.includes(subLower) || subLower.includes(kw.split(' ')[0]);
});
let matchingSignals;
if (subKeywords.length > 0) {
matchingSignals = allSignals.filter(s => {
const lower = (s.reasoning || s.ticker || '').toLowerCase();
return subKeywords.some(kw => lower.includes(kw));
});
} else {
const hash = sub.split('').reduce((h, c) => ((h << 5) - h + c.charCodeAt(0)) | 0, 0);
matchingSignals = categorySignals.filter((_, i) => i % category.subcategories.length === Math.abs(hash) % category.subcategories.length);
}
const signalCount = matchingSignals.length;
const sentiment = matchingSignals.reduce((s, sig) => {
const sentSum = (sig.sources || []).reduce((ss, src) => ss + (src.sentiment || 0), 0);
return s + sentSum;
}, 0) / Math.max(matchingSignals.length, 1);
const intensity = Math.min(1, signalCount / Math.max(maxSubSignals * 0.5, 1));
return {
name: sub,
signalCount,
sentiment,
intensity,
matchingKeywords: subKeywords.length > 0 ? subKeywords : category.keywords,
categoryIcon: category.icon,
};
});
return { category, subcategoryData };
});
const catStats = PULSE_CATEGORIES.map(cat => {
const catSignals = allSignals.filter(s => {
const lower = (s.reasoning || s.ticker || '').toLowerCase();
return cat.keywords.some(kw => lower.includes(kw));
});
return {
name: cat.name,
icon: cat.icon,
baseColor: cat.baseColor,
cssColor: cat.cssColor,
totalSignals: catSignals.length,
avgSentiment: catSignals.length > 0
? catSignals.reduce((s, sig) => s + (sig.sources || []).reduce((ss, src) => ss + (src.sentiment || 0), 0), 0) / catSignals.length
: 0,
};
});
const overall = {
totalSignals: allSignals.length,
buyYes: allSignals.filter(s => s.direction === 'BUY_YES').length,
buyNo: allSignals.filter(s => s.direction === 'BUY_NO').length,
approved: allSignals.filter(s => s.risk_approved).length,
avgEdge: allSignals.length > 0 ? allSignals.reduce((s, sig) => s + parseFloat(sig.expected_edge || 0), 0) / allSignals.length : 0,
avgConf: allSignals.length > 0 ? allSignals.reduce((s, sig) => s + parseFloat(sig.confidence || 0), 0) / allSignals.length : 0,
hottest: [...catStats].sort((a, b) => b.totalSignals - a.totalSignals)[0]?.name || 'None',
};
return { categoryData: catData, categoryStats: catStats, overallStats: overall };
}, [signals]);
if (loading) return (
<div className="flex items-center justify-center h-64">
<div
className="w-8 h-8 rounded-full border-2 border-t-transparent animate-spin"
style={{ borderColor: 'var(--cyan)', borderTopColor: 'transparent' }}
/>
</div>
);
const overallStatsConfig = [
{ label: 'Total Signals', value: overallStats.totalSignals || 0, color: 'var(--cyan)' },
{ label: 'BUY YES', value: overallStats.buyYes || 0, color: 'var(--green)' },
{ label: 'BUY NO', value: overallStats.buyNo || 0, color: 'var(--red)' },
{ label: 'Approved', value: overallStats.approved || 0, color: 'var(--green)' },
{ label: 'Avg Edge', value: `${((overallStats.avgEdge || 0) * 100).toFixed(1)}%`, color: 'var(--orange)' },
{ label: 'Avg Confidence', value: `${((overallStats.avgConf || 0) * 100).toFixed(0)}%`, color: 'var(--blue)' },
{ label: 'Hottest Sector', value: overallStats.hottest, color: 'var(--red)' },
];
return (
<div className="space-y-6">
{/* Shimmer + hover scale CSS */}
<style>{`
@keyframes shimmer {
0% { transform: translateX(-100%); }
100% { transform: translateX(200%); }
}
`}</style>
{/* Page Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="heading text-2xl font-bold flex items-center gap-3">
{/* Live pulsing indicator */}
<span
className="inline-flex items-center justify-center w-3 h-3 rounded-full pulse-cyan"
style={{ backgroundColor: 'var(--cyan)', flexShrink: 0 }}
/>
<span className="gradient-cyan">Pulse of America</span>
</h1>
<p className="section-subtitle mt-1">
Sentiment and activity heatmap across American prediction markets
</p>
</div>
<div className="flex items-center gap-3">
<div
className="flex items-center gap-0.5 rounded-lg p-0.5"
style={{ background: 'var(--surface-el)', border: '1px solid var(--border)' }}
>
{['24h', '7d', '30d'].map(tw => (
<button
key={tw}
onClick={() => setTimeWindow(tw)}
className="btn btn-sm"
style={timeWindow === tw
? { background: 'var(--cyan)', color: 'var(--bg)', fontWeight: 700 }
: { background: 'transparent', color: 'var(--text-muted)' }
}
>
{tw}
</button>
))}
</div>
<button onClick={load} className="btn btn-o btn-sm">Refresh</button>
</div>
</div>
{/* Overall Stats */}
<div className="grid grid-cols-7 gap-3">
{overallStatsConfig.map(s => (
<div key={s.label} className="card">
<p className="stat-label mb-1">{s.label}</p>
<p className="stat-v text-lg" style={{ color: s.color }}>{s.value}</p>
</div>
))}
</div>
{/* National Overview */}
<NationalOverview categoryStats={categoryStats} />
{/* Live Signal Heatmap Grid */}
<div className="card p-4">
<div className="flex items-center justify-between mb-5">
<div className="flex items-center gap-2">
<div
className="w-2 h-2 rounded-full animate-pulse"
style={{ backgroundColor: 'var(--red)' }}
/>
<h2 className="section-title">Live Signal Heatmap</h2>
</div>
<IntensityScale />
</div>
<div className="space-y-5">
{categoryData.map(({ category, subcategoryData }) => (
<CategoryRow
key={category.name}
category={category}
subcategoryData={subcategoryData}
onCellClick={cell => setSelectedCell(
selectedCell?.category === cell.category && selectedCell?.subcategory === cell.subcategory
? null
: cell
)}
selectedCell={selectedCell}
/>
))}
</div>
</div>
{/* Cell Detail */}
{selectedCell && (
<CellDetail
cell={selectedCell}
signals={signals || []}
onClose={() => setSelectedCell(null)}
/>
)}
{/* Signal Source Breakdown */}
<div className="card">
<div className="flex items-center gap-2 mb-4">
<h2 className="section-title">Signal Source Breakdown</h2>
</div>
<div className="grid grid-cols-4 gap-3">
{(signalsDash?.sourceBreakdown || []).slice(0, 8).map(src => {
const sent = parseFloat(src.avg_sent) || 0;
const count = parseInt(src.cnt) || 0;
return (
<div
key={src.source}
className="rounded-lg p-3"
style={{ background: 'var(--surface-el)', border: '1px solid var(--border)' }}
>
<div className="flex items-center justify-between mb-2">
<span className="text-xs font-bold heading" style={{ color: 'var(--text-dim)' }}>
{src.source.replace('_', ' ').toUpperCase()}
</span>
<span className="mono text-xs font-bold" style={{ color: 'var(--orange)' }}>{count}</span>
</div>
<div className="flex items-center gap-2">
<div className="progress-bar flex-1">
<div
className="progress-fill"
style={{
width: `${Math.min(100, Math.abs(sent) * 5000 + 10)}%`,
background: sent > 0 ? 'var(--green)' : sent < 0 ? 'var(--red)' : 'var(--text-muted)',
}}
/>
</div>
<span
className="text-[10px] mono"
style={{ color: sent > 0 ? 'var(--green)' : sent < 0 ? 'var(--red)' : 'var(--text-muted)' }}
>
{sent > 0 ? '+' : ''}{sent.toFixed(3)}
</span>
</div>
</div>
);
})}
</div>
</div>
{/* Market Pulse Metrics from Dashboard */}
{data && (
<div className="card">
<div className="flex items-center gap-2 mb-4">
<h2 className="section-title">Market Pulse Metrics</h2>
</div>
<div className="grid grid-cols-5 gap-3 text-center">
{[
{
label: 'Balance',
value: `$${data.balance ? (data.balance / 100).toLocaleString(undefined, { minimumFractionDigits: 0 }) : '0'}`,
color: 'var(--green)',
},
{
label: "Today's P&L",
value: `${(data.daily_pnl || 0) >= 0 ? '+' : ''}$${((data.daily_pnl || 0) / 100).toFixed(2)}`,
color: (data.daily_pnl || 0) >= 0 ? 'var(--green)' : 'var(--red)',
},
{
label: "Today's Trades",
value: (data.today_trades || 0).toLocaleString(),
color: 'var(--orange)',
},
{
label: 'Win Rate',
value: `${data.today_trades > 0 ? ((data.today_wins / data.today_trades) * 100).toFixed(1) : '0'}%`,
color: data.today_trades > 0 && (data.today_wins / data.today_trades * 100) >= 50
? 'var(--green)'
: 'var(--red)',
},
{
label: 'Markets Tracked',
value: data.markets_tracked || 0,
color: 'var(--purple)',
},
].map(s => (
<div
key={s.label}
className="rounded-lg p-3"
style={{ background: 'var(--surface-el)', border: '1px solid var(--border)' }}
>
<p className="stat-label mb-1">{s.label}</p>
<p className="stat-v text-lg" style={{ color: s.color }}>{s.value}</p>
</div>
))}
</div>
</div>
)}
</div>
);
}