백테스팅 워밍업데이터 적용
This commit is contained in:
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user