Spaces:
Sleeping
Sleeping
| """ | |
| veto.py β Hard filters. Any single veto cancels the setup entirely. | |
| Key fixes vs prior version: | |
| - Absorption is now a hard veto (was missing) | |
| - Climax volume is a hard veto (was only a score penalty) | |
| - Failed breakout in last N bars = hard veto | |
| - Price too extended from mean = hard veto (separate per direction) | |
| - Vol ratio threshold lowered to 2.2 (fires more reliably) | |
| - No volatility compression = veto (require setups to emerge from bases) | |
| - Regime confidence below minimum = veto (new structural gate) | |
| """ | |
| from typing import Dict, Any, Tuple | |
| from config import ( | |
| VETO_VOLUME_MIN, | |
| VETO_VOL_RATIO_MAX, | |
| VETO_STRUCTURE_MIN, | |
| VETO_CLIMAX, | |
| VETO_ABSORPTION, | |
| VETO_EXTENDED_PRICE, | |
| VETO_NO_COMPRESSION, | |
| REGIME_CONFIDENCE_MIN, | |
| VOL_COMPRESSION_LOOKBACK, | |
| ) | |
| _FAILED_BREAKOUT_LOOKBACK = 5 # bars to look back for recent fake signals | |
| _CONSEC_FAILED_MAX = 2 # veto if N or more recent fakes | |
| def apply_veto( | |
| regime_data: Dict[str, Any], | |
| volume_data: Dict[str, Any], | |
| structure_score: float, | |
| direction: int = 1, # +1 = evaluating long, -1 = evaluating short | |
| ) -> Tuple[bool, str]: | |
| """ | |
| Returns (vetoed: bool, reason: str). | |
| direction controls which extension check applies. | |
| """ | |
| reasons = [] | |
| volume_score = volume_data.get("volume_score", 0.0) | |
| vol_ratio = regime_data.get("vol_ratio", 1.0) | |
| trend = regime_data.get("trend", "ranging") | |
| vol_compressed = regime_data.get("vol_compressed", False) | |
| vol_expanding = regime_data.get("vol_expanding", False) | |
| regime_confidence = regime_data.get("regime_confidence", 0.0) | |
| price_extended_long = regime_data.get("price_extended_long", False) | |
| price_extended_short = regime_data.get("price_extended_short", False) | |
| adx = regime_data.get("adx", 0.0) | |
| spike = volume_data.get("spike", False) | |
| climax = volume_data.get("climax", False) | |
| absorption = volume_data.get("absorption", False) | |
| failed_breakout = volume_data.get("failed_breakout", False) | |
| recent_failed = volume_data.get("recent_failed_count", 0) | |
| weak = volume_data.get("weak", False) | |
| # ββ VOLUME GATES βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| if volume_score < VETO_VOLUME_MIN: | |
| reasons.append(f"WEAK_VOLUME_SCORE ({volume_score:.2f} < {VETO_VOLUME_MIN})") | |
| if weak: | |
| reasons.append(f"VOLUME_BELOW_MA (ratio={volume_data.get('vol_ratio', 0):.2f})") | |
| if VETO_CLIMAX and climax: | |
| reasons.append("CLIMAX_VOLUME β potential exhaustion, not entry") | |
| if VETO_ABSORPTION and absorption: | |
| reasons.append("ABSORPTION_DETECTED β institutional supply at resistance") | |
| # ββ BREAKOUT INTEGRITY ββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| if failed_breakout: | |
| reasons.append("FAILED_BREAKOUT β most recent breakout reversed") | |
| if recent_failed >= _CONSEC_FAILED_MAX: | |
| reasons.append(f"REPEATED_FAKE_BREAKOUTS ({recent_failed} in last 10 bars)") | |
| # ββ VOLATILITY GATES ββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| if vol_ratio > VETO_VOL_RATIO_MAX: | |
| reasons.append(f"EXTREME_VOLATILITY (ratio={vol_ratio:.2f} > {VETO_VOL_RATIO_MAX})") | |
| if VETO_NO_COMPRESSION and not vol_compressed and not vol_expanding: | |
| # Allow through if currently expanding β expansion from base is fine | |
| # Only veto if vol is neither compressed nor just broke out | |
| if vol_ratio < 0.9 or vol_ratio > 1.6: | |
| reasons.append( | |
| f"NO_VOLATILITY_BASE (ratio={vol_ratio:.2f}, compressed={vol_compressed})" | |
| ) | |
| # ββ STRUCTURE GATE ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| if structure_score < VETO_STRUCTURE_MIN: | |
| reasons.append(f"WEAK_STRUCTURE ({structure_score:.2f} < {VETO_STRUCTURE_MIN})") | |
| if trend == "bearish" and direction == 1: | |
| reasons.append("BEARISH_TREND β long entry contra-trend") | |
| if trend == "bullish" and direction == -1: | |
| reasons.append("BULLISH_TREND β short entry contra-trend") | |
| # ββ REGIME CONFIDENCE GATE ββββββββββββββββββββββββββββββββββββββββββββββββ | |
| if regime_confidence < REGIME_CONFIDENCE_MIN: | |
| reasons.append( | |
| f"LOW_REGIME_CONFIDENCE ({regime_confidence:.2f} < {REGIME_CONFIDENCE_MIN})" | |
| ) | |
| # ββ PRICE EXTENSION GATE βββββββββββββββββββββββββββββββββββββββββββββββββ | |
| if VETO_EXTENDED_PRICE: | |
| if direction == 1 and price_extended_long: | |
| dist = regime_data.get("dist_atr", 0.0) | |
| reasons.append(f"PRICE_EXTENDED_LONG ({dist:.2f} ATR above mean)") | |
| if direction == -1 and price_extended_short: | |
| dist = regime_data.get("dist_atr", 0.0) | |
| reasons.append(f"PRICE_EXTENDED_SHORT ({dist:.2f} ATR below mean)") | |
| if reasons: | |
| return True, " | ".join(reasons) | |
| return False, "" | |
| def veto_summary(vetoed: bool, reason: str) -> str: | |
| return f"VETOED β {reason}" if vetoed else "APPROVED" | |