지표선택 목록 추가
This commit is contained in:
@@ -65,6 +65,21 @@ export default function ConditionPresetGraph({ type, size = 48 }: Props) {
|
||||
<path d="M4 18 L44 16" fill="none" stroke={line} strokeWidth="2" strokeLinecap="round" />
|
||||
</>
|
||||
)}
|
||||
{(type === 'path_first_cross' || type === 'path_pullback_recross' || type === 'path_deep_recovery') && (
|
||||
<>
|
||||
<line x1="4" y1="8" x2="44" y2="8" stroke={thresh} strokeWidth="1.2" strokeDasharray="3 2" />
|
||||
<line x1="4" y1="20" x2="44" y2="20" stroke={thresh} strokeWidth="1.2" strokeDasharray="3 2" />
|
||||
</>
|
||||
)}
|
||||
{type === 'path_first_cross' && (
|
||||
<path d="M4 16 Q14 16 20 12 T34 6" fill="none" stroke={line} strokeWidth="2" strokeLinecap="round" />
|
||||
)}
|
||||
{type === 'path_pullback_recross' && (
|
||||
<path d="M4 10 L14 8 L22 12 L30 10 L38 14 L44 6" fill="none" stroke={line} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
)}
|
||||
{type === 'path_deep_recovery' && (
|
||||
<path d="M4 12 L12 10 L18 22 L26 18 L34 10 L44 6" fill="none" stroke={line} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
)}
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,38 +1,84 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import type { ConditionDSL } from '../../utils/strategyTypes';
|
||||
import type { ConditionDSL, LogicNode } from '../../utils/strategyTypes';
|
||||
import type { DefType } from '../../utils/strategyEditorShared';
|
||||
import {
|
||||
applyConditionPreset,
|
||||
applyCompoundConditionPreset,
|
||||
getConditionPresetsForIndicator,
|
||||
type ConditionPreset,
|
||||
} from '../../utils/conditionPresets';
|
||||
import { isCompoundConditionPreset } from '../../utils/conditionPathPresets';
|
||||
import ConditionPresetGraph from './ConditionPresetGraph';
|
||||
|
||||
interface Props {
|
||||
condition: ConditionDSL;
|
||||
def: DefType;
|
||||
signalType: 'buy' | 'sell';
|
||||
nodeId: string;
|
||||
onChange: (next: ConditionDSL) => void;
|
||||
onReplaceNode: (node: LogicNode) => void;
|
||||
onClose: () => void;
|
||||
popoverClassName?: string;
|
||||
}
|
||||
|
||||
function PresetSection({
|
||||
title,
|
||||
presets,
|
||||
onPick,
|
||||
}: {
|
||||
title: string;
|
||||
presets: ConditionPreset[];
|
||||
onPick: (preset: ConditionPreset) => void;
|
||||
}) {
|
||||
if (presets.length === 0) return null;
|
||||
return (
|
||||
<>
|
||||
<p className="se-cond-preset-section">{title}</p>
|
||||
<ul className="se-cond-preset-list">
|
||||
{presets.map(preset => (
|
||||
<li key={preset.id}>
|
||||
<button
|
||||
type="button"
|
||||
className="se-cond-preset-item"
|
||||
onClick={() => onPick(preset)}
|
||||
>
|
||||
<span className="se-cond-preset-graph-wrap">
|
||||
<ConditionPresetGraph type={preset.graph} size={52} />
|
||||
</span>
|
||||
<span className="se-cond-preset-text">
|
||||
<span className="se-cond-preset-label">{preset.label}</span>
|
||||
<span className="se-cond-preset-desc">{preset.description}</span>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function ConditionPresetPicker({
|
||||
condition,
|
||||
def,
|
||||
signalType,
|
||||
nodeId,
|
||||
onChange,
|
||||
onReplaceNode,
|
||||
onClose,
|
||||
popoverClassName = 'sp-node-preset-pop',
|
||||
}: Props) {
|
||||
const popRef = useRef<HTMLDivElement>(null);
|
||||
const presets = getConditionPresetsForIndicator(condition.indicatorType, def, signalType);
|
||||
|
||||
const sorted = [...presets].sort((a, b) => {
|
||||
const aMatch = a.tags?.includes(signalType) ? 0 : 1;
|
||||
const bMatch = b.tags?.includes(signalType) ? 0 : 1;
|
||||
return aMatch - bMatch;
|
||||
});
|
||||
const sortBySignal = (list: ConditionPreset[]) =>
|
||||
[...list].sort((a, b) => {
|
||||
const aMatch = a.tags?.includes(signalType) ? 0 : 1;
|
||||
const bMatch = b.tags?.includes(signalType) ? 0 : 1;
|
||||
return aMatch - bMatch;
|
||||
});
|
||||
|
||||
const basicPresets = sortBySignal(presets.filter(p => p.category !== 'path'));
|
||||
const pathPresets = sortBySignal(presets.filter(p => p.category === 'path'));
|
||||
|
||||
useEffect(() => {
|
||||
const onDoc = (e: MouseEvent) => {
|
||||
@@ -48,11 +94,15 @@ export default function ConditionPresetPicker({
|
||||
}, [onClose]);
|
||||
|
||||
const handlePick = (preset: ConditionPreset) => {
|
||||
onChange(applyConditionPreset(condition, preset, def));
|
||||
if (isCompoundConditionPreset(preset)) {
|
||||
onReplaceNode(applyCompoundConditionPreset(nodeId, condition, preset, def));
|
||||
} else {
|
||||
onChange(applyConditionPreset(condition, preset, def));
|
||||
}
|
||||
onClose();
|
||||
};
|
||||
|
||||
if (sorted.length === 0) {
|
||||
if (presets.length === 0) {
|
||||
return (
|
||||
<div ref={popRef} className={popoverClassName} role="dialog" aria-label="조건 선택">
|
||||
<div className="se-cond-preset-head">
|
||||
@@ -71,25 +121,8 @@ export default function ConditionPresetPicker({
|
||||
<button type="button" className="se-flow-settings-close" onClick={onClose} aria-label="닫기">×</button>
|
||||
</div>
|
||||
<p className="se-cond-preset-hint">그래프 패턴을 선택하면 캔들 범위·조건대상·조건이 자동 설정됩니다.</p>
|
||||
<ul className="se-cond-preset-list">
|
||||
{sorted.map(preset => (
|
||||
<li key={preset.id}>
|
||||
<button
|
||||
type="button"
|
||||
className="se-cond-preset-item"
|
||||
onClick={() => handlePick(preset)}
|
||||
>
|
||||
<span className="se-cond-preset-graph-wrap">
|
||||
<ConditionPresetGraph type={preset.graph} size={52} />
|
||||
</span>
|
||||
<span className="se-cond-preset-text">
|
||||
<span className="se-cond-preset-label">{preset.label}</span>
|
||||
<span className="se-cond-preset-desc">{preset.description}</span>
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<PresetSection title="기본 조건" presets={basicPresets} onPick={handlePick} />
|
||||
<PresetSection title="상향 돌파 경로 (복합 · AND)" presets={pathPresets} onPick={handlePick} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -218,7 +218,9 @@ const TreeNodeComp: React.FC<TreeNodeProps> = ({
|
||||
condition={node.condition}
|
||||
def={def}
|
||||
signalType={signalType}
|
||||
nodeId={node.id}
|
||||
onChange={handleCondChange}
|
||||
onReplaceNode={onUpdate}
|
||||
onClose={() => setPresetOpen(false)}
|
||||
popoverClassName="sp-node-preset-pop"
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user