← back to Bertha

kalshi-dash/src/pages/Risk.jsx

756 lines

import React, { useState, useEffect, useMemo } from 'react';
import { api, post } from '../api';
import {
  BarChart, Bar, AreaChart, Area,
  XAxis, YAxis, Tooltip, ResponsiveContainer,
  Cell, PieChart, Pie,
} from 'recharts';

// ── Strategy color palette ──
const STRAT_COLORS = {
  aggressive: '#ef4444', conservative: '#3b82f6', weather: '#06b6d4', momentum: '#f59e0b',
  contrarian: '#a855f7', ai_enhanced: '#ec4899', mc_validated: '#8b5cf6', scalper: '#10b981',
  patient: '#6366f1', random: '#f97316', penny_pincher: '#84cc16', spray_pray: '#14b8a6',
  nickel_slots: '#eab308', degen_lite: '#e11d48', politics_junkie: '#7c3aed', double_down: '#dc2626',
  volatility_rider: '#0ea5e9', whale: '#1d4ed8', yolo: '#be123c', full_send: '#ea580c',
  kelly_criterion: '#4f46e5', sharpe_sniper: '#059669', bayesian_blend: '#7c3aed', ensemble_lock: '#0891b2',
  mean_revert: '#4338ca', info_ratio: '#b45309', anti_fomo: '#64748b', nightcrawler: '#334155',
  heatseeker: '#dc2626', quant_core: '#2563eb',
};
const ETF_COLOR = '#F7931A';

function getColor(strategy) {
  if (strategy.startsWith('etf_')) return ETF_COLOR;
  return STRAT_COLORS[strategy] || '#6b7280';
}

// ── Mini win/loss bar ──
function MiniBar({ won, lost, total }) {
  if (!total) return <span className="text-xs" style={{ color: 'var(--text-muted)' }}>no trades</span>;
  const w = (won / total) * 100;
  const l = (lost / total) * 100;
  return (
    <div className="flex h-1.5 rounded-full overflow-hidden w-full" style={{ background: 'var(--border)' }}>
      {won > 0 && <div style={{ width: `${w}%`, background: 'var(--green)' }} />}
      {lost > 0 && <div style={{ width: `${l}%`, background: 'var(--red)' }} />}
    </div>
  );
}

// ── Streak badge ──
function StreakBadge({ streak }) {
  if (streak === 0) return <span style={{ color: 'var(--text-muted)', fontSize: '0.7rem' }}>—</span>;
  const hot = streak > 0;
  return (
    <span
      className="mono font-bold text-xs"
      style={{ color: hot ? 'var(--green)' : 'var(--red)' }}
    >
      {hot ? '+' : ''}{streak}
    </span>
  );
}

// ── Risk level badge ──
function RiskBadge({ level }) {
  if (!level) return null;
  const map = {
    low: 'bg-green',
    medium: 'bg-orange',
    high: 'bg-red',
  };
  return (
    <span className={`badge ${map[level] || 'bg-blue'}`}>
      {level.toUpperCase()}
    </span>
  );
}

// ── Trade direction helpers ──
const sideLabel = (dir) => dir === 'BUY_YES' ? 'YES' : 'NO';
const sideBadge = (dir) => dir === 'BUY_YES' ? 'bg-green' : 'bg-red';
const pnlColor = (cents) => cents >= 0 ? 'var(--green)' : 'var(--red)';

