146 lines
6.1 KiB
JavaScript
146 lines
6.1 KiB
JavaScript
/**
|
|
* 실시간 호가창 (OrderBook) 렌더러
|
|
* 0D: 주식호가잔량, 0w: 프로그램매매
|
|
*/
|
|
|
|
// 호가창 초기화
|
|
function initOrderBook() {
|
|
renderOrderBook(null);
|
|
renderProgram(null);
|
|
}
|
|
|
|
// 호가창 렌더링
|
|
// asks[0] = 매도1호가(최우선, 가장 낮은 매도가), asks[9] = 매도10호가
|
|
// bids[0] = 매수1호가(최우선, 가장 높은 매수가), bids[9] = 매수10호가
|
|
function renderOrderBook(ob) {
|
|
const tbody = document.getElementById('orderbookBody');
|
|
if (!tbody) return;
|
|
|
|
if (!ob) {
|
|
tbody.innerHTML = `<tr><td colspan="3" class="text-center py-4 text-xs text-gray-400">호가 데이터 수신 대기 중...</td></tr>`;
|
|
return;
|
|
}
|
|
|
|
// 최대 잔량 (진행바 비율 계산용)
|
|
const maxVol = Math.max(
|
|
...ob.asks.map(a => a.volume),
|
|
...ob.bids.map(b => b.volume),
|
|
1
|
|
);
|
|
|
|
let html = '';
|
|
|
|
// 매도호가: 10호가부터 1호가 순으로 위에서 아래 (asks[9]→asks[0])
|
|
for (let i = 9; i >= 0; i--) {
|
|
const ask = ob.asks[i] || { price: 0, volume: 0 };
|
|
const pct = Math.round((ask.volume / maxVol) * 100);
|
|
html += `
|
|
<tr class="ask-row border-b border-gray-50 hover:bg-red-50 transition-colors cursor-pointer" data-price="${ask.price}">
|
|
<td class="py-1.5 px-2 text-right">
|
|
<div class="relative h-6 flex items-center justify-end">
|
|
<div class="absolute right-0 top-0 h-full bg-red-100 rounded-l" style="width:${pct}%"></div>
|
|
<span class="relative text-xs text-gray-600 font-mono z-10">${ask.volume > 0 ? ask.volume.toLocaleString('ko-KR') : ''}</span>
|
|
</div>
|
|
</td>
|
|
<td class="py-1.5 px-2 text-center">
|
|
<span class="text-sm font-bold text-red-500">${ask.price > 0 ? ask.price.toLocaleString('ko-KR') : '-'}</span>
|
|
</td>
|
|
<td class="py-1.5 px-2"></td>
|
|
</tr>`;
|
|
}
|
|
|
|
// 예상체결 행 (스프레드 사이)
|
|
if (ob.expectedPrc > 0) {
|
|
html += `
|
|
<tr class="bg-yellow-50 border-y-2 border-yellow-300">
|
|
<td class="py-1.5 px-2 text-right text-xs text-yellow-700">예상</td>
|
|
<td class="py-1.5 px-2 text-center text-sm font-bold text-yellow-700">${ob.expectedPrc.toLocaleString('ko-KR')}</td>
|
|
<td class="py-1.5 px-2 text-left text-xs text-yellow-700">${ob.expectedVol > 0 ? ob.expectedVol.toLocaleString('ko-KR') : ''}</td>
|
|
</tr>`;
|
|
}
|
|
|
|
// 매수호가: 1호가부터 10호가 순으로 위에서 아래 (bids[0]→bids[9])
|
|
for (let i = 0; i < ob.bids.length; i++) {
|
|
const bid = ob.bids[i] || { price: 0, volume: 0 };
|
|
const pct = Math.round((bid.volume / maxVol) * 100);
|
|
html += `
|
|
<tr class="bid-row border-b border-gray-50 hover:bg-blue-50 transition-colors cursor-pointer" data-price="${bid.price}">
|
|
<td class="py-1.5 px-2"></td>
|
|
<td class="py-1.5 px-2 text-center">
|
|
<span class="text-sm font-bold text-blue-500">${bid.price > 0 ? bid.price.toLocaleString('ko-KR') : '-'}</span>
|
|
</td>
|
|
<td class="py-1.5 px-2 text-left">
|
|
<div class="relative h-6 flex items-center">
|
|
<div class="absolute left-0 top-0 h-full bg-blue-100 rounded-r" style="width:${pct}%"></div>
|
|
<span class="relative text-xs text-gray-600 font-mono z-10">${bid.volume > 0 ? bid.volume.toLocaleString('ko-KR') : ''}</span>
|
|
</div>
|
|
</td>
|
|
</tr>`;
|
|
}
|
|
|
|
tbody.innerHTML = html;
|
|
|
|
// 호가행 클릭 → 주문창 단가 자동 입력
|
|
tbody.querySelectorAll('tr[data-price]').forEach(row => {
|
|
row.addEventListener('click', () => {
|
|
const price = parseInt(row.dataset.price, 10);
|
|
if (price > 0 && window.setOrderPrice) window.setOrderPrice(price);
|
|
});
|
|
});
|
|
|
|
// 총잔량 업데이트
|
|
const totalAsk = document.getElementById('totalAskVol');
|
|
const totalBid = document.getElementById('totalBidVol');
|
|
if (totalAsk) totalAsk.textContent = ob.totalAskVol.toLocaleString('ko-KR');
|
|
if (totalBid) totalBid.textContent = ob.totalBidVol.toLocaleString('ko-KR');
|
|
|
|
// 호가 시간 업데이트
|
|
const askTime = document.getElementById('askTime');
|
|
if (askTime && ob.askTime && ob.askTime.length >= 6) {
|
|
const t = ob.askTime;
|
|
askTime.textContent = `${t.slice(0,2)}:${t.slice(2,4)}:${t.slice(4,6)}`;
|
|
}
|
|
}
|
|
|
|
// 프로그램 매매 렌더링
|
|
function renderProgram(pg) {
|
|
const container = document.getElementById('programTrading');
|
|
if (!container) return;
|
|
|
|
if (!pg) {
|
|
container.innerHTML = `<span class="text-xs text-gray-400">프로그램 매매 데이터 수신 대기 중...</span>`;
|
|
return;
|
|
}
|
|
|
|
const netClass = pg.netBuyVolume >= 0 ? 'text-red-500' : 'text-blue-500';
|
|
const netSign = pg.netBuyVolume >= 0 ? '+' : '';
|
|
|
|
container.innerHTML = `
|
|
<div class="grid grid-cols-3 gap-3 text-xs">
|
|
<div class="text-center">
|
|
<p class="text-gray-400 mb-0.5">매도</p>
|
|
<p class="font-semibold text-blue-500">${(pg.sellVolume||0).toLocaleString('ko-KR')}</p>
|
|
<p class="text-gray-500">${formatMoney(pg.sellAmount)}원</p>
|
|
</div>
|
|
<div class="text-center border-x border-gray-100">
|
|
<p class="text-gray-400 mb-0.5">순매수</p>
|
|
<p class="font-semibold ${netClass}">${netSign}${(pg.netBuyVolume||0).toLocaleString('ko-KR')}</p>
|
|
<p class="${netClass}">${netSign}${formatMoney(pg.netBuyAmount)}원</p>
|
|
</div>
|
|
<div class="text-center">
|
|
<p class="text-gray-400 mb-0.5">매수</p>
|
|
<p class="font-semibold text-red-500">${(pg.buyVolume||0).toLocaleString('ko-KR')}</p>
|
|
<p class="text-gray-500">${formatMoney(pg.buyAmount)}원</p>
|
|
</div>
|
|
</div>`;
|
|
}
|
|
|
|
// 금액을 억/만 단위로 포맷
|
|
function formatMoney(n) {
|
|
if (!n) return '0';
|
|
const abs = Math.abs(n);
|
|
if (abs >= 100000000) return (n / 100000000).toFixed(1) + '억';
|
|
if (abs >= 10000) return Math.round(n / 10000) + '만';
|
|
return n.toLocaleString('ko-KR');
|
|
}
|