From 1e8d700f46d4b5f35e8beeb845fa3670d2117aa8 Mon Sep 17 00:00:00 2001 From: Axel Hocks Date: Thu, 13 Aug 2026 16:33:11 +0200 Subject: [PATCH] "S/R-Close kappt die Squeeze-Laeufer" = widerlegt (backtest_squeeze_srclose2.py) Meine eigene Hypothese vom selben Tag -- gemessen falsch, und zwar doppelt. (1) BACKTEST (80k M5, 2 Halbjahre, Einstiege ueber den geteilten squeeze_scan.scan(on_entry=...), ruhende Order am Level mit Fill bei Beruehrung, kanonischer Exit, Echtkosten, ein Slot; identische Entry-Liste ueber alle Varianten, variiert wird NUR der Exit): A) ohne S/R-Close H1 -0,151 / PF 0,72 H2 -0,079 / PF 0,86 B) mit p<0,35 (LIVE) H1 -0,119 / PF 0,76 H2 -0,049 / PF 0,91 D +0,032/+0,030 B) mit p<0,45 H1 -0,088 H2 -0,046 D +0,063/+0,033 Der S/R-Close ist in BEIDEN Haelften besser als ohne -- die vorab fixierte Regel sagt: anlassen. Und die von mir vorgeschlagene "Abhilfe" (Close erst ab Mindestgewinn) macht H1 schlechter (D -0,006 / -0,011). (2) LIVE, 11 Squeeze-Trades ab 01.08.: sl 5 Trades -198,17 EUR sr_close 4 Trades +15,98 EUR <- der verdaechtigte Exit hat BEIGETRAGEN manual 2 Trades +8,73 EUR Der groesste Gewinner (+77,34) kam ebenfalls ueber sl, also ueber das Trailing. Die Groesse steckt allein auf der Verlustseite: -175,21 EUR bei 2,91 Lots = 0,695 $ = der planmaessige 2xATR-Stop. Der Hebel zeigt auf SIZING, nicht Exit. GRENZE, nicht ueberspielt: die ABSOLUTEN Werte reproduzieren den dokumentierten Kontrollwert NICHT (backtest_squeeze_touchfill.py: +0,124/+0,292 bei 1.092/1.807 Trades gegen -0,151/-0,079 bei 273/422). Belastbar ist allein der RELATIVE Vergleich; "der Squeeze ist negativ" ist hier NICHT belegt. Zwei eigene Fehler dokumentiert, weil es Fehlerklassen sind: - erster Lauf nahm den Modul-Default ATRMIN_STD=0,12 statt des Live-Werts 0,06 -> H1 127 gegen H2 483 Einstiege, voellig andere Population. Dieselbe Klasse wie das fehlende set_entry_room(0.6): ein aufgerufenes Gate ist nicht dasselbe wie ein aktives Gate. - erster Entwurf haengte den Close an stop_when (prueft den BAR-CLOSE gegen ein 0,018-$-Fenster) -> feuerte fast nie. Live prueft den laufenden Kurs im Sekundentakt; das Analogon ist die Bar-SPANNE. Nichts am Live-System geaendert. Co-Authored-By: Claude Opus 5 --- CLAUDE.md | 64 +++++++ backtest_squeeze_srclose2.py | 315 +++++++++++++++++++++++++++++++++++ 2 files changed, 379 insertions(+) create mode 100644 backtest_squeeze_srclose2.py diff --git a/CLAUDE.md b/CLAUDE.md index 5d85c80..97553c8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -5553,6 +5553,70 @@ läuft die B5-Regel **nie ab**, der schlechteste Zustand. `auto_sr_close=True`, Circuit-Breaker **aus** (`limit_pct=0`), Sizing 95 % Margin, Balance 1.279 €. Bei Ø-Verlust 94 € sind das **7,4 % des Kontos je Verlusttrade**. +### ✅⚠ „DER S/R-CLOSE KAPPT DIE SQUEEZE-LÄUFER" = WIDERLEGT (`backtest_squeeze_srclose2.py`, 2026-08-13) + +Meine eigene Hypothese vom selben Tag (drei von sieben Pending-Trades per +`sr_close` bei +0,26 / +2,88 / +0,28 € beendet) — **gemessen falsch, und zwar +doppelt.** + +**(1) BACKTEST** (80k M5, 2 Halbjahre, Einstiege über den GETEILTEN +`squeeze_scan.scan(on_entry=…)`, ruhende Order am Level mit Fill bei Berührung, +kanonischer Exit, Echtkosten, EIN Slot; identische Entry-Liste über alle +Varianten, variiert wird NUR der Exit): +| Variante | H1 ØR / PF | H2 ØR / PF | Δ gegen ohne | +|---|---|---|---| +| **A) ohne S/R-Close** | −0,151 / 0,72 | −0,079 / 0,86 | — | +| **B) mit, p<0,35 (LIVE)** | **−0,119 / 0,76** | **−0,049 / 0,91** | **+0,032 / +0,030** | +| B) p<0,45 | −0,088 / 0,82 | −0,046 / 0,92 | +0,063 / +0,033 | +| B) p<0,55 | −0,086 / 0,82 | −0,038 / 0,93 | +0,065 / +0,041 | +**Der S/R-Close ist in BEIDEN Hälften BESSER als ohne** — die vorab fixierte +Regel sagt damit: **anlassen.** ⚠ Und die von mir vorgeschlagene „Abhilfe" +(Close erst ab Mindestgewinn) macht H1 **schlechter** (Δ −0,006 / −0,011 bei +0,5 bzw. 1,0×ATR) — der Eingriff hätte geschadet. + +**(2) LIVE, 11 Squeeze-Trades ab 01.08. — die Zerlegung ist eindeutig:** +| Schließgrund | n | Σ | Ø | +|---|---|---|---| +| **`sl`** (Stop/Trailing) | 5 | **−198,17 €** | −39,63 | +| `sr_close` | 4 | **+15,98 €** | +4,00 | +| `manual` | 2 | +8,73 € | +4,37 | +⚠⚠ **Der verdächtigte Exit hat +15,98 € BEIGETRAGEN, nicht gekostet.** Und der +größte Gewinner des Zeitraums (+77,34 €) kam ebenfalls über `sl` — also über das +**Trailing**, nicht trotz ihm. +⚠ Die Größe steckt allein auf der Verlustseite: **−175,21 € bei 2,91 Lots** = +0,695 $ Bewegung — das ist der **planmäßige 2×ATR-Stop** bei dem ATR jenes +Moments, kein Ausreißer und kein Exit-Defekt. Ø-Verlust 94,15 € auf 1.279 € +Konto = **7,4 % je Verlusttrade**. +✅ **Damit zeigt der Hebel auf SIZING, nicht auf den Exit** — genau dorthin, wo +CLAUDE.md ihn ohnehin verortet („Der Hebel ist Sizing, nicht der Exit"). + +⚠⚠ **GRENZE, die nicht überspielt werden darf: die ABSOLUTEN Werte reproduzieren +den dokumentierten Kontrollwert NICHT.** `backtest_squeeze_touchfill.py` liefert +für denselben nominellen Aufbau **+0,124/+0,292** bei 1.092/1.807 Trades, hier +sind es −0,151/−0,079 bei 273/422. Die Referenz hat also **mehr Trades als ich +Einstiege habe** — ihre Auswahl ist lockerer (Fenster/Cooldown/Slot-Behandlung). +**Deshalb ist der Befund „der Squeeze ist negativ" hier NICHT belegt**; belastbar +ist allein der **relative** Vergleich, weil alle Varianten auf derselben +Entry-Liste laufen und sich nur im Exit unterscheiden. Wer die Absolutwerte +braucht, muss zuerst die Kontrolle reproduzieren (die Projekt-Regel, die +`backtest_verworfene_v2.py` vorbildlich befolgt). + +⚠⚠ **EIGENER FEHLER auf dem Weg, dokumentiert weil es eine Fehlerklasse ist:** +der erste Lauf nahm den Modul-Default `squeeze_scan.ATRMIN_STD = 0,12`, während +live und die Referenz **0,06** nutzen (der Floor wurde gemessen gesenkt). Folge: +**H1 127 gegen H2 483 Einstiege** — der hohe Floor filtert die Niedrig-Vola- +Hälfte weg und erzeugt eine ganz andere Population. Aufgefallen nur durch die +Schieflage der Entry-Zahlen. Dieselbe Klasse wie das fehlende +`set_entry_room(0.6)` am 05.08.: **ein aufgerufenes Gate ist nicht dasselbe wie +ein aktives Gate; beim Wiederverwenden eines Kerns gehört die Live-KONFIGURATION +mitkopiert.** ⚠ Die Schlussfolgerung war in beiden Läufen dieselbe. +⚠ Zweiter eigener Fehler: der erste Entwurf hängte den S/R-Close an den +`stop_when`-Haken, der zum **Bar-Close** prüft — bei einem „am Level"-Fenster von +0,15×ATR ≈ 0,018 $ feuerte er fast nie (Δ ≈ 0,000), während er live 94–97 % aller +Berührungen schliesst. Live prüft den **laufenden** Kurs im Sekundentakt; das +Analogon ist die **Bar-Spanne**. Danach feuert er in 21–23 % der Trades (live +4 von 11 = 36 %, bei n=11 kein Widerspruch). + ### ✅ S/R-AUTO-CLOSE WIEDER AN (User-Entscheidung 2026-08-11) Frage war: „sollten wir Trades bei einem S/R-Bounce automatisch schliessen?" diff --git a/backtest_squeeze_srclose2.py b/backtest_squeeze_srclose2.py new file mode 100644 index 0000000..3a162a2 --- /dev/null +++ b/backtest_squeeze_srclose2.py @@ -0,0 +1,315 @@ +#!/usr/bin/env python3 +"""Kappt der S/R-Auto-Close die Squeeze-Laeufer? — NEU unter dem HEUTIGEN Stand. + +ANLASS (2026-08-13). Der Pending-Umbau vom 05.08. hat gewirkt (7 von 8 Bot-Trades +fuellen per ruhender Order), der Squeeze verliert aber weiter. Die Signatur zeigt +auf den Exit, nicht mehr auf den Einstieg: + Trefferquote 73 % · OeGewinn 13,62 EUR gegen OeVerlust 94,15 EUR (Faktor 7) + Drei von sieben Pending-Trades per `sr_close` bei +0,26 / +2,88 / +0,28 EUR + geschlossen, waehrend die Verlierer bis zum vollen Stop liefen. +Der Squeeze lebt gemessen von LAEUFERN (43 % Treffer, Ertrag im Tail). + +⚠⚠ WARUM DIE ALTE MESSUNG NICHT MEHR BINDET. `backtest_squeeze_srclose.py` +(24.07.) fand auf Squeeze-Entries einen WASH (+0,190→+0,224 / +0,394→+0,377) — +aber mit dem **alten** P(break)-Modell und Schwelle **0,55/0,60**. Seit dem +31.07. laeuft das **nachtrainierte** Modell mit Schwelle **0,35**, und das +schliesst live **94-97 %** aller Level-Beruehrungen. Gemessen unter X, betrieben +unter Y — dasselbe Muster wie die acht dokumentierten Deployment-Drift-Faelle. + +VORAB FIXIERTE ENTSCHEIDUNGSREGEL: + Der S/R-Close bleibt fuer Squeeze-Trades an, wenn er in BEIDEN Haelften + mindestens so gut ist wie OHNE ihn (Variante A). Sonst: Squeeze-Trades vom + S/R-Close ausnehmen — oder, falls eine Mindestgewinn-Schwelle in BEIDEN + Haelften A schlaegt, diese setzen. + +AUFBAU — live-treu, mit den geteilten Kernen: + · Einstiege ueber `core.squeeze_scan.scan(on_entry=...)` = DERSELBE Code wie + der Live-Squeeze. ⚠ `on_entry` statt `exit_fn`, sonst bekommt man still die + Kontrollgruppe ALLER Box-Ausbrueche (real: 519 statt 132). + · Einstieg als ruhende Stop-Order am Level, **Fill bei Beruehrung** — das ist + das Live-Verhalten seit dem 05.08. + · Exit `core.exit_model.simulate` mit dem `stop_when`-Haken; damit wird die + Phasen-Mechanik NICHT kopiert (die Kopiererei hat vier divergierende + Exit-Modelle erzeugt). + · Level ueber `core.sr_levels` (M30-Pivots, geclustert) = derselbe Code wie + `_draw_levels`. P(break) ueber `core.engine._p_break` mit den Live-Gewichten, + Merkmale trainingsgleich aus M5. + · Sequentiell EIN Slot, Echtkosten aus dem Bar-Spread, 2 Halbjahre, Bootstrap-KI. + +⚠ ZWEI BEWUSSTE NAEHERUNGEN, ehrlich benannt: + (1) Der Haken schliesst zum BAR-CLOSE; live feuert der Check im 1-s-Takt. + Dieselbe Naeherung wie bei der Flip-Close-Messung. + (2) Live glaettet P(break) ueber ~60 s und hat eine Hysterese von ±5 Pp. Auf + 5-Minuten-Bars ist beides gegenstandslos — jeder Bar ist eine frische + Auswertung. Die Schwellen-Naehe wird dadurch etwas haeufiger ueberschritten + als live, also wirkt der S/R-Close hier eher ZU stark. Das faellt zu + Gunsten der Nullhypothese aus (kein Eingriff). + +Aufruf: python backtest_squeeze_srclose2.py [bars] +""" +from __future__ import annotations + +import random +import sys +from bisect import bisect_left, bisect_right + +import MetaTrader5 as mt5 + +from core.engine import _p_break +from core.exit_model import LIVE, simulate +from core.sr_levels import halten, linien, naechste +from core.squeeze_scan import atr_series, scan + +_PIV_K = 3 +_LOOK = 50 +_ATR5_FLOOR = 0.12 +_AT_LEVEL = 0.15 # „am Level" wie live (_sr_close_hint) +_WARTE = 8 # Bars, die die ruhende Order liegt +# Live-Parameter des Squeeze (core/wave_rec.py) +_SQ_N, _SQ_K, _SQ_MULT = 10, 0.1, 2.5 +# ⚠⚠ ATR-FLOOR: `core.squeeze_scan` hat als Default ATRMIN_STD = 0,12 — LIVE und +# der dokumentierte Referenzlauf (`backtest_squeeze_touchfill.py`) nutzen aber +# **0,06** (am 2026-07-xx gemessen gesenkt, `backtest_atrfloor.py`: in BEIDEN +# Haelften besser). Der erste Lauf hier nahm den Modul-Default und bekam damit +# eine voellig andere Population — H1 127 gegen H2 483 Einstiege, weil der +# hohe Floor die Niedrig-Vola-Haelfte wegfiltert. Dieselbe Fehlerklasse wie +# das fehlende `set_entry_room(0.6)` am 05.08.: **ein aufgerufenes Gate ist +# nicht dasselbe wie ein aktives Gate.** +_ATRMIN = 0.06 + + +def agg(H, L, C, step): + hh, hl, hc = [], [], [] + for s in range(0, len(C) - step + 1, step): + hh.append(max(H[s:s + step])); hl.append(min(L[s:s + step])) + hc.append(C[s + step - 1]) + return hh, hl, hc + + +def pivots_m30(H, L, C): + """ALLE M30-Pivots EINMAL (Index + Preis) — O(log n) statt quadratisch.""" + hh, hl, _ = agg(H, L, C, 6) + ph_i, ph_v, pl_i, pl_v = [], [], [], [] + for x in range(_PIV_K, len(hh) - _PIV_K): + if hh[x] == max(hh[x - _PIV_K:x + _PIV_K + 1]): + ph_i.append(x); ph_v.append(round(hh[x], 3)) + if hl[x] == min(hl[x - _PIV_K:x + _PIV_K + 1]): + pl_i.append(x); pl_v.append(round(hl[x], 3)) + return (ph_i, ph_v), (pl_i, pl_v) + + +def level_serie(H, L, C, A5): + """(res, sup) je M5-Bar — DERSELBE Weg wie `_draw_levels`, inkl. Hysterese.""" + (ph_i, ph_v), (pl_i, pl_v) = pivots_m30(H, L, C) + out = [(None, None)] * len(C) + pr = ps = None + for i in range(len(C)): + a = A5[i] + if not a: + continue + a5f = max(a, _ATR5_FLOOR) + j = i // 6 - 1 + if j < _PIV_K + 5: + continue + a1, b1 = bisect_left(ph_i, j - _LOOK), bisect_right(ph_i, j - _PIV_K) + a2, b2 = bisect_left(pl_i, j - _LOOK), bisect_right(pl_i, j - _PIV_K) + ph, pl = ph_v[a1:b1], pl_v[a2:b2] + if not ph and not pl: + continue + r, s = naechste(linien(ph, pl, a5f), C[i]) + r = halten(r, pr, C[i], a5f, True) + s = halten(s, ps, C[i], a5f, False) + pr, ps = r, s + out[i] = (r, s) + return out + + +def merkmale(C, A5): + """mom6 / mom3 / ema_diff je Bar — trainingsgleich aus M5.""" + def ema(v, p): + k = 2 / (p + 1); o = [v[0]] + for x in v[1:]: + o.append(o[-1] + k * (x - o[-1])) + return o + ef, es = ema(C, 12), ema(C, 50) + m6, m3, ed = [0.0] * len(C), [0.0] * len(C), [0.0] * len(C) + for i in range(6, len(C)): + a5f = max(A5[i] or _ATR5_FLOOR, _ATR5_FLOOR) + m6[i] = (C[i] - C[i - 6]) / a5f + m3[i] = (C[i] - C[i - 3]) / a5f + ed[i] = (ef[i] - es[i]) / a5f + return m6, m3, ed + + +def entries(H, L, C, A, SP, point, lo, hi): + """Squeeze-Ausbrueche ueber den GETEILTEN Kern einsammeln.""" + got = [] + scan(H, L, C, A, SP, point, lo, hi, _SQ_MULT, box_n=_SQ_N, k=_SQ_K, + atr_min=_ATRMIN, + exit_fn=lambda e, d, a, h, l, c, j: 0.0, + on_entry=lambda j, d, lvl, a, komp: ( + got.append({"j": j, "d": d, "lvl": lvl, "atr": a}) if komp else None)) + return got + + +def lauf(ein, H, L, C, SP, A5, LV, M6, M3, ED, schwelle, min_gain=0.0): + """Sequentiell EIN Slot. `schwelle=None` → ohne S/R-Close.""" + Rs = [] + n_close = 0 # wie oft der S/R-Close wirklich gefeuert hat + frei_ab = -1 + grenze = len(C) - LIVE.max_hold - 2 + for e in ein: + i, d, lvl = e["j"], e["d"], e["lvl"] + if i < frei_ab or i >= grenze: + continue + a5f = max(A5[i] or _ATR5_FLOOR, _ATR5_FLOOR) + fill = None + for k in range(i + 1, min(i + 1 + _WARTE, grenze)): + if L[k] <= lvl <= H[k]: + fill = k; break + if fill is None: + continue + + # ── (1) natuerlicher Exit ohne S/R-Close ───────────────────────── + R, xb = simulate(lvl, d, a5f, H, L, C, fill + 1, LIVE, ret_bar=True) + + # ── (2) S/R-Close davor? ───────────────────────────────────────── + # ⚠⚠ NICHT ueber `stop_when`. Der Haken schliesst zum BAR-CLOSE, und + # die Bedingung „am Level" ist ein Fenster von 0,15xATR ≈ 0,018 $ — + # dass ein 5-Minuten-Close genau darin landet, ist selten. Ein + # Probelauf zeigte deshalb Δ ≈ 0,000, waehrend der Close live + # 94-97 % aller Beruehrungen schliesst. Live prueft den LAUFENDEN + # Kurs im Sekundentakt — das Analogon ist die Bar-SPANNE. + # ⚠ Ausstieg zum ERSTEN Moment, in dem die Bedingung feuern kann + # (Level minus Fensterbreite), nicht zum Level selbst. Das ist das + # konservative Ende und entspricht dem 1-s-Loop. + # ⚠ Strikt VOR `xb`: faellt beides in denselben Bar, hat der SL + # Vorrang — dieselbe pessimistische Konvention wie in `simulate`. + if schwelle is not None: + for j in range(fill + 1, xb): + if profit_bedingung(j, d, lvl, a5f, min_gain, LV, C, M6, M3, ED, + H, L, schwelle): + ziel = LV[j][0] if d > 0 else LV[j][1] + R = (ziel - d * _AT_LEVEL * a5f - lvl) * d / a5f + xb = j + n_close += 1 + break + Rs.append(R - SP[i] / a5f) + frei_ab = xb + 1 + return Rs, n_close + + +def profit_bedingung(j, d, entry, a5f, min_gain, LV, C, M6, M3, ED, H, L, schwelle): + """Feuert der S/R-Auto-Close in Bar `j`? — die Live-Bedingungen der Reihe nach.""" + ziel = LV[j][0] if d > 0 else LV[j][1] # Ziel-Seite wie _sr_close_hint + if ziel is None: + return False + # „am Level": der Kurs kam im Bar bis auf 0,15xATR heran (Bar-SPANNE, s. o.) + nah = (H[j] >= ziel - _AT_LEVEL * a5f) if d > 0 else (L[j] <= ziel + _AT_LEVEL * a5f) + if not nah: + return False + aus = ziel - d * _AT_LEVEL * a5f # Preis im Moment des Ausloesens + gewinn = (aus - entry) * d / a5f + if gewinn <= 0 or gewinn < min_gain: # live: nur im PLUS + return False + gap = abs(ziel - C[j]) + wt = 1.0 if ED[j] * d > 0 else 0.0 + return _p_break(M6[j] * d, M3[j] * d, wt, gap / a5f) < schwelle + + +def ci(Rs, n=3000, seed=42): + if len(Rs) < 20: + return None, None + r = random.Random(seed); m = len(Rs) + b = sorted(sum(Rs[r.randrange(m)] for _ in range(m)) / m for _ in range(n)) + return b[int(.025 * n)], b[int(.975 * n)] + + +def kenn(Rs): + if not Rs: + return None + g = sum(x for x in Rs if x > 0); v = -sum(x for x in Rs if x < 0) + u, o = ci(Rs) + return {"n": len(Rs), "oer": sum(Rs) / len(Rs), "sr": sum(Rs), + "wr": 100 * sum(1 for x in Rs if x > 0) / len(Rs), + "pf": (g / v if v else float("inf")), "u": u, "o": o} + + +def zeile(name, k, basis=None): + if not k: + return f" {name:<38} —" + kib = " — " if k["u"] is None else f"[{k['u']:+.3f} … {k['o']:+.3f}]" + dlt = "" if basis is None else f" Δ {k['oer'] - basis['oer']:+.3f}" + cl = "" if k.get("cl") is None else f" Close {k['cl']:>3} ({k['clq']:>4.1f}%)" + return (f" {name:<38} n={k['n']:>4} WR {k['wr']:>4.1f}% OeR {k['oer']:>+.3f} " + f"PF {k['pf']:>5.2f} SR {k['sr']:>+7.1f} {kib}{dlt}{cl}") + + +def main(): + n = int(sys.argv[1]) if len(sys.argv) > 1 else 80000 + mt5.initialize() + sym = next((c for c in ("SpotCrude", "USOIL", "WTI", "XTIUSD") + if mt5.symbol_info(c)), None) + bars = mt5.copy_rates_from_pos(sym, mt5.TIMEFRAME_M5, 0, n) + point = mt5.symbol_info(sym).point + mt5.shutdown() + if bars is None: + print(" keine Bars"); return 1 + H = [float(b["high"]) for b in bars]; L = [float(b["low"]) for b in bars] + C = [float(b["close"]) for b in bars]; SP = [float(b["spread"]) * point for b in bars] + A5 = atr_series(H, L, C) + LV = level_serie(H, L, C, A5) + M6, M3, ED = merkmale(C, A5) + mid = len(C) // 2 + e1 = entries(H, L, C, A5, SP, point, 60, mid) + e2 = entries(H, L, C, A5, SP, point, mid, len(C)) + print(f"\n {sym} · {len(C)} M5-Bars · Squeeze-Entries H1 {len(e1)} · H2 {len(e2)}") + print(f" Einstieg ruhend am Level (Fill bei Beruehrung) · kanonischer Exit · " + f"Echtkosten · EIN Slot") + print(f" Regel vorab: der S/R-Close bleibt an, wenn er in BEIDEN Haelften " + f"mindestens so gut ist wie OHNE ihn.\n") + + def rechne(schw, mg=0.0): + r1, c1 = lauf(e1, H, L, C, SP, A5, LV, M6, M3, ED, schw, mg) + r2, c2 = lauf(e2, H, L, C, SP, A5, LV, M6, M3, ED, schw, mg) + k1, k2 = kenn(r1), kenn(r2) + for k, c in ((k1, c1), (k2, c2)): + if k: + k["cl"] = c + k["clq"] = 100 * c / k["n"] if k["n"] else 0 + return k1, k2 + + b1, b2 = rechne(None) + print(" ── BASIS: OHNE S/R-Close (nur kanonischer Exit) ───────────────") + print(zeile("A) ohne S/R-Close H1", b1)) + print(zeile("A) ohne S/R-Close H2", b2)) + + print("\n ── MIT S/R-Close, Schwellen-Sweep (live = 0,35) ───────────────") + for s in (0.30, 0.35, 0.45, 0.55): + k1, k2 = rechne(s) + mark = " ← LIVE" if abs(s - 0.35) < 1e-9 else "" + print(zeile(f"B) p_break < {s:.2f} H1{mark}", k1, b1)) + print(zeile(f"B) p_break < {s:.2f} H2{mark}", k2, b2)) + + print("\n ── ABHILFE-VARIANTE: Close erst ab Mindestgewinn (bei 0,35) ───") + for mg in (0.5, 1.0, 1.5): + k1, k2 = rechne(0.35, mg) + print(zeile(f"C) 0,35 + erst ab {mg:.1f}xATR H1", k1, b1)) + print(zeile(f"C) 0,35 + erst ab {mg:.1f}xATR H2", k2, b2)) + + l1, l2 = rechne(0.35) + print("\n ── URTEIL ─────────────────────────────────────────────────────") + ok = bool(l1 and l2 and b1 and b2 + and l1["oer"] >= b1["oer"] and l2["oer"] >= b2["oer"]) + print(f" S/R-Close (0,35) mindestens so gut wie ohne, in BEIDEN: " + f"{'✅ ja — anlassen' if ok else '❌ nein'}") + if not ok: + print(f" H1 {l1['oer']:+.3f} gegen {b1['oer']:+.3f} " + f"H2 {l2['oer']:+.3f} gegen {b2['oer']:+.3f}") + print(" ➜ Squeeze-Trades vom S/R-Close ausnehmen, ODER eine " + "Mindestgewinn-Schwelle, die BEIDE Haelften schlaegt (Tabelle C).") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())