코드 변경

This commit is contained in:
2026-03-10 09:49:03 +09:00
parent a15ecaa314
commit 096e087a9a
8 changed files with 930 additions and 290 deletions

View File

@@ -31,7 +31,7 @@ sys.path.insert(0, str(Path(__file__).parent))
from app.core.config import settings
from app.core.database import AsyncSessionLocal # 🔧 공통 DB 모듈 사용
from sqlalchemy import select as sa_select
from sqlalchemy import select as sa_select, func as sa_func
from app.models.models import StockDailyPrice
from app.services.analysis_db_service import bulk_upsert_analysis, passes_price_filter, run_db_scanner # 🔧 단일화
from app.services.market_analyzer import analyze_stock_for_batch_db, StockRecommendation
@@ -118,14 +118,64 @@ async def detect_and_log_market_regime() -> str:
# 2. 종목 목록 조회
# -------------------------------------------------------------------
async def _get_stocks_from_db(limit: int = 500) -> list[tuple[str, str]]:
"""stock_daily_prices DB에서 종목 목록 조회 (KIS API 실패 시 폴백).
최근 거래일 기준으로 데이터가 있는 종목을 조회한다.
ETF / 레버리지 / 인버스 / 스팩 등 특수종목은 이름 기반으로 제외.
"""
SKIP_KEYWORDS = [
"ETF", "레버리지", "인버스", "KODEX", "TIGER", "ARIRANG",
"KBSTAR", "HANARO", "SOL", "ACE", "KOSEF", "KINDEX", "RISE",
"선물", "스팩", "리츠", "SPAC",
]
async with AsyncSessionLocal() as db:
# 최근 거래일 조회
latest_date_result = await db.execute(
sa_select(sa_func.max(StockDailyPrice.trade_date))
)
latest_date = latest_date_result.scalar()
if not latest_date:
logger.warning("⚠️ stock_daily_prices 테이블이 비어 있음")
return []
logger.info("📂 DB 폴백: %s 기준 종목 조회", latest_date)
# 최근 거래일 종목 전체 조회 (종목코드, 종목명)
result = await db.execute(
sa_select(StockDailyPrice.stock_code, StockDailyPrice.stock_name)
.where(StockDailyPrice.trade_date == latest_date)
.where(StockDailyPrice.close_price > 5000)
.order_by(StockDailyPrice.volume.desc())
.limit(limit * 3) # 필터 후 충분한 수가 남도록 여유분 조회
)
rows = result.all()
stocks = []
for code, name in rows:
if not name:
continue
name_upper = name.upper()
if any(kw.upper() in name_upper for kw in SKIP_KEYWORDS):
continue
stocks.append((code, name))
if len(stocks) >= limit:
break
logger.info("📂 DB 폴백 결과: %d개 종목 (%s 기준)", len(stocks), latest_date)
return stocks
async def get_all_tradable_stocks(limit: int = 500) -> list[tuple[str, str]]:
"""KIS 거래량 순위 API로 거래 가능 종목 조회 (KOSPI+KOSDAQ 각 top 40).
KIS FHPST01710000: 거래량 순위 — 5,000원 이상 활성 종목.
ETF / 레버리지 / 스팩 등 특수종목 자동 제외.
Raises:
RuntimeError: 조회 결과가 MIN_STOCK_COUNT(50) 미만인 경우 배치 중단
KIS API 결과가 MIN_STOCK_COUNT(50) 미만이면 stock_daily_prices DB 폴백을 사용한다.
DB 폴백도 실패하면 RuntimeError를 발생시켜 배치를 중단한다.
"""
from app.services.market_analyzer import get_all_tradable_stocks as _kis_get_stocks
@@ -133,10 +183,21 @@ async def get_all_tradable_stocks(limit: int = 500) -> list[tuple[str, str]]:
stocks = await _kis_get_stocks()
if len(stocks) < MIN_STOCK_COUNT:
raise RuntimeError(
f"종목 조회 실패: KIS API 결과 부족 ({len(stocks)}개 < {MIN_STOCK_COUNT}개). "
f"KIS API 설정을 확인하세요."
logger.warning(
"⚠️ KIS API 결과 부족 (%d개 < %d개) — DB 폴백 사용",
len(stocks), MIN_STOCK_COUNT,
)
db_stocks = await _get_stocks_from_db(limit)
if len(db_stocks) < MIN_STOCK_COUNT:
raise RuntimeError(
f"종목 조회 실패: KIS API({len(stocks)}개) 및 DB 폴백({len(db_stocks)}개) 모두 "
f"최소 기준({MIN_STOCK_COUNT}개) 미달. KIS API 설정 및 DB 데이터를 확인하세요."
)
result = db_stocks[:limit]
logger.info("✅ DB 폴백 선정 %d개", len(result))
return result
result = stocks[:limit]
logger.info("✅ 선정 %d개", len(result))