58 lines
1.9 KiB
TypeScript
58 lines
1.9 KiB
TypeScript
/**
|
|
* 실시간 차트 상단·우측 현재가·등락률 (크로스헤어와 분리, 업비트 티커 우선)
|
|
*/
|
|
import type { TickerData } from '../hooks/useMarketTicker';
|
|
import type { OHLCVBar } from '../types';
|
|
import { formatUpbitKrwPrice } from './safeFormat';
|
|
|
|
export interface ChartLiveQuote {
|
|
price: number | null;
|
|
/** 전일 대비 등락률 (%) */
|
|
changePct: number | null;
|
|
isUp: boolean;
|
|
}
|
|
|
|
export function resolveChartLiveQuote(
|
|
ticker: TickerData | undefined | null,
|
|
lastBar: OHLCVBar | null,
|
|
bars: OHLCVBar[],
|
|
): ChartLiveQuote {
|
|
// 실시간 차트: STOMP/WS로 갱신되는 최신 봉 종가 우선 (티커 REST 초기값에 고정되지 않도록)
|
|
const barPrice = lastBar?.close;
|
|
const tickerPrice = ticker?.tradePrice;
|
|
const price =
|
|
barPrice != null && Number.isFinite(barPrice)
|
|
? barPrice
|
|
: tickerPrice != null && Number.isFinite(tickerPrice)
|
|
? tickerPrice
|
|
: null;
|
|
|
|
if (ticker?.changeRate != null && Number.isFinite(ticker.changeRate)) {
|
|
const changePct = ticker.changeRate * 100;
|
|
return { price, changePct, isUp: ticker.changeRate >= 0 };
|
|
}
|
|
|
|
if (lastBar && bars.length > 1) {
|
|
const prevClose = bars[bars.length - 2].close;
|
|
if (prevClose > 0) {
|
|
const ch = lastBar.close - prevClose;
|
|
const changePct = (ch / prevClose) * 100;
|
|
return { price, changePct, isUp: ch >= 0 };
|
|
}
|
|
}
|
|
|
|
const isUp = lastBar ? lastBar.close >= lastBar.open : true;
|
|
return { price, changePct: null, isUp };
|
|
}
|
|
|
|
export function formatChartLivePrice(price: number | null): string {
|
|
if (price == null || !Number.isFinite(price)) return '—';
|
|
return `${formatUpbitKrwPrice(price)} KRW`;
|
|
}
|
|
|
|
export function formatChartLiveChangePct(changePct: number | null): string {
|
|
if (changePct == null || !Number.isFinite(changePct)) return '';
|
|
const up = changePct >= 0;
|
|
return `${up ? '▲' : '▼'} ${Math.abs(changePct).toFixed(2)}%`;
|
|
}
|