import { useState, useEffect, useCallback } from "react"; // ───────────────────────────────────────────────────────────────────────────── // STYLES — exact Top Tier Pro design system // ───────────────────────────────────────────────────────────────────────────── const CSS = ` @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@700;900&family=Rajdhani:wght@400;500;600;700&family=IBM+Plex+Mono:wght@300;400;600;700&display=swap'); *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } :root { --bg:#07090f; --panel:#0c1221; --panel2:#101827; --border:#1c2c42; --border2:#243448; --bull:#00c9a7; --bear:#ff2d55; --gold:#c9963a; --gold2:#e8b84b; --blue:#3d7eff; --steel:#7090a8; --dim:#3a5268; --dim2:#4e6880; --text:#adc8e0; --text2:#c8ddf0; --mono:'IBM Plex Mono',monospace; --sans:'Rajdhani',sans-serif; --disp:'Orbitron',sans-serif; } body { background:var(--bg); color:var(--text); font-family:var(--sans); } .app { min-height:100vh; background:var(--bg); padding:16px; max-width:980px; margin:0 auto; background-image: radial-gradient(ellipse at 15% 0%,rgba(201,150,58,0.05) 0%,transparent 55%), radial-gradient(ellipse at 85% 100%,rgba(61,126,255,0.04) 0%,transparent 55%); } /* HEADER */ .hdr { display:flex; align-items:center; justify-content:space-between; padding:22px 0; border-bottom:1px solid var(--border); margin-bottom:20px; position:relative; } .hdr::before { content:''; position:absolute; bottom:-1px; left:0; width:80px; height:2px; background:linear-gradient(90deg,var(--gold),transparent); } .logo { font-family:var(--disp); font-weight:900; font-size:18px; letter-spacing:4px; color:var(--text2); } .logo em { color:var(--gold2); font-style:normal; } .tagline { font-family:var(--mono); font-size:9px; color:var(--dim2); letter-spacing:3px; margin-top:6px; text-transform:uppercase; } /* PROGRESS */ .prog { display:flex; gap:3px; margin-bottom:18px; } .pseg { flex:1; height:3px; background:var(--border2); border-radius:2px; transition:background 0.3s; } .pseg.done { background:var(--gold); opacity:0.7; } .pseg.active { background:var(--gold2); } /* STEP TABS */ .step-row { display:flex; gap:2px; margin-bottom:20px; border-bottom:1px solid var(--border); overflow-x:auto; } .stab { flex-shrink:0; background:transparent; border:none; border-bottom:2px solid transparent; color:var(--dim2); font-family:var(--sans); font-weight:600; font-size:11px; letter-spacing:2px; padding:10px 16px 12px; cursor:pointer; transition:all 0.2s; text-transform:uppercase; margin-bottom:-1px; } .stab:hover { color:var(--text); border-bottom-color:var(--border2); } .stab.active { border-bottom-color:var(--gold2); color:var(--gold2); } .stab.done { color:var(--bull); } .stab.done::after { content:' ✓'; font-size:9px; } /* PANEL */ .panel { background:var(--panel); border:1px solid var(--border); border-top:2px solid var(--border2); padding:22px 24px; margin-bottom:14px; position:relative; } .panel::before { content:''; position:absolute; top:-2px; left:-1px; width:32px; height:2px; background:var(--gold); } .pt { font-family:var(--sans); font-weight:700; font-size:10px; letter-spacing:4px; text-transform:uppercase; color:var(--gold); margin-bottom:16px; display:flex; align-items:center; gap:12px; opacity:0.9; } .pt::after { content:''; flex:1; height:1px; background:linear-gradient(90deg,var(--border2),transparent); } /* SIG BUTTONS */ .sig { flex:1; min-width:90px; padding:13px 10px; border:1px solid var(--border2); background:var(--bg); font-family:var(--mono); font-weight:600; font-size:11px; letter-spacing:1px; cursor:pointer; transition:all 0.18s; text-transform:uppercase; color:var(--dim2); position:relative; overflow:hidden; } .sig::after { content:''; position:absolute; bottom:0; left:0; right:0; height:2px; background:currentColor; opacity:0; transition:opacity 0.18s; } .sig:hover { opacity:0.8; color:var(--text); } .sig.gold { color:var(--gold2); border-color:rgba(201,150,58,0.35); } .sig.gold.on { background:rgba(201,150,58,0.08); border-color:var(--gold2); } .sig.gold.on::after { opacity:1; } .sig.bull { color:var(--bull); border-color:rgba(0,201,167,0.35); } .sig.bull.on { background:rgba(0,201,167,0.08); border-color:var(--bull); } .sig.bull.on::after { opacity:1; } .sig.bear { color:var(--bear); border-color:rgba(255,45,85,0.35); } .sig.bear.on { background:rgba(255,45,85,0.09); border-color:var(--bear); } .sig.bear.on::after { opacity:1; } /* CALC ROWS */ .crow { display:flex; justify-content:space-between; align-items:center; padding:9px 0; border-bottom:1px solid rgba(28,44,66,0.6); font-family:var(--mono); font-size:11px; } .crow:last-child { border-bottom:none; } .ckey { color:var(--dim2); letter-spacing:1px; text-transform:uppercase; font-size:10px; } .cval { color:var(--text2); font-weight:600; } .cval.bull { color:var(--bull); } .cval.bear { color:var(--bear); } .cval.gold { color:var(--gold2); } .cval.steel { color:var(--steel); } /* BADGES */ .badge { display:inline-block; font-family:var(--mono); font-size:8px; letter-spacing:2px; padding:2px 7px; border:1px solid; text-transform:uppercase; vertical-align:middle; border-radius:2px; } .badge.bull { border-color:var(--bull); color:var(--bull); } .badge.bear { border-color:var(--bear); color:var(--bear); } .badge.gold { border-color:var(--gold2); color:var(--gold2); } .badge.steel { border-color:var(--steel); color:var(--steel); } .badge.dim { border-color:var(--dim); color:var(--dim2); } /* RULE CARDS */ .rule { border-left:2px solid var(--bull); padding:11px 16px; background:rgba(0,201,167,0.03); margin-bottom:7px; font-family:var(--sans); font-size:14px; line-height:1.55; color:var(--text); } .rule strong { font-weight:700; } .rule.bear { border-color:var(--bear); background:rgba(255,45,85,0.03); } .rule.gold { border-color:var(--gold); background:rgba(201,150,58,0.03); } /* SUMMARY GRID */ .sum-grid { display:grid; grid-template-columns:repeat(3,1fr); gap:1px; background:var(--border); border:1px solid var(--border); margin-bottom:16px; } .sum-cell { background:var(--panel); padding:16px 14px; text-align:center; } .sum-val { font-family:var(--disp); font-weight:700; font-size:20px; margin-bottom:5px; letter-spacing:1px; } .sum-key { font-family:var(--mono); font-size:8px; color:var(--dim2); letter-spacing:2px; text-transform:uppercase; } /* FIELD */ .field { display:flex; flex-direction:column; gap:5px; } .field label { font-family:var(--mono); font-size:9px; color:var(--dim2); letter-spacing:2px; text-transform:uppercase; } .field input,.field select { background:var(--bg); border:1px solid var(--border); border-bottom:1px solid var(--border2); color:var(--text2); font-family:var(--mono); font-size:13px; padding:9px 12px; outline:none; transition:border-color 0.2s; width:100%; } .field input:focus,.field select:focus { border-color:var(--gold); background:rgba(201,150,58,0.03); } /* NAV */ .nav-row { display:flex; gap:10px; margin-top:20px; justify-content:flex-end; } .btn { font-family:var(--mono); font-size:10px; letter-spacing:3px; padding:11px 28px; border:1px solid; background:transparent; cursor:pointer; text-transform:uppercase; transition:all 0.18s; font-weight:600; } .btn.primary { border-color:var(--gold); color:var(--gold2); } .btn.primary:hover { background:rgba(201,150,58,0.1); } .btn.primary:disabled { opacity:0.35; cursor:not-allowed; } .btn.sec { border-color:var(--dim); color:var(--dim2); } .btn.sec:hover { border-color:var(--border2); color:var(--text); } .btn.teal { border-color:var(--bull); color:var(--bull); } .btn.teal:hover { background:rgba(0,201,167,0.08); } .btn.teal:disabled { opacity:0.35; cursor:not-allowed; } /* SECTOR CARDS */ .sec-grid { display:grid; grid-template-columns:repeat(3,1fr); gap:8px; } .sec-card { background:var(--bg); border:1px solid var(--border); padding:14px 12px; cursor:pointer; transition:all 0.2s; position:relative; } .sec-card:hover { border-color:var(--border2); } .sec-card.s-bull { border-color:var(--bull); background:rgba(0,201,167,0.04); } .sec-card.s-bull::before { content:''; position:absolute; top:0; left:0; right:0; height:2px; background:var(--bull); } .sec-card.s-bear { border-color:var(--bear); background:rgba(255,45,85,0.04); } .sec-card.s-bear::before { content:''; position:absolute; top:0; left:0; right:0; height:2px; background:var(--bear); } .sec-card.s-gold { border-color:var(--gold2); background:rgba(201,150,58,0.04); } .sec-card.s-gold::before { content:''; position:absolute; top:0; left:0; right:0; height:2px; background:var(--gold2); } .ret-grid { display:grid; grid-template-columns:1fr 1fr 1fr; gap:4px; margin-top:10px; } .ret-cell { background:var(--panel); border:1px solid var(--border); padding:5px 4px; text-align:center; } .ret-lbl { font-family:var(--mono); font-size:6px; color:var(--dim2); letter-spacing:1px; margin-bottom:2px; } .ret-v { font-family:var(--mono); font-size:10px; font-weight:700; } .rv-pos { color:var(--bull); } .rv-neg { color:var(--bear); } .rv-dim { color:var(--dim2); } @keyframes shimmer { 0%,100%{opacity:0.25} 50%{opacity:1} } .rv-load { color:var(--border2); animation:shimmer 1.2s ease-in-out infinite; font-family:var(--mono); font-size:10px; } /* TECH TAGS */ .tt { display:inline-flex; align-items:center; gap:3px; font-family:var(--mono); font-size:7px; letter-spacing:1px; padding:2px 6px; border:1px solid; margin-right:3px; margin-bottom:3px; } .tt.bull { color:var(--bull); border-color:rgba(0,201,167,0.4); background:rgba(0,201,167,0.05); } .tt.bear { color:var(--bear); border-color:rgba(255,45,85,0.4); background:rgba(255,45,85,0.05); } .tt.gold { color:var(--gold2); border-color:rgba(201,150,58,0.4); background:rgba(201,150,58,0.05); } .tt.dim { color:var(--dim2); border-color:var(--border); } /* STOCK TABLE */ .stbl { width:100%; border-collapse:collapse; } .stbl th { font-family:var(--mono); font-size:7px; letter-spacing:2px; color:var(--dim2); padding:9px 10px; text-align:left; border-bottom:1px solid var(--border2); background:var(--panel); position:sticky; top:0; z-index:2; white-space:nowrap; } .stbl th.r { text-align:right; } .stbl th.c { text-align:center; } .stbl td { font-family:var(--mono); font-size:9px; padding:8px 10px; border-bottom:1px solid var(--border); vertical-align:middle; } .stbl tr:hover td { background:rgba(255,255,255,0.013); } .stbl td.r { text-align:right; } .stbl td.c { text-align:center; } /* RISK DRAWER */ .risk-dr { background:var(--panel2); border-top:2px solid var(--border2); padding:20px 22px; } .risk-dt { font-family:var(--sans); font-weight:700; font-size:10px; letter-spacing:4px; text-transform:uppercase; color:var(--gold); margin-bottom:16px; display:flex; align-items:center; gap:12px; } .risk-dt::after { content:''; flex:1; height:1px; background:linear-gradient(90deg,var(--border2),transparent); } .tog-row { display:flex; gap:2px; margin-bottom:12px; } .tog { padding:7px 14px; border:1px solid var(--border2); background:var(--bg); font-family:var(--mono); font-size:9px; letter-spacing:2px; cursor:pointer; color:var(--dim2); transition:all 0.15s; text-transform:uppercase; } .tog.on-auto { border-color:var(--gold2); color:var(--gold2); background:rgba(201,150,58,0.07); } .tog.on-man { border-color:var(--blue); color:var(--blue); background:rgba(61,126,255,0.07); } .ri-lbl { font-family:var(--mono); font-size:9px; color:var(--dim2); letter-spacing:2px; text-transform:uppercase; margin-bottom:4px; } .ri { background:var(--bg); border:1px solid var(--border); border-bottom:1px solid var(--border2); color:var(--text2); font-family:var(--mono); font-size:13px; padding:9px 12px; outline:none; transition:border-color 0.2s; width:100%; } .ri:focus { border-color:var(--gold); background:rgba(201,150,58,0.03); } .stop-box { border:1px solid rgba(255,45,85,0.35); background:rgba(255,45,85,0.04); padding:14px 16px; margin-bottom:8px; } .tgt-row { display:grid; grid-template-columns:28px 1fr 60px; align-items:center; gap:8px; padding:7px 10px; background:var(--bg); border:1px solid var(--border); margin-bottom:5px; } .rr-box { text-align:center; padding:10px 12px; border:1px solid var(--border); background:var(--bg); margin-bottom:6px; } .rr-num { font-family:var(--disp); font-weight:700; font-size:18px; } /* VERDICT */ .verdict { border:1px solid; text-align:center; padding:22px 20px; position:relative; overflow:hidden; } .verdict::before { content:''; position:absolute; top:0; left:0; right:0; height:1px; background:currentColor; opacity:0.4; } .verdict.go { border-color:rgba(0,201,167,0.4); background:rgba(0,201,167,0.04); } .verdict.wait { border-color:rgba(201,150,58,0.4); background:rgba(201,150,58,0.04); } .verdict.no { border-color:rgba(255,45,85,0.4); background:rgba(255,45,85,0.04); } .vt { font-family:var(--disp); font-weight:900; font-size:18px; letter-spacing:5px; text-transform:uppercase; } .vs { font-family:var(--mono); font-size:9px; color:var(--dim2); margin-top:6px; letter-spacing:2px; } /* COMMENT */ .cmt { width:100%; background:var(--bg); border:1px solid var(--border); border-bottom:1px solid var(--border2); color:var(--text2); font-family:var(--mono); font-size:9px; padding:7px 9px; resize:none; outline:none; height:48px; line-height:1.6; transition:border-color 0.2s; } .cmt:focus { border-color:var(--gold); } .cmt::placeholder { color:var(--dim); } /* STATUS BANNERS */ .status { font-family:var(--mono); font-size:9px; padding:9px 14px; border:1px solid; letter-spacing:1px; margin-bottom:12px; display:flex; align-items:center; gap:10px; } .status.fetching { border-color:rgba(0,201,167,0.25); color:var(--bull); background:rgba(0,201,167,0.03); } .status.error { border-color:rgba(255,45,85,0.25); color:var(--bear); background:rgba(255,45,85,0.03); } .status.haiku { border-color:rgba(201,150,58,0.25); color:var(--gold2); background:rgba(201,150,58,0.03); } @keyframes fadein { from{opacity:0;transform:translateY(6px)} to{opacity:1;transform:translateY(0)} } .fadein { animation:fadein 0.22s ease-out; } .wt-bar { height:2px; background:var(--border); margin-top:3px; } .wt-fill { height:100%; background:var(--gold); opacity:0.45; } ::-webkit-scrollbar{width:4px;height:4px} ::-webkit-scrollbar-track{background:var(--bg)} ::-webkit-scrollbar-thumb{background:var(--border2)} `; // ───────────────────────────────────────────────────────────────────────────── // DATA // ───────────────────────────────────────────────────────────────────────────── const STEPS = ["TIMEFRAME", "SECTORS", "DIRECTION", "STOCKS"]; const TIMEFRAMES = [ { id:"DAY", label:"Day Trade", desc:"Intraday · close same session", stop:0.5, t1:1.0, t2:1.8, t3:2.5 }, { id:"1-3D", label:"1 – 3 Days", desc:"Short swing · hold 1-3 days", stop:1.0, t1:2.0, t2:3.5, t3:5.0 }, { id:"3-5D", label:"3 – 5 Days", desc:"Extended swing · hold 3-5 days", stop:1.5, t1:3.0, t2:5.0, t3:8.0 }, { id:"1-2W", label:"1 – 2 Weeks", desc:"Weekly swing trade", stop:2.5, t1:5.0, t2:8.0, t3:12.0 }, { id:"1-3M", label:"1 – 3 Months", desc:"Position trade · monthly hold", stop:4.0, t1:8.0, t2:14.0, t3:20.0 }, ]; const SECTORS = [ { id:"tech", etf:"XLK", name:"Technology", wt:31.2, tickers:["AAPL","MSFT","NVDA","AVGO","ORCL","CRM","AMD","ADBE","ACN","CSCO"], names:["Apple","Microsoft","NVIDIA","Broadcom","Oracle","Salesforce","AMD","Adobe","Accenture","Cisco"], wts:[22.1,21.3,18.7,4.8,2.8,2.4,2.2,1.9,1.8,1.7] }, { id:"fin", etf:"XLF", name:"Financials", wt:13.1, tickers:["BRK.B","JPM","V","MA","BAC","WFC","GS","MS","BLK","SPGI"], names:["Berkshire","JPMorgan","Visa","Mastercard","Bank of America","Wells Fargo","Goldman","Morgan Stanley","BlackRock","S&P Global"], wts:[14.6,13.8,7.9,6.1,4.4,3.8,3.2,2.9,2.6,2.3] }, { id:"hlth", etf:"XLV", name:"Healthcare", wt:12.4, tickers:["UNH","LLY","JNJ","ABBV","MRK","TMO","ABT","DHR","BMY","AMGN"], names:["UnitedHealth","Eli Lilly","J&J","AbbVie","Merck","Thermo Fisher","Abbott","Danaher","Bristol-Myers","Amgen"], wts:[14.2,13.8,8.4,7.2,5.8,4.4,3.9,3.3,2.8,2.6] }, { id:"disc", etf:"XLY", name:"Consumer Disc.", wt:10.2, tickers:["AMZN","TSLA","HD","MCD","NKE","LOW","BKNG","TJX","SBUX","CMG"], names:["Amazon","Tesla","Home Depot","McDonald's","Nike","Lowe's","Booking","TJX","Starbucks","Chipotle"], wts:[24.8,17.2,9.4,4.8,3.9,3.6,3.4,3.1,2.8,2.4] }, { id:"comm", etf:"XLC", name:"Communications", wt:8.9, tickers:["META","GOOGL","GOOG","NFLX","DIS","CHTR","T","VZ","TMUS","EA"], names:["Meta","Alphabet A","Alphabet C","Netflix","Disney","Charter","AT&T","Verizon","T-Mobile","Electronic Arts"], wts:[23.4,14.8,12.6,5.4,4.1,3.6,3.2,2.9,2.7,1.8] }, { id:"ind", etf:"XLI", name:"Industrials", wt:8.7, tickers:["GE","RTX","HON","UPS","CAT","ETN","BA","LMT","DE","FDX"], names:["GE Aerospace","RTX","Honeywell","UPS","Caterpillar","Eaton","Boeing","Lockheed","Deere","FedEx"], wts:[5.9,5.6,5.1,4.8,4.6,4.2,3.9,3.4,3.1,2.7] }, { id:"stap", etf:"XLP", name:"Consumer Staples", wt:5.8, tickers:["PG","KO","COST","PEP","WMT","PM","MO","CL","MDLZ","KMB"], names:["P&G","Coca-Cola","Costco","PepsiCo","Walmart","Philip Morris","Altria","Colgate","Mondelez","Kimberly-Clark"], wts:[15.4,11.8,10.6,9.8,9.2,5.4,3.8,3.4,3.2,2.8] }, { id:"enrg", etf:"XLE", name:"Energy", wt:3.8, tickers:["XOM","CVX","COP","EOG","SLB","MPC","PSX","VLO","OXY","KMI"], names:["Exxon","Chevron","ConocoPhillips","EOG","SLB","Marathon Pet.","Phillips 66","Valero","Occidental","Kinder Morgan"], wts:[22.6,15.8,7.4,5.2,4.8,4.4,4.1,3.8,3.6,3.2] }, { id:"util", etf:"XLU", name:"Utilities", wt:2.4, tickers:["NEE","SO","DUK","D","SRE","AEP","EXC","PCG","XEL","ED"], names:["NextEra","Southern Co","Duke Energy","Dominion","Sempra","Am. Elec.","Exelon","PG&E","Xcel Energy","Con Edison"], wts:[16.2,8.6,7.9,6.4,5.8,5.4,4.9,4.2,3.8,3.4] }, { id:"mat", etf:"XLB", name:"Basic Materials", wt:2.3, tickers:["LIN","APD","SHW","ECL","FCX","NEM","NUE","ALB","PPG","IFF"], names:["Linde","Air Products","Sherwin-Williams","Ecolab","Freeport","Newmont","Nucor","Albemarle","PPG","Intl Flavors"], wts:[21.4,7.8,7.2,6.6,6.2,5.8,4.4,3.2,3.1,2.8] }, { id:"re", etf:"XLRE", name:"Real Estate", wt:2.2, tickers:["PLD","AMT","EQIX","WELL","SPG","PSA","O","VICI","DLR","EXR"], names:["Prologis","American Tower","Equinix","Welltower","Simon Property","Public Storage","Realty Income","VICI","Digital Realty","Extra Space"], wts:[11.8,9.4,8.2,5.8,5.4,5.1,4.8,4.4,4.2,3.9] }, ]; // ───────────────────────────────────────────────────────────────────────────── // STORAGE — window.storage (artifact) with localStorage fallback (Squarespace) // ───────────────────────────────────────────────────────────────────────────── const store = { set: async (k, v) => { const s = JSON.stringify(v); try { if (window.storage?.set) { await window.storage.set(k, s); return; } } catch {} try { localStorage.setItem(k, s); } catch {} }, get: async (k) => { try { if (window.storage?.get) { const r = await window.storage.get(k); return r?.value ? JSON.parse(r.value) : null; } } catch {} try { const v = localStorage.getItem(k); return v ? JSON.parse(v) : null; } catch { return null; } }, }; // ───────────────────────────────────────────────────────────────────────────── // TECHNICAL CALCULATIONS — computed from raw Yahoo Finance closes // ───────────────────────────────────────────────────────────────────────────── const calcEMA = (closes, period) => { if (closes.length < period) return null; const k = 2 / (period + 1); let ema = closes.slice(0, period).reduce((a, b) => a + b, 0) / period; for (let i = period; i < closes.length; i++) ema = closes[i] * k + ema * (1 - k); return ema; }; const calcSMA = (closes, period) => { if (closes.length < period) return null; return closes.slice(-period).reduce((a, b) => a + b, 0) / period; }; const calcMACD = (closes) => { if (closes.length < 35) return null; const k12 = 2/13, k26 = 2/27, k9 = 2/10; let e12 = closes.slice(0, 12).reduce((a, b) => a + b, 0) / 12; let e26 = closes.slice(0, 26).reduce((a, b) => a + b, 0) / 26; const macdLine = []; for (let i = 12; i < closes.length; i++) { e12 = closes[i] * k12 + e12 * (1 - k12); if (i >= 25) e26 = closes[i] * k26 + e26 * (1 - k26); if (i >= 26) macdLine.push(e12 - e26); } if (macdLine.length < 9) return null; let sig = macdLine.slice(0, 9).reduce((a, b) => a + b, 0) / 9; let prevHist = null; for (let i = 9; i < macdLine.length; i++) { prevHist = macdLine[i - 1] - sig; sig = macdLine[i] * k9 + sig * (1 - k9); } const hist = macdLine[macdLine.length - 1] - sig; if (prevHist !== null && ((hist > 0 && prevHist <= 0) || (hist < 0 && prevHist >= 0))) return "CROSS"; return hist > 0 ? "POS" : "NEG"; }; const computeFromCloses = (closes) => { if (!closes || closes.length < 30) return null; const last = closes[closes.length - 1]; const p1w = closes[Math.max(0, closes.length - 6)]; const p1m = closes[Math.max(0, closes.length - 22)]; const p90 = closes[Math.max(0, closes.length - 64)]; const pct = (a, b) => { const v = ((a - b) / b * 100).toFixed(1); return parseFloat(v) >= 0 ? `+${v}` : v; }; const w = pct(last, p1w), m = pct(last, p1m), d90 = pct(last, p90); const ema8 = calcEMA(closes, 8); const ema21 = calcEMA(closes, 21); const sma200= calcSMA(closes, 200); const macd = calcMACD(closes); const ema8v21 = ema8 && ema21 ? (ema8 > ema21 ? "BULL" : "BEAR") : null; const vs200 = sma200 ? (last > sma200 ? "ABOVE" : "BELOW") : null; const bulls = [ema8v21==="BULL", vs200==="ABOVE", macd==="POS"].filter(Boolean).length; const bears = [ema8v21==="BEAR", vs200==="BELOW", macd==="NEG"].filter(Boolean).length; const signal = bulls >= 2 ? "LONG" : bears >= 2 ? "SHORT" : "WATCH"; const setups = { LONG: { 3:"All three bullish · strong alignment", 2:"Two of three bullish · watch third", 1:"Weak bull · partial alignment" }, SHORT: { 3:"All three bearish · strong alignment", 2:"Two of three bearish · watch third", 1:"Weak bear · partial alignment" }, WATCH: "Mixed signals · no clear trend · monitor", }; const setup = signal==="LONG" ? setups.LONG[bulls] : signal==="SHORT" ? setups.SHORT[bears] : setups.WATCH; return { w, m, d90, ema8v21, vs200, macd, signal, setup }; }; // ───────────────────────────────────────────────────────────────────────────── // FETCH STRATEGIES — mirrors Top Tier Pro pattern exactly // 1) Yahoo Finance direct (real data, no API key) // 2) Haiku AI fallback (model knowledge, no web search, fast) // ───────────────────────────────────────────────────────────────────────────── // Yahoo Finance ticker format (BRK.B → BRK-B) const yTicker = t => t.replace(/\./g, "-"); // Fetch single ticker from Yahoo Finance → compute all indicators const tryYahooDirect = async (ticker) => { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 6000); try { const url = `https://query2.finance.yahoo.com/v8/finance/chart/${yTicker(ticker)}?interval=1d&range=1y`; const res = await fetch(url, { signal: controller.signal }); clearTimeout(timeout); if (!res.ok) throw new Error(`HTTP ${res.status}`); const data = await res.json(); const result = data?.chart?.result?.[0]; if (!result) throw new Error("no result"); const closes = (result.indicators.quote[0].close || []).filter(v => v != null); if (closes.length < 30) throw new Error("insufficient data"); return computeFromCloses(closes); } catch (e) { clearTimeout(timeout); throw e; } }; // Haiku fallback — uses model knowledge (no web search) for returns + signals const tryHaikuFallback = async (tickers, kind) => { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 20000); try { const res = await fetch("https://api.anthropic.com/v1/messages", { signal: controller.signal, method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ model: "claude-haiku-4-5-20251001", max_tokens: 1000, system: "Output ONLY a raw JSON object. No markdown. No explanation. No code fences.", messages: [{ role: "user", content: `Provide approximate recent performance and technical data for these ${kind}: ${tickers.join(", ")} Use your best knowledge of recent prices and trends. Return ONLY JSON: {"TICKER":{"w":"+1.2","m":"-3.4","d90":"+8.5","ema8v21":"BULL","vs200":"ABOVE","macd":"POS","signal":"LONG","setup":"Brief 6-word setup note"},...} w=1wk%change, m=1mo%change, d90=90day%change. Include + or - sign. ema8v21: BULL (8 above 21) or BEAR. vs200: ABOVE or BELOW 200-day SMA. macd: POS NEG or CROSS. signal: LONG SHORT or WATCH.` }] }) }); clearTimeout(timeout); if (!res.ok) throw new Error(`Haiku ${res.status}`); const data = await res.json(); const txt = (data.content || []).find(b => b.type === "text")?.text || ""; const match = txt.match(/\{[\s\S]*\}/); if (!match) throw new Error("no JSON in response"); return JSON.parse(match[0]); } catch (e) { clearTimeout(timeout); throw e; } }; // Fetch a batch of tickers — Yahoo first, Haiku fallback for any that fail const fetchBatch = async (tickers, kind, onProgress) => { const results = {}; const failed = []; // Phase 1: Yahoo Finance in parallel await Promise.all(tickers.map(async (t) => { try { results[t] = await tryYahooDirect(t); onProgress && onProgress(t, "yahoo"); } catch { failed.push(t); } })); // Phase 2: Haiku for any that failed Yahoo if (failed.length > 0) { onProgress && onProgress(null, "haiku", failed); try { const haikuData = await tryHaikuFallback(failed, kind); Object.assign(results, haikuData); } catch { // Leave failed tickers as empty — they'll show "—" } } return results; }; // ───────────────────────────────────────────────────────────────────────────── // HELPERS // ───────────────────────────────────────────────────────────────────────────── const fr = v => { if (!v && v !== 0) return { txt:"—", cls:"rv-dim" }; const n = parseFloat(v); if (isNaN(n)) return { txt:"—", cls:"rv-dim" }; return { txt:`${n > 0 ? "+" : ""}${n.toFixed(1)}%`, cls:n > 0 ? "rv-pos" : n < 0 ? "rv-neg" : "rv-dim" }; }; const f$ = v => v ? `$${parseFloat(v).toFixed(2)}` : "—"; const fRR = v => v ? `${v.toFixed(2)}R` : "—"; const rrColor = v => !v ? "var(--dim2)" : v >= 3 ? "var(--bull)" : v >= 2 ? "var(--gold2)" : v >= 1 ? "var(--steel)" : "var(--bear)"; const sigBadge = s => s==="LONG"?"bull":s==="SHORT"?"bear":s==="WATCH"?"gold":"dim"; const calcRisk = (entry, sP, t1P, t2P, t3P, dir) => { const e = parseFloat(entry); if (!e || isNaN(e) || e <= 0) return null; const m = dir==="LONG" ? 1 : -1; const stop=e*(1-m*sP/100), t1=e*(1+m*t1P/100), t2=e*(1+m*t2P/100), t3=e*(1+m*t3P/100); const risk=Math.abs(e-stop); if (!risk) return null; return { stop, t1, t2, t3, risk, rr1:Math.abs(t1-e)/risk, rr2:Math.abs(t2-e)/risk, rr3:Math.abs(t3-e)/risk }; }; // ───────────────────────────────────────────────────────────────────────────── // RISK DRAWER // ───────────────────────────────────────────────────────────────────────────── function RiskDrawer({ ticker, dir, tfObj, rs, onChange }) { const { mode="AUTO", entry="", mStop="", mT1="", mT2="", mT3="" } = rs; const set = (k, v) => onChange({ ...rs, [k]: v }); const sP = mode==="AUTO" ? tfObj.stop : (parseFloat(mStop) || tfObj.stop); const t1P = mode==="AUTO" ? tfObj.t1 : (parseFloat(mT1) || tfObj.t1); const t2P = mode==="AUTO" ? tfObj.t2 : (parseFloat(mT2) || tfObj.t2); const t3P = mode==="AUTO" ? tfObj.t3 : (parseFloat(mT3) || tfObj.t3); const c = calcRisk(entry, sP, t1P, t2P, t3P, dir); const bestRR = c?.rr1 || 0; const vrd = bestRR >= 2 ? "go" : bestRR >= 1 ? "wait" : "no"; const vMap = { go:{l:"TAKE THE TRADE",s:"R:R clears minimum threshold",c:"var(--bull)"}, wait:{l:"REVIEW SETUP",s:"Borderline · tighten your entry",c:"var(--gold2)"}, no:{l:"PASS ON IT",s:"R:R too low · wait for better setup",c:"var(--bear)"} }; const V = vMap[vrd]; return (
{ticker} · Risk Management · {dir} · {tfObj.label}
{/* Entry + Mode */}
Entry Price
set("entry",e.target.value)} style={{marginBottom:14}}/>
Stop Mode
{mode==="AUTO" && (
Stop {sP}%
T1 {t1P}% · T2 {t2P}%
T3 {t3P}%
)} {mode==="MANUAL" && <>
Stop %
set("mStop",e.target.value)}/>}
{/* Stop Trigger */}
{dir==="LONG"?"Long":"Short"} Stop Trigger
{dir==="LONG"?"▼ EXIT LONG BELOW":"▲ EXIT SHORT ABOVE"}
{f$(c?.stop)}
Risk/share: {f$(c?.risk)} · {sP}% from entry
{mode==="MANUAL" && <>
Override Stop $
set("mStop",e.target.value)}/>}
{/* Targets */}
Price Targets
{[{l:"T1",p:c?.t1,r:c?.rr1,mk:"mT1",pct:t1P},{l:"T2",p:c?.t2,r:c?.rr2,mk:"mT2",pct:t2P},{l:"T3",p:c?.t3,r:c?.rr3,mk:"mT3",pct:t3P}].map(({l,p,r,mk,pct})=>(
{l}
{f$(p)}
{mode==="MANUAL" && set(mk,e.target.value)}/>}
{fRR(r)}
))}
{/* R:R */}
Risk : Reward
{[{l:"T1 R:R",r:c?.rr1},{l:"T2 R:R",r:c?.rr2},{l:"T3 R:R",r:c?.rr3}].map(({l,r})=>(
=2?"rgba(0,201,167,0.4)":"var(--border)"}}>
{l}
{r?r.toFixed(2):"—"}
))}
≥3R ideal   ≥2R ok   ≥1R min
{/* Verdict */}
Verdict
{V.l}
{V.s}
); } // ───────────────────────────────────────────────────────────────────────────── // STEP 1 — TIMEFRAME // ───────────────────────────────────────────────────────────────────────────── function PageTimeframe({ tf, setTf }) { const sel = TIMEFRAMES.find(t => t.id === tf); return (
Select Trade Timeframe
{TIMEFRAMES.map(t => ( ))}
{sel && ( <>
Auto Risk Levels — {sel.label}
{[{k:"STOP LOSS",v:`${sel.stop}%`,c:"bear"},{k:"TARGET 1",v:`${sel.t1}%`,c:"gold"},{k:"TARGET 2",v:`${sel.t2}%`,c:"blue"},{k:"TARGET 3",v:`${sel.t3}%`,c:"bull"}].map(r=>(
{r.v}
{r.k}
))}
Timeframe Rules
Day Trade: Close before session end. Intraday anchored VWAPs (1AM, 6AM, 8:30AM CT). VScore on 1-min.
1–3 / 3–5 Days: Overnight hold. Daily anchored VWAP is trend guide. Earnings check required.
1–2 Weeks: Weekly swing. Monthly VWAP + POC on daily. No earnings within hold window.
1–3 Months: Position trade. Annual + quarterly VWAP. VScore resets monthly. Full macro alignment.
)}
); } // ───────────────────────────────────────────────────────────────────────────── // STEP 2 — SECTORS // ───────────────────────────────────────────────────────────────────────────── function PageSectors({ selectedId, setSelectedId, sectorData, onFetch, loading, fetchStatus }) { const hasFetched = Object.keys(sectorData).length > 0; const sorted = hasFetched ? [...SECTORS].sort((a,b) => parseFloat(sectorData[b.id]?.w||0) - parseFloat(sectorData[a.id]?.w||0)) : SECTORS; return (
Sector Performance
{loading && fetchStatus && (
{fetchStatus}
)}
{sorted.map((s, rank) => { const d = sectorData[s.id] || {}; const w=fr(d.w), m=fr(d.m), d9=fr(d.d90); const sig = d.signal; const isSel = selectedId === s.id; const selCls = isSel ? (sig==="LONG"?"s-bull":sig==="SHORT"?"s-bear":"s-gold") : ""; return (
setSelectedId(isSel?null:s.id)}> {hasFetched && rank < 3 &&
#{rank+1}
}
{s.etf}
{s.name}
{sig ? {sig} : {s.wt}% S&P}
{[["1W",w],["1M",m],["90D",d9]].map(([lbl,ret])=>(
{lbl}
{loading&&!d.w?"···":ret.txt}
))}
{d.ema8v21 && (
{d.ema8v21==="BULL"?"▲":"▼"} 8/21 {d.vs200==="ABOVE"?"↑":"↓"} 200 MACD {d.macd}
)}
); })}
{hasFetched && ( <>
1-Week Ranking
{sorted.map((s,i)=>{ const d=sectorData[s.id]||{}; const w=fr(d.w); return (
setSelectedId(selectedId===s.id?null:s.id)}> #{i+1} {s.etf} {s.name} {selectedId===s.id&&SELECTED} {w.txt} {d.signal&&{d.signal}}
); })} )}
{selectedId && (
◆ Selected: {SECTORS.find(s=>s.id===selectedId)?.etf} — {SECTORS.find(s=>s.id===selectedId)?.name}
)}
); } // ───────────────────────────────────────────────────────────────────────────── // STEP 3 — DIRECTION // ───────────────────────────────────────────────────────────────────────────── function PageDirection({ dir, setDir, selectedId, sectorData }) { const sec = SECTORS.find(s=>s.id===selectedId); const d = sec ? (sectorData[sec.id]||{}) : {}; const w=fr(d.w), m=fr(d.m), d9=fr(d.d90); return (
{sec && (
{sec.etf} · {sec.name} · Context
{[["1-WEEK",w],["1-MONTH",m],["90-DAY",d9]].map(([lbl,ret])=>(
{ret.txt}
{lbl} RETURN
))}
{d.ema8v21 && (
{d.ema8v21==="BULL"?"▲":"▼"} 8/21 EMA — {d.ema8v21} {d.vs200==="ABOVE"?"↑":"↓"} 200 SMA — {d.vs200} MACD — {d.macd} {d.signal && {d.signal}}
)} {d.setup &&
◆ {d.setup}
}
)}
Select Trade Direction
{dir && ( <>
VWAPMax {dir === "LONG" ? "Long" : "Short"} Criteria
{dir==="LONG" ? ( <>
Above anchored VWAP (yearly + monthly) — institutions net bullish
8 EMA above 21 EMA — short-term momentum confirmed bullish
Above 200-day SMA — long-term trend structure intact
MACD histogram positive or crossing above zero
VScore −2 to 0 — reverting from below mean, not chasing
Best entry: Pullback to VWAP or 34 EMA Wave — never chase the breakout
) : ( <>
Below anchored VWAP (yearly + monthly) — institutions net bearish
8 EMA below 21 EMA — short-term momentum confirmed bearish
Below 200-day SMA — long-term trend structure broken
MACD histogram negative or crossing below zero
VScore 0 to +2 — reverting from above mean, not chasing
Best entry: Rally to VWAP or 34 EMA Wave — never chase the breakdown
)} )}
); } // ───────────────────────────────────────────────────────────────────────────── // STEP 4 — STOCKS // ───────────────────────────────────────────────────────────────────────────── function PageStocks({ tf, dir, selectedId, stockData, onFetch, loading, fetchStatus, comments, onComment, risks, onRisk }) { const [open, setOpen] = useState({}); const sec = SECTORS.find(s=>s.id===selectedId); if (!sec) return
No Sector Selected

