""" Day Trading Strategy (Refactored) 기존 day_trading_strategy.py를 BaseStrategy 구조로 변환 전략: - ORB (Opening Range Breakout) - VWAP Bounce - RSI+BB 과매도 반등 """ import logging from typing import Optional, Dict, Any from datetime import datetime, time import pytz from app.strategy.base_strategy import BaseStrategy from app.strategy.signal import Signal, SignalType from app.core.config import settings logger = logging.getLogger(__name__) class DayTradingStrategy(BaseStrategy): """ 데이트레이딩 전략 (단기 스칼핑) 09:00~15:20 간 급등/급락 모멘텀 포착 """ DEFAULT_CONFIG = { "max_positions": 3, "max_capital_ratio": 0.1, # 종목당 10% "orb_breakout_margin": 0.002, # 0.2% 돌파 "stop_loss_pct": 0.025, # -2.5% "entry_cutoff": "14:30", # 신규 진입 마감 "eod_flat": "15:20", # EOD 청산 } def __init__(self, name: str = "DayTrading", config: Optional[Dict[str, Any]] = None): merged_config = {**self.DEFAULT_CONFIG, **(config or {})} super().__init__(name, merged_config) self.kst = pytz.timezone("Asia/Seoul") self.entry_cutoff = self._parse_time(self.config["entry_cutoff"]) self.eod_flat = self._parse_time(self.config["eod_flat"]) def _parse_time(self, time_str: str) -> time: """문자열 "HH:MM" → time 객체""" h, m = map(int, time_str.split(":")) return time(h, m) async def analyze( self, stock_code: str, market_data: Dict[str, Any], portfolio_data: Optional[Dict[str, Any]] = None ) -> Optional[Signal]: """ 시장 데이터 분석 Args: market_data: { "current_price": float, "minute_chart": List[dict], # 1분봉 "orderbook": dict, "stock_name": str, } portfolio_data: { "holding_quantity": int, "avg_price": float, } """ current_time = datetime.now(self.kst).time() # 1. 시간 체크 if current_time >= self.eod_flat: # EOD 청산 신호 if portfolio_data and portfolio_data.get("holding_quantity", 0) > 0: return Signal( stock_code=stock_code, signal_type=SignalType.SELL, strategy_name=self.name, timestamp=datetime.now(self.kst), quantity=portfolio_data["holding_quantity"], price=None, # 시장가 confidence=1.0, reason="EOD 강제 청산" ) return None if current_time >= self.entry_cutoff: # 신규 진입 차단 return None # 2. 현재 보유 중이면 청산 조건 체크 if portfolio_data and portfolio_data.get("holding_quantity", 0) > 0: exit_signal = self._check_exit( stock_code, market_data, portfolio_data ) if exit_signal: return exit_signal return None # 보유 유지 # 3. 신규 진입 검토 return self._check_entry(stock_code, market_data) def _check_entry(self, stock_code: str, market_data: Dict[str, Any]) -> Optional[Signal]: """신규 진입 신호 체크""" current_price = market_data.get("current_price") minute_chart = market_data.get("minute_chart", []) if not current_price or not minute_chart: return None # 예시: ORB 전략 (간단화) # 실제는 기존 check_orb_signal() 등을 호출 # 임시 구현: 단순 예시 if len(minute_chart) >= 30: # 09:00~09:30 데이터 있음 orb_high = max(c.get("high", 0) for c in minute_chart[:30]) if current_price > orb_high * (1 + self.config["orb_breakout_margin"]): stop_loss = current_price * (1 - self.config["stop_loss_pct"]) target = current_price * 1.03 # +3% 목표 return Signal( stock_code=stock_code, signal_type=SignalType.BUY, strategy_name=self.name, timestamp=datetime.now(self.kst), quantity=None, # OrderManager가 계산 price=current_price, confidence=0.75, stop_loss_price=stop_loss, take_profit_price=target, reason=f"ORB 돌파: {orb_high:.0f} → {current_price:.0f}", metadata={"strategy_type": "ORB"} ) return None def _check_exit(self, stock_code: str, market_data: Dict[str, Any], portfolio_data: Dict[str, Any]) -> Optional[Signal]: """청산 신호 체크""" current_price = market_data.get("current_price") avg_price = portfolio_data.get("avg_price") quantity = portfolio_data.get("holding_quantity") if not current_price or not avg_price or not quantity: return None # 손절 체크 stop_loss = avg_price * (1 - self.config["stop_loss_pct"]) if current_price <= stop_loss: return Signal( stock_code=stock_code, signal_type=SignalType.STOP_LOSS, strategy_name=self.name, timestamp=datetime.now(self.kst), quantity=quantity, price=None, # 시장가 confidence=1.0, reason=f"손절: {current_price:.0f} ≤ {stop_loss:.0f}" ) # 익절 체크 target = avg_price * 1.03 if current_price >= target: return Signal( stock_code=stock_code, signal_type=SignalType.TAKE_PROFIT, strategy_name=self.name, timestamp=datetime.now(self.kst), quantity=quantity, price=None, confidence=1.0, reason=f"목표가 도달: {current_price:.0f} ≥ {target:.0f}" ) return None