매매 시그널 알림 화면 수정

This commit is contained in:
Macbook
2026-05-29 00:46:20 +09:00
parent cbad62a5b0
commit e43b5cbd5a
45 changed files with 2186 additions and 415 deletions
+44 -16
View File
@@ -7,6 +7,7 @@ import {
encodeConditionForSave,
mergeStartMetaForLoad,
normalizeStartCombineOp,
syncEditorStateCandleTypesForSave,
type EditorConditionState,
} from './strategyConditionSerde';
import { getStartCandleTypes, normalizeStartCandleType } from './strategyStartNodes';
@@ -59,8 +60,20 @@ export function collectUiEvaluationTimeframes(
return collectFromStrategyDto(strategy);
}
function evaluationTimeframesMatch(uiTfs: string[], serverTfs: string[]): boolean {
const uiSet = new Set(uiTfs.map(tf => normalizeStartCandleType(tf)));
const serverSet = new Set(serverTfs.map(tf => normalizeStartCandleType(tf)));
const missingOnServer = uiTfs.some(tf => !serverSet.has(normalizeStartCandleType(tf)));
const extraOnServer = [...serverSet].some(tf => !uiSet.has(tf));
if (!missingOnServer && !extraOnServer) return true;
if (uiSet.size === 1 && uiSet.has('1m') && serverSet.size === 1 && serverSet.has('1m')) {
return true;
}
return false;
}
/**
* UI 분봉과 서버 DSL 불일치 시 안내.
* UI 분봉과 서버 DSL 불일치 시 자동 동기화 시도 후, 여전히 다르면 안내.
* @returns false — 진행 불가(저장 필요)
*/
export async function warnStrategyTimeframeMismatch(
@@ -68,7 +81,8 @@ export async function warnStrategyTimeframeMismatch(
strategy?: StrategyDto | null,
editorState?: { buy: EditorConditionState; sell: EditorConditionState },
): Promise<boolean> {
const uiTfs = collectUiEvaluationTimeframes(strategyId, strategy, editorState);
const strat = strategy ?? await loadStrategy(strategyId);
const uiTfs = collectUiEvaluationTimeframes(strategyId, strat, editorState);
if (uiTfs.length === 0) return true;
let serverTfs: string[];
@@ -77,18 +91,27 @@ export async function warnStrategyTimeframeMismatch(
} catch {
return true;
}
if (evaluationTimeframesMatch(uiTfs, serverTfs)) return true;
const synced = await syncStrategyTimeframesFromLayoutIfNeeded(strategyId, strat);
if (synced) {
try {
serverTfs = await loadStrategyTimeframes(strategyId);
if (evaluationTimeframesMatch(uiTfs, serverTfs)) return true;
} 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)));
const extraOnServer = [...serverSet].filter(tf => !uiSet.has(tf));
if (missingOnServer.length === 0 && extraOnServer.length === 0) return true;
if (uiSet.size === 1 && uiSet.has('1m') && serverSet.size === 1 && serverSet.has('1m')) {
const uiLabel = uiTfs.map(tf => normalizeStartCandleType(tf)).join(', ') || '1m';
const serverLabel = [...serverSet].join(', ') || '1m';
if (uiSet.size === serverSet.size && [...uiSet].every(tf => serverSet.has(tf))) {
return true;
}
const uiLabel = uiTfs.map(tf => normalizeStartCandleType(tf)).join(', ') || '1m';
const serverLabel = [...serverSet].join(', ') || '1m';
window.alert(
`전략 화면에는 ${uiLabel} 봉으로 설정되어 있지만, 서버에는 ${serverLabel} 만 저장되어 있습니다.\n\n`
+ '전략편집기에서 「저장」을 누르거나 START 분봉을 다시 선택해 DB에 반영한 뒤 실시간 체크를 켜 주세요.',
@@ -105,13 +128,14 @@ function buildEditorStateFromStrategy(
(side === 'buy' ? strategy.buyCondition : strategy.sellCondition) as LogicNode | null,
);
const snap = layout?.[side];
return {
const merged: EditorConditionState = {
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),
};
return syncEditorStateCandleTypesForSave(merged);
}
/**
@@ -121,7 +145,7 @@ export async function syncStrategyTimeframesFromLayoutIfNeeded(
strategyId: number,
strategy?: StrategyDto | null,
): Promise<boolean> {
const strat = strategy ?? await loadStrategy(strategyId);
const strat = (await loadStrategy(strategyId)) ?? strategy ?? null;
if (!strat) return true;
const uiTfs = collectUiEvaluationTimeframes(strategyId, strat);
@@ -133,14 +157,18 @@ export async function syncStrategyTimeframesFromLayoutIfNeeded(
} 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;
if (evaluationTimeframesMatch(uiTfs, serverTfs)) return true;
const layout = strat.flowLayout;
if (!layout) {
return warnStrategyTimeframeMismatch(strategyId, strat);
try {
await repairStrategyTimeframes(strategyId);
const repaired = await loadStrategyTimeframes(strategyId);
return evaluationTimeframesMatch(uiTfs, repaired);
} catch {
return false;
}
}
const buyState = buildEditorStateFromStrategy(strat, layout, 'buy');