Setup views: primary-target column, floor-target prune, liveness cutoff
Three follow-ups to the gate probability floor (8f41143):
- Signals table shows the starred primary target (shared primaryTarget
helper) instead of an independently computed max-probability best,
so Overview, Signals and ticker details agree by construction.
- Targets pinned at the 3% probability clamp floor collapse to the
nearest one (enhance_trade_setup + backtest candidates in parity):
floor-pinned levels are indistinguishable to the model, so farther
ones were duplicate 3% rows inviting lottery headlines.
- get_trade_setups only returns setups re-emitted within
LIVE_SETUP_MAX_AGE_DAYS (3): an older latest row means the daily
scan no longer confirms the setup, and such rows otherwise surface
forever on Overview/Signals/ticker/alerts. History endpoints keep
full history.
Backtest on the Jul-3 snapshot is metric-identical to the gate-floor
run on all qualified stats (1089 qualified, Sharpe 2.02, CAGR +49.6%,
DD -15.8%): the prune only removes noise the gate already rejected.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -45,6 +45,12 @@ _MODERATE_MAX_ATR = 4.6
|
||||
# the same tolerance the chart and alerts use, so S/R is one model app-wide.
|
||||
_SR_ZONE_TOLERANCE = 0.02
|
||||
|
||||
# Reach-probability estimates are clamped to this band; a target at the floor
|
||||
# means "the model considers it essentially unreachable" and floor-pinned
|
||||
# targets are mutually indistinguishable.
|
||||
_PROBABILITY_CLAMP_LOW = 3.0
|
||||
_PROBABILITY_CLAMP_HIGH = 95.0
|
||||
|
||||
|
||||
def _clamp(value: float, low: float, high: float) -> float:
|
||||
return max(low, min(high, value))
|
||||
@@ -408,7 +414,7 @@ class ProbabilityEstimator:
|
||||
elif opposed:
|
||||
probability -= signal_weight * 100.0
|
||||
|
||||
return round(_clamp(probability, 3.0, 95.0), 2)
|
||||
return round(_clamp(probability, _PROBABILITY_CLAMP_LOW, _PROBABILITY_CLAMP_HIGH), 2)
|
||||
|
||||
|
||||
signal_conflict_detector = SignalConflictDetector()
|
||||
@@ -582,6 +588,26 @@ PRIMARY_TARGET_MIN_RR = 1.5
|
||||
PRIMARY_TARGET_MIN_PROBABILITY = MIN_TARGET_PROBABILITY
|
||||
|
||||
|
||||
def _prune_floor_pinned_targets(targets: list[dict]) -> list[dict]:
|
||||
"""Keep only the nearest target pinned at the probability clamp floor.
|
||||
|
||||
Floor-pinned targets are indistinguishable to the model (true probability
|
||||
at/below the clamp), so farther ones add no information — they just fill
|
||||
the table with duplicate "3%" rows whose inflated R:R invites lottery
|
||||
picks. ``targets`` is distance-sorted by the generator, so the first
|
||||
floor-pinned entry is the nearest (most reachable) representative.
|
||||
"""
|
||||
pruned: list[dict] = []
|
||||
seen_floor = False
|
||||
for target in targets:
|
||||
if float(target.get("probability", 0.0)) <= _PROBABILITY_CLAMP_LOW:
|
||||
if seen_floor:
|
||||
continue
|
||||
seen_floor = True
|
||||
pruned.append(target)
|
||||
return pruned
|
||||
|
||||
|
||||
def _select_primary_target(
|
||||
targets: list[dict],
|
||||
min_rr: float = PRIMARY_TARGET_MIN_RR,
|
||||
@@ -665,6 +691,9 @@ async def enhance_trade_setup(
|
||||
# Label follows from the reach-probability: high prob = Conservative.
|
||||
target["classification"] = _classify_by_probability(target["probability"])
|
||||
|
||||
# Collapse duplicate floor-pinned lottery targets to the nearest one.
|
||||
targets = _prune_floor_pinned_targets(targets)
|
||||
|
||||
# Primary target = most-likely target with real asymmetry (see
|
||||
# _select_primary_target), not the old quality-score pick that ignored
|
||||
# probability. Sync the setup's headline target/rr_ratio so the chart, gate
|
||||
|
||||
Reference in New Issue
Block a user