전략편집기 복합지표 기능 반영

This commit is contained in:
Macbook
2026-05-25 01:34:52 +09:00
parent ca249ad318
commit 20dd257c29
21 changed files with 1781 additions and 183 deletions
+37 -14
View File
@@ -4,11 +4,11 @@
import React, { useState, useCallback, useEffect, useMemo, useRef } from 'react';
import { ReactFlowProvider } from '@xyflow/react';
import DraggableModalFrame from './DraggableModalFrame';
import type { Theme, IndicatorConfig } from '../types/index';
import type { Theme } from '../types/index';
import { loadStrategies, saveStrategy, deleteStrategy, type StrategyDto as ApiStrategyDto } from '../utils/backendApi';
import type { LogicNode } from '../utils/strategyTypes';
import {
buildDef,
buildStrategyEditorDef,
loadStratsLocal,
saveStratsLocal,
mergeAtRoot,
@@ -19,6 +19,10 @@ import {
type StrategyDto,
} from '../utils/strategyEditorShared';
import { findLogicNode, getIndicatorPeriodLabel } from '../utils/strategyFlowLayout';
import {
COMPOSITE_INDICATOR_ITEMS,
compositePeriodLabel,
} from '../utils/compositeIndicators';
import {
emptySignalFlowLayout,
loadStrategyFlowLayout,
@@ -66,7 +70,6 @@ const TERMINAL_DEFAULT = 140;
interface Props {
theme: Theme;
activeIndicators?: IndicatorConfig[];
}
function readTabLayout(strategyKey: string, tab: 'buy' | 'sell'): SignalFlowLayoutSnapshot {
@@ -80,8 +83,8 @@ function readTabLayout(strategyKey: string, tab: 'buy' | 'sell'): SignalFlowLayo
};
}
export default function StrategyEditorPage({ theme, activeIndicators = [] }: Props) {
const DEF = useMemo(() => buildDef(activeIndicators), [activeIndicators]);
export default function StrategyEditorPage({ theme }: Props) {
const DEF = useMemo(() => buildStrategyEditorDef(), []);
const [strategies, setStrategies] = useState<StrategyDto[]>(() => loadStratsLocal());
const [selectedId, setSelectedId] = useState<number | null>(null);
@@ -511,8 +514,8 @@ export default function StrategyEditorPage({ theme, activeIndicators = [] }: Pro
}
}, []);
const applyPalette = useCallback((type: string, value: string, label: string) => {
const newNode = makeNode(type, value, signalTab, DEF);
const applyPalette = useCallback((type: string, value: string, _label: string, composite = false) => {
const newNode = makeNode(type, value, signalTab, DEF, composite ? { composite: true } : undefined);
const root = currentRoot;
if (!root) setCurrentRoot(newNode);
else if (type === 'operator') setCurrentRoot(mergeAtRoot(root, newNode, true));
@@ -576,11 +579,11 @@ export default function StrategyEditorPage({ theme, activeIndicators = [] }: Pro
const match = (label: string, desc?: string) =>
!q || label.toLowerCase().includes(q) || (desc?.toLowerCase().includes(q) ?? false);
const paletteKey = (type: 'operator' | 'indicator', value: string) => `${type}:${value}`;
const selectPalette = (type: 'operator' | 'indicator', value: string) => {
const paletteKey = (type: 'operator' | 'indicator' | 'composite', value: string) => `${type}:${value}`;
const selectPalette = (type: 'operator' | 'indicator' | 'composite', value: string) => {
setSelectedPaletteKey(paletteKey(type, value));
};
const isPaletteSelected = (type: 'operator' | 'indicator', value: string) =>
const isPaletteSelected = (type: 'operator' | 'indicator' | 'composite', value: string) =>
selectedPaletteKey === paletteKey(type, value);
return (
@@ -780,7 +783,7 @@ export default function StrategyEditorPage({ theme, activeIndicators = [] }: Pro
}
}}
/>
<span className="se-sync-tip"> · {getIndicatorPeriodLabel(selectedLogicNode.condition.indicatorType, DEF) || '—'}</span>
<span className="se-sync-tip"> · </span>
</div>
)}
</>
@@ -846,7 +849,7 @@ export default function StrategyEditorPage({ theme, activeIndicators = [] }: Pro
</div>
<div className="se-palette-section se-palette-section--band">
<h3> · </h3>
<div className="se-palette-grid">
<div className="se-palette-grid se-palette-grid--3">
{maBandItems.filter(i => match(i.label, i.desc)).map(item => (
<PaletteChip
key={item.value}
@@ -859,9 +862,9 @@ export default function StrategyEditorPage({ theme, activeIndicators = [] }: Pro
))}
</div>
</div>
<div className="se-palette-section se-palette-section--aux se-palette-section--scroll">
<div className="se-palette-section se-palette-section--aux">
<h3></h3>
<div className="se-palette-grid">
<div className="se-palette-grid se-palette-grid--3">
{indicatorItems.filter(i => match(i.label, i.desc)).map(item => (
<PaletteChip
key={item.value}
@@ -878,6 +881,26 @@ export default function StrategyEditorPage({ theme, activeIndicators = [] }: Pro
))}
</div>
</div>
<div className="se-palette-section se-palette-section--composite se-palette-section--scroll">
<h3></h3>
<div className="se-palette-grid se-palette-grid--3">
{COMPOSITE_INDICATOR_ITEMS.filter(i => match(i.label, i.desc)).map(item => (
<PaletteChip
key={`composite-${item.value}`}
type="indicator"
value={item.value}
label={item.label}
desc={item.desc}
color="composite"
composite
period={compositePeriodLabel(item.value, DEF)}
selected={isPaletteSelected('composite', item.value)}
onSelect={() => selectPalette('composite', item.value)}
onAdd={() => applyPalette('indicator', item.value, item.label, true)}
/>
))}
</div>
</div>
</>
)}
{rightTab === 'templates' && (