diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 28bbaac..4f1e850 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -66,7 +66,8 @@ "Bash(ssh:*)", "Bash(find /Users/commylife/myproject/java/workspace/auto-stock/frontend/src -type f \\\\\\(-name *.tsx -o -name *.ts \\\\\\))", "Bash(git push:*)", - "Bash(git fetch:*)" + "Bash(git fetch:*)", + "Bash(sqlite3 /Users/commylife/myproject/java/workspace/auto-stock/data/trading_db.sqlite \"SELECT sql FROM sqlite_master WHERE type=''table'' AND name=''trade_history'';\")" ] }, "enableAllProjectMcpServers": true, diff --git a/backend/app/core/config.py b/backend/app/core/config.py index 6a5cee8..cc169de 100644 --- a/backend/app/core/config.py +++ b/backend/app/core/config.py @@ -206,8 +206,8 @@ class Settings(BaseSettings): DAY_TRADE_ORB_VOL_MULT: float = 1.5 # ORB 돌파 거래량 배수 DAY_TRADE_TRAILING_TRIGGER: float = 0.01 # 트레일링 발동 최소 수익률 (1%) DAY_TRADE_RSI_BREAKEVEN_THRESHOLD: float = 40.0 # RSI_BB 진입 기준 (RSI ≤ 40) - DAY_TRADE_ORB_STOP_LOSS_PCT: float = 0.02 # ORB 손절 -2% - DAY_TRADE_REVERSION_STOP_LOSS_PCT: float = 0.025 # VWAP/RSI_BB 손절 -2.5% + DAY_TRADE_ORB_STOP_LOSS_PCT: float = 0.03 # ORB 손절 -3% (기존 2% → 노이즈 손절 방지) + DAY_TRADE_REVERSION_STOP_LOSS_PCT: float = 0.035 # VWAP/RSI_BB 손절 -3.5% (기존 2.5% → 반등 여유 확보) DAY_TRADE_TRAILING_ATR_MULT: float = 1.5 # 트레일링 ATR 배수 (1.0→1.5 whipsaw 방지) DAY_TRADE_ORB_BREAKOUT_MARGIN: float = 0.002 # ORB 돌파 최소 여유 +0.2% DAY_TRADE_COST_PCT: float = 0.005 # 수수료+슬리피지 합산 (매수+매도) @@ -225,13 +225,13 @@ class Settings(BaseSettings): DAY_TRADE_ORB_ATR_FLOOR_MULT: float = 1.0 # ORB ATR 손절 기본 배수 DAY_TRADE_VWAP_ATR_MULT: float = 1.5 # VWAP ATR 손절 배수 DAY_TRADE_RSI_BB_ATR_MULT: float = 1.5 # RSI_BB ATR 손절 배수 - DAY_TRADE_TARGET_MULTIPLIER: float = 2.0 # 목표가 = entry + risk × 2.0R + DAY_TRADE_TARGET_MULTIPLIER: float = 1.5 # 목표가 = entry + risk × 1.5R (기존 2.0 → 현실적 목표) DAY_TRADE_TIME_STOP_MINUTES: int = 30 # 시간 손절 (30분) - DAY_TRADE_TIME_STOP_MIN_GAIN: float = 0.005 # 시간 손절 최소 수익 +0.5% (수수료 커버) + DAY_TRADE_TIME_STOP_MIN_GAIN: float = -0.003 # 시간 손절: -0.3% 미만 시 청산 (기존 +0.5% → 수익 중이면 보유 유지) DAY_TRADE_ORB_MARGIN_CHECK: float = 0.03 # ORB 목표가 최소 마진 (3%) DAY_TRADE_TRAILING_DEFAULT: float = 0.02 # 트레일링 기본값 -2% (ATR 없을 때) DAY_TRADE_TRAILING_MIN_HOLD_SEC: int = 300 # 트레일링 활성화 최소 보유 시간 (초, 5분) - DAY_TRADE_MIN_STOP_LOSS_PCT: float = 0.01 # 최소 손절폭 -1.0% (너무 타이트한 ATR 손절 방지) + DAY_TRADE_MIN_STOP_LOSS_PCT: float = 0.02 # 최소 손절폭 -2.0% (기존 1.0% → 노이즈 손절 방지) # ═════════════════════════════════════════════════════════════════════════════ # 코스닥 스캐너 (섹션 7.1) diff --git a/backend/app/services/day_trading_strategy.py b/backend/app/services/day_trading_strategy.py index 237ec59..1aaf399 100644 --- a/backend/app/services/day_trading_strategy.py +++ b/backend/app/services/day_trading_strategy.py @@ -341,17 +341,14 @@ def check_orb_signal( # [P1] ORB 손절: ATR 하한 + 하드 스탑 상한 클램프 atr_orb = _atr(aggregate_to_5min(candles_1min), 14) - hard_stop = current_price * (1 - settings.DAY_TRADE_REVERSION_STOP_LOSS_PCT) # 상한 클램프 orb_stop = max(orb.low, current_price * (1 - settings.DAY_TRADE_ORB_STOP_LOSS_PCT)) # ATR 기반 하한: 최소 ATR 손절폭 확보 (수수료 커버) if atr_orb: atr_floor = current_price - settings.DAY_TRADE_ORB_ATR_FLOOR_MULT * atr_orb orb_stop = max(orb_stop, atr_floor) - # 상한 클램프: 일일 손실 한도 대비 종목당 최대 한도 - stop_loss = max(orb_stop, hard_stop) - # 최소 손절폭 보장: ATR/ORB가 너무 좁으면 노이즈에 즉시 손절 → 최소 -1.0% 확보 + # 최소 손절폭 보장: 노이즈 손절 방지 min_stop = current_price * (1 - settings.DAY_TRADE_MIN_STOP_LOSS_PCT) - stop_loss = min(stop_loss, min_stop) + stop_loss = min(orb_stop, min_stop) risk = current_price - stop_loss if risk <= 0: return None # 손절폭이 0 이하면 진입 무의미 @@ -412,12 +409,11 @@ def check_vwap_bounce_signal( return None atr = _atr(candles_5min, 14) - raw_stop = (current_price - settings.DAY_TRADE_VWAP_ATR_MULT * atr) if atr else current_price * (1 - settings.DAY_TRADE_ORB_STOP_LOSS_PCT) - hard_stop_vwap = current_price * (1 - settings.DAY_TRADE_REVERSION_STOP_LOSS_PCT) - stop_loss = max(raw_stop, hard_stop_vwap, 1.0) # 타이트한 쪽 = 진입가에 가까운 = 높은 가격, 최소 1원 보장 - # 최소 손절폭 보장: ATR이 너무 작으면 노이즈에 즉시 손절 → 최소 -1.0% 확보 + raw_stop = (current_price - settings.DAY_TRADE_VWAP_ATR_MULT * atr) if atr else current_price * (1 - settings.DAY_TRADE_REVERSION_STOP_LOSS_PCT) + # 최소 손절폭 보장: 노이즈 손절 방지 min_stop = current_price * (1 - settings.DAY_TRADE_MIN_STOP_LOSS_PCT) - stop_loss = min(stop_loss, min_stop) + stop_loss = min(raw_stop, min_stop) + stop_loss = max(stop_loss, 1.0) # 최소 1원 보장 target = (current_price + settings.DAY_TRADE_TARGET_MULTIPLIER * atr) if atr else vwap * 1.03 return DayTradeSignal( @@ -474,12 +470,11 @@ def check_rsi_bb_signal( return None atr = _atr(candles_5min, 14) - raw_stop = (current_price - settings.DAY_TRADE_RSI_BB_ATR_MULT * atr) if atr else current_price * (1 - settings.DAY_TRADE_ORB_STOP_LOSS_PCT) - hard_stop_rsi = current_price * (1 - settings.DAY_TRADE_REVERSION_STOP_LOSS_PCT) - stop_loss = max(raw_stop, hard_stop_rsi, 1.0) # 타이트한 쪽 = 진입가에 가까운 = 높은 가격, 최소 1원 보장 - # 최소 손절폭 보장: ATR이 너무 작으면 노이즈에 즉시 손절 → 최소 -1.0% 확보 + raw_stop = (current_price - settings.DAY_TRADE_RSI_BB_ATR_MULT * atr) if atr else current_price * (1 - settings.DAY_TRADE_REVERSION_STOP_LOSS_PCT) + # 최소 손절폭 보장: 노이즈 손절 방지 min_stop = current_price * (1 - settings.DAY_TRADE_MIN_STOP_LOSS_PCT) - stop_loss = min(stop_loss, min_stop) + stop_loss = min(raw_stop, min_stop) + stop_loss = max(stop_loss, 1.0) # 최소 1원 보장 # 최소 1.5R 보장: bb_middle이 너무 가까우면 R:R 확보를 위해 목표 상향 risk = current_price - stop_loss target = max(bb_middle, current_price + risk * 1.5) # 1.5R 보장