// ── Portfolio Trades Table (shown inside expanded card) ──
function PortfolioTradesTable({ trades, loading }) {
  if (loading) {
    return (
      <div className="flex justify-center py-4">
        <div className="w-5 h-5 rounded-full border-2 animate-spin" style={{ borderColor: 'var(--border)', borderTopColor: 'var(--cyan)' }} />
      </div>
    );
  }
  if (!trades || trades.length === 0) {
    return (
      <p className="text-xs text-center py-3" style={{ color: 'var(--text-muted)' }}>
        No trades found for this portfolio
      </p>
    );
  }
  return (
    <div className="overflow-x-auto" style={{ maxHeight: 240, overflowY: 'auto' }}>
      <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '0.7rem' }}>
        <thead>
          <tr style={{ borderBottom: '1px solid var(--border)' }}>
            {['Market', 'Dir', 'Entry', 'Qty', 'Cost', 'P&L', 'Status'].map(h => (
              <th key={h} style={{
                padding: '4px 6px', textAlign: 'left', color: 'var(--text-muted)',
                fontWeight: 600, fontFamily: 'var(--font-body)', fontSize: '0.65rem',
                whiteSpace: 'nowrap',
              }}>{h}</th>
            ))}
          </tr>
        </thead>
        <tbody>
          {trades.map((t, i) => {
            const pnl = parseInt(t.pnl_cents) || 0;
            const cost = parseInt(t.cost_cents) || 0;
            const entry = parseInt(t.entry_price_cents) || 0;
            const contracts = parseInt(t.contracts) || 0;
            const status = t.status || 'open';
            return (
              <tr key={t.id || i} style={{
                borderBottom: '1px solid var(--border)',
                background: i % 2 === 0 ? 'transparent' : 'rgba(255,255,255,0.01)',
              }}>
                <td style={{
                  padding: '4px 6px', maxWidth: 140, overflow: 'hidden',
                  textOverflow: 'ellipsis', whiteSpace: 'nowrap', color: 'var(--text-dim)',
                  fontFamily: 'var(--font-body)',
                }} title={t.market_title || t.market_id}>
                  {(t.market_title || t.market_id || '').substring(0, 28)}{(t.market_title || t.market_id || '').length > 28 ? '...' : ''}
                </td>
                <td style={{ padding: '4px 6px' }}>
                  <span className={`badge ${sideBadge(t.direction)}`} style={{ fontSize: '0.6rem', padding: '1px 5px' }}>
                    {sideLabel(t.direction)}
                  </span>
                </td>
                <td style={{ padding: '4px 6px', fontFamily: "'JetBrains Mono', monospace", color: 'var(--orange)' }}>
                  {entry}¢
                </td>
                <td style={{ padding: '4px 6px', fontFamily: "'JetBrains Mono', monospace", color: 'var(--text-dim)' }}>
                  {contracts}
                </td>
                <td style={{ padding: '4px 6px', fontFamily: "'JetBrains Mono', monospace", color: 'var(--text-dim)' }}>
                  ${(cost / 100).toFixed(2)}
                </td>
                <td style={{
                  padding: '4px 6px', fontFamily: "'JetBrains Mono', monospace", fontWeight: 700,
                  color: pnlColor(pnl),
                }}>
                  {pnl !== 0 ? `${pnl >= 0 ? '+' : ''}$${(pnl / 100).toFixed(2)}` : '—'}
                </td>
                <td style={{ padding: '4px 6px' }}>
                  <span className={`badge ${status === 'open' ? 'bg-blue' : status === 'closed' ? 'bg-green' : 'bg-orange'}`}
                    style={{ fontSize: '0.6rem', padding: '1px 5px' }}
                  >
                    {status}
                  </span>
                </td>
              </tr>
            );
          })}
        </tbody>
      </table>
    </div>
  );
}

