복합지표 전략 추가
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user