n봉 침체 터치 조건 추가

This commit is contained in:
Macbook
2026-06-12 09:29:59 +09:00
parent d741d3fec6
commit 94934d8ce5
7 changed files with 109 additions and 6 deletions
+42 -1
View File
@@ -3,6 +3,7 @@ import React from 'react';
import type { IndicatorConfig } from '../types/index';
import { getIndicatorDef, getHLineLabel, INDICATOR_REGISTRY, type PlotDef, type HLineDef } from '../utils/indicatorRegistry';
import type { LogicNode, LogicNodeType, ConditionDSL } from '../utils/strategyTypes';
import { CONDITION_LABEL, INDICATOR_CONDITIONS } from '../utils/strategyTypes';
import {
COMPOSITE_INDICATOR_ITEMS,
compositeDisplayName,
@@ -331,6 +332,28 @@ const COND_OPTIONS = [
{ v: 'DIFF_GT', l: '차이 > 값' },
{ v: 'DIFF_LT', l: '차이 < 값' },
{ v: 'HOLD_N_DAYS', l: 'N일 연속 유지' },
{ v: 'LOWEST_LTE', l: 'N봉 최저 ≤ 값' },
{ v: 'LOWEST_GTE', l: 'N봉 최저 ≥ 값' },
];
const condLabelFromRegistry = (ind: string, v: string): string => {
const fromCond = COND_OPTIONS.concat(ICHIMOKU_CONDS).find(o => o.v === v)?.l;
if (fromCond) return fromCond;
return CONDITION_LABEL[v] ?? v;
};
type CondTypeOpt = { v: string; l: string };
/** 지표별 조건 타입 드롭다운 — 오실레이터는 룩백 조건 포함 */
export const getCondOptionsForIndicator = (ind: string): CondTypeOpt[] => {
if (ind === 'ICHIMOKU') return ICHIMOKU_CONDS;
const keys = INDICATOR_CONDITIONS[ind] ?? COMMON_COND_KEYS;
return keys.map(v => ({ v, l: condLabelFromRegistry(ind, v) }));
};
const COMMON_COND_KEYS = [
'GT', 'LT', 'GTE', 'LTE', 'EQ', 'NEQ', 'CROSS_UP', 'CROSS_DOWN',
'SLOPE_UP', 'SLOPE_DOWN', 'DIFF_GT', 'DIFF_LT', 'HOLD_N_DAYS',
];
const ICHIMOKU_CONDS = [
@@ -660,6 +683,10 @@ const applyCondTypeDefaults = (cond: ConditionDSL, newCondType: string, DEF: Def
} else if (newCondType === 'HOLD_N_DAYS') {
updated.leftField = 'NONE'; updated.rightField = 'NONE';
updated.holdDays = updated.holdDays ?? 3;
} else if (['LOWEST_LTE', 'LOWEST_GTE'].includes(newCondType)) {
if (!isValid(updated.leftField)) updated.leftField = def.l;
if (!isValid(updated.rightField)) updated.rightField = THRESHOLD_HL_UNDER;
updated.lookbackPeriod = updated.lookbackPeriod ?? 20;
}
// Ichimoku 특수처리
@@ -731,6 +758,11 @@ export const nodeToText = (node: LogicNode, DEF: DefType = DEF_DEFAULTS): string
const R = opts.find(o => o.value === rv)?.label
?? (chartRef != null && !isThresholdOverridden(c) ? `기준(${chartRef})` : null)
?? (rv && parseThresholdField(rv) != null ? `임계(${parseThresholdField(rv)})` : rv ?? '');
if (['LOWEST_LTE', 'LOWEST_GTE'].includes(c.conditionType)) {
const n = c.lookbackPeriod ?? 20;
const op = c.conditionType === 'LOWEST_LTE' ? '≤' : '≥';
return `${indName} - 최근 ${n}${L} 최저 ${op} ${R || '침체선'}`;
}
if (R && R !== '선택안함') return `${indName} - ${L} ${C} ${R}`;
return `${indName} - ${L} ${C}`;
}
@@ -845,7 +877,7 @@ export const CondEditor: React.FC<CondEditorProps> = ({
}) => {
const normalized = normalizeCompositeCondition(cond);
const fieldOpts = getFieldOpts(normalized.indicatorType, signalType, def, normalized);
const condOpts = normalized.indicatorType === 'ICHIMOKU' ? ICHIMOKU_CONDS : COND_OPTIONS;
const condOpts = getCondOptionsForIndicator(normalized.indicatorType);
const isValid = (v: string|undefined) => !!v && fieldOpts.some(o => o.value === v);
const defaults = getDefaultConditionFields(normalized.indicatorType, signalType, def);
@@ -868,6 +900,7 @@ export const CondEditor: React.FC<CondEditorProps> = ({
const isSlopeType = ['SLOPE_UP','SLOPE_DOWN'].includes(normalized.conditionType);
const isHoldType = normalized.conditionType === 'HOLD_N_DAYS';
const isLookbackType = ['LOWEST_LTE', 'LOWEST_GTE'].includes(normalized.conditionType);
const isDiffType = ['DIFF_GT','DIFF_LT'].includes(normalized.conditionType);
const rightValue = isSlopeType ? 'NONE' : isHoldType ? 'NONE' : getRightValue();
@@ -1150,6 +1183,14 @@ export const CondEditor: React.FC<CondEditorProps> = ({
onChange={e => onChange({ ...cond, holdDays: parseInt(e.target.value) || 3 })} />
</div>
)}
{isLookbackType && (
<div className="sp-cond-extra">
<label className="sp-cond-lbl"> ()</label>
<input type="number" className="sp-cond-num" min={2} max={500}
value={cond.lookbackPeriod ?? ''} placeholder="예: 20"
onChange={e => onChange({ ...cond, lookbackPeriod: parseInt(e.target.value) || 20 })} />
</div>
)}
</div>
);
};