// ── Portfolio Card ──
function PortfolioCard({ p, dailyPnl, expanded, onToggle, pfTrades, pfTradesLoading }) {
  const pnl = (p.total_returned_cents - p.total_invested_cents) / 100;
  const totalTrades = p.trades_won + p.trades_lost + p.trades_expired;
  const invested = p.total_invested_cents / 100;
  const returned = p.total_returned_cents / 100;
  const active = totalTrades > 0;
  const color = getColor(p.strategy);

  // Allocation as % of daily budget vs a synthetic max
  const allocPct = Math.min(100, (p.daily_budget_cents / Math.max(1, 100000)) * 100);

  return (
    <div
      className="card cursor-pointer transition-all"
      style={
        expanded
          ? {
              borderColor: 'var(--cyan)',
              boxShadow: '0 0 20px var(--cyan-glow)',
              opacity: 1,
            }
          : active
          ? { opacity: 1 }
          : { opacity: 0.5 }
      }
      onClick={onToggle}
    >
      {/* Header row */}
      <div className="flex items-center justify-between mb-2">
        <div className="flex items-center gap-2 min-w-0">
          <div
            className="w-2.5 h-2.5 rounded-full flex-shrink-0"
            style={{ background: color }}
          />
          <span
            className="heading font-semibold text-sm truncate"
            style={{ color: 'var(--text)' }}
          >
            {p.name}
          </span>
          <span className="mono text-xs flex-shrink-0" style={{ color: 'var(--text-muted)' }}>
            #{p.id}
          </span>
          {totalTrades > 0 && (
            <span className="text-xs flex-shrink-0 px-1.5 py-0.5 rounded"
              style={{ background: 'var(--surface-el)', color: 'var(--text-muted)', fontSize: '0.65rem' }}>
              {totalTrades} trade{totalTrades !== 1 ? 's' : ''}
            </span>
          )}
        </div>
        <span
          className="mono text-sm font-bold flex-shrink-0 ml-2"
          style={{ color: pnl >= 0 ? 'var(--green)' : 'var(--red)' }}
        >
          {pnl >= 0 ? '+' : ''}${pnl.toFixed(2)}
        </span>
      </div>

      {/* Strategy tag + streak */}
      <div className="flex items-center gap-2 mb-3">
        <span
          className="mono px-1.5 py-0.5 rounded text-xs flex-shrink-0"
          style={{
            background: `${color}15`,
            color,
            border: `1px solid ${color}30`,
          }}
        >
          {p.strategy}
        </span>
        <StreakBadge streak={p.streak} />
        <div className="flex-1" />
        <span
          className="badge flex-shrink-0"
          style={
            active
              ? { background: 'var(--green-dim)', color: 'var(--green)' }
              : { background: 'var(--border)', color: 'var(--text-muted)' }
          }
        >
          {active ? 'Active' : 'Dormant'}
        </span>
      </div>

      {/* Win/loss bar */}
      <div className="mb-3">
        <MiniBar won={p.trades_won} lost={p.trades_lost} total={totalTrades} />
        <div className="flex justify-between mt-1">
          <span className="text-xs" style={{ color: 'var(--text-muted)' }}>
            {p.trades_won}W / {p.trades_lost}L
          </span>
          <span className="mono text-xs" style={{ color: 'var(--text-dim)' }}>
            {p.win_rate || 0}%
          </span>
        </div>
      </div>

      {/* Allocation bar */}
      <div className="mb-3">
        <div className="flex justify-between text-xs mb-1" style={{ color: 'var(--text-muted)' }}>
          <span>Daily Budget</span>
          <span className="mono" style={{ color: 'var(--text-dim)' }}>
            ${(p.daily_budget_cents / 100).toFixed(2)}
          </span>
        </div>
        <div className="progress-bar">
          <div
            className="progress-fill"
            style={{ width: `${allocPct}%`, background: `linear-gradient(90deg, ${color}, ${color}99)` }}
          />
        </div>
      </div>

      {/* Stats grid */}
      <div className="grid grid-cols-2 gap-x-3 gap-y-1" style={{ fontSize: '0.7rem' }}>
        <div className="flex justify-between">
          <span style={{ color: 'var(--text-muted)' }}>Invested</span>
          <span className="mono" style={{ color: 'var(--text-dim)' }}>${invested.toFixed(2)}</span>
        </div>
        <div className="flex justify-between">
          <span style={{ color: 'var(--text-muted)' }}>Returned</span>
          <span className="mono" style={{ color: 'var(--text-dim)' }}>${returned.toFixed(2)}</span>
        </div>
        <div className="flex justify-between">
          <span style={{ color: 'var(--text-muted)' }}>Trades Today</span>
          <span className="mono" style={{ color: 'var(--cyan)' }}>{p.trades_today}</span>
        </div>
        <div className="flex justify-between">
          <span style={{ color: 'var(--text-muted)' }}>Total</span>
          <span className="mono" style={{ color: 'var(--text-dim)' }}>{totalTrades}</span>
        </div>
      </div>

      {/* Expanded sparkline */}
      {expanded && dailyPnl && dailyPnl.length > 0 && (
        <div className="mt-3 pt-3" style={{ borderTop: '1px solid var(--border)' }}>
          <p className="text-xs mb-1" style={{ color: 'var(--text-muted)' }}>Cumulative P&L</p>
          <ResponsiveContainer width="100%" height={80}>
            <AreaChart data={dailyPnl}>
              <XAxis dataKey="date" hide />
              <YAxis hide domain={['dataMin', 'dataMax']} />
              <Tooltip
                contentStyle={{
                  background: 'var(--surface-el)',
                  border: '1px solid var(--border)',
                  borderRadius: 8,
                  fontSize: 11,
                  fontFamily: 'var(--font-mono)',
                }}
                formatter={v => [`$${(v / 100).toFixed(2)}`, 'P&L']}
                labelFormatter={l => l}
              />
              <Area
                type="monotone"
                dataKey="cumulative"
                stroke={color}
                fill={color}
                fillOpacity={0.1}
                strokeWidth={1.5}
                dot={false}
              />
            </AreaChart>
          </ResponsiveContainer>
          {p.description && (
            <p className="text-xs mt-1" style={{ color: 'var(--text-muted)' }}>{p.description}</p>
          )}

          {/* Portfolio Trades Drill-Down */}
          <div className="mt-3 pt-3" style={{ borderTop: '1px solid var(--border)' }}>
            <p className="text-xs mb-2 font-semibold" style={{ color: 'var(--cyan)' }}>
              Individual Trades
            </p>
            <PortfolioTradesTable trades={pfTrades} loading={pfTradesLoading} />
          </div>
        </div>
      )}

      {/* If expanded but no dailyPnl chart, still show trades */}
      {expanded && (!dailyPnl || dailyPnl.length === 0) && (
        <div className="mt-3 pt-3" style={{ borderTop: '1px solid var(--border)' }}>
          <p className="text-xs mb-2 font-semibold" style={{ color: 'var(--cyan)' }}>
            Individual Trades
          </p>
          <PortfolioTradesTable trades={pfTrades} loading={pfTradesLoading} />
        </div>
      )}
    </div>
  );
}

