// tradeData.jsx — sample data and shared utilities const OPTION_STRATEGIES = [ { id: 'single', label: 'Single', shortLabel: 'SINGLE', desc: 'One call or put' }, { id: 'bull_call_spread', label: 'Bull Call Spread', shortLabel: 'CALL SPD', desc: 'Buy lower call · Sell higher call' }, { id: 'bear_put_spread', label: 'Bear Put Spread', shortLabel: 'PUT SPD', desc: 'Buy higher put · Sell lower put' }, { id: 'straddle', label: 'Straddle', shortLabel: 'STRDL', desc: 'Buy call + put · same strike/expiry' }, { id: 'strangle', label: 'Strangle', shortLabel: 'STRNGL', desc: 'Buy call + put · different strikes' }, { id: 'calendar', label: 'Calendar Spread', shortLabel: 'CAL SPD', desc: 'Same strike · different expiries' }, { id: 'synthetic', label: 'Synthetic Long', shortLabel: 'SYNTH', desc: 'Sell put + Buy call · same strike' }, { id: 'custom', label: 'Custom Combo', shortLabel: 'COMBO', desc: 'Build your own multi-leg trade' }, ]; const STRATEGY_LEG_TEMPLATES = { single: [{ legType: 'long', optionType: 'call', hint: 'Leg' }], bull_call_spread: [{ legType: 'long', optionType: 'call', hint: 'Buy Call · lower strike' }, { legType: 'short', optionType: 'call', hint: 'Sell Call · higher strike' }], bear_put_spread: [{ legType: 'long', optionType: 'put', hint: 'Buy Put · higher strike' }, { legType: 'short', optionType: 'put', hint: 'Sell Put · lower strike' }], straddle: [{ legType: 'long', optionType: 'call', hint: 'Buy Call · ATM' }, { legType: 'long', optionType: 'put', hint: 'Buy Put · same strike' }], strangle: [{ legType: 'long', optionType: 'call', hint: 'Buy Call · OTM' }, { legType: 'long', optionType: 'put', hint: 'Buy Put · OTM lower strike' }], calendar: [{ legType: 'long', optionType: 'call', hint: 'Buy · far expiry' }, { legType: 'short', optionType: 'call', hint: 'Sell · near expiry · same strike' }], synthetic: [{ legType: 'short', optionType: 'put', hint: 'Sell Put · obligation to buy at strike' }, { legType: 'long', optionType: 'call', hint: 'Buy Call · same strike/expiry' }], custom: [], }; const PREDEFINED_LABELS = [ 'Tech', 'AI', 'Index', 'Macro', 'Earnings', 'Energy', 'Financials', 'Swing', 'Day Trade', 'Scalp', 'Position', 'Overnight', 'Momentum', 'Mean Reversion' ]; // Default labels per symbol applied after array definition const SYMBOL_DEFAULT_LABELS = { AAPL: ['Tech', 'Swing'], TSLA: ['Tech', 'Momentum'], NVDA: ['Tech', 'AI'], SPY: ['Index', 'Macro'], META: ['Tech', 'Earnings'], }; const PREDEFINED_SIGNALS = [ 'Breakout', 'VWAP Bounce', 'Gap Fill', 'EMA Cross', 'Support Bounce', 'Resistance Break', 'Momentum', 'Reversal', 'News Play', 'Earnings', 'Opening Range', 'Pullback' ]; const SAMPLE_TRADES = [ { id: 't1', date: '2026-02-03', symbol: 'AAPL', type: 'stock', direction: 'long', quantity: 200, entryPrice: 228.50, stopLoss: 225.00, exits: [ { id: 'e1', price: 235.00, quantity: 100, date: '2026-02-04' }, { id: 'e2', price: 238.50, quantity: 100, date: '2026-02-05' } ], signals: ['Breakout', 'EMA Cross'], customSignal: '', notes: 'Clean breakout above 228 resistance on high volume. Scaled out at two targets.', snapshot: null, status: 'closed', pnl: 1650, rMultiple: 2.36 }, { id: 't2', date: '2026-02-06', symbol: 'TSLA', type: 'stock', direction: 'long', quantity: 50, entryPrice: 320.00, stopLoss: 312.00, exits: [{ id: 'e3', price: 315.00, quantity: 50, date: '2026-02-07' }], signals: ['Momentum'], customSignal: '', notes: 'Momentum trade failed. Price reversed immediately after entry.', snapshot: null, status: 'closed', pnl: -250, rMultiple: -0.63 }, { id: 't3', date: '2026-02-10', symbol: 'SPY', type: 'option', direction: 'long', optionType: 'call', strike: 590, expiry: '2026-02-21', contracts: 5, premium: 3.20, stopLoss: null, exits: [{ id: 'e4', price: 6.80, contracts: 5, date: '2026-02-12' }], signals: ['Breakout', 'Support Bounce'], customSignal: '', notes: 'SPY breakout play. Held overnight and sold into morning gap up.', snapshot: null, status: 'closed', pnl: 1800, rMultiple: 1.13 }, { id: 't4', date: '2026-02-13', symbol: 'NVDA', type: 'stock', direction: 'short', quantity: 30, entryPrice: 875.00, stopLoss: 885.00, exits: [{ id: 'e5', price: 855.00, quantity: 30, date: '2026-02-14' }], signals: ['Resistance Break', 'Reversal'], customSignal: 'Overbought RSI', notes: 'Short at key resistance level. RSI divergence on 15m chart.', snapshot: null, status: 'closed', pnl: 600, rMultiple: 2.0 }, { id: 't5', date: '2026-02-18', symbol: 'META', type: 'stock', direction: 'long', quantity: 40, entryPrice: 612.00, stopLoss: 605.00, exits: [{ id: 'e6', price: 608.00, quantity: 40, date: '2026-02-19' }], signals: ['Gap Fill'], customSignal: '', notes: 'Gap fill attempt failed. Stopped out quickly.', snapshot: null, status: 'closed', pnl: -160, rMultiple: -0.57 }, { id: 't6', date: '2026-02-20', symbol: 'SPY', type: 'option', direction: 'long', optionType: 'put', strike: 585, expiry: '2026-02-28', contracts: 4, premium: 2.50, stopLoss: null, exits: [{ id: 'e7', price: 4.88, contracts: 4, date: '2026-02-21' }], signals: ['Reversal', 'VWAP Bounce'], customSignal: 'FOMC Day', notes: 'FOMC day puts. Sold into the volatility spike.', snapshot: null, status: 'closed', pnl: 952, rMultiple: 0.95 }, { id: 't7', date: '2026-02-24', symbol: 'AAPL', type: 'option', direction: 'long', optionType: 'call', strike: 235, expiry: '2026-02-28', contracts: 4, premium: 1.00, stopLoss: null, exits: [{ id: 'e8', price: 0.05, contracts: 4, date: '2026-02-28' }], signals: ['Momentum'], customSignal: '', notes: 'Weekly calls expired nearly worthless. Should have cut early.', snapshot: null, status: 'closed', pnl: -380, rMultiple: -0.95 }, { id: 't8', date: '2026-02-27', symbol: 'TSLA', type: 'stock', direction: 'long', quantity: 60, entryPrice: 335.00, stopLoss: 327.00, exits: [ { id: 'e9', price: 345.00, quantity: 30, date: '2026-02-28' }, { id: 'e10', price: 355.00, quantity: 30, date: '2026-03-03' } ], signals: ['Opening Range', 'Momentum'], customSignal: '', notes: 'Strong open. Held partial for extended move.', snapshot: null, status: 'closed', pnl: 1200, rMultiple: 2.5 }, { id: 't9', date: '2026-03-04', symbol: 'NVDA', type: 'stock', direction: 'long', quantity: 40, entryPrice: 890.00, stopLoss: 882.00, exits: [ { id: 'e11', price: 905.00, quantity: 20, date: '2026-03-05' }, { id: 'e12', price: 918.00, quantity: 20, date: '2026-03-06' } ], signals: ['Breakout', 'EMA Cross'], customSignal: '', notes: 'NVDA weekly breakout. Scaled into strength.', snapshot: null, status: 'closed', pnl: 860, rMultiple: 2.69 }, { id: 't10', date: '2026-03-07', symbol: 'SPY', type: 'option', direction: 'long', optionType: 'call', strike: 595, expiry: '2026-03-14', contracts: 3, premium: 1.00, stopLoss: null, exits: [{ id: 'e13', price: 0.00, contracts: 3, date: '2026-03-14' }], signals: ['Momentum'], customSignal: '', notes: 'Market pulled back hard. Calls expired worthless.', snapshot: null, status: 'closed', pnl: -300, rMultiple: -1.0 }, { id: 't11', date: '2026-03-11', symbol: 'AAPL', type: 'stock', direction: 'long', quantity: 100, entryPrice: 226.00, stopLoss: 222.00, exits: [{ id: 'e14', price: 230.80, quantity: 100, date: '2026-03-12' }], signals: ['Support Bounce', 'VWAP Bounce'], customSignal: '', notes: 'Bounce off key support. Clean exit at R1.', snapshot: null, status: 'closed', pnl: 480, rMultiple: 1.2 }, { id: 't12', date: '2026-03-14', symbol: 'META', type: 'option', direction: 'long', optionType: 'put', strike: 595, expiry: '2026-03-28', contracts: 5, premium: 1.20, stopLoss: null, exits: [{ id: 'e15', price: 4.00, contracts: 5, date: '2026-03-18' }], signals: ['Resistance Break', 'Reversal'], customSignal: 'Breakdown', notes: 'META breakdown through key support. Quick 3R move.', snapshot: null, status: 'closed', pnl: 1400, rMultiple: 2.33 }, { id: 't13', date: '2026-03-17', symbol: 'TSLA', type: 'stock', direction: 'short', quantity: 50, entryPrice: 342.00, stopLoss: 349.00, exits: [{ id: 'e16', price: 349.00, quantity: 50, date: '2026-03-18' }], signals: ['Reversal'], customSignal: '', notes: 'Short thesis was wrong. Stopped out at stop.', snapshot: null, status: 'closed', pnl: -350, rMultiple: -1.0 }, { id: 't14', date: '2026-03-20', symbol: 'SPY', type: 'option', direction: 'long', optionType: 'call', strike: 588, expiry: '2026-03-28', contracts: 5, premium: 1.50, stopLoss: null, exits: [{ id: 'e17', price: 2.70, contracts: 5, date: '2026-03-24' }], signals: ['Support Bounce', 'Opening Range'], customSignal: '', notes: 'Quad witching week bounce off lows.', snapshot: null, status: 'closed', pnl: 600, rMultiple: 0.80 }, { id: 't15', date: '2026-03-25', symbol: 'NVDA', type: 'option', direction: 'long', optionType: 'call', strike: 920, expiry: '2026-04-04', contracts: 8, premium: 2.50, stopLoss: null, exits: [ { id: 'e18', price: 5.50, contracts: 4, date: '2026-03-27' }, { id: 'e19', price: 8.20, contracts: 4, date: '2026-03-28' } ], signals: ['Breakout', 'Momentum', 'EMA Cross'], customSignal: '', notes: 'NVDA breakout to all-time highs. Scaled out perfectly.', snapshot: null, status: 'closed', pnl: 2440, rMultiple: 3.22 }, { id: 't16', date: '2026-03-28', symbol: 'AAPL', type: 'stock', direction: 'long', quantity: 100, entryPrice: 232.00, stopLoss: 228.00, exits: [{ id: 'e20', price: 230.00, quantity: 100, date: '2026-03-31' }], signals: ['Pullback'], customSignal: '', notes: 'Pullback entry but market continued lower.', snapshot: null, status: 'closed', pnl: -200, rMultiple: -0.5 }, { id: 't17', date: '2026-04-02', symbol: 'SPY', type: 'option', direction: 'long', optionType: 'put', strike: 580, expiry: '2026-04-11', contracts: 6, premium: 1.80, stopLoss: null, exits: [{ id: 'e21', price: 3.62, contracts: 6, date: '2026-04-04' }], signals: ['Reversal', 'Resistance Break'], customSignal: 'Macro Selloff', notes: 'Tariff news catalyst. Strong put move into close.', snapshot: null, status: 'closed', pnl: 1092, rMultiple: 1.01 }, { id: 't18', date: '2026-04-07', symbol: 'TSLA', type: 'stock', direction: 'long', quantity: 75, entryPrice: 230.00, stopLoss: 222.00, exits: [{ id: 'e22', price: 242.00, quantity: 75, date: '2026-04-09' }], signals: ['Support Bounce', 'Reversal'], customSignal: '', notes: 'Oversold bounce after macro selloff. Clean R:R.', snapshot: null, status: 'closed', pnl: 900, rMultiple: 1.5 }, { id: 't19', date: '2026-04-10', symbol: 'META', type: 'option', direction: 'long', optionType: 'call', strike: 580, expiry: '2026-04-17', contracts: 4, premium: 2.50, stopLoss: null, exits: [{ id: 'e23', price: 1.25, contracts: 4, date: '2026-04-14' }], signals: ['Momentum'], customSignal: '', notes: 'Bad entry. Market rolled over again.', snapshot: null, status: 'closed', pnl: -500, rMultiple: -0.50 }, { id: 't20', date: '2026-04-15', symbol: 'NVDA', type: 'stock', direction: 'long', quantity: 50, entryPrice: 848.00, stopLoss: 840.00, exits: [ { id: 'e24', price: 860.00, quantity: 25, date: '2026-04-16' }, { id: 'e25', price: 880.00, quantity: 25, date: '2026-04-17' } ], signals: ['Support Bounce', 'EMA Cross'], customSignal: '', notes: 'NVDA recovering from macro lows. Clean structure.', snapshot: null, status: 'closed', pnl: 1300, rMultiple: 2.75 }, { id: 't21', date: '2026-04-18', symbol: 'SPY', type: 'option', direction: 'long', optionType: 'call', strike: 535, expiry: '2026-04-25', contracts: 10, premium: 1.20, stopLoss: null, exits: [{ id: 'e26', price: 1.80, contracts: 10, date: '2026-04-21' }], signals: ['Support Bounce', 'Opening Range'], customSignal: '', notes: 'Bounce play. Took quick profit.', snapshot: null, status: 'closed', pnl: 600, rMultiple: 0.50 }, { id: 't22', date: '2026-04-22', symbol: 'AAPL', type: 'stock', direction: 'long', quantity: 150, entryPrice: 192.50, stopLoss: 188.00, exits: [], signals: ['Support Bounce', 'Pullback'], customSignal: '', notes: 'Buying the dip at major support. Still holding.', snapshot: null, status: 'open', pnl: null, rMultiple: null }, { id: 't23', date: '2026-03-10', symbol: 'SPY', type: 'option', direction: 'long', strategy: 'bull_call_spread', legs: [ { id: 'l1', legType: 'long', optionType: 'call', strike: 580, expiry: '2026-03-21', contracts: 5, premium: 3.50 }, { id: 'l2', legType: 'short', optionType: 'call', strike: 590, expiry: '2026-03-21', contracts: 5, premium: 1.80 }, ], optionType: 'call', strike: 580, expiry: '2026-03-21', contracts: 5, premium: 1.70, stopLoss: null, exits: [{ id: 'ex1', price: 2.40, contracts: 5, date: '2026-03-14' }], signals: ['Breakout', 'Momentum'], customSignal: '', notes: '580/590 bull call spread. Net debit $1.70/contract. Took profits at $2.40.', snapshot: null, status: 'closed', pnl: 350, rMultiple: 1.24 }, { id: 't24', date: '2026-04-16', symbol: 'NVDA', type: 'option', direction: 'long', strategy: 'straddle', legs: [ { id: 'l3', legType: 'long', optionType: 'call', strike: 870, expiry: '2026-04-25', contracts: 3, premium: 5.20 }, { id: 'l4', legType: 'long', optionType: 'put', strike: 870, expiry: '2026-04-25', contracts: 3, premium: 4.80 }, ], optionType: 'call', strike: 870, expiry: '2026-04-25', contracts: 3, premium: 10.00, stopLoss: null, exits: [{ id: 'ex2', price: 14.50, contracts: 3, date: '2026-04-18' }], signals: ['Earnings', 'Momentum'], customSignal: '', notes: 'NVDA 870 straddle ahead of earnings. Big move expected. Call side ran.', snapshot: null, status: 'closed', pnl: 1350, rMultiple: 1.50 } ]; // Utility functions const fmtPrice = (v) => v == null ? '—' : '$' + Number(v).toFixed(2); const fmtPnL = (v) => { if (v == null) return '—'; const sign = v >= 0 ? '+' : ''; return sign + '$' + Math.abs(v).toFixed(2); }; const fmtR = (v) => { if (v == null) return '—'; const sign = v >= 0 ? '+' : ''; return sign + Number(v).toFixed(2) + 'R'; }; const fmtDate = (d) => { if (!d) return '—'; const dt = new Date(d + 'T00:00:00'); return dt.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: '2-digit' }); }; const genId = () => Math.random().toString(36).substr(2, 9); const computeStats = (trades) => { const closed = trades.filter(t => t.status === 'closed'); const wins = closed.filter(t => t.pnl > 0); const losses = closed.filter(t => t.pnl < 0); const totalPnL = closed.reduce((s, t) => s + (t.pnl || 0), 0); const winRate = closed.length > 0 ? wins.length / closed.length : 0; const avgWin = wins.length > 0 ? wins.reduce((s, t) => s + t.pnl, 0) / wins.length : 0; const avgLoss = losses.length > 0 ? Math.abs(losses.reduce((s, t) => s + t.pnl, 0) / losses.length) : 0; const rr = avgLoss > 0 ? avgWin / avgLoss : 0; const avgR = closed.filter(t => t.rMultiple != null).reduce((s, t) => s + t.rMultiple, 0) / (closed.filter(t => t.rMultiple != null).length || 1); return { totalPnL, winRate, wins: wins.length, losses: losses.length, total: closed.length, avgWin, avgLoss, rr, avgR }; }; const getEquityCurve = (trades) => { const closed = [...trades.filter(t => t.status === 'closed')] .sort((a, b) => a.date.localeCompare(b.date)); let cum = 0; return [{ date: null, value: 0 }, ...closed.map(t => { cum += t.pnl || 0; return { date: t.date, value: cum }; })]; }; const getPnLByMonth = (trades) => { const closed = trades.filter(t => t.status === 'closed'); const map = {}; closed.forEach(t => { const key = t.date.slice(0, 7); if (!map[key]) map[key] = { pnl: 0, wins: 0, losses: 0 }; map[key].pnl += t.pnl || 0; if (t.pnl > 0) map[key].wins++; else map[key].losses++; }); return Object.entries(map).sort((a, b) => a[0].localeCompare(b[0])) .map(([month, v]) => ({ month, ...v })); }; const getPnLByDate = (trades) => { const map = {}; trades.filter(t => t.status === 'closed').forEach(t => { if (!map[t.date]) map[t.date] = 0; map[t.date] += t.pnl || 0; }); return map; }; const filterByPeriod = (trades, period) => { if (period === 'all') return trades; const now = new Date(); const starts = { year: new Date(now.getFullYear(), 0, 1), month: new Date(now.getFullYear(), now.getMonth(), 1), week: (() => { const d = new Date(now); d.setDate(d.getDate() - d.getDay()); d.setHours(0,0,0,0); return d; })() }; const start = starts[period]; return trades.filter(t => new Date(t.date + 'T00:00:00') >= start); }; const calcAvgHoldTime = (trades) => { const valid = trades.filter(t => t.status === 'closed' && t.exits.some(e => e.date)); if (!valid.length) return null; const days = valid.map(t => { const entry = new Date(t.date + 'T00:00:00'); const exitDates = t.exits.filter(e => e.date).map(e => new Date(e.date + 'T00:00:00')); const lastExit = exitDates.reduce((a, b) => b > a ? b : a, entry); return Math.max(0, (lastExit - entry) / 86400000); }); return days.reduce((s, d) => s + d, 0) / days.length; }; const fmtHoldTime = (days) => { if (days == null) return '—'; if (days < 1) return '< 1 day'; if (days < 1.5) return '1 day'; return `${days.toFixed(1)} days`; }; // Auto-assign labels to sample trades that don't have them SAMPLE_TRADES.forEach(t => { if (!t.labels) t.labels = SYMBOL_DEFAULT_LABELS[t.symbol] || []; }); const computeMaxDrawdown = (trades) => { const sorted = [...trades.filter(t => t.status === 'closed')].sort((a,b) => a.date.localeCompare(b.date)); let peak = 0, maxDD = 0, cum = 0; sorted.forEach(t => { cum += t.pnl || 0; if (cum > peak) peak = cum; const dd = peak - cum; if (dd > maxDD) maxDD = dd; }); return { maxDD, pct: peak > 0 ? (maxDD / peak) * 100 : 0 }; }; const computeStreaks = (trades) => { const sorted = [...trades.filter(t => t.status === 'closed')].sort((a,b) => a.date.localeCompare(b.date)); let curWin = 0, curLoss = 0, maxWin = 0, maxLoss = 0, currentStreak = 0, currentType = null; sorted.forEach(t => { const isWin = (t.pnl || 0) > 0; if (isWin) { curWin++; curLoss = 0; if (curWin > maxWin) maxWin = curWin; } else { curLoss++; curWin = 0; if (curLoss > maxLoss) maxLoss = curLoss; } if (currentType === null) { currentType = isWin; currentStreak = 1; } else if (isWin === currentType) currentStreak++; else { currentType = isWin; currentStreak = 1; } }); return { maxWin, maxLoss, currentStreak, currentType }; }; const computeDayOfWeek = (trades) => { const days = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat']; const map = {}; days.forEach(d => { map[d] = { wins: 0, losses: 0, pnl: 0 }; }); trades.filter(t => t.status === 'closed').forEach(t => { const dow = days[new Date(t.date + 'T00:00:00').getDay()]; map[dow].pnl += t.pnl || 0; if ((t.pnl || 0) > 0) map[dow].wins++; else map[dow].losses++; }); return days.map(d => ({ day: d, ...map[d], total: map[d].wins + map[d].losses, winRate: (map[d].wins + map[d].losses) > 0 ? map[d].wins / (map[d].wins + map[d].losses) : null })); }; const computeHoldTimeDistribution = (trades) => { const buckets = [ { label: 'Same Day', min: 0, max: 0.99 }, { label: '1–2 Days', min: 1, max: 2 }, { label: '3–5 Days', min: 3, max: 5 }, { label: '1–2 Weeks', min: 6, max: 14 }, { label: '2+ Weeks', min: 15, max: Infinity }, ]; const closed = trades.filter(t => t.status === 'closed' && t.exits.some(e => e.date)); return buckets.map(b => { const count = closed.filter(t => { const days = (new Date(t.exits.find(e=>e.date)?.date+'T00:00:00') - new Date(t.date+'T00:00:00')) / 86400000; return days >= b.min && days <= b.max; }).length; return { ...b, count }; }); }; const computeHoldStats = (trades) => { const getHoldDays = (t) => { if (t.status !== 'closed' || !t.exits.some(e => e.date)) return null; const entry = new Date(t.date + 'T00:00:00'); const exitDates = t.exits.filter(e => e.date).map(e => new Date(e.date + 'T00:00:00')); const lastExit = exitDates.reduce((a, b) => b > a ? b : a, entry); return Math.max(0, (lastExit - entry) / 86400000); }; const closed = trades.filter(t => t.status === 'closed'); const holdDays = closed.map(t => ({ days: getHoldDays(t), pnl: t.pnl || 0 })).filter(x => x.days != null); const wins = holdDays.filter(x => x.pnl > 0); const losses = holdDays.filter(x => x.pnl <= 0); const avg = (arr) => arr.length > 0 ? arr.reduce((s, x) => s + x.days, 0) / arr.length : null; const max = holdDays.length > 0 ? Math.max(...holdDays.map(x => x.days)) : null; return { avg: avg(holdDays), max, avgWin: avg(wins), avgLoss: avg(losses), }; }; Object.assign(window, { PREDEFINED_SIGNALS, PREDEFINED_LABELS, SAMPLE_TRADES, OPTION_STRATEGIES, STRATEGY_LEG_TEMPLATES, fmtPrice, fmtPnL, fmtR, fmtDate, genId, fmtHoldTime, computeStats, getEquityCurve, getPnLByMonth, getPnLByDate, filterByPeriod, calcAvgHoldTime, computeMaxDrawdown, computeStreaks, computeDayOfWeek, computeHoldTimeDistribution, computeHoldStats });