// data.jsx — simulated stock data + pattern detection

const TICKERS = [
  // ── MEGA CAP TECH ──
  { sym: 'NVDA', name: 'NVIDIA CORP',          px: 892.40, sec: 'SEMI'   },
  { sym: 'AAPL', name: 'APPLE INC',            px: 184.32, sec: 'TECH'   },
  { sym: 'MSFT', name: 'MICROSOFT CORP',       px: 412.65, sec: 'TECH'   },
  { sym: 'GOOG', name: 'ALPHABET INC',         px: 156.04, sec: 'TECH'   },
  { sym: 'GOOGL',name: 'ALPHABET CL A',        px: 154.78, sec: 'TECH'   },
  { sym: 'META', name: 'META PLATFORMS',       px: 502.71, sec: 'TECH'   },
  { sym: 'AMZN', name: 'AMAZON.COM INC',       px: 178.92, sec: 'ECOM'   },
  { sym: 'TSLA', name: 'TESLA INC',            px: 247.18, sec: 'AUTO'   },
  { sym: 'BRK.B',name: 'BERKSHIRE HATHAWAY',   px: 412.30, sec: 'FIN'    },
  // ── SEMICONDUCTORS ──
  { sym: 'AMD',  name: 'ADVANCED MICRO DEV',   px: 168.55, sec: 'SEMI'   },
  { sym: 'AVGO', name: 'BROADCOM INC',         px:1342.10, sec: 'SEMI'   },
  { sym: 'INTC', name: 'INTEL CORP',           px:  31.45, sec: 'SEMI'   },
  { sym: 'QCOM', name: 'QUALCOMM INC',         px: 168.20, sec: 'SEMI'   },
  { sym: 'TXN',  name: 'TEXAS INSTRUMENTS',    px: 178.50, sec: 'SEMI'   },
  { sym: 'MU',   name: 'MICRON TECHNOLOGY',    px: 102.30, sec: 'SEMI'   },
  { sym: 'AMAT', name: 'APPLIED MATERIALS',    px: 198.40, sec: 'SEMI'   },
  { sym: 'LRCX', name: 'LAM RESEARCH',         px: 920.50, sec: 'SEMI'   },
  { sym: 'KLAC', name: 'KLA CORP',             px: 698.20, sec: 'SEMI'   },
  { sym: 'MRVL', name: 'MARVELL TECH',         px:  72.18, sec: 'SEMI'   },
  // ── SOFTWARE / TECH ──
  { sym: 'ORCL', name: 'ORACLE CORP',          px: 138.40, sec: 'TECH'   },
  { sym: 'CRM',  name: 'SALESFORCE INC',       px: 268.50, sec: 'TECH'   },
  { sym: 'ADBE', name: 'ADOBE INC',            px: 521.40, sec: 'TECH'   },
  { sym: 'NOW',  name: 'SERVICENOW INC',       px: 745.20, sec: 'TECH'   },
  { sym: 'INTU', name: 'INTUIT INC',           px: 612.80, sec: 'TECH'   },
  { sym: 'PLTR', name: 'PALANTIR TECH',        px:  24.81, sec: 'TECH'   },
  { sym: 'SNOW', name: 'SNOWFLAKE INC',        px: 158.40, sec: 'TECH'   },
  { sym: 'PANW', name: 'PALO ALTO NETWORKS',   px: 312.50, sec: 'TECH'   },
  { sym: 'CRWD', name: 'CROWDSTRIKE',          px: 295.40, sec: 'TECH'   },
  { sym: 'NET',  name: 'CLOUDFLARE INC',       px:  84.50, sec: 'TECH'   },
  { sym: 'DDOG', name: 'DATADOG INC',          px: 124.20, sec: 'TECH'   },
  { sym: 'WDAY', name: 'WORKDAY INC',          px: 248.80, sec: 'TECH'   },
  { sym: 'IBM',  name: 'IBM CORP',             px: 195.40, sec: 'TECH'   },
  { sym: 'CSCO', name: 'CISCO SYSTEMS',        px:  49.85, sec: 'TECH'   },
  // ── CLEAN ENERGY / SOLAR ──
  { sym: 'SEDG', name: 'SOLAREDGE TECH',       px:  23.40, sec: 'SOLAR'  },
  { sym: 'ENPH', name: 'ENPHASE ENERGY',       px:  98.20, sec: 'SOLAR'  },
  { sym: 'FSLR', name: 'FIRST SOLAR INC',      px: 178.40, sec: 'SOLAR'  },
  { sym: 'RUN',  name: 'SUNRUN INC',           px:  12.85, sec: 'SOLAR'  },
  { sym: 'NEE',  name: 'NEXTERA ENERGY',       px:  72.40, sec: 'UTIL'   },
  // ── E-COMMERCE / RETAIL ──
  { sym: 'SHOP', name: 'SHOPIFY INC',          px:  72.15, sec: 'ECOM'   },
  { sym: 'WMT',  name: 'WALMART INC',          px:  68.20, sec: 'RETAIL' },
  { sym: 'COST', name: 'COSTCO WHOLESALE',     px: 845.60, sec: 'RETAIL' },
  { sym: 'TGT',  name: 'TARGET CORP',          px: 152.40, sec: 'RETAIL' },
  { sym: 'HD',   name: 'HOME DEPOT',           px: 372.80, sec: 'RETAIL' },
  { sym: 'LOW',  name: 'LOWES COMPANIES',      px: 248.50, sec: 'RETAIL' },
  { sym: 'NKE',  name: 'NIKE INC',             px:  78.40, sec: 'RETAIL' },
  // ── MEDIA / ENTERTAINMENT ──
  { sym: 'NFLX', name: 'NETFLIX INC',          px: 615.40, sec: 'MEDIA'  },
  { sym: 'DIS',  name: 'WALT DISNEY CO',       px:  98.50, sec: 'MEDIA'  },
  { sym: 'CMCSA',name: 'COMCAST CORP',         px:  39.80, sec: 'MEDIA'  },
  { sym: 'WBD',  name: 'WARNER BROS DISCOV',   px:   8.20, sec: 'MEDIA'  },
  { sym: 'ROKU', name: 'ROKU INC',             px:  62.40, sec: 'MEDIA'  },
  { sym: 'SPOT', name: 'SPOTIFY TECH',         px: 348.20, sec: 'MEDIA'  },
  // ── FINANCE / BANKS ──
  { sym: 'JPM',  name: 'JPMORGAN CHASE',       px: 218.40, sec: 'FIN'    },
  { sym: 'BAC',  name: 'BANK OF AMERICA',      px:  42.50, sec: 'FIN'    },
  { sym: 'WFC',  name: 'WELLS FARGO',          px:  64.80, sec: 'FIN'    },
  { sym: 'GS',   name: 'GOLDMAN SACHS',        px: 502.40, sec: 'FIN'    },
  { sym: 'MS',   name: 'MORGAN STANLEY',       px: 105.20, sec: 'FIN'    },
  { sym: 'C',    name: 'CITIGROUP INC',        px:  68.40, sec: 'FIN'    },
  { sym: 'BLK',  name: 'BLACKROCK INC',        px: 892.50, sec: 'FIN'    },
  { sym: 'V',    name: 'VISA INC',             px: 282.40, sec: 'FIN'    },
  { sym: 'MA',   name: 'MASTERCARD INC',       px: 478.60, sec: 'FIN'    },
  { sym: 'AXP',  name: 'AMERICAN EXPRESS',     px: 248.40, sec: 'FIN'    },
  { sym: 'COIN', name: 'COINBASE GLOBAL',      px: 218.90, sec: 'FIN'    },
  { sym: 'PYPL', name: 'PAYPAL HOLDINGS',      px:  72.40, sec: 'FIN'    },
  { sym: 'XYZ',  name: 'BLOCK INC',             px:  68.50, sec: 'FIN'    },
  { sym: 'HOOD', name: 'ROBINHOOD MARKETS',    px:  22.80, sec: 'FIN'    },
  // ── HEALTHCARE / PHARMA ──
  { sym: 'UNH',  name: 'UNITEDHEALTH GROUP',   px: 528.40, sec: 'HEALTH' },
  { sym: 'JNJ',  name: 'JOHNSON & JOHNSON',    px: 158.20, sec: 'HEALTH' },
  { sym: 'LLY',  name: 'ELI LILLY & CO',       px: 762.80, sec: 'PHARMA' },
  { sym: 'PFE',  name: 'PFIZER INC',           px:  28.40, sec: 'PHARMA' },
  { sym: 'MRK',  name: 'MERCK & CO',           px: 124.50, sec: 'PHARMA' },
  { sym: 'ABBV', name: 'ABBVIE INC',           px: 178.20, sec: 'PHARMA' },
  { sym: 'TMO',  name: 'THERMO FISHER',        px: 568.40, sec: 'HEALTH' },
  { sym: 'ABT',  name: 'ABBOTT LABORATORIES',  px: 112.40, sec: 'HEALTH' },
  { sym: 'CVS',  name: 'CVS HEALTH CORP',      px:  62.50, sec: 'HEALTH' },
  { sym: 'NVO',  name: 'NOVO NORDISK',         px: 128.40, sec: 'PHARMA' },
  { sym: 'MRNA', name: 'MODERNA INC',          px:  82.40, sec: 'PHARMA' },
  // ── ENERGY / OIL ──
  { sym: 'XOM',  name: 'EXXON MOBIL',          px: 118.20, sec: 'ENERGY' },
  { sym: 'CVX',  name: 'CHEVRON CORP',         px: 158.40, sec: 'ENERGY' },
  { sym: 'COP',  name: 'CONOCOPHILLIPS',       px: 108.50, sec: 'ENERGY' },
  { sym: 'OXY',  name: 'OCCIDENTAL PETRO',     px:  52.80, sec: 'ENERGY' },
  { sym: 'SLB',  name: 'SCHLUMBERGER NV',      px:  48.20, sec: 'ENERGY' },
  { sym: 'EOG',  name: 'EOG RESOURCES',        px: 128.40, sec: 'ENERGY' },
  // ── INDUSTRIALS / AUTO ──
  { sym: 'BA',   name: 'BOEING CO',            px: 178.20, sec: 'INDUS'  },
  { sym: 'CAT',  name: 'CATERPILLAR INC',      px: 348.50, sec: 'INDUS'  },
  { sym: 'GE',   name: 'GENERAL ELECTRIC',     px: 168.40, sec: 'INDUS'  },
  { sym: 'F',    name: 'FORD MOTOR CO',        px:  10.85, sec: 'AUTO'   },
  { sym: 'GM',   name: 'GENERAL MOTORS',       px:  48.20, sec: 'AUTO'   },
  { sym: 'RIVN', name: 'RIVIAN AUTOMOTIVE',    px:  14.50, sec: 'AUTO'   },
  { sym: 'LCID', name: 'LUCID GROUP',          px:   2.85, sec: 'AUTO'   },
  { sym: 'UBER', name: 'UBER TECHNOLOGIES',    px:  72.40, sec: 'TRANSP' },
  { sym: 'LYFT', name: 'LYFT INC',             px:  18.20, sec: 'TRANSP' },
  // ── CONSUMER / FOOD ──
  { sym: 'KO',   name: 'COCA-COLA CO',         px:  68.40, sec: 'CONS'   },
  { sym: 'PEP',  name: 'PEPSICO INC',          px: 168.20, sec: 'CONS'   },
  { sym: 'MCD',  name: 'MCDONALDS CORP',       px: 282.40, sec: 'CONS'   },
  { sym: 'SBUX', name: 'STARBUCKS CORP',       px:  92.50, sec: 'CONS'   },
  { sym: 'PG',   name: 'PROCTER & GAMBLE',     px: 168.40, sec: 'CONS'   },
  { sym: 'CL',   name: 'COLGATE-PALMOLIVE',    px:  98.20, sec: 'CONS'   },
  // ── TELECOM ──
  { sym: 'T',    name: 'AT&T INC',             px:  22.40, sec: 'TELCO'  },
  { sym: 'VZ',   name: 'VERIZON COMMS',        px:  42.80, sec: 'TELCO'  },
  // ── AIRLINES / TRAVEL ──
  { sym: 'DAL',  name: 'DELTA AIR LINES',      px:  52.40, sec: 'TRANSP' },
  { sym: 'UAL',  name: 'UNITED AIRLINES',      px:  78.50, sec: 'TRANSP' },
  { sym: 'AAL',  name: 'AMERICAN AIRLINES',    px:  14.20, sec: 'TRANSP' },
  { sym: 'ABNB', name: 'AIRBNB INC',           px: 138.40, sec: 'TRAVEL' },
  { sym: 'BKNG', name: 'BOOKING HOLDINGS',     px:4582.40, sec: 'TRAVEL' },
  // ── REIT / REAL ESTATE ──
  { sym: 'AMT',  name: 'AMERICAN TOWER',       px: 198.50, sec: 'REIT'   },
  { sym: 'PLD',  name: 'PROLOGIS INC',         px: 118.40, sec: 'REIT'   },
  // ── CRYPTO-ADJACENT / SPECULATIVE ──
  { sym: 'MARA', name: 'MARATHON DIGITAL',     px:  18.40, sec: 'CRYPTO' },
  { sym: 'RIOT', name: 'RIOT PLATFORMS',       px:  10.20, sec: 'CRYPTO' },
  { sym: 'MSTR', name: 'MICROSTRATEGY INC',    px: 178.40, sec: 'CRYPTO' },
  { sym: 'GME',  name: 'GAMESTOP CORP',        px:  22.80, sec: 'RETAIL' },
  { sym: 'AMC',  name: 'AMC ENTERTAINMENT',    px:   3.85, sec: 'MEDIA'  },
];

