Require aligned action for qualified setups
Deploy / lint (push) Successful in 7s
Deploy / test (push) Successful in 1m7s
Deploy / deploy (push) Successful in 39s

This commit is contained in:
2026-07-03 16:13:27 +02:00
parent eaad935a2a
commit 14327ab25a
6 changed files with 57 additions and 24 deletions
+17 -5
View File
@@ -17,6 +17,16 @@ from typing import Any
HIGH_CONVICTION_ACTIONS = {"LONG_HIGH", "SHORT_HIGH"}
def _action_direction(action: str | None) -> str:
if not action or action == "NEUTRAL":
return "neutral"
if action.startswith("LONG"):
return "long"
if action.startswith("SHORT"):
return "short"
return "neutral"
def best_target_probability(setup: Any) -> float:
"""Highest probability among a setup's targets, 0 if none."""
targets = getattr(setup, "targets", None) or []
@@ -78,12 +88,14 @@ def setup_qualifies(setup: Any, config: dict) -> bool:
momentum_percentile = getattr(setup, "momentum_percentile", None)
if momentum_percentile is not None and momentum_percentile < min_pct:
return False
# A NEUTRAL recommendation means the engine found no clear directional setup —
# not an actionable signal, so by default it doesn't qualify (and can't be a
# top pick). ``exclude_neutral`` defaults on; turn it off to also count
# no-clear-direction residual momentum leaders.
# A setup is actionable only when the live ticker action points in the same
# direction. NEUTRAL means no clear signal; an opposite action means the
# setup is counter-bias. ``exclude_neutral`` defaults on; callers that omit
# it keep legacy floor-only behavior.
if config.get("exclude_neutral"):
if (setup.recommended_action or "NEUTRAL") == "NEUTRAL":
action_direction = _action_direction(getattr(setup, "recommended_action", None))
setup_direction = (getattr(setup, "direction", "long") or "long").lower()
if action_direction == "neutral" or action_direction != setup_direction:
return False
if config.get("require_high_conviction"):
if (setup.recommended_action or "") not in HIGH_CONVICTION_ACTIONS: