import type { BacktestAnalysis, BacktestResultRecord, BacktestSignal, BacktestStats, } from './backendApi'; import type { BacktestAnalysisReportModel } from '../components/backtest/BacktestAnalysisReportModal'; import type { LiveExecutionItem } from './liveExecutionGroups'; import { buildAnalysisFromPaperTrades } from './paperMetrics'; import { fmtListTimestamp, resolveBacktestRecordExecTimeframe, toUpbitMarket } from './backtestUiUtils'; import { getKoreanName } from './marketNameCache'; import { repairUtf8Mojibake } from './textEncoding'; /** 실시간 매매 탭 — 체결 이력 기반 분석 */ export function buildAnalysisFromLive( item: LiveExecutionItem, initialCapital = 10_000_000, ): BacktestAnalysis { return buildAnalysisFromPaperTrades(item.trades, initialCapital); } function buildAnalysisFromStats(s: BacktestStats): BacktestAnalysis { return { initialCapital: 10_000_000, finalEquity: s.finalEquity ?? 0, totalReturnPct: s.totalReturn ?? 0, totalProfitLoss: (s.finalEquity ?? 0) - 10_000_000, grossProfit: 0, grossLoss: 0, avgReturnPct: s.avgReturn ?? 0, profitLossRatio: 0, numberOfPositions: s.totalTrades ?? 0, numberOfWinning: s.winTrades ?? 0, numberOfLosing: (s.totalTrades ?? 0) - (s.winTrades ?? 0), numberOfBreakEven: 0, winRate: s.winRate ?? 0, maxDrawdownPct: s.maxDrawdown ?? 0, maxRunupPct: 0, sharpeRatio: 0, sortinoRatio: 0, calmarRatio: 0, valueAtRisk95: 0, expectedShortfall: 0, buyAndHoldReturnPct: 0, vsBuyAndHold: 0, }; } /** 실시간 차트에서 실행한 백테스트 → 투자분석 상세 레포트 모델 */ export function buildChartBacktestReportModel(opts: { analysis: BacktestAnalysis | null; stats: BacktestStats | null; signals: BacktestSignal[]; strategyName: string; symbol: string; timeframe: string; barCount: number; createdAt?: string; }): BacktestAnalysisReportModel | null { const analysis = opts.analysis ?? (opts.stats ? buildAnalysisFromStats(opts.stats) : null); if (!analysis) return null; const market = toUpbitMarket(opts.symbol); return { analysis, signals: opts.signals, strategyName: repairUtf8Mojibake(opts.strategyName || '전략'), symbol: opts.symbol, symbolKo: repairUtf8Mojibake(getKoreanName(market)), timeframe: opts.timeframe, barCount: opts.barCount || 300, createdAt: opts.createdAt, reportKind: 'backtest', }; } export function buildBacktestReportModel( record: BacktestResultRecord, analysis: BacktestAnalysis, signals: BacktestSignal[], _strategyNamesById: Record = {}, ): BacktestAnalysisReportModel { const market = toUpbitMarket(record.symbol); const strategyName = repairUtf8Mojibake( record.strategyName || '전략 없음', ); return { analysis, signals, strategyName, symbol: record.symbol, symbolKo: repairUtf8Mojibake(getKoreanName(market)), timeframe: resolveBacktestRecordExecTimeframe(record), barCount: record.barCount || 300, createdAt: record.createdAt ? fmtListTimestamp(record.createdAt) : undefined, reportKind: 'backtest', }; } export function buildLiveReportModel( item: LiveExecutionItem, signals: BacktestSignal[], initialCapital = 10_000_000, ): BacktestAnalysisReportModel { const market = toUpbitMarket(item.symbol); const timeframe = item.timeframe !== 'unknown' ? item.timeframe : '—'; return { analysis: buildAnalysisFromLive(item, initialCapital), signals, strategyName: repairUtf8Mojibake(item.strategyLabel), symbol: item.symbol, symbolKo: getKoreanName(market), timeframe, barCount: item.trades.length, createdAt: item.createdAt ? fmtListTimestamp(item.createdAt) : undefined, reportKind: 'live', }; }