diff --git a/backend/app/services/technical_indicators.py b/backend/app/services/technical_indicators.py index bc0ce58..bdea914 100644 --- a/backend/app/services/technical_indicators.py +++ b/backend/app/services/technical_indicators.py @@ -37,7 +37,6 @@ def _passes_uptrend(closes: list, ma_short: int = TREND_MA_SHORT, ma_long: int = close = closes[-1] if ma_s is None or ma_l is None: return None - # 허용 오차 적용: 거의 골든크로스/이탈 직후도 허용 return (ma_s >= ma_l * (1 - tol)) and (close >= ma_s * (1 - tol)) @@ -158,6 +157,14 @@ def analyze_macd(chart_data: list) -> dict: def analyze_rsi(chart_data: list) -> dict: + """RSI 분석 + 볼린저 하단 결합 진입 신호 + + [2026-03 수정] RSI < 30 구간에서도 반등 확인 시 BUY 신호 발생 + - 기존: rsi < 30 → signal=None (항상 반등 대기만) + - 개선: rsi < 30 AND rsi > rsi_prev(반등 시작) AND BB 하단 근처 → BUY + → 이유: RSI 30 이하에서 반등이 시작된 시점이 핵심 진입 기회이며, + 30~45 구간으로 올라오기까지 기다리면 이미 3~5% 상승 후 진입 + """ rsi_period = settings.RSI_PERIOD # 기본값 14 if len(chart_data) < rsi_period + 1: return {"signal": None, "description": "데이터 부족"} @@ -183,6 +190,7 @@ def analyze_rsi(chart_data: list) -> dict: rs = avg_gain / avg_loss rsi = 100 - (100 / (1 + rs)) + # 직전 RSI 계산 (반등 확인용) if len(chart_data) >= rsi_period + 2: prev_prices = [float(item.get("stck_clpr", 0)) for item in chart_data[-(rsi_period+2):-(1)]] prev_gains = [] @@ -205,9 +213,37 @@ def analyze_rsi(chart_data: list) -> dict: rsi_prev = 100 - (100 / (1 + prev_rs)) else: rsi_prev = rsi - + + # [2026-03] RSI < 30 구간: 반등 시작 + BB 하단 근처 시 BUY if rsi < 30: - return {"signal": None, "description": f"RSI 과매도 심화 ({rsi:.1f}) - 반등 대기"} + rsi_rebounding = rsi > rsi_prev # RSI 바닥 찍고 반등 중 + + if rsi_rebounding: + # 볼린저 하단 근처 확인 (-3% ~ +1.5%) + bb_lower = None + if len(chart_data) >= 20: + bb_closes = [float(item.get("stck_clpr", 0)) for item in chart_data[-20:]] + bb_mid = sum(bb_closes) / 20 + bb_std = (sum((p - bb_mid) ** 2 for p in bb_closes) / 20) ** 0.5 + bb_lower = bb_mid - 2 * bb_std + + close = float(chart_data[-1].get("stck_clpr", 0)) + if bb_lower and bb_lower * 0.97 <= close <= bb_lower * 1.015: + return { + "signal": "buy", + "description": f"RSI 바닥 탈출 반등 ({rsi_prev:.1f}→{rsi:.1f}) + BB하단" + } + else: + # BB 하단 근처는 아니지만 RSI 반등 중: 약한 신호 + return { + "signal": None, + "description": f"RSI 과매도 반등 중 ({rsi_prev:.1f}→{rsi:.1f}) - BB하단 대기" + } + else: + # RSI 여전히 하락 중 → 낙하하는 칼 잡기 방지 + return {"signal": None, "description": f"RSI 과매도 심화 ({rsi:.1f}) - 반등 대기"} + + # 기존 30~45 반등 구간 로직 유지 elif 30 <= rsi <= 45 and rsi > rsi_prev: return {"signal": "buy", "description": f"RSI 바닥 탈출 반등 ({rsi:.1f})"} elif rsi > 70: