보조지표 직접입력 오류 수정
This commit is contained in:
@@ -20,12 +20,18 @@ public final class IndicatorHlineResolver {
|
|||||||
|
|
||||||
if (field == null || field.isBlank()) return null;
|
if (field == null || field.isBlank()) return null;
|
||||||
|
|
||||||
// K_ 직접입력 — targetValue 가 있으면 thresholdOverride 플래그와 무관하게 우선
|
boolean override = cond != null && cond.path("thresholdOverride").asBoolean(false);
|
||||||
if (field.startsWith("K_") && cond != null && cond.has("targetValue") && !cond.path("targetValue").isNull()) {
|
|
||||||
return cond.path("targetValue").asDouble();
|
// K_ 직접입력 — targetValue·override 시 필드명/스냅샷 숫자 우선
|
||||||
|
if (field.startsWith("K_")) {
|
||||||
|
if (cond != null && cond.has("targetValue") && !cond.path("targetValue").isNull()) {
|
||||||
|
return cond.path("targetValue").asDouble();
|
||||||
|
}
|
||||||
|
if (override) {
|
||||||
|
try { return Double.parseDouble(field.substring(2)); } catch (NumberFormatException ignored) {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean override = cond != null && cond.path("thresholdOverride").asBoolean(false);
|
|
||||||
if (override) {
|
if (override) {
|
||||||
if (cond.has("targetValue") && !cond.path("targetValue").isNull()) {
|
if (cond.has("targetValue") && !cond.path("targetValue").isNull()) {
|
||||||
return cond.path("targetValue").asDouble();
|
return cond.path("targetValue").asDouble();
|
||||||
|
|||||||
@@ -281,6 +281,19 @@ export default function StrategyEvaluationPage({ theme = 'dark' }: Props) {
|
|||||||
return () => { cancelled = true; };
|
return () => { cancelled = true; };
|
||||||
}, [selectedStrategyId]);
|
}, [selectedStrategyId]);
|
||||||
|
|
||||||
|
/** 편집기 저장 후 동일 전략 평가 시 DSL·시그널 갱신 */
|
||||||
|
useEffect(() => {
|
||||||
|
const onSaved = (e: Event) => {
|
||||||
|
const detail = (e as CustomEvent<{ strategyId?: number }>).detail;
|
||||||
|
if (detail?.strategyId == null || detail.strategyId !== selectedStrategyId) return;
|
||||||
|
void loadStrategy(detail.strategyId).then(s => {
|
||||||
|
setSelectedStrategy(s ? hydrateStrategyDto(s) : null);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
window.addEventListener('gc:strategy-saved', onSaved);
|
||||||
|
return () => window.removeEventListener('gc:strategy-saved', onSaved);
|
||||||
|
}, [selectedStrategyId]);
|
||||||
|
|
||||||
const filteredStrategies = useMemo(() => {
|
const filteredStrategies = useMemo(() => {
|
||||||
const q = strategySearch.trim().toLowerCase();
|
const q = strategySearch.trim().toLowerCase();
|
||||||
if (!q) return strategies;
|
if (!q) return strategies;
|
||||||
|
|||||||
@@ -82,6 +82,15 @@ function parseFieldPeriod(field?: string): number | null {
|
|||||||
return Number.isFinite(n) && n > 0 ? n : null;
|
return Number.isFinite(n) && n > 0 ? n : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 조건 DSL에서 Ta4j 평가용 기간 — period 필드·leftField 접미사 */
|
||||||
|
export function resolveConditionDslPeriod(c: ConditionDSL): number | undefined {
|
||||||
|
if (c.period != null && c.period > 0) return c.period;
|
||||||
|
const fromLeft = parseFieldPeriod(c.leftField);
|
||||||
|
if (fromLeft) return fromLeft;
|
||||||
|
if (c.composite && c.leftPeriod != null && c.leftPeriod > 0) return c.leftPeriod;
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
/** 조건 노드의 RSI 등 값(좌측) 기간 — true=전략 전용, false/미설정=보조지표 설정(DEF) 상속 */
|
/** 조건 노드의 RSI 등 값(좌측) 기간 — true=전략 전용, false/미설정=보조지표 설정(DEF) 상속 */
|
||||||
export function isValuePeriodOverridden(cond: ConditionDSL): boolean {
|
export function isValuePeriodOverridden(cond: ConditionDSL): boolean {
|
||||||
return cond.valuePeriodOverride === true;
|
return cond.valuePeriodOverride === true;
|
||||||
@@ -271,7 +280,7 @@ export function activateDirectThresholdInput(
|
|||||||
?? getChartReferenceThreshold(cond, side, inheritedField, def)
|
?? getChartReferenceThreshold(cond, side, inheritedField, def)
|
||||||
?? parseThresholdField(side === 'right' ? cond.rightField : cond.leftField);
|
?? parseThresholdField(side === 'right' ? cond.rightField : cond.leftField);
|
||||||
if (value == null || !Number.isFinite(value)) return cond;
|
if (value == null || !Number.isFinite(value)) return cond;
|
||||||
return setConditionThreshold(cond, side, value, def, { directInput: true });
|
return setConditionThreshold(cond, side, value, def);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setConditionThreshold(
|
export function setConditionThreshold(
|
||||||
@@ -302,22 +311,21 @@ export function setConditionThreshold(
|
|||||||
? getChartReferenceThreshold(cond, side, inheritedField, def)
|
? getChartReferenceThreshold(cond, side, inheritedField, def)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
if (!options?.directInput) {
|
// 30/50/70 등 hline 역할과 정확히 일치하면 직접입력·프리셋 모두 HL_* 로 통일 (평가 일치)
|
||||||
const matchedRole = def
|
const matchedRole = def
|
||||||
? inferThresholdRoleForValue(value, cond.indicatorType, def.hlThresh)
|
? inferThresholdRoleForValue(value, cond.indicatorType, def.hlThresh)
|
||||||
: null;
|
: null;
|
||||||
if (matchedRole != null) {
|
if (matchedRole != null) {
|
||||||
const { targetValue: _t, ...rest } = cond;
|
const { targetValue: _t, ...rest } = cond;
|
||||||
return { ...rest, [fieldKey]: matchedRole, thresholdOverride: false };
|
return { ...rest, [fieldKey]: matchedRole, thresholdOverride: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chartRef != null && Math.abs(chartRef - value) < 0.0001) {
|
if (!options?.directInput && chartRef != null && Math.abs(chartRef - value) < 0.0001) {
|
||||||
const role = isThresholdSymbol(inheritedField)
|
const role = isThresholdSymbol(inheritedField)
|
||||||
? inheritedField
|
? inheritedField
|
||||||
: (side === 'right' ? THRESHOLD_HL_OVER : inheritedField);
|
: (side === 'right' ? THRESHOLD_HL_OVER : inheritedField);
|
||||||
const { targetValue: _t, ...rest } = cond;
|
const { targetValue: _t, ...rest } = cond;
|
||||||
return { ...rest, [fieldKey]: role, thresholdOverride: false };
|
return { ...rest, [fieldKey]: role, thresholdOverride: false };
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
} from './conditionPeriods';
|
} from './conditionPeriods';
|
||||||
import {
|
import {
|
||||||
defaultInheritedThresholdField,
|
defaultInheritedThresholdField,
|
||||||
|
inferThresholdRoleForValue,
|
||||||
inferThresholdSymbolFromLegacy,
|
inferThresholdSymbolFromLegacy,
|
||||||
inferThresholdSymbolFromLegacyExact,
|
inferThresholdSymbolFromLegacyExact,
|
||||||
isThresholdFieldOrSymbol,
|
isThresholdFieldOrSymbol,
|
||||||
@@ -97,10 +98,26 @@ export function migrateLogicRootForEditor(
|
|||||||
return repairPriceExtremePairForest(migrated.root, migrated.orphans);
|
return repairPriceExtremePairForest(migrated.root, migrated.orphans);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 저장 직전 — 상속 모드는 숫자 스냅샷·targetValue 제거 (직접입력 K_+targetValue 는 유지) */
|
/** 저장 직전 — 상속 모드는 숫자 스냅샷·targetValue 제거; 직접입력도 hline 역할과 일치하면 HL_* 로 통일 */
|
||||||
export function normalizeConditionForPersistence(cond: ConditionDSL): ConditionDSL {
|
export function normalizeConditionForPersistence(cond: ConditionDSL): ConditionDSL {
|
||||||
let next: ConditionDSL = { ...cond };
|
let next: ConditionDSL = { ...cond };
|
||||||
|
|
||||||
|
if (isThresholdOverridden(next)) {
|
||||||
|
if (next.targetValue != null && Number.isFinite(next.targetValue)) {
|
||||||
|
const role = inferThresholdRoleForValue(next.targetValue, next.indicatorType, {});
|
||||||
|
if (role != null) {
|
||||||
|
const { targetValue: _t, ...rest } = next;
|
||||||
|
next = { ...rest, rightField: role, thresholdOverride: false };
|
||||||
|
}
|
||||||
|
} else if (next.rightField?.startsWith('K_')) {
|
||||||
|
const role = inferThresholdSymbolFromLegacyExact(next.rightField, next.indicatorType, {});
|
||||||
|
if (role != null) {
|
||||||
|
const { targetValue: _t, ...rest } = next;
|
||||||
|
next = { ...rest, rightField: role, thresholdOverride: false };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!isThresholdOverridden(next)) {
|
if (!isThresholdOverridden(next)) {
|
||||||
if (next.rightField?.startsWith('K_')) {
|
if (next.rightField?.startsWith('K_')) {
|
||||||
next.rightField = inferThresholdSymbolFromLegacyExact(
|
next.rightField = inferThresholdSymbolFromLegacyExact(
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { normalizeSmaConfig } from './smaConfig';
|
|||||||
import { normalizeIchimokuConfig } from './ichimokuConfig';
|
import { normalizeIchimokuConfig } from './ichimokuConfig';
|
||||||
import type { EditorConditionState } from './strategyConditionSerde';
|
import type { EditorConditionState } from './strategyConditionSerde';
|
||||||
import { collectUiEvaluationTimeframes } from './strategyTimeframeSync';
|
import { collectUiEvaluationTimeframes } from './strategyTimeframeSync';
|
||||||
|
import { resolveConditionDslPeriod } from './conditionPeriods';
|
||||||
import {
|
import {
|
||||||
applyStrategyRefToParams,
|
applyStrategyRefToParams,
|
||||||
resolveConditionThresholdForChart,
|
resolveConditionThresholdForChart,
|
||||||
@@ -139,13 +140,13 @@ function walkIndicatorRefs(
|
|||||||
const c = node.condition;
|
const c = node.condition;
|
||||||
const dslType = c.indicatorType;
|
const dslType = c.indicatorType;
|
||||||
const registryType = DSL_TO_REGISTRY[dslType] ?? dslType;
|
const registryType = DSL_TO_REGISTRY[dslType] ?? dslType;
|
||||||
const key = `${registryType}:${c.period ?? ''}:${c.leftPeriod ?? ''}:${c.rightPeriod ?? ''}:${resolveConditionThresholdForChart(c) ?? ''}`;
|
const key = `${registryType}:${resolveConditionDslPeriod(c) ?? ''}:${c.leftPeriod ?? ''}:${c.rightPeriod ?? ''}:${resolveConditionThresholdForChart(c) ?? ''}`;
|
||||||
if (seen.has(key)) return;
|
if (seen.has(key)) return;
|
||||||
seen.add(key);
|
seen.add(key);
|
||||||
out.push({
|
out.push({
|
||||||
dslType,
|
dslType,
|
||||||
registryType,
|
registryType,
|
||||||
period: c.period,
|
period: resolveConditionDslPeriod(c),
|
||||||
leftPeriod: c.leftPeriod,
|
leftPeriod: c.leftPeriod,
|
||||||
rightPeriod: c.rightPeriod,
|
rightPeriod: c.rightPeriod,
|
||||||
targetValue: resolveConditionThresholdForChart(c),
|
targetValue: resolveConditionThresholdForChart(c),
|
||||||
|
|||||||
Reference in New Issue
Block a user