115 lines
4.0 KiB
TypeScript
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;
|
|
}
|