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