GNU/_PAGE/chart/upbit/whale/quantum_flow.php
<?php
/**
 * BTC 자금 흐름 분석 시스템 (V5.15 - 스코어보드 색상 직관성 강화)
 * - 구조 유지, 코드 유지, 코드 구조 수정 절대 금지
 * - Global Score 색상 보정: 양수(+) 녹색 / 음수(-) 적색 적용
 * - HUD 폰트 13px 및 상태 문구 정자체 유지
 * - collected_ms 기반 인덱스 쿼리 및 런타임 Null Guard 유지
 */

set_time_limit(30);
error_reporting(E_ALL & ~E_NOTICE);
ini_set('display_errors', 1);

require_once("/home/www/DB/db_upbit.php");

if (!isset($db_upbit)) {
    die("데이터베이스 연결 실패");
}

$target_market = 'KRW-BTC';
$intervals = [
    1     => ['label' => '1분봉 기준',    'table' => 'daemon_upbit_coin_1m'],
    5     => ['label' => '5분봉 기준',    'table' => 'daemon_upbit_coin_1m'],
    15    => ['label' => '15분봉 기준',   'table' => 'daemon_upbit_coin_1m'],
    30    => ['label' => '30분봉 기준',   'table' => 'daemon_upbit_coin_1m'],
    60    => ['label' => '1시간 기준',    'table' => 'daemon_upbit_coin_1m'],
    240   => ['label' => '4시간 기준',    'table' => 'daemon_upbit_coin_1m'],
    480   => ['label' => '8시간 기준',    'table' => 'daemon_upbit_coin_5m'],
    720   => ['label' => '12시간 기준',   'table' => 'daemon_upbit_coin_5m'], 
    1440  => ['label' => '24시간 기준',   'table' => 'daemon_upbit_coin_5m'],
    10080 => ['label' => '주봉(7D) 기준',  'table' => 'daemon_upbit_coin_24h'],
    43200 => ['label' => '월봉(30D) 기준', 'table' => 'daemon_upbit_coin_24h']
];

$WEIGHTS = [
    '1분봉 기준'     => 6,
    '5분봉 기준'     => 10,
    '15분봉 기준'    => 8,
    '30분봉 기준'    => 5,
    '1시간 기준'     => 18,
    '4시간 기준'     => 22,
    '8시간 기준'     => 5,
    '12시간 기준'    => 5,
    '24시간 기준'    => 15,
    '주봉(7D) 기준'  => 2,
    '월봉(30D) 기준' => 2,
];

$short_labels = [
    1=>"1m", 5=>"5m", 15=>"15m", 30=>"30m", 60=>"1h", 240=>"4h", 480=>"8h", 720=>"12h", 1440=>"24h", 10080=>"1w", 43200=>"1M"
];

