"""매수 기회 스캔 루프 - 60초마다 전체 시장 스캔 + 신호 종목 빠른 폴링.""" import logging import os import threading import time from core import trader from core.fng import get_fng from core.market import get_top_tickers from core.strategy import FNG_MAX_ENTRY, get_active_signals, should_buy logger = logging.getLogger(__name__) SCAN_INTERVAL = 60 # 전체 시장 스캔 주기 (초) SIGNAL_POLL_INTERVAL = int(os.getenv("SIGNAL_POLL_INTERVAL", "15")) # 신호 종목 빠른 감시 주기 (초) def _fast_poll_loop() -> None: """활성 신호 종목을 SIGNAL_POLL_INTERVAL 초마다 빠르게 체크. 신호 감지 후 전체 스캔 60초를 기다리지 않고, 신호 종목만 빠르게 감시하여 목표 임계값 도달 시 즉시 매수한다. """ logger.info(f"신호 감시 시작 (주기={SIGNAL_POLL_INTERVAL}초)") while True: try: signals = get_active_signals() if signals: positions = trader.get_positions() for ticker in list(signals): if ticker in positions: continue if len(positions) >= trader.MAX_POSITIONS: break try: if should_buy(ticker): vol_r = signals.get(ticker, {}).get("vol_ratio", 0.0) logger.info(f"[빠른감시] 매수 신호: {ticker} (vol={vol_r:.1f}x)") trader.buy(ticker, vol_ratio=vol_r) time.sleep(0.1) except Exception as e: logger.error(f"[빠른감시] 오류 {ticker}: {e}") except Exception as e: logger.error(f"신호 감시 루프 오류: {e}") time.sleep(SIGNAL_POLL_INTERVAL) def run_scanner() -> None: """메인 스캔 루프.""" # 신호 종목 빠른 감시 스레드 시작 t = threading.Thread(target=_fast_poll_loop, daemon=True, name="signal-fast-poll") t.start() logger.info(f"스캐너 시작 (스캔={SCAN_INTERVAL}초 | 신호감시={SIGNAL_POLL_INTERVAL}초)") while True: try: # 포지션 꽉 찼으면 스캔 스킵 if len(trader.get_positions()) >= trader.MAX_POSITIONS: logger.info("포지션 최대치 도달, 스캔 스킵") time.sleep(SCAN_INTERVAL) continue # F&G 탐욕/극탐욕 구간 → 전체 스캔 스킵 (strategy.py와 동일 기준) fv = get_fng() fng_label = ( "극탐욕" if fv >= 76 else "탐욕" if fv >= 56 else "중립" if fv >= 46 else "약공포" if fv >= 41 else "공포" if fv >= 26 else "극공포" ) if fv > FNG_MAX_ENTRY: logger.info( f"[F&G차단] F&G={fv} ({fng_label}) > {FNG_MAX_ENTRY} — 탐욕 구간 스캔 스킵" ) time.sleep(SCAN_INTERVAL) continue tickers = get_top_tickers() logger.info(f"스캔 시작: {len(tickers)}개 종목 | F&G={fv}({fng_label})") for ticker in tickers: # 이미 보유 중인 종목 제외 if ticker in trader.get_positions(): continue try: if should_buy(ticker): vol_r = get_active_signals().get(ticker, {}).get("vol_ratio", 0.0) logger.info(f"매수 신호: {ticker} (vol={vol_r:.1f}x)") trader.buy(ticker, vol_ratio=vol_r) time.sleep(0.15) # API rate limit 방지 except Exception as e: logger.error(f"스캔 오류 {ticker}: {e}") time.sleep(0.3) except Exception as e: logger.error(f"스캐너 루프 오류: {e}") time.sleep(SCAN_INTERVAL)