const PATTERNS = {
  HEAD_SHOULDERS:     { name: 'Head & Shoulders',         dir: 'SELL', success: 81, target: -16, desc: 'Three peaks, middle highest. Bearish reversal of uptrend. Breakdown below neckline confirms.' },
  INV_HEAD_SHOULDERS: { name: 'Inverse Head & Shoulders', dir: 'BUY',  success: 89, target: 45,  desc: 'Three troughs, middle lowest. Bullish reversal. Breakout above neckline confirms.' },
  DOUBLE_TOP:         { name: 'Double Top',               dir: 'SELL', success: 75, target: -19, desc: 'Two peaks at similar highs (M-shape). Bearish reversal. Support break confirms.' },
  DOUBLE_BOTTOM:      { name: 'Double Bottom',            dir: 'BUY',  success: 88, target: 50,  desc: 'Two troughs at similar lows (W-shape). Bullish reversal. Resistance break confirms.' },
  TRIPLE_TOP:         { name: 'Triple Top',               dir: 'SELL', success: 70, target: -19, desc: 'Three peaks rejected at same resistance. Strong bearish reversal on support break.' },
  TRIPLE_BOTTOM:      { name: 'Triple Bottom',            dir: 'BUY',  success: 87, target: 47,  desc: 'Three troughs at same support (VVV-shape). Strong bullish reversal on breakout.' },
  CUP_HANDLE:         { name: 'Cup & Handle',             dir: 'BUY',  success: 95, target: 54,  desc: 'U-shaped base + small downward handle. Highest-rated bullish continuation. Breakout above rim.' },
  ASC_TRIANGLE:       { name: 'Ascending Triangle',       dir: 'BUY',  success: 83, target: 35,  desc: 'Flat resistance + rising support. Bullish breakout above resistance line.' },
  DESC_TRIANGLE:      { name: 'Descending Triangle',      dir: 'SELL', success: 87, target: -16, desc: 'Flat support + falling resistance. Bearish breakdown below support line.' },
  SYM_TRIANGLE:       { name: 'Symmetrical Triangle',     dir: 'BUY',  success: 72, target: 28,  desc: 'Converging trendlines, falling highs + rising lows. Direction follows breakout.' },
  FALLING_WEDGE:      { name: 'Falling Wedge',            dir: 'BUY',  success: 82, target: 38,  desc: 'Converging downward trendlines. Bullish reversal on upside breakout.' },
  RISING_WEDGE:       { name: 'Rising Wedge',             dir: 'SELL', success: 81, target: -19, desc: 'Converging upward trendlines. Bearish reversal on downside breakdown.' },
  BULL_FLAG:          { name: 'Bull Flag',                dir: 'BUY',  success: 80, target: 22,  desc: 'Sharp rally (pole) + tight downward consolidation (flag). Bullish continuation.' },
  BEAR_FLAG:          { name: 'Bear Flag',                dir: 'SELL', success: 76, target: -18, desc: 'Sharp drop (pole) + tight upward consolidation (flag). Bearish continuation.' },
  BULL_PENNANT:       { name: 'Bull Pennant',             dir: 'BUY',  success: 73, target: 25,  desc: 'Sharp rally + symmetrical triangle consolidation. Continuation. Volume confirms.' },
  BEAR_PENNANT:       { name: 'Bear Pennant',             dir: 'SELL', success: 71, target: -22, desc: 'Sharp drop + symmetrical triangle consolidation. Bearish continuation.' },
  RECT_TOP:           { name: 'Rectangle Top',            dir: 'BUY',  success: 85, target: 51,  desc: 'Sideways range with parallel S/R. Bullish on upside breakout from consolidation.' },
  RECT_BOTTOM:        { name: 'Rectangle Bottom',         dir: 'SELL', success: 76, target: -16, desc: 'Sideways range at trend bottom. Bearish breakdown through support line.' },
  VOL_BREAKOUT:       { name: 'Volume Breakout',          dir: 'BUY',  success: 78, target: 18,  desc: 'Volume > 1.5x 30-day avg with price breaking above prior resistance.' },
  SUPPORT_BREAK:      { name: 'Support Break',            dir: 'SELL', success: 74, target: -14, desc: 'Price closes below established support level on rising volume.' },
  RESISTANCE_BREAK:   { name: 'Resistance Break',         dir: 'BUY',  success: 79, target: 17,  desc: 'Price closes above established resistance on rising volume.' },
  GOLDEN_CROSS:       { name: 'Golden Cross',             dir: 'BUY',  success: 77, target: 21,  desc: '50-day MA crosses above 200-day MA. Long-term bullish trend confirmation.' },
  DEATH_CROSS:        { name: 'Death Cross',              dir: 'SELL', success: 72, target: -15, desc: '50-day MA crosses below 200-day MA. Long-term bearish trend signal.' },
};

