코드 변경
This commit is contained in:
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user