import type { PaperSummaryDto, PaperTradeDto } from './backendApi'; import { buildAnalysisFromPaperTrades, computePaperMetrics } from './paperMetrics'; export interface LiveExecutionItem { id: string; symbol: string; strategyLabel: string; sourceLabel: string; /** 체결 시점 평가 시간봉 */ timeframe: string; executionType?: string; strategyId?: number | null; trades: PaperTradeDto[]; createdAt: string; totalReturnPct: number; winRatePct: number; tradeCount: number; roundTripCount: number; mddPct: number; sharpeRatio: number; profitFactor: number; } function dayKey(iso: string | null | undefined): string { if (!iso) return 'unknown'; return iso.slice(0, 10); } function sourceLabel(source: string): string { if (source === 'STRATEGY') return '자동'; return '수동'; } function resolveStrategyLabel( trades: PaperTradeDto[], strategyNames: Record, ): string { for (const t of trades) { if (t.strategyName?.trim()) return t.strategyName.trim(); } const first = trades[0]; if (first?.strategyId != null) { return strategyNames[first.strategyId] ?? `전략 #${first.strategyId}`; } return '수동 매매'; } /** 자동매매: 종목·전략·시간봉·실행방식 기준 / 수동: 종목·일자 기준 */ function groupKey(t: PaperTradeDto): string { if (t.source === 'STRATEGY') { const tf = t.candleType ?? 'unknown'; const exec = t.executionType ?? ''; return `${t.symbol}|${t.strategyId ?? 0}|${tf}|${exec}|STRATEGY`; } return `${t.symbol}|manual|${dayKey(t.createdAt)}|MANUAL`; } /** 가상투자·모의투자 체결 이력을 실행 단위 목록으로 그룹 */ export function buildLiveExecutionItems( trades: PaperTradeDto[], summary: PaperSummaryDto | null, strategyNames: Record = {}, ): LiveExecutionItem[] { if (!trades.length) return []; const groups = new Map(); for (const t of trades) { const key = groupKey(t); const list = groups.get(key) ?? []; list.push(t); groups.set(key, list); } const initial = summary?.initialCapital ?? 10_000_000; return [...groups.entries()] .map(([key, groupTrades]) => { const sorted = [...groupTrades].sort((a, b) => (a.createdAt ?? '').localeCompare(b.createdAt ?? ''), ); const first = sorted[0]; const last = sorted[sorted.length - 1]; const analysis = buildAnalysisFromPaperTrades(sorted, initial); const metrics = computePaperMetrics(sorted, summary); return { id: key, symbol: first.symbol, strategyLabel: resolveStrategyLabel(sorted, strategyNames), sourceLabel: sourceLabel(first.source), timeframe: first.candleType ?? 'unknown', executionType: first.executionType ?? undefined, strategyId: first.strategyId, trades: sorted, createdAt: last.createdAt ?? first.createdAt ?? new Date().toISOString(), totalReturnPct: analysis.totalReturnPct, winRatePct: analysis.winRate, tradeCount: sorted.length, roundTripCount: analysis.numberOfPositions, mddPct: analysis.maxDrawdownPct, sharpeRatio: analysis.sharpeRatio, profitFactor: metrics.profitFactor, }; }) .sort((a, b) => b.createdAt.localeCompare(a.createdAt)); } export function paperTradesToSignals(trades: PaperTradeDto[]) { return trades .filter(t => t.createdAt) .map(t => ({ time: Math.floor(new Date(t.createdAt!).getTime() / 1000), type: t.side as 'BUY' | 'SELL', price: t.price, quantity: t.quantity, barIndex: 0, })); }