// 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
});
