일목균형표 전략 추가
This commit is contained in:
@@ -487,6 +487,10 @@ public class StrategyDslToTa4jAdapter {
|
|||||||
Map<String, Object> indParams = condNodeParams.isEmpty() ? globalParams
|
Map<String, Object> indParams = condNodeParams.isEmpty() ? globalParams
|
||||||
: mergeParams(globalParams, condNodeParams);
|
: mergeParams(globalParams, condNodeParams);
|
||||||
|
|
||||||
|
if ("ICHIMOKU_BB".equals(indType)) {
|
||||||
|
return buildIchimokuBbRule(series, ctx, cond, condType, candleRangeMode, candleRange);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Indicator<Num> left = resolveField(leftField, indType, indParams, series, condPeriod, leftPeriod, cond, ctx);
|
Indicator<Num> left = resolveField(leftField, indType, indParams, series, condPeriod, leftPeriod, cond, ctx);
|
||||||
Indicator<Num> right = resolveField(rightField, indType, indParams, series, condPeriod, rightPeriod, cond, ctx);
|
Indicator<Num> right = resolveField(rightField, indType, indParams, series, condPeriod, rightPeriod, cond, ctx);
|
||||||
@@ -1138,6 +1142,54 @@ public class StrategyDslToTa4jAdapter {
|
|||||||
return new CrossedUpIndicatorRule(close, priorCloudTop);
|
return new CrossedUpIndicatorRule(close, priorCloudTop);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 일목 후행스팬(현재 종가) vs N봉 전 볼린저밴드 — 상향/하향 돌파·위/아래 유지.
|
||||||
|
* cond.params.chikouDisplacement (기본 26), rightField: UPPER_BAND | MIDDLE_BAND | LOWER_BAND
|
||||||
|
*/
|
||||||
|
private Rule buildIchimokuBbRule(
|
||||||
|
BarSeries series,
|
||||||
|
RuleBuildContext ctx,
|
||||||
|
JsonNode cond,
|
||||||
|
String condType,
|
||||||
|
String candleRangeMode,
|
||||||
|
int candleRange) {
|
||||||
|
Map<String, Map<String, Object>> allParams = ctx.indicatorParams();
|
||||||
|
Map<String, Object> ichGlobal = allParams != null
|
||||||
|
? allParams.getOrDefault("IchimokuCloud", Map.of())
|
||||||
|
: Map.of();
|
||||||
|
Map<String, Object> bbGlobal = allParams != null
|
||||||
|
? allParams.getOrDefault("BollingerBands", Map.of())
|
||||||
|
: Map.of();
|
||||||
|
Map<String, Object> nodeParams = extractConditionNodeParams(cond);
|
||||||
|
Map<String, Object> ichParams = nodeParams.isEmpty() ? ichGlobal
|
||||||
|
: mergeParams(ichGlobal, nodeParams);
|
||||||
|
Map<String, Object> bbParams = nodeParams.isEmpty() ? bbGlobal
|
||||||
|
: mergeParams(bbGlobal, nodeParams);
|
||||||
|
|
||||||
|
int d = intP(ichParams, "chikouDisplacement",
|
||||||
|
ichimokuChikouDisplacement(ichParams));
|
||||||
|
String bandField = cond.path("rightField").asText("UPPER_BAND");
|
||||||
|
if (!"UPPER_BAND".equals(bandField) && !"MIDDLE_BAND".equals(bandField) && !"LOWER_BAND".equals(bandField)) {
|
||||||
|
bandField = "UPPER_BAND";
|
||||||
|
}
|
||||||
|
|
||||||
|
ClosePriceIndicator close = new ClosePriceIndicator(series);
|
||||||
|
Indicator<Num> bbLine = resolveField(bandField, "BOLLINGER", bbParams, series, -1, -1, cond, ctx);
|
||||||
|
Indicator<Num> priorBb = new PreviousValueIndicator(bbLine, d);
|
||||||
|
|
||||||
|
Rule core = switch (condType) {
|
||||||
|
case "CROSS_UP" -> buildCrossUpRule(close, priorBb);
|
||||||
|
case "CROSS_DOWN" -> buildCrossDownRule(close, priorBb);
|
||||||
|
case "GT", "GTE" -> new OverIndicatorRule(close, priorBb);
|
||||||
|
case "LT", "LTE" -> new UnderIndicatorRule(close, priorBb);
|
||||||
|
default -> {
|
||||||
|
log.warn("[Adapter] ICHIMOKU_BB 미지원 conditionType: {}", condType);
|
||||||
|
yield new BooleanRule(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return applyCandleRangeWindow(core, candleRangeMode, candleRange, condType);
|
||||||
|
}
|
||||||
|
|
||||||
/** 종가가 구름 위 */
|
/** 종가가 구름 위 */
|
||||||
private Rule buildCloudAboveRule(BarSeries s, Map<String, Object> p) {
|
private Rule buildCloudAboveRule(BarSeries s, Map<String, Object> p) {
|
||||||
NumericIndicator cloudTop = NumericIndicator.of(ichimokuSpanA(s, p)).max(ichimokuSpanB(s, p));
|
NumericIndicator cloudTop = NumericIndicator.of(ichimokuSpanA(s, p)).max(ichimokuSpanB(s, p));
|
||||||
|
|||||||
@@ -75,6 +75,15 @@ import {
|
|||||||
inferInflection33FilterLevel,
|
inferInflection33FilterLevel,
|
||||||
type Inflection33FilterLevel,
|
type Inflection33FilterLevel,
|
||||||
} from '../utils/inflection33Strategy';
|
} from '../utils/inflection33Strategy';
|
||||||
|
import {
|
||||||
|
buildIchimokuBbTree,
|
||||||
|
isIchimokuBbPaletteValue,
|
||||||
|
isIchimokuBbPairRoot,
|
||||||
|
setIchimokuBbPairConfig,
|
||||||
|
patchIchimokuBbPairInTree,
|
||||||
|
inferIchimokuBbConfig,
|
||||||
|
type IchimokuBbPairConfig,
|
||||||
|
} from '../utils/ichimokuBbStrategy';
|
||||||
import {
|
import {
|
||||||
buildStableStrategyTree,
|
buildStableStrategyTree,
|
||||||
isStableStrategyPaletteValue,
|
isStableStrategyPaletteValue,
|
||||||
@@ -96,6 +105,7 @@ import {
|
|||||||
import StochPairNodeSettings from './strategyEditor/StochPairNodeSettings';
|
import StochPairNodeSettings from './strategyEditor/StochPairNodeSettings';
|
||||||
import PriceExtremePairNodeSettings from './strategyEditor/PriceExtremePairNodeSettings';
|
import PriceExtremePairNodeSettings from './strategyEditor/PriceExtremePairNodeSettings';
|
||||||
import Inflection33PairNodeSettings from './strategyEditor/Inflection33PairNodeSettings';
|
import Inflection33PairNodeSettings from './strategyEditor/Inflection33PairNodeSettings';
|
||||||
|
import IchimokuBbPairNodeSettings from './strategyEditor/IchimokuBbPairNodeSettings';
|
||||||
import StableStrategyPairNodeSettings from './strategyEditor/StableStrategyPairNodeSettings';
|
import StableStrategyPairNodeSettings from './strategyEditor/StableStrategyPairNodeSettings';
|
||||||
import {
|
import {
|
||||||
emptySignalFlowLayout,
|
emptySignalFlowLayout,
|
||||||
@@ -731,6 +741,36 @@ export default function StrategyEditorPage({
|
|||||||
handleOrphansChange, handleRootChange, handleExtraRootsChange,
|
handleOrphansChange, handleRootChange, handleExtraRootsChange,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const applyIchimokuBbConfig = useCallback((pairNodeId: string, patch: Partial<IchimokuBbPairConfig>) => {
|
||||||
|
if (currentOrphans.some(o => o.id === pairNodeId)) {
|
||||||
|
handleOrphansChange(currentOrphans.map(o => (
|
||||||
|
o.id === pairNodeId ? setIchimokuBbPairConfig(o, patch, DEF) : o
|
||||||
|
)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (currentRoot) {
|
||||||
|
const patched = patchIchimokuBbPairInTree(currentRoot, pairNodeId, patch, DEF);
|
||||||
|
if (patched && patched !== currentRoot) {
|
||||||
|
handleRootChange(patched);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const [startId, branch] of Object.entries(currentLayout.extraRoots ?? {})) {
|
||||||
|
if (!branch) continue;
|
||||||
|
const patched = patchIchimokuBbPairInTree(branch, pairNodeId, patch, 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) => {
|
const applyStableStrategyFilterLevel = useCallback((pairNodeId: string, filterLevel: StableStrategyFilterLevel) => {
|
||||||
if (currentOrphans.some(o => o.id === pairNodeId)) {
|
if (currentOrphans.some(o => o.id === pairNodeId)) {
|
||||||
handleOrphansChange(currentOrphans.map(o => (
|
handleOrphansChange(currentOrphans.map(o => (
|
||||||
@@ -1355,14 +1395,17 @@ export default function StrategyEditorPage({
|
|||||||
const stochPair = isStochOverboughtPairPaletteValue(item.value);
|
const stochPair = isStochOverboughtPairPaletteValue(item.value);
|
||||||
const priceExtremeBreakout = isPriceExtremeBreakoutPairPaletteValue(item.value);
|
const priceExtremeBreakout = isPriceExtremeBreakoutPairPaletteValue(item.value);
|
||||||
const inflection33 = isInflection33PaletteValue(item.value);
|
const inflection33 = isInflection33PaletteValue(item.value);
|
||||||
|
const ichimokuBb = isIchimokuBbPaletteValue(item.value);
|
||||||
const stableStrategy = isStableStrategyPaletteValue(item.value);
|
const stableStrategy = isStableStrategyPaletteValue(item.value);
|
||||||
const composite = item.kind === 'composite' && !stochPair && !priceExtremeBreakout && !inflection33 && !stableStrategy;
|
const composite = item.kind === 'composite' && !stochPair && !priceExtremeBreakout && !inflection33 && !ichimokuBb && !stableStrategy;
|
||||||
const newNode = stochPair
|
const newNode = stochPair
|
||||||
? buildStochOverboughtPairTree(item.secondaryIndicator ?? 'CCI', DEF)
|
? buildStochOverboughtPairTree(item.secondaryIndicator ?? 'CCI', DEF)
|
||||||
: priceExtremeBreakout
|
: priceExtremeBreakout
|
||||||
? buildPriceExtremeBreakoutTree(item.value, DEF)
|
? buildPriceExtremeBreakoutTree(item.value, DEF)
|
||||||
: inflection33
|
: inflection33
|
||||||
? buildInflection33Tree(item.value, DEF)
|
? buildInflection33Tree(item.value, DEF)
|
||||||
|
: ichimokuBb
|
||||||
|
? buildIchimokuBbTree(item.value, DEF)
|
||||||
: stableStrategy
|
: stableStrategy
|
||||||
? buildStableStrategyTree(item.value, DEF)
|
? buildStableStrategyTree(item.value, DEF)
|
||||||
: makeNode('indicator', item.value, signalTab, DEF, composite ? { composite: true } : undefined);
|
: makeNode('indicator', item.value, signalTab, DEF, composite ? { composite: true } : undefined);
|
||||||
@@ -2143,6 +2186,17 @@ export default function StrategyEditorPage({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{selectedLogicNode && isIchimokuBbPairRoot(selectedLogicNode) && selectedLogicNode.ichimokuBbPair && (
|
||||||
|
<div className="se-node-config-bar se-node-config-bar--ichimoku-bb">
|
||||||
|
<span className="se-node-config-label">일목×볼린저</span>
|
||||||
|
<IchimokuBbPairNodeSettings
|
||||||
|
variant="inline"
|
||||||
|
config={inferIchimokuBbConfig(selectedLogicNode)}
|
||||||
|
onChange={patch => applyIchimokuBbConfig(selectedLogicNode.id, patch)}
|
||||||
|
onClose={() => {}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{selectedLogicNode && isStableStrategyPairRoot(selectedLogicNode) && selectedLogicNode.stableStrategyPair && (
|
{selectedLogicNode && isStableStrategyPairRoot(selectedLogicNode) && selectedLogicNode.stableStrategyPair && (
|
||||||
<div className="se-node-config-bar se-node-config-bar--stable-strategy">
|
<div className="se-node-config-bar se-node-config-bar--stable-strategy">
|
||||||
<span className="se-node-config-label">추천 전략 필터</span>
|
<span className="se-node-config-label">추천 전략 필터</span>
|
||||||
|
|||||||
@@ -20,7 +20,13 @@ import {
|
|||||||
inflection33DisplayName,
|
inflection33DisplayName,
|
||||||
inferInflection33FilterLevel,
|
inferInflection33FilterLevel,
|
||||||
} from '../../utils/inflection33Strategy';
|
} from '../../utils/inflection33Strategy';
|
||||||
|
import {
|
||||||
|
isIchimokuBbPairRoot,
|
||||||
|
ichimokuBbDisplayName,
|
||||||
|
inferIchimokuBbConfig,
|
||||||
|
} from '../../utils/ichimokuBbStrategy';
|
||||||
import Inflection33PairNodeSettings from './Inflection33PairNodeSettings';
|
import Inflection33PairNodeSettings from './Inflection33PairNodeSettings';
|
||||||
|
import IchimokuBbPairNodeSettings from './IchimokuBbPairNodeSettings';
|
||||||
import {
|
import {
|
||||||
isStableStrategyPairRoot,
|
isStableStrategyPairRoot,
|
||||||
stableStrategyDisplayName,
|
stableStrategyDisplayName,
|
||||||
@@ -235,13 +241,14 @@ export const LogicGateNode = memo(function LogicGateNode({ id, data, selected }:
|
|||||||
const isStochPair = isStochOverboughtPairRoot(node);
|
const isStochPair = isStochOverboughtPairRoot(node);
|
||||||
const isPriceExtremePair = isPriceExtremeBreakoutPairRoot(node);
|
const isPriceExtremePair = isPriceExtremeBreakoutPairRoot(node);
|
||||||
const isInflection33Pair = isInflection33PairRoot(node);
|
const isInflection33Pair = isInflection33PairRoot(node);
|
||||||
|
const isIchimokuBbPair = isIchimokuBbPairRoot(node);
|
||||||
const isStableStrategyPair = isStableStrategyPairRoot(node);
|
const isStableStrategyPair = isStableStrategyPairRoot(node);
|
||||||
const { onDragEnter, onDragOver, onDragLeave, onDrop } = usePaletteDropHandlers(id, d);
|
const { onDragEnter, onDragOver, onDragLeave, onDrop } = usePaletteDropHandlers(id, d);
|
||||||
const [settingsOpen, setSettingsOpen] = useState(false);
|
const [settingsOpen, setSettingsOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
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' : ''}`}
|
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' : ''}${isIchimokuBbPair ? ' se-flow-node--ichimoku-bb-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}
|
style={{ '--se-gate-color': color } as React.CSSProperties}
|
||||||
onDragEnter={onDragEnter}
|
onDragEnter={onDragEnter}
|
||||||
onDragOver={onDragOver}
|
onDragOver={onDragOver}
|
||||||
@@ -312,6 +319,28 @@ export const LogicGateNode = memo(function LogicGateNode({ id, data, selected }:
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{isIchimokuBbPair && node.ichimokuBbPair && (
|
||||||
|
<div className="se-flow-gate-stoch-pair">
|
||||||
|
<span className="se-flow-gate-stoch-pair-title">
|
||||||
|
{ichimokuBbDisplayName(node.ichimokuBbPair.mode, node.ichimokuBbPair.chikouDisplacement)}
|
||||||
|
</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 && (
|
{isPriceExtremePair && node.priceExtremePair && (
|
||||||
<div className="se-flow-gate-stoch-pair">
|
<div className="se-flow-gate-stoch-pair">
|
||||||
<span className="se-flow-gate-stoch-pair-title">
|
<span className="se-flow-gate-stoch-pair-title">
|
||||||
@@ -390,6 +419,13 @@ export const LogicGateNode = memo(function LogicGateNode({ id, data, selected }:
|
|||||||
onClose={() => setSettingsOpen(false)}
|
onClose={() => setSettingsOpen(false)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{settingsOpen && isIchimokuBbPair && node.ichimokuBbPair && (
|
||||||
|
<IchimokuBbPairNodeSettings
|
||||||
|
config={inferIchimokuBbConfig(node)}
|
||||||
|
onChange={patch => d.onUpdateIchimokuBbConfig?.(id, patch)}
|
||||||
|
onClose={() => setSettingsOpen(false)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{settingsOpen && isPriceExtremePair && node.priceExtremePair && (
|
{settingsOpen && isPriceExtremePair && node.priceExtremePair && (
|
||||||
<PriceExtremePairNodeSettings
|
<PriceExtremePairNodeSettings
|
||||||
mode={node.priceExtremePair.mode}
|
mode={node.priceExtremePair.mode}
|
||||||
|
|||||||
@@ -0,0 +1,128 @@
|
|||||||
|
import React, { useEffect, useRef } from 'react';
|
||||||
|
import {
|
||||||
|
ICHIMOKU_BB_BAND_OPTIONS,
|
||||||
|
ICHIMOKU_BB_CONDITION_OPTIONS,
|
||||||
|
ichimokuBbDisplayName,
|
||||||
|
ichimokuBbSummaryText,
|
||||||
|
type IchimokuBbBand,
|
||||||
|
type IchimokuBbConditionType,
|
||||||
|
type IchimokuBbPairConfig,
|
||||||
|
} from '../../utils/ichimokuBbStrategy';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
config: IchimokuBbPairConfig;
|
||||||
|
onChange: (patch: Partial<IchimokuBbPairConfig>) => void;
|
||||||
|
onClose: () => void;
|
||||||
|
popoverClassName?: string;
|
||||||
|
variant?: 'popover' | 'inline';
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function IchimokuBbPairNodeSettings({
|
||||||
|
config,
|
||||||
|
onChange,
|
||||||
|
onClose,
|
||||||
|
popoverClassName = 'se-flow-settings-pop',
|
||||||
|
variant = 'popover',
|
||||||
|
}: Props) {
|
||||||
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (variant === 'inline') return;
|
||||||
|
const onDoc = (e: MouseEvent) => {
|
||||||
|
if (ref.current && !ref.current.contains(e.target as Node)) onClose();
|
||||||
|
};
|
||||||
|
document.addEventListener('mousedown', onDoc);
|
||||||
|
return () => document.removeEventListener('mousedown', onDoc);
|
||||||
|
}, [onClose, variant]);
|
||||||
|
|
||||||
|
const body = (
|
||||||
|
<>
|
||||||
|
<label className="se-flow-settings-field se-flow-settings-field--readonly">
|
||||||
|
<span>전략</span>
|
||||||
|
<output className="se-flow-settings-readout">
|
||||||
|
{ichimokuBbDisplayName(config.mode, config.chikouDisplacement)}
|
||||||
|
</output>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label className="se-flow-settings-field">
|
||||||
|
<span>후행 N봉</span>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
className="se-combo-num-input"
|
||||||
|
min={1}
|
||||||
|
max={120}
|
||||||
|
value={config.chikouDisplacement}
|
||||||
|
onChange={e => {
|
||||||
|
const n = parseInt(e.target.value, 10);
|
||||||
|
if (Number.isFinite(n) && n > 0) onChange({ chikouDisplacement: n });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label className="se-flow-settings-field">
|
||||||
|
<span>볼린저 밴드</span>
|
||||||
|
<select
|
||||||
|
className="se-combo-num-select sp-cond-sel"
|
||||||
|
value={config.bbBand}
|
||||||
|
onChange={e => onChange({ bbBand: e.target.value as IchimokuBbBand })}
|
||||||
|
>
|
||||||
|
{ICHIMOKU_BB_BAND_OPTIONS.map(o => (
|
||||||
|
<option key={o.value} value={o.value}>{o.label}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label className="se-flow-settings-field">
|
||||||
|
<span>조건</span>
|
||||||
|
<select
|
||||||
|
className="se-combo-num-select sp-cond-sel"
|
||||||
|
value={config.conditionType}
|
||||||
|
onChange={e => onChange({ conditionType: e.target.value as IchimokuBbConditionType })}
|
||||||
|
>
|
||||||
|
{ICHIMOKU_BB_CONDITION_OPTIONS.map(o => (
|
||||||
|
<option key={o.value} value={o.value}>{o.label}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<p className="se-flow-settings-hint se-flow-settings-hint--muted">
|
||||||
|
현재 후행스팬(종가)과 N봉 전 시점의 볼린저 밴드를 비교합니다.
|
||||||
|
</p>
|
||||||
|
<p className="se-flow-settings-hint se-flow-settings-hint--muted">
|
||||||
|
{ichimokuBbSummaryText(config)}
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (variant === 'inline') {
|
||||||
|
return (
|
||||||
|
<div className="se-ichimoku-bb-inline-settings" onClick={e => e.stopPropagation()}>
|
||||||
|
{body}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={popoverClassName}
|
||||||
|
role="dialog"
|
||||||
|
aria-label="일목×볼린저 설정"
|
||||||
|
onClick={e => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<div className="se-flow-settings-head">
|
||||||
|
<span>일목×볼린저</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="se-flow-settings-close"
|
||||||
|
aria-label="닫기"
|
||||||
|
title="닫기"
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{body}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import { getCompositeDefaultPeriods } from '../../utils/compositeIndicators';
|
|||||||
import { isStochOverboughtPairPaletteValue } from '../../utils/stochOverboughtPair';
|
import { isStochOverboughtPairPaletteValue } from '../../utils/stochOverboughtPair';
|
||||||
import { isPriceExtremeBreakoutPairPaletteValue } from '../../utils/priceExtremeBreakoutPair';
|
import { isPriceExtremeBreakoutPairPaletteValue } from '../../utils/priceExtremeBreakoutPair';
|
||||||
import { isInflection33PaletteValue } from '../../utils/inflection33Strategy';
|
import { isInflection33PaletteValue } from '../../utils/inflection33Strategy';
|
||||||
|
import { isIchimokuBbPaletteValue } from '../../utils/ichimokuBbStrategy';
|
||||||
import { isStableStrategyPaletteValue, STABLE_STRATEGY_PALETTE_ITEMS } from '../../utils/stableStrategyPairs';
|
import { isStableStrategyPaletteValue, STABLE_STRATEGY_PALETTE_ITEMS } from '../../utils/stableStrategyPairs';
|
||||||
import { getDefaultIndicatorPeriod } from '../../utils/conditionPeriods';
|
import { getDefaultIndicatorPeriod } from '../../utils/conditionPeriods';
|
||||||
import { getStrategyIndicatorDisplayName } from '../../utils/strategyPaletteStorage';
|
import { getStrategyIndicatorDisplayName } from '../../utils/strategyPaletteStorage';
|
||||||
@@ -31,6 +32,9 @@ function periodLabel(item: PaletteItem, def: DefType): string {
|
|||||||
if (item.kind === 'composite' && isInflection33PaletteValue(item.value)) {
|
if (item.kind === 'composite' && isInflection33PaletteValue(item.value)) {
|
||||||
return `${item.period ?? 33}일`;
|
return `${item.period ?? 33}일`;
|
||||||
}
|
}
|
||||||
|
if (item.kind === 'composite' && isIchimokuBbPaletteValue(item.value)) {
|
||||||
|
return `${item.period ?? 26}봉`;
|
||||||
|
}
|
||||||
if (item.kind === 'composite' && isStableStrategyPaletteValue(item.value)) {
|
if (item.kind === 'composite' && isStableStrategyPaletteValue(item.value)) {
|
||||||
const meta = STABLE_STRATEGY_PALETTE_ITEMS.find(i => i.value === item.value);
|
const meta = STABLE_STRATEGY_PALETTE_ITEMS.find(i => i.value === item.value);
|
||||||
return meta?.periodHint ?? '';
|
return meta?.periodHint ?? '';
|
||||||
@@ -189,10 +193,12 @@ export default function IndicatorPaletteTab({
|
|||||||
&& !isStochOverboughtPairPaletteValue(item.value)
|
&& !isStochOverboughtPairPaletteValue(item.value)
|
||||||
&& !isPriceExtremeBreakoutPairPaletteValue(item.value)
|
&& !isPriceExtremeBreakoutPairPaletteValue(item.value)
|
||||||
&& !isInflection33PaletteValue(item.value)
|
&& !isInflection33PaletteValue(item.value)
|
||||||
|
&& !isIchimokuBbPaletteValue(item.value)
|
||||||
&& !isStableStrategyPaletteValue(item.value)}
|
&& !isStableStrategyPaletteValue(item.value)}
|
||||||
stochPair={isStochOverboughtPairPaletteValue(item.value)}
|
stochPair={isStochOverboughtPairPaletteValue(item.value)}
|
||||||
priceExtremeBreakout={isPriceExtremeBreakoutPairPaletteValue(item.value)}
|
priceExtremeBreakout={isPriceExtremeBreakoutPairPaletteValue(item.value)}
|
||||||
inflection33={isInflection33PaletteValue(item.value)}
|
inflection33={isInflection33PaletteValue(item.value)}
|
||||||
|
ichimokuBb={isIchimokuBbPaletteValue(item.value)}
|
||||||
stableStrategy={isStableStrategyPaletteValue(item.value)}
|
stableStrategy={isStableStrategyPaletteValue(item.value)}
|
||||||
secondaryIndicator={item.secondaryIndicator}
|
secondaryIndicator={item.secondaryIndicator}
|
||||||
period={periodLabel(item, def)}
|
period={periodLabel(item, def)}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ interface Props {
|
|||||||
stochPair?: boolean;
|
stochPair?: boolean;
|
||||||
priceExtremeBreakout?: boolean;
|
priceExtremeBreakout?: boolean;
|
||||||
inflection33?: boolean;
|
inflection33?: boolean;
|
||||||
|
ichimokuBb?: boolean;
|
||||||
stableStrategy?: boolean;
|
stableStrategy?: boolean;
|
||||||
secondaryIndicator?: string;
|
secondaryIndicator?: string;
|
||||||
selected?: boolean;
|
selected?: boolean;
|
||||||
@@ -36,7 +37,7 @@ interface Props {
|
|||||||
|
|
||||||
export default function PaletteChip({
|
export default function PaletteChip({
|
||||||
type, value, label, desc, color, period, periodValue, shortPeriod, longPeriod,
|
type, value, label, desc, color, period, periodValue, shortPeriod, longPeriod,
|
||||||
composite = false, stochPair = false, priceExtremeBreakout = false, inflection33 = false, stableStrategy = false, secondaryIndicator, selected = false, onSelect, onAdd,
|
composite = false, stochPair = false, priceExtremeBreakout = false, inflection33 = false, ichimokuBb = false, stableStrategy = false, secondaryIndicator, selected = false, onSelect, onAdd,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const buildPayload = (): PaletteDragPayload => ({
|
const buildPayload = (): PaletteDragPayload => ({
|
||||||
type, value, label,
|
type, value, label,
|
||||||
@@ -44,6 +45,7 @@ export default function PaletteChip({
|
|||||||
stochPair: stochPair || undefined,
|
stochPair: stochPair || undefined,
|
||||||
priceExtremeBreakout: priceExtremeBreakout || undefined,
|
priceExtremeBreakout: priceExtremeBreakout || undefined,
|
||||||
inflection33: inflection33 || undefined,
|
inflection33: inflection33 || undefined,
|
||||||
|
ichimokuBb: ichimokuBb || undefined,
|
||||||
stableStrategy: stableStrategy || undefined,
|
stableStrategy: stableStrategy || undefined,
|
||||||
secondaryIndicator,
|
secondaryIndicator,
|
||||||
period: periodValue,
|
period: periodValue,
|
||||||
|
|||||||
@@ -57,6 +57,10 @@ import {
|
|||||||
setInflection33PairFilterLevel,
|
setInflection33PairFilterLevel,
|
||||||
type Inflection33FilterLevel,
|
type Inflection33FilterLevel,
|
||||||
} from '../../utils/inflection33Strategy';
|
} from '../../utils/inflection33Strategy';
|
||||||
|
import {
|
||||||
|
setIchimokuBbPairConfig,
|
||||||
|
type IchimokuBbPairConfig,
|
||||||
|
} from '../../utils/ichimokuBbStrategy';
|
||||||
import {
|
import {
|
||||||
setStableStrategyPairFilterLevel,
|
setStableStrategyPairFilterLevel,
|
||||||
type StableStrategyFilterLevel,
|
type StableStrategyFilterLevel,
|
||||||
@@ -772,6 +776,11 @@ function StrategyEditorCanvasInner({
|
|||||||
: n.inflection33Pair?.mode === 'sell'
|
: n.inflection33Pair?.mode === 'sell'
|
||||||
? (gateType === 'OR' ? n.inflection33Pair : undefined)
|
? (gateType === 'OR' ? n.inflection33Pair : undefined)
|
||||||
: undefined,
|
: undefined,
|
||||||
|
ichimokuBbPair: n.ichimokuBbPair?.mode === 'buy'
|
||||||
|
? (gateType === 'AND' ? n.ichimokuBbPair : undefined)
|
||||||
|
: n.ichimokuBbPair?.mode === 'sell'
|
||||||
|
? (gateType === 'OR' ? n.ichimokuBbPair : undefined)
|
||||||
|
: undefined,
|
||||||
stableStrategyPair: n.stableStrategyPair?.mode === 'buy'
|
stableStrategyPair: n.stableStrategyPair?.mode === 'buy'
|
||||||
? (gateType === 'AND' ? n.stableStrategyPair : undefined)
|
? (gateType === 'AND' ? n.stableStrategyPair : undefined)
|
||||||
: n.stableStrategyPair?.mode === 'sell'
|
: n.stableStrategyPair?.mode === 'sell'
|
||||||
@@ -866,6 +875,27 @@ function StrategyEditorCanvasInner({
|
|||||||
}
|
}
|
||||||
}, [root, extraRoots, orphans, def, onChange, onOrphansChange, onExtraRootsChange]);
|
}, [root, extraRoots, orphans, def, onChange, onOrphansChange, onExtraRootsChange]);
|
||||||
|
|
||||||
|
const handleUpdateIchimokuBbConfig = useCallback((id: string, patch: Partial<IchimokuBbPairConfig>) => {
|
||||||
|
const patchPair = (n: LogicNode) => setIchimokuBbPairConfig(n, patch, def);
|
||||||
|
if (isOrphanNode(orphans, id)) {
|
||||||
|
onOrphansChange(orphans.map(o => (o.id === id ? patchPair(o) : o)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (root && findNodeInTree(root, id)) {
|
||||||
|
onChange(updateNode(root, id, patchPair));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const [sid, branch] of Object.entries(extraRoots)) {
|
||||||
|
if (branch && findNodeInTree(branch, id)) {
|
||||||
|
onExtraRootsChange?.({
|
||||||
|
...extraRoots,
|
||||||
|
[sid]: updateNode(branch, id, patchPair),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [root, extraRoots, orphans, def, onChange, onOrphansChange, onExtraRootsChange]);
|
||||||
|
|
||||||
const handleUpdateStableStrategyFilterLevel = useCallback((id: string, filterLevel: StableStrategyFilterLevel) => {
|
const handleUpdateStableStrategyFilterLevel = useCallback((id: string, filterLevel: StableStrategyFilterLevel) => {
|
||||||
const patchPair = (n: LogicNode) => setStableStrategyPairFilterLevel(n, filterLevel, def);
|
const patchPair = (n: LogicNode) => setStableStrategyPairFilterLevel(n, filterLevel, def);
|
||||||
if (isOrphanNode(orphans, id)) {
|
if (isOrphanNode(orphans, id)) {
|
||||||
@@ -894,6 +924,7 @@ function StrategyEditorCanvasInner({
|
|||||||
onUpdateStochPairSecondary: handleUpdateStochPairSecondary,
|
onUpdateStochPairSecondary: handleUpdateStochPairSecondary,
|
||||||
onUpdatePriceExtremeFilterLevel: handleUpdatePriceExtremeFilterLevel,
|
onUpdatePriceExtremeFilterLevel: handleUpdatePriceExtremeFilterLevel,
|
||||||
onUpdateInflection33FilterLevel: handleUpdateInflection33FilterLevel,
|
onUpdateInflection33FilterLevel: handleUpdateInflection33FilterLevel,
|
||||||
|
onUpdateIchimokuBbConfig: handleUpdateIchimokuBbConfig,
|
||||||
onUpdateStableStrategyFilterLevel: handleUpdateStableStrategyFilterLevel,
|
onUpdateStableStrategyFilterLevel: handleUpdateStableStrategyFilterLevel,
|
||||||
onChangeLogicGateType: handleChangeLogicGateType,
|
onChangeLogicGateType: handleChangeLogicGateType,
|
||||||
onDropTarget: handleDropTarget,
|
onDropTarget: handleDropTarget,
|
||||||
@@ -902,7 +933,7 @@ function StrategyEditorCanvasInner({
|
|||||||
onStartCandleTypesChange: handleStartCandleTypesChange,
|
onStartCandleTypesChange: handleStartCandleTypesChange,
|
||||||
onDeleteStart: handleDeleteStart,
|
onDeleteStart: handleDeleteStart,
|
||||||
}), [
|
}), [
|
||||||
handleDelete, handleUpdateCondition, handleReplaceLogicNode, handleUpdateStochPairSecondary, handleUpdatePriceExtremeFilterLevel, handleUpdateInflection33FilterLevel, handleUpdateStableStrategyFilterLevel, handleChangeLogicGateType,
|
handleDelete, handleUpdateCondition, handleReplaceLogicNode, handleUpdateStochPairSecondary, handleUpdatePriceExtremeFilterLevel, handleUpdateInflection33FilterLevel, handleUpdateIchimokuBbConfig, handleUpdateStableStrategyFilterLevel, handleChangeLogicGateType,
|
||||||
handleDropTarget, handleDragOverTarget, handleDragLeaveTarget,
|
handleDropTarget, handleDragOverTarget, handleDragLeaveTarget,
|
||||||
handleStartCandleTypesChange, handleDeleteStart,
|
handleStartCandleTypesChange, handleDeleteStart,
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -43,6 +43,10 @@ import {
|
|||||||
buildInflection33Tree,
|
buildInflection33Tree,
|
||||||
isInflection33PaletteValue,
|
isInflection33PaletteValue,
|
||||||
} from '../utils/inflection33Strategy';
|
} from '../utils/inflection33Strategy';
|
||||||
|
import {
|
||||||
|
buildIchimokuBbTree,
|
||||||
|
isIchimokuBbPaletteValue,
|
||||||
|
} from '../utils/ichimokuBbStrategy';
|
||||||
import {
|
import {
|
||||||
buildStableStrategyTree,
|
buildStableStrategyTree,
|
||||||
isStableStrategyPaletteValue,
|
isStableStrategyPaletteValue,
|
||||||
@@ -442,14 +446,17 @@ export function useStrategyEvaluationEditor({
|
|||||||
const stochPair = isStochOverboughtPairPaletteValue(item.value);
|
const stochPair = isStochOverboughtPairPaletteValue(item.value);
|
||||||
const priceExtremeBreakout = isPriceExtremeBreakoutPairPaletteValue(item.value);
|
const priceExtremeBreakout = isPriceExtremeBreakoutPairPaletteValue(item.value);
|
||||||
const inflection33 = isInflection33PaletteValue(item.value);
|
const inflection33 = isInflection33PaletteValue(item.value);
|
||||||
|
const ichimokuBb = isIchimokuBbPaletteValue(item.value);
|
||||||
const stableStrategy = isStableStrategyPaletteValue(item.value);
|
const stableStrategy = isStableStrategyPaletteValue(item.value);
|
||||||
const composite = item.kind === 'composite' && !stochPair && !priceExtremeBreakout && !inflection33 && !stableStrategy;
|
const composite = item.kind === 'composite' && !stochPair && !priceExtremeBreakout && !inflection33 && !ichimokuBb && !stableStrategy;
|
||||||
const newNode = stochPair
|
const newNode = stochPair
|
||||||
? buildStochOverboughtPairTree(item.secondaryIndicator ?? 'CCI', def)
|
? buildStochOverboughtPairTree(item.secondaryIndicator ?? 'CCI', def)
|
||||||
: priceExtremeBreakout
|
: priceExtremeBreakout
|
||||||
? buildPriceExtremeBreakoutTree(item.value, def)
|
? buildPriceExtremeBreakoutTree(item.value, def)
|
||||||
: inflection33
|
: inflection33
|
||||||
? buildInflection33Tree(item.value, def)
|
? buildInflection33Tree(item.value, def)
|
||||||
|
: ichimokuBb
|
||||||
|
? buildIchimokuBbTree(item.value, def)
|
||||||
: stableStrategy
|
: stableStrategy
|
||||||
? buildStableStrategyTree(item.value, def)
|
? buildStableStrategyTree(item.value, def)
|
||||||
: makeNode('indicator', item.value, signalTab, def, composite ? { composite: true } : undefined);
|
: makeNode('indicator', item.value, signalTab, def, composite ? { composite: true } : undefined);
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export const DEFAULT_BB_BAND_BACKGROUND = {
|
|||||||
color: '#42A5F528',
|
color: '#42A5F528',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
const BB_PLOT_TITLES = ['중앙값', '어퍼', '로우어'] as const;
|
const BB_PLOT_TITLES = ['중앙값', '상한선', '하한선'] as const;
|
||||||
const BB_PLOT_IDS = ['plot0', 'plot1', 'plot2'] as const;
|
const BB_PLOT_IDS = ['plot0', 'plot1', 'plot2'] as const;
|
||||||
const BB_DEFAULT_COLORS = ['#FF9800', '#42A5F5', '#42A5F5'] as const;
|
const BB_DEFAULT_COLORS = ['#FF9800', '#42A5F5', '#42A5F5'] as const;
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ export function resolveBbBandBackground(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 업비트 플롯 정의 병합 (중앙값·어퍼·로우어, 기본 색) */
|
/** 업비트 플롯 정의 병합 (중앙값·상한선·하한선, 기본 색) */
|
||||||
export function mergeBbPlots(
|
export function mergeBbPlots(
|
||||||
saved: import('./indicatorRegistry').PlotDef[] | undefined,
|
saved: import('./indicatorRegistry').PlotDef[] | undefined,
|
||||||
defaults: import('./indicatorRegistry').PlotDef[],
|
defaults: import('./indicatorRegistry').PlotDef[],
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ export type ChartCustomOverlaySelection = {
|
|||||||
export const BB_CUSTOM_PLOT_IDS = ['plot0', 'plot1', 'plot2'] as const;
|
export const BB_CUSTOM_PLOT_IDS = ['plot0', 'plot1', 'plot2'] as const;
|
||||||
export const BB_CUSTOM_LABELS: Record<(typeof BB_CUSTOM_PLOT_IDS)[number], string> = {
|
export const BB_CUSTOM_LABELS: Record<(typeof BB_CUSTOM_PLOT_IDS)[number], string> = {
|
||||||
plot0: '중앙값',
|
plot0: '중앙값',
|
||||||
plot1: '어퍼',
|
plot1: '상한선',
|
||||||
plot2: '로우어',
|
plot2: '하한선',
|
||||||
};
|
};
|
||||||
|
|
||||||
export function createDefaultChartCustomOverlaySelection(): ChartCustomOverlaySelection {
|
export function createDefaultChartCustomOverlaySelection(): ChartCustomOverlaySelection {
|
||||||
|
|||||||
@@ -0,0 +1,198 @@
|
|||||||
|
/**
|
||||||
|
* 일목 후행스팬 × 볼린저밴드 복합 전략
|
||||||
|
*
|
||||||
|
* 현재 후행스팬(종가) vs N봉(chikouDisplacement) 전 볼린저 상한선·중앙값·하한선
|
||||||
|
* — 상향/하향 돌파·위/아래 유지 조건
|
||||||
|
*/
|
||||||
|
import type { ConditionDSL, LogicNode } from './strategyTypes';
|
||||||
|
import { initConditionPeriodsInherit, type IndicatorPeriodDef } from './conditionPeriods';
|
||||||
|
import { genId } from './strategyNodeIds';
|
||||||
|
|
||||||
|
export const ICHIMOKU_BB_BUY_VALUE = 'ICHIMOKU_BB_BUY';
|
||||||
|
export const ICHIMOKU_BB_SELL_VALUE = 'ICHIMOKU_BB_SELL';
|
||||||
|
|
||||||
|
export const DEFAULT_CHIKOU_DISPLACEMENT = 26;
|
||||||
|
|
||||||
|
export type IchimokuBbBand = 'UPPER_BAND' | 'MIDDLE_BAND' | 'LOWER_BAND';
|
||||||
|
export type IchimokuBbConditionType = 'CROSS_UP' | 'CROSS_DOWN' | 'GT' | 'LT';
|
||||||
|
|
||||||
|
export interface IchimokuBbPairConfig {
|
||||||
|
mode: 'buy' | 'sell';
|
||||||
|
chikouDisplacement: number;
|
||||||
|
bbBand: IchimokuBbBand;
|
||||||
|
conditionType: IchimokuBbConditionType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ICHIMOKU_BB_BAND_OPTIONS: { value: IchimokuBbBand; label: string }[] = [
|
||||||
|
{ value: 'UPPER_BAND', label: '상한선' },
|
||||||
|
{ value: 'MIDDLE_BAND', label: '중앙값' },
|
||||||
|
{ value: 'LOWER_BAND', label: '하한선' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const ICHIMOKU_BB_CONDITION_OPTIONS: { value: IchimokuBbConditionType; label: string }[] = [
|
||||||
|
{ value: 'CROSS_UP', label: '상향 돌파' },
|
||||||
|
{ value: 'CROSS_DOWN', label: '하향 돌파' },
|
||||||
|
{ value: 'GT', label: '위 유지(>)' },
|
||||||
|
{ value: 'LT', label: '아래 유지(<)' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const DEFAULT_BUY: Omit<IchimokuBbPairConfig, 'mode'> = {
|
||||||
|
chikouDisplacement: DEFAULT_CHIKOU_DISPLACEMENT,
|
||||||
|
bbBand: 'UPPER_BAND',
|
||||||
|
conditionType: 'CROSS_UP',
|
||||||
|
};
|
||||||
|
|
||||||
|
const DEFAULT_SELL: Omit<IchimokuBbPairConfig, 'mode'> = {
|
||||||
|
chikouDisplacement: DEFAULT_CHIKOU_DISPLACEMENT,
|
||||||
|
bbBand: 'LOWER_BAND',
|
||||||
|
conditionType: 'CROSS_DOWN',
|
||||||
|
};
|
||||||
|
|
||||||
|
export function isIchimokuBbBuyPaletteValue(value: string): boolean {
|
||||||
|
return value === ICHIMOKU_BB_BUY_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isIchimokuBbSellPaletteValue(value: string): boolean {
|
||||||
|
return value === ICHIMOKU_BB_SELL_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isIchimokuBbPaletteValue(value: string): boolean {
|
||||||
|
return isIchimokuBbBuyPaletteValue(value) || isIchimokuBbSellPaletteValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isIchimokuBbPairRoot(node: LogicNode): boolean {
|
||||||
|
return !!node.ichimokuBbPair?.mode && (node.type === 'AND' || node.type === 'OR');
|
||||||
|
}
|
||||||
|
|
||||||
|
function bandLabel(band: IchimokuBbBand): string {
|
||||||
|
return ICHIMOKU_BB_BAND_OPTIONS.find(o => o.value === band)?.label ?? band;
|
||||||
|
}
|
||||||
|
|
||||||
|
function conditionLabel(ct: IchimokuBbConditionType): string {
|
||||||
|
return ICHIMOKU_BB_CONDITION_OPTIONS.find(o => o.value === ct)?.label ?? ct;
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeIchimokuBbCondition(
|
||||||
|
def: IndicatorPeriodDef,
|
||||||
|
config: Omit<IchimokuBbPairConfig, 'mode'>,
|
||||||
|
): LogicNode {
|
||||||
|
const base: ConditionDSL = {
|
||||||
|
indicatorType: 'ICHIMOKU_BB',
|
||||||
|
conditionType: config.conditionType,
|
||||||
|
leftField: 'LAGGING_SPAN',
|
||||||
|
rightField: config.bbBand,
|
||||||
|
candleRange: 1,
|
||||||
|
valuePeriodOverride: false,
|
||||||
|
thresholdOverride: false,
|
||||||
|
rightPeriodOverride: false,
|
||||||
|
params: {
|
||||||
|
chikouDisplacement: config.chikouDisplacement,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
id: genId(),
|
||||||
|
type: 'CONDITION',
|
||||||
|
condition: initConditionPeriodsInherit('ICHIMOKU_BB', base, def),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function wrapPairRoot(
|
||||||
|
mode: 'buy' | 'sell',
|
||||||
|
config: Omit<IchimokuBbPairConfig, 'mode'>,
|
||||||
|
def: IndicatorPeriodDef,
|
||||||
|
): LogicNode {
|
||||||
|
return {
|
||||||
|
id: genId(),
|
||||||
|
type: 'AND',
|
||||||
|
ichimokuBbPair: { mode, ...config },
|
||||||
|
children: [makeIchimokuBbCondition(def, config)],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildIchimokuBbBuyTree(
|
||||||
|
def: IndicatorPeriodDef,
|
||||||
|
config: Partial<Omit<IchimokuBbPairConfig, 'mode'>> = {},
|
||||||
|
): LogicNode {
|
||||||
|
return wrapPairRoot('buy', { ...DEFAULT_BUY, ...config }, def);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildIchimokuBbSellTree(
|
||||||
|
def: IndicatorPeriodDef,
|
||||||
|
config: Partial<Omit<IchimokuBbPairConfig, 'mode'>> = {},
|
||||||
|
): LogicNode {
|
||||||
|
return wrapPairRoot('sell', { ...DEFAULT_SELL, ...config }, def);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildIchimokuBbTree(value: string, def: IndicatorPeriodDef): LogicNode | null {
|
||||||
|
if (isIchimokuBbBuyPaletteValue(value)) return buildIchimokuBbBuyTree(def);
|
||||||
|
if (isIchimokuBbSellPaletteValue(value)) return buildIchimokuBbSellTree(def);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ichimokuBbDisplayName(mode: 'buy' | 'sell', displacement = DEFAULT_CHIKOU_DISPLACEMENT): string {
|
||||||
|
return mode === 'buy'
|
||||||
|
? `일목×BB 매수 (N=${displacement})`
|
||||||
|
: `일목×BB 매도 (N=${displacement})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ichimokuBbSummaryText(config: IchimokuBbPairConfig): string {
|
||||||
|
return `후행스팬 ${config.chikouDisplacement}봉 전 ${bandLabel(config.bbBand)} ${conditionLabel(config.conditionType)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ichimokuBbPaletteDesc(value: string): string {
|
||||||
|
if (isIchimokuBbBuyPaletteValue(value)) {
|
||||||
|
return `후행스팬(종가)이 ${DEFAULT_CHIKOU_DISPLACEMENT}봉 전 볼린저 상한선 상향 돌파 — 밴드·N·조건 변경 가능`;
|
||||||
|
}
|
||||||
|
if (isIchimokuBbSellPaletteValue(value)) {
|
||||||
|
return `후행스팬(종가)이 ${DEFAULT_CHIKOU_DISPLACEMENT}봉 전 볼린저 하한선 하향 돌파 — 밴드·N·조건 변경 가능`;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function inferIchimokuBbConfig(node: LogicNode): IchimokuBbPairConfig {
|
||||||
|
if (node.ichimokuBbPair) return node.ichimokuBbPair;
|
||||||
|
|
||||||
|
const cond = node.children?.[0]?.condition;
|
||||||
|
const displacement = Number(cond?.params?.chikouDisplacement) || DEFAULT_CHIKOU_DISPLACEMENT;
|
||||||
|
const bbBand = (cond?.rightField as IchimokuBbBand) ?? DEFAULT_BUY.bbBand;
|
||||||
|
const conditionType = (cond?.conditionType as IchimokuBbConditionType) ?? DEFAULT_BUY.conditionType;
|
||||||
|
return {
|
||||||
|
mode: 'buy',
|
||||||
|
chikouDisplacement: displacement,
|
||||||
|
bbBand,
|
||||||
|
conditionType,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setIchimokuBbPairConfig(
|
||||||
|
node: LogicNode,
|
||||||
|
patch: Partial<IchimokuBbPairConfig>,
|
||||||
|
def: IndicatorPeriodDef,
|
||||||
|
): LogicNode {
|
||||||
|
if (!isIchimokuBbPairRoot(node) || !node.ichimokuBbPair) return node;
|
||||||
|
const next: IchimokuBbPairConfig = { ...node.ichimokuBbPair, ...patch };
|
||||||
|
const rebuilt = next.mode === 'buy'
|
||||||
|
? buildIchimokuBbBuyTree(def, next)
|
||||||
|
: buildIchimokuBbSellTree(def, next);
|
||||||
|
return { ...rebuilt, id: node.id };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function patchIchimokuBbPairInTree(
|
||||||
|
root: LogicNode | null,
|
||||||
|
pairNodeId: string,
|
||||||
|
patch: Partial<IchimokuBbPairConfig>,
|
||||||
|
def: IndicatorPeriodDef,
|
||||||
|
): LogicNode | null {
|
||||||
|
if (!root) return null;
|
||||||
|
if (root.id === pairNodeId && isIchimokuBbPairRoot(root)) {
|
||||||
|
return setIchimokuBbPairConfig(root, patch, def);
|
||||||
|
}
|
||||||
|
if (!root.children?.length) return root;
|
||||||
|
let changed = false;
|
||||||
|
const children = root.children.map(c => {
|
||||||
|
const patched = patchIchimokuBbPairInTree(c, pairNodeId, patch, def);
|
||||||
|
if (patched !== c) changed = true;
|
||||||
|
return patched ?? c;
|
||||||
|
});
|
||||||
|
return changed ? { ...root, children } : root;
|
||||||
|
}
|
||||||
@@ -101,8 +101,10 @@ const PLOT_LABELS: Record<string, LabelPair> = {
|
|||||||
Middle: { ko: '중간', en: 'Middle' },
|
Middle: { ko: '중간', en: 'Middle' },
|
||||||
Basis: { ko: '기준', en: 'Basis' },
|
Basis: { ko: '기준', en: 'Basis' },
|
||||||
중앙값: { ko: '중앙값', en: 'Basis' },
|
중앙값: { ko: '중앙값', en: 'Basis' },
|
||||||
어퍼: { ko: '어퍼', en: 'Upper' },
|
어퍼: { ko: '상한선', en: 'Upper' },
|
||||||
로우어: { ko: '로우어', en: 'Lower' },
|
로우어: { ko: '하한선', en: 'Lower' },
|
||||||
|
상한선: { ko: '상한선', en: 'Upper' },
|
||||||
|
하한선: { ko: '하한선', en: 'Lower' },
|
||||||
Tenkan: { ko: '전환선', en: 'Tenkan' },
|
Tenkan: { ko: '전환선', en: 'Tenkan' },
|
||||||
Kijun: { ko: '기준선', en: 'Kijun' },
|
Kijun: { ko: '기준선', en: 'Kijun' },
|
||||||
SpanA: { ko: '선행스팬1', en: 'SpanA' },
|
SpanA: { ko: '선행스팬1', en: 'SpanA' },
|
||||||
|
|||||||
@@ -359,7 +359,7 @@ export const INDICATOR_REGISTRY: IndicatorDef[] = [
|
|||||||
{ type:'MACross',name:'MA Cross', koreanName:'이동평균교차', shortName:'MACross',category:'Moving Averages', overlay:true, defaultParams:{fastLength:9, slowLength:21, src:'close'},plots:[{id:'plot0',title:'Fast', color:'#2962FF',type:'line',lineWidth:2},{id:'plot1',title:'Slow',color:'#FF6D00',type:'line',lineWidth:2}], description:'이동평균 교차 (골든크로스/데드크로스)' },
|
{ type:'MACross',name:'MA Cross', koreanName:'이동평균교차', shortName:'MACross',category:'Moving Averages', overlay:true, defaultParams:{fastLength:9, slowLength:21, src:'close'},plots:[{id:'plot0',title:'Fast', color:'#2962FF',type:'line',lineWidth:2},{id:'plot1',title:'Slow',color:'#FF6D00',type:'line',lineWidth:2}], description:'이동평균 교차 (골든크로스/데드크로스)' },
|
||||||
|
|
||||||
// ── Channels & Bands ──────────────────────────────────────────────────────
|
// ── Channels & Bands ──────────────────────────────────────────────────────
|
||||||
{ type:'BollingerBands', name:'Bollinger Bands', koreanName:'볼린저밴드', shortName:'BB', category:'Channels & Bands', overlay:true, defaultParams:{symbolMode:'main', refSymbol:'', length:20, mult:2, offset:0, src:'close', maType:'SMA'}, plots:[{id:'plot0',title:'중앙값',color:'#FF9800',type:'line',lineWidth:1},{id:'plot1',title:'어퍼',color:'#42A5F5',type:'line',lineWidth:1},{id:'plot2',title:'로우어',color:'#42A5F5',type:'line',lineWidth:1}], description:'볼린저 밴드 (SMA ± σ×곱)' },
|
{ type:'BollingerBands', name:'Bollinger Bands', koreanName:'볼린저밴드', shortName:'BB', category:'Channels & Bands', overlay:true, defaultParams:{symbolMode:'main', refSymbol:'', length:20, mult:2, offset:0, src:'close', maType:'SMA'}, plots:[{id:'plot0',title:'중앙값',color:'#FF9800',type:'line',lineWidth:1},{id:'plot1',title:'상한선',color:'#42A5F5',type:'line',lineWidth:1},{id:'plot2',title:'하한선',color:'#42A5F5',type:'line',lineWidth:1}], description:'볼린저 밴드 (SMA ± σ×곱)' },
|
||||||
{ type:'KeltnerChannels',name:'Keltner Channels', koreanName:'켈트너채널', shortName:'KC', category:'Channels & Bands', overlay:true, defaultParams:{length:20, multiplier:2}, plots:[{id:'plot0',title:'Upper', color:'#7E57C2',type:'line',lineWidth:1},{id:'plot1',title:'Middle',color:'#EF5350',type:'line',lineWidth:1},{id:'plot2',title:'Lower',color:'#7E57C2',type:'line',lineWidth:1}], description:'켈트너 채널' },
|
{ type:'KeltnerChannels',name:'Keltner Channels', koreanName:'켈트너채널', shortName:'KC', category:'Channels & Bands', overlay:true, defaultParams:{length:20, multiplier:2}, plots:[{id:'plot0',title:'Upper', color:'#7E57C2',type:'line',lineWidth:1},{id:'plot1',title:'Middle',color:'#EF5350',type:'line',lineWidth:1},{id:'plot2',title:'Lower',color:'#7E57C2',type:'line',lineWidth:1}], description:'켈트너 채널' },
|
||||||
{ type:'DonchianChannels',name:'Donchian Channels', koreanName:'돈치안채널', shortName:'DC', category:'Channels & Bands', overlay:true, defaultParams:{length:20}, plots:[{id:'plot0',title:'Upper', color:'#4CAF50',type:'line',lineWidth:1},{id:'plot1',title:'Basis',color:'#FFC107',type:'line',lineWidth:1},{id:'plot2',title:'Lower',color:'#4CAF50',type:'line',lineWidth:1}], description:'돈치안 채널' },
|
{ type:'DonchianChannels',name:'Donchian Channels', koreanName:'돈치안채널', shortName:'DC', category:'Channels & Bands', overlay:true, defaultParams:{length:20}, plots:[{id:'plot0',title:'Upper', color:'#4CAF50',type:'line',lineWidth:1},{id:'plot1',title:'Basis',color:'#FFC107',type:'line',lineWidth:1},{id:'plot2',title:'Lower',color:'#4CAF50',type:'line',lineWidth:1}], description:'돈치안 채널' },
|
||||||
{ type:'Envelope', name:'Envelope', koreanName:'엔벨로프', shortName:'Env', category:'Channels & Bands', overlay:true, defaultParams:{length:20, percent:0.1, src:'close'}, plots:[{id:'plot0',title:'Upper', color:'#009688',type:'line',lineWidth:1},{id:'plot1',title:'Basis',color:'#607D8B',type:'line',lineWidth:1},{id:'plot2',title:'Lower',color:'#009688',type:'line',lineWidth:1}], description:'엔벨로프 채널' },
|
{ type:'Envelope', name:'Envelope', koreanName:'엔벨로프', shortName:'Env', category:'Channels & Bands', overlay:true, defaultParams:{length:20, percent:0.1, src:'close'}, plots:[{id:'plot0',title:'Upper', color:'#009688',type:'line',lineWidth:1},{id:'plot1',title:'Basis',color:'#607D8B',type:'line',lineWidth:1},{id:'plot2',title:'Lower',color:'#009688',type:'line',lineWidth:1}], description:'엔벨로프 채널' },
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export type StableStrategyId =
|
|||||||
| 'MA_TREND'
|
| 'MA_TREND'
|
||||||
| 'ICHIMOKU_TREND'
|
| 'ICHIMOKU_TREND'
|
||||||
| 'ICHIMOKU_SANYAKU'
|
| 'ICHIMOKU_SANYAKU'
|
||||||
|
| 'ICHIMOKU_PERFECT'
|
||||||
| 'MACD_MOMENTUM'
|
| 'MACD_MOMENTUM'
|
||||||
| 'BOLLINGER_RANGE';
|
| 'BOLLINGER_RANGE';
|
||||||
|
|
||||||
@@ -57,6 +58,8 @@ export const ICHIMOKU_TREND_BUY = 'ICHIMOKU_TREND_BUY';
|
|||||||
export const ICHIMOKU_TREND_SELL = 'ICHIMOKU_TREND_SELL';
|
export const ICHIMOKU_TREND_SELL = 'ICHIMOKU_TREND_SELL';
|
||||||
export const ICHIMOKU_SANYAKU_BUY = 'ICHIMOKU_SANYAKU_BUY';
|
export const ICHIMOKU_SANYAKU_BUY = 'ICHIMOKU_SANYAKU_BUY';
|
||||||
export const ICHIMOKU_SANYAKU_SELL = 'ICHIMOKU_SANYAKU_SELL';
|
export const ICHIMOKU_SANYAKU_SELL = 'ICHIMOKU_SANYAKU_SELL';
|
||||||
|
export const ICHIMOKU_PERFECT_BUY = 'ICHIMOKU_PERFECT_BUY';
|
||||||
|
export const ICHIMOKU_PERFECT_SELL = 'ICHIMOKU_PERFECT_SELL';
|
||||||
export const MACD_MOMENTUM_BUY = 'MACD_MOMENTUM_BUY';
|
export const MACD_MOMENTUM_BUY = 'MACD_MOMENTUM_BUY';
|
||||||
export const MACD_MOMENTUM_SELL = 'MACD_MOMENTUM_SELL';
|
export const MACD_MOMENTUM_SELL = 'MACD_MOMENTUM_SELL';
|
||||||
export const BOLLINGER_RANGE_BUY = 'BOLLINGER_RANGE_BUY';
|
export const BOLLINGER_RANGE_BUY = 'BOLLINGER_RANGE_BUY';
|
||||||
@@ -75,6 +78,8 @@ const VALUE_TO_META: Record<string, { id: StableStrategyId; mode: 'buy' | 'sell'
|
|||||||
[ICHIMOKU_TREND_SELL]: { id: 'ICHIMOKU_TREND', mode: 'sell' },
|
[ICHIMOKU_TREND_SELL]: { id: 'ICHIMOKU_TREND', mode: 'sell' },
|
||||||
[ICHIMOKU_SANYAKU_BUY]: { id: 'ICHIMOKU_SANYAKU', mode: 'buy' },
|
[ICHIMOKU_SANYAKU_BUY]: { id: 'ICHIMOKU_SANYAKU', mode: 'buy' },
|
||||||
[ICHIMOKU_SANYAKU_SELL]: { id: 'ICHIMOKU_SANYAKU', mode: 'sell' },
|
[ICHIMOKU_SANYAKU_SELL]: { id: 'ICHIMOKU_SANYAKU', mode: 'sell' },
|
||||||
|
[ICHIMOKU_PERFECT_BUY]: { id: 'ICHIMOKU_PERFECT', mode: 'buy' },
|
||||||
|
[ICHIMOKU_PERFECT_SELL]: { id: 'ICHIMOKU_PERFECT', mode: 'sell' },
|
||||||
[MACD_MOMENTUM_BUY]: { id: 'MACD_MOMENTUM', mode: 'buy' },
|
[MACD_MOMENTUM_BUY]: { id: 'MACD_MOMENTUM', mode: 'buy' },
|
||||||
[MACD_MOMENTUM_SELL]: { id: 'MACD_MOMENTUM', mode: 'sell' },
|
[MACD_MOMENTUM_SELL]: { id: 'MACD_MOMENTUM', mode: 'sell' },
|
||||||
[BOLLINGER_RANGE_BUY]: { id: 'BOLLINGER_RANGE', mode: 'buy' },
|
[BOLLINGER_RANGE_BUY]: { id: 'BOLLINGER_RANGE', mode: 'buy' },
|
||||||
@@ -151,6 +156,16 @@ export const STABLE_STRATEGY_PALETTE_ITEMS: StableStrategyPaletteMeta[] = [
|
|||||||
desc: '전환·기준·구름 하향 돌파(현재봉 1회) — 유지 조건 없음',
|
desc: '전환·기준·구름 하향 돌파(현재봉 1회) — 유지 조건 없음',
|
||||||
periodHint: '9 / 26 / 52',
|
periodHint: '9 / 26 / 52',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
value: ICHIMOKU_PERFECT_BUY, label: '일목 5원소 매수', strategyId: 'ICHIMOKU_PERFECT', mode: 'buy',
|
||||||
|
desc: '괘·주가·구름·후행 4중 AND — 전환>기준 + 구름 위 + 양운 + 후행>26봉전 종가',
|
||||||
|
periodHint: '9 / 26 / 52',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: ICHIMOKU_PERFECT_SELL, label: '일목 5원소 매도', strategyId: 'ICHIMOKU_PERFECT', mode: 'sell',
|
||||||
|
desc: '괘·주가·구름·후행 4중 AND — 전환<기준 + 구름 아래 + 음운 + 후행<26봉전 종가',
|
||||||
|
periodHint: '9 / 26 / 52',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
value: MACD_MOMENTUM_BUY, label: 'MACD 모멘텀 매수', strategyId: 'MACD_MOMENTUM', mode: 'buy',
|
value: MACD_MOMENTUM_BUY, label: 'MACD 모멘텀 매수', strategyId: 'MACD_MOMENTUM', mode: 'buy',
|
||||||
desc: 'MACD 상향돌파 + EMA60 위 + ADX',
|
desc: 'MACD 상향돌파 + EMA60 위 + ADX',
|
||||||
@@ -472,6 +487,34 @@ function buildIchimokuSanyakuSell(def: IndicatorPeriodDef, level: StableStrategy
|
|||||||
return wrapOrRoot(branches, pairMeta('ICHIMOKU_SANYAKU', 'sell', level));
|
return wrapOrRoot(branches, pairMeta('ICHIMOKU_SANYAKU', 'sell', level));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 일목 5원소 입체 매매 — ta4j PerfectIchimokuStrategy 와 동일한 4중 AND 필터.
|
||||||
|
* 괘(전환/기준) · 주가(구름 위/아래) · 구름(양운/음운) · 후행(26봉전 종가 대비).
|
||||||
|
*/
|
||||||
|
function buildIchimokuPerfectBuy(def: IndicatorPeriodDef, level: StableStrategyFilterLevel): LogicNode {
|
||||||
|
const nodes: LogicNode[] = [
|
||||||
|
makeCondition('ICHIMOKU', 'GT', 'CONVERSION_LINE', 'BASE_LINE', def),
|
||||||
|
makeCondition('ICHIMOKU', 'ABOVE_CLOUD', 'CLOSE_PRICE', 'NONE', def),
|
||||||
|
];
|
||||||
|
if (level !== 'relaxed') {
|
||||||
|
nodes.push(makeCondition('ICHIMOKU', 'SPAN1_GT_SPAN2', 'LEADING_SPAN1', 'LEADING_SPAN2', def));
|
||||||
|
nodes.push(makeCondition('ICHIMOKU', 'LAGGING_GT_PRICE', 'LAGGING_SPAN', 'CLOSE_PRICE', def));
|
||||||
|
}
|
||||||
|
return wrapAnd(nodes, pairMeta('ICHIMOKU_PERFECT', 'buy', level));
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildIchimokuPerfectSell(def: IndicatorPeriodDef, level: StableStrategyFilterLevel): LogicNode {
|
||||||
|
const nodes: LogicNode[] = [
|
||||||
|
makeCondition('ICHIMOKU', 'LT', 'CONVERSION_LINE', 'BASE_LINE', def),
|
||||||
|
makeCondition('ICHIMOKU', 'BELOW_CLOUD', 'CLOSE_PRICE', 'NONE', def),
|
||||||
|
];
|
||||||
|
if (level !== 'relaxed') {
|
||||||
|
nodes.push(makeCondition('ICHIMOKU', 'SPAN1_LT_SPAN2', 'LEADING_SPAN1', 'LEADING_SPAN2', def));
|
||||||
|
nodes.push(makeCondition('ICHIMOKU', 'LAGGING_LT_PRICE', 'LAGGING_SPAN', 'CLOSE_PRICE', def));
|
||||||
|
}
|
||||||
|
return wrapAnd(nodes, pairMeta('ICHIMOKU_PERFECT', 'sell', level));
|
||||||
|
}
|
||||||
|
|
||||||
function buildMacdMomentumBuy(def: IndicatorPeriodDef, level: StableStrategyFilterLevel): LogicNode {
|
function buildMacdMomentumBuy(def: IndicatorPeriodDef, level: StableStrategyFilterLevel): LogicNode {
|
||||||
const nodes: LogicNode[] = [
|
const nodes: LogicNode[] = [
|
||||||
makeCondition('MACD', 'CROSS_UP', 'MACD_LINE', 'SIGNAL_LINE', def),
|
makeCondition('MACD', 'CROSS_UP', 'MACD_LINE', 'SIGNAL_LINE', def),
|
||||||
@@ -522,6 +565,7 @@ const BUILDERS: Record<StableStrategyId, {
|
|||||||
MA_TREND: { buy: buildMaTrendBuy, sell: buildMaTrendSell },
|
MA_TREND: { buy: buildMaTrendBuy, sell: buildMaTrendSell },
|
||||||
ICHIMOKU_TREND: { buy: buildIchimokuTrendBuy, sell: buildIchimokuTrendSell },
|
ICHIMOKU_TREND: { buy: buildIchimokuTrendBuy, sell: buildIchimokuTrendSell },
|
||||||
ICHIMOKU_SANYAKU: { buy: buildIchimokuSanyakuBuy, sell: buildIchimokuSanyakuSell },
|
ICHIMOKU_SANYAKU: { buy: buildIchimokuSanyakuBuy, sell: buildIchimokuSanyakuSell },
|
||||||
|
ICHIMOKU_PERFECT: { buy: buildIchimokuPerfectBuy, sell: buildIchimokuPerfectSell },
|
||||||
MACD_MOMENTUM: { buy: buildMacdMomentumBuy, sell: buildMacdMomentumSell },
|
MACD_MOMENTUM: { buy: buildMacdMomentumBuy, sell: buildMacdMomentumSell },
|
||||||
BOLLINGER_RANGE: { buy: buildBollingerRangeBuy, sell: buildBollingerRangeSell },
|
BOLLINGER_RANGE: { buy: buildBollingerRangeBuy, sell: buildBollingerRangeSell },
|
||||||
};
|
};
|
||||||
@@ -559,7 +603,8 @@ export function stableStrategySummaryText(
|
|||||||
CROSS_UP: '상향돌파', CROSS_DOWN: '하향돌파',
|
CROSS_UP: '상향돌파', CROSS_DOWN: '하향돌파',
|
||||||
GT: '>', LT: '<', GTE: '≥', LTE: '≤',
|
GT: '>', LT: '<', GTE: '≥', LTE: '≤',
|
||||||
ABOVE_CLOUD: '구름 위', BELOW_CLOUD: '구름 아래',
|
ABOVE_CLOUD: '구름 위', BELOW_CLOUD: '구름 아래',
|
||||||
SPAN1_GT_SPAN2: '양운(선행1>2)', LAGGING_GT_PRICE: '후행>26봉전 종가',
|
SPAN1_GT_SPAN2: '양운(선행1>2)', SPAN1_LT_SPAN2: '음운(선행1<2)',
|
||||||
|
LAGGING_GT_PRICE: '후행>26봉전 종가', LAGGING_LT_PRICE: '후행<26봉전 종가',
|
||||||
LAGGING_ABOVE_PRIOR_CLOUD: '후행>26봉전 구름', VOLUME_GT_MA_RATIO: '거래량≥MA×비율',
|
LAGGING_ABOVE_PRIOR_CLOUD: '후행>26봉전 구름', VOLUME_GT_MA_RATIO: '거래량≥MA×비율',
|
||||||
};
|
};
|
||||||
const fmt = (n: LogicNode): string => {
|
const fmt = (n: LogicNode): string => {
|
||||||
@@ -595,6 +640,10 @@ export function stableStrategyFilterSummary(
|
|||||||
else if (level === 'balanced') parts.push(`돌파1회·필터N${w}·(전환>기준 OR 양운)`);
|
else if (level === 'balanced') parts.push(`돌파1회·필터N${w}·(전환>기준 OR 양운)`);
|
||||||
else parts.push('돌파1회·종가>전환');
|
else parts.push('돌파1회·종가>전환');
|
||||||
}
|
}
|
||||||
|
if (strategyId === 'ICHIMOKU_PERFECT') {
|
||||||
|
if (level === 'relaxed') parts.push('괘·주가(구름) 2조건만');
|
||||||
|
else parts.push('괘·주가·구름·후행 4중 AND');
|
||||||
|
}
|
||||||
if (strategyId === 'MACD_MOMENTUM' && level === 'relaxed') parts.push('EMA60·ADX 없음');
|
if (strategyId === 'MACD_MOMENTUM' && level === 'relaxed') parts.push('EMA60·ADX 없음');
|
||||||
if (strategyId === 'MA_TREND' && level === 'relaxed') parts.push('ADX 없음');
|
if (strategyId === 'MA_TREND' && level === 'relaxed') parts.push('ADX 없음');
|
||||||
return parts.join(', ');
|
return parts.join(', ');
|
||||||
@@ -639,6 +688,12 @@ export function inferStableStrategyFilterLevel(node: LogicNode): StableStrategyF
|
|||||||
if (hasLagging && (hasCloudBreak || hasChikouCross)) return 'balanced';
|
if (hasLagging && (hasCloudBreak || hasChikouCross)) return 'balanced';
|
||||||
if (hasCloudBreak || hasChikouCross) return 'relaxed';
|
if (hasCloudBreak || hasChikouCross) return 'relaxed';
|
||||||
}
|
}
|
||||||
|
if (id === 'ICHIMOKU_PERFECT') {
|
||||||
|
const hasSpan = conds.some(c => c.conditionType === 'SPAN1_GT_SPAN2' || c.conditionType === 'SPAN1_LT_SPAN2');
|
||||||
|
const hasLagging = conds.some(c => c.conditionType === 'LAGGING_GT_PRICE' || c.conditionType === 'LAGGING_LT_PRICE');
|
||||||
|
if (!hasSpan && !hasLagging) return 'relaxed';
|
||||||
|
return 'balanced';
|
||||||
|
}
|
||||||
if (id === 'MACD_MOMENTUM' && !conds.some(c => c.rightField === emaField(60))) return 'relaxed';
|
if (id === 'MACD_MOMENTUM' && !conds.some(c => c.rightField === emaField(60))) return 'relaxed';
|
||||||
if (id === 'MA_TREND' && !conds.some(c => c.indicatorType === 'ADX')) return 'relaxed';
|
if (id === 'MA_TREND' && !conds.some(c => c.indicatorType === 'ADX')) return 'relaxed';
|
||||||
if (id === 'BOLLINGER_RANGE') {
|
if (id === 'BOLLINGER_RANGE') {
|
||||||
|
|||||||
@@ -44,6 +44,14 @@ import {
|
|||||||
inferInflection33FilterLevel,
|
inferInflection33FilterLevel,
|
||||||
inflection33FilterLevelLabel,
|
inflection33FilterLevelLabel,
|
||||||
} from '../utils/inflection33Strategy';
|
} from '../utils/inflection33Strategy';
|
||||||
|
import {
|
||||||
|
buildIchimokuBbTree,
|
||||||
|
isIchimokuBbPairRoot,
|
||||||
|
isIchimokuBbPaletteValue,
|
||||||
|
ichimokuBbDisplayName,
|
||||||
|
ichimokuBbSummaryText,
|
||||||
|
inferIchimokuBbConfig,
|
||||||
|
} from '../utils/ichimokuBbStrategy';
|
||||||
import {
|
import {
|
||||||
buildStableStrategyTree,
|
buildStableStrategyTree,
|
||||||
isStableStrategyPairRoot,
|
isStableStrategyPairRoot,
|
||||||
@@ -704,18 +712,18 @@ function buildFieldOptsForSlot(
|
|||||||
return slot === 'right'
|
return slot === 'right'
|
||||||
? [
|
? [
|
||||||
NONE,
|
NONE,
|
||||||
{ value: 'UPPER_BAND', label: `상단밴드(${DEF.bbPeriod}일, σ${DEF.bbStdDev})` },
|
{ value: 'UPPER_BAND', label: `상한선(${DEF.bbPeriod}일, σ${DEF.bbStdDev})` },
|
||||||
{ value: 'MIDDLE_BAND', label: `중심선(${DEF.bbPeriod}일)` },
|
{ value: 'MIDDLE_BAND', label: `중앙값(${DEF.bbPeriod}일)` },
|
||||||
{ value: 'LOWER_BAND', label: `하단밴드(${DEF.bbPeriod}일, σ${DEF.bbStdDev})` },
|
{ value: 'LOWER_BAND', label: `하한선(${DEF.bbPeriod}일, σ${DEF.bbStdDev})` },
|
||||||
{ value: 'CLOSE_PRICE', label: '종가' },
|
{ value: 'CLOSE_PRICE', label: '종가' },
|
||||||
{ value: 'OPEN_PRICE', label: '시가' },
|
{ value: 'OPEN_PRICE', label: '시가' },
|
||||||
{ value: 'HIGH_PRICE', label: '고가' },
|
{ value: 'HIGH_PRICE', label: '고가' },
|
||||||
{ value: 'LOW_PRICE', label: '저가' },
|
{ value: 'LOW_PRICE', label: '저가' },
|
||||||
]
|
]
|
||||||
: [
|
: [
|
||||||
{ value: 'UPPER_BAND', label: `상단밴드(${DEF.bbPeriod}일, σ${DEF.bbStdDev})` },
|
{ value: 'UPPER_BAND', label: `상한선(${DEF.bbPeriod}일, σ${DEF.bbStdDev})` },
|
||||||
{ value: 'MIDDLE_BAND', label: `중심선(${DEF.bbPeriod}일)` },
|
{ value: 'MIDDLE_BAND', label: `중앙값(${DEF.bbPeriod}일)` },
|
||||||
{ value: 'LOWER_BAND', label: `하단밴드(${DEF.bbPeriod}일, σ${DEF.bbStdDev})` },
|
{ value: 'LOWER_BAND', label: `하한선(${DEF.bbPeriod}일, σ${DEF.bbStdDev})` },
|
||||||
{ value: 'CLOSE_PRICE', label: '종가' },
|
{ value: 'CLOSE_PRICE', label: '종가' },
|
||||||
{ value: 'OPEN_PRICE', label: '시가' },
|
{ value: 'OPEN_PRICE', label: '시가' },
|
||||||
{ value: 'HIGH_PRICE', label: '고가' },
|
{ value: 'HIGH_PRICE', label: '고가' },
|
||||||
@@ -985,6 +993,10 @@ export const nodeToText = (node: LogicNode, DEF: DefType = DEF_DEFAULTS): string
|
|||||||
const level = inferInflection33FilterLevel(node);
|
const level = inferInflection33FilterLevel(node);
|
||||||
return `${inflection33DisplayName(mode, period)} [필터:${inflection33FilterLevelLabel(level)}]\n${inflection33SummaryText(mode, period, level)}`;
|
return `${inflection33DisplayName(mode, period)} [필터:${inflection33FilterLevelLabel(level)}]\n${inflection33SummaryText(mode, period, level)}`;
|
||||||
}
|
}
|
||||||
|
if (isIchimokuBbPairRoot(node) && node.ichimokuBbPair?.mode === 'buy') {
|
||||||
|
const cfg = inferIchimokuBbConfig(node);
|
||||||
|
return `${ichimokuBbDisplayName(cfg.mode, cfg.chikouDisplacement)}\n${ichimokuBbSummaryText(cfg)}`;
|
||||||
|
}
|
||||||
if (isPriceExtremeBreakoutPairRoot(node) && node.priceExtremePair) {
|
if (isPriceExtremeBreakoutPairRoot(node) && node.priceExtremePair) {
|
||||||
const { mode, shortPeriod, longPeriod } = node.priceExtremePair;
|
const { mode, shortPeriod, longPeriod } = node.priceExtremePair;
|
||||||
const level = inferPriceExtremeFilterLevel(node);
|
const level = inferPriceExtremeFilterLevel(node);
|
||||||
@@ -1004,6 +1016,10 @@ export const nodeToText = (node: LogicNode, DEF: DefType = DEF_DEFAULTS): string
|
|||||||
const level = inferInflection33FilterLevel(node);
|
const level = inferInflection33FilterLevel(node);
|
||||||
return `${inflection33DisplayName(mode, period)} [필터:${inflection33FilterLevelLabel(level)}]\n${inflection33SummaryText(mode, period, level)}`;
|
return `${inflection33DisplayName(mode, period)} [필터:${inflection33FilterLevelLabel(level)}]\n${inflection33SummaryText(mode, period, level)}`;
|
||||||
}
|
}
|
||||||
|
if (isIchimokuBbPairRoot(node) && node.ichimokuBbPair?.mode === 'sell') {
|
||||||
|
const cfg = inferIchimokuBbConfig(node);
|
||||||
|
return `${ichimokuBbDisplayName(cfg.mode, cfg.chikouDisplacement)}\n${ichimokuBbSummaryText(cfg)}`;
|
||||||
|
}
|
||||||
if (isStochOverboughtPairRoot(node)) {
|
if (isStochOverboughtPairRoot(node)) {
|
||||||
const sec = node.stochPair!.secondaryIndicatorType;
|
const sec = node.stochPair!.secondaryIndicatorType;
|
||||||
return `${stochPairDisplayName(sec)}\n${stochPairSummaryText(sec)}`;
|
return `${stochPairDisplayName(sec)}\n${stochPairSummaryText(sec)}`;
|
||||||
@@ -1527,6 +1543,8 @@ export type MakeNodeOptions = {
|
|||||||
priceExtremeBreakout?: boolean;
|
priceExtremeBreakout?: boolean;
|
||||||
/** 33변곡 EMA+채널 복합 */
|
/** 33변곡 EMA+채널 복합 */
|
||||||
inflection33?: boolean;
|
inflection33?: boolean;
|
||||||
|
/** 일목 후행×볼린저 복합 */
|
||||||
|
ichimokuBb?: boolean;
|
||||||
/** 추천 안정형 전략 복합 */
|
/** 추천 안정형 전략 복합 */
|
||||||
stableStrategy?: boolean;
|
stableStrategy?: boolean;
|
||||||
};
|
};
|
||||||
@@ -1537,6 +1555,7 @@ export function makeNodeOptionsFromPalette(data: {
|
|||||||
stochPair?: boolean;
|
stochPair?: boolean;
|
||||||
priceExtremeBreakout?: boolean;
|
priceExtremeBreakout?: boolean;
|
||||||
inflection33?: boolean;
|
inflection33?: boolean;
|
||||||
|
ichimokuBb?: boolean;
|
||||||
stableStrategy?: boolean;
|
stableStrategy?: boolean;
|
||||||
secondaryIndicator?: string;
|
secondaryIndicator?: string;
|
||||||
period?: number;
|
period?: number;
|
||||||
@@ -1558,6 +1577,9 @@ export function makeNodeOptionsFromPalette(data: {
|
|||||||
if (data.inflection33 || (data.value && isInflection33PaletteValue(data.value))) {
|
if (data.inflection33 || (data.value && isInflection33PaletteValue(data.value))) {
|
||||||
return { inflection33: true };
|
return { inflection33: true };
|
||||||
}
|
}
|
||||||
|
if (data.ichimokuBb || (data.value && isIchimokuBbPaletteValue(data.value))) {
|
||||||
|
return { ichimokuBb: true };
|
||||||
|
}
|
||||||
if (data.stableStrategy || (data.value && isStableStrategyPaletteValue(data.value))) {
|
if (data.stableStrategy || (data.value && isStableStrategyPaletteValue(data.value))) {
|
||||||
return { stableStrategy: true };
|
return { stableStrategy: true };
|
||||||
}
|
}
|
||||||
@@ -1590,6 +1612,10 @@ export const makeNode = (
|
|||||||
const tree = buildInflection33Tree(value, DEF);
|
const tree = buildInflection33Tree(value, DEF);
|
||||||
if (tree) return tree;
|
if (tree) return tree;
|
||||||
}
|
}
|
||||||
|
if (options?.ichimokuBb || isIchimokuBbPaletteValue(value)) {
|
||||||
|
const tree = buildIchimokuBbTree(value, DEF);
|
||||||
|
if (tree) return tree;
|
||||||
|
}
|
||||||
if (options?.stableStrategy || isStableStrategyPaletteValue(value)) {
|
if (options?.stableStrategy || isStableStrategyPaletteValue(value)) {
|
||||||
const tree = buildStableStrategyTree(value, DEF);
|
const tree = buildStableStrategyTree(value, DEF);
|
||||||
if (tree) return tree;
|
if (tree) return tree;
|
||||||
|
|||||||
@@ -48,6 +48,14 @@ export type StrategyFlowNodeData = {
|
|||||||
onUpdatePriceExtremeFilterLevel?: (nodeId: string, filterLevel: 'strict' | 'balanced' | 'relaxed') => void;
|
onUpdatePriceExtremeFilterLevel?: (nodeId: string, filterLevel: 'strict' | 'balanced' | 'relaxed') => void;
|
||||||
/** 33변곡 EMA+채널 복합 — 필터 강도 변경 */
|
/** 33변곡 EMA+채널 복합 — 필터 강도 변경 */
|
||||||
onUpdateInflection33FilterLevel?: (nodeId: string, filterLevel: 'strict' | 'balanced' | 'relaxed') => void;
|
onUpdateInflection33FilterLevel?: (nodeId: string, filterLevel: 'strict' | 'balanced' | 'relaxed') => void;
|
||||||
|
onUpdateIchimokuBbConfig?: (
|
||||||
|
nodeId: string,
|
||||||
|
patch: Partial<{
|
||||||
|
chikouDisplacement: number;
|
||||||
|
bbBand: 'UPPER_BAND' | 'MIDDLE_BAND' | 'LOWER_BAND';
|
||||||
|
conditionType: 'CROSS_UP' | 'CROSS_DOWN' | 'GT' | 'LT';
|
||||||
|
}>,
|
||||||
|
) => void;
|
||||||
/** 추천 안정형 전략 — 필터 강도 변경 */
|
/** 추천 안정형 전략 — 필터 강도 변경 */
|
||||||
onUpdateStableStrategyFilterLevel?: (nodeId: string, filterLevel: 'strict' | 'balanced' | 'relaxed') => void;
|
onUpdateStableStrategyFilterLevel?: (nodeId: string, filterLevel: 'strict' | 'balanced' | 'relaxed') => void;
|
||||||
/** AND ↔ OR 전환 */
|
/** AND ↔ OR 전환 */
|
||||||
@@ -482,7 +490,7 @@ function layoutTree(
|
|||||||
signalTab: 'buy' | 'sell',
|
signalTab: 'buy' | 'sell',
|
||||||
callbacks: Pick<
|
callbacks: Pick<
|
||||||
StrategyFlowNodeData,
|
StrategyFlowNodeData,
|
||||||
'onDelete' | 'onUpdateCondition' | 'onReplaceLogicNode' | 'onUpdateStochPairSecondary' | 'onUpdatePriceExtremeFilterLevel' | 'onUpdateInflection33FilterLevel' | 'onUpdateStableStrategyFilterLevel' | 'onChangeLogicGateType'
|
'onDelete' | 'onUpdateCondition' | 'onReplaceLogicNode' | 'onUpdateStochPairSecondary' | 'onUpdatePriceExtremeFilterLevel' | 'onUpdateInflection33FilterLevel' | 'onUpdateIchimokuBbConfig' | 'onUpdateStableStrategyFilterLevel' | 'onChangeLogicGateType'
|
||||||
| 'onDropTarget' | 'onDragOverTarget' | 'onDragLeaveTarget'
|
| 'onDropTarget' | 'onDragOverTarget' | 'onDragLeaveTarget'
|
||||||
>,
|
>,
|
||||||
dragOverId: string | null,
|
dragOverId: string | null,
|
||||||
@@ -508,6 +516,7 @@ function layoutTree(
|
|||||||
onUpdateStochPairSecondary: callbacks.onUpdateStochPairSecondary,
|
onUpdateStochPairSecondary: callbacks.onUpdateStochPairSecondary,
|
||||||
onUpdatePriceExtremeFilterLevel: callbacks.onUpdatePriceExtremeFilterLevel,
|
onUpdatePriceExtremeFilterLevel: callbacks.onUpdatePriceExtremeFilterLevel,
|
||||||
onUpdateInflection33FilterLevel: callbacks.onUpdateInflection33FilterLevel,
|
onUpdateInflection33FilterLevel: callbacks.onUpdateInflection33FilterLevel,
|
||||||
|
onUpdateIchimokuBbConfig: callbacks.onUpdateIchimokuBbConfig,
|
||||||
onUpdateStableStrategyFilterLevel: callbacks.onUpdateStableStrategyFilterLevel,
|
onUpdateStableStrategyFilterLevel: callbacks.onUpdateStableStrategyFilterLevel,
|
||||||
onChangeLogicGateType: callbacks.onChangeLogicGateType,
|
onChangeLogicGateType: callbacks.onChangeLogicGateType,
|
||||||
onDropTarget: callbacks.onDropTarget,
|
onDropTarget: callbacks.onDropTarget,
|
||||||
@@ -638,7 +647,7 @@ function appendOrphanFlowNodes(
|
|||||||
signalTab: 'buy' | 'sell',
|
signalTab: 'buy' | 'sell',
|
||||||
callbacks: Pick<
|
callbacks: Pick<
|
||||||
StrategyFlowNodeData,
|
StrategyFlowNodeData,
|
||||||
'onDelete' | 'onUpdateCondition' | 'onReplaceLogicNode' | 'onUpdateStochPairSecondary' | 'onUpdatePriceExtremeFilterLevel' | 'onUpdateInflection33FilterLevel' | 'onUpdateStableStrategyFilterLevel' | 'onChangeLogicGateType'
|
'onDelete' | 'onUpdateCondition' | 'onReplaceLogicNode' | 'onUpdateStochPairSecondary' | 'onUpdatePriceExtremeFilterLevel' | 'onUpdateInflection33FilterLevel' | 'onUpdateIchimokuBbConfig' | 'onUpdateStableStrategyFilterLevel' | 'onChangeLogicGateType'
|
||||||
| 'onDropTarget' | 'onDragOverTarget' | 'onDragLeaveTarget'
|
| 'onDropTarget' | 'onDragOverTarget' | 'onDragLeaveTarget'
|
||||||
>,
|
>,
|
||||||
): void {
|
): void {
|
||||||
@@ -664,6 +673,7 @@ function appendOrphanFlowNodes(
|
|||||||
onUpdateStochPairSecondary: callbacks.onUpdateStochPairSecondary,
|
onUpdateStochPairSecondary: callbacks.onUpdateStochPairSecondary,
|
||||||
onUpdatePriceExtremeFilterLevel: callbacks.onUpdatePriceExtremeFilterLevel,
|
onUpdatePriceExtremeFilterLevel: callbacks.onUpdatePriceExtremeFilterLevel,
|
||||||
onUpdateInflection33FilterLevel: callbacks.onUpdateInflection33FilterLevel,
|
onUpdateInflection33FilterLevel: callbacks.onUpdateInflection33FilterLevel,
|
||||||
|
onUpdateIchimokuBbConfig: callbacks.onUpdateIchimokuBbConfig,
|
||||||
onUpdateStableStrategyFilterLevel: callbacks.onUpdateStableStrategyFilterLevel,
|
onUpdateStableStrategyFilterLevel: callbacks.onUpdateStableStrategyFilterLevel,
|
||||||
onChangeLogicGateType: callbacks.onChangeLogicGateType,
|
onChangeLogicGateType: callbacks.onChangeLogicGateType,
|
||||||
onDropTarget: callbacks.onDropTarget,
|
onDropTarget: callbacks.onDropTarget,
|
||||||
@@ -692,6 +702,7 @@ export function logicNodeToFlow(
|
|||||||
| 'onUpdateStochPairSecondary'
|
| 'onUpdateStochPairSecondary'
|
||||||
| 'onUpdatePriceExtremeFilterLevel'
|
| 'onUpdatePriceExtremeFilterLevel'
|
||||||
| 'onUpdateInflection33FilterLevel'
|
| 'onUpdateInflection33FilterLevel'
|
||||||
|
| 'onUpdateIchimokuBbConfig'
|
||||||
| 'onUpdateStableStrategyFilterLevel'
|
| 'onUpdateStableStrategyFilterLevel'
|
||||||
| 'onDropTarget'
|
| 'onDropTarget'
|
||||||
| 'onDragOverTarget'
|
| 'onDragOverTarget'
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ import {
|
|||||||
INFLECTION_33_SELL_VALUE,
|
INFLECTION_33_SELL_VALUE,
|
||||||
inflection33PaletteDesc,
|
inflection33PaletteDesc,
|
||||||
} from './inflection33Strategy';
|
} from './inflection33Strategy';
|
||||||
|
import {
|
||||||
|
ICHIMOKU_BB_BUY_VALUE,
|
||||||
|
ICHIMOKU_BB_SELL_VALUE,
|
||||||
|
ichimokuBbPaletteDesc,
|
||||||
|
} from './ichimokuBbStrategy';
|
||||||
import {
|
import {
|
||||||
STABLE_STRATEGY_PALETTE_ITEMS,
|
STABLE_STRATEGY_PALETTE_ITEMS,
|
||||||
} from './stableStrategyPairs';
|
} from './stableStrategyPairs';
|
||||||
@@ -97,6 +102,20 @@ export const DEFAULT_COMPOSITE_ITEMS: Omit<PaletteItem, 'id' | 'kind'>[] = [
|
|||||||
builtIn: true,
|
builtIn: true,
|
||||||
period: 33,
|
period: 33,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
value: ICHIMOKU_BB_BUY_VALUE,
|
||||||
|
label: '일목×BB 매수',
|
||||||
|
desc: ichimokuBbPaletteDesc(ICHIMOKU_BB_BUY_VALUE),
|
||||||
|
builtIn: true,
|
||||||
|
period: 26,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: ICHIMOKU_BB_SELL_VALUE,
|
||||||
|
label: '일목×BB 매도',
|
||||||
|
desc: ichimokuBbPaletteDesc(ICHIMOKU_BB_SELL_VALUE),
|
||||||
|
builtIn: true,
|
||||||
|
period: 26,
|
||||||
|
},
|
||||||
...STABLE_STRATEGY_PALETTE_ITEMS.map(i => ({
|
...STABLE_STRATEGY_PALETTE_ITEMS.map(i => ({
|
||||||
value: i.value,
|
value: i.value,
|
||||||
label: i.label,
|
label: i.label,
|
||||||
|
|||||||
@@ -73,6 +73,13 @@ export interface LogicNode {
|
|||||||
mode: 'buy' | 'sell';
|
mode: 'buy' | 'sell';
|
||||||
filterLevel?: 'strict' | 'balanced' | 'relaxed';
|
filterLevel?: 'strict' | 'balanced' | 'relaxed';
|
||||||
};
|
};
|
||||||
|
/** 일목 후행스팬 × 볼린저밴드 복합 */
|
||||||
|
ichimokuBbPair?: {
|
||||||
|
mode: 'buy' | 'sell';
|
||||||
|
chikouDisplacement: number;
|
||||||
|
bbBand: 'UPPER_BAND' | 'MIDDLE_BAND' | 'LOWER_BAND';
|
||||||
|
conditionType: 'CROSS_UP' | 'CROSS_DOWN' | 'GT' | 'LT';
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StrategyDSLDto {
|
export interface StrategyDSLDto {
|
||||||
@@ -130,6 +137,7 @@ export const INDICATOR_CONDITIONS: Record<string, string[]> = {
|
|||||||
'LAGGING_GT_PRICE', 'LAGGING_LT_PRICE', 'LAGGING_ABOVE_PRIOR_CLOUD',
|
'LAGGING_GT_PRICE', 'LAGGING_LT_PRICE', 'LAGGING_ABOVE_PRIOR_CLOUD',
|
||||||
'CHIKOU_CROSS_ABOVE_PRIOR_CLOUD',
|
'CHIKOU_CROSS_ABOVE_PRIOR_CLOUD',
|
||||||
],
|
],
|
||||||
|
ICHIMOKU_BB: ['GT', 'LT', 'GTE', 'LTE', 'CROSS_UP', 'CROSS_DOWN'],
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── 지표별 leftField 옵션 ───────────────────────────────────────────────────
|
// ── 지표별 leftField 옵션 ───────────────────────────────────────────────────
|
||||||
@@ -167,8 +175,8 @@ export const LEFT_FIELD_OPTIONS: Record<string, FieldOption[]> = {
|
|||||||
],
|
],
|
||||||
BOLLINGER: [
|
BOLLINGER: [
|
||||||
{ value: 'CLOSE_PRICE', label: '종가' },
|
{ value: 'CLOSE_PRICE', label: '종가' },
|
||||||
{ value: 'UPPER', label: '상단밴드' }, { value: 'MIDDLE', label: '중단밴드' },
|
{ value: 'UPPER', label: '상한선' }, { value: 'MIDDLE', label: '중앙값' },
|
||||||
{ value: 'LOWER', label: '하단밴드' },
|
{ value: 'LOWER', label: '하한선' },
|
||||||
],
|
],
|
||||||
DONCHIAN: [
|
DONCHIAN: [
|
||||||
{ value: 'CLOSE_PRICE', label: '종가' },
|
{ value: 'CLOSE_PRICE', label: '종가' },
|
||||||
@@ -189,6 +197,9 @@ export const LEFT_FIELD_OPTIONS: Record<string, FieldOption[]> = {
|
|||||||
{ value: 'LEADING_SPAN1', label: '선행1' }, { value: 'LEADING_SPAN2', label: '선행2' },
|
{ value: 'LEADING_SPAN1', label: '선행1' }, { value: 'LEADING_SPAN2', label: '선행2' },
|
||||||
{ value: 'LAGGING_SPAN', label: '후행스팬' },
|
{ value: 'LAGGING_SPAN', label: '후행스팬' },
|
||||||
],
|
],
|
||||||
|
ICHIMOKU_BB: [
|
||||||
|
{ value: 'LAGGING_SPAN', label: '후행스팬' },
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RIGHT_FIELD_OPTIONS: Record<string, FieldOption[]> = {
|
export const RIGHT_FIELD_OPTIONS: Record<string, FieldOption[]> = {
|
||||||
@@ -250,8 +261,8 @@ export const RIGHT_FIELD_OPTIONS: Record<string, FieldOption[]> = {
|
|||||||
{ value: 'CUSTOM', label: '직접 입력' },
|
{ value: 'CUSTOM', label: '직접 입력' },
|
||||||
],
|
],
|
||||||
BOLLINGER: [
|
BOLLINGER: [
|
||||||
{ value: 'UPPER', label: '상단밴드' }, { value: 'MIDDLE', label: '중단밴드' },
|
{ value: 'UPPER', label: '상한선' }, { value: 'MIDDLE', label: '중앙값' },
|
||||||
{ value: 'LOWER', label: '하단밴드' }, { value: 'CUSTOM', label: '직접 입력' },
|
{ value: 'LOWER', label: '하한선' }, { value: 'CUSTOM', label: '직접 입력' },
|
||||||
],
|
],
|
||||||
DONCHIAN: [
|
DONCHIAN: [
|
||||||
{ value: 'DC_UPPER_9', label: '상단(9일 최고가)' },
|
{ value: 'DC_UPPER_9', label: '상단(9일 최고가)' },
|
||||||
@@ -282,6 +293,11 @@ export const RIGHT_FIELD_OPTIONS: Record<string, FieldOption[]> = {
|
|||||||
{ value: 'LEADING_SPAN1', label: '선행1' }, { value: 'LEADING_SPAN2', label: '선행2' },
|
{ value: 'LEADING_SPAN1', label: '선행1' }, { value: 'LEADING_SPAN2', label: '선행2' },
|
||||||
{ value: 'LAGGING_SPAN', label: '후행스팬' }, { value: 'CLOSE_PRICE', label: '종가' },
|
{ value: 'LAGGING_SPAN', label: '후행스팬' }, { value: 'CLOSE_PRICE', label: '종가' },
|
||||||
],
|
],
|
||||||
|
ICHIMOKU_BB: [
|
||||||
|
{ value: 'UPPER_BAND', label: '상한선' },
|
||||||
|
{ value: 'MIDDLE_BAND', label: '중앙값' },
|
||||||
|
{ value: 'LOWER_BAND', label: '하한선' },
|
||||||
|
],
|
||||||
VOLUME: [{ value: 'MA5', label: 'MA(5일)' }, { value: 'MA20', label: 'MA(20일)' }, { value: 'CUSTOM', label: '직접 입력' }],
|
VOLUME: [{ value: 'MA5', label: 'MA(5일)' }, { value: 'MA20', label: 'MA(20일)' }, { value: 'CUSTOM', label: '직접 입력' }],
|
||||||
DISPARITY: [{ value: 'NEUTRAL_100', label: '기준(100)' }, { value: 'OVERBOUGHT_105', label: '과매수(105)' }, { value: 'OVERSOLD_95', label: '과매도(95)' }, { value: 'CUSTOM', label: '직접 입력' }],
|
DISPARITY: [{ value: 'NEUTRAL_100', label: '기준(100)' }, { value: 'OVERBOUGHT_105', label: '과매수(105)' }, { value: 'OVERSOLD_95', label: '과매도(95)' }, { value: 'CUSTOM', label: '직접 입력' }],
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user