Quant review 2026-09-26: alert cooldown, plain-language alerts, M15 grade-A SELL block, 1R printed targets, panel/breach fixes
Live signals.db review (2026-09-04 → 09-25, 100 unique setups replayed on the scans' stored candles): the stop-hunt claim is refuted (71% of stop-outs ran ≥ 1R past the stop, the same as the books' 70–72%); −25.1R of the −37.8R raw live book were consecutive-bar re-entries the backtester's 5-bar cooldown never takes; the cooldown view sits at the 3rd–5th percentile of the books' three-week windows. Delivery (book-neutral): - telegram_bot: 5-bar alert cooldown per timeframe (_ALERT_COOLDOWN_BARS = backtest.cooldown_bars); setup block prints the trail first as the tested exit, TP1/TP2 as guides with their R, "Planned TP1 RR", and one validated-book line per setup class (agent_panel_base_rates.json); the informational 100-pip TP line is removed (owner request). - AI block rewritten for a new trader: "AI check: the odds look better than / worse than / about usual", the chance "in 100" against the usual odds, one reason for and one against, no verdict word or confidence figure; rendered whenever the verdict carries a probability. agent_panel: persona prompts ban trading jargon (PANEL_PROMPT_VERSION v2.1-2026-09-26); the macro-source verdict now carries its persona's p_win (every live v2 row had panel_probability NULL). Dashboard and guides mirror the wording. - telegram_bot: level-breach rescans skip the settlement-pause window (the 20:45 M15 bar was re-logged at 22:01/22:06/22:11 every evening). Signal (pre-registered rule-1 campaign, CUDA twin paired with gpu_backtest.py): - M15 grade-A SELLs blocked (sell_min_quality ["A+","B"], M15 override 6): W2 813/+154.15R PF 1.53 vs 931/+146.14R PF 1.43, OOS +68.5 vs +60.67, W3 max DD 20.9 vs 19.3R. PASSED a–f. - Printed target 1 floored at 1R in every generator (signals.tp_print_floor_rr), applied AFTER the min-RR and spread gates so no trade changes; mirrored in cuda_signal_kernels.py K2. Label-only re-pin of all four timeframes (only tp1/rr_planned columns move). A floor applied before the gate cost −24.6R and was rejected. - FAILED: M15 min_rr_ratio 1.0 (trade-identical, −2.0R). Owner's call: M5 tp_floor_rr 1.0 (+48.9R W2, max DD 10.1 → 15.9R), not shipped. New pins: M5 W2 e599c8a8…, M15 W2 5b65ecf6…, M15 OOS 09b8a8e7…, H1 W3 a44fd19b…, H4 W3 f3587bae…; twin and reference MD5s equal on every window. Base-rate table rebuilt from the new pins. Suite 558 passed. Evidence: backtest/research/rr_and_sell_campaign_2026-09-26.md (plus the 2026-09-18 loss-anatomy and wrong-direction reports it builds on). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
+65
-1
@@ -903,6 +903,22 @@ class SignalAggregator:
|
||||
if height < min_tp:
|
||||
return
|
||||
|
||||
# Printed-target floor AFTER the gates (2026-09-26, see
|
||||
# config.SignalConfig.tp_print_floor_rr): the measured move stays
|
||||
# the gated target; the printed guide is at least the floor.
|
||||
# Mirrored in cuda_signal_kernels.py K2.
|
||||
tp1_dist = height
|
||||
floor_dist = sl_dist * self._printed_target_floor()
|
||||
if tp1_dist < floor_dist:
|
||||
tp1_dist = floor_dist
|
||||
if is_buy:
|
||||
tp1 = entry + floor_dist
|
||||
tp2 = entry + 1.5 * floor_dist
|
||||
else:
|
||||
tp1 = entry - floor_dist
|
||||
tp2 = entry - 1.5 * floor_dist
|
||||
rr = tp1_dist / sl_dist
|
||||
|
||||
report.trade_setup = TradeSetup(
|
||||
pair=report.symbol,
|
||||
display_name=report.display_name,
|
||||
@@ -913,7 +929,7 @@ class SignalAggregator:
|
||||
take_profit_2=tp2,
|
||||
risk_reward=round(rr, 2),
|
||||
risk_pips=sl_dist / pip_value,
|
||||
reward_pips=height / pip_value,
|
||||
reward_pips=tp1_dist / pip_value,
|
||||
pip_value=pip_value,
|
||||
strategy="XAU_CP",
|
||||
setup_pattern=pat["pattern"],
|
||||
@@ -1394,6 +1410,13 @@ class SignalAggregator:
|
||||
report.warnings[:] = kept
|
||||
report.cleaned_warnings.extend(stale)
|
||||
|
||||
def _printed_target_floor(self) -> float:
|
||||
"""The floor every generator applies to the PRINTED target 1, as a
|
||||
fraction of the stop: max(tp_floor_rr, tp_print_floor_rr). Applied
|
||||
after the gates (see config.SignalConfig.tp_print_floor_rr)."""
|
||||
sig = self.config.signals
|
||||
return max(sig.tp_floor_rr, getattr(sig, "tp_print_floor_rr", 0.0))
|
||||
|
||||
def _generate_xau_atr_trade_setup(
|
||||
self, report: SignalReport, atr: float
|
||||
) -> Optional[TradeSetup]:
|
||||
@@ -1446,6 +1469,31 @@ class SignalAggregator:
|
||||
)
|
||||
return None
|
||||
|
||||
# Printed-target floor (2026-09-26): the guide target is at least
|
||||
# max(tp_floor_rr, tp_print_floor_rr) x the stop (1R by default).
|
||||
# With a 2 x ATR stop the grade-A target of 1.5 x ATR printed
|
||||
# "RR 1:0.8" (1 x ATR with two exhaustion tags, "1:0.5"); on M15
|
||||
# the factory floor is 1R, on M5 (0.5) nothing binds. Applied
|
||||
# AFTER the gates on purpose: the min-RR and spread checks above
|
||||
# keep judging the generator's own 1.5 x ATR target, so which
|
||||
# generator serves the signal (and therefore the stop) is
|
||||
# unchanged and the books are trade-identical (a floor applied
|
||||
# before the spread gate moved 94 grade-A M15 setups onto the
|
||||
# 2 x ATR stop and cost -24.6R on W2: rr_and_sell_campaign_
|
||||
# 2026-09-26.md). TP1 is advisory in the backtester (the trail is
|
||||
# the exit), so only the exported tp1/tp2/rr_planned columns
|
||||
# move. Mirrored in cuda_signal_kernels.py K2.
|
||||
floor_dist = sl_distance * self._printed_target_floor()
|
||||
if tp_distance < floor_dist:
|
||||
tp_distance = floor_dist
|
||||
if is_buy:
|
||||
tp1 = price + floor_dist
|
||||
tp2 = price + floor_dist * 1.5
|
||||
else:
|
||||
tp1 = price - floor_dist
|
||||
tp2 = price - floor_dist * 1.5
|
||||
rr = tp_distance / sl_distance
|
||||
|
||||
return TradeSetup(
|
||||
pair=report.symbol,
|
||||
display_name=report.display_name,
|
||||
@@ -1865,6 +1913,22 @@ class SignalAggregator:
|
||||
)
|
||||
return None
|
||||
|
||||
# Printed-target floor AFTER the gates (2026-09-26, see
|
||||
# config.SignalConfig.tp_print_floor_rr): the gates above judged
|
||||
# the swing/ATR target, so the trade is unchanged; only the guide
|
||||
# moves. TP2 keeps its "at least 1 ATR beyond TP1" rule.
|
||||
# Mirrored in cuda_signal_kernels.py K2.
|
||||
floor_dist = sl_distance * self._printed_target_floor()
|
||||
if tp1_distance < floor_dist:
|
||||
tp1_distance = floor_dist
|
||||
if is_buy:
|
||||
tp1 = entry + floor_dist
|
||||
tp2 = max(tp2, tp1 + atr)
|
||||
else:
|
||||
tp1 = entry - floor_dist
|
||||
tp2 = min(tp2, tp1 - atr)
|
||||
rr = tp1_distance / sl_distance
|
||||
|
||||
risk_pips = sl_distance / pip_value
|
||||
reward_pips = tp1_distance / pip_value
|
||||
|
||||
|
||||
Reference in New Issue
Block a user