// StatsView.jsx — full analytics with Max Drawdown, Streaks, Day-of-Week, Hold Time const StatsPage = ({ trades }) => { const { useState } = React; const [period, setPeriod] = useState('all'); const [instrument, setInstrument] = useState('all'); const filtered = filterByPeriod(trades, period) .filter(t => instrument === 'all' || t.type === instrument); const closed = filtered.filter(t => t.status === 'closed'); const stats = computeStats(filtered); const curve = getEquityCurve(filtered); const monthly = getPnLByMonth(filtered); const dd = computeMaxDrawdown(filtered); const streaks = computeStreaks(filtered); const dowData = computeDayOfWeek(filtered); const holdDist = computeHoldTimeDistribution(filtered); const holdStats = computeHoldStats(filtered); // Signal performance const signalMap = {}; closed.forEach(t => { const sigs = [...(t.signals || []), ...(t.customSignal ? [t.customSignal] : [])]; sigs.forEach(s => { if (!signalMap[s]) signalMap[s] = { wins: 0, losses: 0, pnl: 0, trades: 0 }; signalMap[s].trades++; signalMap[s].pnl += t.pnl || 0; if (t.pnl > 0) signalMap[s].wins++; else signalMap[s].losses++; }); }); const signals = Object.entries(signalMap) .map(([name, v]) => ({ name, ...v, winRate: v.trades > 0 ? v.wins / v.trades : 0 })) .sort((a, b) => b.pnl - a.pnl); // Symbol breakdown const symMap = {}; closed.forEach(t => { if (!symMap[t.symbol]) symMap[t.symbol] = { pnl: 0, trades: 0, wins: 0 }; symMap[t.symbol].pnl += t.pnl || 0; symMap[t.symbol].trades++; if (t.pnl > 0) symMap[t.symbol].wins++; }); const symbols = Object.entries(symMap) .map(([sym, v]) => ({ sym, ...v, winRate: v.trades > 0 ? v.wins / v.trades : 0 })) .sort((a, b) => b.pnl - a.pnl); // Best / worst const sortedClosed = [...closed].sort((a, b) => (b.pnl || 0) - (a.pnl || 0)); const best3 = sortedClosed.slice(0, 3); const worst3 = sortedClosed.slice(-3).reverse(); // P&L distribution const buckets = [ { label: '< -$500', min: -Infinity, max: -500 }, { label: '-$500–-$100', min: -500, max: -100 }, { label: '-$100–$0', min: -100, max: 0 }, { label: '$0–$100', min: 0, max: 100 }, { label: '$100–$500', min: 100, max: 500 }, { label: '> $500', min: 500, max: Infinity }, ]; const distribution = buckets.map(b => ({ ...b, count: closed.filter(t => (t.pnl || 0) > b.min && (t.pnl || 0) <= b.max).length })); const distMax = Math.max(...distribution.map(b => b.count), 1); const holdMax = Math.max(...holdDist.map(b => b.count), 1); // Equity curve SVG const W = 600, H = 120; const vals = curve.map(p => p.value); const minV = Math.min(...vals), maxV = Math.max(...vals); const rangeV = maxV - minV || 1; const eqPts = curve.map((p, i) => { const x = (i / (curve.length - 1)) * W; const y = H - ((p.value - minV) / rangeV) * (H - 16) - 8; return `${x},${y}`; }).join(' '); const lineColor = stats.totalPnL >= 0 ? '#00d27a' : '#f04060'; const card = (children, style = {}) => React.createElement('div', { style: { background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: '10px', padding: '22px 24px', ...style } }, children); const sectionTitle = (t) => React.createElement('div', { style: { fontSize: '11px', color: 'var(--muted)', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: '16px' } }, t); return React.createElement('div', { style: { padding: '28px 32px', maxWidth: '1240px' } }, [ // Header + filters React.createElement('div', { key: 'hdr', style: { marginBottom: '24px', display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' } }, [ React.createElement('div', { key: 'left' }, [ React.createElement('h1', { key: 'h', style: { fontSize: '22px', fontWeight: 700, color: 'var(--text)', margin: 0 } }, 'Analytics'), React.createElement('p', { key: 'sub', style: { fontSize: '13px', color: 'var(--muted)', marginTop: '4px', margin: '4px 0 0' } }, 'Deep-dive performance statistics') ]), React.createElement('div', { key: 'filters', style: { display: 'flex', gap: '8px' } }, [ // Instrument filter React.createElement('div', { key: 'inst', style: { display: 'flex', gap: '4px', background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: '8px', padding: '4px' } }, [['all','All'],['stock','Stock'],['option','Option']].map(([id, lbl]) => React.createElement('button', { key: id, onClick: () => setInstrument(id), style: { padding: '6px 12px', fontSize: '12px', fontWeight: instrument === id ? 600 : 400, borderRadius: '6px', border: 'none', cursor: 'pointer', fontFamily: 'Space Grotesk, sans-serif', background: instrument === id ? 'var(--border)' : 'transparent', color: instrument === id ? 'var(--text)' : 'var(--muted)', transition: 'all 0.15s' } }, lbl) ) ), // Period filter React.createElement('div', { key: 'periods', style: { display: 'flex', gap: '4px', background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: '8px', padding: '4px' } }, [['all','All Time'],['year','This Year'],['month','This Month'],['week','This Week']].map(([id, lbl]) => React.createElement('button', { key: id, onClick: () => setPeriod(id), style: { padding: '6px 14px', fontSize: '12px', fontWeight: period === id ? 600 : 400, borderRadius: '6px', border: 'none', cursor: 'pointer', fontFamily: 'Space Grotesk, sans-serif', background: period === id ? '#5588ff' : 'transparent', color: period === id ? '#fff' : 'var(--muted)', transition: 'all 0.15s' } }, lbl) ) ) ]) ]), // Row 1: 5 top stat cards React.createElement('div', { key: 'row1', style: { display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)', gap: '12px', marginBottom: '16px' } }, [ ['Total P&L', fmtPnL(stats.totalPnL), stats.totalPnL >= 0 ? '#00d27a' : '#f04060'], ['Win Rate', `${(stats.winRate * 100).toFixed(1)}%`, '#5588ff'], ['Avg R:R', `${stats.rr.toFixed(2)}x`, '#a064ff'], ['Avg Win', `+$${stats.avgWin.toFixed(0)}`, '#00d27a'], ['Avg Loss', `-$${stats.avgLoss.toFixed(0)}`, '#f04060'], ].map(([label, value, color]) => React.createElement('div', { key: label, style: { background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: '10px', padding: '16px 18px', borderTop: `2px solid ${color}` } }, [ React.createElement('div', { key: 'l', style: { fontSize: '11px', color: 'var(--muted)', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.07em', marginBottom: '6px' } }, label), React.createElement('div', { key: 'v', style: { fontSize: '22px', fontWeight: 700, color, fontFamily: 'JetBrains Mono, monospace', lineHeight: 1 } }, value) ]) ) ), // Row 2: Streak + Drawdown cards React.createElement('div', { key: 'row2', style: { display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '12px', marginBottom: '16px' } }, [ React.createElement('div', { key: 'mw', style: { background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: '10px', padding: '16px 18px', borderTop: '2px solid #00d27a' } }, [ React.createElement('div', { key: 'l', style: { fontSize: '11px', color: 'var(--muted)', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.07em', marginBottom: '6px' } }, 'Max Win Streak'), React.createElement('div', { key: 'v', style: { fontSize: '28px', fontWeight: 700, color: '#00d27a', fontFamily: 'JetBrains Mono, monospace', lineHeight: 1 } }, `${streaks.maxWin}`), React.createElement('div', { key: 's', style: { fontSize: '11px', color: 'var(--muted)', marginTop: '4px' } }, 'consecutive wins'), ]), React.createElement('div', { key: 'ml', style: { background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: '10px', padding: '16px 18px', borderTop: '2px solid #f04060' } }, [ React.createElement('div', { key: 'l', style: { fontSize: '11px', color: 'var(--muted)', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.07em', marginBottom: '6px' } }, 'Max Loss Streak'), React.createElement('div', { key: 'v', style: { fontSize: '28px', fontWeight: 700, color: '#f04060', fontFamily: 'JetBrains Mono, monospace', lineHeight: 1 } }, `${streaks.maxLoss}`), React.createElement('div', { key: 's', style: { fontSize: '11px', color: 'var(--muted)', marginTop: '4px' } }, 'consecutive losses'), ]), React.createElement('div', { key: 'cur', style: { background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: '10px', padding: '16px 18px', borderTop: `2px solid ${streaks.currentType ? '#00d27a' : '#f04060'}` } }, [ React.createElement('div', { key: 'l', style: { fontSize: '11px', color: 'var(--muted)', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.07em', marginBottom: '6px' } }, 'Current Streak'), React.createElement('div', { key: 'v', style: { fontSize: '28px', fontWeight: 700, color: streaks.currentType ? '#00d27a' : '#f04060', fontFamily: 'JetBrains Mono, monospace', lineHeight: 1 } }, `${streaks.currentStreak}`), React.createElement('div', { key: 's', style: { fontSize: '11px', color: 'var(--muted)', marginTop: '4px' } }, streaks.currentType ? 'winning' : 'losing'), ]), React.createElement('div', { key: 'dd', style: { background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: '10px', padding: '16px 18px', borderTop: '2px solid #ffb83f' } }, [ React.createElement('div', { key: 'l', style: { fontSize: '11px', color: 'var(--muted)', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.07em', marginBottom: '6px' } }, 'Max Drawdown'), React.createElement('div', { key: 'v', style: { fontSize: '22px', fontWeight: 700, color: '#ffb83f', fontFamily: 'JetBrains Mono, monospace', lineHeight: 1 } }, `-$${dd.maxDD.toFixed(0)}`), React.createElement('div', { key: 's', style: { fontSize: '11px', color: 'var(--muted)', marginTop: '4px' } }, `${dd.pct.toFixed(1)}% from peak`), ]), ]), // Row 3: Hold time stats React.createElement('div', { key: 'row-hold', style: { display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '12px', marginBottom: '16px' } }, [ [ ['Avg Hold Time', holdStats.avg, '#5588ff', 'all closed trades'], ['Max Hold Time', holdStats.max, '#a064ff', 'longest single trade'], ['Avg Hold — Winners', holdStats.avgWin, '#00d27a', 'winning trades only'], ['Avg Hold — Losers', holdStats.avgLoss, '#f04060', 'losing trades only'], ].map(([label, days, color, sub]) => React.createElement('div', { key: label, style: { background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: '10px', padding: '16px 18px', borderTop: `2px solid ${color}` } }, [ React.createElement('div', { key: 'l', style: { fontSize: '11px', color: 'var(--muted)', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.07em', marginBottom: '6px' } }, label), React.createElement('div', { key: 'v', style: { fontSize: '22px', fontWeight: 700, color, fontFamily: 'JetBrains Mono, monospace', lineHeight: 1 } }, days == null ? '—' : days < 1 ? '< 1d' : `${days.toFixed(1)}d` ), React.createElement('div', { key: 's', style: { fontSize: '11px', color: 'var(--muted)', marginTop: '4px' } }, sub) ]) ) ]), // Row 4: Equity + P&L Distribution React.createElement('div', { key: 'row3', style: { display: 'grid', gridTemplateColumns: '1fr 340px', gap: '16px', marginBottom: '16px' } }, [ card([ sectionTitle('Equity Curve'), React.createElement('svg', { key: 'svg', width: '100%', viewBox: `0 0 ${W} ${H}`, preserveAspectRatio: 'none', style: { display: 'block', height: '120px' } }, [ React.createElement('defs', { key: 'd' }, React.createElement('linearGradient', { id: 'statsGrad', x1: 0, y1: 0, x2: 0, y2: 1 }, [ React.createElement('stop', { key: 's1', offset: '0%', stopColor: lineColor, stopOpacity: 0.25 }), React.createElement('stop', { key: 's2', offset: '100%', stopColor: lineColor, stopOpacity: 0 }) ]) ), React.createElement('polygon', { key: 'a', points: `0,${H} ${eqPts} ${W},${H}`, fill: 'url(#statsGrad)' }), React.createElement('polyline', { key: 'l', points: eqPts, fill: 'none', stroke: lineColor, strokeWidth: 2, strokeLinejoin: 'round' }), (() => { const zeroY = H - ((0 - minV) / rangeV) * (H - 16) - 8; return React.createElement('line', { key: 'z', x1: 0, y1: zeroY, x2: W, y2: zeroY, stroke: 'var(--border)', strokeWidth: 1, strokeDasharray: '4 4' }); })() ]), React.createElement('div', { key: 'footer', style: { display: 'flex', justifyContent: 'space-between', marginTop: '10px', fontSize: '12px', color: 'var(--muted)' } }, [ React.createElement('span', { key: 'l' }, 'Start: $0.00'), React.createElement('span', { key: 'dd', style: { color: '#ffb83f' } }, `Max DD: -$${dd.maxDD.toFixed(0)}`), React.createElement('span', { key: 'r' }, `Now: ${fmtPnL(stats.totalPnL)}`) ]) ]), card([ sectionTitle('P&L Distribution'), React.createElement('div', { key: 'bars', style: { display: 'flex', flexDirection: 'column', gap: '8px' } }, distribution.map(b => { const color = b.min >= 0 ? '#00d27a' : '#f04060'; return React.createElement('div', { key: b.label, style: { display: 'flex', alignItems: 'center', gap: '8px' } }, [ React.createElement('span', { key: 'l', style: { fontSize: '11px', color: 'var(--muted)', width: '110px', textAlign: 'right', whiteSpace: 'nowrap' } }, b.label), React.createElement('div', { key: 'bar', style: { flex: 1, height: '6px', background: 'var(--surface2)', borderRadius: '3px' } }, React.createElement('div', { style: { height: '100%', width: `${(b.count / distMax) * 100}%`, background: color, borderRadius: '3px', transition: 'width 0.4s' } }) ), React.createElement('span', { key: 'v', style: { fontSize: '12px', color: 'var(--text2)', fontFamily: 'JetBrains Mono, monospace', width: '18px' } }, b.count) ]); }) ) ]) ]), // Row 4: Day of week + Hold time distribution React.createElement('div', { key: 'row4', style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px', marginBottom: '16px' } }, [ card([ sectionTitle('Win Rate by Day of Week'), React.createElement('div', { key: 'days', style: { display: 'flex', flexDirection: 'column', gap: '8px' } }, dowData.filter(d => d.day !== 'Sun' && d.day !== 'Sat').map(d => { const wr = d.winRate; const hasData = d.total > 0; const barColor = wr >= 0.6 ? '#00d27a' : wr >= 0.4 ? '#ffb83f' : '#f04060'; return React.createElement('div', { key: d.day, style: { display: 'flex', alignItems: 'center', gap: '10px' } }, [ React.createElement('span', { key: 'l', style: { fontSize: '12px', color: 'var(--muted)', fontWeight: 600, width: '36px' } }, d.day), React.createElement('div', { key: 'bar', style: { flex: 1, height: '8px', background: 'var(--surface2)', borderRadius: '4px', overflow: 'hidden' } }, hasData && React.createElement('div', { style: { height: '100%', width: `${wr * 100}%`, background: barColor, borderRadius: '4px', transition: 'width 0.4s' } }) ), React.createElement('span', { key: 'wr', style: { fontSize: '12px', color: hasData ? barColor : 'var(--dim)', fontFamily: 'JetBrains Mono, monospace', width: '38px' } }, hasData ? `${(wr * 100).toFixed(0)}%` : '—' ), React.createElement('span', { key: 'pnl', style: { fontSize: '12px', fontFamily: 'JetBrains Mono, monospace', color: d.pnl >= 0 ? '#00d27a' : '#f04060', width: '70px', textAlign: 'right' } }, hasData ? fmtPnL(d.pnl) : '' ), React.createElement('span', { key: 'cnt', style: { fontSize: '11px', color: 'var(--dim)', width: '40px', textAlign: 'right' } }, hasData ? `${d.wins}W ${d.losses}L` : '' ) ]); }) ) ]), card([ sectionTitle('Hold Time Distribution'), React.createElement('div', { key: 'bars', style: { display: 'flex', flexDirection: 'column', gap: '10px' } }, holdDist.map(b => React.createElement('div', { key: b.label, style: { display: 'flex', alignItems: 'center', gap: '10px' } }, [ React.createElement('span', { key: 'l', style: { fontSize: '12px', color: 'var(--muted)', width: '90px', whiteSpace: 'nowrap' } }, b.label), React.createElement('div', { key: 'bar', style: { flex: 1, height: '8px', background: 'var(--surface2)', borderRadius: '4px', overflow: 'hidden' } }, React.createElement('div', { style: { height: '100%', width: `${(b.count / holdMax) * 100}%`, background: '#5588ff', borderRadius: '4px', transition: 'width 0.4s' } }) ), React.createElement('span', { key: 'v', style: { fontSize: '12px', color: 'var(--text2)', fontFamily: 'JetBrains Mono, monospace', width: '18px' } }, b.count) ])) ) ]) ]), // Row 5: Signal + Symbol React.createElement('div', { key: 'row5', style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px', marginBottom: '16px' } }, [ card([ sectionTitle('Signal Performance'), React.createElement('div', { key: 'hdr-row', style: { display: 'grid', gridTemplateColumns: '1fr 60px 60px 80px', gap: '8px', marginBottom: '8px', paddingBottom: '8px', borderBottom: '1px solid var(--surface2)' } }, ['Signal','Trades','Win%','P&L'].map(h => React.createElement('span', { key: h, style: { fontSize: '11px', color: 'var(--muted)', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.06em' } }, h)) ), React.createElement('div', { key: 'rows', style: { display: 'flex', flexDirection: 'column', gap: '2px' } }, signals.map(s => React.createElement('div', { key: s.name, style: { display: 'grid', gridTemplateColumns: '1fr 60px 60px 80px', gap: '8px', padding: '7px 0', borderBottom: '1px solid #0a0d14', alignItems: 'center' } }, [ React.createElement('span', { key: 'n', style: { fontSize: '12px', color: 'var(--text)' } }, s.name), React.createElement('span', { key: 't', style: { fontSize: '12px', color: 'var(--muted)', fontFamily: 'JetBrains Mono, monospace' } }, s.trades), React.createElement('span', { key: 'w', style: { fontSize: '12px', color: s.winRate >= 0.6 ? '#00d27a' : s.winRate >= 0.4 ? '#ffb83f' : '#f04060', fontFamily: 'JetBrains Mono, monospace' } }, `${(s.winRate * 100).toFixed(0)}%`), React.createElement(PnLValue, { key: 'p', value: s.pnl }) ])) ) ]), card([ sectionTitle('Symbol Breakdown'), React.createElement('div', { key: 'hdr-row', style: { display: 'grid', gridTemplateColumns: '80px 60px 60px 80px', gap: '8px', marginBottom: '8px', paddingBottom: '8px', borderBottom: '1px solid var(--surface2)' } }, ['Symbol','Trades','Win%','Net P&L'].map(h => React.createElement('span', { key: h, style: { fontSize: '11px', color: 'var(--muted)', fontWeight: 600, textTransform: 'uppercase', letterSpacing: '0.06em' } }, h)) ), React.createElement('div', { key: 'rows', style: { display: 'flex', flexDirection: 'column', gap: '2px' } }, symbols.map(s => React.createElement('div', { key: s.sym, style: { display: 'grid', gridTemplateColumns: '80px 60px 60px 80px', gap: '8px', padding: '7px 0', borderBottom: '1px solid #0a0d14', alignItems: 'center' } }, [ React.createElement(SymbolCell, { key: 'sym', symbol: s.sym }), React.createElement('span', { key: 't', style: { fontSize: '12px', color: 'var(--muted)', fontFamily: 'JetBrains Mono, monospace' } }, s.trades), React.createElement('span', { key: 'w', style: { fontSize: '12px', color: s.winRate >= 0.6 ? '#00d27a' : s.winRate >= 0.4 ? '#ffb83f' : '#f04060', fontFamily: 'JetBrains Mono, monospace' } }, `${(s.winRate * 100).toFixed(0)}%`), React.createElement(PnLValue, { key: 'p', value: s.pnl }) ])) ) ]) ]), // Row 6: Best / Worst trades React.createElement('div', { key: 'row6', style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px' } }, [ card([ sectionTitle('Best Trades'), React.createElement('div', { key: 'rows', style: { display: 'flex', flexDirection: 'column', gap: '8px' } }, best3.map((t, i) => React.createElement('div', { key: t.id, style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', background: 'rgba(0,210,122,0.06)', border: '1px solid rgba(0,210,122,0.15)', borderRadius: '6px', padding: '10px 14px' } }, [ React.createElement('div', { key: 'l', style: { display: 'flex', alignItems: 'center', gap: '10px' } }, [ React.createElement('span', { key: 'rank', style: { fontSize: '11px', color: 'var(--muted)', fontFamily: 'JetBrains Mono, monospace', width: '16px' } }, `#${i+1}`), React.createElement(SymbolCell, { key: 'sym', symbol: t.symbol }), React.createElement(TypeBadge, { key: 'type', type: t.type, optionType: t.optionType, strategy: t.strategy }), React.createElement('span', { key: 'date', style: { fontSize: '11px', color: 'var(--muted)' } }, fmtDate(t.date)) ]), React.createElement('div', { key: 'r', style: { display: 'flex', alignItems: 'center', gap: '10px' } }, [ React.createElement(RValue, { key: 'r', value: t.rMultiple }), React.createElement(PnLValue, { key: 'p', value: t.pnl }) ]) ]) ) ) ]), card([ sectionTitle('Worst Trades'), React.createElement('div', { key: 'rows', style: { display: 'flex', flexDirection: 'column', gap: '8px' } }, worst3.map((t, i) => React.createElement('div', { key: t.id, style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', background: 'rgba(240,64,96,0.06)', border: '1px solid rgba(240,64,96,0.15)', borderRadius: '6px', padding: '10px 14px' } }, [ React.createElement('div', { key: 'l', style: { display: 'flex', alignItems: 'center', gap: '10px' } }, [ React.createElement('span', { key: 'rank', style: { fontSize: '11px', color: 'var(--muted)', fontFamily: 'JetBrains Mono, monospace', width: '16px' } }, `#${i+1}`), React.createElement(SymbolCell, { key: 'sym', symbol: t.symbol }), React.createElement(TypeBadge, { key: 'type', type: t.type, optionType: t.optionType, strategy: t.strategy }), React.createElement('span', { key: 'date', style: { fontSize: '11px', color: 'var(--muted)' } }, fmtDate(t.date)) ]), React.createElement('div', { key: 'r', style: { display: 'flex', alignItems: 'center', gap: '10px' } }, [ React.createElement(RValue, { key: 'r', value: t.rMultiple }), React.createElement(PnLValue, { key: 'p', value: t.pnl }) ]) ]) ) ) ]) ]) ]); }; Object.assign(window, { StatsPage });