n봉 침체 터치 조건 추가
This commit is contained in:
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user