백테스팅 시간봉 문제

This commit is contained in:
Macbook
2026-06-12 00:07:48 +09:00
parent d5338274d4
commit 578cba3eb8
3 changed files with 44 additions and 28 deletions
+10 -10
View File
@@ -1173,10 +1173,10 @@ export default function StrategyEditorPage({ theme, onNavigateToBacktest }: Prop
setQbError(null); setQbError(null);
try { try {
const selectedStrategy = strategies.find(s => s.id === selectedId); const selectedStrategy = strategies.find(s => s.id === selectedId);
const strategyTimeframe = resolveStrategyPrimaryTimeframe({ const strategyTimeframe = resolveStrategyPrimaryTimeframe(
buyCondition: buyCondition, selectedStrategy,
sellCondition: sellCondition, { buy: buyEditorState, sell: sellEditorState },
} as ApiStrategyDto); );
const execTimeframe = resolveBacktestExecTimeframe(qbTimeframe, strategyTimeframe); const execTimeframe = resolveBacktestExecTimeframe(qbTimeframe, strategyTimeframe);
const [settings, indicatorParams, barsRes] = await Promise.all([ const [settings, indicatorParams, barsRes] = await Promise.all([
loadBacktestSettings(), loadBacktestSettings(),
@@ -1213,14 +1213,14 @@ export default function StrategyEditorPage({ theme, onNavigateToBacktest }: Prop
} finally { } finally {
setQbLoading(false); setQbLoading(false);
} }
}, [selectedId, strategies, qbMarket, qbTimeframe, buyCondition, sellCondition, onNavigateToBacktest]); }, [selectedId, strategies, qbMarket, qbTimeframe, buyEditorState, sellEditorState, onNavigateToBacktest]);
const qbStrategyTimeframe = useMemo( const qbStrategyTimeframe = useMemo(
() => resolveStrategyPrimaryTimeframe({ () => resolveStrategyPrimaryTimeframe(
buyCondition: buyCondition, selectedId ? strategies.find(s => s.id === selectedId) : undefined,
sellCondition: sellCondition, { buy: buyEditorState, sell: sellEditorState },
} as ApiStrategyDto), ),
[buyCondition, sellCondition], [selectedId, strategies, buyEditorState, sellEditorState],
); );
const qbTimeframeOptions = useMemo( const qbTimeframeOptions = useMemo(
() => buildQuickRunTimeframeSelectOptions(qbStrategyTimeframe), () => buildQuickRunTimeframeSelectOptions(qbStrategyTimeframe),
+27 -15
View File
@@ -17,7 +17,8 @@ import { mergeGlobalDefaultsOntoChartIndicators } from './indicatorMainConfig';
import { buildMainIndicatorConfig } from './indicatorMainConfig'; import { buildMainIndicatorConfig } from './indicatorMainConfig';
import { normalizeSmaConfig } from './smaConfig'; import { normalizeSmaConfig } from './smaConfig';
import { normalizeIchimokuConfig } from './ichimokuConfig'; import { normalizeIchimokuConfig } from './ichimokuConfig';
import { extractVirtualConditions } from './virtualStrategyConditions'; import type { EditorConditionState } from './strategyConditionSerde';
import { collectUiEvaluationTimeframes } from './strategyTimeframeSync';
const DSL_TO_REGISTRY: Record<string, string> = { const DSL_TO_REGISTRY: Record<string, string> = {
RSI: 'RSI', RSI: 'RSI',
@@ -93,22 +94,33 @@ export function candleTypeToTimeframe(candleType: string): Timeframe {
return '1m'; return '1m';
} }
export function resolveStrategyTimeframes(strategy: StrategyDto | undefined): Timeframe[] { const TIMEFRAME_SORT_ORDER: Timeframe[] = ['1m', '3m', '5m', '10m', '15m', '30m', '1h', '4h', '1D', '1W', '1M'];
if (!strategy) return ['1m'];
const conditions = extractVirtualConditions(strategy); function sortStrategyTimeframes(tfs: Timeframe[]): Timeframe[] {
const set = new Set<Timeframe>(); return [...tfs].sort(
for (const row of conditions) { (a, b) => TIMEFRAME_SORT_ORDER.indexOf(a) - TIMEFRAME_SORT_ORDER.indexOf(b),
set.add(candleTypeToTimeframe(row.timeframe)); );
}
if (set.size === 0) set.add('1m');
return [...set].sort((a, b) => {
const order: Timeframe[] = ['1m', '3m', '5m', '10m', '15m', '30m', '1h', '4h', '1D', '1W', '1M'];
return order.indexOf(a) - order.indexOf(b);
});
} }
export function resolveStrategyPrimaryTimeframe(strategy: StrategyDto | undefined): Timeframe { /** 편집기 START·flowLayout·저장 DSL에서 전략 평가 시간봉 수집 */
const tfs = resolveStrategyTimeframes(strategy); export function resolveStrategyTimeframes(
strategy: StrategyDto | undefined,
editorState?: { buy: EditorConditionState; sell: EditorConditionState },
): Timeframe[] {
const candleTypes = collectUiEvaluationTimeframes(strategy?.id ?? 0, strategy ?? null, editorState);
if (candleTypes.length === 0) return ['1m'];
const set = new Set<Timeframe>();
for (const ct of candleTypes) {
set.add(candleTypeToTimeframe(ct));
}
return sortStrategyTimeframes([...set]);
}
export function resolveStrategyPrimaryTimeframe(
strategy: StrategyDto | undefined,
editorState?: { buy: EditorConditionState; sell: EditorConditionState },
): Timeframe {
const tfs = resolveStrategyTimeframes(strategy, editorState);
return tfs[0] ?? '1m'; return tfs[0] ?? '1m';
} }
@@ -7,6 +7,7 @@ import type { StrategyDto } from './backendApi';
import { formatIndicatorDisplayLabel } from './indicatorRegistry'; import { formatIndicatorDisplayLabel } from './indicatorRegistry';
import { coerceFiniteNumber, safeToFixed } from './safeFormat'; import { coerceFiniteNumber, safeToFixed } from './safeFormat';
import { asLogicNode } from './strategyHydrate'; import { asLogicNode } from './strategyHydrate';
import { normalizeStartCandleType } from './strategyStartNodes';
export { coerceFiniteNumber } from './safeFormat'; export { coerceFiniteNumber } from './safeFormat';
@@ -37,7 +38,9 @@ function walk(
if (!node) return; if (!node) return;
if (node.type === 'TIMEFRAME') { if (node.type === 'TIMEFRAME') {
const tf = node.candleType ?? timeframe; const tf = normalizeStartCandleType(
node.candleTypes?.[0] ?? node.candleType ?? timeframe,
);
node.children?.forEach(c => walk(c, tf, side, out, seen)); node.children?.forEach(c => walk(c, tf, side, out, seen));
return; return;
} }
@@ -51,7 +54,8 @@ function walk(
// StrategyDslToTa4jAdapter 와 동일: type 미설정(구버전 DSL) 시 CONDITION 으로 처리 // StrategyDslToTa4jAdapter 와 동일: type 미설정(구버전 DSL) 시 CONDITION 으로 처리
if ((!node.type || node.type === 'CONDITION') && node.condition) { if ((!node.type || node.type === 'CONDITION') && node.condition) {
const c = node.condition; const c = node.condition;
const key = `${side}:${timeframe}:${c.indicatorType}:${c.conditionType}:${c.targetValue ?? ''}:${c.leftField ?? ''}`; const rowTf = normalizeStartCandleType(c.leftCandleType ?? c.rightCandleType ?? timeframe);
const key = `${side}:${rowTf}:${c.indicatorType}:${c.conditionType}:${c.targetValue ?? ''}:${c.leftField ?? ''}`;
if (seen.has(key)) return; if (seen.has(key)) return;
seen.add(key); seen.add(key);
@@ -66,7 +70,7 @@ function walk(
conditionType: c.conditionType, conditionType: c.conditionType,
conditionLabel: condLabel, conditionLabel: condLabel,
targetValue: target, targetValue: target,
timeframe, timeframe: rowTf,
side, side,
plotKey, plotKey,
}); });