diff --git a/frontend/scripts/test-threshold-normalize.mjs b/frontend/scripts/test-threshold-normalize.mjs index 1ab1d62..b43baa5 100644 --- a/frontend/scripts/test-threshold-normalize.mjs +++ b/frontend/scripts/test-threshold-normalize.mjs @@ -83,4 +83,21 @@ function migrateK(cond) { const migrated = migrateK({ indicatorType: 'RSI', rightField: 'K_55', thresholdOverride: false }); assertOk('migrate K_55 stays K_55', migrated.rightField === 'K_55' && migrated.targetValue === 55); +// 비-임계값 rightField (RSI 신호선 등) — 로드 시 기본 과열선으로 되돌리지 않음 +function migrateRightField(cond, signalType = 'buy') { + if (cond.thresholdOverride === true) return cond; + let rightField = cond.rightField; + if (rightField?.startsWith('K_')) return cond; + const THRESHOLD = new Set(['HL_OVER', 'HL_MID', 'HL_UNDER']); + const RSI_RIGHT_OPTS = ['RSI_SIGNAL', 'NONE', 'HL_OVER', 'HL_MID', 'HL_UNDER']; + if (!rightField) { + rightField = 'HL_OVER'; + } else if (!THRESHOLD.has(rightField) && !rightField.startsWith('K_')) { + if (!RSI_RIGHT_OPTS.includes(rightField)) rightField = 'HL_OVER'; + } + return { ...cond, rightField, thresholdOverride: false }; +} +const rsiSignal = migrateRightField({ indicatorType: 'RSI', rightField: 'RSI_SIGNAL', conditionType: 'CROSS_UP' }); +assertOk('RSI_SIGNAL preserved on load', rsiSignal.rightField === 'RSI_SIGNAL'); + process.exit(failed > 0 ? 1 : 0); diff --git a/frontend/src/utils/strategyConditionNormalize.ts b/frontend/src/utils/strategyConditionNormalize.ts index f36047c..cdf5d90 100644 --- a/frontend/src/utils/strategyConditionNormalize.ts +++ b/frontend/src/utils/strategyConditionNormalize.ts @@ -1,7 +1,7 @@ /** 전략 조건 DSL — 보조지표 DB 설정과 임계값·기간 상inherit 정규화 */ import type { ConditionDSL, LogicNode } from './strategyTypes'; import type { DefType } from './strategyEditorShared'; -import { getDefaultConditionFields } from './strategyEditorShared'; +import { getDefaultConditionFields, getRightFieldOpts } from './strategyEditorShared'; import { ensureDirectThresholdSnapshot, isThresholdOverridden, @@ -10,9 +10,7 @@ import { VALUE_FIELD_PREFIX, } from './conditionPeriods'; import { - defaultInheritedThresholdField, inferThresholdSymbolFromLegacyExact, - isThresholdFieldOrSymbol, isThresholdSymbol, } from './thresholdSymbols'; import { parseThresholdField, setDirectThreshold } from './conditionPeriods'; @@ -45,10 +43,13 @@ function migrateConditionThreshold( } } } - if (!rightField || (!isThresholdSymbol(rightField) && !rightField.startsWith('K_'))) { - const defaults = getDefaultConditionFields(cond.indicatorType, signalType, def); - if (isThresholdFieldOrSymbol(defaults.r)) { - rightField = defaults.r; + if (!rightField) { + rightField = getDefaultConditionFields(cond.indicatorType, signalType, def).r; + } else if (!isThresholdSymbol(rightField) && !rightField.startsWith('K_')) { + const rightOpts = getRightFieldOpts(cond.indicatorType, signalType, def, cond); + const isValidRight = rightOpts.some(o => o.value === rightField); + if (!isValidRight) { + rightField = getDefaultConditionFields(cond.indicatorType, signalType, def).r; } }