전략편집기 기준값 보조지표 설정값 동기화
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
/** 전략 조건 DSL — 보조지표 DB 설정과 임계값·기간 상inherit 정규화 */
|
||||
import type { ConditionDSL, LogicNode } from './strategyTypes';
|
||||
import type { DefType } from './strategyEditorShared';
|
||||
import { getDefaultConditionFields } from './strategyEditorShared';
|
||||
import {
|
||||
isThresholdOverridden,
|
||||
isValuePeriodOverridden,
|
||||
parseThresholdField,
|
||||
usesValuePeriodField,
|
||||
VALUE_FIELD_PREFIX,
|
||||
} from './conditionPeriods';
|
||||
import {
|
||||
defaultInheritedThresholdField,
|
||||
inferThresholdSymbolFromLegacy,
|
||||
isThresholdFieldOrSymbol,
|
||||
isThresholdSymbol,
|
||||
THRESHOLD_HL_MID,
|
||||
THRESHOLD_HL_OVER,
|
||||
} from './thresholdSymbols';
|
||||
|
||||
function migrateConditionThreshold(
|
||||
cond: ConditionDSL,
|
||||
def: DefType,
|
||||
signalType: 'buy' | 'sell',
|
||||
): ConditionDSL {
|
||||
if (isThresholdOverridden(cond)) return cond;
|
||||
|
||||
let rightField = cond.rightField;
|
||||
if (rightField?.startsWith('K_')) {
|
||||
rightField = inferThresholdSymbolFromLegacy(rightField, cond.indicatorType, def.hlThresh);
|
||||
}
|
||||
if (!rightField || (!isThresholdSymbol(rightField) && !rightField.startsWith('K_'))) {
|
||||
const defaults = getDefaultConditionFields(cond.indicatorType, signalType, def);
|
||||
if (isThresholdFieldOrSymbol(defaults.r)) {
|
||||
rightField = defaults.r;
|
||||
}
|
||||
}
|
||||
|
||||
const next: ConditionDSL = {
|
||||
...cond,
|
||||
rightField,
|
||||
thresholdOverride: false,
|
||||
};
|
||||
const { targetValue: _t, ...rest } = next;
|
||||
return rest;
|
||||
}
|
||||
|
||||
function migrateConditionPeriod(cond: ConditionDSL): ConditionDSL {
|
||||
if (isValuePeriodOverridden(cond)) return cond;
|
||||
const prefix = VALUE_FIELD_PREFIX[cond.indicatorType];
|
||||
if (!prefix || !usesValuePeriodField(cond)) return cond;
|
||||
const lf = cond.leftField ?? '';
|
||||
if (lf.startsWith(`${prefix}_`)) {
|
||||
const { period: _p, ...rest } = cond;
|
||||
return { ...rest, leftField: prefix, valuePeriodOverride: false };
|
||||
}
|
||||
return cond;
|
||||
}
|
||||
|
||||
export function migrateConditionForEditor(
|
||||
cond: ConditionDSL,
|
||||
def: DefType,
|
||||
signalType: 'buy' | 'sell',
|
||||
): ConditionDSL {
|
||||
return migrateConditionPeriod(migrateConditionThreshold(cond, def, signalType));
|
||||
}
|
||||
|
||||
function migrateLogicNode(
|
||||
node: LogicNode,
|
||||
def: DefType,
|
||||
signalType: 'buy' | 'sell',
|
||||
): LogicNode {
|
||||
let next = node;
|
||||
if (node.type === 'CONDITION' && node.condition) {
|
||||
next = { ...node, condition: migrateConditionForEditor(node.condition, def, signalType) };
|
||||
}
|
||||
if (next.children?.length) {
|
||||
next = { ...next, children: next.children.map(c => migrateLogicNode(c, def, signalType)) };
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
/** 전략 로드·편집기 진입 시 레거시 K_ 스냅샷 → hline 역할 심볼 */
|
||||
export function migrateLogicRootForEditor(
|
||||
root: LogicNode | null,
|
||||
orphans: LogicNode[],
|
||||
def: DefType,
|
||||
signalType: 'buy' | 'sell',
|
||||
): { root: LogicNode | null; orphans: LogicNode[] } {
|
||||
return {
|
||||
root: root ? migrateLogicNode(root, def, signalType) : null,
|
||||
orphans: orphans.map(n => migrateLogicNode(n, def, signalType)),
|
||||
};
|
||||
}
|
||||
|
||||
/** 저장 직전 — 상속 모드는 숫자 스냅샷·targetValue 제거 */
|
||||
export function normalizeConditionForPersistence(cond: ConditionDSL): ConditionDSL {
|
||||
let next: ConditionDSL = { ...cond };
|
||||
|
||||
if (!isThresholdOverridden(next)) {
|
||||
if (next.rightField?.startsWith('K_')) {
|
||||
next.rightField = defaultInheritedThresholdField(next.indicatorType);
|
||||
}
|
||||
if (!isThresholdSymbol(next.rightField)) {
|
||||
const role = next.indicatorType === 'DISPARITY' || next.indicatorType === 'VOLUME_OSC'
|
||||
|| next.indicatorType === 'TRIX'
|
||||
? THRESHOLD_HL_MID : THRESHOLD_HL_OVER;
|
||||
if (parseThresholdField(next.rightField) != null || !next.rightField) {
|
||||
next.rightField = role;
|
||||
}
|
||||
}
|
||||
const { targetValue: _t, ...rest } = next;
|
||||
next = { ...rest, thresholdOverride: false };
|
||||
}
|
||||
|
||||
if (!isValuePeriodOverridden(next)) {
|
||||
const prefix = VALUE_FIELD_PREFIX[next.indicatorType];
|
||||
if (prefix && usesValuePeriodField(next)) {
|
||||
next.leftField = prefix;
|
||||
}
|
||||
const { period: _p, ...rest } = next;
|
||||
next = rest;
|
||||
}
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
function normalizeLogicNode(node: LogicNode): LogicNode {
|
||||
let next = node;
|
||||
if (node.type === 'CONDITION' && node.condition) {
|
||||
next = { ...node, condition: normalizeConditionForPersistence(node.condition) };
|
||||
}
|
||||
if (next.children?.length) {
|
||||
next = { ...next, children: next.children.map(normalizeLogicNode) };
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
export function normalizeLogicRootForPersistence(root: LogicNode | null): LogicNode | null {
|
||||
return root ? normalizeLogicNode(root) : null;
|
||||
}
|
||||
Reference in New Issue
Block a user