From c4123b51f8fde4daf6fd395e0ebe7b44b6bc34a2 Mon Sep 17 00:00:00 2001 From: Macbook Date: Tue, 16 Jun 2026 21:15:57 +0900 Subject: [PATCH] =?UTF-8?q?=EC=9D=BC=EB=AA=A9=EA=B7=A0=ED=98=95=ED=91=9C?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/utils/stableStrategyPairs.ts | 52 +++++++++-------------- 1 file changed, 21 insertions(+), 31 deletions(-) diff --git a/frontend/src/utils/stableStrategyPairs.ts b/frontend/src/utils/stableStrategyPairs.ts index 1e007fc..28b4b82 100644 --- a/frontend/src/utils/stableStrategyPairs.ts +++ b/frontend/src/utils/stableStrategyPairs.ts @@ -143,12 +143,12 @@ export const STABLE_STRATEGY_PALETTE_ITEMS: StableStrategyPaletteMeta[] = [ }, { value: ICHIMOKU_SANYAKU_BUY, label: '일목 삼역호전 매수', strategyId: 'ICHIMOKU_SANYAKU', mode: 'buy', - desc: '구름 돌파 + 삼역 필터 — 최근 N봉 내 존재로 전후 봉 분산 조건 허용 (보통 20봉)', + desc: '구름 돌파(현재봉) + 보조필터 N봉 내 존재 — 전후 봉 분산 허용, 트리거는 1회', periodHint: '9 / 26 / 52', }, { value: ICHIMOKU_SANYAKU_SELL, label: '일목 삼역호전 매도', strategyId: 'ICHIMOKU_SANYAKU', mode: 'sell', - desc: '전환·기준·구름 이탈 — N봉 내 돌파 존재 + 전환선 이탈 fallback', + desc: '전환·기준·구름 하향 돌파(현재봉 1회) — 유지 조건 없음', periodHint: '9 / 26 / 52', }, { @@ -394,7 +394,7 @@ function buildIchimokuTrendSell(def: IndicatorPeriodDef, level: StableStrategyFi return wrapOrRoot(branches, pairMeta('ICHIMOKU_TREND', 'sell', level)); } -/** 일목 삼역호전 — N봉 내 존재 윈도우 (3m 기준: 12≈36분, 20≈1h, 26≈78분) */ +/** 일목 삼역호전 — N봉 내 존재 윈도우 (3m: 12≈36분, 20≈1h, 26≈78분) — 추세 확인·상태 필터에만 사용 */ function ichimokuSanyakuWindow(level: StableStrategyFilterLevel): number { switch (level) { case 'relaxed': return 12; @@ -403,22 +403,18 @@ function ichimokuSanyakuWindow(level: StableStrategyFilterLevel): number { } } -/** 일목 삼역호전 — 매수 트리거 (구름/후행 돌파, N봉 내 존재) */ +/** 매수 트리거 — 1회성 돌파 (현재 봉만). EXISTS_IN 적용 시 N봉마다 재충족되어 시그널 폭증 */ function ichimokuSanyakuBuyTrigger(def: IndicatorPeriodDef, level: StableStrategyFilterLevel): LogicNode { - const w = ichimokuSanyakuWindow(level); if (level === 'strict') { return wrapOr([ - makeExistsIn('ICHIMOKU', 'CLOUD_BREAK_UP', 'NONE', 'NONE', def, w), - makeExistsIn('ICHIMOKU', 'CHIKOU_CROSS_ABOVE_PRIOR_CLOUD', 'NONE', 'NONE', def, w), + makeCondition('ICHIMOKU', 'CLOUD_BREAK_UP', 'NONE', 'NONE', def), + makeCondition('ICHIMOKU', 'CHIKOU_CROSS_ABOVE_PRIOR_CLOUD', 'NONE', 'NONE', def), ]); } - return makeExistsIn('ICHIMOKU', 'CLOUD_BREAK_UP', 'NONE', 'NONE', def, w); + return makeCondition('ICHIMOKU', 'CLOUD_BREAK_UP', 'NONE', 'NONE', def); } -/** - * 구름 돌파 직후에는 전환>기준·양운이 아직 false인 경우가 많음. - * N봉 내 존재로 전후 봉에 분산된 추세 확인 허용. - */ +/** 전환>기준·양운 — 돌파 봉 전후 시간차 허용 (N봉 내 존재) */ function ichimokuSanyakuTrendConfirm(def: IndicatorPeriodDef, level: StableStrategyFilterLevel): LogicNode[] { if (level === 'relaxed') return []; const w = ichimokuSanyakuWindow(level); @@ -430,13 +426,14 @@ function ichimokuSanyakuTrendConfirm(def: IndicatorPeriodDef, level: StableStrat ]; } -/** 일목 삼역호전 — 공통 추세·구름 필터 (N봉 내 존재) */ +/** + * 돌파 트리거(현재 봉) + 보조 필터(N봉 내 존재). + * 트리거·돌파 이벤트에 EXISTS_IN을 쓰면 윈도우 동안 매 봉 AND 충족 → 시그널 과다. + */ function ichimokuSanyakuCoreFilters(def: IndicatorPeriodDef, level: StableStrategyFilterLevel): LogicNode[] { const w = ichimokuSanyakuWindow(level); if (level === 'relaxed') { - return [ - makeExistsIn('ICHIMOKU', 'GT', 'CLOSE_PRICE', 'CONVERSION_LINE', def, w), - ]; + return [makeCondition('ICHIMOKU', 'GT', 'CLOSE_PRICE', 'CONVERSION_LINE', def)]; } const nodes: LogicNode[] = [ makeExistsIn('ICHIMOKU', 'GT', 'CLOSE_PRICE', 'CONVERSION_LINE', def, w), @@ -464,21 +461,14 @@ function buildIchimokuSanyakuBuy(def: IndicatorPeriodDef, level: StableStrategyF return wrapAnd(nodes, pairMeta('ICHIMOKU_SANYAKU', 'buy', level)); } -/** 일목 삼역호전 — 청산 (돌파·이탈 N봉 내 존재 + 전환선 이탈 fallback) */ +/** 일목 삼역호전 — 청산 (1회성 하향 돌파만; 유지 조건 LT/구름아래 제거 → 연속 매도 방지) */ function buildIchimokuSanyakuSell(def: IndicatorPeriodDef, level: StableStrategyFilterLevel): LogicNode { - const w = ichimokuSanyakuWindow(level); const branches: LogicNode[] = [ - makeExistsIn('ICHIMOKU', 'CROSS_DOWN', 'CLOSE_PRICE', 'CONVERSION_LINE', def, w), - makeExistsIn('ICHIMOKU', 'CROSS_DOWN', 'CLOSE_PRICE', 'BASE_LINE', def, w), - makeExistsIn('ICHIMOKU', 'CROSS_DOWN', 'CONVERSION_LINE', 'BASE_LINE', def, w), - makeExistsIn('ICHIMOKU', 'CLOUD_BREAK_DOWN', 'CLOSE_PRICE', 'NONE', def, w), + makeCondition('ICHIMOKU', 'CROSS_DOWN', 'CLOSE_PRICE', 'CONVERSION_LINE', def), + makeCondition('ICHIMOKU', 'CROSS_DOWN', 'CLOSE_PRICE', 'BASE_LINE', def), + makeCondition('ICHIMOKU', 'CROSS_DOWN', 'CONVERSION_LINE', 'BASE_LINE', def), + makeCondition('ICHIMOKU', 'CLOUD_BREAK_DOWN', 'CLOSE_PRICE', 'NONE', def), ]; - if (level !== 'strict') { - branches.push(makeCondition('ICHIMOKU', 'LT', 'CLOSE_PRICE', 'CONVERSION_LINE', def)); - if (level === 'relaxed') { - branches.push(makeCondition('ICHIMOKU', 'BELOW_CLOUD', 'CLOSE_PRICE', 'NONE', def)); - } - } return wrapOrRoot(branches, pairMeta('ICHIMOKU_SANYAKU', 'sell', level)); } @@ -601,9 +591,9 @@ export function stableStrategyFilterSummary( if (strategyId === 'ICHIMOKU_TREND' && level === 'relaxed') parts.push('구름필터 없음'); if (strategyId === 'ICHIMOKU_SANYAKU') { const w = ichimokuSanyakuWindow(level); - if (level === 'strict') parts.push(`N${w}봉·삼역·MA60·거래량`); - else if (level === 'balanced') parts.push(`N${w}봉·구름돌파·(전환>기준 OR 양운)`); - else parts.push(`N${w}봉·구름돌파·종가>전환`); + if (level === 'strict') parts.push(`돌파1회·필터N${w}·MA60·거래량`); + else if (level === 'balanced') parts.push(`돌파1회·필터N${w}·(전환>기준 OR 양운)`); + else parts.push('돌파1회·종가>전환'); } if (strategyId === 'MACD_MOMENTUM' && level === 'relaxed') parts.push('EMA60·ADX 없음'); if (strategyId === 'MA_TREND' && level === 'relaxed') parts.push('ADX 없음');