Revert blue-sky projection; keep played-out setup UX
A local backtest (offline prod snapshot, 506 tickers) evaluated blue-sky projected targets under the PRODUCTION exit (3x ATR trailing + 30d max hold, paper_trade_service DEFAULT_EXIT_MODE="atr_trailing"). Blue-sky setups are dilutive: the qualified book scored 328% return / Sharpe 1.84 / DD -21.0% WITHOUT them vs 300% / 1.58 / -18.7% WITH them. They rank high on momentum by construction, so they grab slots from S/R setups that catch bigger runs under a trailing-stop exit (only ~2pp worse drawdown doesn't justify the lost return and Sharpe). Reverts the scanner/TargetGenerator measured-move projection, the stricter projected activation gate, the frontend qualification mirror, the `projected` type field, and the projected tests -- all backend files are now byte-identical to the pre-blue-sky commit. Keeps the played-out "No current setup" UX (RecommendationPanel): when price has run past the target (played out) or through the stop (invalidated), the panel shows a plain no-setup state instead of a stale actionable card. This is frontend-only (reads last close + existing setup fields) and is what actually fixes the reported stale-below-price bug -- no backend change or rescan needed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -6,7 +6,6 @@ from types import SimpleNamespace
|
||||
|
||||
from app.services.qualification import (
|
||||
best_target_probability,
|
||||
primary_target_is_projected,
|
||||
primary_target_probability,
|
||||
setup_qualifies,
|
||||
)
|
||||
@@ -156,54 +155,6 @@ class TestExcludeNeutral:
|
||||
assert setup_qualifies(_setup(recommended_action="NEUTRAL"), DEFAULT_GATE) is True
|
||||
|
||||
|
||||
def _projected_setup(**kwargs):
|
||||
"""A setup whose primary (headline) target is a blue-sky projection."""
|
||||
base = dict(
|
||||
direction="long",
|
||||
momentum_percentile=92.0,
|
||||
confidence_score=80.0,
|
||||
targets=[{"probability": 30.0, "is_primary": True, "projected": True}],
|
||||
)
|
||||
base.update(kwargs)
|
||||
return _setup(**base)
|
||||
|
||||
|
||||
class TestProjectedTargetGate:
|
||||
"""Projected targets clear a stricter bar, independent of the momentum gate."""
|
||||
|
||||
def test_projected_passes_stricter_bar(self):
|
||||
# DEFAULT_GATE has the momentum selection OFF, yet the projected block
|
||||
# still requires strong momentum + higher confidence — and this one clears.
|
||||
assert setup_qualifies(_projected_setup(), DEFAULT_GATE) is True
|
||||
|
||||
def test_projected_fails_below_momentum_floor(self):
|
||||
assert setup_qualifies(_projected_setup(momentum_percentile=85.0), DEFAULT_GATE) is False
|
||||
|
||||
def test_projected_fails_missing_momentum(self):
|
||||
assert setup_qualifies(_projected_setup(momentum_percentile=None), DEFAULT_GATE) is False
|
||||
|
||||
def test_projected_fails_below_raised_confidence_floor(self):
|
||||
# min_confidence 55 + 10 margin = 65; a 60% confidence projected setup fails
|
||||
# even though it would clear the plain 55 floor.
|
||||
assert setup_qualifies(_projected_setup(confidence_score=60.0), DEFAULT_GATE) is False
|
||||
|
||||
def test_projected_short_never_qualifies(self):
|
||||
s = _projected_setup(direction="short", recommended_action="SHORT_HIGH")
|
||||
assert setup_qualifies(s, DEFAULT_GATE) is False
|
||||
|
||||
def test_sr_anchored_setup_unaffected_by_projected_bar(self):
|
||||
# A normal (non-projected) setup with modest momentum still passes DEFAULT.
|
||||
assert setup_qualifies(_setup(momentum_percentile=10.0), DEFAULT_GATE) is True
|
||||
|
||||
def test_primary_target_is_projected_helper(self):
|
||||
assert primary_target_is_projected(_projected_setup()) is True
|
||||
assert primary_target_is_projected(_setup()) is False
|
||||
|
||||
def test_projected_flag_falls_back_when_no_primary(self):
|
||||
s = _setup(targets=[{"probability": 30.0, "projected": True}])
|
||||
assert primary_target_is_projected(s) is True
|
||||
|
||||
|
||||
class TestBestTargetProbability:
|
||||
def test_returns_max(self):
|
||||
s = _setup(targets=[{"probability": 40.0}, {"probability": 72.0}, {"probability": 55.0}])
|
||||
|
||||
Reference in New Issue
Block a user