refactor: MVC 구조 분리 + 미사용 파일 archive 정리
- tick_trader.py를 Controller로 축소, 로직을 3개 모듈로 분리: - core/signal.py: 시그널 감지, 지표 계산 (calc_vr, calc_atr, detect_signal) - core/order.py: Upbit 주문 실행 (매수/매도/취소/조회) - core/position_manager.py: 포지션 관리, DB sync, 복구, 청산 조건 - type hints, Google docstring, 구체적 예외 타입 적용 - 50줄 초과 함수 분리 (process_signal, restore_positions) - 미사용 파일 58개 archive/ 폴더로 이동 - README.md 추가 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
100
archive/daemon/runner.py
Normal file
100
archive/daemon/runner.py
Normal file
@@ -0,0 +1,100 @@
|
||||
"""매수 기회 스캔 루프 - 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)
|
||||
Reference in New Issue
Block a user