Files
goldenChart/frontend/src/utils/backtestReportModel.ts
T
2026-06-12 00:00:06 +09:00

118 lines
3.9 KiB
TypeScript

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<number, string> = {},
): 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',
};
}