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
+22 -8
View File
@@ -59,6 +59,7 @@ from app.services.admin_service import get_activation_config, update_setting
from app.services.indicator_service import _extract_ohlcv, compute_atr
from app.services.momentum_service import (
STRATEGY_RANK_MOMENTUM_WEIGHT,
blend_strategy_rank,
compute_realized_vol_6m,
)
from app.services.outcome_service import (
@@ -77,6 +78,7 @@ from app.services.qualification import (
setup_qualifies,
)
from app.services.recommendation_service import (
PRIMARY_TARGET_MIN_RR,
_choose_recommended_action,
_classify_by_probability,
_prune_floor_pinned_targets,
@@ -337,11 +339,11 @@ def _window_setups(
targets = _prune_floor_pinned_targets(targets)
primary = _select_primary_target(
targets,
min_rr=1.5,
min_rr=PRIMARY_TARGET_MIN_RR,
)
if primary is None:
continue
# Flag the primary so qualification's EV uses the primary target's
# Flag the primary so qualification uses the primary target's
# probability (matching production's enhance_trade_setup).
for t in targets:
t["is_primary"] = t is primary
@@ -1265,15 +1267,27 @@ def _assign_weighted_blend(
primary_weight: float,
secondary_key: str,
) -> None:
secondary_weight = 1.0 - primary_weight
"""Blend ranks; fall back to primary when secondary is missing.
Matches live ``blend_strategy_rank`` for the production 80/20 key: a name
with residual momentum but no vol history keeps its mom percentile instead
of ranking as 0 / None at the bottom of the book.
"""
for c in candidates:
primary = c.get(primary_key)
secondary = c.get(secondary_key)
c[output_key] = (
primary * primary_weight + secondary * secondary_weight
if primary is not None and secondary is not None
else None
)
# Production weight path: reuse the shared helper so live/sim cannot drift.
if primary_weight == STRATEGY_RANK_MOMENTUM_WEIGHT:
c[output_key] = blend_strategy_rank(
None if primary is None else float(primary),
None if secondary is None else float(secondary),
momentum_weight=primary_weight,
)
continue
if primary is not None and secondary is not None:
c[output_key] = primary * primary_weight + secondary * (1.0 - primary_weight)
else:
c[output_key] = primary
def _assign_residual_low_vol_blend(candidates: list[dict]) -> None: