복합지표 전략 추가

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
+176 -2
View File
@@ -51,6 +51,35 @@ import {
loadPaletteItems,
type PaletteItem,
} from '../utils/strategyPaletteStorage';
import {
buildPriceExtremeBreakoutTree,
isPriceExtremeBreakoutPairPaletteValue,
isPriceExtremeBreakoutPairRoot,
findPriceExtremePairRootInForest,
patchPriceExtremePairInTree,
setPriceExtremePairFilterLevel,
inferPriceExtremeFilterLevel,
type PriceExtremeFilterLevel,
} from '../utils/priceExtremeBreakoutPair';
import {
buildInflection33Tree,
isInflection33PaletteValue,
isInflection33PairRoot,
setInflection33PairFilterLevel,
patchInflection33PairInTree,
inferInflection33FilterLevel,
type Inflection33FilterLevel,
} from '../utils/inflection33Strategy';
import {
buildStableStrategyTree,
isStableStrategyPaletteValue,
isStableStrategyPairRoot,
setStableStrategyPairFilterLevel,
patchStableStrategyPairInTree,
inferStableStrategyFilterLevel,
type StableStrategyFilterLevel,
type StableStrategyId,
} from '../utils/stableStrategyPairs';
import {
buildStochOverboughtPairTree,
findStochPairRootInForest,
@@ -60,6 +89,9 @@ import {
setStochPairSecondary,
} from '../utils/stochOverboughtPair';
import StochPairNodeSettings from './strategyEditor/StochPairNodeSettings';
import PriceExtremePairNodeSettings from './strategyEditor/PriceExtremePairNodeSettings';
import Inflection33PairNodeSettings from './strategyEditor/Inflection33PairNodeSettings';
import StableStrategyPairNodeSettings from './strategyEditor/StableStrategyPairNodeSettings';
import {
emptySignalFlowLayout,
buildStrategyFlowLayoutStore,
@@ -568,6 +600,96 @@ export default function StrategyEditorPage({ theme, onNavigateToBacktest }: Prop
handleOrphansChange, handleRootChange, handleExtraRootsChange,
]);
const applyPriceExtremeFilterLevel = useCallback((andNodeId: string, filterLevel: PriceExtremeFilterLevel) => {
if (currentOrphans.some(o => o.id === andNodeId)) {
handleOrphansChange(currentOrphans.map(o => (
o.id === andNodeId ? setPriceExtremePairFilterLevel(o, filterLevel, DEF) : o
)));
return;
}
if (currentRoot) {
const patched = patchPriceExtremePairInTree(currentRoot, andNodeId, filterLevel, DEF);
if (patched && patched !== currentRoot) {
handleRootChange(patched);
return;
}
}
for (const [startId, branch] of Object.entries(currentLayout.extraRoots ?? {})) {
if (!branch) continue;
const patched = patchPriceExtremePairInTree(branch, andNodeId, filterLevel, DEF);
if (patched && patched !== branch) {
handleExtraRootsChange({
...(currentLayout.extraRoots ?? {}),
[startId]: patched,
});
return;
}
}
}, [
currentRoot, currentOrphans, currentLayout.extraRoots, DEF,
handleOrphansChange, handleRootChange, handleExtraRootsChange,
]);
const applyInflection33FilterLevel = useCallback((pairNodeId: string, filterLevel: Inflection33FilterLevel) => {
if (currentOrphans.some(o => o.id === pairNodeId)) {
handleOrphansChange(currentOrphans.map(o => (
o.id === pairNodeId ? setInflection33PairFilterLevel(o, filterLevel, DEF) : o
)));
return;
}
if (currentRoot) {
const patched = patchInflection33PairInTree(currentRoot, pairNodeId, filterLevel, DEF);
if (patched && patched !== currentRoot) {
handleRootChange(patched);
return;
}
}
for (const [startId, branch] of Object.entries(currentLayout.extraRoots ?? {})) {
if (!branch) continue;
const patched = patchInflection33PairInTree(branch, pairNodeId, filterLevel, DEF);
if (patched && patched !== branch) {
handleExtraRootsChange({
...(currentLayout.extraRoots ?? {}),
[startId]: patched,
});
return;
}
}
}, [
currentRoot, currentOrphans, currentLayout.extraRoots, DEF,
handleOrphansChange, handleRootChange, handleExtraRootsChange,
]);
const applyStableStrategyFilterLevel = useCallback((pairNodeId: string, filterLevel: StableStrategyFilterLevel) => {
if (currentOrphans.some(o => o.id === pairNodeId)) {
handleOrphansChange(currentOrphans.map(o => (
o.id === pairNodeId ? setStableStrategyPairFilterLevel(o, filterLevel, DEF) : o
)));
return;
}
if (currentRoot) {
const patched = patchStableStrategyPairInTree(currentRoot, pairNodeId, filterLevel, DEF);
if (patched && patched !== currentRoot) {
handleRootChange(patched);
return;
}
}
for (const [startId, branch] of Object.entries(currentLayout.extraRoots ?? {})) {
if (!branch) continue;
const patched = patchStableStrategyPairInTree(branch, pairNodeId, filterLevel, 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;
@@ -1147,10 +1269,20 @@ export default function StrategyEditorPage({ theme, onNavigateToBacktest }: Prop
const applyPaletteItem = useCallback((item: PaletteItem) => {
const stochPair = isStochOverboughtPairPaletteValue(item.value);
const composite = item.kind === 'composite' && !stochPair;
const priceExtremeBreakout = isPriceExtremeBreakoutPairPaletteValue(item.value);
const inflection33 = isInflection33PaletteValue(item.value);
const stableStrategy = isStableStrategyPaletteValue(item.value);
const composite = item.kind === 'composite' && !stochPair && !priceExtremeBreakout && !inflection33 && !stableStrategy;
const newNode = stochPair
? buildStochOverboughtPairTree(item.secondaryIndicator ?? 'CCI', DEF)
: makeNode('indicator', item.value, signalTab, DEF, composite ? { composite: true } : undefined);
: priceExtremeBreakout
? buildPriceExtremeBreakoutTree(item.value, DEF)
: inflection33
? buildInflection33Tree(item.value, DEF)
: stableStrategy
? buildStableStrategyTree(item.value, DEF)
: makeNode('indicator', item.value, signalTab, DEF, composite ? { composite: true } : undefined);
if (!newNode) return;
const root = currentRoot;
if (!root) setCurrentRoot(newNode);
else setCurrentRoot(mergeAtRoot(root, newNode, false));
@@ -1774,6 +1906,48 @@ export default function StrategyEditorPage({ theme, onNavigateToBacktest }: Prop
/>
</div>
)}
{selectedLogicNode && isPriceExtremeBreakoutPairRoot(selectedLogicNode) && selectedLogicNode.priceExtremePair && (
<div className="se-node-config-bar se-node-config-bar--price-extreme">
<span className="se-node-config-label">9·20 / </span>
<PriceExtremePairNodeSettings
variant="inline"
mode={selectedLogicNode.priceExtremePair.mode}
shortPeriod={selectedLogicNode.priceExtremePair.shortPeriod}
longPeriod={selectedLogicNode.priceExtremePair.longPeriod}
filterLevel={inferPriceExtremeFilterLevel(selectedLogicNode)}
onChange={next => applyPriceExtremeFilterLevel(selectedLogicNode.id, next)}
onClose={() => {}}
/>
</div>
)}
{selectedLogicNode && isInflection33PairRoot(selectedLogicNode) && selectedLogicNode.inflection33Pair && (
<div className="se-node-config-bar se-node-config-bar--inflection33">
<span className="se-node-config-label">33 </span>
<Inflection33PairNodeSettings
variant="inline"
mode={selectedLogicNode.inflection33Pair.mode}
period={selectedLogicNode.inflection33Pair.period}
filterLevel={inferInflection33FilterLevel(selectedLogicNode)}
onChange={next => applyInflection33FilterLevel(selectedLogicNode.id, next)}
onClose={() => {}}
/>
</div>
)}
{selectedLogicNode && isStableStrategyPairRoot(selectedLogicNode) && selectedLogicNode.stableStrategyPair && (
<div className="se-node-config-bar se-node-config-bar--stable-strategy">
<span className="se-node-config-label"> </span>
<StableStrategyPairNodeSettings
variant="inline"
strategyId={selectedLogicNode.stableStrategyPair.strategyId as StableStrategyId}
mode={selectedLogicNode.stableStrategyPair.mode}
filterLevel={inferStableStrategyFilterLevel(selectedLogicNode)}
def={DEF}
onChange={next => applyStableStrategyFilterLevel(selectedLogicNode.id, next)}
onClose={() => {}}
/>
</div>
)}
</>
) : (
<StrategyListEditor