복합지표 전략 추가

This commit is contained in:
Macbook
2026-06-12 13:26:53 +09:00
parent 9208418c33
commit 52137cf1db
27 changed files with 2712 additions and 55 deletions
@@ -10,12 +10,31 @@ import StartTimeframeCheckboxes from './StartTimeframeCheckboxes';
import LogicGateOpToggle from './LogicGateOpToggle';
import { hasNodeSettings } from '../../utils/conditionPeriods';
import { getStrategyIndicatorDisplayName } from '../../utils/strategyPaletteStorage';
import {
isPriceExtremeBreakoutPairRoot,
priceExtremePairDisplayName,
inferPriceExtremeFilterLevel,
} from '../../utils/priceExtremeBreakoutPair';
import {
isInflection33PairRoot,
inflection33DisplayName,
inferInflection33FilterLevel,
} from '../../utils/inflection33Strategy';
import Inflection33PairNodeSettings from './Inflection33PairNodeSettings';
import {
isStableStrategyPairRoot,
stableStrategyDisplayName,
inferStableStrategyFilterLevel,
type StableStrategyId,
} from '../../utils/stableStrategyPairs';
import StableStrategyPairNodeSettings from './StableStrategyPairNodeSettings';
import {
isStochOverboughtPairRoot,
stochPairDisplayName,
} from '../../utils/stochOverboughtPair';
import ConditionNodeSettings from './ConditionNodeSettings';
import StochPairNodeSettings from './StochPairNodeSettings';
import PriceExtremePairNodeSettings from './PriceExtremePairNodeSettings';
const LOGIC_COLORS: Record<string, string> = {
AND: '#00aaff',
@@ -196,12 +215,15 @@ export const LogicGateNode = memo(function LogicGateNode({ id, data, selected }:
const color = LOGIC_COLORS[node.type] ?? '#00aaff';
const isSell = d.signalTab === 'sell';
const isStochPair = isStochOverboughtPairRoot(node);
const isPriceExtremePair = isPriceExtremeBreakoutPairRoot(node);
const isInflection33Pair = isInflection33PairRoot(node);
const isStableStrategyPair = isStableStrategyPairRoot(node);
const { onDragOver, onDragLeave, onDrop } = usePaletteDropHandlers(id, d);
const [settingsOpen, setSettingsOpen] = useState(false);
return (
<div
className={`se-flow-node se-flow-node--logic${isStochPair ? ' se-flow-node--stoch-pair' : ''}${isSell ? ' se-flow-node--sell-mode' : ''}${d.isOrphan ? ' se-flow-node--orphan' : ''} ${selected ? 'se-flow-node--selected' : ''} ${d.dragOver ? 'se-flow-node--drop' : ''}${settingsOpen ? ' se-flow-node--settings-open' : ''}`}
className={`se-flow-node se-flow-node--logic${isStochPair ? ' se-flow-node--stoch-pair' : ''}${isPriceExtremePair ? ' se-flow-node--price-extreme-pair' : ''}${isInflection33Pair ? ' se-flow-node--inflection33-pair' : ''}${isStableStrategyPair ? ' se-flow-node--stable-strategy-pair' : ''}${isSell ? ' se-flow-node--sell-mode' : ''}${d.isOrphan ? ' se-flow-node--orphan' : ''} ${selected ? 'se-flow-node--selected' : ''} ${d.dragOver ? ' se-flow-node--drop' : ''}${settingsOpen ? ' se-flow-node--settings-open' : ''}`}
style={{ '--se-gate-color': color } as React.CSSProperties}
onDragOver={onDragOver}
onDragLeave={onDragLeave}
@@ -224,6 +246,79 @@ export const LogicGateNode = memo(function LogicGateNode({ id, data, selected }:
<span className="se-flow-gate-badge">[{node.type}]</span>
)}
</div>
{isStableStrategyPair && node.stableStrategyPair && (
<div className="se-flow-gate-stoch-pair">
<span className="se-flow-gate-stoch-pair-title">
{stableStrategyDisplayName(
node.stableStrategyPair.strategyId as StableStrategyId,
node.stableStrategyPair.mode,
)}
</span>
<button
type="button"
className="se-flow-settings"
title="필터 강도 설정"
aria-label="필터 강도 설정"
onClick={e => {
e.stopPropagation();
setSettingsOpen(v => !v);
}}
>
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<circle cx="12" cy="12" r="3"/>
<path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/>
</svg>
</button>
</div>
)}
{isInflection33Pair && node.inflection33Pair && (
<div className="se-flow-gate-stoch-pair">
<span className="se-flow-gate-stoch-pair-title">
{inflection33DisplayName(node.inflection33Pair.mode, node.inflection33Pair.period)}
</span>
<button
type="button"
className="se-flow-settings"
title="필터 강도 설정"
aria-label="필터 강도 설정"
onClick={e => {
e.stopPropagation();
setSettingsOpen(v => !v);
}}
>
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<circle cx="12" cy="12" r="3"/>
<path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/>
</svg>
</button>
</div>
)}
{isPriceExtremePair && node.priceExtremePair && (
<div className="se-flow-gate-stoch-pair">
<span className="se-flow-gate-stoch-pair-title">
{priceExtremePairDisplayName(
node.priceExtremePair.mode,
node.priceExtremePair.shortPeriod,
node.priceExtremePair.longPeriod,
)}
</span>
<button
type="button"
className="se-flow-settings"
title="필터 강도 설정"
aria-label="필터 강도 설정"
onClick={e => {
e.stopPropagation();
setSettingsOpen(v => !v);
}}
>
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<circle cx="12" cy="12" r="3"/>
<path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/>
</svg>
</button>
</div>
)}
{isStochPair && node.stochPair && (
<div className="se-flow-gate-stoch-pair">
<span className="se-flow-gate-stoch-pair-title">
@@ -257,6 +352,35 @@ export const LogicGateNode = memo(function LogicGateNode({ id, data, selected }:
onClose={() => setSettingsOpen(false)}
/>
)}
{settingsOpen && isStableStrategyPair && node.stableStrategyPair && (
<StableStrategyPairNodeSettings
strategyId={node.stableStrategyPair.strategyId as StableStrategyId}
mode={node.stableStrategyPair.mode}
filterLevel={inferStableStrategyFilterLevel(node)}
def={d.def!}
onChange={level => d.onUpdateStableStrategyFilterLevel?.(id, level)}
onClose={() => setSettingsOpen(false)}
/>
)}
{settingsOpen && isInflection33Pair && node.inflection33Pair && (
<Inflection33PairNodeSettings
mode={node.inflection33Pair.mode}
period={node.inflection33Pair.period}
filterLevel={inferInflection33FilterLevel(node)}
onChange={level => d.onUpdateInflection33FilterLevel?.(id, level)}
onClose={() => setSettingsOpen(false)}
/>
)}
{settingsOpen && isPriceExtremePair && node.priceExtremePair && (
<PriceExtremePairNodeSettings
mode={node.priceExtremePair.mode}
shortPeriod={node.priceExtremePair.shortPeriod}
longPeriod={node.priceExtremePair.longPeriod}
filterLevel={inferPriceExtremeFilterLevel(node)}
onChange={level => d.onUpdatePriceExtremeFilterLevel?.(id, level)}
onClose={() => setSettingsOpen(false)}
/>
)}
</div>
);
});