function get_aggregated_data($db, $target_market, $intervals) {
    $now_ms = round(microtime(true) * 1000);
    $cut24_ms = $now_ms - (24 * 60 * 60 * 1000);
    $sql_24h = "SELECT SUM(tr_trade_price * tr_trade_volume) AS amt_24h FROM daemon_upbit_coin_24h WHERE market = ? AND collected_ms >= ?";
    $stmt_24h = $db->prepare($sql_24h);
    $stmt_24h->execute([$target_market, $cut24_ms]);
    $amt_24h_total = (float)($stmt_24h->fetch(PDO::FETCH_ASSOC)['amt_24h'] ?? 0);

    $results = [];
    foreach ($intervals as $n_min => $info) {
        $n_min = (int)$n_min;
        $cut_ms = $now_ms - ($n_min * 60 * 1000);
        $sql = "SELECT MAX(korean_name) AS kname, SUM(tr_trade_price * tr_trade_volume) AS amt, SUM(tr_trade_volume) AS vol, SUM(CASE WHEN tr_ask_bid='BID' THEN tr_trade_price * tr_trade_volume ELSE 0 END) AS buy_amt, SUM(CASE WHEN tr_ask_bid='ASK' THEN tr_trade_price * tr_trade_volume ELSE 0 END) AS sell_amt, AVG(signed_change_rate) AS avg_rate, MAX(highest_52_week_price) AS h52, MAX(lowest_52_week_price) AS l52, MAX(trade_price) AS cp FROM {$info['table']} WHERE market = ? AND collected_ms >= ?";
        $stmt = $db->prepare($sql);
        $stmt->execute([$target_market, $cut_ms]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        $total_amt = (float)($row['amt'] ?? 0); $buy_amt = (float)($row['buy_amt'] ?? 0); $sell_amt = (float)($row['sell_amt'] ?? 0); $net_flow = $buy_amt - $sell_amt;
        $h52 = (float)($row['h52'] ?? 0); $l52 = (float)($row['l52'] ?? 0); $cp = (float)($row['cp'] ?? 0); $diff_range = $h52 - $l52; $pos_52w = ($diff_range > 0) ? (($cp - $l52) / $diff_range) * 100 : 0;
        $results[$n_min] = [
            'label' => $info['label'], 'total_amt' => $total_amt, 'volume' => (float)($row['vol'] ?? 0), 'net_flow' => $net_flow, 'buy_ratio' => ($buy_amt + $sell_amt > 0) ? ($buy_amt / ($buy_amt + $sell_amt)) * 100 : 0, 'ratio_24h' => ($amt_24h_total > 0) ? ($total_amt / $amt_24h_total) * 100 : 0, 'imbalance' => ($total_amt > 0) ? ($net_flow / $total_amt) * 100 : 0, 'avg_rate' => ((float)($row['avg_rate'] ?? 0)) * 100, 'pos_52w' => $pos_52w, 'h52' => $h52, 'l52' => $l52, 'is_whale' => ($net_flow > 0 && $pos_52w < 15), 'is_pump' => (($total_amt > 0 ? ($net_flow / $total_amt) * 100 : 0) > 30)
        ];
    }
    return $results;
}

if (isset($_GET['mode']) && $_GET['mode'] === 'update') {
    header('Content-Type: application/json; charset=utf-8');
    $payload = ['sync_time' => date("Y-m-d H:i:s"), 'data' => get_aggregated_data($db_upbit, $target_market, $intervals)];
    echo json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    exit;
}

$initial_data = get_aggregated_data($db_upbit, $target_market, $intervals);

$global_weight_sum = 0; $global_score_sum = 0.0; $initial_details_html = '';
foreach ($initial_data as $n_min => $r) {
    $w = (int)($WEIGHTS[$r['label']] ?? 0);
    if ($w <= 0) continue;
    $lbl = $short_labels[$n_min] ?? $n_min;
    $strength = ($r['total_amt'] > 0) ? $r['net_flow'] / $r['total_amt'] : 0.0;
    $strength = max(-1, min(1, $strength)); $weighted = $strength * $w;
    $global_score_sum += $weighted; $global_weight_sum += $w;
    $color = $strength > 0 ? 'text-emerald-500' : ($strength < 0 ? 'text-rose-500' : 'text-slate-500');
    $initial_details_html .= "<span class='inline-block mr-2'><span class='text-slate-600'>{$lbl}:</span><span class='{$color}'>".sprintf("%+.2f", $strength)."</span><span class='text-slate-700'>×{$w}=</span><span class='{$color}'>".sprintf("%+.2f", $weighted)."</span></span>";
}
$GLOBAL_SCORE = ($global_weight_sum > 0) ? ($global_score_sum / $global_weight_sum) : 0.0;
$GLOBAL_SCORE_PCT = $GLOBAL_SCORE * 100.0;
$initial_summary_html = "<span class='text-sky-500 px-2'>[ <i class='fa-solid fa-layer-group mr-1'></i>가중합:".sprintf("%+.2f", $global_score_sum)." / 가중치합:{$global_weight_sum} = ".sprintf("%+.4f", $GLOBAL_SCORE)." ]</span>";
?>
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>BTC 자금 흐름 분석 v5.15</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700;900&family=JetBrains+Mono:wght@500;800&display=swap" rel="stylesheet">
    <style>
        :root { --terminal-bg: #020617; --card-bg: rgba(15, 23, 42, 0.8); --neon-blue: #38bdf8; --neon-emerald: #10b981; --neon-rose: #f43f5e; }
        body { background: var(--terminal-bg); color: #f1f5f9; font-family: 'Inter', sans-serif; margin: 0; padding: 0; overflow-x: hidden; }
        .wrapper { width: 100%; padding: 40px 50px; box-sizing: border-box; opacity: 0; transform: translateY(20px); transition: all 0.8s ease-out; }
        .wrapper.loaded { opacity: 1; transform: translateY(0); }
        .font-mono { font-family: 'JetBrains Mono', monospace; }
        .whale-bg { background: rgba(16, 185, 129, 0.1) !important; border-left: 4px solid var(--neon-emerald); }
        .pump-bg { background: rgba(244, 63, 94, 0.1) !important; border-left: 4px solid var(--neon-rose); }
        .update-flash { animation: flash 0.8s ease-out; }
        @keyframes flash { from { background: rgba(56, 189, 248, 0.2); } to { background: transparent; } }
        #preloader { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: #020617; z-index: 9999; display: flex; flex-direction: column; align-items: center; justify-content: center; transition: opacity 0.5s ease; }
        .loader-box { width: 40px; height: 40px; border: 3px solid rgba(56, 189, 248, 0.1); border-top-color: var(--neon-blue); border-radius: 50%; animation: spin 1s linear infinite; }
        @keyframes spin { to { transform: rotate(360deg); } }
        .glass { background: var(--card-bg); backdrop-filter: blur(10px); border: 1px solid rgba(255,255,255,0.05); }
        .scoreboard-glow { box-shadow: 0 0 30px rgba(56, 189, 248, 0.1); }
        #score-hud-panel { background: rgba(0, 0, 0, 0.4); border: 1px solid rgba(56, 189, 248, 0.15); border-radius: 12px; padding: 12px 18px; min-width: 450px; max-width: 900px; box-shadow: inset 0 0 10px rgba(0,0,0,0.5); }
        .hud-label { color: #475569; font-size: 9px; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 4px; display: block; font-weight: 900; }
        #score-details-content { font-size: 13px; line-height: 1.6; }
        #score-desc { font-style: normal !important; text-transform: uppercase; }
    </style>
</head>
<body>

    <div id="preloader">
        <div class="loader-box"></div>
        <div class="mt-4 font-mono text-[10px] tracking-[0.3em] text-sky-500 uppercase animate-pulse">Initializing Data Stream</div>
    </div>

    <div class="wrapper" id="main-content">
        <header class="flex justify-between items-end mb-8">
            <div>
                <h1 class="text-4xl font-black tracking-tighter text-white">
                    <i class="fa-solid fa-bolt-lightning text-sky-500 mr-3"></i>BTC <span class="text-sky-500">퀀텀</span> 플로우
                </h1>
                <p class="text-slate-400 font-medium mt-1 uppercase text-xs tracking-widest">분석 터미널 v5.15 / 색상 동기화 모드</p>
            </div>
            <div class="text-right">
                <div class="text-[11px] font-mono text-slate-500">
                    <i class="fa-solid fa-microchip mr-1"></i>상태: <span id="sync-time"><?=date("Y-m-d H:i:s")?></span>
                </div>
                <div class="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-emerald-500/10 text-emerald-400 text-[10px] font-bold mt-2 uppercase border border-emerald-500/20">
                    <span class="relative flex h-2 w-2">
                      <span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
                      <span class="relative inline-flex rounded-full h-2 w-2 bg-emerald-500"></span>
                    </span>
                    실시간 스트림 무결성 확인됨
                </div>
            </div>
        </header>

        <!-- 상단 종합 점수판 (색상 직관성 강화) -->
        <?php
        // [수정] 양수(+)면 녹색, 음수(-)면 적색 적용
        $scoreClass = 'text-slate-400'; 
        if ($GLOBAL_SCORE_PCT > 0) $scoreClass = 'text-emerald-400';
        else if ($GLOBAL_SCORE_PCT < 0) $scoreClass = 'text-rose-400';

        $scoreDesc = '중립 상태';
        if ($GLOBAL_SCORE >= 0.15) $scoreDesc = '매수세 누적 중';
        if ($GLOBAL_SCORE >= 0.60) $scoreDesc = '강력한 매수 압력';
        if ($GLOBAL_SCORE <= -0.15) $scoreDesc = '매도세 분산 중';
        if ($GLOBAL_SCORE <= -0.60) $scoreDesc = '강력한 매도 패닉';
        ?>
        <div id="global-scoreboard" class="mb-6 p-8 rounded-2xl glass scoreboard-glow flex flex-col border-l-4 border-l-sky-500">
            <div class="flex items-center justify-between w-full">
                <div class="flex items-center gap-8">
                    <div class="flex flex-col">
                        <div class="text-[10px] font-black tracking-[0.4em] text-slate-500 uppercase mb-2">Global Net Flow Index</div>
                        <div id="score-val" class="font-black font-mono leading-none <?=$scoreClass?> text-7xl lg:text-8xl tracking-tighter">
                            <?=($GLOBAL_SCORE_PCT>=0?'+':'').number_format($GLOBAL_SCORE_PCT,2)?>%
                        </div>
                    </div>
                    
                    <div id="score-hud-panel" class="self-end mb-1">
                        <span class="hud-label"><i class="fa-solid fa-calculator mr-1"></i> Calculation Logs (Strength × Weight)</span>
                        <div id="score-details-content" class="font-mono leading-relaxed">
                            <?= $initial_details_html ?>
                            <div id="score-summary-line" class="mt-2 pt-2 border-t border-slate-800/50">
                                <?= $initial_summary_html ?>
                            </div>
                        </div>
                    </div>
                </div>
                
                <div class="text-right">
                    <div id="score-desc" class="text-3xl font-black text-white uppercase"><?=$scoreDesc?></div>
                    <div class="text-[10px] mt-3 text-slate-500 font-mono flex items-center justify-end gap-2 uppercase">
                        <i class="fa-solid fa-server text-sky-500"></i> Quantum Engine v5.15
                    </div>
                </div>
            </div>
        </div>

        <div class="overflow-hidden rounded-2xl border border-slate-800 bg-slate-900/50 shadow-2xl">
            <table class="w-full text-right" id="data-table">
                <thead class="text-slate-400 text-[10px] font-black uppercase bg-slate-800/80 tracking-widest">
                    <tr>
                        <th class="p-5 text-left border-b border-slate-800">분석 구간</th>
                        <th class="p-5 border-b border-slate-800">거래대금 (KRW)</th>
                        <th class="p-5 border-b border-slate-800">순자금 유입</th>
                        <th class="p-5 border-b border-slate-800">매수 비중</th>
                        <th class="p-5 border-b border-slate-800">24H 집중도</th>
                        <th class="p-5 border-b border-slate-800">불균형 지수</th>
                        <th class="p-5 border-b border-slate-800">평균 변화율</th>
                        <th class="p-5 border-b border-slate-800 text-center">52주 최고/최저</th>
                    </tr>
                </thead>
                <tbody class="divide-y divide-slate-800">
                    <?php foreach ($initial_data as $n_min => $row): ?>
                    <tr id="row-<?= $n_min ?>" class="transition-colors hover:bg-white/5 <?= $row['is_whale'] ? 'whale-bg' : ($row['is_pump'] ? 'pump-bg' : '') ?>">
                        <td class="p-5 text-left font-bold border-r border-slate-800/50">
                            <div class="flex items-center gap-3">
                                <i class="fa-solid fa-clock-rotate-left text-sky-500 text-xs"></i>
                                <div>
                                    <div class="text-sky-400 text-xs font-black"><?= $row['label'] ?></div>
                                    <div class="text-[10px] text-slate-500 font-mono tracking-tighter">KRW-BTC</div>
                                </div>
                            </div>
                        </td>
                        <td class="p-5 font-mono font-extrabold text-slate-300 val-amt"><?= number_format($row['total_amt'] / 1e6, 2) ?>M</td>
                        <td class="p-5 font-mono font-bold val-net <?= $row['net_flow'] >= 0 ? 'text-emerald-400' : 'text-rose-400' ?>">
                            <?= ($row['net_flow'] > 0 ? '+' : '') . number_format($row['net_flow'] / 1e6, 2) ?>M
                        </td>
                        <td class="p-5 font-mono font-bold val-buyratio <?= $row['buy_ratio'] >= 50 ? 'text-emerald-400' : 'text-rose-400' ?>"><?= number_format($row['buy_ratio'], 1) ?>%</td>
                        <td class="p-5 font-mono text-amber-400 font-bold val-ratio24"><?= number_format($row['ratio_24h'], 1) ?>%</td>
                        <td class="p-5 font-mono val-imb"><?= number_format($row['imbalance'], 1) ?>%</td>
                        <td class="p-5 font-mono val-avg"><?= number_format($row['avg_rate'], 2) ?>%</td>
                        <td class="p-5 font-mono val-pos min-w-[200px]">
                            <div class="flex flex-col gap-1">
                                <div class="flex justify-between text-[9px] text-slate-500 uppercase tracking-tighter">
                                    <span>L: <span class="val-l52"><?= $row['l52'] > 0 ? number_format($row['l52']) : 'N/A' ?></span></span>
                                    <span>H: <span class="val-h52"><?= $row['h52'] > 0 ? number_format($row['h52']) : 'N/A' ?></span></span>
                                </div>
                                <div class="w-full bg-slate-800 h-1.5 rounded-full overflow-hidden border border-slate-700">
                                    <div class="bg-sky-500 h-full shadow-[0_0_10px_#38bdf8]" style="width: <?= min(100, $row['pos_52w']) ?>%"></div>
                                </div>
                                <div class="text-right pos-text text-[10px] font-bold"><?= number_format($row['pos_52w'], 1) ?>%</div>
                            </div>
                        </td>
                    </tr>
                    <?php endforeach; ?>
                </tbody>
            </table>
        </div>

        <footer class="grid grid-cols-2 gap-8 mt-10">
            <div class="p-6 glass rounded-2xl border-l-4 border-l-sky-500">
                <strong class="text-sky-400 block mb-2 uppercase text-xs tracking-widest"><i class="fa-solid fa-circle-info mr-2"></i>시스템 프로토콜</strong>
                <p class="text-slate-400 text-xs leading-relaxed font-medium">
                    본 데이터는 10초마다 갱신되며, 상단 HUD를 통해 각 구간별 지표 기여도를 실시간으로 모니터링할 수 있습니다. 
                    색상 인디케이터는 순자금의 유입(+)과 유출(-)을 즉각적으로 시각화합니다.
                </p>
            </div>
            <div class="p-6 glass rounded-2xl border-l-4 border-l-amber-500">
                <strong class="text-amber-400 block mb-2 uppercase text-xs tracking-widest"><i class="fa-solid fa-shield-halved mr-2"></i>분석 시그널</strong>
                <div class="flex gap-6">
                    <div class="text-[11px] text-slate-400"><span class="text-emerald-400 font-black"><i class="fa-solid fa-fish-fins"></i> WHALE:</span> 저점 구간 대량 유입</div>
                    <div class="text-[11px] text-slate-400"><span class="text-rose-400 font-black"><i class="fa-solid fa-fire-flame-curved"></i> PUMP:</span> 불균형 가속</div>
                </div>
            </div>
        </footer>
    </div>

    <script>
        const weights = <?= json_encode($WEIGHTS) ?>;
        const shortLabels = <?= json_encode($short_labels) ?>;

        window.addEventListener('load', () => {
            const preloader = document.getElementById('preloader');
            const content = document.getElementById('main-content');
            setTimeout(() => { if (preloader) preloader.style.opacity = '0'; if (content) content.classList.add('loaded'); setTimeout(() => { if (preloader) preloader.style.display = 'none'; }, 500); }, 800);
        });

        function updateDashboard() {
            fetch(window.location.pathname + '?mode=update&_ts=' + Date.now(), { cache: 'no-store' })
                .then(response => { if (!response.ok) throw new Error('HTTP_' + response.status); return response.json(); })
                .then(res => {
                    if (!res || !res.data || typeof res.data !== 'object') return;
                    const syncEl = document.getElementById('sync-time');
                    if (syncEl) syncEl.innerText = res.sync_time;

                    let globalScoreSum = 0; let globalWeightSum = 0; let detailHtml = '';

                    Object.keys(res.data).forEach(n_min => {
                        const row = res.data[n_min];
                        const tr = document.getElementById('row-' + n_min);
                        if (!tr) return;

                        const w = weights[row.label] || 0;
                        const shortLbl = shortLabels[n_min] || n_min;
                        let strength = 0; let weighted = 0;

                        if (w > 0) {
                            strength = Number(row.total_amt || 0) > 0 ? (Number(row.net_flow || 0) / Number(row.total_amt || 0)) : 0;
                            strength = Math.max(-1, Math.min(1, strength));
                            weighted = strength * w; globalScoreSum += weighted; globalWeightSum += w;
                            const colorClass = strength > 0 ? 'text-emerald-500' : (strength < 0 ? 'text-rose-500' : 'text-slate-500');
                            detailHtml += `<span class='inline-block mr-2'><span class='text-slate-600'>${shortLbl}:</span><span class='${colorClass}'>${strength >= 0 ? '+' : ''}${strength.toFixed(2)}</span><span class='text-slate-700'>×${w}=</span><span class='${colorClass}'>${weighted >= 0 ? '+' : ''}${weighted.toFixed(2)}</span></span>`;
                        }

                        tr.className = `transition-colors hover:bg-white/5 ${row.is_whale ? 'whale-bg' : (row.is_pump ? 'pump-bg' : '')}`;
                        updateCell(tr.querySelector('.val-amt'), (Number(row.total_amt || 0) / 1e6).toFixed(2) + 'M');
                        const netEl = tr.querySelector('.val-net');
                        if (netEl) { const nf = Number(row.net_flow || 0); netEl.innerText = (nf > 0 ? '+' : '') + (nf / 1e6).toFixed(2) + 'M'; netEl.className = `p-5 font-mono font-bold val-net ${nf >= 0 ? 'text-emerald-400' : 'text-rose-400'}`; }
                        const brEl = tr.querySelector('.val-buyratio');
                        if (brEl) { const br = Number(row.buy_ratio || 0); brEl.innerText = br.toFixed(1) + '%'; brEl.className = `p-5 font-mono font-bold val-buyratio ${br >= 50 ? 'text-emerald-400' : 'text-rose-400'}`; }
                        if (tr.querySelector('.val-ratio24')) tr.querySelector('.val-ratio24').innerText = Number(row.ratio_24h || 0).toFixed(1) + '%';
                        if (tr.querySelector('.val-imb')) tr.querySelector('.val-imb').innerText = Number(row.imbalance || 0).toFixed(1) + '%';
                        if (tr.querySelector('.val-avg')) tr.querySelector('.val-avg').innerText = Number(row.avg_rate || 0).toFixed(2) + '%';
                        if (tr.querySelector('.val-h52')) tr.querySelector('.val-h52').innerText = Number(row.h52 || 0) > 0 ? Number(row.h52).toLocaleString() : 'N/A';
                        if (tr.querySelector('.val-l52')) tr.querySelector('.val-l52').innerText = Number(row.l52 || 0) > 0 ? Number(row.l52).toLocaleString() : 'N/A';
                        const bar = tr.querySelector('.val-pos .bg-sky-500'); if (bar) bar.style.width = Math.min(100, Number(row.pos_52w || 0)) + '%';
                        const posText = tr.querySelector('.val-pos .pos-text'); if (posText) posText.innerText = Number(row.pos_52w || 0).toFixed(1) + '%';
                    });

                    const finalScore = globalWeightSum > 0 ? (globalScoreSum / globalWeightSum) : 0;
                    const finalScorePct = finalScore * 100;
                    const scoreValEl = document.getElementById('score-val');
                    const scoreDescEl = document.getElementById('score-desc');
                    const hudContentEl = document.getElementById('score-details-content');

                    if (scoreValEl) {
                        scoreValEl.innerText = (finalScorePct >= 0 ? '+' : '') + finalScorePct.toFixed(2) + '%';
                        // [수정] 실시간 업데이트 시에도 양수 녹색 / 음수 적색 고정
                        const cls = finalScorePct > 0 ? 'text-emerald-400' : (finalScorePct < 0 ? 'text-rose-400' : 'text-slate-400');
                        scoreValEl.className = `font-black font-mono leading-none ${cls} text-7xl lg:text-8xl tracking-tighter`;
                    }
                    if (scoreDescEl) {
                        if (finalScore >= 0.60) scoreDescEl.innerText = '강력한 매수 압력';
                        else if (finalScore >= 0.15) scoreDescEl.innerText = '매수세 누적 중';
                        else if (finalScore <= -0.60) scoreDescEl.innerText = '강력한 매도 패닉';
                        else if (finalScore <= -0.15) scoreDescEl.innerText = '매도세 분산 중';
                        else scoreDescEl.innerText = '중립 상태';
                    }
                    if (hudContentEl) {
                        const summaryHtml = `<div class='mt-2 pt-2 border-t border-slate-800/50'><span class='text-sky-500 px-2'>[ <i class='fa-solid fa-layer-group mr-1'></i>가중합:${globalScoreSum >= 0 ? '+' : ''}${globalScoreSum.toFixed(2)} / 가중치합:${globalWeightSum} = ${finalScore >= 0 ? '+' : ''}${finalScore.toFixed(4)} ]</span></div>`;
                        hudContentEl.innerHTML = detailHtml + summaryHtml;
                    }
                })
                .catch(err => console.error("Update System Error:", err));
        }

        function updateCell(el, newVal) { if (el && el.innerText !== newVal) { el.innerText = newVal; el.classList.add('update-flash'); setTimeout(() => el.classList.remove('update-flash'), 800); } }

        updateDashboard(); setInterval(updateDashboard, 10000);
    </script>
</body>
</html>