// Seeded RNG so tickers stay consistent between renders
function mulberry32(seed) {
  return function() {
    let t = seed += 0x6D2B79F5;
    t = Math.imul(t ^ t >>> 15, t | 1);
    t ^= t + Math.imul(t ^ t >>> 7, t | 61);
    return ((t ^ t >>> 14) >>> 0) / 4294967296;
  };
}

// Generate a price series shaped like one of the patterns
function generateSeries(pattern, basePrice, len = 60, seed = 1) {
  const rnd = mulberry32(seed);
  const arr = [];
  const noise = () => (rnd() - 0.5) * basePrice * 0.008;

  const shape = (i) => {
    const t = i / (len - 1); // 0..1
    switch (pattern) {
      case 'TRIPLE_TOP': {
        const tops = Math.exp(-Math.pow((t - 0.2) * 10, 2)) * 0.05
                   + Math.exp(-Math.pow((t - 0.45) * 10, 2)) * 0.05
                   + Math.exp(-Math.pow((t - 0.7) * 10, 2)) * 0.05;
        const breakdown = t > 0.85 ? -(t - 0.85) * 0.6 : 0;
        return basePrice * (1 + tops + breakdown - 0.01);
      }
      case 'TRIPLE_BOTTOM': {
        const bots = -Math.exp(-Math.pow((t - 0.2) * 10, 2)) * 0.05
                   - Math.exp(-Math.pow((t - 0.45) * 10, 2)) * 0.05
                   - Math.exp(-Math.pow((t - 0.7) * 10, 2)) * 0.05;
        const breakout = t > 0.85 ? (t - 0.85) * 0.6 : 0;
        return basePrice * (1 + bots + breakout + 0.01);
      }
      case 'SYM_TRIANGLE': {
        const conv = Math.sin(t * Math.PI * 6) * (0.05 - t * 0.045);
        const brk = t > 0.85 ? (t - 0.85) * 0.5 : 0;
        return basePrice * (1 + conv + brk);
      }
      case 'BULL_PENNANT': {
        const pole = t < 0.4 ? t * 0.16 : 0.064;
        const pen = t >= 0.4 && t < 0.85 ? Math.sin((t - 0.4) * Math.PI * 7) * (0.025 - (t - 0.4) * 0.04) : 0;
        const brk = t >= 0.85 ? (t - 0.85) * 0.7 : 0;
        return basePrice * (1 + pole + pen + brk - 0.05);
      }
      case 'BEAR_PENNANT': {
        const pole = t < 0.4 ? -t * 0.16 : -0.064;
        const pen = t >= 0.4 && t < 0.85 ? Math.sin((t - 0.4) * Math.PI * 7) * (0.025 - (t - 0.4) * 0.04) : 0;
        const brk = t >= 0.85 ? -(t - 0.85) * 0.7 : 0;
        return basePrice * (1 + pole + pen + brk + 0.05);
      }
      case 'RECT_TOP': {
        const rng = Math.sin(t * Math.PI * 7) * 0.025;
        const trend = t < 0.15 ? t * 0.15 : 0.0225;
        const brk = t > 0.85 ? (t - 0.85) * 0.5 : 0;
        return basePrice * (1 + rng + trend + brk - 0.02);
      }
      case 'RECT_BOTTOM': {
        const rng = Math.sin(t * Math.PI * 7) * 0.025;
        const trend = t < 0.15 ? -t * 0.15 : -0.0225;
        const brk = t > 0.85 ? -(t - 0.85) * 0.5 : 0;
        return basePrice * (1 + rng + trend + brk + 0.02);
      }
      case 'GOLDEN_CROSS': {
        const dn = -Math.exp(-Math.pow((t - 0.3) * 4, 2)) * 0.05;
        const up = t > 0.45 ? (t - 0.45) * 0.18 : 0;
        return basePrice * (1 + dn + up - 0.02);
      }
      case 'DEATH_CROSS': {
        const up = Math.exp(-Math.pow((t - 0.3) * 4, 2)) * 0.05;
        const dn = t > 0.45 ? -(t - 0.45) * 0.18 : 0;
        return basePrice * (1 + up + dn + 0.02);
      }
      case 'HEAD_SHOULDERS': {
        // up, peak1, dip, peak2 (highest), dip, peak3, breakdown
        const peaks = Math.sin(t * Math.PI * 3) * 0.04;
        const head = Math.exp(-Math.pow((t - 0.5) * 6, 2)) * 0.06;
        const breakdown = t > 0.85 ? -(t - 0.85) * 0.5 : 0;
        return basePrice * (1 + peaks + head + breakdown - 0.02);
      }
      case 'INV_HEAD_SHOULDERS': {
        const troughs = -Math.sin(t * Math.PI * 3) * 0.04;
        const head = -Math.exp(-Math.pow((t - 0.5) * 6, 2)) * 0.06;
        const breakout = t > 0.85 ? (t - 0.85) * 0.5 : 0;
        return basePrice * (1 + troughs + head + breakout + 0.02);
      }
      case 'DOUBLE_TOP': {
        const tops = Math.exp(-Math.pow((t - 0.3) * 8, 2)) * 0.05 + Math.exp(-Math.pow((t - 0.65) * 8, 2)) * 0.05;
        const breakdown = t > 0.85 ? -(t - 0.85) * 0.6 : 0;
        return basePrice * (1 + tops + breakdown - 0.01);
      }
      case 'DOUBLE_BOTTOM': {
        const bots = -Math.exp(-Math.pow((t - 0.3) * 8, 2)) * 0.05 - Math.exp(-Math.pow((t - 0.65) * 8, 2)) * 0.05;
        const breakout = t > 0.85 ? (t - 0.85) * 0.6 : 0;
        return basePrice * (1 + bots + breakout + 0.01);
      }
      case 'CUP_HANDLE': {
        const cup = -Math.sin(t * Math.PI * 1.2) * 0.05;
        const handle = t > 0.75 && t < 0.92 ? -((t - 0.75) * 0.15) : 0;
        const brk = t >= 0.92 ? (t - 0.92) * 0.8 : 0;
        return basePrice * (1 + cup + handle + brk);
      }
      case 'ASC_TRIANGLE': {
        const conv = Math.sin(t * Math.PI * 5) * (0.05 - t * 0.04);
        const trend = t * 0.02;
        const brk = t > 0.85 ? (t - 0.85) * 0.6 : 0;
        return basePrice * (1 + conv + trend + brk);
      }
      case 'DESC_TRIANGLE': {
        const conv = Math.sin(t * Math.PI * 5) * (0.05 - t * 0.04);
        const trend = -t * 0.02;
        const brk = t > 0.85 ? -(t - 0.85) * 0.6 : 0;
        return basePrice * (1 + conv + trend + brk);
      }
      case 'FALLING_WEDGE': {
        const conv = Math.sin(t * Math.PI * 4) * (0.06 - t * 0.05);
        const trend = -t * 0.03;
        const brk = t > 0.85 ? (t - 0.85) * 0.6 : 0;
        return basePrice * (1 + conv + trend + brk + 0.02);
      }
      case 'RISING_WEDGE': {
        const conv = Math.sin(t * Math.PI * 4) * (0.06 - t * 0.05);
        const trend = t * 0.03;
        const brk = t > 0.85 ? -(t - 0.85) * 0.6 : 0;
        return basePrice * (1 + conv + trend + brk - 0.02);
      }
      case 'BULL_FLAG': {
        const pole = t < 0.4 ? t * 0.15 : 0.06;
        const flag = t >= 0.4 && t < 0.85 ? Math.sin((t - 0.4) * Math.PI * 6) * 0.012 - (t - 0.4) * 0.02 : 0;
        const brk = t >= 0.85 ? (t - 0.85) * 0.7 : 0;
        return basePrice * (1 + pole + flag + brk - 0.05);
      }
      case 'BEAR_FLAG': {
        const pole = t < 0.4 ? -t * 0.15 : -0.06;
        const flag = t >= 0.4 && t < 0.85 ? Math.sin((t - 0.4) * Math.PI * 6) * 0.012 + (t - 0.4) * 0.02 : 0;
        const brk = t >= 0.85 ? -(t - 0.85) * 0.7 : 0;
        return basePrice * (1 + pole + flag + brk + 0.05);
      }
      case 'VOL_BREAKOUT':
      case 'RESISTANCE_BREAK': {
        const consol = Math.sin(t * Math.PI * 8) * 0.012;
        const brk = t > 0.8 ? (t - 0.8) * 0.5 : 0;
        return basePrice * (1 + consol + brk);
      }
      case 'SUPPORT_BREAK': {
        const consol = Math.sin(t * Math.PI * 8) * 0.012;
        const brk = t > 0.8 ? -(t - 0.8) * 0.5 : 0;
        return basePrice * (1 + consol + brk);
      }
      default: {
        // random walk
        return basePrice * (1 + Math.sin(t * Math.PI * 3) * 0.02 + (rnd() - 0.5) * 0.04);
      }
    }
  };

  for (let i = 0; i < len; i++) {
    arr.push(shape(i) + noise());
  }
  return arr;
}

