/** * 매매 시그널 알림 표시용 포맷·라벨 */ import { getKoreanName } from './marketNameCache'; import { formatUnixDateTime, formatUnixClock, getDisplayTimezone } from './timezone'; export interface TradeSignalDisplaySource { market: string; signalType: 'BUY' | 'SELL'; price: number; candleTime: number; strategyName?: string | null; strategyId?: number | null; executionType?: string; candleType?: string; receivedAt?: number; } export function parseMarket(market: string) { const parts = market.split('-'); const fiat = parts[0] ?? 'KRW'; const coin = parts.length > 1 ? parts.slice(1).join('-') : market; return { fiat, coin, code: market }; } export function formatSignalPrice(price: number): string { if (!Number.isFinite(price) || price <= 0) return '—'; return `₩${price.toLocaleString('ko-KR', { maximumFractionDigits: 0 })}`; } export function formatSignalTime(unixSec: number, withDate = true): string { if (!unixSec) return '—'; const tz = getDisplayTimezone(); if (!withDate) return formatUnixClock(unixSec, tz, true); return formatUnixDateTime(unixSec, tz); } export function getSignalTypeKo(type: 'BUY' | 'SELL'): string { return type === 'BUY' ? '매수' : '매도'; } export function getSignalHeadline(item: TradeSignalDisplaySource): string { const korean = getKoreanName(item.market); const { coin } = parseMarket(item.market); const name = korean || coin; return `${name} ${getSignalTypeKo(item.signalType)} 알림`; } export function getExecutionLabel(executionType?: string, candleType?: string): string { const candle = candleType ?? '1m'; const candleKo = candle === '1m' ? '1분봉' : candle; if (executionType === 'REALTIME_TICK') return `실시간 틱 · ${candleKo}`; if (executionType === 'CANDLE_CLOSE') return `봉 마감 · ${candleKo}`; return candleKo; } export function getMarketDisplayLine(market: string): { primary: string; secondary: string } { const korean = getKoreanName(market); const { code } = parseMarket(market); return { primary: korean || code, secondary: code, }; } /** 상세 행 목록 (스낵바·목록 공통) */ export function buildSignalDetailRows(item: TradeSignalDisplaySource): Array<{ label: string; value: string; highlight?: boolean }> { const { primary, secondary } = getMarketDisplayLine(item.market); const rows: Array<{ label: string; value: string; highlight?: boolean }> = [ { label: '종목', value: primary !== secondary ? `${secondary} · ${primary}` : secondary }, { label: '매매 구분', value: `${getSignalTypeKo(item.signalType)} (${item.signalType})`, highlight: true }, { label: '기준 가격', value: formatSignalPrice(item.price), highlight: true }, { label: '캔들 시각', value: `${formatSignalTime(item.candleTime)} · ${item.candleType ?? '1m'}` }, ]; if (item.receivedAt && Math.abs(item.receivedAt - item.candleTime * 1000) > 2000) { rows.push({ label: '수신 시각', value: formatSignalTime(Math.floor(item.receivedAt / 1000)) }); } rows.push( { label: '전략', value: item.strategyName?.trim() || (item.strategyId != null ? `전략 #${item.strategyId}` : '실시간 전략') }, { label: '실행 방식', value: getExecutionLabel(item.executionType, item.candleType) }, ); return rows; }