File size: 5,615 Bytes
cfe8ecb c24b1eb 5b5d74d c24b1eb 5b5d74d cfe8ecb c24b1eb cfe8ecb c24b1eb cfe8ecb c24b1eb cfe8ecb c24b1eb 5b5d74d cfe8ecb 5b5d74d cfe8ecb c24b1eb cfe8ecb 5b5d74d cfe8ecb c24b1eb 5b5d74d cfe8ecb 5b5d74d cfe8ecb 5b5d74d cfe8ecb 5b5d74d cfe8ecb 5b5d74d cfe8ecb 5b5d74d cfe8ecb 5b5d74d cfe8ecb 5b5d74d cfe8ecb c24b1eb 5b5d74d c24b1eb 5b5d74d cfe8ecb | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 | """
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"
|