백테스팅 시간봉 문제
This commit is contained in:
@@ -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),
|
||||||
|
|||||||
@@ -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,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user