From 00ca2406f15fab6bf98c230ed02dffe4d3afa317 Mon Sep 17 00:00:00 2001 From: Axel Hocks Date: Fri, 31 Jul 2026 21:58:23 +0200 Subject: [PATCH] 15-Minuten-Regel ganz abgeschaltet (adverse_15min_atr = 0) User-Frage "vielleicht die auto close auf 30 min setzen?" -> Zeit-Achse mitgemessen (backtest_adverse15_squeeze.py um Zeitpunkt und zweite Population erweitert): 15/30/45/60 min x 0,5/0,8xATR, Squeeze- UND Wave-Signal-Entries, kanonischer Exit, Echtkosten, 2 Halbjahre. Zeit Schwelle Squeeze H1/H2 Wave H1/H2 15 0,5 -18 / -34 -25 / -41 (war live) 30 0,5 -20 / -35 -22 / -13 45 0,8 -12 / -18 -3 / -7 60 0,8 -8 / -10 -3 / -3 ALLE 16 Kombinationen sind in BEIDEN Haelften negativ. Die Regel wird nur monoton weniger schaedlich, je spaeter und lockerer sie prueft - die "beste" Variante ist praktisch die, die nie feuert. 30 min haette den Schaden halbiert, nicht beendet. Damit ist auch meine Einschaetzung von vor einer Stunde korrigiert ("auf dem Auto-Signal-Pfad ~neutral"): sie stuetzte sich auf backtest_auto_signal.py mit eigenem Exit-Modell (+0,001 vs +0,026); mit dem kanonischen Exit sind es -25/-41. Sechster Fall desselben Musters an einem Tag. Schutz-Stack bleibt vollstaendig: Broker-SL 2xATR + Trailing + Time-Stop 120 min. Die Squeeze-Ausnahme bleibt im Code, falls die Regel je wieder eingeschaltet wird. Config-Waechter-Anker auf 0 gezogen, mit Begruendung im Eintrag. Co-Authored-By: Claude Opus 5 --- CLAUDE.md | 22 +++++++++++ backtest_adverse15_squeeze.py | 73 ++++++++++++++++++++++++++++------- measurement_reminder.py | 12 ++++-- 3 files changed, 88 insertions(+), 19 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index b51974b..e968508 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -990,6 +990,28 @@ dort bereits nachvalidiert, ØR +0,305.) **Eine** Oberfläche: weil gemessen nicht tragfähig; zeigt die Schwelle an), neustart-fest via `runtime_state.json`. **B5-Pflicht:** Live-ØR/PF gegen die Backtest-Erwartung halten (H2-Regime +0,14 vs H1-Regime −0,05) — driftet es weg, **zurückbauen** (`=false`). +- **⚠⚠ 15-MINUTEN-REGEL GANZ ABGESCHALTET (`adverse_15min_atr = 0`, 2026-07-31, + User-Entscheidung nach der Messung).** Auf die User-Frage „vielleicht die Auto-Close + auf 30 min setzen?" wurde die **ZEIT-Achse** mitgemessen + (`backtest_adverse15_squeeze.py`, 15/30/45/60 min × 0,5/0,8×ATR, BEIDE Populationen, + kanonischer Exit, Echtkosten, 2 Halbjahre): + | Zeitpunkt | Schwelle | Squeeze Δ H1/H2 | Wave Δ H1/H2 | + |---|---|---|---| + | 15 min | 0,5 | −18 / −34 ← war live | −25 / −41 | + | 30 min | 0,5 | −20 / −35 | −22 / −13 | + | 45 min | 0,8 | −12 / −18 | −3 / −7 | + | 60 min | 0,8 | −8 / −10 | −3 / −3 | + **ALLE 16 Kombinationen sind in BEIDEN Hälften negativ** — die Regel wird nur + *monoton weniger schädlich*, je später und lockerer sie prüft. Die „beste" Variante + ist praktisch die, die nie feuert. **Es gibt keine Einstellung, bei der sie hilft** + → 30 min hätte den Schaden halbiert, nicht beendet. Deshalb ganz aus. + ⚠ **Damit ist auch die frühere Einschätzung „auf dem Auto-Signal-Pfad ~neutral" + korrigiert**: sie stützte sich auf `backtest_auto_signal.py` (eigenes Exit-Modell, + +0,001 vs +0,026); mit dem kanonischen Exit sind es −25/−41. Sechster Fall desselben + Musters an einem Tag (gemessen unter X, betrieben unter Y). + Der Schutz-Stack bleibt vollständig: **Broker-SL 2×ATR + Trailing + Time-Stop 120 min**. + Die Squeeze-Ausnahme (unten) bleibt im Code — falls die Regel je reaktiviert wird, + gilt sie weiterhin nicht für den Squeeze. - **⚠ 15-MINUTEN-REGEL: SQUEEZE-TRADES AUSGENOMMEN (gemessen `backtest_adverse15_squeeze.py`, 2026-07-31).** Die Schwelle 0,5×ATR wurde in `backtest_auto_signal.py` auf **WAVE-SIGNAL-Entries** kalibriert und danach auf ALLE diff --git a/backtest_adverse15_squeeze.py b/backtest_adverse15_squeeze.py index 352b76c..b07fcf5 100644 --- a/backtest_adverse15_squeeze.py +++ b/backtest_adverse15_squeeze.py @@ -66,8 +66,9 @@ def squeeze_signals(H, L, C, AT, lo, hi): return out -def run(sig, H, L, C, SP, adverse=None): - """`adverse` = Schwelle in ×ATR (None = Regel aus).""" +def run(sig, H, L, C, SP, adverse=None, minutes=15): + """`adverse` = Schwelle in ×ATR (None = Regel aus), `minutes` = Prüfzeitpunkt.""" + bars = max(1, minutes // 5) Rs = [] for (j, d, atr, lvl) in sig: cost = (SP[j] if SP[j] > 0 else 0.0225) / atr @@ -75,13 +76,41 @@ def run(sig, H, L, C, SP, adverse=None): if adverse is not None: j0 = j + 1 - def hook(k, prof_atr, _j0=j0, _x=adverse): - # 1× genau beim 15-Min-Zeitpunkt prüfen (wie live) - return (k - _j0) == _BARS_15MIN - 1 and prof_atr <= -_x + def hook(k, prof_atr, _j0=j0, _x=adverse, _b=bars): + # 1× genau zum Prüfzeitpunkt (wie live: einmal je Ticket) + return (k - _j0) == _b - 1 and prof_atr <= -_x Rs.append(simulate(lvl, d, atr, H, L, C, j + 1, LIVE, stop_when=hook) - cost) return Rs +def wave_signals(H, L, C, AT, lo, hi, step=6): + """WAVE-Signal-Entries — die Population, auf der die Regel kalibriert WURDE + und auf der sie live noch gilt (Auto-Signal-Pfad).""" + from core.analysis import calc_trend_angle + from core.wave_rec import (_EMA_FAST, _EMA_SLOW, _ANGLE_LR, _ANGLE_DEAD, + _REVERSAL_STRETCH, _STRETCH_MAX) + def ema(v, p): + k = 2.0 / (p + 1); o = []; e = v[0] + for i, x in enumerate(v): + e = x if i == 0 else x * k + e * (1 - k); o.append(e) + return o + EF = ema(C, _EMA_FAST); ES = ema(C, _EMA_SLOW) + out = [] + for i in range(max(lo, _N_BARS), min(hi, len(C) - LIVE.max_hold - 1), step): + atr = AT[i] + if not atr or atr < _ATRMIN: + continue + stretch = (C[i] - ES[i]) / atr + ang = calc_trend_angle(C[i - _ANGLE_LR - 2:i], _ANGLE_LR) - 90.0 + d = 0 + if stretch <= -_REVERSAL_STRETCH and ang >= _ANGLE_DEAD: d = 1 + elif stretch >= _REVERSAL_STRETCH and ang <= -_ANGLE_DEAD: d = -1 + elif abs(stretch) < _STRETCH_MAX: d = 1 if EF[i] > ES[i] else -1 if EF[i] < ES[i] else 0 + if d: + out.append((i, d, atr, C[i])) + return out + + def st(Rs): if not Rs: return " —" @@ -115,17 +144,31 @@ def main(): for lbl, _, _ in halves: print(f" {lbl}: {len(SIG[lbl])} Squeeze-Entries") - base = {} - for name, adv in (("OHNE Regel", None), ("0,3×ATR", 0.3), - ("0,5×ATR ← live", 0.5), ("0,8×ATR", 0.8), ("1,2×ATR", 1.2)): - print(f"\n{name}") + WAV = {lbl: wave_signals(H, L, C, AT, a, b) for lbl, a, b in halves} + for lbl, _, _ in halves: + print(f" {lbl}: {len(WAV[lbl])} Wave-Signal-Entries") + + # ZEIT-Achse mitmessen (User-Frage 2026-07-31 „vielleicht die auto close auf + # 30 min setzen?"): nicht nur WIE WEIT gegen die Richtung, sondern auch WANN + # geprüft wird. Beide Populationen, weil die Regel auf der einen kalibriert + # wurde und auf der anderen (bis heute) angewandt. + for pop, SETS in (("SQUEEZE-Entries", SIG), ("WAVE-SIGNAL-Entries", WAV)): + print(f"\n{'=' * 92}\n {pop}\n{'=' * 92}") + base = {} for lbl, _, _ in halves: - Rs = run(SIG[lbl], H, L, C, SP, adverse=adv) - s = sum(Rs) - if adv is None: - base[lbl] = s - d = f" Δ ohne Regel {s - base.get(lbl, 0):+.0f}" if adv is not None else "" - print(f" {lbl} {st(Rs)}{d}") + Rs0 = run(SETS[lbl], H, L, C, SP, adverse=None) + base[lbl] = sum(Rs0) + print(f" {lbl} OHNE Regel {st(Rs0)}") + print(f"\n {'Zeitpunkt':<12}{'Schwelle':<12}" + + "".join(f"{l[:2]:>11}" for l, _, _ in halves) + " (Δ gegen OHNE Regel)") + for mins in (15, 30, 45, 60): + for adv in (0.5, 0.8): + cells = [] + for lbl, _, _ in halves: + tot = sum(run(SETS[lbl], H, L, C, SP, adverse=adv, minutes=mins)) + cells.append(f"{tot - base[lbl]:>+11.0f}") + mark = " ← live" if (mins == 15 and adv == 0.5) else "" + print(f" {mins:>4} min {adv:>5.1f}×ATR " + "".join(cells) + mark) if __name__ == "__main__": diff --git a/measurement_reminder.py b/measurement_reminder.py index 4d2da23..b032d6b 100644 --- a/measurement_reminder.py +++ b/measurement_reminder.py @@ -194,10 +194,14 @@ CONFIG_DEPS = [ "P(DD>80 %)). Ändert sich er, ändert sich das Ruin-Risiko.", }, { - "key": "adverse_15min_atr", "test": "backtest_auto_signal.py (15-Min-Achse)", - "validated": "0.5", - "why": "0,5 war die beste Regel-Variante; 0,0 (sofort im Minus schliessen) war " - "die SCHLECHTESTE (WR bricht 39→31 %). Andere Werte sind ungemessen.", + "key": "adverse_15min_atr", "test": "backtest_adverse15_squeeze.py", + "validated": "0", + "why": "0 = AUS (2026-07-31). Nachgemessen mit dem kanonischen Exit über die " + "ZEIT- und Schwellen-Achse (15/30/45/60 min × 0,5/0,8×ATR) auf BEIDEN " + "Populationen: **alle 16 Kombinationen sind in beiden Hälften negativ**, " + "nur monoton weniger schädlich je später/lockerer. Es gibt keine " + "Einstellung, bei der die Regel hilft. Wiedereinschalten wäre nicht " + "gedeckt — der Schutz läuft über Broker-SL 2×ATR, Trailing und Time-Stop.", }, { "key": "auto_signal_min_conf", "test": "backtest_auto_signal.py",