""" Risk gate — final hard stop before venue execution. The planner is not trusted. The optimiser is not trusted, the exchange adapter is not trusted. This gate enforces hard invariants. """ from __future__ import annotations import time from collections import defaultdict from typing import Deque from collections import deque from malkhut.actions import FulfilmentAction, PlannedPolicy, RiskDecision from malkhut.state import ( ActionKind, FulfilmentPolicyParams, MarketWorldState, MAX_ACCOUNT_LEVERAGE, MAX_CANCELS_PER_SYMBOL_PER_MINUTE, MAX_SYMBOL_NOTIONAL_FRACTION, Side, ) from malkhut.cwm import materialize_price_from_action class RiskGate: def __init__(self) -> None: self._kill_switch: bool = False self._cancel_timestamps: dict[str, Deque[float]] = defaultdict( lambda: deque(maxlen=MAX_CANCELS_PER_SYMBOL_PER_MINUTE + 10) ) def set_kill_switch(self, active: bool) -> None: """Operator-controlled emergency stop.""" self._kill_switch = active def record_cancel(self, symbol: str) -> None: """Record a cancel event for rate-limit tracking.""" self._cancel_timestamps[symbol].append(time.time()) def validate( self, state: MarketWorldState, planned: PlannedPolicy, params: FulfilmentPolicyParams, daat_verdict: str = "KNOWN", ) -> RiskDecision: """Validate a planned action. Args: daat_verdict: "KNOWN" | "MARGINAL" | "OUT_OF_DISTRIBUTION" From DAAT classifier. If OUT_OF_DISTRIBUTION, veto the action and fall back to doctrinal simple policy. """ action = planned.selected_action if daat_verdict == "OUT_OF_DISTRIBUTION": return RiskDecision(True, None, "ood_veto_fall_back_to_doctrinal") if action.kind == ActionKind.NOOP: return RiskDecision(True, action, "noop") if self._kill_switch_active(): return RiskDecision(False, None, "kill_switch") if self._cancel_rate_would_exceed(state, action): return RiskDecision(False, None, "cancel_rate_limit") if self._would_self_trade(state, action): return RiskDecision(False, None, "self_trade_risk") if self._would_exceed_leverage(state, action, params): return RiskDecision(False, None, "leverage_limit") if self._would_exceed_symbol_notional(state, action, params): return RiskDecision(False, None, "symbol_notional_limit") if self._post_only_would_cross(state, action): return RiskDecision(False, None, "post_only_cross") if self._violates_venue_minima(state, action): return RiskDecision(False, None, "venue_minimum") return RiskDecision(True, action, "approved") def _kill_switch_active(self) -> bool: return self._kill_switch def _cancel_rate_would_exceed(self, state: MarketWorldState, action: FulfilmentAction) -> bool: if action.kind != ActionKind.CANCEL and action.kind != ActionKind.CANCEL_REPLACE: return False symbol = state.venue.symbol now = time.time() window = self._cancel_timestamps[symbol] cutoff = now - 60.0 while window and window[0] < cutoff: window.popleft() return len(window) >= MAX_CANCELS_PER_SYMBOL_PER_MINUTE def _would_self_trade(self, state: MarketWorldState, action: FulfilmentAction) -> bool: if action.kind not in (ActionKind.PLACE, ActionKind.CANCEL_REPLACE, ActionKind.CROSS_SPREAD): return False if action.side is None: return False for oo in state.open_orders: if oo.symbol != state.venue.symbol: continue if oo.side != action.side: continue if oo.client_order_id == action.cancel_order_id: continue if oo.price is None or action.price_ticks_from_best is None: continue our_price = materialize_price_from_action(state, action) if our_price is None: continue if abs(our_price - oo.price) < state.venue.tick_size: return True return False def _would_exceed_leverage( self, state: MarketWorldState, action: FulfilmentAction, params: FulfilmentPolicyParams, ) -> bool: return state.account.total_notional / max(state.account.equity, 1e-12) > MAX_ACCOUNT_LEVERAGE def _would_exceed_symbol_notional( self, state: MarketWorldState, action: FulfilmentAction, params: FulfilmentPolicyParams, ) -> bool: if action.kind not in (ActionKind.PLACE, ActionKind.CANCEL_REPLACE, ActionKind.CROSS_SPREAD): return False price = materialize_price_from_action(state, action) if price is None: return False qty = action.qty_fraction * state.account.available_balance / max(price, 1e-12) order_notional = price * qty max_notional = state.account.equity * MAX_SYMBOL_NOTIONAL_FRACTION current_notional = 0.0 for oo in state.open_orders: if oo.symbol == state.venue.symbol and oo.price is not None: current_notional += oo.price * oo.remaining_qty return (current_notional + order_notional) > max_notional def _post_only_would_cross(self, state: MarketWorldState, action: FulfilmentAction) -> bool: if not action.post_only: return False price = materialize_price_from_action(state, action) if price is None: return False if not state.book.bids or not state.book.asks: return False if action.side == Side.BUY and price >= state.book.best_ask: return True if action.side == Side.SELL and price <= state.book.best_bid: return True return False def _violates_venue_minima(self, state: MarketWorldState, action: FulfilmentAction) -> bool: if action.kind not in (ActionKind.PLACE, ActionKind.CANCEL_REPLACE): return False price = materialize_price_from_action(state, action) if price is None: return False if price <= 0: return True tick = state.venue.tick_size if tick > 0: remainder = price % tick if remainder > 1e-9 and tick - remainder > 1e-9: return True qty = action.qty_fraction * state.account.available_balance / max(price, 1e-12) lot = state.venue.lot_size if lot > 0 and qty > 0: rounded = round(qty / lot) * lot if rounded < state.venue.min_qty: return True notional = price * qty if notional < state.venue.min_notional: return True return False