function generateVolume(pattern, len = 60, seed = 7) {
  const rnd = mulberry32(seed);
  const arr = [];
  for (let i = 0; i < len; i++) {
    const t = i / (len - 1);
    let base = 0.5 + rnd() * 0.4;
    // breakout patterns spike volume at the end
    if (['VOL_BREAKOUT','RESISTANCE_BREAK','SUPPORT_BREAK','HEAD_SHOULDERS','INV_HEAD_SHOULDERS','DOUBLE_TOP','DOUBLE_BOTTOM','CUP_HANDLE','ASC_TRIANGLE','DESC_TRIANGLE','BULL_FLAG','BEAR_FLAG'].includes(pattern)) {
      if (t > 0.82) base += (t - 0.82) * 4;
    }
    arr.push(Math.max(0.1, base));
  }
  return arr;
}

// Each ticker is pre-assigned a primary pattern (rotated for autonomous mode demo)
const PATTERN_KEYS = Object.keys(PATTERNS);
function buildStocks(rotation = 0) {
  return TICKERS.map((t, i) => {
    const patternKey = PATTERN_KEYS[(i + rotation) % PATTERN_KEYS.length];
    const series = generateSeries(patternKey, t.px, 60, i + 1 + rotation);
    const volume = generateVolume(patternKey, 60, i + 11 + rotation);
    const last = series[series.length - 1];
    const first = series[0];
    const change = ((last - first) / first) * 100;
    const avgVol30 = volume.slice(0, 30).reduce((a,b)=>a+b,0) / 30;
    const lastVol = volume[volume.length - 1];
    const volRatio = lastVol / avgVol30;
    const confidence = Math.min(98, Math.round(60 + Math.abs(change) * 3 + (volRatio - 1) * 10 + (i % 5)));
    const pattern = PATTERNS[patternKey];
    // Risk/reward: stop loss 3% adverse, target 6% favorable
    const entry = last;
    const stop = pattern.dir === 'BUY' ? entry * 0.97 : entry * 1.03;
    const target = pattern.dir === 'BUY' ? entry * 1.06 : entry * 0.94;
    return {
      ...t,
      px: last,
      change,
      series,
      volume,
      avgVol30,
      volRatio,
      patternKey,
      pattern,
      confidence,
      entry,
      stop,
      target,
      rr: 2.0,
    };
  });
}

// Live feed: pre-baked log lines in addition to live-generated ones
function generateLogLine(stock, ts) {
  const pad = (n, l = 2) => String(n).padStart(l, '0');
  const time = `${pad(ts.getHours())}:${pad(ts.getMinutes())}:${pad(ts.getSeconds())}`;
  return {
    time,
    sym: stock.sym,
    dir: stock.pattern.dir,
    pattern: stock.pattern.name,
    px: stock.px,
    confidence: stock.confidence,
  };
}

window.TICKERS = TICKERS;
window.PATTERNS = PATTERNS;
window.buildStocks = buildStocks;
window.generateSeries = generateSeries;
window.generateLogLine = generateLogLine;
