< var DB=[ {c:"AAPL",n:"Apple",y:"AAPL",m:"US"},{c:"MSFT",n:"Microsoft",y:"MSFT",m:"US"}, {c:"NVDA",n:"Nvidia",y:"NVDA",m:"US"},{c:"GOOGL",n:"Alphabet",y:"GOOGL",m:"US"}, {c:"AMZN",n:"Amazon",y:"AMZN",m:"US"},{c:"META",n:"Meta",y:"META",m:"US"}, {c:"TSLA",n:"Tesla",y:"TSLA",m:"US"},{c:"AVGO",n:"Broadcom",y:"AVGO",m:"US"}, {c:"NFLX",n:"Netflix",y:"NFLX",m:"US"},{c:"AMD",n:"AMD",y:"AMD",m:"US"}, {c:"INTC",n:"Intel",y:"INTC",m:"US"},{c:"QCOM",n:"Qualcomm",y:"QCOM",m:"US"}, {c:"MU",n:"Micron",y:"MU",m:"US"},{c:"AMAT",n:"Applied Materials",y:"AMAT",m:"US"}, {c:"PLTR",n:"Palantir",y:"PLTR",m:"US"},{c:"COIN",n:"Coinbase",y:"COIN",m:"US"}, {c:"MSTR",n:"MicroStrategy",y:"MSTR",m:"US"},{c:"MARA",n:"MARA Holdings",y:"MARA",m:"US"}, {c:"RIOT",n:"Riot Platforms",y:"RIOT",m:"US"},{c:"GME",n:"GameStop",y:"GME",m:"US"}, {c:"AMC",n:"AMC",y:"AMC",m:"US"},{c:"LCID",n:"Lucid Group",y:"LCID",m:"US"}, {c:"RIVN",n:"Rivian",y:"RIVN",m:"US"},{c:"NKLA",n:"Nikola",y:"NKLA",m:"US"}, {c:"SPCE",n:"Virgin Galactic",y:"SPCE",m:"US"},{c:"JPM",n:"JPMorgan",y:"JPM",m:"US"}, {c:"BAC",n:"Bank of America",y:"BAC",m:"US"},{c:"GS",n:"Goldman Sachs",y:"GS",m:"US"}, {c:"V",n:"Visa",y:"V",m:"US"},{c:"MA",n:"Mastercard",y:"MA",m:"US"}, {c:"WMT",n:"Walmart",y:"WMT",m:"US"},{c:"DIS",n:"Disney",y:"DIS",m:"US"}, {c:"PFE",n:"Pfizer",y:"PFE",m:"US"},{c:"MRNA",n:"Moderna",y:"MRNA",m:"US"}, {c:"XOM",n:"ExxonMobil",y:"XOM",m:"US"},{c:"BABA",n:"Alibaba",y:"BABA",m:"US"}, {c:"NIO",n:"NIO",y:"NIO",m:"US"},{c:"SMCI",n:"Super Micro",y:"SMCI",m:"US"}, {c:"ARM",n:"ARM Holdings",y:"ARM",m:"US"},{c:"ASML",n:"ASML",y:"ASML",m:"US"}, {c:"TSM",n:"TSMC",y:"TSM",m:"US"},{c:"SOUN",n:"SoundHound",y:"SOUN",m:"US"}, {c:"RKLB",n:"Rocket Lab",y:"RKLB",m:"US"},{c:"IONQ",n:"IonQ",y:"IONQ",m:"US"}, {c:"AI",n:"C3.ai",y:"AI",m:"US"},{c:"SOFI",n:"SoFi",y:"SOFI",m:"US"}, {c:"HOOD",n:"Robinhood",y:"HOOD",m:"US"},{c:"ORCL",n:"Oracle",y:"ORCL",m:"US"}, {c:"UBER",n:"Uber",y:"UBER",m:"US"},{c:"COST",n:"Costco",y:"COST",m:"US"}, {c:"RBLX",n:"Roblox",y:"RBLX",m:"US"},{c:"SNAP",n:"Snap",y:"SNAP",m:"US"}, {c:"SPY",n:"S&P500 ETF",y:"SPY",m:"US"},{c:"QQQ",n:"나스닥 ETF",y:"QQQ",m:"US"}, {c:"SQQQ",n:"나스닥 3x 인버스",y:"SQQQ",m:"US"},{c:"TQQQ",n:"나스닥 3x 레버리지",y:"TQQQ",m:"US"}, {c:"ARKK",n:"ARK Innovation",y:"ARKK",m:"US"},{c:"GLD",n:"금 ETF",y:"GLD",m:"US"}, {c:"BRK-B",n:"Berkshire B",y:"BRK-B",m:"US"}, // KR {c:"005930",n:"삼성전자",y:"005930.KS",m:"KR"},{c:"000660",n:"SK하이닉스",y:"000660.KS",m:"KR"}, {c:"005380",n:"현대자동차",y:"005380.KS",m:"KR"},{c:"000270",n:"기아",y:"000270.KS",m:"KR"}, {c:"035420",n:"NAVER",y:"035420.KS",m:"KR"},{c:"035720",n:"카카오",y:"035720.KQ",m:"KR"}, {c:"051910",n:"LG화학",y:"051910.KS",m:"KR"},{c:"006400",n:"삼성SDI",y:"006400.KS",m:"KR"}, {c:"068270",n:"셀트리온",y:"068270.KQ",m:"KR"},{c:"105560",n:"KB금융",y:"105560.KS",m:"KR"}, {c:"055550",n:"신한지주",y:"055550.KS",m:"KR"},{c:"086790",n:"하나금융지주",y:"086790.KS",m:"KR"}, {c:"316140",n:"우리금융지주",y:"316140.KS",m:"KR"},{c:"207940",n:"삼성바이오로직스",y:"207940.KS",m:"KR"}, {c:"373220",n:"LG에너지솔루션",y:"373220.KS",m:"KR"},{c:"005490",n:"포스코홀딩스",y:"005490.KS",m:"KR"}, {c:"012330",n:"현대모비스",y:"012330.KS",m:"KR"},{c:"028260",n:"삼성물산",y:"028260.KS",m:"KR"}, {c:"017670",n:"SK텔레콤",y:"017670.KS",m:"KR"},{c:"066570",n:"LG전자",y:"066570.KS",m:"KR"}, {c:"015760",n:"한국전력",y:"015760.KS",m:"KR"},{c:"030200",n:"KT",y:"030200.KS",m:"KR"}, {c:"003550",n:"LG",y:"003550.KS",m:"KR"},{c:"086520",n:"에코프로",y:"086520.KQ",m:"KR"}, {c:"247540",n:"에코프로비엠",y:"247540.KQ",m:"KR"},{c:"196170",n:"알테오젠",y:"196170.KQ",m:"KR"}, {c:"011200",n:"HMM",y:"011200.KS",m:"KR"},{c:"352820",n:"하이브",y:"352820.KS",m:"KR"}, {c:"041510",n:"에스엠",y:"041510.KQ",m:"KR"},{c:"035900",n:"JYP엔터테인먼트",y:"035900.KQ",m:"KR"}, {c:"122870",n:"YG엔터테인먼트",y:"122870.KQ",m:"KR"},{c:"036570",n:"엔씨소프트",y:"036570.KQ",m:"KR"}, {c:"263750",n:"펄어비스",y:"263750.KQ",m:"KR"},{c:"003490",n:"대한항공",y:"003490.KS",m:"KR"}, {c:"096770",n:"SK이노베이션",y:"096770.KS",m:"KR"},{c:"034020",n:"두산에너빌리티",y:"034020.KS",m:"KR"}, {c:"009540",n:"HD한국조선해양",y:"009540.KS",m:"KR"},{c:"042660",n:"한화오션",y:"042660.KS",m:"KR"}, {c:"012450",n:"한화에어로스페이스",y:"012450.KS",m:"KR"},{c:"003670",n:"포스코퓨처엠",y:"003670.KS",m:"KR"}, {c:"000100",n:"유한양행",y:"000100.KS",m:"KR"},{c:"128940",n:"한미약품",y:"128940.KS",m:"KR"}, {c:"293490",n:"카카오게임즈",y:"293490.KQ",m:"KR"},{c:"323410",n:"카카오뱅크",y:"323410.KQ",m:"KR"}, {c:"139480",n:"이마트",y:"139480.KS",m:"KR"},{c:"145020",n:"휴젤",y:"145020.KQ",m:"KR"}, {c:"214150",n:"클래시스",y:"214150.KQ",m:"KR"},{c:"066970",n:"엘앤에프",y:"066970.KQ",m:"KR"}, {c:"032830",n:"삼성생명",y:"032830.KS",m:"KR"},{c:"009150",n:"삼성전기",y:"009150.KS",m:"KR"}, {c:"259960",n:"크래프톤",y:"259960.KS",m:"KR"},{c:"112040",n:"위메이드",y:"112040.KQ",m:"KR"}, ]; // ═══════════════ STATE ═══════════════ var mkt="US", busy=false, lastR=null, acRes=[], acSel=-1, pend=null, _acT=null; var TOG={sma20:true, sma60:true, pred:true, sm2:true, rsi:true, macd:true, obv:false, cmf:false}; // ═══════════════ UTILS ═══════════════ function fN(v, p) { return v ? v.toFixed(p) : "0.00"; } function toast_(m) { var t = document.getElementById("toast"); if(!t) return; t.textContent = m; t.classList.add("show"); setTimeout(() => t.classList.remove("show"), 2000); } function showSt(id) { ["stE", "stL", "stErr2", "stR"].forEach(s => { var el = document.getElementById(s); if(el) el.style.display = (s === id) ? "flex" : "none"; }); } // ═══════════════ NETWORK (Cloudflare Proxy) ═══════════════ async function proxyFetch(url) { try { // Using the /yf/ proxy defined in _redirects const res = await fetch("/yf/" + url); if (!res.ok) throw new Error("Server Proxy Error"); return await res.json(); } catch (e) { console.error("Network Error:", e); throw e; } } async function ySearch(q) { var lang = mkt === "KR" ? "ko-KR" : "en-US"; var url = "v1/finance/search?q=" + encodeURIComponent(q) + "&lang=" + lang + ""esCount=10"; try { const d = await proxyFetch(url); if (!d || !d.quotes) return []; return d.quotes.filter(x => x.symbol && x.quoteType && x.quoteType !== "CURRENCY").map(x => { var isKR = x.symbol.includes(".KS") || x.symbol.includes(".KQ") || (x.exchange && ["KSC","KOE","KSE","KOEQ"].includes(x.exchange)); if (mkt === "US" && isKR) return null; if (mkt === "KR" && !isKR) return null; var sym = x.symbol; var short = x.shortname || x.longname || x.symbol; return { c: sym.replace(/\.(KS|KQ)$/, ""), n: short, y: sym, m: isKR ? "KR" : "US" }; }).filter(Boolean); } catch (e) { return []; } } async function yFetch(sym) { var url = "v8/finance/chart/" + encodeURIComponent(sym) + "?interval=1d&range=6mo&includePrePost=false"; try { const d = await proxyFetch(url); let res = (d && d.chart && d.chart.result) ? d.chart.result[0] : (d && d.result ? d.result[0] : null); if (res) return { chart: { result: [res] } }; } catch (e) { } throw new Error("데이터 로드 실패"); } // ═══════════════ UI INTERACTION ═══════════════ function setMkt(m) { mkt = m; document.getElementById("bUS").className = "mkt-btn" + (m === "US" ? " on" : ""); document.getElementById("bKR").className = "mkt-btn" + (m === "KR" ? " on" : ""); document.getElementById("si").value = ""; pend = null; closeAc(); } function doSearch(q) { if (!q) return []; var ql = q.toLowerCase().replace(/\s/g, ""); return DB.filter(s => s.m === mkt && (s.c.toLowerCase().startsWith(ql) || s.n.toLowerCase().includes(ql))).slice(0, 8); } function renderAc(list) { var ac = document.getElementById("ac"); if (!list || !list.length) { ac.style.display = "none"; return; } var html = list.map((s, i) => `<<${s.m==="US"?"🇺🇸":"🇰🇷"}<${s.c}<${s.n}<${s.m}`).join(""); ac.innerHTML = html; ac.style.display = "block"; ac.querySelectorAll(".aci").forEach((el, i) => { el.onclick = () => pickAc(i); el.onmouseover = () => { acSel = i; hiAc(); }; }); hiAc(); } function hiAc() { document.querySelectorAll(".aci").forEach((el, i) => el.classList.toggle("sel", i === acSel)); } function pickAc(i) { var s = acRes[i]; if (!s) return; pend = s; document.getElementById("si").value = s.n + " (" + s.c + ")"; closeAc(); doAnalyze(); } function closeAc() { var ac = document.getElementById("ac"); if(ac) ac.style.display = "none"; acSel = -1; } document.getElementById("si").addEventListener("input", function(e) { pend = null; var q = e.target.value.trim(); if (!q) { closeAc(); return; } acRes = doSearch(q); acSel = -1; renderAc(acRes); clearTimeout(_acT); _acT = setTimeout(async () => { const live = await ySearch(q); if (live && live.length) { const ex = live.filter(l => !acRes.find(r => r.c === l.c)); if (ex.length) { acRes = acRes.concat(ex).slice(0, 8); renderAc(acRes); } } }, 350); }); document.getElementById("si").addEventListener("keydown", function(e) { if (e.key === "ArrowDown") { e.preventDefault(); acSel = Math.min(acSel + 1, acRes.length - 1); hiAc(); } else if (e.key === "ArrowUp") { e.preventDefault(); acSel = Math.max(acSel - 1, -1); hiAc(); } else if (e.key === "Enter") { e.preventDefault(); if (acSel >= 0) pickAc(acSel); else { closeAc(); doAnalyze(); } } else if (e.key === "Escape") closeAc(); }); async function doAnalyze() { if (busy) return; closeAc(); var stock = pend || findStock(document.getElementById("si").value.trim()); if (!stock) { toast_("종목을 찾을 수 없습니다"); return; } try { busy = true; var btn = document.getElementById("abtn"); btn.disabled = true; btn.textContent = "분석 중..."; showSt("stL"); document.getElementById("lt").textContent = "FETCHING DATA..."; var symbol = stock.y || stock.c; if (stock.m === "KR" && !symbol.includes(".")) symbol += ".KS"; const data = await yFetch(symbol); const cl = data.chart.result[0].quotes.close; const hi = data.chart.result[0].quotes.high; const lo = data.chart.result[0].quotes.low; const op = data.chart.result[0].quotes.open; const vl = data.chart.result[0].quotes.volume; const obv = calcOBV(cl, vl); const cmf = calcCMF(hi, lo, cl, vl); const sma20 = calcSMA(cl, 20); const sma60 = calcSMA(cl, 60); const rsi = calcRSI(cl); const macd = calcMACD(cl); const vp = calcVP(cl, vl); const wy = detectWy(cl, vl, obv); const sm = detectSM(cl, vl, obv, cmf, hi, lo); const candles = analyzeCandles(op, hi, lo, cl); const pred = predictFuture(cl); const accScore = calcAcc(cl, vl, obv, cmf, wy.idx); renderAll(stock, data, { cl, hi, lo, op, vl, obv, cmf, sma20, sma60, rsi, macd, vp, wy, sm, candles, pred, accScore }); showSt("stR"); toast_("분석 완료"); } catch (e) { console.error(e); showSt("stErr2"); document.getElementById("em2").textContent = "로드 실패: " + e.message; } finally { busy = false; var btn = document.getElementById("abtn"); btn.disabled = false; btn.textContent = "분석 ▶"; } } function findStock(q) { if(!q) return null; var ql = q.toLowerCase(); for (var i = 0; i << DB DB.length; i++) { var s = DB[i]; if (s.m === mkt && (s.c.toLowerCase() === ql || s.n.toLowerCase().includes(ql))) return s; } return { c: q, n: q, y: q, m: mkt, _search: true }; } function renderAll(stock, rawData, ind) { const { cl, hi, lo, op, vl, obv, cmf, sma20, sma60, rsi, macd, vp, wy, sm, candles, pred, accScore } = ind; const cur = cl[cl.length - 1]; const prev = cl[cl.length - 2]; const chg = cur - prev; const pct = (chg / prev) * 100; document.getElementById("rSym").textContent = stock.c; document.getElementById("rName").textContent = stock.n; document.getElementById("rExch").textContent = stock.m === "US" ? "NASDAQ/NYSE" : "KOSPI/KOSDAQ"; document.getElementById("rPr").textContent = fN(cur, 2); document.getElementById("rCh").textContent = (chg >= 0 ? "▲ " : "▼ ") + fN(Math.abs(pct), 2) + "%"; document.getElementById("rCh").style.color = pct >= 0 ? "var(--grn)" : "var(--red)"; document.getElementById("rTs").textContent = "Updated: " + new Date().toLocaleTimeString(); document.getElementById("rSc").textContent = accScore + " / 100"; document.getElementById("rBa").style.width = accScore + "%"; document.getElementById("rBa").style.background = accScore > 70 ? "var(--grn)" : (accScore > 40 ? "var(--amb)" : "var(--red)"); document.getElementById("rVd").textContent = "종합 지표 분석 결과, 현재 세력 점수는 " + accScore + "점입니다."; const metrics = [ { l: "RSI", v: fN(rsi[rsi.length-1], 2), c: rsi[rsi.length-1] > 70 ? "var(--red)" : (rsi[rsi.length-1] << 30 ? "var(--grn)" : "var(--acc)") }, { l: "SMA20", v: fN(sma20[sma20.length-1], 2), c: cur > sma20[sma20.length-1] ? "var(--grn)" : "var(--red)" }, { l: "MACD", v: fN(macd.m[macd.m.length-1], 2), c: macd.m[macd.m.length-1] > 0 ? "var(--grn)" : "var(--red)" }, { l: "OBV", v: "SENSING", c: "var(--acc)" } ]; document.getElementById("rMet").innerHTML = metrics.map(m => `<<${m.l}<${m.v}`).join(""); const min = Math.min(...cl); const max = Math.max(...cl); const pos = ((cur - min) / (max - min)) * 100; document.getElementById("r52").innerHTML = `<52주 저가: ${fN(min,2)} / 고가: ${fN(max,2)}<<`; document.getElementById("rWy").innerHTML = `<${wy.label}<현재 사이클: ${wy.phase}`; document.getElementById("rSmT").innerHTML = `<${sm.trend}`; document.getElementById("rSmS").innerHTML = sm.sigs.slice(-5).reverse().map(s => `<<${s.signal==='inflow'?'🚀':(s.signal==='outflow'?'📉':'🔍')}<<${s.desc}<가격: ${fN(s.price,2)}`).join(""); renderChart(cl, sma20, sma60, rsi, macd, obv, pred); } function renderChart(cl, s20, s60, rsi, macd, obv, pred) { const canvas = document.getElementById("cv"); if(!canvas) return; const ctx = canvas.getContext("2d"); const w = canvas.parentElement.clientWidth; const h = 300; canvas.width = w; canvas.height = h; ctx.clearRect(0,0,w,h); const min = Math.min(...cl); const max = Math.max(...cl); const range = max - min; const step = w / cl.length; ctx.beginPath(); ctx.strokeStyle = "#6C63FF"; ctx.lineWidth = 2; for(let i=0; i<