1/* í v2(ë°°ì ì¹´íë¡ê·¸): íµí© ê²ì, ê°ì·ì§í ì¹´ë, ë°ì´í° 목차, ê±°ëì ì¹´ë, ìì¥ ìì½, 미리보기 */ 2(function () { 3 const { EXCHANGES, fmt } = L; 4 const esc = (x) => 5 String(x ?? "") 6 .replace(/&/g, "&") 7 .replace(/</g, "<") 8 .replace(/>/g, ">") 9 .replace(/"/g, """); 10 11 // ---- ì¹´íë¡ê·¸ ì본 ---- 12 const LESSONS = [ 13 [ 14 "00-mindset-and-chart-analysis", 15 0, 16 "ë§ì¸ë ì ì ê³¼ ì°¨í¸ë¶ìì ì¤ìì±", 17 "기ì¤ì ·ìì¹Â·ëª©í·리ì¤í¬ ê´ë¦¬, 매매 ì ì ê°ì¶°ì¼ í ë¤ì¯ ê°ì§", 18 10, 19 6, 20 ], 21 [ 22 "01-candle-patterns", 23 1, 24 "ìºë¤ í¨í´ì íì©í 매매ë²", 25 "ëì§Â·ì¤í·ì¥ì í·ì¼ë³Â·ì¼ë²í, ë¹í¸ì½ì¸ìì ì주 ëì¤ë í¨í´ê³¼ ì¤ì ëì", 26 21, 27 13, 28 ], 29 [ 30 "02-trend-and-chart-patterns", 31 2, 32 "ì¶ì¸ + ì°¨í¸ í¨í´ì íì©í 매매ë²", 33 "ì¶ì¸ì ì¶ì¸ì , ë³ê³¡ì ì ì½ê³ ë¹í¸ì½ì¸ì ì주 ëì¤ë ì°¨í¸ í¨í´ì¼ë¡ ëìíë ë²", 34 27, 35 16, 36 ], 37 [ 38 "03-volume-and-rsi", 39 3, 40 "ê±°ëëê³¼ RSI ì§í를 íì©í 매매ë²", 41 "RSI 과매ì·과매ëì ë¤ì´ë²ì ì¤, ì íì§íì¸ ê±°ëëì¼ë¡ í¨í´ì ì 뢰ë를 ëì´ë ë²", 42 21, 43 13, 44 ], 45 [ 46 "04-supply-zone-support-resistance", 47 4, 48 "매물ëì ì§ì§Â·ì íì 구조", 49 "ë§¤ë¬¼ì´ ìì¸ êµ¬ê°ì´ ì ê°ì¥ ê°í ì§ì§Â·ì íì´ ëëì§, ì¶ì¸ì ê³¼ ìíì ì¼ë¡ ì½ë ë²", 50 15, 51 9, 52 ], 53 ["05-moving-averages", 5, "ì´íì ì´ ìë ¤ì£¼ë ì í¸", "ì´íì ì ì¢ ë¥ì 기ê°ë³ ì°ì, í¬ë¡ì¤Â·ë°°ì´Â·ì´ê²©ê³¼ ë°ì§ì¼ë¡ ì½ë ì¶ì¸ ì í¸", 12, 7], 54 ["06-dominance", 6, "ë미ëì¤ì ì¤ìì± (ë¹í¸ì½ì¸, í ë)", "ë¹í¸ì½ì¸Â·í ë ë미ëì¤ë¡ ìì¥ ìê¸ì´ ì´ëë¡ í르ëì§ ì½ë ë²", 13, 8], 55 ["07-trading-psychology", 7, "ì¤ì 매매 ì¬ë¦¬ì ë§ì¸ë 컨í¸ë¡¤", "ìì¹Â·ê¸°ë¤ë¦¼Â·ìµê´ì¼ë¡ ìì¬ê³¼ ì¡°ê¸í¨, í¬ëª¨ë¥¼ ì´ê¸°ë ë²", 18, 11], 56 ]; 57 const INDS = [ 58 ["magnus", "Magnus", "매그ëì¤", "ìë ì¶ì¸ ê°ì§ + 구조ì ì§ì§/ì í"], 59 ["solid", "Solid", "ì리ë", "í¸ë ë íì + ë¨ê¸° 리ì ì ë§ì»¤"], 60 ["helper", "Helper", "í¬í¼", "í¼ë² ì§ì§/ì íì + ëí ìê·¸ë"], 61 ["clone", "Clone", "í´ë¡ ", "ê³¼ì´ â ë°ì íë³´ â 구조 íì 3ë¨ê³"], 62 ]; 63 const GUIDES = [ 64 ["/exchange", "ì§ì ê±°ëì · ê°ì ì½ë", "ê±°ëì"], 65 ["/guide/bitget", "Bitget ë í¼ë´ ë³ê²½", "ê°ì´ë"], 66 ["/guide/okx", "OKX ë í¼ë´ ë³ê²½", "ê°ì´ë"], 67 ["/guide/bybit", "Bybit ë í¼ë´ ë³ê²½", "ê°ì´ë"], 68 ["/room", "Alpha Room ìê° Â· ì ì¥ ë°©ë²", "ìë´"], 69 ["/briefing", "Alpha Room ë¸ë¦¬í", "ë¸ë¦¬í"], 70 ["/news", "ì¤ìê° ì½ì¸ ë´ì¤", "ë´ì¤"], 71 ["/channel", "ì°¨í ì±ë 모ì", "ìë´"], 72 ["/review", "í기 · ììµ ì¸ì¦", "ìë´"], 73 ]; 74 75 const $ = (id) => document.getElementById(id); 76 if ($("cp-lessons")) 77 $("cp-lessons").innerHTML = LESSONS.map( 78 ([slug, no, title, sub, pages, mins]) => 79 `<li><a href="/study/${slug}"><b>${no}ê°</b><span class="t">${esc(title)}<small>${esc(sub)}</small></span><em>${pages}ì¥ Â· ì½ ${mins}ë¶</em><i>â</i></a></li>`, 80 ).join(""); 81 if ($("cp-inds")) 82 $("cp-inds").innerHTML = INDS.map( 83 ([slug, name, ko, d]) => 84 `<li><a href="/indicator/${slug}"><img src="/img/cp/ind-${slug}-thumb.jpg" width="132" height="66" loading="lazy" alt="${name}(${ko}) ìê·¸ë ìì"><b>${name}<em>${ko}</em></b><span>${esc(d)}</span><i>â</i></a></li>`, 85 ).join(""); 86 87 // ---- ê±°ëì ---- 88 if ($("cp-ex")) L.renderExRows($("cp-ex")); 89 90 // ---- íµí© ê²ì + ë°ì´í° 목차 (/api/seo ì íì´ì§ ë©í ì¬ì©) ---- 91 let INDEX = [ 92 ...LESSONS.map(([slug, no, title, sub]) => ({ href: `/study/${slug}`, t: `${no}ê° ${title}`, k: "ì°¨í¸ êµì¡", x: sub })), 93 ...INDS.map(([slug, name, ko, d]) => ({ href: `/indicator/${slug}`, t: `${name} (${ko})`, k: "ì ì© ì§í", x: d })), 94 ...GUIDES.map(([href, t, k]) => ({ href, t, k, x: "" })), 95 ]; 96 let PAGES = {}, 97 curTab = 0; 98 const LIVE = ["liq-heatmap", "kimp", "funding", "fear-greed"]; 99 const GLABEL = { ì¤ìê°: "ì¤ìê°", ì²ì°: "ì²ì°", ê³ì½: "ê³ì½", ì§í: "ì§í" }; 100 function renderData() { 101 const el = $("cp-dtiles"); 102 if (!el) return; 103 const list = Object.entries(PAGES).filter(([k]) => !LIVE.includes(k)); 104 el.innerHTML = list 105 .map(([k, v]) => `<a class="cp-dtile" href="/data/${k}"><small>${esc(GLABEL[v.group] || v.group)}</small><b>${esc(v.h1)}</b></a>`) 106 .join(""); 107 } 108 renderData(); 109 fetch("/api/seo") 110 .then((r) => r.json()) 111 .then((r) => { 112 PAGES = r.pages || {}; 113 INDEX = INDEX.concat(Object.entries(PAGES).map(([k, v]) => ({ href: `/data/${k}`, t: v.h1, k: "ì¤ìê° ë°ì´í°", x: v.desc }))); 114 renderData(); 115 }) 116 .catch(() => {}); 117 118 const q = $("cp-q"), 119 box = $("cp-results"), 120 chips = $("cp-chips"); 121 if (q && box) { 122 const CHIPS = ["ìºë¤ í¨í´", "ì²ì° íí¸ë§µ", "ê¹í", "Magnus", "ë í¼ë´ ë³ê²½"]; 123 chips.innerHTML = CHIPS.map((c) => `<button type="button">${c}</button>`).join(""); 124 chips.querySelectorAll("button").forEach( 125 (b) => 126 (b.onclick = () => { 127 q.value = b.textContent; 128 run(); 129 q.focus(); 130 }), 131 ); 132 let sel = -1, 133 hits = []; 134 const norm = (s) => s.toLowerCase().replace(/\s+/g, ""); 135 function run() { 136 const v = norm(q.value); 137 sel = -1; 138 if (!v) { 139 box.hidden = true; 140 box.innerHTML = ""; 141 return; 142 } 143 hits = INDEX.map((it) => { 144 const a = norm(it.t), 145 b = norm(it.x + it.k); 146 return { it, s: a.startsWith(v) ? 3 : a.includes(v) ? 2 : b.includes(v) ? 1 : 0 }; 147 }) 148 .filter((h) => h.s) 149 .sort((a, b) => b.s - a.s) 150 .slice(0, 8) 151 .map((h) => h.it); 152 box.hidden = false;
153 box.innerHTML = hits.length 154 ? hits.map((h, i) => `<a role="option" href="${h.href}" data-i="${i}"><span>${h.k}</span><b>${esc(h.t)}</b><i>â</i></a>`).join("") 155 : `<p>â${esc(q.value)}âì ë§ë ê°ì·ì§í·ë°ì´í°ê° ììµëë¤. ë¤ë¥¸ ë±ë§ë¡ ì°¾ìë³´ì¸ì.</p>`; 156 } 157 const paint = () => box.querySelectorAll("a").forEach((a, i) => a.classList.toggle("on", i === sel)); 158 q.addEventListener("input", run); 159 q.addEventListener("keydown", (e) => { 160 if (e.key === "ArrowDown" || e.key === "ArrowUp") { 161 e.preventDefault(); 162 if (!hits.length) return; 163 sel = (sel + (e.key === "ArrowDown" ? 1 : -1) + hits.length) % hits.length; 164 paint(); 165 } else if (e.key === "Enter") { 166 const h = hits[sel < 0 ? 0 : sel]; 167 if (h) location.href = h.href; 168 } else if (e.key === "Escape") { 169 q.value = ""; 170 run(); 171 } 172 }); 173 document.addEventListener("keydown", (e) => { 174 if (e.key === "/" && document.activeElement !== q && !/INPUT|TEXTAREA|SELECT/.test(document.activeElement.tagName)) { 175 e.preventDefault(); 176 q.focus(); 177 } 178 }); 179 // ìì ë±ë§ì´ ë°ëë placeholder (ìì§ì ì¤ì´ê¸° ì¤ì ì´ë©´ ê³ ì ) 180 if (!matchMedia("(prefers-reduced-motion: reduce)").matches) { 181 const ex = ["ìºë¤ í¨í´", "ê¹í", "Magnus", "ì²ì° íí¸ë§µ", "ì´íì ", "íë©ë¹"]; 182 let n = 0; 183 setInterval(() => { 184 if (document.activeElement !== q && !q.value) q.placeholder = ex[(n = (n + 1) % ex.length)] + "â¦"; 185 }, 2600); 186 } 187 } 188 189 // ---- ë¸ë¦¬í · ë´ì¤ ---- 190 const rel = (ts) => { 191 const m = Math.floor((Date.now() - ts) / 6e4), 192 h = Math.floor(m / 60); 193 return m < 1 ? "ë°©ê¸" : m < 60 ? m + "ë¶ ì " : h < 24 ? h + "ìê° ì " : Math.floor(h / 24) + "ì¼ ì "; 194 }; 195 if ($("home-briefs")) 196 fetch("/api/posts?limit=4") 197 .then((r) => r.json()) 198 .then((r) => { 199 const el = $("home-briefs"); 200 if (!r.posts.length) { 201 el.innerHTML = `<div class="cp-empty"><b>ìì§ ê²ìë ë¸ë¦¬íì´ ììµëë¤</b><p>Alpha Roomì ì ë¸ë¦¬íì´ ì¬ë¼ì¤ë©´ ì´ê³³ì ì ëª©ì´ ë¨¼ì ë³´ì ëë¤. 본문ì UID ì¸ì¦ íììê² ì´ë¦½ëë¤.</p><a href="/room#join">ì ì¥ ë°©ë² ë³´ê¸° â</a></div>`; 202 return; 203 } 204 el.innerHTML = `<ul class="cp-rows">${r.posts.map((p) => `<li><a href="/briefing/${p.slug}"><b>${esc(p.title)}</b><span>${esc(p.topic || "Alpha Room")} · ${rel(p.date)}${p.locked ? " · íì ì ì©" : ""}</span><i>â</i></a></li>`).join("")}</ul>`; 205 }) 206 .catch(() => {}); 207 if ($("home-news")) 208 fetch("/api/news?limit=6") 209 .then((r) => r.json()) 210 .then((r) => { 211 const sm = Object.fromEntries((r.sources || []).map((s) => [s.id, s])); 212 $("home-news").innerHTML = 213 r.news 214 .map( 215 (n) => 216 `<li><a href="${esc(n.link)}" target="_blank" rel="noopener nofollow"><b>${esc(n.title)}</b><span>${esc((sm[n.source] || {}).name || n.source)} · ${rel(n.date)}</span><i>â</i></a></li>`, 217 ) 218 .join("") || `<li class="muted">ì ë´ì¤ê° ììµëë¤.</li>`; 219 }) 220 .catch(() => {}); 221 222 // ---- ì¤ìê° ë°ì´í° ë³´ë(ë©ì¸ v3.1): ì«ì 6칸. ë¹ ë¥¸ ê° 15ì´, ë린 ê° 5ë¶. íì´ ê°ë ¤ì ¸ ìì¼ë©´ ì¼ ---- 223 if ($("cp-live")) { 224 const jf = (u) => 225 fetch(u).then((r) => { 226 if (!r.ok) throw new Error(u + " " + r.status); 227 return r.json(); 228 }); 229 const put = (id, v) => { 230 const el = $(id); 231 if (el && v != null) el.textContent = v; 232 }; 233 const usdB = (v) => "$" + (v / 1e9).toFixed(2) + "B"; 234 async function fast() { 235 const [px, spot, up, fx, cb] = await Promise.allSettled([ 236 jf("https://fapi.binance.com/fapi/v1/premiumIndex?symbol=BTCUSDT"), 237 jf("https://api.binance.com/api/v3/ticker/price?symbol=BTCUSDT"), 238 M.pub("upbitTicker", { markets: "KRW-BTC" }), 239 M.fx(), 240 M.pub("coinbaseTicker", { product: "BTC-USD" }), 241 ]).then((a) => a.map((r) => (r.status === "fulfilled" ? r.value : null))); 242 const usd = spot ? +spot.price : 0; 243 if (up && usd && fx) { 244 put("lv-kimp", fmt.pct((up[0].trade_price / (usd * fx) - 1) * 100)); 245 put("lv-kimp-s", "ì ë¹í¸ " + fmt.krw(up[0].trade_price) + " · íì¨ " + fx.toFixed(0)); 246 } 247 if (px) { 248 const fr = +px.lastFundingRate * 100; 249 put("lv-fund", fmt.pct(fr, 4)); 250 put("lv-fund-s", "ë°ì´ë¸ì¤ BTC · ì°ì¨ " + fmt.pct(fr * 3 * 365, 1)); 251 } 252 if (cb && usd && +cb.price) { 253 const d = +cb.price - usd; 254 put("lv-cb", (d >= 0 ? "+$" : "â$") + Math.abs(d).toFixed(1)); 255 put("lv-cb-s", "ì½ì¸ë² ì´ì¤ $" + fmt.price(+cb.price) + " · " + fmt.pct((d / usd) * 100, 3)); 256 } 257 put("lv-time", new Date().toLocaleTimeString("ko-KR", { hour: "2-digit", minute: "2-digit", second: "2-digit" }) + " 기ì¤"); 258 } 259 async function slow() { 260 M.multiOI("BTC") 261 .then(({ list }) => { 262 if (list.length) { 263 put("lv-oi", usdB(list.reduce((a, b) => a + b.oi, 0))); 264 put("lv-oi-s", "BTC · " + list.map((x) => x.ex).join(" · ")); 265 } 266 }) 267 .catch(() => {}); 268 M.pub("cgGlobal") 269 .then((r) => { 270 const d = r.data; 271 put("lv-dom", d.market_cap_percentage.btc.toFixed(1) + "%"); 272 put( 273 "lv-dom-s", 274 "ETH " + d.market_cap_percentage.eth.toFixed(1) + "% · ì ì²´ ìì´ $" + (d.total_market_cap.usd / 1e12).toFixed(2) + "T", 275 ); 276 }) 277 .catch(() => {}); 278 M.fng(2) 279 .then((d) => { 280 const last = d[d.length - 1], 281 prev = d[d.length - 2]; 282 put("lv-fng", last.v); 283 put( 284 "lv-fng-s", 285 ({ "Extreme Fear": "ê·¹ë¨ì ê³µí¬", Fear: "ê³µí¬", Neutral: "ì¤ë¦½", Greed: "íì", "Extreme Greed": "ê·¹ë¨ì íì" }[last.label] || 286 last.label) + (prev ? " · ì´ì " + prev.v : ""), 287 ); 288 }) 289 .catch(() => {}); 290 } 291 fast().catch(() => {}); 292 slow(); 293 // 첫 ë¡ëë íì ì¤í, ì´í 주기 ê°±ì ë§ íì´ ë³´ì¼ ëë¡ ì í. ë¤ì ë³´ì´ë©´ ë°ë¦° ê°±ì ì ë°ë¡ ì¤í 294 let slowAt = Date.now(); 295 const tickSlow = () => { 296 slowAt = Date.now(); 297 slow(); 298 }; 299 setInterval(() => { 300 if (!document.hidden) fast().catch(() => {}); 301 }, 15000); 302 setInterval(() => { 303 if (!document.hidden) tickSlow(); 304 }, 300e3); 305 document.addEventListener("visibilitychange", () => { 306 if (document.hidden) return; 307 fast().catch(() => {}); 308 if (Date.now() - slowAt > 300e3) tickSlow(); 309 }); 310 } 311 312 // ---- ì¤ìê° ì¤ë ì· ---- 313 const set = (id, fn) => { 314 const el = $(id); 315 if (el) fn(el); 316 }; 317 async function ticker() { 318 try { 319 const [t24, prem, fx] = await Promise.all([M.ticker24(), M.premiumIndex(), M.fx()]); 320 const btc = t24.find((t) => t.symbol === "BTCUSDT"); 321 const p = prem.find((t) => t.symbol === "BTCUSDT"); 322 set("tk-btc", (el) => (el.textContent = "$" + fmt.price(+btc.lastPrice))); 323 set("tk-btc-chg", (el) => (el.textContent = "24h " + fmt.pct(+btc.priceChangePercent))); 324 set("tk-btc-chg", (el) => (el.className = "s mono " + fmt.cls(+btc.priceChangePercent))); 325 const fr = +p.lastFundingRate * 100; 326 set("tk-fund", (el) => (el.textContent = fmt.pct(fr, 4))); 327 set("tk-fund", (el) => (el.className = "v mono " + fmt.cls(fr))); 328 set("tk-fund-sub", (el) => (el.textContent = "ì°ì¨ " + fmt.pct(fr * 3 * 365, 1))); 329 const up = await M.pub("upbitTicker", { markets: "KRW-BTC" });
330 const kimp = (up[0].trade_price / (+btc.lastPrice * fx) - 1) * 100; 331 set("tk-kimp", (el) => (el.textContent = fmt.pct(kimp))); 332 set("tk-kimp", (el) => (el.className = "v mono " + fmt.cls(kimp))); 333 set("tk-kimp-sub", (el) => (el.textContent = "ì ë¹í¸ " + fmt.krw(up[0].trade_price) + " · íì¨ " + fx.toFixed(0))); 334 set( 335 "tk-time", 336 (el) => (el.textContent = new Date().toLocaleTimeString("ko-KR", { hour: "2-digit", minute: "2-digit", second: "2-digit" })), 337 ); 338 } catch (e) { 339 console.warn("ticker", e); 340 } 341 } 342 if (document.getElementById("tk-btc")) { 343 ticker(); 344 setInterval(ticker, 15000); 345 } 346 347 async function fng() { 348 try { 349 const d = await M.fng(60); 350 const last = d[d.length - 1]; 351 if ($("tk-fng")) { 352 set("tk-fng", (el) => (el.textContent = last.v)); 353 set("tk-fng-sub", (el) => (el.textContent = last.label)); 354 } 355 // 미ë ë¼ì¸ 356 const c = $("pv-fng"); 357 if (!c) return; 358 const ctx = c.getContext("2d"), 359 dpr = devicePixelRatio || 1, 360 w = c.clientWidth, 361 h = c.clientHeight; 362 c.width = w * dpr; 363 c.height = h * dpr; 364 ctx.setTransform(dpr, 0, 0, dpr, 0, 0); 365 ctx.fillStyle = getComputedStyle(document.documentElement).getPropertyValue("--paper").trim() || "#fff"; 366 ctx.fillRect(0, 0, w, h); 367 ["rgba(224,53,61,.10)", "rgba(255,96,0,.08)", "rgba(10,157,99,.10)"].forEach((col, i) => { 368 ctx.fillStyle = col; 369 ctx.fillRect(0, (h / 3) * (2 - i), w, h / 3); 370 }); 371 ctx.beginPath(); 372 ctx.strokeStyle = "#ff6000"; 373 ctx.lineWidth = 1.6; 374 d.forEach((p, i) => { 375 const x = (i / (d.length - 1)) * w, 376 y = h - (p.v / 100) * h; 377 i ? ctx.lineTo(x, y) : ctx.moveTo(x, y); 378 }); 379 ctx.stroke(); 380 $("pv-fng-sub").innerHTML = `íì¬ <b>${last.v}</b> · ${last.label} · 60ì¼ ì¶ì´`; 381 } catch (e) { 382 console.warn("fng", e); 383 } 384 } 385 fng(); 386 387 async function previews() { 388 if (!$("pv-kimp")) return; // ë©ì¸ v3 ìë 미리보기 ìì ¯ì´ ìì 389 try { 390 const { rows } = await M.kimpTable(); 391 const top = rows.slice(0, 5); 392 $("pv-kimp").innerHTML = 393 `<table class="dtable" style="font-size:12px">${top.map((r) => `<tr><td>${r.symbol}</td><td>${fmt.krw(r.krw)}</td><td class="${fmt.cls(r.kimp)}">${fmt.pct(r.kimp)}</td></tr>`).join("")}</table>`; 394 } catch (e) { 395 $("pv-kimp").textContent = "ë¡ë ì¤í¨"; 396 } 397 try { 398 const prem = await M.premiumIndex(); 399 const want = ["BTCUSDT", "ETHUSDT", "SOLUSDT", "XRPUSDT", "DOGEUSDT"]; 400 $("pv-fund").innerHTML = `<table class="dtable" style="font-size:12px">${want 401 .map((s) => { 402 const p = prem.find((x) => x.symbol === s); 403 const v = p ? +p.lastFundingRate * 100 : 0; 404 return `<tr><td>${s.slice(0, -4)}</td><td>$${fmt.price(+p.markPrice)}</td><td class="${fmt.cls(v)}">${fmt.pct(v, 4)}</td></tr>`; 405 }) 406 .join("")}</table>`; 407 } catch (e) { 408 $("pv-fund").textContent = "ë¡ë ì¤í¨"; 409 } 410 try { 411 const model = await Liq.build({ symbol: "BTCUSDT", interval: "1h", limit: 300, buckets: 160 }); 412 Liq.mini($("pv-heat"), model); 413 } catch (e) { 414 console.warn("heat preview", e); 415 } 416 } 417 previews(); 418})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.