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
+37 -21
View File
@@ -1,7 +1,8 @@
"""Integration tests for R:R scanner full flow with quality-based target selection.
"""Integration tests for R:R scanner full flow with probability-based primary.
Verifies the complete scan_ticker pipeline: quality-based S/R level selection,
correct TradeSetup field population, and database persistence.
Verifies scan_ticker → enhance_trade_setup: headline target is the primary
selected by probability floors (not the pre-enhance quality candidate loop),
TradeSetup fields, and persistence.
**Validates: Requirements 2.1, 2.2, 2.3, 2.4, 3.4**
"""
@@ -63,35 +64,52 @@ def _make_ohlcv_bars(
# ===========================================================================
# 8.1 Integration test: full scan_ticker flow with quality-based selection,
# 8.1 Integration test: full scan_ticker flow with probability primary,
# correct TradeSetup fields, and database persistence
# ===========================================================================
def _assert_headline_is_probability_primary(setup: TradeSetup) -> None:
"""Headline target/rr must match the starred primary from _select_primary_target."""
targets = setup.targets or []
assert targets, "expected generated targets after enhance"
primaries = [t for t in targets if t.get("is_primary")]
assert len(primaries) == 1, "exactly one primary target expected"
primary = primaries[0]
assert setup.target == pytest.approx(float(primary["price"]), abs=0.01)
assert setup.rr_ratio == pytest.approx(float(primary["rr_ratio"]), abs=0.01)
worthwhile = [
t for t in targets
if float(t.get("rr_ratio", 0.0)) >= 1.5 and float(t.get("probability", 0.0)) >= 20.0
]
pool = worthwhile or targets
best = max(pool, key=lambda t: (float(t["probability"]), float(t["rr_ratio"])))
assert primary["price"] == pytest.approx(float(best["price"]), abs=0.01)
@pytest.mark.asyncio
async def test_scan_ticker_full_flow_quality_selection_and_persistence(
async def test_scan_ticker_full_flow_probability_primary_and_persistence(
scan_session: AsyncSession,
):
"""Integration test for the complete scan_ticker pipeline.
"""Integration test for the complete scan_ticker → enhance pipeline.
Scenario:
- Entry ≈ 100, ATR ≈ 2.0, risk ≈ 3.0 (atr_multiplier=1.5)
- 3 resistance levels above (long candidates):
A: price=105, strength=90 (strong, near) → highest quality
A: price=105, strength=90 (strong, near) → typically highest reach-prob
B: price=115, strength=40 (medium, mid)
C: price=135, strength=5 (weak, far)
C: price=135, strength=5 (weak, far / lottery)
- 3 support levels below (short candidates):
D: price=95, strength=85 (strong, near) → highest quality
D: price=95, strength=85 (strong, near)
E: price=85, strength=35 (medium, mid)
F: price=65, strength=8 (weak, far)
- CompositeScore: 72.5
Verifies:
1. Both long and short setups are produced
2. Long target = Level A (highest quality, not most distant)
3. Short target = Level D (highest quality, not most distant)
4. All TradeSetup fields are correct and rounded to 4 decimals
5. rr_ratio is the actual R:R of the selected level
6. Old setups are deleted, new ones persisted
2. Headline is the probability-based primary (not a distant lottery)
3. Near/strong levels win over far/weak when they clear floors
4. rr_ratio matches the selected primary's R:R
5. Old setups are deleted, new ones persisted
"""
# -- Setup: create ticker --
ticker = Ticker(symbol="INTEG")
@@ -172,16 +190,14 @@ async def test_scan_ticker_full_flow_quality_selection_and_persistence(
long_setup = long_setups[0]
short_setup = short_setups[0]
# -- Assert: long target is Level A (highest quality, not most distant) --
# Level A: price=105 (strong, near) should beat Level C: price=135 (weak, far)
# -- Assert: headline is probability primary; near/strong beats far lottery --
_assert_headline_is_probability_primary(long_setup)
_assert_headline_is_probability_primary(short_setup)
assert long_setup.target == pytest.approx(105.0, abs=0.01), (
f"Long target should be 105.0 (highest quality), got {long_setup.target}"
f"Long primary should be 105.0 (near, high reach-prob), got {long_setup.target}"
)
# -- Assert: short target is Level D (highest quality, not most distant) --
# Level D: price=95 (strong, near) should beat Level F: price=65 (weak, far)
assert short_setup.target == pytest.approx(95.0, abs=0.01), (
f"Short target should be 95.0 (highest quality), got {short_setup.target}"
f"Short primary should be 95.0 (near, high reach-prob), got {short_setup.target}"
)
# -- Assert: entry_price is the last close (≈ 100) --