Setup views: primary-target column, floor-target prune, liveness cutoff

Three follow-ups to the gate probability floor (8f41143):

- Signals table shows the starred primary target (shared primaryTarget
  helper) instead of an independently computed max-probability best,
  so Overview, Signals and ticker details agree by construction.
- Targets pinned at the 3% probability clamp floor collapse to the
  nearest one (enhance_trade_setup + backtest candidates in parity):
  floor-pinned levels are indistinguishable to the model, so farther
  ones were duplicate 3% rows inviting lottery headlines.
- get_trade_setups only returns setups re-emitted within
  LIVE_SETUP_MAX_AGE_DAYS (3): an older latest row means the daily
  scan no longer confirms the setup, and such rows otherwise surface
  forever on Overview/Signals/ticker/alerts. History endpoints keep
  full history.

Backtest on the Jul-3 snapshot is metric-identical to the gate-floor
run on all qualified stats (1089 qualified, Sharpe 2.02, CAGR +49.6%,
DD -15.8%): the prune only removes noise the gate already rejected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-11 10:06:34 +02:00
co-authored by Claude Fable 5
parent 8f411435ee
commit fdc49d0e28
8 changed files with 170 additions and 42 deletions
+30
View File
@@ -5,6 +5,7 @@ from dataclasses import dataclass
from app.services.recommendation_service import (
_build_reasoning,
_choose_recommended_action,
_prune_floor_pinned_targets,
_select_primary_target,
direction_analyzer,
probability_estimator,
@@ -154,6 +155,35 @@ def test_primary_target_requires_probability_floor():
assert primary["price"] == 112.0
def test_prune_keeps_only_nearest_floor_pinned_target():
# Two targets pinned at the 3% clamp floor are indistinguishable to the
# model — only the nearest survives; farther ones are duplicate noise.
targets = [
{"price": 204.0, "rr_ratio": 0.7, "probability": 25.6},
{"price": 241.0, "rr_ratio": 2.0, "probability": 3.0},
{"price": 272.0, "rr_ratio": 3.1, "probability": 3.0},
]
pruned = _prune_floor_pinned_targets(targets)
assert [t["price"] for t in pruned] == [204.0, 241.0]
def test_prune_leaves_targets_above_floor_untouched():
targets = [
{"price": 110.0, "rr_ratio": 2.0, "probability": 65.0},
{"price": 120.0, "rr_ratio": 3.5, "probability": 20.0},
]
assert _prune_floor_pinned_targets(targets) == targets
def test_prune_all_floor_pinned_keeps_nearest_only():
targets = [
{"price": 241.0, "rr_ratio": 2.0, "probability": 3.0},
{"price": 272.0, "rr_ratio": 3.1, "probability": 3.0},
]
pruned = _prune_floor_pinned_targets(targets)
assert [t["price"] for t in pruned] == [241.0]
def test_detects_sentiment_technical_conflict():
conflicts = signal_conflict_detector.detect_conflicts(
dimension_scores={"technical": 72.0, "momentum": 55.0, "fundamental": 50.0},