일목균형표 복합지표 추가
This commit is contained in:
@@ -91,6 +91,10 @@ import {
|
||||
type StableStrategyFilterLevel,
|
||||
type StableStrategyId,
|
||||
} from '../utils/stableStrategyPairs';
|
||||
import {
|
||||
buildIchimokuSituationTree,
|
||||
isIchimokuSituationPaletteValue,
|
||||
} from '../utils/ichimokuSituationStrategy';
|
||||
import {
|
||||
buildStochOverboughtPairTree,
|
||||
findStochPairRootInForest,
|
||||
@@ -1367,7 +1371,8 @@ export default function StrategyEditorPage({
|
||||
const inflection33 = isInflection33PaletteValue(item.value);
|
||||
const ichimokuBb = isIchimokuBbPaletteValue(item.value);
|
||||
const stableStrategy = isStableStrategyPaletteValue(item.value);
|
||||
const composite = item.kind === 'composite' && !stochPair && !priceExtremeBreakout && !inflection33 && !ichimokuBb && !stableStrategy;
|
||||
const ichimokuSituation = isIchimokuSituationPaletteValue(item.value);
|
||||
const composite = item.kind === 'composite' && !stochPair && !priceExtremeBreakout && !inflection33 && !ichimokuBb && !stableStrategy && !ichimokuSituation;
|
||||
const newNode = stochPair
|
||||
? buildStochOverboughtPairTree(item.secondaryIndicator ?? 'CCI', DEF)
|
||||
: priceExtremeBreakout
|
||||
@@ -1378,6 +1383,8 @@ export default function StrategyEditorPage({
|
||||
? buildIchimokuBbTree(item.value, DEF)
|
||||
: stableStrategy
|
||||
? buildStableStrategyTree(item.value, DEF)
|
||||
: ichimokuSituation
|
||||
? buildIchimokuSituationTree(item.value, DEF)
|
||||
: makeNode('indicator', item.value, signalTab, DEF, composite ? { composite: true } : undefined);
|
||||
if (!newNode) return;
|
||||
handleEditorStateChange(prev => {
|
||||
|
||||
@@ -31,6 +31,11 @@ import {
|
||||
inferStableStrategyFilterLevel,
|
||||
type StableStrategyId,
|
||||
} from '../../utils/stableStrategyPairs';
|
||||
import {
|
||||
isIchimokuSituationPairRoot,
|
||||
ichimokuSituationDisplayName,
|
||||
type IchimokuSituationId,
|
||||
} from '../../utils/ichimokuSituationStrategy';
|
||||
import StableStrategyPairNodeSettings from './StableStrategyPairNodeSettings';
|
||||
import {
|
||||
isStochOverboughtPairRoot,
|
||||
@@ -241,12 +246,13 @@ export const LogicGateNode = memo(function LogicGateNode({ id, data, selected }:
|
||||
const isInflection33Pair = isInflection33PairRoot(node);
|
||||
const isIchimokuBbPair = isIchimokuBbPairRoot(node);
|
||||
const isStableStrategyPair = isStableStrategyPairRoot(node);
|
||||
const isIchimokuSituationPair = isIchimokuSituationPairRoot(node);
|
||||
const { onDragEnter, onDragOver, onDragLeave, onDrop } = usePaletteDropHandlers(id, d);
|
||||
const [settingsOpen, setSettingsOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<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' : ''}${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' : ''}`}
|
||||
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' : ''}${isIchimokuSituationPair ? ' se-flow-node--ichimoku-situation-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}
|
||||
onDragEnter={onDragEnter}
|
||||
onDragOver={onDragOver}
|
||||
@@ -297,6 +303,16 @@ export const LogicGateNode = memo(function LogicGateNode({ id, data, selected }:
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{isIchimokuSituationPair && node.ichimokuSituationPair && (
|
||||
<div className="se-flow-gate-stoch-pair">
|
||||
<span className="se-flow-gate-stoch-pair-title">
|
||||
{ichimokuSituationDisplayName(
|
||||
node.ichimokuSituationPair.situationId as IchimokuSituationId,
|
||||
node.ichimokuSituationPair.mode,
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{isInflection33Pair && node.inflection33Pair && (
|
||||
<div className="se-flow-gate-stoch-pair">
|
||||
<span className="se-flow-gate-stoch-pair-title">
|
||||
|
||||
@@ -8,6 +8,7 @@ import { isPriceExtremeBreakoutPairPaletteValue } from '../../utils/priceExtreme
|
||||
import { isInflection33PaletteValue } from '../../utils/inflection33Strategy';
|
||||
import { isIchimokuBbPaletteValue } from '../../utils/ichimokuBbStrategy';
|
||||
import { isStableStrategyPaletteValue, STABLE_STRATEGY_PALETTE_ITEMS } from '../../utils/stableStrategyPairs';
|
||||
import { isIchimokuSituationPaletteValue } from '../../utils/ichimokuSituationStrategy';
|
||||
import { getDefaultIndicatorPeriod } from '../../utils/conditionPeriods';
|
||||
import { getStrategyIndicatorDisplayName } from '../../utils/strategyPaletteStorage';
|
||||
import { getIndicatorPeriodLabel } from '../../utils/strategyFlowLayout';
|
||||
@@ -39,6 +40,9 @@ function periodLabel(item: PaletteItem, def: DefType): string {
|
||||
const meta = STABLE_STRATEGY_PALETTE_ITEMS.find(i => i.value === item.value);
|
||||
return meta?.periodHint ?? '';
|
||||
}
|
||||
if (item.kind === 'composite' && isIchimokuSituationPaletteValue(item.value)) {
|
||||
return '9 / 26 / 52';
|
||||
}
|
||||
if (item.kind === 'composite') {
|
||||
const d = getCompositeDefaultPeriods(item.value, def);
|
||||
return `${d.short} / ${d.long}`;
|
||||
@@ -194,12 +198,14 @@ export default function IndicatorPaletteTab({
|
||||
&& !isPriceExtremeBreakoutPairPaletteValue(item.value)
|
||||
&& !isInflection33PaletteValue(item.value)
|
||||
&& !isIchimokuBbPaletteValue(item.value)
|
||||
&& !isStableStrategyPaletteValue(item.value)}
|
||||
&& !isStableStrategyPaletteValue(item.value)
|
||||
&& !isIchimokuSituationPaletteValue(item.value)}
|
||||
stochPair={isStochOverboughtPairPaletteValue(item.value)}
|
||||
priceExtremeBreakout={isPriceExtremeBreakoutPairPaletteValue(item.value)}
|
||||
inflection33={isInflection33PaletteValue(item.value)}
|
||||
ichimokuBb={isIchimokuBbPaletteValue(item.value)}
|
||||
stableStrategy={isStableStrategyPaletteValue(item.value)}
|
||||
ichimokuSituation={isIchimokuSituationPaletteValue(item.value)}
|
||||
secondaryIndicator={item.secondaryIndicator}
|
||||
period={periodLabel(item, def)}
|
||||
periodValue={item.period}
|
||||
|
||||
@@ -29,6 +29,7 @@ interface Props {
|
||||
inflection33?: boolean;
|
||||
ichimokuBb?: boolean;
|
||||
stableStrategy?: boolean;
|
||||
ichimokuSituation?: boolean;
|
||||
secondaryIndicator?: string;
|
||||
selected?: boolean;
|
||||
onSelect?: () => void;
|
||||
@@ -37,7 +38,7 @@ interface Props {
|
||||
|
||||
export default function PaletteChip({
|
||||
type, value, label, desc, color, period, periodValue, shortPeriod, longPeriod,
|
||||
composite = false, stochPair = false, priceExtremeBreakout = false, inflection33 = false, ichimokuBb = false, stableStrategy = false, secondaryIndicator, selected = false, onSelect, onAdd,
|
||||
composite = false, stochPair = false, priceExtremeBreakout = false, inflection33 = false, ichimokuBb = false, stableStrategy = false, ichimokuSituation = false, secondaryIndicator, selected = false, onSelect, onAdd,
|
||||
}: Props) {
|
||||
const buildPayload = (): PaletteDragPayload => ({
|
||||
type, value, label,
|
||||
@@ -47,6 +48,7 @@ export default function PaletteChip({
|
||||
inflection33: inflection33 || undefined,
|
||||
ichimokuBb: ichimokuBb || undefined,
|
||||
stableStrategy: stableStrategy || undefined,
|
||||
ichimokuSituation: ichimokuSituation || undefined,
|
||||
secondaryIndicator,
|
||||
period: periodValue,
|
||||
shortPeriod,
|
||||
|
||||
@@ -794,6 +794,9 @@ function StrategyEditorCanvasInner({
|
||||
: n.stableStrategyPair?.mode === 'sell'
|
||||
? (gateType === 'OR' ? n.stableStrategyPair : undefined)
|
||||
: undefined,
|
||||
ichimokuSituationPair: n.ichimokuSituationPair
|
||||
? n.ichimokuSituationPair
|
||||
: undefined,
|
||||
}
|
||||
: n
|
||||
);
|
||||
|
||||
@@ -51,6 +51,10 @@ import {
|
||||
buildStableStrategyTree,
|
||||
isStableStrategyPaletteValue,
|
||||
} from '../utils/stableStrategyPairs';
|
||||
import {
|
||||
buildIchimokuSituationTree,
|
||||
isIchimokuSituationPaletteValue,
|
||||
} from '../utils/ichimokuSituationStrategy';
|
||||
import {
|
||||
buildSidewaysFilterNode,
|
||||
loadSidewaysPaletteItems,
|
||||
@@ -453,7 +457,8 @@ export function useStrategyEvaluationEditor({
|
||||
const inflection33 = isInflection33PaletteValue(item.value);
|
||||
const ichimokuBb = isIchimokuBbPaletteValue(item.value);
|
||||
const stableStrategy = isStableStrategyPaletteValue(item.value);
|
||||
const composite = item.kind === 'composite' && !stochPair && !priceExtremeBreakout && !inflection33 && !ichimokuBb && !stableStrategy;
|
||||
const ichimokuSituation = isIchimokuSituationPaletteValue(item.value);
|
||||
const composite = item.kind === 'composite' && !stochPair && !priceExtremeBreakout && !inflection33 && !ichimokuBb && !stableStrategy && !ichimokuSituation;
|
||||
const newNode = stochPair
|
||||
? buildStochOverboughtPairTree(item.secondaryIndicator ?? 'CCI', def)
|
||||
: priceExtremeBreakout
|
||||
@@ -464,6 +469,8 @@ export function useStrategyEvaluationEditor({
|
||||
? buildIchimokuBbTree(item.value, def)
|
||||
: stableStrategy
|
||||
? buildStableStrategyTree(item.value, def)
|
||||
: ichimokuSituation
|
||||
? buildIchimokuSituationTree(item.value, def)
|
||||
: makeNode('indicator', item.value, signalTab, def, composite ? { composite: true } : undefined);
|
||||
if (!newNode) return;
|
||||
handleEditorStateChange(prev => {
|
||||
|
||||
@@ -0,0 +1,358 @@
|
||||
/**
|
||||
* 일목균형표 상황별·엘리어트 파동 복합 전략
|
||||
* @see 일목균형표 전략.md
|
||||
*/
|
||||
import type { ConditionDSL, LogicNode } from './strategyTypes';
|
||||
import { initConditionPeriodsInherit, type IndicatorPeriodDef } from './conditionPeriods';
|
||||
import { genId } from './strategyNodeIds';
|
||||
|
||||
export type IchimokuSituationCategory = 'basic' | 'alignment' | 'elliott';
|
||||
|
||||
export type IchimokuSituationId =
|
||||
| 'GOLDEN_CROSS'
|
||||
| 'BASE_BREAKUP'
|
||||
| 'CLOUD_BREAKUP'
|
||||
| 'LAGGING_BULL'
|
||||
| 'CLOUD_SUPPORT'
|
||||
| 'DEAD_CROSS'
|
||||
| 'BASE_BREAKDOWN'
|
||||
| 'CLOUD_BREAKDOWN'
|
||||
| 'LAGGING_BEAR'
|
||||
| 'CLOUD_RESISTANCE'
|
||||
| 'SANYAKU_REVERSE'
|
||||
| 'HOJEON'
|
||||
| 'GYEOKJEON'
|
||||
| 'EW1'
|
||||
| 'EW2'
|
||||
| 'EW3'
|
||||
| 'EW4'
|
||||
| 'EW5'
|
||||
| 'EW_A'
|
||||
| 'EW_B'
|
||||
| 'EW_C';
|
||||
|
||||
const SITUATION_PREFIX = 'ICHIMOKU_SIT_';
|
||||
|
||||
export function ichimokuSituationPaletteValue(id: IchimokuSituationId, mode: 'buy' | 'sell'): string {
|
||||
return `${SITUATION_PREFIX}${id}_${mode.toUpperCase()}`;
|
||||
}
|
||||
|
||||
export interface IchimokuSituationPaletteMeta {
|
||||
value: string;
|
||||
label: string;
|
||||
desc: string;
|
||||
situationId: IchimokuSituationId;
|
||||
mode: 'buy' | 'sell';
|
||||
category: IchimokuSituationCategory;
|
||||
}
|
||||
|
||||
export const ICHIMOKU_SITUATION_PALETTE_ITEMS: IchimokuSituationPaletteMeta[] = [
|
||||
// §2.1 기본 매수
|
||||
{ value: ichimokuSituationPaletteValue('GOLDEN_CROSS', 'buy'), label: '일목 골든크로스 매수', situationId: 'GOLDEN_CROSS', mode: 'buy', category: 'basic',
|
||||
desc: 'L1 — 전환선이 기준선 상향 돌파 (호전 1차)' },
|
||||
{ value: ichimokuSituationPaletteValue('BASE_BREAKUP', 'buy'), label: '일목 기준선 돌파 매수', situationId: 'BASE_BREAKUP', mode: 'buy', category: 'basic',
|
||||
desc: 'L2 — 종가가 기준선 상향 돌파 (중기 추세 전환)' },
|
||||
{ value: ichimokuSituationPaletteValue('CLOUD_BREAKUP', 'buy'), label: '일목 구름 돌파 매수', situationId: 'CLOUD_BREAKUP', mode: 'buy', category: 'basic',
|
||||
desc: 'L3 — 구름대 상향 돌파 (매물·심리 저항 돌파)' },
|
||||
{ value: ichimokuSituationPaletteValue('LAGGING_BULL', 'buy'), label: '일목 후행 호전 매수', situationId: 'LAGGING_BULL', mode: 'buy', category: 'basic',
|
||||
desc: 'L4 — 후행스팬 > 26봉 전 종가 (추세 확정)' },
|
||||
{ value: ichimokuSituationPaletteValue('CLOUD_SUPPORT', 'buy'), label: '일목 구름 지지 매수', situationId: 'CLOUD_SUPPORT', mode: 'buy', category: 'basic',
|
||||
desc: 'L5 — 종가 > 선행1·기준선 (양운·기준선 지지 반등)' },
|
||||
// §1.3 호전
|
||||
{ value: ichimokuSituationPaletteValue('HOJEON', 'buy'), label: '일목 호전(정배열) 매수', situationId: 'HOJEON', mode: 'buy', category: 'alignment',
|
||||
desc: '괘·구름 위·양운·후행 — 완벽한 상승 추세 4조건' },
|
||||
// §2.2 기본 매도
|
||||
{ value: ichimokuSituationPaletteValue('DEAD_CROSS', 'sell'), label: '일목 데드크로스 매도', situationId: 'DEAD_CROSS', mode: 'sell', category: 'basic',
|
||||
desc: 'S1 — 전환선이 기준선 하향 이탈' },
|
||||
{ value: ichimokuSituationPaletteValue('BASE_BREAKDOWN', 'sell'), label: '일목 기준선 이탈 매도', situationId: 'BASE_BREAKDOWN', mode: 'sell', category: 'basic',
|
||||
desc: 'S2 — 종가가 기준선 하향 이탈' },
|
||||
{ value: ichimokuSituationPaletteValue('CLOUD_BREAKDOWN', 'sell'), label: '일목 구름 이탈 매도', situationId: 'CLOUD_BREAKDOWN', mode: 'sell', category: 'basic',
|
||||
desc: 'S3 — 구름대 하향 이탈 (지지 붕괴)' },
|
||||
{ value: ichimokuSituationPaletteValue('LAGGING_BEAR', 'sell'), label: '일목 후행 역전 매도', situationId: 'LAGGING_BEAR', mode: 'sell', category: 'basic',
|
||||
desc: 'S4 — 후행스팬 < 26봉 전 종가' },
|
||||
{ value: ichimokuSituationPaletteValue('CLOUD_RESISTANCE', 'sell'), label: '일목 구름 저항 매도', situationId: 'CLOUD_RESISTANCE', mode: 'sell', category: 'basic',
|
||||
desc: 'S5 — 종가 < 선행1·기준선 (음운·기준선 저항)' },
|
||||
{ value: ichimokuSituationPaletteValue('SANYAKU_REVERSE', 'sell'), label: '일목 삼역역전 매도', situationId: 'SANYAKU_REVERSE', mode: 'sell', category: 'alignment',
|
||||
desc: 'S6 — 데드 + 구름 아래 + 후행 역전 (강력 청산)' },
|
||||
{ value: ichimokuSituationPaletteValue('GYEOKJEON', 'sell'), label: '일목 역전(역배열) 매도', situationId: 'GYEOKJEON', mode: 'sell', category: 'alignment',
|
||||
desc: '괘·구름 아래·음운·후행 — 완벽한 하락 추세 4조건' },
|
||||
// §4 엘리어트 파동
|
||||
{ value: ichimokuSituationPaletteValue('EW1', 'buy'), label: '일목 1파 매수', situationId: 'EW1', mode: 'buy', category: 'elliott',
|
||||
desc: '추세 전환 시동 — 기준선 돌파 + 전환>기준 (소액·분할)' },
|
||||
{ value: ichimokuSituationPaletteValue('EW2', 'buy'), label: '일목 2파 매수', situationId: 'EW2', mode: 'buy', category: 'elliott',
|
||||
desc: '눌림목 — 기준선·구름하단(선행2) 지지 + 전환>기준' },
|
||||
{ value: ichimokuSituationPaletteValue('EW2', 'sell'), label: '일목 2파 손절', situationId: 'EW2', mode: 'sell', category: 'elliott',
|
||||
desc: '2파 지지 이탈 — 기준선 이탈 OR 구름 하향 돌파' },
|
||||
{ value: ichimokuSituationPaletteValue('EW3', 'buy'), label: '일목 3파 매수', situationId: 'EW3', mode: 'buy', category: 'elliott',
|
||||
desc: '본격 상승 — 삼역호전(괘·구름·후행·양운) 불타기' },
|
||||
{ value: ichimokuSituationPaletteValue('EW4', 'buy'), label: '일목 4파 재매수', situationId: 'EW4', mode: 'buy', category: 'elliott',
|
||||
desc: '횡보 조정 — 구름 상단(선행1)·기준선 지지 반등' },
|
||||
{ value: ichimokuSituationPaletteValue('EW4', 'sell'), label: '일목 4파 익절', situationId: 'EW4', mode: 'sell', category: 'elliott',
|
||||
desc: '4파 조정 — 기준선 이탈 OR 구름 하향 이탈' },
|
||||
{ value: ichimokuSituationPaletteValue('EW5', 'sell'), label: '일목 5파 분할매도', situationId: 'EW5', mode: 'sell', category: 'elliott',
|
||||
desc: '과열 — 전환선·데드·구름 이탈 (분할 청산)' },
|
||||
{ value: ichimokuSituationPaletteValue('EW_A', 'sell'), label: '일목 A파 매도', situationId: 'EW_A', mode: 'sell', category: 'elliott',
|
||||
desc: '하락 개시 — 기준선·데드크로스' },
|
||||
{ value: ichimokuSituationPaletteValue('EW_B', 'sell'), label: '일목 B파 매도', situationId: 'EW_B', mode: 'sell', category: 'elliott',
|
||||
desc: '기술적 반등 실패 — 기준선·음운 저항 (전량 대피)' },
|
||||
{ value: ichimokuSituationPaletteValue('EW_C', 'sell'), label: '일목 C파 매도', situationId: 'EW_C', mode: 'sell', category: 'elliott',
|
||||
desc: '본 하락 — 삼역역전 + 구름 이탈 (물타기 금지)' },
|
||||
];
|
||||
|
||||
const VALUE_TO_META: Record<string, { situationId: IchimokuSituationId; mode: 'buy' | 'sell' }> =
|
||||
Object.fromEntries(ICHIMOKU_SITUATION_PALETTE_ITEMS.map(i => [i.value, { situationId: i.situationId, mode: i.mode }]));
|
||||
|
||||
export function isIchimokuSituationPaletteValue(value: string): boolean {
|
||||
return value.startsWith(SITUATION_PREFIX) && value in VALUE_TO_META;
|
||||
}
|
||||
|
||||
export function isIchimokuSituationPairRoot(node: LogicNode): boolean {
|
||||
return !!node.ichimokuSituationPair?.situationId
|
||||
&& (node.type === 'AND' || node.type === 'OR');
|
||||
}
|
||||
|
||||
function makeIch(
|
||||
def: IndicatorPeriodDef,
|
||||
conditionType: string,
|
||||
leftField: string,
|
||||
rightField: string,
|
||||
extra?: Partial<ConditionDSL>,
|
||||
): LogicNode {
|
||||
const base: ConditionDSL = {
|
||||
indicatorType: 'ICHIMOKU',
|
||||
conditionType,
|
||||
leftField,
|
||||
rightField,
|
||||
candleRange: 1,
|
||||
valuePeriodOverride: false,
|
||||
thresholdOverride: false,
|
||||
rightPeriodOverride: false,
|
||||
...extra,
|
||||
};
|
||||
return {
|
||||
id: genId(),
|
||||
type: 'CONDITION',
|
||||
condition: initConditionPeriodsInherit('ICHIMOKU', base, def),
|
||||
};
|
||||
}
|
||||
|
||||
function pairMeta(
|
||||
situationId: IchimokuSituationId,
|
||||
mode: 'buy' | 'sell',
|
||||
): LogicNode['ichimokuSituationPair'] {
|
||||
return { situationId, mode };
|
||||
}
|
||||
|
||||
function wrapAnd(children: LogicNode[], meta: LogicNode['ichimokuSituationPair']): LogicNode {
|
||||
return { id: genId(), type: 'AND', ichimokuSituationPair: meta, children };
|
||||
}
|
||||
|
||||
function wrapOr(children: LogicNode[], meta: LogicNode['ichimokuSituationPair']): LogicNode {
|
||||
return { id: genId(), type: 'OR', ichimokuSituationPair: meta, children };
|
||||
}
|
||||
|
||||
type Builder = (def: IndicatorPeriodDef) => LogicNode;
|
||||
|
||||
const BUILDERS: Record<IchimokuSituationId, Partial<Record<'buy' | 'sell', Builder>>> = {
|
||||
GOLDEN_CROSS: {
|
||||
buy: def => wrapAnd([
|
||||
makeIch(def, 'CROSS_UP', 'CONVERSION_LINE', 'BASE_LINE'),
|
||||
], pairMeta('GOLDEN_CROSS', 'buy')),
|
||||
},
|
||||
BASE_BREAKUP: {
|
||||
buy: def => wrapAnd([
|
||||
makeIch(def, 'CROSS_UP', 'CLOSE_PRICE', 'BASE_LINE'),
|
||||
], pairMeta('BASE_BREAKUP', 'buy')),
|
||||
},
|
||||
CLOUD_BREAKUP: {
|
||||
buy: def => wrapAnd([
|
||||
makeIch(def, 'CLOUD_BREAK_UP', 'CLOSE_PRICE', 'NONE'),
|
||||
], pairMeta('CLOUD_BREAKUP', 'buy')),
|
||||
},
|
||||
LAGGING_BULL: {
|
||||
buy: def => wrapAnd([
|
||||
makeIch(def, 'LAGGING_GT_PRICE', 'LAGGING_SPAN', 'CLOSE_PRICE'),
|
||||
], pairMeta('LAGGING_BULL', 'buy')),
|
||||
},
|
||||
CLOUD_SUPPORT: {
|
||||
buy: def => wrapAnd([
|
||||
makeIch(def, 'GT', 'CLOSE_PRICE', 'LEADING_SPAN1'),
|
||||
makeIch(def, 'GT', 'CLOSE_PRICE', 'BASE_LINE'),
|
||||
], pairMeta('CLOUD_SUPPORT', 'buy')),
|
||||
},
|
||||
HOJEON: {
|
||||
buy: def => wrapAnd([
|
||||
makeIch(def, 'GT', 'CONVERSION_LINE', 'BASE_LINE'),
|
||||
makeIch(def, 'ABOVE_CLOUD', 'CLOSE_PRICE', 'NONE'),
|
||||
makeIch(def, 'SPAN1_GT_SPAN2', 'LEADING_SPAN1', 'LEADING_SPAN2'),
|
||||
makeIch(def, 'LAGGING_GT_PRICE', 'LAGGING_SPAN', 'CLOSE_PRICE'),
|
||||
], pairMeta('HOJEON', 'buy')),
|
||||
},
|
||||
DEAD_CROSS: {
|
||||
sell: def => wrapOr([
|
||||
makeIch(def, 'CROSS_DOWN', 'CONVERSION_LINE', 'BASE_LINE'),
|
||||
], pairMeta('DEAD_CROSS', 'sell')),
|
||||
},
|
||||
BASE_BREAKDOWN: {
|
||||
sell: def => wrapOr([
|
||||
makeIch(def, 'CROSS_DOWN', 'CLOSE_PRICE', 'BASE_LINE'),
|
||||
], pairMeta('BASE_BREAKDOWN', 'sell')),
|
||||
},
|
||||
CLOUD_BREAKDOWN: {
|
||||
sell: def => wrapOr([
|
||||
makeIch(def, 'CLOUD_BREAK_DOWN', 'CLOSE_PRICE', 'NONE'),
|
||||
], pairMeta('CLOUD_BREAKDOWN', 'sell')),
|
||||
},
|
||||
LAGGING_BEAR: {
|
||||
sell: def => wrapOr([
|
||||
makeIch(def, 'LAGGING_LT_PRICE', 'LAGGING_SPAN', 'CLOSE_PRICE'),
|
||||
], pairMeta('LAGGING_BEAR', 'sell')),
|
||||
},
|
||||
CLOUD_RESISTANCE: {
|
||||
sell: def => wrapAnd([
|
||||
makeIch(def, 'LT', 'CLOSE_PRICE', 'LEADING_SPAN1'),
|
||||
makeIch(def, 'LT', 'CLOSE_PRICE', 'BASE_LINE'),
|
||||
], pairMeta('CLOUD_RESISTANCE', 'sell')),
|
||||
},
|
||||
SANYAKU_REVERSE: {
|
||||
sell: def => wrapAnd([
|
||||
makeIch(def, 'LT', 'CONVERSION_LINE', 'BASE_LINE'),
|
||||
makeIch(def, 'BELOW_CLOUD', 'CLOSE_PRICE', 'NONE'),
|
||||
makeIch(def, 'LAGGING_LT_PRICE', 'LAGGING_SPAN', 'CLOSE_PRICE'),
|
||||
], pairMeta('SANYAKU_REVERSE', 'sell')),
|
||||
},
|
||||
GYEOKJEON: {
|
||||
sell: def => wrapAnd([
|
||||
makeIch(def, 'LT', 'CONVERSION_LINE', 'BASE_LINE'),
|
||||
makeIch(def, 'BELOW_CLOUD', 'CLOSE_PRICE', 'NONE'),
|
||||
makeIch(def, 'SPAN1_LT_SPAN2', 'LEADING_SPAN1', 'LEADING_SPAN2'),
|
||||
makeIch(def, 'LAGGING_LT_PRICE', 'LAGGING_SPAN', 'CLOSE_PRICE'),
|
||||
], pairMeta('GYEOKJEON', 'sell')),
|
||||
},
|
||||
EW1: {
|
||||
buy: def => wrapAnd([
|
||||
makeIch(def, 'CROSS_UP', 'CLOSE_PRICE', 'BASE_LINE'),
|
||||
makeIch(def, 'GT', 'CONVERSION_LINE', 'BASE_LINE'),
|
||||
], pairMeta('EW1', 'buy')),
|
||||
},
|
||||
EW2: {
|
||||
buy: def => wrapAnd([
|
||||
makeIch(def, 'GT', 'CLOSE_PRICE', 'BASE_LINE'),
|
||||
makeIch(def, 'GT', 'CLOSE_PRICE', 'LEADING_SPAN2'),
|
||||
makeIch(def, 'GT', 'CONVERSION_LINE', 'BASE_LINE'),
|
||||
], pairMeta('EW2', 'buy')),
|
||||
sell: def => wrapOr([
|
||||
makeIch(def, 'CROSS_DOWN', 'CLOSE_PRICE', 'BASE_LINE'),
|
||||
makeIch(def, 'CLOUD_BREAK_DOWN', 'CLOSE_PRICE', 'NONE'),
|
||||
], pairMeta('EW2', 'sell')),
|
||||
},
|
||||
EW3: {
|
||||
buy: def => wrapAnd([
|
||||
makeIch(def, 'GT', 'CONVERSION_LINE', 'BASE_LINE'),
|
||||
makeIch(def, 'ABOVE_CLOUD', 'CLOSE_PRICE', 'NONE'),
|
||||
makeIch(def, 'LAGGING_GT_PRICE', 'LAGGING_SPAN', 'CLOSE_PRICE'),
|
||||
makeIch(def, 'SPAN1_GT_SPAN2', 'LEADING_SPAN1', 'LEADING_SPAN2'),
|
||||
], pairMeta('EW3', 'buy')),
|
||||
},
|
||||
EW4: {
|
||||
buy: def => wrapAnd([
|
||||
makeIch(def, 'GT', 'CLOSE_PRICE', 'LEADING_SPAN1'),
|
||||
makeIch(def, 'GT', 'CONVERSION_LINE', 'BASE_LINE'),
|
||||
], pairMeta('EW4', 'buy')),
|
||||
sell: def => wrapOr([
|
||||
makeIch(def, 'CROSS_DOWN', 'CLOSE_PRICE', 'BASE_LINE'),
|
||||
makeIch(def, 'CLOUD_BREAK_DOWN', 'CLOSE_PRICE', 'NONE'),
|
||||
], pairMeta('EW4', 'sell')),
|
||||
},
|
||||
EW5: {
|
||||
sell: def => wrapOr([
|
||||
makeIch(def, 'CROSS_DOWN', 'CLOSE_PRICE', 'CONVERSION_LINE'),
|
||||
makeIch(def, 'CROSS_DOWN', 'CONVERSION_LINE', 'BASE_LINE'),
|
||||
makeIch(def, 'CLOUD_BREAK_DOWN', 'CLOSE_PRICE', 'NONE'),
|
||||
], pairMeta('EW5', 'sell')),
|
||||
},
|
||||
EW_A: {
|
||||
sell: def => wrapOr([
|
||||
makeIch(def, 'CROSS_DOWN', 'CLOSE_PRICE', 'BASE_LINE'),
|
||||
makeIch(def, 'CROSS_DOWN', 'CONVERSION_LINE', 'BASE_LINE'),
|
||||
], pairMeta('EW_A', 'sell')),
|
||||
},
|
||||
EW_B: {
|
||||
sell: def => wrapAnd([
|
||||
makeIch(def, 'LT', 'CLOSE_PRICE', 'BASE_LINE'),
|
||||
makeIch(def, 'BELOW_CLOUD', 'CLOSE_PRICE', 'NONE'),
|
||||
makeIch(def, 'LT', 'CONVERSION_LINE', 'BASE_LINE'),
|
||||
], pairMeta('EW_B', 'sell')),
|
||||
},
|
||||
EW_C: {
|
||||
sell: def => wrapAnd([
|
||||
makeIch(def, 'CLOUD_BREAK_DOWN', 'CLOSE_PRICE', 'NONE'),
|
||||
makeIch(def, 'BELOW_CLOUD', 'CLOSE_PRICE', 'NONE'),
|
||||
makeIch(def, 'LT', 'CONVERSION_LINE', 'BASE_LINE'),
|
||||
makeIch(def, 'LAGGING_LT_PRICE', 'LAGGING_SPAN', 'CLOSE_PRICE'),
|
||||
], pairMeta('EW_C', 'sell')),
|
||||
},
|
||||
};
|
||||
|
||||
export function ichimokuSituationMetaFromValue(value: string): { situationId: IchimokuSituationId; mode: 'buy' | 'sell' } | null {
|
||||
return VALUE_TO_META[value] ?? null;
|
||||
}
|
||||
|
||||
export function buildIchimokuSituationTree(value: string, def: IndicatorPeriodDef): LogicNode | null {
|
||||
const meta = ichimokuSituationMetaFromValue(value);
|
||||
if (!meta) return null;
|
||||
const builder = BUILDERS[meta.situationId][meta.mode];
|
||||
return builder ? builder(def) : null;
|
||||
}
|
||||
|
||||
export function ichimokuSituationDisplayName(situationId: IchimokuSituationId, mode: 'buy' | 'sell'): string {
|
||||
const item = ICHIMOKU_SITUATION_PALETTE_ITEMS.find(
|
||||
i => i.situationId === situationId && i.mode === mode,
|
||||
);
|
||||
return item?.label ?? `${situationId} ${mode === 'buy' ? '매수' : '매도'}`;
|
||||
}
|
||||
|
||||
export function ichimokuSituationPaletteDesc(value: string): string {
|
||||
return ICHIMOKU_SITUATION_PALETTE_ITEMS.find(i => i.value === value)?.desc ?? '';
|
||||
}
|
||||
|
||||
const COND_LABEL: Record<string, string> = {
|
||||
CROSS_UP: '상향돌파', CROSS_DOWN: '하향돌파',
|
||||
GT: '>', LT: '<',
|
||||
ABOVE_CLOUD: '구름 위', BELOW_CLOUD: '구름 아래',
|
||||
CLOUD_BREAK_UP: '구름 상향돌파', CLOUD_BREAK_DOWN: '구름 하향이탈',
|
||||
SPAN1_GT_SPAN2: '양운', SPAN1_LT_SPAN2: '음운',
|
||||
LAGGING_GT_PRICE: '후행>26봉전 종가', LAGGING_LT_PRICE: '후행<26봉전 종가',
|
||||
};
|
||||
|
||||
function fmtNode(n: LogicNode): string {
|
||||
if (n.type === 'CONDITION' && n.condition) {
|
||||
const c = n.condition;
|
||||
const op = COND_LABEL[c.conditionType] ?? c.conditionType;
|
||||
return `${c.leftField} ${op} ${c.rightField ?? ''}`.trim();
|
||||
}
|
||||
if (n.type === 'AND' || n.type === 'OR') {
|
||||
return (n.children ?? []).map(fmtNode).join(` ${n.type} `);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
export function ichimokuSituationSummaryText(
|
||||
situationId: IchimokuSituationId,
|
||||
mode: 'buy' | 'sell',
|
||||
def: IndicatorPeriodDef = {
|
||||
rsiPeriod: 14, cciPeriod: 20, adxPeriod: 14, williamsR: 14,
|
||||
trixPeriod: 12, vrPeriod: 26, psyPeriod: 12, newPsy: 12, investPsy: 12,
|
||||
},
|
||||
): string {
|
||||
const tree = BUILDERS[situationId][mode]?.(def);
|
||||
return tree ? fmtNode(tree) : '';
|
||||
}
|
||||
|
||||
export function ichimokuSituationCategoryLabel(cat: IchimokuSituationCategory): string {
|
||||
switch (cat) {
|
||||
case 'basic': return '기본';
|
||||
case 'alignment': return '호전·역전';
|
||||
case 'elliott': return '엘리어트';
|
||||
}
|
||||
}
|
||||
@@ -63,6 +63,14 @@ import {
|
||||
stableStrategyFilterLevelLabel,
|
||||
type StableStrategyId,
|
||||
} from '../utils/stableStrategyPairs';
|
||||
import {
|
||||
buildIchimokuSituationTree,
|
||||
isIchimokuSituationPairRoot,
|
||||
isIchimokuSituationPaletteValue,
|
||||
ichimokuSituationDisplayName,
|
||||
ichimokuSituationSummaryText,
|
||||
type IchimokuSituationId,
|
||||
} from '../utils/ichimokuSituationStrategy';
|
||||
import {
|
||||
buildPriceExtremeBreakoutTree,
|
||||
isPriceExtremeBreakoutPairPaletteValue,
|
||||
@@ -1015,6 +1023,10 @@ export const nodeToText = (node: LogicNode, DEF: DefType = DEF_DEFAULTS): string
|
||||
const cfg = inferIchimokuBbConfig(node);
|
||||
return `${ichimokuBbDisplayName(cfg.mode, cfg.chikouDisplacement)}\n${ichimokuBbSummaryText(cfg)}`;
|
||||
}
|
||||
if (isIchimokuSituationPairRoot(node) && node.ichimokuSituationPair) {
|
||||
const { situationId, mode } = node.ichimokuSituationPair;
|
||||
return `${ichimokuSituationDisplayName(situationId as IchimokuSituationId, mode)}\n${ichimokuSituationSummaryText(situationId as IchimokuSituationId, mode, DEF)}`;
|
||||
}
|
||||
if (isPriceExtremeBreakoutPairRoot(node) && node.priceExtremePair) {
|
||||
const { mode, shortPeriod, longPeriod } = node.priceExtremePair;
|
||||
const level = inferPriceExtremeFilterLevel(node);
|
||||
@@ -1038,6 +1050,10 @@ export const nodeToText = (node: LogicNode, DEF: DefType = DEF_DEFAULTS): string
|
||||
const cfg = inferIchimokuBbConfig(node);
|
||||
return `${ichimokuBbDisplayName(cfg.mode, cfg.chikouDisplacement)}\n${ichimokuBbSummaryText(cfg)}`;
|
||||
}
|
||||
if (isIchimokuSituationPairRoot(node) && node.ichimokuSituationPair) {
|
||||
const { situationId, mode } = node.ichimokuSituationPair;
|
||||
return `${ichimokuSituationDisplayName(situationId as IchimokuSituationId, mode)}\n${ichimokuSituationSummaryText(situationId as IchimokuSituationId, mode, DEF)}`;
|
||||
}
|
||||
if (isStochOverboughtPairRoot(node)) {
|
||||
const sec = node.stochPair!.secondaryIndicatorType;
|
||||
return `${stochPairDisplayName(sec)}\n${stochPairSummaryText(sec)}`;
|
||||
@@ -1565,6 +1581,8 @@ export type MakeNodeOptions = {
|
||||
ichimokuBb?: boolean;
|
||||
/** 추천 안정형 전략 복합 */
|
||||
stableStrategy?: boolean;
|
||||
/** 일목균형표 상황별·엘리어트 복합 */
|
||||
ichimokuSituation?: boolean;
|
||||
};
|
||||
|
||||
/** 팔레트 드래그 payload → makeNode 옵션 */
|
||||
@@ -1575,6 +1593,7 @@ export function makeNodeOptionsFromPalette(data: {
|
||||
inflection33?: boolean;
|
||||
ichimokuBb?: boolean;
|
||||
stableStrategy?: boolean;
|
||||
ichimokuSituation?: boolean;
|
||||
secondaryIndicator?: string;
|
||||
period?: number;
|
||||
shortPeriod?: number;
|
||||
@@ -1601,6 +1620,9 @@ export function makeNodeOptionsFromPalette(data: {
|
||||
if (data.stableStrategy || (data.value && isStableStrategyPaletteValue(data.value))) {
|
||||
return { stableStrategy: true };
|
||||
}
|
||||
if (data.ichimokuSituation || (data.value && isIchimokuSituationPaletteValue(data.value))) {
|
||||
return { ichimokuSituation: true };
|
||||
}
|
||||
if (data.composite) {
|
||||
return {
|
||||
composite: true,
|
||||
@@ -1638,6 +1660,10 @@ export const makeNode = (
|
||||
const tree = buildStableStrategyTree(value, DEF);
|
||||
if (tree) return tree;
|
||||
}
|
||||
if (options?.ichimokuSituation || isIchimokuSituationPaletteValue(value)) {
|
||||
const tree = buildIchimokuSituationTree(value, DEF);
|
||||
if (tree) return tree;
|
||||
}
|
||||
if (options?.composite) {
|
||||
let condition = makeCompositeCondition(value, signalType, DEF);
|
||||
condition = {
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
isNewLow920SellPaletteValue,
|
||||
} from './priceExtremeBreakoutPair';
|
||||
import { STABLE_STRATEGY_PALETTE_ITEMS } from './stableStrategyPairs';
|
||||
import { ichimokuSituationMetaFromValue } from './ichimokuSituationStrategy';
|
||||
|
||||
export type TemplatePaletteSource =
|
||||
| 'user'
|
||||
@@ -50,6 +51,8 @@ export function inferCompositePaletteSignal(item: PaletteItem): 'buy' | 'sell' {
|
||||
}
|
||||
const stable = STABLE_STRATEGY_PALETTE_ITEMS.find(s => s.value === item.value);
|
||||
if (stable) return stable.mode;
|
||||
const ichimokuSit = ichimokuSituationMetaFromValue(item.value);
|
||||
if (ichimokuSit) return ichimokuSit.mode;
|
||||
if (item.value.endsWith('_BUY')) return 'buy';
|
||||
if (item.value.endsWith('_SELL')) return 'sell';
|
||||
return 'buy';
|
||||
|
||||
@@ -26,6 +26,10 @@ import {
|
||||
buildStableStrategyTree,
|
||||
isStableStrategyPaletteValue,
|
||||
} from './stableStrategyPairs';
|
||||
import {
|
||||
buildIchimokuSituationTree,
|
||||
isIchimokuSituationPaletteValue,
|
||||
} from './ichimokuSituationStrategy';
|
||||
import {
|
||||
buildSidewaysFilterNode,
|
||||
loadSidewaysPaletteItems,
|
||||
@@ -71,18 +75,21 @@ function buildLogicNodeFromPaletteItem(
|
||||
const inflection33 = isInflection33PaletteValue(item.value);
|
||||
const ichimokuBb = isIchimokuBbPaletteValue(item.value);
|
||||
const stableStrategy = isStableStrategyPaletteValue(item.value);
|
||||
const ichimokuSituation = isIchimokuSituationPaletteValue(item.value);
|
||||
const composite = item.kind === 'composite'
|
||||
&& !stochPair
|
||||
&& !priceExtremeBreakout
|
||||
&& !inflection33
|
||||
&& !ichimokuBb
|
||||
&& !stableStrategy;
|
||||
&& !stableStrategy
|
||||
&& !ichimokuSituation;
|
||||
|
||||
if (stochPair) return buildStochOverboughtPairTree(item.secondaryIndicator ?? 'CCI', def);
|
||||
if (priceExtremeBreakout) return buildPriceExtremeBreakoutTree(item.value, def);
|
||||
if (inflection33) return buildInflection33Tree(item.value, def);
|
||||
if (ichimokuBb) return buildIchimokuBbTree(item.value, def);
|
||||
if (stableStrategy) return buildStableStrategyTree(item.value, def);
|
||||
if (ichimokuSituation) return buildIchimokuSituationTree(item.value, def);
|
||||
return makeNode('indicator', item.value, signalTab, def, composite ? { composite: true } : undefined);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,9 @@ import {
|
||||
import {
|
||||
STABLE_STRATEGY_PALETTE_ITEMS,
|
||||
} from './stableStrategyPairs';
|
||||
import {
|
||||
ICHIMOKU_SITUATION_PALETTE_ITEMS,
|
||||
} from './ichimokuSituationStrategy';
|
||||
import {
|
||||
NEW_HIGH_9_20_BUY_VALUE,
|
||||
NEW_LOW_9_20_SELL_VALUE,
|
||||
@@ -125,6 +128,12 @@ export const DEFAULT_COMPOSITE_ITEMS: Omit<PaletteItem, 'id' | 'kind'>[] = [
|
||||
builtIn: true,
|
||||
period: 26,
|
||||
},
|
||||
...ICHIMOKU_SITUATION_PALETTE_ITEMS.map(i => ({
|
||||
value: i.value,
|
||||
label: i.label,
|
||||
desc: `[${i.category === 'basic' ? '기본' : i.category === 'alignment' ? '호전·역전' : '엘리어트'}] ${i.desc}`,
|
||||
builtIn: true,
|
||||
})),
|
||||
...STABLE_STRATEGY_PALETTE_ITEMS.map(i => ({
|
||||
value: i.value,
|
||||
label: i.label,
|
||||
|
||||
@@ -80,6 +80,11 @@ export interface LogicNode {
|
||||
bbBand: 'UPPER_BAND' | 'MIDDLE_BAND' | 'LOWER_BAND';
|
||||
conditionType: 'CROSS_UP' | 'CROSS_DOWN' | 'GT' | 'LT';
|
||||
};
|
||||
/** 일목균형표 상황별·엘리어트 파동 복합 — @see 일목균형표 전략.md */
|
||||
ichimokuSituationPair?: {
|
||||
situationId: string;
|
||||
mode: 'buy' | 'sell';
|
||||
};
|
||||
}
|
||||
|
||||
export interface StrategyDSLDto {
|
||||
|
||||
Reference in New Issue
Block a user