전략시간봉 오류 재수정
This commit is contained in:
@@ -13,7 +13,10 @@ import {
|
||||
saveLiveStrategySettings,
|
||||
type LiveStrategySettingsDto,
|
||||
} from '../utils/backendApi';
|
||||
import { warnStrategyTimeframeMismatch } from '../utils/strategyTimeframeSync';
|
||||
import {
|
||||
syncStrategyTimeframesFromLayoutIfNeeded,
|
||||
warnStrategyTimeframeMismatch,
|
||||
} from '../utils/strategyTimeframeSync';
|
||||
import { getKoreanName } from '../utils/marketNameCache';
|
||||
|
||||
interface Strategy {
|
||||
@@ -73,6 +76,8 @@ const LiveStrategyPanel: React.FC<LiveStrategyPanelProps> = ({
|
||||
const next: LiveStrategySettingsDto = { ...settings, ...patch, market };
|
||||
const prev = settings;
|
||||
if (next.isLiveCheck && next.strategyId != null) {
|
||||
const synced = await syncStrategyTimeframesFromLayoutIfNeeded(next.strategyId);
|
||||
if (!synced) return;
|
||||
const ok = await warnStrategyTimeframeMismatch(next.strategyId);
|
||||
if (!ok) return;
|
||||
try {
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
saveLiveStrategySettings,
|
||||
type LiveStrategySettingsDto,
|
||||
} from '../utils/backendApi';
|
||||
import { syncStrategyTimeframesFromLayoutIfNeeded } from '../utils/strategyTimeframeSync';
|
||||
import { BacktestSettingsPanel } from './BacktestSettingsPanel';
|
||||
import IndicatorMainDefaultsPanel, {
|
||||
type IndicatorSaveUiState,
|
||||
@@ -930,6 +931,10 @@ const StrategyPanel: React.FC<StrategyPanelProps> = ({
|
||||
const persistLive = useCallback(async (patch: Partial<LiveStrategySettingsDto>) => {
|
||||
if (virtualDriven) return;
|
||||
const next: LiveStrategySettingsDto = { ...liveSettings, ...patch, market: liveMarket };
|
||||
if (next.isLiveCheck && next.strategyId != null) {
|
||||
const synced = await syncStrategyTimeframesFromLayoutIfNeeded(next.strategyId);
|
||||
if (!synced) return;
|
||||
}
|
||||
setLiveSaving(true);
|
||||
try {
|
||||
const saved = await saveLiveStrategySettings(next);
|
||||
|
||||
@@ -52,7 +52,10 @@ import {
|
||||
syncVirtualTargetsToBackend,
|
||||
stopVirtualLiveOnBackend,
|
||||
} from '../utils/virtualLiveStrategySync';
|
||||
import { warnStrategyTimeframeMismatch } from '../utils/strategyTimeframeSync';
|
||||
import {
|
||||
syncStrategyTimeframesFromLayoutIfNeeded,
|
||||
warnStrategyTimeframeMismatch,
|
||||
} from '../utils/strategyTimeframeSync';
|
||||
import { pinStrategyEvaluationTimeframes } from '../utils/strategyTimeframePin';
|
||||
import { persistVirtualTargetPinned } from '../utils/virtualTargetMutations';
|
||||
import {
|
||||
@@ -284,6 +287,11 @@ const VirtualTradingPage: React.FC<Props> = ({
|
||||
return;
|
||||
}
|
||||
const globalStrat = strategies.find(s => s.id === session.globalStrategyId);
|
||||
const synced = await syncStrategyTimeframesFromLayoutIfNeeded(
|
||||
session.globalStrategyId,
|
||||
globalStrat,
|
||||
);
|
||||
if (!synced) return;
|
||||
const ok = await warnStrategyTimeframeMismatch(session.globalStrategyId, globalStrat);
|
||||
if (!ok) return;
|
||||
try {
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { loadStrategyTimeframes, pinCandleWatch, repairStrategyTimeframes } from './backendApi';
|
||||
import { normalizeStartCandleType } from './strategyStartNodes';
|
||||
import { syncStrategyTimeframesFromLayoutIfNeeded } from './strategyTimeframeSync';
|
||||
|
||||
/** 전략 DSL 평가 분봉 전체 pin — 1m 집계·상위 봉 warm-up */
|
||||
export async function pinStrategyEvaluationTimeframes(
|
||||
market: string,
|
||||
strategyId: number,
|
||||
): Promise<string[]> {
|
||||
await syncStrategyTimeframesFromLayoutIfNeeded(strategyId);
|
||||
try {
|
||||
await repairStrategyTimeframes(strategyId);
|
||||
} catch {
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user