<
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<