백테스팅 수정

This commit is contained in:
Macbook
2026-06-12 00:00:06 +09:00
parent eb6cf894da
commit d5338274d4
10 changed files with 151 additions and 72 deletions
+3
View File
@@ -1209,6 +1209,9 @@ export interface BacktestResponse {
stats: BacktestStats;
analysis?: BacktestAnalysis;
resultId?: number;
/** 실행에 사용된 시간봉 */
timeframe?: string;
symbol?: string;
}
/** DB 저장 이력 레코드 */
+2 -2
View File
@@ -7,7 +7,7 @@ import type {
import type { BacktestAnalysisReportModel } from '../components/backtest/BacktestAnalysisReportModal';
import type { LiveExecutionItem } from './liveExecutionGroups';
import { buildAnalysisFromPaperTrades } from './paperMetrics';
import { fmtListTimestamp, toUpbitMarket } from './backtestUiUtils';
import { fmtListTimestamp, resolveBacktestRecordExecTimeframe, toUpbitMarket } from './backtestUiUtils';
import { getKoreanName } from './marketNameCache';
import { repairUtf8Mojibake } from './textEncoding';
@@ -89,7 +89,7 @@ export function buildBacktestReportModel(
strategyName,
symbol: record.symbol,
symbolKo: repairUtf8Mojibake(getKoreanName(market)),
timeframe: record.timeframe,
timeframe: resolveBacktestRecordExecTimeframe(record),
barCount: record.barCount || 300,
createdAt: record.createdAt ? fmtListTimestamp(record.createdAt) : undefined,
reportKind: 'backtest',
@@ -0,0 +1,50 @@
import type { Timeframe } from '../types';
import { formatTimeframeKo } from './backtestUiUtils';
/** 빠른 백테스팅 — 전략 DSL에 저장된 시간봉 사용 */
export const BACKTEST_STRATEGY_TIMEFRAME = '__strategy__' as const;
export type BacktestRunTimeframeChoice = Timeframe | typeof BACKTEST_STRATEGY_TIMEFRAME;
export const QUICK_RUN_TIMEFRAME_ITEMS: { value: Timeframe; label: string }[] = [
{ value: '1m', label: '1분' },
{ value: '3m', label: '3분' },
{ value: '5m', label: '5분' },
{ value: '10m', label: '10분' },
{ value: '15m', label: '15분' },
{ value: '30m', label: '30분' },
{ value: '1h', label: '1시간' },
{ value: '4h', label: '4시간' },
{ value: '1D', label: '일봉' },
{ value: '1W', label: '주봉' },
];
export function isBacktestStrategyTimeframeChoice(
value: string,
): value is typeof BACKTEST_STRATEGY_TIMEFRAME {
return value === BACKTEST_STRATEGY_TIMEFRAME;
}
export function parseBacktestRunTimeframeChoice(value: string): BacktestRunTimeframeChoice {
if (isBacktestStrategyTimeframeChoice(value)) return BACKTEST_STRATEGY_TIMEFRAME;
return value as Timeframe;
}
/** UI 선택 → 실제 실행 시간봉 */
export function resolveBacktestExecTimeframe(
choice: BacktestRunTimeframeChoice,
strategyTimeframe: Timeframe,
): Timeframe {
if (choice === BACKTEST_STRATEGY_TIMEFRAME) return strategyTimeframe;
return choice;
}
export function buildQuickRunTimeframeSelectOptions(strategyTimeframe?: Timeframe) {
const stratLabel = strategyTimeframe
? `전략 시간봉 (${formatTimeframeKo(strategyTimeframe)})`
: '전략 시간봉';
return [
{ value: BACKTEST_STRATEGY_TIMEFRAME, label: stratLabel },
...QUICK_RUN_TIMEFRAME_ITEMS,
];
}
+16 -1
View File
@@ -1,4 +1,5 @@
import type { ChartType, Timeframe } from '../types';
import type { BacktestResultRecord } from './backendApi';
import { formatUpbitKrwPrice } from './safeFormat';
export function toUpbitMarket(symbol: string): string {
@@ -19,12 +20,26 @@ const TIMEFRAME_KO: Record<Timeframe, string> = {
'1M': '1월',
};
/** 차트·목록용 시간봉 한글 라벨 */
/** 차트·목록용 한글 라벨 */
export function formatTimeframeKo(raw: string | undefined): string {
const tf = normalizeChartTimeframe(raw);
return TIMEFRAME_KO[tf] ?? tf;
}
/** 백테스트 결과 — 실제 실행 시간봉 (스냅샷 우선, 전략 DSL과 무관) */
export function resolveBacktestRecordExecTimeframe(record: BacktestResultRecord): string {
if (record.executionSnapshotJson) {
try {
const snap = JSON.parse(record.executionSnapshotJson) as { timeframe?: string };
const fromSnap = snap?.timeframe?.trim();
if (fromSnap) return fromSnap;
} catch { /* ignore */ }
}
const fromCol = record.timeframe?.trim();
if (fromCol && fromCol !== 'unknown') return fromCol;
return '1m';
}
const CHART_TYPE_KO: Record<ChartType, string> = {
candlestick: '캔들스틱',
bar: '바',