118 lines
3.9 KiB
TypeScript
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',
|
|
};
|
|
}
|