"""Shared definition of a 'qualified' (actionable) trade setup. A single predicate, driven by the admin activation config, used by the performance stats (server) and mirrored on the frontend. Beyond raw R:R and confidence, an actionable setup must show genuine conviction: a high-conviction recommended action, a clean (conflict-free) read, and a probable primary target. """ from __future__ import annotations from typing import Any HIGH_CONVICTION_ACTIONS = {"LONG_HIGH", "SHORT_HIGH"} def best_target_probability(setup: Any) -> float: """Highest probability among a setup's targets, 0 if none.""" targets = getattr(setup, "targets", None) or [] probs = [float(t.get("probability", 0.0)) for t in targets if isinstance(t, dict)] return max(probs, default=0.0) def setup_qualifies(setup: Any, config: dict) -> bool: """Whether a setup clears the activation gate. ``setup`` is duck-typed: any object exposing rr_ratio, confidence_score, recommended_action, risk_level and a ``targets`` list of dicts. """ if setup.rr_ratio < config["min_rr"]: return False if (setup.confidence_score or 0.0) < config["min_confidence"]: return False if config.get("require_high_conviction"): if (setup.recommended_action or "") not in HIGH_CONVICTION_ACTIONS: return False if config.get("exclude_conflicts"): if (setup.risk_level or "") != "Low": return False min_tp = float(config.get("min_target_probability", 0.0)) if min_tp > 0 and best_target_probability(setup) < min_tp: return False return True