Files
goldenChart/frontend/src/components/strategyEditor/ConditionNodeSettings.tsx
T
2026-06-11 22:53:19 +09:00

182 lines
7.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useEffect, useRef } from 'react';
import type { ConditionDSL } from '../../utils/strategyTypes';
import { getDefaultConditionFields, type DefType } from '../../utils/strategyEditorShared';
import {
getCompositeLeftCandleType,
getCompositeRightCandleType,
getConditionRightPeriod,
getConditionThreshold,
getChartReferenceThreshold,
isThresholdOverridden,
getConditionValuePeriod,
getCompositePeriodPresetOptions,
getPeriodPresetOptions,
getPeriodSettingsLabel,
getThresholdBounds,
getThresholdPresetOptions,
hasEditableThreshold,
setCompositeLeftCandleType,
setCompositeRightCandleType,
setConditionRightPeriod,
setConditionThreshold,
setConditionValuePeriod,
usesValuePeriodField,
} from '../../utils/conditionPeriods';
import { compositeDisplayName, compositeElementLabel, COMPOSITE_INDICATOR_ITEMS, switchCompositeIndicatorType } from '../../utils/compositeIndicators';
import { STRATEGY_CANDLE_TYPE_OPTIONS } from '../../utils/strategyStartNodes';
import ComboNumberInput from './ComboNumberInput';
interface Props {
condition: ConditionDSL;
def: DefType;
signalType?: 'buy' | 'sell';
onChange: (c: ConditionDSL) => void;
onClose: () => void;
popoverClassName?: string;
}
export default function ConditionNodeSettings({
condition,
def,
signalType = 'buy',
onChange,
onClose,
popoverClassName = 'se-flow-settings-pop',
}: Props) {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
const onDoc = (e: MouseEvent) => {
if (ref.current && !ref.current.contains(e.target as Node)) onClose();
};
document.addEventListener('mousedown', onDoc);
return () => document.removeEventListener('mousedown', onDoc);
}, [onClose]);
const inheritedThreshold = getDefaultConditionFields(condition.indicatorType, signalType, def).r;
const chartRef = getChartReferenceThreshold(condition, 'right', inheritedThreshold, def);
const strategyThreshold = getConditionThreshold(condition, 'right', inheritedThreshold, def);
const showThreshold = hasEditableThreshold(condition) && chartRef != null;
const periodPresets = getPeriodPresetOptions(condition.indicatorType, def);
const thresholdPresets = getThresholdPresetOptions(condition.indicatorType);
const thresholdBounds = getThresholdBounds(condition.indicatorType);
return (
<div
ref={ref}
className={popoverClassName}
role="dialog"
aria-label="지표 설정"
onClick={e => e.stopPropagation()}
>
<div className="se-flow-settings-head">
<span>지표 설정</span>
<button
type="button"
className="se-flow-settings-close"
aria-label="닫기"
title="닫기"
onClick={onClose}
>
×
</button>
</div>
{condition.composite ? (
<>
<label className="se-flow-settings-field">
<span>지표</span>
<select
className="se-combo-num-select sp-cond-sel"
value={condition.indicatorType}
onChange={e => onChange(switchCompositeIndicatorType(condition, e.target.value, def))}
>
{COMPOSITE_INDICATOR_ITEMS.map(o => (
<option key={o.value} value={o.value}>{o.label}</option>
))}
</select>
</label>
<label className="se-flow-settings-field">
<span>{compositeElementLabel(condition.indicatorType, 1)} 시간봉</span>
<select
className="se-combo-num-select sp-cond-sel"
value={getCompositeLeftCandleType(condition)}
onChange={e => onChange(setCompositeLeftCandleType(condition, e.target.value))}
>
{STRATEGY_CANDLE_TYPE_OPTIONS.map(o => (
<option key={o.value} value={o.value}>{o.label}</option>
))}
</select>
</label>
<label className="se-flow-settings-field">
<span>{compositeElementLabel(condition.indicatorType, 2)} 시간봉</span>
<select
className="se-combo-num-select sp-cond-sel"
value={getCompositeRightCandleType(condition)}
onChange={e => onChange(setCompositeRightCandleType(condition, e.target.value))}
>
{STRATEGY_CANDLE_TYPE_OPTIONS.map(o => (
<option key={o.value} value={o.value}>{o.label}</option>
))}
</select>
</label>
<label className="se-flow-settings-field">
<span>{compositeElementLabel(condition.indicatorType, 1)} 기준값 (기간, )</span>
<ComboNumberInput
key={`composite-left-${condition.indicatorType}-${getConditionValuePeriod(condition, def)}`}
value={getConditionValuePeriod(condition, def)}
options={getCompositePeriodPresetOptions(condition.indicatorType, def, 'left')}
min={1}
max={500}
onChange={p => onChange(setConditionValuePeriod(condition, p, def))}
/>
</label>
<label className="se-flow-settings-field">
<span>{compositeElementLabel(condition.indicatorType, 2)} 기준값 (기간, )</span>
<ComboNumberInput
key={`composite-right-${condition.indicatorType}-${getConditionRightPeriod(condition, def)}`}
value={getConditionRightPeriod(condition, def)}
options={getCompositePeriodPresetOptions(condition.indicatorType, def, 'right')}
min={1}
max={500}
onChange={p => onChange(setConditionRightPeriod(condition, p))}
/>
</label>
</>
) : usesValuePeriodField(condition) ? (
<label className="se-flow-settings-field">
<span>{getPeriodSettingsLabel(condition) ?? `${condition.indicatorType} 기간 (일)`}</span>
<ComboNumberInput
value={getConditionValuePeriod(condition, def)}
options={periodPresets}
min={1}
max={500}
onChange={p => onChange(setConditionValuePeriod(condition, p, def))}
/>
</label>
) : null}
{showThreshold && chartRef != null && (
<>
<label className="se-flow-settings-field se-flow-settings-field--readonly">
<span>차트 기준값 (보조지표 설정)</span>
<output className="se-flow-settings-readout">{chartRef}</output>
</label>
<label className="se-flow-settings-field">
<span>전략 조건값{isThresholdOverridden(condition) ? ' (전용)' : ''}</span>
<ComboNumberInput
value={strategyThreshold ?? chartRef}
options={thresholdPresets}
min={thresholdBounds.min}
max={thresholdBounds.max}
allowDecimal
onChange={v => onChange(setConditionThreshold(condition, 'right', v, def))}
/>
</label>
</>
)}
{condition.composite && (
<p className="se-flow-settings-hint">{compositeDisplayName(condition.indicatorType)}</p>
)}
</div>
);
}