백테스팅 워밍업데이터 적용

This commit is contained in:
Macbook
2026-06-12 01:41:34 +09:00
parent 0d338fffe4
commit 74b0ea4ab6
12 changed files with 335 additions and 52 deletions
+13 -13
View File
@@ -15,14 +15,13 @@ import {
runBacktest,
loadBacktestSettings,
loadIndicatorSettings,
API_BASE,
type BacktestResultRecord,
type BacktestAnalysis,
type BacktestSignal,
type StrategyDto,
} from '../utils/backendApi';
import { timeframeToCandleType } from '../utils/chartCandleType';
import type { Theme, Timeframe } from '../types';
import { fetchBacktestCandleBundle } from '../utils/backtestWarmup';
import { buildEquityFromSignals } from '../utils/backtestEquity';
import {
formatChartTypeKo,
@@ -30,6 +29,7 @@ import {
normalizeEpochSec,
normalizeEpochSecOptional,
resolveBacktestRecordExecTimeframe,
resolveBacktestChartBarCount,
toUpbitMarket,
} from '../utils/backtestUiUtils';
import BacktestExecutionList, { type ExecutionListTab } from './backtest/BacktestExecutionList';
@@ -272,19 +272,18 @@ export function BacktestHistoryPage({ theme = 'dark' }: Props) {
? resolveStrategyPrimaryTimeframe(strategy)
: '1m';
const execTimeframe = resolveBacktestExecTimeframe(runTimeframe, strategyTimeframe);
const [settings, indicatorParams, barsRes] = await Promise.all([
const [settings, indicatorParams] = await Promise.all([
loadBacktestSettings(),
loadIndicatorSettings(),
fetch(`${API_BASE}/candles/history?${new URLSearchParams({
market: runMarket,
type: timeframeToCandleType(execTimeframe),
count: '800',
})}`),
]);
if (!barsRes.ok) throw new Error(`캔들 로딩 실패 (${barsRes.status})`);
const bars = (await barsRes.json()) as Array<{
time: number; open: number; high: number; low: number; close: number; volume: number;
}>;
const candleBundle = await fetchBacktestCandleBundle({
market: runMarket,
timeframe: execTimeframe as Timeframe,
buyDsl: strategy?.buyCondition,
sellDsl: strategy?.sellCondition,
indicatorParams,
});
const { bars, evaluationBarCount } = candleBundle;
if (bars.length < 5) {
throw new Error('캔들 데이터가 부족합니다. 잠시 후 다시 시도하거나 타임프레임을 변경해 주세요.');
}
@@ -296,6 +295,7 @@ export function BacktestHistoryPage({ theme = 'dark' }: Props) {
indicatorParams,
symbol: runMarket,
strategyName: strategy?.name,
evaluationBarCount,
});
if (!result) throw new Error('백테스팅 실행에 실패했습니다.');
const list = await loadBacktestResults();
@@ -360,7 +360,7 @@ export function BacktestHistoryPage({ theme = 'dark' }: Props) {
timeframe: resolveBacktestRecordExecTimeframe(selectedBacktest),
toTimeSec: toSec,
fromTimeSec: fromSec > 0 ? fromSec : undefined,
barCount: selectedBacktest.barCount || 300,
barCount: resolveBacktestChartBarCount(selectedBacktest),
strategyId: selectedBacktest.strategyId,
};
}
+11 -12
View File
@@ -5,9 +5,9 @@ import React, { useState, useCallback, useEffect, useMemo, useRef } from 'react'
import { ReactFlowProvider } from '@xyflow/react';
import DraggableModalFrame from './DraggableModalFrame';
import type { Theme } from '../types/index';
import { hasRegisteredUser, loadStrategies, loadStrategy, saveStrategy, deleteStrategy, runBacktest, loadBacktestSettings, loadIndicatorSettings, API_BASE, type StrategyDto as ApiStrategyDto } from '../utils/backendApi';
import { timeframeToCandleType } from '../utils/chartCandleType';
import { hasRegisteredUser, loadStrategies, loadStrategy, saveStrategy, deleteStrategy, runBacktest, loadBacktestSettings, loadIndicatorSettings, type StrategyDto as ApiStrategyDto } from '../utils/backendApi';
import { resolveStrategyPrimaryTimeframe } from '../utils/strategyToChartIndicators';
import { fetchBacktestCandleBundle } from '../utils/backtestWarmup';
import { syncStrategyTimeframesFromLayoutIfNeeded } from '../utils/strategyTimeframeSync';
import type { LogicNode } from '../utils/strategyTypes';
import {
@@ -1178,19 +1178,17 @@ export default function StrategyEditorPage({ theme, onNavigateToBacktest }: Prop
{ buy: buyEditorState, sell: sellEditorState },
);
const execTimeframe = resolveBacktestExecTimeframe(qbTimeframe, strategyTimeframe);
const [settings, indicatorParams, barsRes] = await Promise.all([
const [settings, indicatorParams] = await Promise.all([
loadBacktestSettings(),
loadIndicatorSettings(),
fetch(`${API_BASE}/candles/history?${new URLSearchParams({
market: qbMarket,
type: timeframeToCandleType(execTimeframe),
count: '800',
})}`),
]);
if (!barsRes.ok) throw new Error(`캔들 로딩 실패 (${barsRes.status})`);
const bars = (await barsRes.json()) as Array<{
time: number; open: number; high: number; low: number; close: number; volume: number;
}>;
const { bars, evaluationBarCount } = await fetchBacktestCandleBundle({
market: qbMarket,
timeframe: execTimeframe as import('../types').Timeframe,
buyDsl: selectedStrategy?.buyCondition,
sellDsl: selectedStrategy?.sellCondition,
indicatorParams,
});
if (bars.length < 5) {
throw new Error('캔들 데이터가 부족합니다. 타임프레임을 변경하거나 잠시 후 다시 시도해주세요.');
}
@@ -1202,6 +1200,7 @@ export default function StrategyEditorPage({ theme, onNavigateToBacktest }: Prop
indicatorParams,
symbol: qbMarket,
strategyName: selectedStrategy?.name,
evaluationBarCount,
});
if (!result) throw new Error('백테스팅 실행에 실패했습니다.');
if (result.resultId) {
@@ -14,7 +14,7 @@ import {
} from '../utils/backendApi';
import { prefetchNotificationStrategies, resolveNotificationStrategy } from '../utils/resolveNotificationStrategy';
import { normalizeMarketCode } from '../utils/strategyHydrate';
import { loadAnalysisCandles } from '../utils/analysisChartData';
import { fetchBacktestCandleBundle } from '../utils/backtestWarmup';
import { buildChartBacktestReportModel } from '../utils/backtestReportModel';
import { candleTypeToTimeframe } from '../utils/strategyToChartIndicators';
import { normalizeEpochSec } from '../utils/backtestUiUtils';
@@ -70,7 +70,6 @@ import { getKoreanName } from '../utils/marketNameCache';
type RightTab = 'trade' | 'orderbook';
const TNL_RIGHT_OPEN_KEY = 'tnl-right-open';
const REPORT_BAR_COUNT = 300;
const REPORT_INDICATOR_TYPES = [
'RSI', 'MACD', 'CCI', 'SMA', 'EMA', 'IchimokuCloud', 'Stochastic', 'ADX', 'MFI', 'NewPsychological',
] as const;
@@ -298,7 +297,17 @@ export const TradeNotificationListPage: React.FC<Props> = ({
const market = normalizeMarketCode(item.market);
const timeframe = candleTypeToTimeframe(item.candleType ?? '1m');
const toTimeSec = normalizeEpochSec(item.candleTime);
const bars = await loadAnalysisCandles(market, timeframe, toTimeSec, REPORT_BAR_COUNT);
const indicatorParams = Object.fromEntries(
REPORT_INDICATOR_TYPES.map(t => [t, getParams(t) as Record<string, unknown>]),
);
const { bars, evaluationBarCount } = await fetchBacktestCandleBundle({
market,
timeframe,
toTimeSec,
buyDsl: strategy.buyCondition,
sellDsl: strategy.sellCondition,
indicatorParams,
});
if (gen !== reportGenRef.current) return;
if (bars.length < 10) {
window.alert('캔들 데이터가 부족해 분석 레포트를 생성할 수 없습니다.');
@@ -321,9 +330,8 @@ export const TradeNotificationListPage: React.FC<Props> = ({
symbol: market,
strategyName,
settings: btSettings,
indicatorParams: Object.fromEntries(
REPORT_INDICATOR_TYPES.map(t => [t, getParams(t) as Record<string, unknown>]),
),
indicatorParams,
evaluationBarCount,
});
if (gen !== reportGenRef.current) return;
if (!res) {
@@ -10,6 +10,7 @@ import {
import { loadAnalysisCandles } from '../../utils/analysisChartData';
import { normalizeChartTimeframe } from '../../utils/backtestUiUtils';
import { resolveStrategyPrimaryTimeframe } from '../../utils/strategyToChartIndicators';
import { resolveEvaluationFromLoadedBars } from '../../utils/backtestWarmup';
import type { OHLCVBar, Theme } from '../../types';
import { useIndicatorSettings } from '../../hooks/useIndicatorSettings';
import {
@@ -83,6 +84,18 @@ const PaperAnalysisChart: React.FC<Props> = ({ market, strategyId, theme = 'dark
setError(null);
try {
const btSettings = await loadBacktestSettings();
const indicatorParams = Object.fromEntries(
['RSI', 'MACD', 'CCI', 'SMA', 'EMA', 'IchimokuCloud', 'Stochastic', 'ADX', 'MFI', 'NewPsychological'].map(t => [
t,
getParams(t) as Record<string, unknown>,
]),
);
const { evaluationBarCount } = resolveEvaluationFromLoadedBars(
bars.length,
strat.buyCondition,
strat.sellCondition,
indicatorParams,
);
const res = await runBacktest({
strategyId: sid,
bars: bars.map(b => ({
@@ -97,12 +110,8 @@ const PaperAnalysisChart: React.FC<Props> = ({ market, strategyId, theme = 'dark
symbol: sym,
strategyName: strat.name,
settings: btSettings,
indicatorParams: Object.fromEntries(
['RSI', 'MACD', 'CCI', 'SMA', 'EMA', 'IchimokuCloud', 'Stochastic', 'ADX', 'MFI', 'NewPsychological'].map(t => [
t,
getParams(t) as Record<string, unknown>,
]),
),
indicatorParams,
evaluationBarCount,
});
if (gen !== backtestGenRef.current) return;
if (!res) {