From 5a452e4714d9cbccfdcee22e946a63010985d041 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=A8=B8=EB=8B=88=ED=8E=98=EB=8B=88?= Date: Thu, 14 May 2026 23:32:59 +0900 Subject: [PATCH] fix: harden screening data collection --- .../services/collectors/price_collector.py | 4 +++- .../services/collectors/stock_collector.py | 5 +++- backend/jobs/screening_job.py | 24 +++++++++++++------ 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/backend/app/services/collectors/price_collector.py b/backend/app/services/collectors/price_collector.py index 1eaa4fe..2ee7dd0 100644 --- a/backend/app/services/collectors/price_collector.py +++ b/backend/app/services/collectors/price_collector.py @@ -240,7 +240,9 @@ class PriceCollector(BaseCollector): logger.info("Collecting stock prices via KRX Open API") total = self._collect_openapi() logger.info(f"Collected {total} price records via Open API") - return total + if total > 0: + return total + logger.warning("KRX Open API returned 0 price records, falling back to pykrx") except Exception as e: logger.warning(f"KRX Open API failed, falling back to pykrx: {e}") diff --git a/backend/app/services/collectors/stock_collector.py b/backend/app/services/collectors/stock_collector.py index 72dde9a..041fcea 100644 --- a/backend/app/services/collectors/stock_collector.py +++ b/backend/app/services/collectors/stock_collector.py @@ -224,7 +224,10 @@ class StockCollector(BaseCollector): if client: try: logger.info("Collecting stock data via KRX Open API") - return self._collect_openapi() + total = self._collect_openapi() + if total > 0: + return total + logger.warning("KRX Open API returned 0 stock records, falling back to pykrx") except Exception as e: logger.warning(f"KRX Open API failed, falling back to pykrx: {e}") diff --git a/backend/jobs/screening_job.py b/backend/jobs/screening_job.py index f72f601..9d7fe23 100644 --- a/backend/jobs/screening_job.py +++ b/backend/jobs/screening_job.py @@ -3,6 +3,7 @@ from collections import defaultdict from datetime import date, datetime, time as dtime, timedelta from decimal import Decimal from typing import Iterable +from zoneinfo import ZoneInfo import pandas as pd from sqlalchemy import func @@ -16,6 +17,15 @@ logger = logging.getLogger(__name__) LOOKBACK_CALENDAR_DAYS = 430 MIN_HISTORY_ROWS = 20 +KST = ZoneInfo("Asia/Seoul") + + +def _now_kst() -> datetime: + return datetime.now(KST) + + +def _today_kst() -> date: + return _now_kst().date() def _as_float(value, default: float = 0.0) -> float: @@ -256,7 +266,7 @@ def run_morning_screening(): def run_intraday_exit_check(): """09:05~15:20 KST - Check exits and optional auto entries.""" - now = datetime.now() + now = _now_kst() if not (dtime(9, 5) <= now.time() <= dtime(15, 20)): logger.info("Outside trading window (09:05~15:20), skipping trade check") return @@ -280,7 +290,7 @@ def run_intraday_exit_check(): if not pos: continue - latest_date = _latest_price_date(db, market="KOSPI") or date.today() + latest_date = _latest_price_date(db, market="KOSPI") or _today_kst() rows = ( db.query(Price) .filter( @@ -316,7 +326,7 @@ def run_intraday_exit_check(): sell_qty = max(1, int(pos.qty * exit_signal["sell_ratio"])) order = executor.place_sell_order(signal.ticker, sell_qty) db.add(AutoOrder( - order_date=datetime.now(), + order_date=_now_kst().replace(tzinfo=None), ticker=signal.ticker, order_type="sell", qty=sell_qty, @@ -327,7 +337,7 @@ def run_intraday_exit_check(): )) if order.success and exit_signal["sell_ratio"] >= 1.0: signal.status = "closed" - signal.exit_date = date.today() + signal.exit_date = _today_kst() signal.exit_price = current_price if not getattr(settings, "screening_auto_trade_enabled", False): @@ -347,7 +357,7 @@ def run_intraday_exit_check(): for signal in watching: if signal.ticker in open_tickers: continue - latest_date = _latest_price_date(db, market="KOSPI") or date.today() + latest_date = _latest_price_date(db, market="KOSPI") or _today_kst() rows = ( db.query(Price) .filter( @@ -376,7 +386,7 @@ def run_intraday_exit_check(): order = executor.place_buy_order(signal.ticker, qty, int(entry["entry_price"])) db.add(AutoOrder( - order_date=datetime.now(), + order_date=_now_kst().replace(tzinfo=None), ticker=signal.ticker, order_type="buy", qty=qty, @@ -407,7 +417,7 @@ def run_closing_review(): logger.info("Starting closing review") db = SessionLocal() try: - today = date.today() + today = _today_kst() stale_before = today - timedelta(days=7) stale_signals = (