Go back to Step 2 and select a sector.

; const tfObj = TIMEFRAMES.find(t=>t.id===tf)||TIMEFRAMES[1]; const sData = stockData[sec.id]||{}; const hasFetched = Object.keys(sData).length > 0; const maxWt = Math.max(...sec.wts); const defaultRs = { mode:"AUTO", entry:"", mStop:"", mT1:"", mT2:"", mT3:"" }; const toggle = t => setOpen(p=>({...p,[t]:!p[t]})); return (
{sec.etf} — {sec.name}
Top 10 Holdings · {tfObj.label} · {dir==="LONG"?"▲":"▼"} {dir}
{loading && fetchStatus && (
{fetchStatus}
)}
{sec.tickers.map((ticker, i) => { const name = sec.names[i], wt = sec.wts[i]; const d = sData[ticker]||{}; const ck = `${sec.id}:${ticker}`; const isOpen = !!open[ticker]; const rs = risks[ck]||defaultRs; const wtPct = (wt/maxWt)*100; const w=fr(d.w), mo=fr(d.m), d9=fr(d.d90); const sig = d.signal||(loading?"…":null); return ( <>
# TICKER NAME WT % TECHNICALS 1 WK 1 MO 90 DAY SIGNAL SETUP NOTE RISK NOTES
{i+1} {ticker} {name}
{wt}%
{loading&&!d.ema8v21 ? ··· : d.ema8v21 ? (
{d.ema8v21==="BULL"?"▲":"▼"} 8/21 {d.ema8v21} {d.vs200==="ABOVE"?"↑":"↓"} 200 {d.vs200} MACD {d.macd}
) : }
{loading&&!d.w?"···":w.txt} {loading&&!d.m?"···":mo.txt} {loading&&!d.d90?"···":d9.txt} {sig?{sig}:} {d.setup||}