Files
auto-stock/backend/app/strategy/strategy_manager.py
shuim c818810667
Some checks failed
CI/CD Pipeline / test-backend (push) Has been cancelled
CI/CD Pipeline / test-frontend (push) Has been cancelled
CI/CD Pipeline / build-and-push (push) Has been cancelled
수정본
2026-03-27 14:47:49 +09:00

255 lines
8.1 KiB
Python

"""
Strategy Manager
전략 등록·실행·관리를 담당하는 오케스트레이터
"""
from typing import Dict, List, Optional, Any
from collections import deque
import logging
import asyncio
from datetime import datetime
from app.strategy.base_strategy import BaseStrategy
from app.strategy.signal import Signal, SignalType
logger = logging.getLogger(__name__)
class StrategyManager:
"""
전략 관리자
- 전략 등록/삭제
- 복수 전략 동시 실행
- 신호 충돌 해결
- 전략 우선순위 관리
"""
def __init__(self):
self.strategies: Dict[str, BaseStrategy] = {} # 전략 등록부
self.priority_order: List[str] = [] # 우선순위
self.signal_history: deque = deque(maxlen=1000) # 신호 이력 (최대 1000건, 메모리 누수 방지)
logger.info("🎯 StrategyManager 초기화")
def register_strategy(self, strategy: BaseStrategy, priority: int = 100):
"""
전략 등록
Args:
strategy: 등록할 전략 인스턴스
priority: 우선순위 (낮을수록 먼저 실행)
"""
if strategy.name in self.strategies:
logger.warning(f"⚠️ 전략 이미 등록됨: {strategy.name}")
return
self.strategies[strategy.name] = strategy
# 우선순위 삽입
inserted = False
for i, name in enumerate(self.priority_order):
existing_strategy = self.strategies[name]
existing_priority = getattr(existing_strategy, '_priority', 100)
if priority < existing_priority:
self.priority_order.insert(i, strategy.name)
inserted = True
break
if not inserted:
self.priority_order.append(strategy.name)
strategy._priority = priority
logger.info(f"✅ 전략 등록: {strategy.name} (priority={priority})")
def unregister_strategy(self, strategy_name: str):
"""전략 등록 해제"""
if strategy_name in self.strategies:
del self.strategies[strategy_name]
self.priority_order.remove(strategy_name)
logger.info(f"❌ 전략 삭제: {strategy_name}")
def get_strategy(self, strategy_name: str) -> Optional[BaseStrategy]:
"""전략 조회"""
return self.strategies.get(strategy_name)
def list_strategies(self) -> List[str]:
"""등록된 전략 목록"""
return list(self.strategies.keys())
async def run_strategy(
self,
strategy_name: str,
stock_code: str,
market_data: Dict[str, Any],
portfolio_data: Optional[Dict[str, Any]] = None
) -> Optional[Signal]:
"""
특정 전략 단독 실행
Args:
strategy_name: 실행할 전략 이름
stock_code: 종목코드
market_data: 시장 데이터
portfolio_data: 포트폴리오 데이터
Returns:
Signal 또는 None
"""
strategy = self.strategies.get(strategy_name)
if not strategy:
logger.error(f"❌ 전략 찾을 수 없음: {strategy_name}")
return None
if not strategy.enabled:
logger.debug(f"⏸️ 전략 비활성화됨: {strategy_name}")
return None
try:
signal = await strategy.analyze(stock_code, market_data, portfolio_data)
if signal:
self.signal_history.append(signal)
logger.info(f"📡 신호 생성: {signal}")
return signal
except Exception as e:
logger.error(f"❌ [{strategy_name}] 실행 중 오류: {e}", exc_info=True)
await strategy.on_error(e, {"stock_code": stock_code})
return None
async def run_all_strategies(
self,
stock_code: str,
market_data: Dict[str, Any],
portfolio_data: Optional[Dict[str, Any]] = None,
parallel: bool = False
) -> List[Signal]:
"""
모든 전략 동시 실행
Args:
stock_code: 종목코드
market_data: 시장 데이터
portfolio_data: 포트폴리오 데이터
parallel: True = 병렬 실행, False = 순차 실행
Returns:
생성된 Signal 목록
"""
signals = []
if parallel:
# 병렬 실행
tasks = [
self.run_strategy(name, stock_code, market_data, portfolio_data)
for name in self.priority_order
]
results = await asyncio.gather(*tasks, return_exceptions=True)
signals = [s for s in results if isinstance(s, Signal)]
else:
# 순차 실행 (우선순위)
for strategy_name in self.priority_order:
signal = await self.run_strategy(
strategy_name, stock_code, market_data, portfolio_data
)
if signal:
signals.append(signal)
return signals
def resolve_conflicts(
self,
signals: List[Signal],
method: str = "highest_confidence"
) -> Optional[Signal]:
"""
신호 충돌 해결
Args:
signals: 신호 목록
method: 해결 방법
- "highest_confidence": 가장 높은 신뢰도
- "priority": 전략 우선순위
- "majority_vote": 다수결
Returns:
최종 선택된 Signal
"""
if not signals:
return None
if len(signals) == 1:
return signals[0]
if method == "highest_confidence":
return max(signals, key=lambda s: s.confidence)
elif method == "priority":
# 우선순위 높은 전략의 신호 선택
for strategy_name in self.priority_order:
for signal in signals:
if signal.strategy_name == strategy_name:
return signal
return signals[0]
elif method == "majority_vote":
# 가장 많이 나온 신호 타입 선택
from collections import Counter
signal_types = [s.signal_type for s in signals]
most_common = Counter(signal_types).most_common(1)[0][0]
# 해당 타입 중 가장 높은 신뢰도
matching_signals = [s for s in signals if s.signal_type == most_common]
return max(matching_signals, key=lambda s: s.confidence)
return signals[0]
def get_signal_history(
self,
stock_code: Optional[str] = None,
strategy_name: Optional[str] = None,
limit: int = 100
) -> List[Signal]:
"""
신호 이력 조회
Args:
stock_code: 종목코드 필터
strategy_name: 전략 이름 필터
limit: 최대 개수
Returns:
Signal 목록
"""
history = list(self.signal_history)
if stock_code:
history = [s for s in history if s.stock_code == stock_code]
if strategy_name:
history = [s for s in history if s.strategy_name == strategy_name]
return history[-limit:]
def get_stats(self) -> Dict[str, Any]:
"""전략 통계 정보"""
return {
"total_strategies": len(self.strategies),
"enabled_strategies": sum(1 for s in self.strategies.values() if s.enabled),
"total_signals": len(self.signal_history),
"strategies": {
name: {
"enabled": strategy.enabled,
"priority": getattr(strategy, '_priority', 100),
"signals_count": len([s for s in self.signal_history if s.strategy_name == name])
}
for name, strategy in self.strategies.items()
}
}
# 싱글톤
strategy_manager = StrategyManager()