182 lines
7.0 KiB
TypeScript
182 lines
7.0 KiB
TypeScript
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>
|
||
);
|
||
}
|