Files
goldenChart/frontend/src/utils/tradeSignalDisplay.ts
T
2026-06-06 00:56:45 +09:00

115 lines
4.0 KiB
TypeScript

/**
* 매매 시그널 알림 표시용 포맷·라벨
*/
import { formatUpbitKrwPrice } from './safeFormat';
import { formatUnixWithChartPattern, splitChartTimePattern } from './chartTimeFormat';
import { getKoreanName } from './marketNameCache';
import { getTradeAlertTimeFormat } from './tradeAlertTimeFormat';
import { 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 `₩${formatUpbitKrwPrice(price, '—')}`;
}
export function formatSignalTime(unixSec: number, withDate = true): string {
if (!unixSec) return '—';
const tz = getDisplayTimezone();
const fmt = getTradeAlertTimeFormat();
if (!withDate) {
const { time } = splitChartTimePattern(fmt);
return formatUnixWithChartPattern(unixSec, time || 'HH:mm', tz);
}
return formatUnixWithChartPattern(unixSec, fmt, tz);
}
export function getSignalTypeKo(type: 'BUY' | 'SELL'): string {
return type === 'BUY' ? '매수' : '매도';
}
const CANDLE_TYPE_KO: Record<string, string> = {
'1m': '1분봉',
'3m': '3분봉',
'5m': '5분봉',
'10m': '10분봉',
'15m': '15분봉',
'30m': '30분봉',
'1h': '1시간봉',
'4h': '4시간봉',
'1w': '주간봉',
'1d': '일봉',
'1D': '일봉',
};
/** 전략 체크·시그널 평가에 사용된 분봉 (한글) */
export function formatCandleTypeKo(candleType?: string | null): string {
const raw = (candleType ?? '1m').trim();
if (!raw) return '1분봉';
const lower = raw.toLowerCase();
return CANDLE_TYPE_KO[raw] ?? CANDLE_TYPE_KO[lower] ?? raw;
}
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 candleKo = formatCandleTypeKo(candleType);
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), highlight: true },
{ label: '기준 가격', value: formatSignalPrice(item.price), highlight: true },
{ label: '캔들 시각', value: `${formatSignalTime(item.candleTime)} · ${formatCandleTypeKo(item.candleType)}` },
];
if (item.receivedAt) {
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;
}