백테스팅 수정
This commit is contained in:
@@ -28,6 +28,7 @@ import {
|
||||
formatTimeframeKo,
|
||||
normalizeEpochSec,
|
||||
normalizeEpochSecOptional,
|
||||
resolveBacktestRecordExecTimeframe,
|
||||
toUpbitMarket,
|
||||
} from '../utils/backtestUiUtils';
|
||||
import BacktestExecutionList, { type ExecutionListTab } from './backtest/BacktestExecutionList';
|
||||
@@ -52,6 +53,13 @@ import { repairUtf8Mojibake } from '../utils/textEncoding';
|
||||
import { enrichStrategyNameMap, strategyNamesFromList } from '../utils/strategyNameResolver';
|
||||
import { markStrategyIdsMissingUnlessValid } from '../utils/strategyMissingCache';
|
||||
import { resolveStrategyPrimaryTimeframe } from '../utils/strategyToChartIndicators';
|
||||
import {
|
||||
BACKTEST_STRATEGY_TIMEFRAME,
|
||||
buildQuickRunTimeframeSelectOptions,
|
||||
parseBacktestRunTimeframeChoice,
|
||||
resolveBacktestExecTimeframe,
|
||||
type BacktestRunTimeframeChoice,
|
||||
} from '../utils/backtestRunTimeframe';
|
||||
|
||||
const LEFT_KEY = 'btd-left-width';
|
||||
const RIGHT_KEY = 'btd-right-width';
|
||||
@@ -77,19 +85,6 @@ interface Props {
|
||||
theme?: Theme;
|
||||
}
|
||||
|
||||
const QUICK_RUN_TIMEFRAMES: { 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 BacktestHistoryPage({ theme = 'dark' }: Props) {
|
||||
const [tab, setTab] = useState<ExecutionListTab>('backtest');
|
||||
const [records, setRecords] = useState<BacktestResultRecord[]>([]);
|
||||
@@ -102,7 +97,7 @@ export function BacktestHistoryPage({ theme = 'dark' }: Props) {
|
||||
|
||||
// ── 빠른 백테스팅 실행 ──────────────────────────────────────────────────────
|
||||
const [runMarket, setRunMarket] = useState<string>('KRW-BTC');
|
||||
const [runTimeframe, setRunTimeframe] = useState<Timeframe>('1D');
|
||||
const [runTimeframe, setRunTimeframe] = useState<BacktestRunTimeframeChoice>(BACKTEST_STRATEGY_TIMEFRAME);
|
||||
const [runStrategyId, setRunStrategyId] = useState<number | null>(null);
|
||||
const [runLoading, setRunLoading] = useState(false);
|
||||
const [runError, setRunError] = useState<string | null>(null);
|
||||
@@ -214,15 +209,6 @@ export function BacktestHistoryPage({ theme = 'dark' }: Props) {
|
||||
|
||||
useEffect(() => { void fetchAll(); }, [fetchAll]);
|
||||
|
||||
// 전략 선택 시 DSL 기준 시간봉으로 자동 동기화 (예: 전략 4=5m 조건인데 3m 실행 방지)
|
||||
useEffect(() => {
|
||||
if (!runStrategyId) return;
|
||||
const strat = strategies.find(s => s.id === runStrategyId);
|
||||
if (!strat) return;
|
||||
const tf = resolveStrategyPrimaryTimeframe(strat);
|
||||
setRunTimeframe(prev => (prev === tf ? prev : tf));
|
||||
}, [runStrategyId, strategies]);
|
||||
|
||||
useEffect(() => {
|
||||
const onTradesChanged = () => { void refreshLiveTrades(); };
|
||||
window.addEventListener(PAPER_TRADES_CHANGED_EVENT, onTradesChanged);
|
||||
@@ -245,7 +231,10 @@ export function BacktestHistoryPage({ theme = 'dark' }: Props) {
|
||||
setRunError(null);
|
||||
try {
|
||||
const strategy = strategies.find(s => s.id === runStrategyId);
|
||||
const execTimeframe = strategy ? resolveStrategyPrimaryTimeframe(strategy) : runTimeframe;
|
||||
const strategyTimeframe = strategy
|
||||
? resolveStrategyPrimaryTimeframe(strategy)
|
||||
: '1m';
|
||||
const execTimeframe = resolveBacktestExecTimeframe(runTimeframe, strategyTimeframe);
|
||||
const [settings, indicatorParams, barsRes] = await Promise.all([
|
||||
loadBacktestSettings(),
|
||||
loadIndicatorSettings(),
|
||||
@@ -272,15 +261,34 @@ export function BacktestHistoryPage({ theme = 'dark' }: Props) {
|
||||
strategyName: strategy?.name,
|
||||
});
|
||||
if (!result) throw new Error('백테스팅 실행에 실패했습니다.');
|
||||
// 결과 목록 갱신 후 새 결과 자동 선택
|
||||
await fetchAll();
|
||||
const list = await loadBacktestResults();
|
||||
setRecords(list);
|
||||
if (result.resultId) {
|
||||
const fresh = list.find(r => r.id === result.resultId) ?? list[0] ?? null;
|
||||
setSelectedBacktest(fresh);
|
||||
} else {
|
||||
setSelectedBacktest(list[0] ?? null);
|
||||
}
|
||||
setTab('backtest');
|
||||
} catch (e) {
|
||||
setRunError(e instanceof Error ? e.message : '실행 중 오류가 발생했습니다.');
|
||||
} finally {
|
||||
setRunLoading(false);
|
||||
}
|
||||
}, [runMarket, runTimeframe, runStrategyId, strategies, fetchAll]);
|
||||
}, [runMarket, runTimeframe, runStrategyId, strategies]);
|
||||
|
||||
const runStrategy = useMemo(
|
||||
() => (runStrategyId ? strategies.find(s => s.id === runStrategyId) : undefined),
|
||||
[runStrategyId, strategies],
|
||||
);
|
||||
const runStrategyTimeframe = useMemo(
|
||||
() => (runStrategy ? resolveStrategyPrimaryTimeframe(runStrategy) : undefined),
|
||||
[runStrategy],
|
||||
);
|
||||
const runTimeframeOptions = useMemo(
|
||||
() => buildQuickRunTimeframeSelectOptions(runStrategyTimeframe),
|
||||
[runStrategyTimeframe],
|
||||
);
|
||||
|
||||
const backtestDetail = selectedBacktest ? parseDetail(selectedBacktest) : null;
|
||||
|
||||
@@ -312,7 +320,7 @@ export function BacktestHistoryPage({ theme = 'dark' }: Props) {
|
||||
const fromSec = normalizeEpochSecOptional(selectedBacktest.fromTime);
|
||||
return {
|
||||
symbol: selectedBacktest.symbol,
|
||||
timeframe: selectedBacktest.timeframe,
|
||||
timeframe: resolveBacktestRecordExecTimeframe(selectedBacktest),
|
||||
toTimeSec: toSec,
|
||||
fromTimeSec: fromSec > 0 ? fromSec : undefined,
|
||||
barCount: selectedBacktest.barCount || 300,
|
||||
@@ -341,7 +349,7 @@ export function BacktestHistoryPage({ theme = 'dark' }: Props) {
|
||||
ko: getKoreanName(market),
|
||||
strategy: selectedBacktest.strategyName || '전략 없음',
|
||||
metaParts: [
|
||||
formatTimeframeKo(selectedBacktest.timeframe),
|
||||
formatTimeframeKo(resolveBacktestRecordExecTimeframe(selectedBacktest)),
|
||||
formatChartTypeKo('candlestick'),
|
||||
],
|
||||
};
|
||||
@@ -452,11 +460,11 @@ export function BacktestHistoryPage({ theme = 'dark' }: Props) {
|
||||
<select
|
||||
className="btd-run-select"
|
||||
value={runTimeframe}
|
||||
onChange={e => setRunTimeframe(e.target.value as Timeframe)}
|
||||
onChange={e => setRunTimeframe(parseBacktestRunTimeframeChoice(e.target.value))}
|
||||
disabled={runLoading}
|
||||
title="타임프레임"
|
||||
title="백테스팅 실행 시간봉"
|
||||
>
|
||||
{QUICK_RUN_TIMEFRAMES.map(tf => (
|
||||
{runTimeframeOptions.map(tf => (
|
||||
<option key={tf.value} value={tf.value}>{tf.label}</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
Reference in New Issue
Block a user