39 lines
1.7 KiB
Python
39 lines
1.7 KiB
Python
from hydra.config.validation import RiskConfig
|
|
from hydra.core.position_tracker import PositionTracker
|
|
from hydra.logging.setup import get_logger
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
DAILY_PNL_KEY = "hydra:daily_pnl"
|
|
|
|
|
|
class RiskEngine:
|
|
def __init__(self, redis_client, position_tracker: PositionTracker, config: RiskConfig | None = None):
|
|
self._redis = redis_client
|
|
self._positions = position_tracker
|
|
self._config = config or RiskConfig()
|
|
|
|
def get_daily_pnl_pct(self) -> float:
|
|
raw = self._redis.get(DAILY_PNL_KEY)
|
|
return float(raw) if raw else 0.0
|
|
|
|
def update_daily_pnl(self, pnl_pct: float) -> None:
|
|
self._redis.set(DAILY_PNL_KEY, str(pnl_pct))
|
|
|
|
def check_order_allowed(self, market: str, symbol: str, position_pct: float) -> tuple[bool, str]:
|
|
"""주문 허용 여부 확인. (allowed, reason) 반환."""
|
|
daily_pnl = self.get_daily_pnl_pct()
|
|
if daily_pnl <= -self._config.daily_loss_kill_pct:
|
|
return False, f"일일 손실 {daily_pnl*100:.1f}% — Kill Switch 레벨"
|
|
if daily_pnl <= -self._config.daily_loss_limit_pct:
|
|
return False, f"일일 손실 {daily_pnl*100:.1f}% — 신규 주문 중단"
|
|
if position_pct > self._config.max_position_per_symbol_pct:
|
|
return False, f"종목 포지션 {position_pct*100:.1f}% 초과 (최대 {self._config.max_position_per_symbol_pct*100:.0f}%)"
|
|
return True, "ok"
|
|
|
|
def should_kill_switch(self) -> tuple[bool, str]:
|
|
daily_pnl = self.get_daily_pnl_pct()
|
|
if daily_pnl <= -self._config.daily_loss_kill_pct:
|
|
return True, f"daily_loss:{daily_pnl*100:.1f}%"
|
|
return False, ""
|