diff --git a/backend/app/services/day_trading_strategy.py b/backend/app/services/day_trading_strategy.py index 1102314..e1a0fd9 100644 --- a/backend/app/services/day_trading_strategy.py +++ b/backend/app/services/day_trading_strategy.py @@ -32,7 +32,9 @@ MAX_CAPITAL_RATIO = 0.10 # 종목당 최대 10% MAX_DAY_TRADE_RATIO = 0.25 # 전체 데이트레이딩 최대 25% MAX_POSITIONS = 3 # 동시 최대 3종목 DAILY_LOSS_LIMIT = -0.03 # 일일 손실 한도 -3% -STOP_LOSS_PCT = 0.02 # 종목 손절 -2% +STOP_LOSS_PCT = 0.02 # ORB 손절 -2% +STOP_LOSS_PCT_REVERSION = 0.025 # VWAP/RSI_BB 평균회귀 전략 손절 -2.5% +# ↑ ORB(-2%)보다 약간 넓게 잡되, 일일 손실 한도(-3%) 이내에서 단일 포지션이 소화 가능하도록 설정 TRAILING_TRIGGER = 0.015 # 트레일링 발동 +1.5% TRAILING_ATR_MULT = 1.0 # 트레일링 ATR 배수 ORB_BREAKOUT_MARGIN = 0.002 # ORB 돌파 최소 여유 +0.2% @@ -301,7 +303,7 @@ def check_vwap_bounce_signal( atr = _atr(candles_5min, 14) raw_stop = (current_price - 1.5 * atr) if atr else current_price * (1 - STOP_LOSS_PCT) - stop_loss = max(raw_stop, current_price * (1 - STOP_LOSS_PCT * 2), 1.0) + stop_loss = max(raw_stop, current_price * (1 - STOP_LOSS_PCT_REVERSION), 1.0) # 하드 스탑 -2.5% (일일 손실 한도 -3% 이내) target = (current_price + 2.0 * atr) if atr else vwap * 1.03 return DayTradeSignal( @@ -359,7 +361,7 @@ def check_rsi_bb_signal( atr = _atr(candles_5min, 14) raw_stop = (current_price - 1.5 * atr) if atr else current_price * (1 - STOP_LOSS_PCT) - stop_loss = max(raw_stop, current_price * (1 - STOP_LOSS_PCT * 2), 1.0) + stop_loss = max(raw_stop, current_price * (1 - STOP_LOSS_PCT_REVERSION), 1.0) # 하드 스탑 -2.5% (일일 손실 한도 -3% 이내) # 최소 1.5R 보장: bb_middle이 너무 가까우면 R:R 확보를 위해 목표 상향 risk = current_price - stop_loss target = max(bb_middle, current_price + risk * 1.5) @@ -429,14 +431,16 @@ def check_day_trade_exit( atr = _atr(candles_5min, 14) if atr: trailing_stop = highest_since_entry - TRAILING_ATR_MULT * atr - if current_price <= trailing_stop: - return DayTradeExit( - action="SELL_ALL", - reason=( - f"트레일링 스탑: 최고={highest_since_entry:.0f}, " - f"트레일={trailing_stop:.0f}, 현재={current_price:.0f}" - ), - ) + else: + trailing_stop = highest_since_entry * (1 - 0.02) # fallback: 최고가 -2% (5분봉 데이터 부족 시) + if current_price <= trailing_stop: + return DayTradeExit( + action="SELL_ALL", + reason=( + f"트레일링 스탑: 최고={highest_since_entry:.0f}, " + f"트레일={trailing_stop:.0f}, 현재={current_price:.0f}" + ), + ) # 5. 과매수 청산 (RSI ≥ 70) rsi = _rsi(candles_5min, 14) diff --git a/backend/app/services/kosdaq_scanner.py b/backend/app/services/kosdaq_scanner.py index 40f63f8..e0e6ec6 100644 --- a/backend/app/services/kosdaq_scanner.py +++ b/backend/app/services/kosdaq_scanner.py @@ -147,7 +147,7 @@ class KosdaqScanner: volume_ratio = td.volume / volume_ma20 rsi_score = max(0.0, (25.0 - rsi14) / 25.0) - bb_score = max(0.0, (0.0 - pct_vs_lower) / 3.0) + bb_score = min(max(0.0, (0.0 - pct_vs_lower) / 3.0), 1.0) # 상한 1.0 — 폭락 종목 점수 왜곡 방지 volume_score = min(volume_ratio, 2.0) total_score = rsi_score * 0.5 + bb_score * 0.3 + volume_score * 0.2 diff --git a/backend/app/services/mean_reversion_strategy.py b/backend/app/services/mean_reversion_strategy.py index b86bb36..ce33ded 100644 --- a/backend/app/services/mean_reversion_strategy.py +++ b/backend/app/services/mean_reversion_strategy.py @@ -404,6 +404,7 @@ def calculate_position_size_v2( base = calculate_position_size( total_assets, available_cash, current_strategy_allocation, regime, max_per_position, max_strategy_capital_bull, max_strategy_capital_bear, + rsi=None, adx=None, # v2에서 자체 보정하므로 base는 배율 없이 계산 (이중 보정 방지) ) if base <= 0: return 0.0 diff --git a/backend/app/services/short_term_strategy.py b/backend/app/services/short_term_strategy.py index 7588d17..978608e 100644 --- a/backend/app/services/short_term_strategy.py +++ b/backend/app/services/short_term_strategy.py @@ -163,8 +163,10 @@ class ShortTermStrategy: return None # 조건 5: MA5 상향 (Fake-out 방지) + # BB 하단 ±1% 구간에서는 최근 급락으로 MA5가 눌려있을 수 있으므로 완화 허용 + # (close < bb_lower * 0.99 → BB 하단을 크게 이탈 중 = 완전 차단, 그 외는 허용) ma5 = sum(m15_closes[-5:]) / 5 - if close <= ma5: + if close < ma5 and close < bb_lower * 0.99: return None # 조건 6: 거래량 급증 (1.5배 강화)