diff --git a/frontend/src/utils/stableStrategyPairs.ts b/frontend/src/utils/stableStrategyPairs.ts index 1fd790a..fe937a9 100644 --- a/frontend/src/utils/stableStrategyPairs.ts +++ b/frontend/src/utils/stableStrategyPairs.ts @@ -382,25 +382,43 @@ function ichimokuSanyakuBuyTrigger(def: IndicatorPeriodDef, level: StableStrateg return makeCondition('ICHIMOKU', 'CLOUD_BREAK_UP', 'NONE', 'NONE', def); } -/** 일목 삼역호전 — 공통 추세·구름 필터 */ -function ichimokuSanyakuCoreFilters(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.unshift(makeCondition('ICHIMOKU', 'GT', 'CLOSE_PRICE', 'CONVERSION_LINE', def)); - return nodes; +/** + * 구름 돌파 직후에는 전환>기준·양운이 아직 false인 경우가 많음(전형적 바닥 반등). + * 보통: 둘 중 하나만 충족하면 됨 / 강함: 둘 다 + 최근 N봉 내 양운 형성 허용 + */ +function ichimokuSanyakuTrendConfirm(def: IndicatorPeriodDef, level: StableStrategyFilterLevel): LogicNode[] { + if (level === 'relaxed') return []; + if (level === 'balanced') { + return [ + wrapOr([ + makeCondition('ICHIMOKU', 'GT', 'CONVERSION_LINE', 'BASE_LINE', def), + makeCondition('ICHIMOKU', 'SPAN1_GT_SPAN2', 'LEADING_SPAN1', 'LEADING_SPAN2', def), + ]), + ]; } - nodes.unshift( + return [ + makeCondition('ICHIMOKU', 'GT', 'CONVERSION_LINE', 'BASE_LINE', def), + makeCondition('ICHIMOKU', 'SPAN1_GT_SPAN2', 'LEADING_SPAN1', 'LEADING_SPAN2', def, { + candleRangeMode: 'EXISTS_IN', + candleRange: 26, + }), + ]; +} + +/** 일목 삼역호전 — 공통 추세·구름 필터 (돌파 봉 기준) */ +function ichimokuSanyakuCoreFilters(def: IndicatorPeriodDef, level: StableStrategyFilterLevel): LogicNode[] { + if (level === 'relaxed') { + return [ + makeCondition('ICHIMOKU', 'GT', 'CLOSE_PRICE', 'CONVERSION_LINE', def), + ]; + } + const nodes: LogicNode[] = [ makeCondition('ICHIMOKU', 'GT', 'CLOSE_PRICE', 'CONVERSION_LINE', def), - makeCondition('ICHIMOKU', 'GT', 'CLOSE_PRICE', 'BASE_LINE', def), - ); - nodes.push( - makeCondition('ICHIMOKU', 'SPAN1_GT_SPAN2', 'LEADING_SPAN1', 'LEADING_SPAN2', def), makeCondition('ICHIMOKU', 'LAGGING_GT_PRICE', 'LAGGING_SPAN', 'CLOSE_PRICE', def), - ); + ...ichimokuSanyakuTrendConfirm(def, level), + ]; if (level === 'strict') { + nodes.splice(1, 0, makeCondition('ICHIMOKU', 'GT', 'CLOSE_PRICE', 'BASE_LINE', def)); nodes.push(makeCondition('ICHIMOKU', 'LAGGING_ABOVE_PRIOR_CLOUD', 'NONE', 'NONE', def)); } return nodes; @@ -554,9 +572,9 @@ export function stableStrategyFilterSummary( } if (strategyId === 'ICHIMOKU_TREND' && level === 'relaxed') parts.push('구름필터 없음'); if (strategyId === 'ICHIMOKU_SANYAKU') { - if (level === 'strict') parts.push('후행구름·MA60·거래량150%'); - else if (level === 'balanced') parts.push('구름돌파·양운·후행'); - else parts.push('구름돌파·핵심만'); + if (level === 'strict') parts.push('삼역 전체·MA60·거래량150%'); + else if (level === 'balanced') parts.push('구름돌파·(전환>기준 OR 양운)'); + else parts.push('구름돌파·종가>전환'); } if (strategyId === 'MACD_MOMENTUM' && level === 'relaxed') parts.push('EMA60·ADX 없음'); if (strategyId === 'MA_TREND' && level === 'relaxed') parts.push('ADX 없음');