<?php require_once '/home/www/GNU/_PAGE/head.php'; ?>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>관측 코어 - 거래량 특화형 (V11-Final-Score)</title>
<!-- 웹 폰트 및 아이콘 로드 -->
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet">
<style>
body {
background-color: #020617;
color: #f8fafc;
margin: 0;
padding: 0;
font-family: 'Inter', 'Pretendard', sans-serif;
overflow-y: auto;
user-select: none;
}
#chart-container {
position: relative;
width: 100%;
height: 450px;
border-bottom: 2px solid #1e293b;
background: #020617;
overflow: hidden;
}
canvas {
image-rendering: auto;
display: block;
cursor: grab;
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
z-index: 5;
}
canvas:active { cursor: grabbing; }
.info-overlay {
position: absolute; top: 15px; left: 15px; z-index: 50;
background: rgba(15, 23, 42, 0.9); padding: 18px; border-radius: 4px;
border: 1px solid #334155; pointer-events: auto; backdrop-filter: blur(12px);
cursor: move; width: 340px; box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.5);
}
#depth-bar {
position: absolute; right: 0; top: 0; width: 45px; height: 450px;
z-index: 10; display: flex; flex-direction: column;
border-left: 1px solid #1e293b; background: #020617;
}
.depth-label {
position: absolute; width: 100%; text-align: center;
font-size: 9px; font-weight: bold; color: white;
text-shadow: 1px 1px 2px black; z-index: 11;
}
#bid-label { top: 5px; }
#ask-label { bottom: 5px; }
.status-bar { position: absolute; bottom: 10px; right: 55px; font-size: 10px; color: #475569; display: flex; gap: 15px; z-index: 20; }
.btn-tool { background: #1e293b; color: #94a3b8; padding: 2px 8px; border-radius: 2px; font-size: 10px; border: 1px solid #475569; transition: all 0.2s; cursor: pointer; }
.btn-tool:hover { background: #334155; color: #f8fafc; }
.briefing-card { background: #0f172a; border: 1px solid #1e293b; border-radius: 4px; padding: 12px; display: flex; flex-direction: column; height: 100%; }
#briefing-content { height: 150px; overflow-y: auto; scroll-behavior: smooth; }
#briefing-content::-webkit-scrollbar { width: 4px; }
#briefing-content::-webkit-scrollbar-thumb { background-color: #334155; border-radius: 10px; }
#ratio-monitor, #score-monitor { width: 100%; height: 25px; background: #010409; display: flex; align-items: center; padding: 0 10px; border-bottom: 1px solid #1e293b; }
.ratio-bar-container, .score-bar-container { flex: 1; height: 4px; background: #111827; border-radius: 10px; overflow: hidden; position: relative; border: 1px solid #1e293b; margin: 0 10px; }
#buy-ratio-fill { height: 100%; background: #f43f5e; transition: width 0.5s ease-out; }
#sell-ratio-fill { height: 100%; background: #3b82f6; transition: width 0.5s ease-out; }
#score-fill { position: absolute; left: 50%; height: 100%; width: 0%; transition: all 0.4s cubic-bezier(0.1, 0.7, 1.0, 0.1); }
#log-container { padding: 1.5rem; display: grid; grid-template-columns: repeat(5, 1fr); gap: 1rem; background: #010409; max-height: 250px; overflow: hidden; }
#manual-container { height: 130px; background: #020617; border-top: 1px solid #1e293b; padding: 12px; display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; overflow: hidden; }
.manual-box { background: #0f172a; border: 1px solid #334155; border-radius: 4px; padding: 10px; }
.score-text-block { font-family: 'JetBrains Mono', monospace; font-size: 12px; line-height: 1.4; border-bottom: 1px solid #1e293b; padding-bottom: 8px; margin-bottom: 8px; }
</style>
</head>
<body>
<div id="chart-container">
<div id="depth-bar">
<span id="bid-label" class="depth-label">0%</span>
<div id="bid-wall" style="height: 50%; background: #f43f5e; opacity: 0.6; transition: height 0.5s;"></div>
<div id="ask-wall" style="height: 50%; background: #3b82f6; opacity: 0.6; transition: height 0.5s;"></div>
<span id="ask-label" class="depth-label">0%</span>
</div>
<div id="draggable-info" class="info-overlay shadow-2xl">
<div id="score-text-display" class="score-text-block">
<div class="text-white">종합 점수 : <span id="txt-global-score">+0.00</span></div>
<div class="text-slate-400">거래량 강도 : <span id="txt-vol-strength">+0.00</span></div>
<div class="text-slate-400">비율 강도 : <span id="txt-ratio-strength">+0.00</span></div>
</div>
<div class="flex justify-between items-start gap-6 mb-2">
<h1 class="text-[10px] font-bold text-blue-400 uppercase tracking-widest opacity-80"><i class="fa-solid fa-crosshairs mr-1"></i> 관측 코어 V11-스코어</h1>
<div class="flex gap-1">
<button onclick="exportData()" class="btn-tool" title="데이터 복사"><i class="fa-solid fa-copy"></i></button>
<button onclick="captureCanvas()" class="btn-tool" title="차트 캡처"><i class="fa-solid fa-camera"></i></button>
<button onclick="location.reload()" class="btn-tool" title="새로고침"><i class="fa-solid fa-rotate"></i></button>
</div>
</div>
<div class="flex justify-between items-center mb-1">
<div class="flex items-baseline gap-3">
<span id="price" class="text-4xl font-mono font-bold text-emerald-400 tracking-tighter leading-none">0</span>
<span id="percent" class="text-base text-slate-500 font-mono">0.00%</span>
</div>
<div class="text-right">
<div class="text-[9px] text-slate-500 font-bold uppercase">종합 스코어</div>
<div id="global-score-text" class="text-2xl font-mono font-bold text-slate-400">0.00</div>
</div>
</div>
<div id="stats" class="grid grid-cols-2 gap-x-6 gap-y-2 mt-4 text-[10px] text-slate-500 uppercase font-medium border-t border-slate-800 pt-4">
<div class="flex justify-between"><span>실시간 거래량</span><span id="v-val" class="text-amber-400 font-bold">-</span></div>
<div class="flex justify-between"><span>예상 거래대금</span><span id="val-val" class="text-slate-300">-</span></div>
<div class="flex justify-between"><span>체결 강도</span><span id="speed-val" class="text-emerald-500 font-bold">-</span></div>
<div class="flex justify-between"><span>상하 변동폭</span><span class="text-slate-300"><span id="h-val" class="text-rose-500">-</span>/<span id="l-val" class="text-blue-500">-</span></span></div>
</div>
</div>
<div class="status-bar">
<span id="whale-alert" class="hidden font-bold text-amber-500"><i class="fa-solid fa-whale mr-1"></i>대량 수량 체결</span>
<span id="drag-mode" class="text-amber-500 font-bold hidden"><i class="fa-solid fa-hand-back-point-left mr-1"></i>과거 탐색 중</span>
<span id="alert-text" class="hidden font-bold alert-blink"><i class="fa-solid fa-triangle-exclamation mr-1"></i>거래량 스파이크</span>
<span id="zoom-stat">확대: 120봉</span>
<span id="conn-stat"><span class="inline-block w-2 h-2 bg-emerald-500 rounded-full mr-1"></span>관측 엔진 가동 중</span>
</div>
<canvas id="mainChart"></canvas>
</div>
<div id="ratio-monitor">
<div class="text-[9px] font-bold text-slate-500 uppercase w-32">실시간 거래량 점유율</div>
<div class="ratio-bar-container">
<div id="buy-ratio-fill" style="width: 50%;"></div>
<div id="sell-ratio-fill" style="width: 50%;"></div>
</div>
<div id="ratio-text" class="text-[10px] font-mono text-slate-400 w-32 text-right">50% : 50%</div>
</div>
<div id="score-monitor">
<div class="text-[9px] font-bold text-slate-500 uppercase w-32">종합 모멘텀 지수</div>
<div class="score-bar-container">
<div id="score-fill"></div>
</div>
<div id="score-val-label" class="text-[10px] font-mono text-slate-400 w-32 text-right">0.00</div>
</div>
<div id="log-container">
<div class="briefing-card">
<div class="text-[10px] text-slate-500 mb-1 font-bold uppercase tracking-widest">체결 모멘텀</div>
<div class="flex items-center gap-3 mb-4">
<div id="m-dot" class="momentum-dot bg-slate-700"></div>
<div id="m-text" class="text-xs font-bold text-slate-400">데이터 로드...</div>
</div>
<div class="text-[10px] text-slate-500 mb-1 font-bold uppercase tracking-widest">거래 밀도</div>
<div id="vix-gauge" class="text-xl font-mono font-bold text-emerald-400">0.00</div>
</div>
<div class="briefing-card md:col-span-2 flex flex-col overflow-hidden">
<div class="text-[10px] text-slate-500 mb-2 font-bold uppercase flex justify-between shrink-0">
<span><i class="fa-solid fa-list-ul mr-1"></i> 거래량 변동 브리핑 로그</span>
<span id="last-update" class="text-slate-700">-</span>
</div>
<div id="briefing-content" class="text-sm font-mono space-y-1"></div>
</div>
<div class="briefing-card md:col-span-2">
<div class="text-[10px] text-slate-500 mb-3 font-bold uppercase tracking-widest flex justify-between border-b border-slate-800 pb-2">
<span>실시간 수량 주도권 분석</span>
<span class="text-blue-400 animate-pulse"><i class="fa-solid fa-microchip"></i> 스코어 AI</span>
</div>
<div class="flex-1 flex flex-col justify-between">
<div id="market-sentiment" class="text-base font-bold text-slate-100 leading-tight">거래량 데이터 대기 중...</div>
<div class="grid grid-cols-3 gap-2 mt-4 bg-black/30 p-3 rounded">
<div class="flex flex-col"><span class="val-label">수량비율</span><span id="val-ratio" class="val-data">-</span></div>
<div class="flex flex-col"><span class="val-label">유입속도</span><span id="val-delta" class="val-data">-</span></div>
<div class="flex flex-col"><span class="val-label">체결강도</span><span id="val-speed" class="val-data">-</span></div>
</div>
</div>
</div>
</div>
<div id="manual-container">
<div class="manual-box">
<span class="manual-title"><i class="fa-solid fa-gamepad"></i> 인터랙션 제어</span>
<div class="manual-item">
<p><span class="key-badge">창 이동</span> 상단 정보창 드래그 이동</p>
<p><span class="key-badge">드래그</span> 차트 가로 탐색</p>
<p><span class="key-badge">휠 조작</span> 캔들 확대/축소 (수량 데이터 연동)</p>
</div>
</div>
<div class="manual-box">
<span class="manual-title"><i class="fa-solid fa-chart-line"></i> 거래량 데이터 독해</span>
<div class="manual-item">
<p>■ <span class="text-rose-500 font-bold">비율 바</span>: 현재 구간 내 매수 체결수량 비중</p>
<p>■ <span class="text-amber-400 font-bold">대량 체결</span>: 평균 대비 320% 수량 발생 시 경보</p>
<p>■ <span class="text-emerald-500 font-bold">종합 점수</span>: 거래량 편차와 체결비율 가중 합산</p>
</div>
</div>
<div class="manual-box">
<span class="manual-title"><i class="fa-solid fa-shield-halved"></i> 시스템 시그널 안내</span>
<div class="manual-item">
<p>■ <span class="text-violet-400 font-bold">관성 이탈</span>: 수량 급감과 함께 추세 이탈 감지</p>
<p>■ <span class="text-amber-500 font-bold">스파이크</span>: 실시간 거래량 폭증 로그 기록</p>
<p>■ <span class="text-emerald-500 font-bold">AI 분석</span>: 체결수량 기반 주도 세력 추정</p>
</div>
</div>
</div>
<script>
// 핵심 설정값
const CONFIG = {
SPIKE_FACTOR: 3.2,
WHALE_VOL_THRESHOLD: 40,
MAX_LOGS: 50,
REFRESH_INTERVAL: 1000,
DEPTH_BAR_WIDTH: 45,
BASE_ZOOM: 120,
Y_PADDING: 40 // 상하단 여백 고정
};
const canvas = document.getElementById('mainChart');
const ctx = canvas.getContext('2d');
const chartData = [];
let zoomPoints = CONFIG.BASE_ZOOM, offset = 0, isDragging = false, lastX = 0;
let basePrice = 100000000, sessionStartPrice = 100000000, lastPriceTime = Date.now();
let audioCtx = null, lastBuyRatio = 50;
const dragInfo = document.getElementById('draggable-info');
let isMoving = false, moveStartX, moveStartY;
// 초기화 및 리사이즈 대응
function init() {
const dpr = window.devicePixelRatio || 1;
const container = document.getElementById('chart-container');
const rect = container.getBoundingClientRect();
// 캔버스 크기를 컨테이너 실제 크기와 DPR에 맞춰 설정
canvas.width = rect.width * dpr;
canvas.height = rect.height * dpr;
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.scale(dpr, dpr);
draw();
}
window.addEventListener('resize', init);
function handleUserInteraction() {
if (!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)();
if (audioCtx.state === 'suspended') audioCtx.resume();
}
// 정보창 드래그
dragInfo.addEventListener('mousedown', (e) => {
if (e.target.closest('button')) return;
handleUserInteraction();
isMoving = true;
moveStartX = e.clientX - dragInfo.offsetLeft;
moveStartY = e.clientY - dragInfo.offsetTop;
dragInfo.style.opacity = "0.8";
});
window.addEventListener('mousemove', (e) => {
if (!isMoving) return;
dragInfo.style.left = (e.clientX - moveStartX) + 'px';
dragInfo.style.top = (e.clientY - moveStartY) + 'px';
});
window.addEventListener('mouseup', () => { isMoving = false; dragInfo.style.opacity = "0.9"; });
function playSound(up, intense = false) {
try {
if(!audioCtx || audioCtx.state !== 'running') return;
const osc = audioCtx.createOscillator(), g = audioCtx.createGain();
osc.frequency.setValueAtTime(up ? (intense ? 1200 : 900) : (intense ? 200 : 350), audioCtx.currentTime);
g.gain.setValueAtTime(intense ? 0.03 : 0.015, audioCtx.currentTime);
g.gain.exponentialRampToValueAtTime(0.00001, audioCtx.currentTime + 0.05);
osc.connect(g); g.connect(audioCtx.destination);
osc.start(); osc.stop(audioCtx.currentTime + 0.05);
} catch(e) {}
}
function addBriefing(msg, type = "normal") {
const content = document.getElementById('briefing-content');
const colorMap = { spike: "text-amber-500 font-bold", breakout: "text-violet-400 font-bold", whale: "text-yellow-400 font-bold", normal: "text-slate-400" };
const logEntry = document.createElement('div');
logEntry.className = colorMap[type] || colorMap.normal;
logEntry.innerText = `[${new Date().toLocaleTimeString('ko-KR',{hour12:false})}] ${msg}`;
content.insertBefore(logEntry, content.firstChild);
if(content.children.length > CONFIG.MAX_LOGS) content.removeChild(content.lastChild);
}
function update() {
const last = chartData.length > 0 ? chartData[chartData.length-1].p : basePrice;
const now = Date.now();
const next = last + (Math.random()*600000 - 300000);
const speed = parseFloat((Math.abs(next - last) / (now - lastPriceTime) * 100).toFixed(2));
lastPriceTime = now;
const vol = Math.random()*45+5;
const tradeValue = vol * next;
const currentSide = Math.random() > 0.48 ? 'B' : 'S';
const isSpike = chartData.length > 0 && vol > chartData[chartData.length-1].v * CONFIG.SPIKE_FACTOR;
const isWhale = vol > CONFIG.WHALE_VOL_THRESHOLD;
const viewForB = chartData.slice(-35);
const bMax = viewForB.length ? Math.max(...viewForB.map(d=>d.p)) : next;
const bMin = viewForB.length ? Math.min(...viewForB.map(d=>d.p)) : next;
let isBreakout = (next > bMax + 95000 || next < bMin - 95000);
if (offset > 0) offset++;
chartData.push({ p: next, v: vol, val: tradeValue, s: currentSide, spd: speed, spike: isSpike, breakout: isBreakout, whale: isWhale });
if(chartData.length > 5000) chartData.shift();
document.getElementById('price').innerText = Math.floor(next).toLocaleString();
document.getElementById('percent').innerText = `${(((next/sessionStartPrice)-1)*100).toFixed(2)}%`;
document.getElementById('v-val').innerText = vol.toFixed(2);
document.getElementById('val-val').innerText = (tradeValue / 100000000).toFixed(1) + "억";
document.getElementById('speed-val').innerText = speed;
document.getElementById('vix-gauge').innerText = (speed / 100).toFixed(2);
if(isSpike) addBriefing(`수량 스파이크 감지`, "spike");
if(isBreakout) { addBriefing(`가격 관성 이탈`, "breakout"); playSound(next > last); }
if(isWhale) { addBriefing(`대량 수량 체결 포착: ${vol.toFixed(2)} unit`, "whale"); playSound(next > last, true); }
document.getElementById('whale-alert').classList.toggle('hidden', !isWhale);
document.getElementById('alert-text').classList.toggle('hidden', !isSpike && !isBreakout);
const viewRange = chartData.slice(-(zoomPoints + offset), offset === 0 ? undefined : -offset);
if(viewRange.length > 0) {
const buyV = viewRange.filter(x => x.s === 'B').reduce((a, b) => a + b.v, 0);
const totalV = viewRange.reduce((a, b) => a + b.v, 0);
const buyRatio = totalV > 0 ? (buyV / totalV * 100) : 50;
document.getElementById('buy-ratio-fill').style.width = buyRatio + "%";
document.getElementById('sell-ratio-fill').style.width = (100 - buyRatio) + "%";
document.getElementById('ratio-text').innerText = `${Math.round(buyRatio)}% : ${Math.round(100 - buyRatio)}%`;
// Depth Bar 업데이트 (height 방식)
document.getElementById('bid-wall').style.height = buyRatio + "%";
document.getElementById('ask-wall').style.height = (100 - buyRatio) + "%";
document.getElementById('bid-label').innerText = Math.round(buyRatio) + "%";
document.getElementById('ask-label').innerText = Math.round(100 - buyRatio) + "%";
const avgV = totalV / viewRange.length || 1;
const strength_v = Math.min(1, Math.max(-1, (vol - avgV) / avgV));
const strength_r = Math.min(1, Math.max(-1, (buyRatio - 50) / 50));
const finalScore = Math.min(1, Math.max(-1, (strength_v * 0.6) + (strength_r * 0.4)));
updateScoreUI(finalScore, strength_v, strength_r);
updateMarketSentiment(buyRatio, last, next, speed);
lastBuyRatio = buyRatio;
}
requestAnimationFrame(draw);
}
function updateScoreUI(score, sv, sr) {
const scoreTextEl = document.getElementById('global-score-text');
const scoreFillEl = document.getElementById('score-fill');
const fmt = (v) => (v >= 0 ? "+" : "") + v.toFixed(2);
document.getElementById('txt-global-score').innerText = fmt(score);
document.getElementById('txt-vol-strength').innerText = fmt(sv);
document.getElementById('txt-ratio-strength').innerText = fmt(sr);
scoreTextEl.innerText = score.toFixed(2);
const color = score > 0.1 ? "#f43f5e" : (score < -0.1 ? "#3b82f6" : "#94a3b8");
scoreTextEl.style.color = color;
scoreFillEl.style.background = color;
if(score >= 0) { scoreFillEl.style.left = "50%"; scoreFillEl.style.width = (Math.min(1, score) * 50) + "%"; }
else { const w = Math.min(1, Math.abs(score)) * 50; scoreFillEl.style.left = (50 - w) + "%"; scoreFillEl.style.width = w + "%"; }
document.getElementById('score-val-label').innerText = fmt(score);
}
function updateMarketSentiment(ratio, prevPrice, curPrice, speed) {
const sentimentEl = document.getElementById('market-sentiment');
const delta = ratio - lastBuyRatio;
let dominance = ratio > 58 ? "수량 매수 압도" : (ratio > 52 ? "수량 매수 우위" : (ratio < 42 ? "수량 매도 압도" : (ratio < 48 ? "수량 매도 우위" : "수급 중립")));
sentimentEl.innerText = `${Math.abs(delta) > 2.5 ? "폭발적" : "활발한"} ${dominance}`;
sentimentEl.style.color = ratio > 52 ? "#f43f5e" : (ratio < 48 ? "#3b82f6" : "#f1f5f9");
document.getElementById('val-ratio').innerText = Math.round(ratio) + "%";
document.getElementById('val-delta').innerText = (delta > 0 ? "+" : "") + delta.toFixed(1);
document.getElementById('val-speed').innerText = speed;
}
function draw() {
const dpr = window.devicePixelRatio || 1;
const w = canvas.width / dpr;
const h = canvas.height / dpr;
ctx.clearRect(0, 0, w, h);
const drawWidth = w - CONFIG.DEPTH_BAR_WIDTH;
const startIndex = Math.max(0, chartData.length - zoomPoints - offset);
const view = chartData.slice(startIndex, startIndex + zoomPoints);
if(view.length < 2) return;
const prices = view.map(d => d.p);
const maxP = Math.max(...prices);
const minP = Math.min(...prices);
const range = (maxP - minP) || 1;
const maxV = Math.max(...view.map(d => d.v)) || 1;
const getX = (i) => i * (drawWidth / (view.length - 1));
// Container 기준 패딩 적용된 정밀 Y 좌표
const getY = (p) => (h - CONFIG.Y_PADDING) - ((p - minP) / range * (h - CONFIG.Y_PADDING * 2));
// 그리드
ctx.strokeStyle = '#0f172a';
ctx.lineWidth = 1;
for(let i=0; i<=5; i++){
const gy = CONFIG.Y_PADDING + i * ((h - CONFIG.Y_PADDING * 2) / 5);
ctx.beginPath(); ctx.moveTo(0, gy); ctx.lineTo(drawWidth, gy); ctx.stroke();
}
// 현재가 가이드선
const curP = view[view.length-1].p, curY = getY(curP);
ctx.setLineDash([2, 4]); ctx.strokeStyle = '#64748b';
ctx.beginPath(); ctx.moveTo(0, curY); ctx.lineTo(drawWidth, curY); ctx.stroke();
ctx.setLineDash([]); ctx.font = "11px 'JetBrains Mono'"; ctx.fillStyle = '#10b981';
ctx.fillText(Math.floor(curP).toLocaleString(), drawWidth - 95, curY-6);
// 거래량 막대
view.forEach((d, i) => {
const x = getX(i);
const bw = Math.max(1, (drawWidth / view.length) * 0.8);
const bh = (Math.log10(1 + d.v) / (Math.log10(1 + maxV) * 1.5)) * (h * 0.4);
ctx.fillStyle = d.spike ? '#fbbf24' : (d.s === 'B' ? '#f43f5e' : '#3b82f6');
ctx.globalAlpha = 0.25;
ctx.fillRect(x - (bw/2), h - bh, bw, bh);
ctx.globalAlpha = 1.0;
});
// 가격 라인
ctx.beginPath(); ctx.strokeStyle = '#10b981'; ctx.lineWidth = 2;
view.forEach((d, i) => { if(i===0) ctx.moveTo(getX(i), getY(d.p)); else ctx.lineTo(getX(i), getY(d.p)); });
ctx.stroke();
}
// 인터랙션 (드래그, 휠)
canvas.addEventListener('mousedown', (e) => { handleUserInteraction(); isDragging = true; lastX = e.clientX; });
window.addEventListener('mouseup', () => { isDragging = false; });
window.addEventListener('mousemove', (e) => {
if (!isDragging) return;
const dx = e.clientX - lastX;
if (Math.abs(dx) > 1) {
const sensitivity = zoomPoints / 600;
offset = Math.max(0, Math.min(chartData.length - zoomPoints, offset - Math.round(dx * sensitivity)));
lastX = e.clientX;
document.getElementById('drag-mode').classList.toggle('hidden', offset === 0);
draw();
}
});
canvas.addEventListener('wheel', (e) => {
e.preventDefault(); handleUserInteraction();
zoomPoints = Math.min(2000, Math.max(20, zoomPoints + (e.deltaY > 0 ? 20 : -20)));
offset = Math.max(0, Math.min(chartData.length - zoomPoints, offset));
document.getElementById('zoom-stat').innerText = `확대: ${zoomPoints}봉`;
draw();
}, { passive: false });
// 초기 데이터 및 실행
for(let i=0; i<1200; i++) {
basePrice += (Math.random()*200000-100000);
chartData.push({p:basePrice, v:Math.random()*15+5, s:Math.random()>0.48?'B':'S', spike:false, breakout:false, whale:false});
}
init();
setInterval(update, CONFIG.REFRESH_INTERVAL);
function exportData() { navigator.clipboard.writeText(JSON.stringify(chartData.slice(-zoomPoints))); alert("데이터 복사 완료."); }
function captureCanvas() { const a = document.createElement('a'); a.href = canvas.toDataURL(); a.download = `V11_SCORE_${Date.now()}.png`; a.click(); }
</script>
</body>
</html>
<?php require_once '/home/www/GNU/_PAGE/tail.php'; ?>