fix: align production defaults and close review parity gaps

Ship greenfield min_rr=2.0 and conf=0, read-only Structural S/R, indicator
cache invalidation, and UI/gate language that treats GTL as screening not exit.
Align strategy_rank missing-vol fallback live vs backtest, single-source
PRIMARY_TARGET_MIN_RR, expand prod parity tests, and drop dead FE clients.
This commit is contained in:
2026-07-18 13:03:22 +02:00
parent e07da0f8f0
commit b0e33e1606
25 changed files with 429 additions and 221 deletions
+21 -18
View File
@@ -1,8 +1,8 @@
"""Fix-checking tests for R:R scanner quality-score selection.
"""Fix-checking tests for R:R scanner probability-based primary selection.
Verify that the fixed scan_ticker selects the candidate with the highest
quality score among all candidates meeting the R:R threshold, for both
long and short setups.
Verify that after enhance_trade_setup the headline target is the most likely
worthwhile primary (R:R + probability floors), for both long and short setups.
The pre-enhance quality loop only seeds a provisional target.
**Validates: Requirements 2.1, 2.2, 2.3, 2.4**
"""
@@ -22,9 +22,7 @@ from app.services.rr_scanner_service import scan_ticker
def _assert_primary_is_most_likely_worthwhile(setup) -> None:
"""The persisted headline target must equal the starred primary in the
targets table, and that primary must be the highest-probability target
with R:R >= 1.5 (fallback: highest R:R)."""
"""Headline = starred primary = max(probability, rr) among floor-clearing targets."""
targets = setup.targets
assert targets, "expected generated targets"
primaries = [t for t in targets if t.get("is_primary")]
@@ -32,7 +30,11 @@ def _assert_primary_is_most_likely_worthwhile(setup) -> None:
primary = primaries[0]
assert setup.target == pytest.approx(primary["price"], abs=0.01)
worthwhile = [t for t in targets if t["rr_ratio"] >= 1.5]
# Mirrors recommendation_service._select_primary_target floors.
worthwhile = [
t for t in targets
if float(t["rr_ratio"]) >= 1.5 and float(t["probability"]) >= 20.0
]
pool = worthwhile or targets
best = max(pool, key=lambda t: (t["probability"], t["rr_ratio"]))
assert primary["price"] == pytest.approx(best["price"], abs=0.01)
@@ -122,7 +124,7 @@ def short_candidate_levels(draw: st.DrawFn) -> list[dict]:
# ---------------------------------------------------------------------------
# Property test: long setup selects highest quality score candidate
# Property test: long setup selects probability-based primary
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
@@ -132,14 +134,14 @@ def short_candidate_levels(draw: st.DrawFn) -> list[dict]:
deadline=None,
suppress_health_check=[HealthCheck.function_scoped_fixture],
)
async def test_property_long_selects_highest_quality(
async def test_property_long_selects_probability_primary(
levels: list[dict],
scan_session: AsyncSession,
):
"""**Validates: Requirements 2.1, 2.3, 2.4**
Property: when multiple resistance levels meet the R:R threshold,
the fixed scan_ticker selects the one with the highest quality score.
the headline after enhance is the probability-based primary.
"""
from tests.conftest import _test_engine, _test_session_factory
from app.database import Base
@@ -183,7 +185,7 @@ async def test_property_long_selects_highest_quality(
# ---------------------------------------------------------------------------
# Property test: short setup selects highest quality score candidate
# Property test: short setup selects probability-based primary
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
@@ -193,14 +195,14 @@ async def test_property_long_selects_highest_quality(
deadline=None,
suppress_health_check=[HealthCheck.function_scoped_fixture],
)
async def test_property_short_selects_highest_quality(
async def test_property_short_selects_probability_primary(
levels: list[dict],
scan_session: AsyncSession,
):
"""**Validates: Requirements 2.2, 2.3, 2.4**
Property: when multiple support levels meet the R:R threshold,
the fixed scan_ticker selects the one with the highest quality score.
the headline after enhance is the probability-based primary.
"""
from tests.conftest import _test_engine, _test_session_factory
from app.database import Base
@@ -303,9 +305,10 @@ async def test_deterministic_long_three_levels(scan_session: AsyncSession):
long_setups = [s for s in setups if s.direction == "long"]
assert len(long_setups) == 1, "Expected exactly one long setup"
# Level A (105, strength=90) should win with highest quality
_assert_primary_is_most_likely_worthwhile(long_setups[0])
# Near/strong level A wins on reach-probability over far lottery C.
assert long_setups[0].target == pytest.approx(105.0, abs=0.01), (
f"Expected target=105.0 (highest quality), got {long_setups[0].target}"
f"Expected primary=105.0 (near, high reach-prob), got {long_setups[0].target}"
)
@@ -366,7 +369,7 @@ async def test_deterministic_short_three_levels(scan_session: AsyncSession):
short_setups = [s for s in setups if s.direction == "short"]
assert len(short_setups) == 1, "Expected exactly one short setup"
# Level A (95, strength=85) should win with highest quality
_assert_primary_is_most_likely_worthwhile(short_setups[0])
assert short_setups[0].target == pytest.approx(95.0, abs=0.01), (
f"Expected target=95.0 (highest quality), got {short_setups[0].target}"
f"Expected primary=95.0 (near, high reach-prob), got {short_setups[0].target}"
)