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:
@@ -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},
|
||||
|
||||
Reference in New Issue
Block a user