/** * 실시간 차트 상단·우측 현재가·등락률 (크로스헤어와 분리, 업비트 티커 우선) */ 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 { const price = ticker?.tradePrice != null && Number.isFinite(ticker.tradePrice) ? ticker.tradePrice : lastBar?.close ?? 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)}%`; }