공휴일 OHLCV 버그 수정 + 진입 신호 진단 로그 추가

- OHLCV 조회를 단일일→7일 범위로 변경해 공휴일(대체공휴일 등) 자동 처리
  (5/25 대체공휴일로 전 종목 목표가 0개 → 오늘 하루 종일 0건 원인)
- 목표가 계산 결과 DEBUG→INFO 레벨 격상 (종목별 목표가·시가 표시)
- 목표가 제외 이유 INFO 로그 추가 (전일 데이터 없음 / 거래대금 미달)
- check_entries에 5분마다 진단 로그 추가 (신호 거절 이유 전 종목 출력)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-26 14:10:54 +09:00
parent 6095b4c7fa
commit 96b710baf0
2 changed files with 48 additions and 11 deletions
+39 -9
View File
@@ -98,6 +98,7 @@ class StockBot:
self._midday_ctx_mtime : float = 0.0 self._midday_ctx_mtime : float = 0.0
self._midday_pos_mult : float = 1.0 # midday position_size_multiplier self._midday_pos_mult : float = 1.0 # midday position_size_multiplier
self._midday_loaded : bool = False self._midday_loaded : bool = False
self._last_diag : float = 0.0 # 신호 진단 로그 마지막 시각
mode = "모의투자" if self.kis.is_mock else "실거래" mode = "모의투자" if self.kis.is_mock else "실거래"
dry = " [DRY_RUN]" if os.getenv("DRY_RUN","true")=="true" else "" dry = " [DRY_RUN]" if os.getenv("DRY_RUN","true")=="true" else ""
@@ -262,12 +263,11 @@ class StockBot:
self.universe = tickers self.universe = tickers
logger.info(f"유니버스: {len(tickers)}종목 (ETF 제외)") logger.info(f"유니버스: {len(tickers)}종목 (ETF 제외)")
# 전일 날짜 계산 # 최근 7일 범위 조회 → 공휴일·대체공휴일 자동 처리
from datetime import timedelta from datetime import timedelta
today = datetime.now() today = datetime.now()
# 월요일이면 금요일로 start_dt = (today - timedelta(days=7)).strftime("%Y%m%d")
offset = 3 if today.weekday() == 0 else 1 end_dt = (today - timedelta(days=1)).strftime("%Y%m%d")
prev_date = (today - timedelta(days=offset)).strftime("%Y%m%d")
for ticker in self.universe: for ticker in self.universe:
# 이미 전일 데이터 있으면 skip # 이미 전일 데이터 있으면 skip
@@ -276,11 +276,11 @@ class StockBot:
try: try:
ohlcv = await self.kis.get_ohlcv_daily( ohlcv = await self.kis.get_ohlcv_daily(
ticker, ticker,
start=prev_date, start=start_dt,
end=prev_date, end=end_dt,
) )
if ohlcv: if ohlcv:
prev = ohlcv[-1] prev = ohlcv[-1] # 가장 최근 거래일
self.strategy.set_prev_data( self.strategy.set_prev_data(
ticker, ticker,
high = prev["high"], high = prev["high"],
@@ -288,6 +288,8 @@ class StockBot:
amount= prev.get("amount", amount= prev.get("amount",
prev.get("volume", 0) * prev.get("close", 0)) prev.get("volume", 0) * prev.get("close", 0))
) )
else:
logger.warning(f"전일 OHLCV 없음 {ticker} ({start_dt}~{end_dt})")
except Exception as e: except Exception as e:
logger.warning(f"전일 데이터 실패 {ticker}: {e}") logger.warning(f"전일 데이터 실패 {ticker}: {e}")
await asyncio.sleep(1.1) # 초당 2.5건으로 제한 await asyncio.sleep(1.1) # 초당 2.5건으로 제한
@@ -302,16 +304,20 @@ class StockBot:
async def calc_targets(self): async def calc_targets(self):
"""당일 시가 기반 목표가 계산""" """당일 시가 기반 목표가 계산"""
logger.info("목표가 계산 시작") logger.info("목표가 계산 시작")
valid_count = 0
for ticker in self.universe: for ticker in self.universe:
try: try:
price_info = await self.kis.get_price(ticker) price_info = await self.kis.get_price(ticker)
self.strategy.set_today_open(ticker, price_info["open"]) self.strategy.set_today_open(ticker, price_info["open"])
target = self.strategy.get_target(ticker) target = self.strategy.get_target(ticker)
name = self.ticker_names.get(ticker, ticker)
if target > 0: if target > 0:
logger.debug(f"{ticker} 목표가: {target:,.0f}") logger.info(f"목표가: {name}({ticker}) {target:,.0f} [시가 {price_info['open']:,}]")
valid_count += 1
await asyncio.sleep(1.1) await asyncio.sleep(1.1)
except Exception as e: except Exception as e:
logger.warning(f"시가 수집 실패 {ticker}: {e}") logger.warning(f"시가 수집 실패 {ticker}: {e}")
logger.info(f"목표가 계산 완료: {valid_count}/{len(self.universe)}종목 유효")
# ───────────────────────────────────────── # ─────────────────────────────────────────
# 메인 매매 루프 (09:00~14:50) # 메인 매매 루프 (09:00~14:50)
@@ -398,15 +404,27 @@ class StockBot:
# lunch_trade_allowed=false이면 점심 세션 진입 차단 # lunch_trade_allowed=false이면 점심 세션 진입 차단
if self._midday_loaded and not self.strategy.context.get("lunch_trade_allowed", True): if self._midday_loaded and not self.strategy.context.get("lunch_trade_allowed", True):
return return
_now_ts = datetime.now().timestamp()
_do_diag = (_now_ts - self._last_diag) >= 300 # 5분마다 진단 로그
_diag = []
for ticker in self.universe: for ticker in self.universe:
if ticker in self.positions: if ticker in self.positions:
if _do_diag:
_diag.append(f"{ticker}:보유중")
continue continue
if ticker in self.sl_tickers: if ticker in self.sl_tickers:
if _do_diag:
_diag.append(f"{ticker}:SL차단")
continue # 당일 SL 종목 재진입 차단 continue # 당일 SL 종목 재진입 차단
if len(self.positions) >= MAX_POSITIONS: if len(self.positions) >= MAX_POSITIONS:
break break
# 목표가 미계산 종목 스킵 (불필요한 API 호출 방지) # 목표가 미계산 종목 스킵 (불필요한 API 호출 방지)
if self.strategy.get_target(ticker) <= 0: target = self.strategy.get_target(ticker)
if target <= 0:
if _do_diag:
_diag.append(f"{ticker}:목표가없음")
continue continue
try: try:
@@ -421,6 +439,11 @@ class StockBot:
) )
if not signal["signal"]: if not signal["signal"]:
if _do_diag:
_diag.append(
f"{name}({ticker}):{signal['reason']}"
f"[현재가{current:,}/목표가{target:,.0f}]"
)
continue continue
balance = await self.kis.get_balance() balance = await self.kis.get_balance()
@@ -470,6 +493,13 @@ class StockBot:
except Exception as e: except Exception as e:
logger.error(f"진입 체크 오류 {ticker}: {type(e).__name__}: {e}") logger.error(f"진입 체크 오류 {ticker}: {type(e).__name__}: {e}")
if _do_diag:
self._last_diag = _now_ts
if _diag:
logger.info(f"[신호진단] {' | '.join(_diag)}")
else:
logger.info("[신호진단] 전 종목 신호 없음 (유니버스 비어있거나 모두 필터됨)")
# ───────────────────────────────────────── # ─────────────────────────────────────────
# 청산 체크 # 청산 체크
# ───────────────────────────────────────── # ─────────────────────────────────────────
+8 -1
View File
@@ -98,7 +98,14 @@ class VolatilityBreakout:
def set_today_open(self, ticker: str, open_price: float): def set_today_open(self, ticker: str, open_price: float):
"""당일 시가로 목표가 계산""" """당일 시가로 목표가 계산"""
prev = self.prev_data.get(ticker) prev = self.prev_data.get(ticker)
if not prev or prev["amount"] < MIN_TRADE_AMOUNT: if not prev:
logger.info(f"목표가 제외({ticker}): 전일 데이터 없음")
return
if prev["amount"] < MIN_TRADE_AMOUNT:
logger.info(
f"목표가 제외({ticker}): 전일 거래대금 {prev['amount']/1e8:.0f}"
f" < 기준 {MIN_TRADE_AMOUNT/1e8:.0f}"
)
return return
prev_range = prev["high"] - prev["low"] prev_range = prev["high"] - prev["low"]