refactor(phase2): Add DayTradingStrategy example and indicators module

This commit is contained in:
2026-03-06 10:53:48 +09:00
parent 025ecb5f4f
commit 8920699eee
3 changed files with 350 additions and 0 deletions

View File

@@ -0,0 +1,181 @@
"""
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