<?php
// volume_only_alarm.php — 거래량 전용 + 알람 + 로그 + ON/OFF 스위치 풀세트
?>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>🔥 실시간 거래량 + 알람 PRO 차트</title>
<!-- 로컬 JS -->
<script src="lightweight-charts.standalone.production.js"></script>
<style>
body { background:#111; color:#eee; font-family:Arial; padding:20px; }
#chart { width:100%; height:480px; border:1px solid #333; margin-top:10px; }
#status { margin-top:8px; font-size:13px; color:#aaa; }
#volInfo { font-size:46px; margin-bottom:10px; font-weight:bold; }
#top-bar { display:flex; gap:10px; align-items:center; }
select {
padding:5px 8px; background:#222; color:#eee;
border:1px solid #555; border-radius:4px;
}
.switch { position:relative; display:inline-block; width:42px; height:22px; }
.switch input{ display:none; }
.slider{
position:absolute; cursor:pointer; top:0; left:0; right:0; bottom:0;
background:#555; transition:.3s; border-radius:22px;
}
.slider:before{
position:absolute; content:""; height:16px; width:16px;
left:3px; bottom:3px; background:white; transition:.3s;
border-radius:50%;
}
input:checked + .slider { background:#28a745; }
input:checked + .slider:before { transform:translateX(20px); }
#toast {
position:fixed; top:20px; right:20px;
background:rgba(0,0,0,0.75); padding:12px 16px; border-radius:6px;
color:#fff; font-size:14px; display:none; z-index:9999;
}
#logPanel {
margin-top:20px; background:#181818; padding:10px; border-radius:6px;
max-height:200px; overflow:auto; border:1px solid #333;
}
.logItem { font-size:13px; padding:3px 0; border-bottom:1px solid #333; }
</style>
</head>
<body>
<h2>🔥 실시간 거래량 PRO 차트 (스파이크 알람 + 로그 + ON/OFF)</h2>
<div id="top-bar">
<div>
코인:
<select id="market">
<option value="KRW-BTC">BTC</option>
<option value="KRW-ETH">ETH</option>
<option value="KRW-XRP">XRP</option>
<option value="KRW-TRUMP">TRUMP</option>
<option value="KRW-QTUM">QTUM</option>
</select>
</div>
<label class="switch">
<input type="checkbox" id="alarmToggle" checked>
<span class="slider"></span>
</label>
<span id="alarmText">🔔 알람 ON</span>
</div>
<div id="volInfo">현재 거래량: -</div>
<div id="chart"></div>
<div id="status">상태: 초기화 중…</div>
<div id="toast"></div>
<h3 style="margin-top:20px;">📘 최근 알람 로그</h3>
<div id="logPanel"></div>
<!-- 🔥 수정된 경로 -->
<audio id="alarmSound" src="/invest/coin/upbit/chart/alarm05.wav" preload="auto"></audio>
<script>
// ======================================
// 전역
// ======================================
let chart = null, volumeSeries = null, ws = null;
let volumes = [];
let lastBucket = null;
let lastPrice = null;
const marketSel = document.getElementById("market");
const statusEl = document.getElementById("status");
const volInfo = document.getElementById("volInfo");
const toastEl = document.getElementById("toast");
const alarmSound = document.getElementById("alarmSound");
const logPanel = document.getElementById("logPanel");
const alarmToggle = document.getElementById("alarmToggle");
const alarmText = document.getElementById("alarmText");
let currentMarket = marketSel.value;
let tfMin = 1;
let SPIKE_THRESHOLD = 2.0;
// 🔥 자동 스크롤 여부
let autoScroll = true;
// ======================================
// 알람 스위치
// ======================================
alarmToggle.addEventListener("change",()=>{
alarmText.textContent = alarmToggle.checked ? "🔔 알람 ON" : "🔕 알람 OFF";
});
// ======================================
// 토스트
// ======================================
function showToast(msg){
toastEl.textContent = msg;
toastEl.style.display = "block";
setTimeout(()=>{ toastEl.style.display="none"; },2000);
}
// ======================================
// 로그
// ======================================
function addLog(msg){
const div=document.createElement("div");
div.className="logItem";
div.textContent = new Date().toLocaleTimeString()+" - "+msg;
logPanel.prepend(div);
if(logPanel.childElementCount > 20)
logPanel.removeChild(logPanel.lastChild);
}
// ======================================
// 거래량 표시
// ======================================
function updateVolInfo(v){
volInfo.textContent = "현재 거래량: " + v.value.toLocaleString();
volInfo.style.color = v.color;
}
// ======================================
// 차트 초기화
// ======================================
function initChart(){
if(chart) chart.remove();
chart = LightweightCharts.createChart(document.getElementById("chart"), {
layout:{ background:{color:"#111"}, textColor:"#DDD" },
rightPriceScale:{ visible:false },
grid:{ vertLines:{color:"#222"}, horzLines:{color:"#222"} },
timeScale:{ borderColor:"#555", timeVisible:true, barSpacing:10 }
});
volumeSeries = chart.addHistogramSeries({
priceFormat:{ type:"volume" },
priceScaleId:"vol"
});
chart.priceScale("vol").applyOptions({
scaleMargins:{ top:0.1, bottom:0 }
});
chart.timeScale().subscribeVisibleLogicalRangeChange(range=>{
if(!range || volumes.length < 2) return;
const lastIndex = volumes.length - 1;
if(range.to < lastIndex - 2) autoScroll = false;
else autoScroll = true;
});
}
// ======================================
// 시간 버킷
// ======================================
function bucketKey(t){ return Math.floor(t/(tfMin*60)); }
// ======================================
// 과거 거래량 로드 (🔥 경로 수정됨)
// ======================================
async function loadHistory(){
statusEl.textContent="상태: 과거 거래량 로딩…";
//const url=`/invest/coin/upbit/chart/upbit_proxy_candles.php?market=${currentMarket}&type=minutes&unit=1&count=200`;
const url = `upbit_proxy_candles.php?market=${currentMarket}&type=minutes&unit=${tfMin}&count=200`;
const res=await fetch(url);
const data=await res.json();
volumes=[];
let prevClose=null;
data.reverse().forEach(c=>{
const time = Math.floor(new Date(c.candle_date_time_kst.replace("T"," ")+" +09:00").getTime()/1000);
let color="#999";
if(prevClose!==null){
if(c.trade_price > prevClose) color="#26a69a";
else if(c.trade_price < prevClose) color="#ef5350";
}
prevClose = c.trade_price;
lastPrice = c.trade_price;
volumes.push({ time, value:c.candle_acc_trade_volume, color });
});
if(volumes.length>0){
lastBucket = bucketKey(volumes[volumes.length-1].time);
volumeSeries.setData(volumes);
updateVolInfo(volumes[volumes.length-1]);
chart.timeScale().scrollToRealTime();
}
statusEl.textContent="상태: 과거 거래량 로드 완료";
}
// ======================================
// 실시간 WebSocket
// ======================================
function connectWS(){
if(ws) ws.close();
ws = new WebSocket("wss://api.upbit.com/websocket/v1");
ws.binaryType="blob";
ws.onopen = ()=>{
ws.send(JSON.stringify([
{ ticket:"vol-pro" },
{ type:"trade", codes:[currentMarket] }
]));
statusEl.textContent="상태: 실시간 연결됨";
};
ws.onmessage = (event)=>{
const reader=new FileReader();
reader.onload=()=>{
const d=JSON.parse(reader.result);
const ts = Math.floor(d.trade_timestamp/1000);
const vol = d.trade_volume;
const price = d.trade_price;
let color="#999";
if(lastPrice!==null){
if(price > lastPrice) color="#26a69a";
else if(price < lastPrice) color="#ef5350";
}
lastPrice = price;
const bk = bucketKey(ts);
const candleTime = bk*tfMin*60;
let v;
if(lastBucket===null || bk > lastBucket){
v = { time:candleTime, value:vol, color };
volumes.push(v);
lastBucket=bk;
volumeSeries.update(v);
} else {
v = volumes[volumes.length-1];
v.value += vol;
v.color = color;
volumeSeries.update(v);
}
updateVolInfo(v);
if(autoScroll){
chart.timeScale().scrollToRealTime();
}
if(alarmToggle.checked){
const prev = volumes[volumes.length-2];
if(prev){
const ratio = v.value / prev.value;
if(ratio >= SPIKE_THRESHOLD){
alarmSound.play();
showToast("📈 거래량 스파이크! ("+ratio.toFixed(1)+"x)");
addLog(`스파이크 감지: ${ratio.toFixed(1)}배`);
}
}
}
};
reader.readAsText(event.data);
};
ws.onclose = ()=>{
statusEl.textContent="상태: 끊김 → 재접속 중…";
setTimeout(connectWS,2000);
};
}
// ======================================
// 초기화
// ======================================
async function initAll(){
initChart();
await loadHistory();
connectWS();
}
marketSel.addEventListener("change",()=>{
currentMarket=marketSel.value;
initAll();
});
window.addEventListener("DOMContentLoaded",()=>initAll());
</script>
</body>
</html>