전략편집기 조건 목록 수정
This commit is contained in:
@@ -74,7 +74,10 @@ import {
|
||||
import ComboFieldSelect from '../components/strategyEditor/ComboFieldSelect';
|
||||
import {
|
||||
activateDirectThresholdInput,
|
||||
buildHwpValueFieldOpt,
|
||||
buildSingleIndicatorPeriodOpts,
|
||||
ensureDirectThresholdSnapshot,
|
||||
VALUE_FIELD_PREFIX,
|
||||
getCompositeFieldOpts,
|
||||
getCompositePeriodPresetOptions,
|
||||
getConditionRightPeriod,
|
||||
@@ -91,6 +94,9 @@ import {
|
||||
parseThresholdField,
|
||||
resolveFieldCustomKind,
|
||||
resolveRightFieldCustomKind,
|
||||
resolveStoredValueField,
|
||||
singleIndicatorLineLabel,
|
||||
singleIndicatorValuePeriodLabel,
|
||||
getCompositeLeftCandleType,
|
||||
getCompositeRightCandleType,
|
||||
setCompositeLeftCandleType,
|
||||
@@ -491,37 +497,35 @@ function buildFieldOptsForSlot(
|
||||
switch (ind) {
|
||||
case 'MACD': {
|
||||
const { mid } = th({ mid: 0 });
|
||||
const signalLabel = `신호선 ${DEF.macdSignal}`;
|
||||
if (slot === 'left') {
|
||||
return [
|
||||
{ value: 'MACD_LINE', label: 'MACD 선' },
|
||||
{ value: 'SIGNAL_LINE', label: `신호선 ${DEF.macdSignal}` },
|
||||
{ value: THRESHOLD_HL_MID, label: `중앙선(${mid})` },
|
||||
{ value: 'MACD_LINE', label: 'MACD선' },
|
||||
{ value: 'SIGNAL_LINE', label: signalLabel },
|
||||
];
|
||||
}
|
||||
return [
|
||||
{ value: 'SIGNAL_LINE', label: `신호선 ${DEF.macdSignal}` },
|
||||
{ value: 'SIGNAL_LINE', label: signalLabel },
|
||||
NONE,
|
||||
{ value: THRESHOLD_HL_MID, label: `중앙선(${mid})` },
|
||||
];
|
||||
}
|
||||
case 'RSI': {
|
||||
const rsiP = cond ? getConditionValuePeriod({ ...cond, indicatorType: 'RSI' }, DEF) : DEF.rsiPeriod;
|
||||
const valueOpt = buildHwpValueFieldOpt('RSI', DEF, cond)!;
|
||||
const signalLabel = `신호선 ${DEF.rsiSignal}`;
|
||||
const thresholds = buildThresholdOpts(th({ over: 70, mid: 50, under: 30 }), overTerm, underTerm);
|
||||
if (slot === 'left') {
|
||||
return [
|
||||
{ value: 'RSI_VALUE', label: `RSI 기간 ${rsiP}` },
|
||||
{ value: 'RSI_SIGNAL', label: `신호선 ${DEF.rsiSignal}` },
|
||||
];
|
||||
return [valueOpt, { value: 'RSI_SIGNAL', label: signalLabel }];
|
||||
}
|
||||
return [
|
||||
{ value: 'RSI_SIGNAL', label: `신호선 ${DEF.rsiSignal}` },
|
||||
{ value: 'RSI_SIGNAL', label: signalLabel },
|
||||
NONE,
|
||||
...thresholds,
|
||||
];
|
||||
}
|
||||
case 'STOCHASTIC': {
|
||||
const thresholds = buildThresholdOpts(th({ over: 80, mid: 50, under: 20 }), overTerm, underTerm);
|
||||
const kLabel = `Stochastic 기간${DEF.stochK}일 %K${DEF.stochSmooth}일`;
|
||||
const kLabel = `Stochastic 기간${DEF.stochK}일 %K${DEF.stochSmooth}`;
|
||||
const dLabel = `Stochastic %D${DEF.stochD}`;
|
||||
if (slot === 'left') {
|
||||
return [
|
||||
@@ -537,38 +541,40 @@ function buildFieldOptsForSlot(
|
||||
}
|
||||
case 'CCI': {
|
||||
const cciP = cond ? getConditionValuePeriod({ ...cond, indicatorType: 'CCI' }, DEF) : DEF.cciPeriod;
|
||||
const signalLabel = `신호선 ${DEF.cciSignal}`;
|
||||
const thresholds = buildThresholdOpts(th({ over: 100, mid: 0, under: -100 }), overTerm, underTerm);
|
||||
if (slot === 'left') {
|
||||
return [
|
||||
{ value: 'CCI_VALUE', label: `CCI 기간 ${cciP}` },
|
||||
{ value: 'CCI_SIGNAL', label: `신호선 ${DEF.cciSignal}` },
|
||||
{ value: 'CCI_SIGNAL', label: signalLabel },
|
||||
];
|
||||
}
|
||||
return [
|
||||
{ value: 'CCI_SIGNAL', label: `신호선 ${DEF.cciSignal}` },
|
||||
{ value: 'CCI_SIGNAL', label: signalLabel },
|
||||
NONE,
|
||||
...thresholds,
|
||||
];
|
||||
}
|
||||
case 'ADX': {
|
||||
const adxP = cond ? getConditionValuePeriod({ ...cond, indicatorType: 'ADX' }, DEF) : DEF.adxPeriod;
|
||||
const valueOpt = buildHwpValueFieldOpt('ADX', DEF, cond)!;
|
||||
const thresholds = buildThresholdOpts(th({ over: 40, mid: 25, under: 20 }), overTerm, underTerm);
|
||||
if (slot === 'left') {
|
||||
return [{ value: 'ADX_VALUE', label: `ADX 기간 ${adxP}` }];
|
||||
return [valueOpt];
|
||||
}
|
||||
return [NONE, ...thresholds];
|
||||
}
|
||||
case 'TRIX': {
|
||||
const trixP = cond ? getConditionValuePeriod({ ...cond, indicatorType: 'TRIX' }, DEF) : DEF.trixPeriod;
|
||||
const valueOpt = buildHwpValueFieldOpt('TRIX', DEF, cond)!;
|
||||
const trmaLabel = `TRMA ${DEF.trixSignal}`;
|
||||
const { mid } = th({ mid: 0 });
|
||||
if (slot === 'left') {
|
||||
return [
|
||||
{ value: 'TRIX_VALUE', label: `TRIX ${trixP}` },
|
||||
{ value: 'TRIX_SIGNAL', label: `TRMA ${DEF.trixSignal}` },
|
||||
valueOpt,
|
||||
{ value: 'TRIX_SIGNAL', label: trmaLabel },
|
||||
];
|
||||
}
|
||||
return [
|
||||
{ value: 'TRIX_SIGNAL', label: `TRMA ${DEF.trixSignal}` },
|
||||
{ value: 'TRIX_SIGNAL', label: trmaLabel },
|
||||
NONE,
|
||||
{ value: THRESHOLD_HL_MID, label: `중앙선(${mid})` },
|
||||
];
|
||||
@@ -579,14 +585,14 @@ function buildFieldOptsForSlot(
|
||||
const minusLabel = `-DI 기간${DEF.dmiPeriod}일`;
|
||||
if (slot === 'left') {
|
||||
return [
|
||||
{ value: 'PDI', label: plusLabel },
|
||||
{ value: 'MDI', label: minusLabel },
|
||||
{ value: 'PDI', label: plusLabel },
|
||||
];
|
||||
}
|
||||
return [
|
||||
NONE,
|
||||
{ value: 'MDI', label: minusLabel },
|
||||
{ value: 'PDI', label: plusLabel },
|
||||
NONE,
|
||||
...thresholds,
|
||||
];
|
||||
}
|
||||
@@ -602,10 +608,10 @@ function buildFieldOptsForSlot(
|
||||
NONE,
|
||||
];
|
||||
case 'WILLIAMS_R': {
|
||||
const wrP = cond ? getConditionValuePeriod({ ...cond, indicatorType: 'WILLIAMS_R' }, DEF) : DEF.williamsR;
|
||||
const valueOpt = buildHwpValueFieldOpt('WILLIAMS_R', DEF, cond)!;
|
||||
const thresholds = buildThresholdOpts(th({ over: -20, mid: -50, under: -80 }), overTerm, underTerm);
|
||||
if (slot === 'left') {
|
||||
return [{ value: 'WILLIAMS_R_VALUE', label: `williams%R 기간${wrP}` }];
|
||||
return [valueOpt];
|
||||
}
|
||||
return [NONE, ...thresholds];
|
||||
}
|
||||
@@ -648,47 +654,42 @@ function buildFieldOptsForSlot(
|
||||
return slot === 'right' ? [NONE, ...lines] : lines;
|
||||
}
|
||||
case 'PSYCHOLOGICAL': {
|
||||
const psyP = cond
|
||||
? getConditionValuePeriod({ ...cond, indicatorType: ind }, DEF)
|
||||
: DEF.psyPeriod;
|
||||
const periodOpts = buildSingleIndicatorPeriodOpts('PSYCHOLOGICAL', DEF, cond);
|
||||
const thresholds = buildThresholdOpts(th({ over: 75, mid: 50, under: 25 }), overTerm, underTerm);
|
||||
if (slot === 'left') {
|
||||
return [{ value: 'PSY_VALUE', label: `심리도 기간 ${psyP}` }];
|
||||
return periodOpts;
|
||||
}
|
||||
return [NONE, ...thresholds];
|
||||
}
|
||||
case 'NEW_PSYCHOLOGICAL': {
|
||||
const nPsyP = cond
|
||||
? getConditionValuePeriod({ ...cond, indicatorType: ind }, DEF)
|
||||
: DEF.newPsy;
|
||||
const periodOpts = buildSingleIndicatorPeriodOpts('NEW_PSYCHOLOGICAL', DEF, cond);
|
||||
const thresholds = buildThresholdOpts(th({ over: 50, mid: 0, under: -50 }), overTerm, underTerm);
|
||||
if (slot === 'left') {
|
||||
return [{ value: 'NEW_PSY_VALUE', label: `신심리도 기간 ${nPsyP}` }];
|
||||
return periodOpts;
|
||||
}
|
||||
return [NONE, ...thresholds];
|
||||
}
|
||||
case 'INVEST_PSYCHOLOGICAL': {
|
||||
const ipsyP = cond
|
||||
? getConditionValuePeriod({ ...cond, indicatorType: 'INVEST_PSYCHOLOGICAL' }, DEF)
|
||||
: DEF.investPsy;
|
||||
const periodOpts = buildSingleIndicatorPeriodOpts('INVEST_PSYCHOLOGICAL', DEF, cond);
|
||||
const thresholds = buildThresholdOpts(th({ over: 75, mid: 50, under: 25 }), overTerm, underTerm);
|
||||
if (slot === 'left') {
|
||||
return [{ value: 'INVEST_PSY_VALUE', label: `투자심리도 기간 ${ipsyP}` }];
|
||||
return periodOpts;
|
||||
}
|
||||
return [NONE, ...thresholds];
|
||||
}
|
||||
case 'BWI': {
|
||||
const periodOpts = buildSingleIndicatorPeriodOpts('BWI', DEF, cond);
|
||||
const thresholds = buildThresholdOpts(th({ over: 80, mid: 50, under: 20 }), overTerm, underTerm);
|
||||
if (slot === 'left') {
|
||||
return [{ value: 'BWI_VALUE', label: `BWI 기간 ${DEF.bwiPeriod}` }];
|
||||
return periodOpts;
|
||||
}
|
||||
return [NONE, ...thresholds];
|
||||
}
|
||||
case 'VR': {
|
||||
const vrP = cond ? getConditionValuePeriod({ ...cond, indicatorType: 'VR' }, DEF) : DEF.vrPeriod;
|
||||
const periodOpts = buildSingleIndicatorPeriodOpts('VR', DEF, cond);
|
||||
const thresholds = buildThresholdOpts(th({ over: 200, mid: 100, under: 50 }), overTerm, underTerm);
|
||||
if (slot === 'left') {
|
||||
return [{ value: 'VR_VALUE', label: `VR 기간 ${vrP}` }];
|
||||
return periodOpts;
|
||||
}
|
||||
return [NONE, ...thresholds];
|
||||
}
|
||||
@@ -941,13 +942,25 @@ export const nodeToText = (node: LogicNode, DEF: DefType = DEF_DEFAULTS): string
|
||||
return `${indName} - ${L} ${C} ${R}${tf}`;
|
||||
}
|
||||
const defaults = getDefaultConditionFields(c.indicatorType, 'buy', DEF);
|
||||
const lv = resolveFieldOptionValue(c.indicatorType, c.leftField);
|
||||
const leftStored = resolveStoredValueField(c.indicatorType, c.leftField, DEF, c);
|
||||
const lv = resolveFieldOptionValue(c.indicatorType, leftStored);
|
||||
const rvRaw = c.rightField;
|
||||
const rv = isThresholdOverridden(c)
|
||||
? resolveFieldOptionValue(c.indicatorType, rvRaw)
|
||||
: (resolveInheritedThresholdField(rvRaw, c.indicatorType, DEF.hlThresh)
|
||||
?? resolveFieldOptionValue(c.indicatorType, rvRaw));
|
||||
const L = opts.find(o => o.value === lv)?.label ?? c.leftField ?? indName;
|
||||
const L = opts.find(o => o.value === leftStored)?.label
|
||||
?? opts.find(o => o.value === lv)?.label
|
||||
?? (() => {
|
||||
const m = leftStored?.match(/_(\d+)$/);
|
||||
if (m) return singleIndicatorValuePeriodLabel(c.indicatorType, parseInt(m[1], 10));
|
||||
const base = VALUE_FIELD_PREFIX[c.indicatorType];
|
||||
if (base && (leftStored === base || lv === base)) {
|
||||
return singleIndicatorValuePeriodLabel(c.indicatorType, getConditionValuePeriod(c, DEF));
|
||||
}
|
||||
return null;
|
||||
})()
|
||||
?? c.leftField ?? indName;
|
||||
const chartRef = getChartReferenceThreshold(c, 'right', defaults.r, DEF);
|
||||
const R = opts.find(o => o.value === rv)?.label
|
||||
?? (chartRef != null && !isThresholdOverridden(c) ? `기준(${chartRef})` : null)
|
||||
@@ -1130,7 +1143,11 @@ export const CondEditor: React.FC<CondEditorProps> = ({
|
||||
const inheritedThresholdField = defaults.r;
|
||||
|
||||
const getLeftValue = () => {
|
||||
const v = resolveFieldOptionValue(normalized.indicatorType, normalized.leftField);
|
||||
const raw = normalized.leftField;
|
||||
const resolved = resolveStoredValueField(normalized.indicatorType, raw, def, normalized);
|
||||
if (resolved && leftFieldOpts.some(o => o.value === resolved)) return resolved;
|
||||
if (raw && leftFieldOpts.some(o => o.value === raw)) return raw;
|
||||
const v = resolveFieldOptionValue(normalized.indicatorType, raw);
|
||||
return isValid(v) ? v! : defaults.l;
|
||||
};
|
||||
const getRightValue = () => {
|
||||
@@ -1378,11 +1395,12 @@ export const CondEditor: React.FC<CondEditorProps> = ({
|
||||
<label className="sp-cond-lbl">조건대상1</label>
|
||||
<ComboFieldSelect
|
||||
options={leftFieldOpts}
|
||||
fieldValue={isHoldType ? 'NONE' : (normalized.leftField ?? getLeftValue())}
|
||||
fieldValue={isHoldType ? 'NONE' : (resolveStoredValueField(normalized.indicatorType, normalized.leftField, def, normalized) ?? getLeftValue())}
|
||||
isPresetField={!isHoldType && (
|
||||
leftFieldOpts.some(o => o.value === normalized.leftField)
|
||||
|| (leftFieldOpts.some(o => o.value === getLeftValue())
|
||||
|| (leftFieldOpts.some(o => o.value === resolveFieldOptionValue(normalized.indicatorType, normalized.leftField))
|
||||
&& !isValuePeriodFieldStored(normalized.indicatorType, normalized.leftField))
|
||||
|| leftFieldOpts.some(o => o.value === resolveStoredValueField(normalized.indicatorType, normalized.leftField, def, normalized))
|
||||
)}
|
||||
customKind={resolveFieldCustomKind(normalized.indicatorType, normalized.leftField)}
|
||||
customNumber={getConditionValuePeriod(normalized, def)}
|
||||
@@ -1390,22 +1408,23 @@ export const CondEditor: React.FC<CondEditorProps> = ({
|
||||
min={1}
|
||||
max={500}
|
||||
disabled={isHoldType}
|
||||
customOptionLabel={singleIndicatorValuePeriodLabel(
|
||||
normalized.indicatorType,
|
||||
getConditionValuePeriod(normalized, def),
|
||||
)}
|
||||
onFieldChange={v => {
|
||||
const bases: Record<string, string> = {
|
||||
RSI: 'RSI_VALUE',
|
||||
CCI: 'CCI_VALUE',
|
||||
ADX: 'ADX_VALUE',
|
||||
WILLIAMS_R: 'WILLIAMS_R_VALUE',
|
||||
TRIX: 'TRIX_VALUE',
|
||||
VR: 'VR_VALUE',
|
||||
PSYCHOLOGICAL: 'PSY_VALUE',
|
||||
NEW_PSYCHOLOGICAL: 'NEW_PSY_VALUE',
|
||||
INVEST_PSYCHOLOGICAL: 'INVEST_PSY_VALUE',
|
||||
};
|
||||
const base = bases[normalized.indicatorType];
|
||||
if (base && v === base) {
|
||||
const p = getConditionValuePeriod(normalized, def);
|
||||
onChange({ ...normalized, leftField: `${base}_${p}`, period: p, valuePeriodOverride: true });
|
||||
const valuePrefix = VALUE_FIELD_PREFIX[normalized.indicatorType];
|
||||
if (valuePrefix && v === valuePrefix) {
|
||||
onChange(initConditionPeriodsInherit(
|
||||
normalized.indicatorType,
|
||||
{ ...normalized, leftField: v },
|
||||
def,
|
||||
));
|
||||
return;
|
||||
}
|
||||
const pMatch = v.match(/_(\d+)$/);
|
||||
if (valuePrefix && v.startsWith(`${valuePrefix}_`) && pMatch) {
|
||||
onChange(setConditionValuePeriod(normalized, parseInt(pMatch[1], 10), def));
|
||||
} else {
|
||||
onChange({ ...normalized, leftField: v });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user