복합지표 추가
This commit is contained in:
@@ -49,6 +49,15 @@ import {
|
||||
loadPaletteItems,
|
||||
type PaletteItem,
|
||||
} from '../utils/strategyPaletteStorage';
|
||||
import {
|
||||
buildStochOverboughtPairTree,
|
||||
findStochPairRootInForest,
|
||||
isStochOverboughtPairPaletteValue,
|
||||
isStochOverboughtPairRoot,
|
||||
patchStochPairInTree,
|
||||
setStochPairSecondary,
|
||||
} from '../utils/stochOverboughtPair';
|
||||
import StochPairNodeSettings from './strategyEditor/StochPairNodeSettings';
|
||||
import {
|
||||
emptySignalFlowLayout,
|
||||
buildStrategyFlowLayoutStore,
|
||||
@@ -302,6 +311,15 @@ export default function StrategyEditorPage({ theme, onNavigateToBacktest }: Prop
|
||||
);
|
||||
}, [selectedNodeId, currentRoot, currentOrphans, currentLayout.extraRoots]);
|
||||
|
||||
const stochPairForest = useMemo(
|
||||
() => [
|
||||
currentRoot,
|
||||
...currentOrphans,
|
||||
...Object.values(currentLayout.extraRoots ?? {}),
|
||||
],
|
||||
[currentRoot, currentOrphans, currentLayout.extraRoots],
|
||||
);
|
||||
|
||||
const orphanTotal = buyOrphans.length + sellOrphans.length;
|
||||
|
||||
const layoutStrategyKey = selectedId != null ? String(selectedId) : 'draft';
|
||||
@@ -447,6 +465,49 @@ export default function StrategyEditorPage({ theme, onNavigateToBacktest }: Prop
|
||||
scheduleStrategyPersist();
|
||||
}, [setCurrentLayout, scheduleStrategyPersist]);
|
||||
|
||||
const applyStochPairSecondary = useCallback((orNodeId: string, secondary: string) => {
|
||||
if (currentOrphans.some(o => o.id === orNodeId)) {
|
||||
handleOrphansChange(currentOrphans.map(o => (
|
||||
o.id === orNodeId ? setStochPairSecondary(o, secondary, DEF) : o
|
||||
)));
|
||||
return;
|
||||
}
|
||||
if (currentRoot) {
|
||||
const patched = patchStochPairInTree(currentRoot, orNodeId, secondary, DEF);
|
||||
if (patched && patched !== currentRoot) {
|
||||
handleRootChange(patched);
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (const [startId, branch] of Object.entries(currentLayout.extraRoots ?? {})) {
|
||||
if (!branch) continue;
|
||||
const patched = patchStochPairInTree(branch, orNodeId, secondary, DEF);
|
||||
if (patched && patched !== branch) {
|
||||
handleExtraRootsChange({
|
||||
...(currentLayout.extraRoots ?? {}),
|
||||
[startId]: patched,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}, [
|
||||
currentRoot, currentOrphans, currentLayout.extraRoots, DEF,
|
||||
handleOrphansChange, handleRootChange, handleExtraRootsChange,
|
||||
]);
|
||||
|
||||
const stochPairEditForSelected = useMemo(() => {
|
||||
if (!selectedNodeId || selectedLogicNode?.type !== 'CONDITION' || !selectedLogicNode.condition) {
|
||||
return undefined;
|
||||
}
|
||||
const pairRoot = findStochPairRootInForest(stochPairForest, selectedNodeId);
|
||||
if (!pairRoot?.stochPair) return undefined;
|
||||
return {
|
||||
secondaryIndicator: pairRoot.stochPair.secondaryIndicatorType,
|
||||
stochLocked: selectedLogicNode.condition.indicatorType === 'STOCHASTIC',
|
||||
onSecondaryChange: (next: string) => applyStochPairSecondary(pairRoot.id, next),
|
||||
};
|
||||
}, [selectedNodeId, selectedLogicNode, stochPairForest, applyStochPairSecondary]);
|
||||
|
||||
const handleEditorStateChange = useCallback((next: EditorConditionState) => {
|
||||
setCurrentRoot(next.root);
|
||||
setCurrentLayout(prev => ({
|
||||
@@ -953,8 +1014,11 @@ export default function StrategyEditorPage({ theme, onNavigateToBacktest }: Prop
|
||||
}, [signalTab, DEF, currentRoot, setCurrentRoot, handleAddStartSection, scheduleStrategyPersist]);
|
||||
|
||||
const applyPaletteItem = useCallback((item: PaletteItem) => {
|
||||
const composite = item.kind === 'composite';
|
||||
const newNode = makeNode('indicator', item.value, signalTab, DEF, composite ? { composite: true } : undefined);
|
||||
const stochPair = isStochOverboughtPairPaletteValue(item.value);
|
||||
const composite = item.kind === 'composite' && !stochPair;
|
||||
const newNode = stochPair
|
||||
? buildStochOverboughtPairTree(item.secondaryIndicator ?? 'CCI', DEF)
|
||||
: makeNode('indicator', item.value, signalTab, DEF, composite ? { composite: true } : undefined);
|
||||
const root = currentRoot;
|
||||
if (!root) setCurrentRoot(newNode);
|
||||
else setCurrentRoot(mergeAtRoot(root, newNode, false));
|
||||
@@ -1348,11 +1412,16 @@ export default function StrategyEditorPage({ theme, onNavigateToBacktest }: Prop
|
||||
|
||||
{selectedLogicNode?.type === 'CONDITION' && selectedLogicNode.condition && (
|
||||
<div className="se-node-config-bar">
|
||||
<span className="se-node-config-label">{selectedLogicNode.condition.indicatorType}</span>
|
||||
<span className="se-node-config-label">
|
||||
{stochPairEditForSelected && !stochPairEditForSelected.stochLocked
|
||||
? 'Stoch 과열×보조'
|
||||
: selectedLogicNode.condition.indicatorType}
|
||||
</span>
|
||||
<CondEditor
|
||||
cond={selectedLogicNode.condition}
|
||||
signalType={signalTab}
|
||||
def={DEF}
|
||||
stochPairEdit={stochPairEditForSelected}
|
||||
onChange={c => {
|
||||
if (!selectedNodeId) return;
|
||||
const inOrphans = currentOrphans.some(o => o.id === selectedNodeId);
|
||||
@@ -1382,6 +1451,18 @@ export default function StrategyEditorPage({ theme, onNavigateToBacktest }: Prop
|
||||
<span className="se-sync-tip">전략 조건 전용 설정 · 차트 보조지표와 무관</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedLogicNode && isStochOverboughtPairRoot(selectedLogicNode) && selectedLogicNode.stochPair && (
|
||||
<div className="se-node-config-bar se-node-config-bar--stoch-pair">
|
||||
<span className="se-node-config-label">Stoch 과열×보조</span>
|
||||
<StochPairNodeSettings
|
||||
variant="inline"
|
||||
secondaryIndicator={selectedLogicNode.stochPair.secondaryIndicatorType}
|
||||
onChange={next => applyStochPairSecondary(selectedLogicNode.id, next)}
|
||||
onClose={() => {}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<StrategyListEditor
|
||||
|
||||
Reference in New Issue
Block a user