Coverage for strategies / GapUpPullback_strategy.py: 100%
38 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-04 15:08 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-04 15:08 +0000
1# strategies/GapUpPullback_strategy.py
3import logging
4from interfaces.strategy import Strategy
5from typing import List, Dict, Optional
6from core.logger import get_strategy_logger
8class GapUpPullbackStrategy(Strategy):
9 def __init__(
10 self,
11 broker,
12 min_gap_rate: float = 5.0,
13 max_pullback_rate: float = 2.0,
14 rebound_rate: float = 2.0,
15 mode: str = "live",
16 logger: Optional[logging.Logger] = None
17 ):
18 self.broker = broker
19 self.min_gap_rate = min_gap_rate
20 self.max_pullback_rate = max_pullback_rate
21 self.rebound_rate = rebound_rate
22 self.mode = mode
23 if logger:
24 self.logger = logger
25 else:
26 self.logger = get_strategy_logger("GapUpPullback")
28 async def run(self, stock_codes: List[str]) -> Dict:
29 selected = []
30 rejected = []
32 for code in stock_codes:
33 summary = await self.broker.get_price_summary(code)
34 name = await self.broker.get_name_by_code(code)
35 display = f"{name}({code})" if name else code
37 previous_close = summary.get("prev_close")
38 open_price = summary.get("open")
39 low = summary.get("low")
40 current = summary.get("current")
42 if not all([previous_close, open_price, low, current]):
43 self.logger.warning(f"[데이터 누락] {display} - 필수 가격 정보 없음")
44 continue
46 gap_up = (open_price - previous_close) / previous_close * 100
47 pullback = (open_price - low) / open_price * 100
48 rebound = (current - low) / low * 100
50 is_candidate = (
51 gap_up >= self.min_gap_rate and
52 pullback >= self.max_pullback_rate and
53 rebound >= self.rebound_rate
54 )
56 if is_candidate:
57 selected.append({"code": code, "name": name})
58 self.logger.info(
59 f"[후보 선정] {display} | 전일종가: {previous_close} | 시가: {open_price} | 저가: {low} | 종가: {current} | "
60 f"갭상승: {gap_up:.2f}% | 눌림: {pullback:.2f}% | 반등: {rebound:.2f}%"
61 )
62 else:
63 rejected.append({"code": code, "name": name})
64 self.logger.info(
65 f"[제외] {display} | 전일종가: {previous_close} | 시가: {open_price} | 저가: {low} | 종가: {current} | "
66 f"갭상승: {gap_up:.2f}% | 눌림: {pullback:.2f}% | 반등: {rebound:.2f}%"
67 )
69 return {
70 "gapup_pullback_selected": selected,
71 "gapup_pullback_rejected": rejected
72 }