전략시간봉 오류 재수정

This commit is contained in:
Macbook
2026-05-28 02:09:38 +09:00
parent 5f9b20d907
commit 9137864f48
8 changed files with 215 additions and 90 deletions
+76 -5
View File
@@ -1,13 +1,16 @@
import { loadStrategyTimeframes, saveStrategy, type StrategyDto } from './backendApi';
import { loadStrategy, loadStrategyTimeframes, repairStrategyTimeframes, saveStrategy, type StrategyDto } from './backendApi';
import { loadStrategyFlowLayout } from './strategyEditorLayoutStorage';
import {
alignBuySellStartCandleTypesForSave,
collectDslBranches,
collectTimeframesFromEditorState,
decodeConditionForEditor,
encodeConditionForSave,
mergeStartMetaForLoad,
normalizeStartCombineOp,
type EditorConditionState,
} from './strategyConditionSerde';
import { START_NODE_ID, getStartCandleTypes, normalizeStartCandleType } from './strategyStartNodes';
import { getStartCandleTypes, normalizeStartCandleType } from './strategyStartNodes';
import type { LogicNode } from './strategyTypes';
import type { StrategyFlowLayoutStore } from './strategyEditorLayoutStorage';
@@ -16,9 +19,11 @@ function collectFromFlowLayout(strategyId: number): string[] {
if (!layout) return [];
const set = new Set<string>();
for (const side of ['buy', 'sell'] as const) {
const meta = layout[side]?.startMeta?.[START_NODE_ID];
if (!meta) continue;
for (const ct of getStartCandleTypes(meta)) set.add(normalizeStartCandleType(ct));
const metaMap = layout[side]?.startMeta;
if (!metaMap) continue;
for (const meta of Object.values(metaMap)) {
for (const ct of getStartCandleTypes(meta)) set.add(normalizeStartCandleType(ct));
}
}
return [...set];
}
@@ -94,6 +99,72 @@ export async function warnStrategyTimeframeMismatch(
return false;
}
function buildEditorStateFromStrategy(
strategy: StrategyDto,
layout: StrategyFlowLayoutStore | null,
side: 'buy' | 'sell',
): EditorConditionState {
const decoded = decodeConditionForEditor(
(side === 'buy' ? strategy.buyCondition : strategy.sellCondition) as LogicNode | null,
);
const snap = layout?.[side];
return {
root: decoded.root,
startMeta: mergeStartMetaForLoad(decoded.startMeta, snap?.startMeta),
extraStartIds: snap?.extraStartIds?.length ? snap.extraStartIds : decoded.extraStartIds,
extraRoots: snap?.extraRoots ?? decoded.extraRoots,
startCombineOp: normalizeStartCombineOp(snap?.startCombineOp ?? decoded.startCombineOp),
};
}
/**
* flow layout(localStorage)의 START 분봉이 서버 DSL과 다르면 편집기 저장 없이 자동 동기화.
* @returns false — layout 없음 등으로 동기화 불가
*/
export async function syncStrategyTimeframesFromLayoutIfNeeded(
strategyId: number,
strategy?: StrategyDto | null,
): Promise<boolean> {
const uiTfs = collectUiEvaluationTimeframes(strategyId, strategy);
if (uiTfs.length === 0) return true;
let serverTfs: string[];
try {
serverTfs = await loadStrategyTimeframes(strategyId);
} catch {
return true;
}
const uiSet = new Set(uiTfs.map(tf => normalizeStartCandleType(tf)));
const serverSet = new Set(serverTfs.map(tf => normalizeStartCandleType(tf)));
const missingOnServer = uiTfs.filter(tf => !serverSet.has(normalizeStartCandleType(tf)));
if (missingOnServer.length === 0) return true;
const layout = loadStrategyFlowLayout(String(strategyId));
if (!layout) {
return warnStrategyTimeframeMismatch(strategyId, strategy);
}
const strat = strategy ?? await loadStrategy(strategyId);
if (!strat) return true;
const buyState = buildEditorStateFromStrategy(strat, layout, 'buy');
const sellState = buildEditorStateFromStrategy(strat, layout, 'sell');
await persistStrategyEvaluationTimeframes(
strategyId,
strat.name,
strat.description ?? '',
buyState,
sellState,
);
try {
await repairStrategyTimeframes(strategyId);
} catch {
/* repair optional */
}
return true;
}
/** START 분봉 변경 시 전략 DSL을 서버에 즉시 반영 */
export async function persistStrategyEvaluationTimeframes(
strategyId: number,