Spaces:
Running
Running
Download backend/risk_engine.py from sysvelop/sentrythetaai: direct link, hf CLI and curl.
- Browser
- Download file 5.26 kB
-
https://huggingface.co/spaces/sysvelop/sentrythetaai/resolve/main/backend/risk_engine.py
- Command line
-
hf download hf://spaces/sysvelop/sentrythetaai/backend/risk_engine.py
-
curl -L -o risk_engine.py https://huggingface.co/spaces/sysvelop/sentrythetaai/resolve/main/backend/risk_engine.py
5.26 kB
| import os | |
| import logging | |
| from typing import Dict, Any, List, Tuple | |
| logger = logging.getLogger("SentryTheta.RiskEngine") | |
| class RiskEngine: | |
| def __init__(self): | |
| # Risk settings from env or defaults | |
| self.max_position_size_pct = float(os.getenv("MAX_POSITION_SIZE_PERCENT", "5.0")) | |
| self.max_daily_drawdown_pct = float(os.getenv("MAX_DAILY_DRAWDOWN_PERCENT", "3.0")) | |
| self.stop_loss_pct = float(os.getenv("STOP_LOSS_PERCENT", "15.0")) | |
| self.take_profit_pct = float(os.getenv("TAKE_PROFIT_PERCENT", "30.0")) | |
| logger.info( | |
| f"Risk Engine initialized: MaxPos={self.max_position_size_pct}%, " | |
| f"MaxDrawdown={self.max_daily_drawdown_pct}%, " | |
| f"SL={self.stop_loss_pct}%, TP={self.take_profit_pct}%" | |
| ) | |
| def evaluate_trade( | |
| self, | |
| symbol: str, | |
| qty: int, | |
| premium_per_contract: float, | |
| side: str, | |
| account_info: Dict[str, Any] | |
| ) -> Tuple[bool, str]: | |
| """ | |
| Evaluate if a proposed trade satisfies risk parameters. | |
| Returns: | |
| (is_approved: bool, reason: str) | |
| """ | |
| equity = account_info.get("equity", 100000.0) | |
| cash = account_info.get("cash", 100000.0) | |
| today_pl = account_info.get("today_pl", 0.0) | |
| # Calculate cost/credit of trade | |
| # Option contract represents 100 shares | |
| trade_value = premium_per_contract * qty * 100 | |
| # 1. Daily Drawdown Check | |
| # Reject new trades if today's drawdown exceeds limit | |
| drawdown_limit = equity * (self.max_daily_drawdown_pct / 100.0) | |
| if today_pl < 0 and abs(today_pl) >= drawdown_limit: | |
| reason = f"Daily drawdown limit of {self.max_daily_drawdown_pct}% exceeded (Current today's P&L: ${today_pl:.2f}). Trading blocked." | |
| logger.warning(reason) | |
| return False, reason | |
| # 2. Position Size Limit Check | |
| # A single position should not exceed MAX_POSITION_SIZE_PERCENT of total equity | |
| max_position_size = equity * (self.max_position_size_pct / 100.0) | |
| if side.lower() == "buy": | |
| # For buying options, the risk is the premium paid | |
| if trade_value > max_position_size: | |
| reason = ( | |
| f"Position size exceeds limit. Proposed cost: ${trade_value:.2f}, " | |
| f"Max allowed ({self.max_position_size_pct}% of equity): ${max_position_size:.2f}." | |
| ) | |
| logger.warning(reason) | |
| return False, reason | |
| # Cash availability check | |
| if trade_value > cash: | |
| reason = f"Insufficient cash to execute trade. Cost: ${trade_value:.2f}, Cash: ${cash:.2f}." | |
| logger.warning(reason) | |
| return False, reason | |
| else: # Selling option (premium collection / write) | |
| # In short options, margin requirement or collateral must be evaluated. | |
| # For simplicity, we limit short premium trades to the same position size | |
| # based on collateral value or margin impact. | |
| if trade_value > max_position_size: | |
| reason = ( | |
| f"Premium size exceeds limit. Proposed margin value: ${trade_value:.2f}, " | |
| f"Max allowed ({self.max_position_size_pct}% of equity): ${max_position_size:.2f}." | |
| ) | |
| logger.warning(reason) | |
| return False, reason | |
| return True, "Trade satisfies all Sentry Risk limits." | |
| def check_portfolio_risk(self, active_positions: List[Dict[str, Any]]) -> List[Dict[str, Any]]: | |
| """ | |
| Scan active positions and return positions that breach Stop Loss or Take Profit targets. | |
| Returns a list of trade suggestions to close positions. | |
| """ | |
| exit_signals = [] | |
| for pos in active_positions: | |
| unrealized_plpc = pos.get("unrealized_plpc", 0.0) * 100 # convert to % | |
| symbol = pos.get("symbol") | |
| qty = pos.get("qty", 0) | |
| # Stop Loss (SL) check (Negative P&L) | |
| if unrealized_plpc <= -self.stop_loss_pct: | |
| logger.warning(f"Stop Loss breached for {symbol}: {unrealized_plpc:.2f}% (Limit: -{self.stop_loss_pct}%)") | |
| exit_signals.append({ | |
| "symbol": symbol, | |
| "qty": abs(qty), | |
| "side": "sell" if qty > 0 else "buy", | |
| "reason": f"Stop Loss target (-{self.stop_loss_pct}%) hit at {unrealized_plpc:.2f}%." | |
| }) | |
| # Take Profit (TP) check (Positive P&L) | |
| elif unrealized_plpc >= self.take_profit_pct: | |
| logger.info(f"Take Profit hit for {symbol}: {unrealized_plpc:.2f}% (Limit: +{self.take_profit_pct}%)") | |
| exit_signals.append({ | |
| "symbol": symbol, | |
| "qty": abs(qty), | |
| "side": "sell" if qty > 0 else "buy", | |
| "reason": f"Take Profit target (+{self.take_profit_pct}%) hit at {unrealized_plpc:.2f}%." | |
| }) | |
| return exit_signals | |