"""시장 레짐(Bull/Neutral/Bear) 판단. BTC·ETH·SOL·XRP 가중 평균 2h 추세로 레짐을 결정하고 매수 조건 파라미터(trend_pct, vol_mult)를 동적으로 반환한다. 계산된 현재가는 price_history DB에 저장해 재활용한다. """ from __future__ import annotations import logging import time import pyupbit from .price_db import get_price_n_hours_ago, insert_prices logger = logging.getLogger(__name__) # 대장 코인 가중치 LEADERS: dict[str, float] = { "KRW-BTC": 0.40, "KRW-ETH": 0.30, "KRW-SOL": 0.15, "KRW-XRP": 0.15, } TREND_HOURS = 2 # 2h 추세 기준 BULL_THRESHOLD = 1.5 # score ≥ 1.5% → Bull BEAR_THRESHOLD = -1.0 # score < -1.0% → Bear # 레짐별 매수 조건 파라미터 REGIME_PARAMS: dict[str, dict] = { "bull": {"trend_pct": 3.0, "vol_mult": 1.5, "emoji": "🟢"}, "neutral": {"trend_pct": 5.0, "vol_mult": 2.0, "emoji": "🟡"}, "bear": {"trend_pct": 8.0, "vol_mult": 3.5, "emoji": "🔴"}, } # 10분 캐시 (스캔 루프마다 API 호출 방지) _cache: dict = {} _cache_ts: float = 0.0 _CACHE_TTL = 600 def get_regime() -> dict: """현재 시장 레짐 반환. Returns: { 'name': 'bull' | 'neutral' | 'bear', 'score': float, # 가중 평균 2h 추세(%) 'trend_pct': float, # 매수 추세 임계값 'vol_mult': float, # 거래량 배수 임계값 'emoji': str, } """ global _cache, _cache_ts if _cache and (time.time() - _cache_ts) < _CACHE_TTL: return _cache score = 0.0 current_prices: dict[str, float] = {} for ticker, weight in LEADERS.items(): try: current = pyupbit.get_current_price(ticker) if not current: continue current_prices[ticker] = current # DB에서 2h 전 가격 조회 → 없으면 API 캔들로 대체 past = get_price_n_hours_ago(ticker, TREND_HOURS) if past is None: df = pyupbit.get_ohlcv(ticker, interval="minute60", count=4) if df is not None and len(df) >= 3: past = float(df["close"].iloc[-3]) if past: trend = (current - past) / past * 100 score += trend * weight logger.debug(f"[레짐] {ticker} {trend:+.2f}% (기여 {trend*weight:+.3f})") except Exception as e: logger.warning(f"[레짐] {ticker} 오류: {e}") # 현재가 DB 저장 (다음 레짐 계산 및 추세 판단에 재활용) if current_prices: try: insert_prices(current_prices) except Exception as e: logger.warning(f"[레짐] 가격 저장 오류: {e}") # 레짐 결정 if score >= BULL_THRESHOLD: name = "bull" elif score < BEAR_THRESHOLD: name = "bear" else: name = "neutral" params = REGIME_PARAMS[name] result = {"name": name, "score": round(score, 3), **params} logger.info( f"[레짐] score={score:+.3f}% → {params['emoji']} {name.upper()} " f"(TREND≥{params['trend_pct']}% / VOL≥{params['vol_mult']}x)" ) _cache = result _cache_ts = time.time() return result