""" Pre-trade toxicity filter — blocks quoting when market is adversarially informed. Uses VPIN and flow toxicity metrics from microstructure module to determine whether to quote, reduce size, or go flat. """ from __future__ import annotations from collections import deque from typing import Optional class ToxicityFilter: """VPIN-based flow toxicity filter for market-making. Monitors trade flow and order book to detect informed trading. Blocks or reduces quoting when toxicity crosses thresholds. Usage: tf = ToxicityFilter() tf.update_trade(buy_vol=0.1, sell_vol=0.05) decision = tf.check() if decision["allow_quoting"]: size_mult = decision["size_multiplier"] """ def __init__( self, vpin_threshold: float = 0.3, vpin_alarm: float = 0.5, vpin_window: int = 50, volume_bucket_size: float = 1.0, size_reduction_steps: int = 5, ): self._threshold = vpin_threshold self._alarm = vpin_alarm self._window = vpin_window self._bucket_size = volume_bucket_size self._steps = size_reduction_steps self._buy_vol: deque[float] = deque(maxlen=5000) self._sell_vol: deque[float] = deque(maxlen=5000) self._current_vpin: float = 0.0 self._last_obi: float = 0.0 self._trade_count: int = 0 def update_trade(self, buy_vol: float, sell_vol: float): """Feed a classified trade volume observation.""" self._buy_vol.append(buy_vol) self._sell_vol.append(sell_vol) self._trade_count += 1 def update_book_imbalance(self, obi: float): """Feed current order-book imbalance.""" self._last_obi = obi def compute(self): """Recompute VPIN from accumulated volume data.""" from microstructure.toxicity import compute_vpin result = compute_vpin( list(self._buy_vol), list(self._sell_vol), volume_bucket_size=self._bucket_size, n_buckets=self._window, ) self._current_vpin = result.get("vpin_value", 0.0) def check(self) -> dict: """Evaluate current toxicity state. Returns: allow_quoting: whether to quote at all size_multiplier: fraction of base size to quote (1.0 = full, 0.0 = none) vpin: current VPIN obi: current OBI reason: explanation if blocked/reduced """ self.compute() v = self._current_vpin if v >= self._alarm: return { "allow_quoting": False, "size_multiplier": 0.0, "vpin": round(v, 4), "obi": round(self._last_obi, 4), "reason": f"VPIN {v:.3f} >= alarm {self._alarm}", } if v >= self._threshold: reduction = (v - self._threshold) / (self._alarm - self._threshold) size = max(0.0, 1.0 - reduction) return { "allow_quoting": size > 0.0, "size_multiplier": round(size, 2), "vpin": round(v, 4), "obi": round(self._last_obi, 4), "reason": f"VPIN {v:.3f} >= threshold {self._threshold}", } return { "allow_quoting": True, "size_multiplier": 1.0, "vpin": round(v, 4), "obi": round(self._last_obi, 4), "reason": "ok", } @property def vpin(self) -> float: return self._current_vpin @property def trade_count(self) -> int: return self._trade_count