// ── Spinner ──
function Spinner() {
  return (
    <div className="flex items-center justify-center h-64">
      <div
        className="w-9 h-9 rounded-full border-2 animate-spin"
        style={{ borderColor: 'var(--border)', borderTopColor: 'var(--cyan)' }}
      />
    </div>
  );
}

export default function Risk() {
  const [portfolios, setPortfolios] = useState([]);
  const [dailyData, setDailyData] = useState({});
  const [loading, setLoading] = useState(true);
  const [filter, setFilter] = useState('all'); // all, active, dormant, etf, core
  const [sort, setSort] = useState('pnl'); // pnl, winrate, invested, name, id
  const [expanded, setExpanded] = useState(null);
  const [pfTrades, setPfTrades] = useState({}); // { portfolioId: [...trades] }
  const [pfTradesLoading, setPfTradesLoading] = useState(null); // portfolioId currently loading

  useEffect(() => { load(); }, []);

  async function load() {
    try {
      const [pf, hist] = await Promise.all([
        api('/portfolios'),
        api('/portfolio-trades?include_history=true').catch(() => ({})),
      ]);
      setPortfolios(pf || []);
      if (hist?.byPortfolio) setDailyData(hist.byPortfolio);
    } catch {}
    setLoading(false);
  }

  // Load trades for a portfolio when expanded
  async function loadPortfolioTrades(portfolioId) {
    if (pfTrades[portfolioId]) return; // already loaded
    setPfTradesLoading(portfolioId);
    try {
      const data = await api(`/portfolio-trades?portfolio_id=${portfolioId}`);
      setPfTrades(prev => ({ ...prev, [portfolioId]: Array.isArray(data) ? data : [] }));
    } catch {
      setPfTrades(prev => ({ ...prev, [portfolioId]: [] }));
    }
    setPfTradesLoading(null);
  }

  function handleToggle(id) {
    if (expanded === id) {
      setExpanded(null);
    } else {
      setExpanded(id);
      loadPortfolioTrades(id);
    }
  }

  const filtered = useMemo(() => {
    let list = [...portfolios];
    if (filter === 'active') list = list.filter(p => p.trades_won + p.trades_lost > 0);
    if (filter === 'dormant') list = list.filter(p => p.trades_won + p.trades_lost === 0);
    if (filter === 'etf') list = list.filter(p => p.strategy.startsWith('etf_'));
    if (filter === 'core') list = list.filter(p => !p.strategy.startsWith('etf_'));

    if (sort === 'pnl')
      list.sort((a, b) =>
        (b.total_returned_cents - b.total_invested_cents) -
        (a.total_returned_cents - a.total_invested_cents)
      );
    if (sort === 'winrate') list.sort((a, b) => (b.win_rate || 0) - (a.win_rate || 0));
    if (sort === 'invested') list.sort((a, b) => b.total_invested_cents - a.total_invested_cents);
    if (sort === 'name') list.sort((a, b) => a.name.localeCompare(b.name));
    if (sort === 'id') list.sort((a, b) => a.id - b.id);
    return list;
  }, [portfolios, filter, sort]);

  const stats = useMemo(() => {
    const totalInvested = portfolios.reduce((s, p) => s + p.total_invested_cents, 0);
    const totalReturned = portfolios.reduce((s, p) => s + p.total_returned_cents, 0);
    const totalWins = portfolios.reduce((s, p) => s + p.trades_won, 0);
    const totalLosses = portfolios.reduce((s, p) => s + p.trades_lost, 0);
    const active = portfolios.filter(p => p.trades_won + p.trades_lost > 0).length;
    const dormant = portfolios.length - active;
    const bestPf = [...portfolios].sort(
      (a, b) =>
        (b.total_returned_cents - b.total_invested_cents) -
        (a.total_returned_cents - a.total_invested_cents)
    )[0];
    const worstPf = [...portfolios].sort(
      (a, b) =>
        (a.total_returned_cents - a.total_invested_cents) -
        (b.total_returned_cents - b.total_invested_cents)
    )[0];
    return { totalInvested, totalReturned, totalWins, totalLosses, active, dormant, bestPf, worstPf };
  }, [portfolios]);

  const barData = useMemo(() =>
    [...portfolios]
      .map(p => ({ name: p.name, pnl: (p.total_returned_cents - p.total_invested_cents) / 100, strategy: p.strategy }))
      .filter(p => p.pnl !== 0)
      .sort((a, b) => b.pnl - a.pnl)
      .slice(0, 20),
    [portfolios]
  );

  const pieData = useMemo(() => {
    const cats = {};
    portfolios.forEach(p => {
      const cat = p.strategy.startsWith('etf_') ? 'ETF Baskets' : p.strategy;
      if (!cats[cat]) cats[cat] = { name: cat, value: 0 };
      cats[cat].value += p.total_invested_cents;
    });
    return Object.values(cats).filter(c => c.value > 0).sort((a, b) => b.value - a.value);
  }, [portfolios]);

  if (loading) return <Spinner />;

  const pnl = (stats.totalReturned - stats.totalInvested) / 100;
  const totalTrades = stats.totalWins + stats.totalLosses;

  return (
    <div className="space-y-6 fade-in">

      {/* ── Page Header ── */}
      <div className="flex items-center justify-between">
        <div>
          <h1 className="heading text-3xl" style={{ color: 'var(--text)' }}>
            Portfauxlios
            <span className="badge bg-cyan ml-3 align-middle text-base">
              {portfolios.length}
            </span>
          </h1>
          <p className="section-subtitle mt-1">
            All {portfolios.length} portfolios — performance, P&L, and strategy breakdown
          </p>
        </div>
        <button onClick={load} className="btn btn-o btn-sm">Refresh</button>
      </div>

      {/* ── Summary Stats ── */}
      <div className="grid grid-cols-3 md:grid-cols-6 gap-3">
        {[
          {
            label: 'Total P&L',
            value: `${pnl >= 0 ? '+' : ''}$${pnl.toFixed(2)}`,
            color: pnl >= 0 ? 'var(--green)' : 'var(--red)',
          },
          {
            label: 'Invested',
            value: `$${(stats.totalInvested / 100).toFixed(2)}`,
            color: 'var(--orange)',
          },
          {
            label: 'Returned',
            value: `$${(stats.totalReturned / 100).toFixed(2)}`,
            color: 'var(--blue)',
          },
          {
            label: 'Win Rate',
            value: totalTrades ? `${((stats.totalWins / totalTrades) * 100).toFixed(1)}%` : '0%',
            color: 'var(--purple)',
          },
          { label: 'Active', value: stats.active, color: 'var(--green)' },
          { label: 'Dormant', value: stats.dormant, color: 'var(--text-muted)' },
        ].map(s => (
          <div key={s.label} className="card text-center" style={{ padding: '1rem' }}>
            <div className="stat-v" style={{ color: s.color, fontSize: '1.4rem' }}>
              {s.value}
            </div>
            <div className="stat-label mt-1">{s.label}</div>
          </div>
        ))}
      </div>

      {/* ── Charts Row ── */}
      <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
        {/* P&L Bar Chart */}
        <div className="card md:col-span-2">
          <h2 className="heading text-sm mb-3" style={{ color: 'var(--text-dim)' }}>
            P&L by Portfolio
          </h2>
          {barData.length > 0 ? (
            <ResponsiveContainer width="100%" height={220}>
              <BarChart data={barData} margin={{ left: 10, right: 10 }}>
                <XAxis
                  dataKey="name"
                  tick={{ fill: 'var(--text-muted)', fontSize: 9, fontFamily: 'var(--font-mono)' }}
                  angle={-45}
                  textAnchor="end"
                  height={60}
                />
                <YAxis
                  tick={{ fill: 'var(--text-muted)', fontSize: 10, fontFamily: 'var(--font-mono)' }}
                  tickFormatter={v => `$${v}`}
                />
                <Tooltip
                  contentStyle={{
                    background: 'var(--surface-el)',
                    border: '1px solid var(--border)',
                    borderRadius: 8,
                    fontSize: 11,
                    fontFamily: 'var(--font-mono)',
                  }}
                  formatter={v => [`$${v.toFixed(2)}`, 'P&L']}
                />
                <Bar dataKey="pnl" radius={[4, 4, 0, 0]}>
                  {barData.map((d, i) => (
                    <Cell
                      key={i}
                      fill={d.pnl >= 0 ? 'var(--green)' : 'var(--red)'}
                      fillOpacity={0.8}
                    />
                  ))}
                </Bar>
              </BarChart>
            </ResponsiveContainer>
          ) : (
            <div className="text-center py-10 text-sm" style={{ color: 'var(--text-muted)' }}>
              No P&L data yet
            </div>
          )}
        </div>

        {/* Allocation Pie */}
        <div className="card">
          <h2 className="heading text-sm mb-3" style={{ color: 'var(--text-dim)' }}>
            Capital Allocation
          </h2>
          {pieData.length > 0 ? (
            <ResponsiveContainer width="100%" height={220}>
              <PieChart>
                <Pie
                  data={pieData}
                  dataKey="value"
                  nameKey="name"
                  cx="50%"
                  cy="50%"
                  innerRadius={40}
                  outerRadius={75}
                  paddingAngle={2}
                  strokeWidth={0}
                >
                  {pieData.map((d, i) => (
                    <Cell
                      key={i}
                      fill={getColor(d.name === 'ETF Baskets' ? 'etf_' : d.name)}
                      fillOpacity={0.85}
                    />
                  ))}
                </Pie>
                <Tooltip
                  contentStyle={{
                    background: 'var(--surface-el)',
                    border: '1px solid var(--border)',
                    borderRadius: 8,
                    fontSize: 11,
                    fontFamily: 'var(--font-mono)',
                  }}
                  formatter={v => [`$${(v / 100).toFixed(2)}`, 'Invested']}
                />
              </PieChart>
            </ResponsiveContainer>
          ) : (
            <div className="text-center py-10 text-sm" style={{ color: 'var(--text-muted)' }}>
              No allocations yet
            </div>
          )}
        </div>
      </div>

      {/* ── Best / Worst Performers ── */}
      <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
        {stats.bestPf && (
          <div
            className="card"
            style={{ borderColor: 'rgba(0,195,137,0.25)', background: 'var(--green-dim)' }}
          >
            <div className="flex items-center gap-2 mb-2">
              <span
                className="badge"
                style={{ background: 'var(--green-dim)', color: 'var(--green)' }}
              >
                Best Performer
              </span>
            </div>
            <div className="flex items-center justify-between">
              <span className="heading font-bold" style={{ color: 'var(--text)' }}>
                {stats.bestPf.name}
              </span>
              <span className="mono font-bold text-green">
                +${((stats.bestPf.total_returned_cents - stats.bestPf.total_invested_cents) / 100).toFixed(2)}
              </span>
            </div>
            <span className="text-xs" style={{ color: 'var(--text-muted)' }}>
              {stats.bestPf.strategy} — {stats.bestPf.win_rate || 0}% win rate
            </span>
          </div>
        )}
        {stats.worstPf && (
          <div
            className="card"
            style={{ borderColor: 'rgba(255,92,92,0.25)', background: 'var(--red-dim)' }}
          >
            <div className="flex items-center gap-2 mb-2">
              <span
                className="badge"
                style={{ background: 'var(--red-dim)', color: 'var(--red)' }}
              >
                Worst Performer
              </span>
            </div>
            <div className="flex items-center justify-between">
              <span className="heading font-bold" style={{ color: 'var(--text)' }}>
                {stats.worstPf.name}
              </span>
              <span className="mono font-bold text-red">
                ${((stats.worstPf.total_returned_cents - stats.worstPf.total_invested_cents) / 100).toFixed(2)}
              </span>
            </div>
            <span className="text-xs" style={{ color: 'var(--text-muted)' }}>
              {stats.worstPf.strategy} — {stats.worstPf.win_rate || 0}% win rate
            </span>
          </div>
        )}
      </div>

      {/* ── Filter & Sort Controls ── */}
      <div className="flex flex-wrap items-center justify-between gap-3">
        <div className="flex flex-wrap gap-2">
          {['all', 'active', 'dormant', 'etf', 'core'].map(f => (
            <button
              key={f}
              onClick={() => setFilter(f)}
              className="px-3 py-1.5 rounded-lg text-xs font-semibold transition"
              style={
                filter === f
                  ? {
                      background: 'var(--cyan-dim)',
                      color: 'var(--cyan)',
                      border: '1px solid rgba(0,240,255,0.3)',
                    }
                  : {
                      background: 'var(--surface-el)',
                      color: 'var(--text-muted)',
                      border: '1px solid var(--border)',
                    }
              }
            >
              {f === 'all'
                ? `All (${portfolios.length})`
                : f === 'active'
                ? `Active (${stats.active})`
                : f === 'dormant'
                ? `Dormant (${stats.dormant})`
                : f === 'etf'
                ? 'ETF Baskets'
                : 'Core Strategies'}
            </button>
          ))}
        </div>
        <div className="flex items-center gap-2">
          <span className="text-xs" style={{ color: 'var(--text-muted)' }}>Sort:</span>
          {['pnl', 'winrate', 'invested', 'name', 'id'].map(s => (
            <button
              key={s}
              onClick={() => setSort(s)}
              className="px-2 py-1 rounded text-xs font-medium transition"
              style={
                sort === s
                  ? { color: 'var(--cyan)', background: 'var(--cyan-dim)' }
                  : { color: 'var(--text-muted)' }
              }
            >
              {s === 'pnl' ? 'P&L' : s === 'winrate' ? 'Win%' : s.charAt(0).toUpperCase() + s.slice(1)}
            </button>
          ))}
        </div>
      </div>

      {/* ── Portfolio Card Grid ── */}
      {filtered.length === 0 ? (
        <div
          className="text-center py-16 text-sm"
          style={{ color: 'var(--text-muted)' }}
        >
          No portfolios match this filter
        </div>
      ) : (
        <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3">
          {filtered.map(p => (
            <PortfolioCard
              key={p.id}
              p={p}
              dailyPnl={dailyData[p.id]?.daily || []}
              expanded={expanded === p.id}
              onToggle={() => handleToggle(p.id)}
              pfTrades={pfTrades[p.id] || null}
              pfTradesLoading={pfTradesLoading === p.id}
            />
          ))}
        </div>
      )}
    </div>
  );
}