<?php
/**
* ============================================================
* 비트코인 대시보드 (100% 실제 DB 데이터) - 통합 거래량
* - 파일명: btc_real_24h_final.php
* - 3개 테이블(Container, Coin_24h, Ticker) 완벽 통합
* - 마우스 오버 효과 및 한글 메뉴 적용 완료
* ============================================================
*/
ob_start();
// 0) 디버그 모드
if (isset($_GET['debug']) && $_GET['debug'] === '1') {
@ini_set('display_errors', 1);
@ini_set('display_startup_errors', 1);
@error_reporting(E_ALL);
}
// 1) AJAX(JSON) 엔드포인트
if (isset($_GET['ajax']) && (string)$_GET['ajax'] === '1') {
if (ob_get_level()) ob_end_clean();
header('Content-Type: application/json; charset=utf-8');
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Pragma: no-cache');
$market = isset($_GET['market']) ? trim((string)$_GET['market']) : 'KRW-BTC';
$db = null;
try {
@include_once "/home/www/DB/db_upbit.php";
if (isset($db_upbit) && $db_upbit instanceof PDO) $db = $db_upbit;
else if (isset($pdo) && $pdo instanceof PDO) $db = $pdo;
if (!$db) throw new Exception("DB 연결 실패");
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (Throwable $e) {
http_response_code(500);
echo json_encode(['ok' => false, 'error' => 'DB_CONN_ERROR', 'message' => $e->getMessage()], JSON_UNESCAPED_UNICODE);
exit;
}
try {
$st = $db->prepare("SELECT * FROM daemon_upbit_Ticker WHERE market = ? ORDER BY id DESC LIMIT 1");
$st->execute([$market]);
$latest = $st->fetch(PDO::FETCH_ASSOC);
$st2 = $db->prepare("
SELECT
(vol_hist + vol_mid + vol_now) AS sum_vol,
(days_hist + days_mid + 1) AS total_days,
vol_now AS today_vol
FROM (
SELECT COALESCE(SUM(candle_acc_trade_volume), 0) as vol_hist, COUNT(*) as days_hist
FROM daemon_coin_container_24h WHERE market = :m
) t1,
(
SELECT COALESCE(SUM(d_max), 0) as vol_mid, COUNT(*) as days_mid
FROM (
SELECT MAX(acc_trade_volume) as d_max
FROM daemon_upbit_coin_24h
WHERE market = :m AND trade_date_kst < CURDATE()
GROUP BY trade_date_kst
) tmp
) t2,
(
SELECT COALESCE(MAX(acc_trade_volume), 0) as vol_now
FROM daemon_upbit_Ticker
WHERE market = :m AND trade_date_kst = CURDATE()
) t3
");
$st2->execute(['m' => $market]);
$res_agg = $st2->fetch(PDO::FETCH_ASSOC);
if (!$latest) throw new Exception("데이터 없음");
$latest['id'] = (string)$latest['id'];
$floatFields = ['opening_price', 'high_price', 'low_price', 'trade_price', 'acc_trade_volume', 'acc_trade_price', 'prev_closing_price', 'change_price', 'change_rate'];
foreach($floatFields as $f) { if(isset($latest[$f])) $latest[$f] = (float)$latest[$f]; }
$total_sum_vol = (float)($res_agg['sum_vol'] ?? 0);
$total_days = (int)($res_agg['total_days'] ?? 1);
$avg_daily_vol = ($total_days > 0) ? $total_sum_vol / $total_days : 0;
echo json_encode([
'ok' => true,
'latest' => $latest,
'agg' => [
'avg_daily_vol' => (float)$avg_daily_vol,
'sum_vol' => (float)$total_sum_vol,
'days' => (int)$total_days,
'today_vol' => (float)($res_agg['today_vol'] ?? 0)
],
'server_time' => date('Y-m-d H:i:s')
], JSON_UNESCAPED_UNICODE);
exit;
} catch (Throwable $e) {
http_response_code(500);
echo json_encode(['ok' => false, 'error' => 'SQL_ERROR', 'message' => $e->getMessage()], JSON_UNESCAPED_UNICODE);
exit;
}
}
if (ob_get_level()) ob_end_flush();
require_once '/home/www/GNU/_PAGE/head.php';
?>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>비트코인 통합 거래량 대시보드</title>
<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=Orbitron:wght@400;700;900&family=Inter:wght@400;700;800&family=JetBrains+Mono:wght@500;700&display=swap" rel="stylesheet">
<style>
:root {
--dark-bg: #020617; --dark-card-bg: rgba(15, 23, 42, 0.7); --dark-card-border: rgba(99, 102, 241, 0.2);
--accent-blue: #818cf8; --accent-purple: #c084fc; --accent-green: #00ff88; --accent-red: #ff3e3e;
--text-primary: #f8fafc; --text-secondary: #cbd5e1; --span-bg: #4f46e5;
}
body { background-color: var(--dark-bg); margin: 0; color: var(--text-primary); font-family: 'Inter', sans-serif; overflow-x: hidden; }
.stars-bg { position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 0; background: radial-gradient(circle at center, #0f172a 0%, #020617 100%); }
.em-main-layout { display: flex; width: 100%; min-height: 100vh; position: relative; z-index: 10; }
.em-sidebar { width: 260px; background: rgba(15, 23, 42, 0.9); backdrop-filter: blur(20px); border-right: 1px solid var(--dark-card-border); padding: 30px 20px; }
/* 메뉴 아이템 및 마우스 오버 효과 강화 */
.em-menu-item {
padding: 15px 20px; border-radius: 12px; color: var(--text-secondary);
text-decoration: none; font-weight: 600; font-size: 14px;
display: flex; align-items: center; gap: 12px; margin-bottom: 8px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
border: 1px solid transparent;
}
.em-menu-item:hover {
background: rgba(79, 70, 229, 0.1);
color: #fff;
transform: translateX(5px);
border-color: var(--dark-card-border);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
.em-menu-item.active {
background: var(--span-bg); color: #fff;
box-shadow: 0 4px 15px rgba(79, 70, 229, 0.4);
}
.em-menu-item i { transition: transform 0.3s; }
.em-menu-item:hover i { transform: scale(1.2); color: var(--accent-blue); }
.em-content-area { flex: 1; padding: 40px; }
.em-dashboard { background: var(--dark-card-bg); backdrop-filter: blur(15px); border: 1px solid var(--dark-card-border); border-radius: 30px; padding: 40px; box-shadow: 0 20px 50px rgba(0,0,0,0.5); }
.em-header-grid { display: flex; justify-content: space-between; align-items: flex-end; margin-bottom: 30px; border-bottom: 1px solid var(--dark-card-border); padding-bottom: 25px; }
.em-live-value { font-size: 60px; font-weight: 900; letter-spacing: -2px; line-height: 1; }
.em-comparison-box { margin: 35px 0; padding: 35px; border-radius: 24px; background: rgba(0, 0, 0, 0.4); border: 2px solid var(--dark-card-border); text-align: center; }
.em-strength-meter { width: 100%; max-width: 600px; height: 12px; background: rgba(255,255,255,0.05); border-radius: 10px; margin: 30px auto 10px; position: relative; overflow: hidden; border: 1px solid rgba(255,255,255,0.1); }
.em-strength-fill { position: absolute; top: 0; height: 100%; transition: 1s cubic-bezier(0.4, 0, 0.2, 1); width: 0%; left: 50%; }
.em-stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 20px; margin-top: 30px; }
.em-stat-item { background: rgba(15, 23, 42, 0.5); border: 1px solid var(--dark-card-border); padding: 25px; border-radius: 20px; transition: 0.3s; }
.em-stat-label { font-size: 11px; color: var(--text-secondary); font-weight: 800; margin-bottom: 12px; text-transform: uppercase; letter-spacing: 1px; }
.em-stat-value { font-family: 'JetBrains Mono'; font-size: 22px; font-weight: 700; color: #fff; }
</style>
</head>
<body>
<div class="stars-bg"></div>
<div class="em-main-layout">
<aside class="em-sidebar">
<a href="<?php G5_URL;?>/GNU/_PAGE/stats/average_volume_24h.php" class="em-menu-item active"><i class="fa-solid fa-chart-line"></i> 통합 DB 현황</a>
<a href="<?php G5_URL;?>/GNU/_PAGE/stats/average_price_24h.php" class="em-menu-item"><i class="fa-solid fa-database"></i> 실시간 평균 거래량 USD</a>
<a href="<?php G5_URL;?>/GNU/_PAGE/stats/average_volume_24h.php" class="em-menu-item"><i class="fa-solid fa-database"></i> 실시간 평균 거래금 USD</a>
<a href="<?php G5_URL;?>/GNU/_PAGE/stats/average_price_krw_24h.php" class="em-menu-item"><i class="fa-solid fa-database"></i> 실시간 평균 거래금 KRW</a>
</aside>
<main class="em-content-area">
<div class="em-dashboard">
<div class="em-header-grid">
<div>
<div style="color:var(--accent-blue); font-weight:800; font-size:13px; margin-bottom:12px; letter-spacing:1px;">
<i class="fa-solid fa-layer-group"></i> 전체 통합 거래량 (누적 총계)
</div>
<div class="em-live-value">
<span id="live_total_vol">0.00</span>
<small style="font-size:24px; color:var(--text-secondary); margin-left:10px;">BTC</small>
</div>
<div style="font-size:12px; color:var(--text-secondary); margin-top:8px; font-weight:600;">과거~현재 실시간 통합 누적량</div>
</div>
<div style="text-align:right">
<div style="font-size:12px; color:var(--text-secondary); margin-bottom:5px;">세션 기준 시간 (KST 09:00)</div>
<div id="start_time" style="font-family:'JetBrains Mono'; color:var(--accent-blue); font-weight:700;">-</div>
</div>
</div>
<div id="comparison_box" class="em-comparison-box">
<div style="font-size:12px; font-weight:800; color:var(--text-secondary); letter-spacing:1px;">오늘의 모멘텀 vs 역사적 평균</div>
<div style="display:flex; justify-content:center; align-items:center; gap:40px; margin:25px 0;">
<span id="status_arrow" style="font-size:70px; transition:0.5s;">-</span>
<div style="text-align:left">
<div id="today_vol_display" style="font-size:48px; font-weight:900; font-family:'JetBrains Mono';">0.00</div>
<div id="momentum_pct" style="font-size:24px; font-weight:800;">0.00%</div>
</div>
</div>
<div class="em-strength-meter">
<div id="strength_fill" class="em-strength-fill"></div>
</div>
</div>
<div class="em-stats-grid">
<div class="em-stat-item">
<div class="em-stat-label">오늘 실시간 누적 (09시~)</div>
<div class="em-stat-value" id="val_today_acc" style="color:var(--accent-blue)">0.00</div>
</div>
<div class="em-stat-item">
<div class="em-stat-label">역사적 일일 평균</div>
<div class="em-stat-value" id="val_hist_avg" style="color:var(--accent-green)">0.00</div>
</div>
<div class="em-stat-item">
<div class="em-stat-label">전체 통합 누적 합계</div>
<div class="em-stat-value" id="val_total_sum" style="color:var(--accent-purple); font-size:18px;">0.00</div>
</div>
<div class="em-stat-item">
<div class="em-stat-label">총 분석 대상 기간</div>
<div class="em-stat-value" id="val_total_days">0 일</div>
</div>
</div>
<div style="margin-top:30px; border-top:1px solid var(--dark-card-border); padding-top:20px; display:flex; justify-content:space-between; font-size:11px; color:#475569;">
<div>데이터 경로: container_24h ➔ upbit_coin_24h ➔ Ticker (09:00 기준 바통 터치)</div>
<div id="sync_time">동기화: -</div>
</div>
</div>
</main>
</div>
<script>
const MARKET = "KRW-BTC";
const AJAX_URL = window.location.origin + window.location.pathname;
async function fetchData() {
try {
const response = await fetch(`${AJAX_URL}?ajax=1&market=${MARKET}`);
const data = await response.json();
if (data.ok) updateUI(data);
} catch (e) { console.error("데이터 호출 에러:", e); }
}
function updateUI(data) {
const agg = data.agg;
const latest = data.latest;
const now = new Date();
let start = new Date(now);
start.setHours(9, 0, 0, 0);
if (now < start) start.setDate(start.getDate() - 1);
document.getElementById('start_time').innerText = start.getFullYear() + '-' + String(start.getMonth() + 1).padStart(2, '0') + '-' + String(start.getDate()).padStart(2, '0') + ' 09:00:00';
const totalSum = agg.sum_vol;
const todayAcc = agg.today_vol;
const histAvg = agg.avg_daily_vol;
const diff = todayAcc - histAvg;
const diffPct = (histAvg > 0) ? (diff / histAvg) * 100 : 0;
document.getElementById('live_total_vol').innerText = totalSum.toLocaleString(undefined, {minimumFractionDigits: 2});
document.getElementById('today_vol_display').innerText = todayAcc.toLocaleString(undefined, {minimumFractionDigits: 4});
document.getElementById('momentum_pct').innerText = (diff >= 0 ? "+" : "") + diffPct.toFixed(2) + "%";
document.getElementById('val_today_acc').innerText = todayAcc.toLocaleString(undefined, {minimumFractionDigits: 4}) + " BTC";
document.getElementById('val_hist_avg').innerText = histAvg.toLocaleString(undefined, {minimumFractionDigits: 4}) + " BTC";
document.getElementById('val_total_sum').innerText = totalSum.toLocaleString(undefined, {minimumFractionDigits: 2});
document.getElementById('val_total_days').innerText = agg.days + " 일";
document.getElementById('sync_time').innerText = "마지막 동기화: " + data.server_time;
const isPlus = diff >= 0;
const color = isPlus ? "var(--accent-green)" : "var(--accent-red)";
const arrowEl = document.getElementById('status_arrow');
const fillEl = document.getElementById('strength_fill');
const boxEl = document.getElementById('comparison_box');
arrowEl.innerText = isPlus ? "▲" : "▼";
arrowEl.style.color = color;
document.getElementById('momentum_pct').style.color = color;
document.getElementById('today_vol_display').style.color = color;
boxEl.style.borderColor = color;
const barWidth = Math.min(Math.abs(diffPct), 50);
fillEl.style.width = barWidth + "%";
fillEl.style.left = isPlus ? "50%" : (50 - barWidth) + "%";
fillEl.style.background = color;
}
setInterval(fetchData, 5000);
fetchData();
</script>
</body>
</html>
<?php require_once '/home/www/GNU/_PAGE/tail.php'; ?>