Exit-Abstand aus M15-ATR gemessen = faellt durch, Exit bleibt am M5-ATR

Zweiter Teil der M15-Idee: M5-Einstieg behalten, nur den Exit groeber fuehren.
Schliesst zugleich den seit 31.07. in core/trailing.py offenen Punkt
("bisher misst er nur M5").

  M5-ATR (heute)  H1 +0,219 / PF 1,41 / Worst -2,17
                  H2 +0,117 / PF 1,25 / Worst -2,23
  M15-ATR         H1 +0,096 / PF 1,14 / Worst -4,26
                  H2 +0,019 / PF 1,02 / Worst -6,73

Schlechter in BEIDEN Haelften und das Tail-Risiko verdreifacht sich. Dasselbe
Muster wie bei backtest_sl_basis.py.

Zwei Methoden-Fallen, die die Messung fast entwertet haetten:

1) simulate() normiert R mit DEMSELBEN ATR, den es fuer SL/Trail/TP nutzt. Mit
   dem 1,86x groesseren M15-ATR waere dieselbe Dollar-Bewegung ein kleineres R -
   die M15-Variante haette mechanisch schlechter ausgesehen. Beide werden
   deshalb in M5-Risikoeinheiten zurueckgerechnet.

2) Der erste Lauf sammelte die Entry-Liste ueber exit_fn - das wird von scan()
   bei JEDEM Box-Ausbruch gerufen, nicht nur bei den komprimierten: 519 statt
   132 Einstiege, also die Kontrollgruppe statt des Squeeze. Behoben ueber den
   additiven Rueckruf on_entry(j, d, lvl, atr, komprimiert) in
   core/squeeze_scan.py; Bitgleichheit weiter durch tests/test_squeeze_scan.py
   belegt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Axel Hocks
2026-08-08 15:01:44 +02:00
co-authored by Claude Opus 5
parent b5eff2a5d6
commit 010007f7a3
3 changed files with 175 additions and 1 deletions
+11 -1
View File
@@ -76,7 +76,8 @@ def scan(H, L, C, A, SP, point, lo_i, hi_i, squeeze_mult, *,
box_n: int = BOX_N_STD, k: float = K_STD, atr_min: float = ATRMIN_STD,
fenster: int = FENSTER_STD, cooldown: int = COOLDOWN_STD,
max_hold: int = MAXH_STD,
exit_fn: Callable | None = None) -> tuple[list, list]:
exit_fn: Callable | None = None,
on_entry: Callable | None = None) -> tuple[list, list]:
"""Ein Durchlauf: sammelt R (netto) je Ausbruch.
Rueckgabe `(Rs_sq, Rs_any)` — `Rs_any` = ALLE Box-Ausbrueche (die Kontrolle),
@@ -86,6 +87,13 @@ def scan(H, L, C, A, SP, point, lo_i, hi_i, squeeze_mult, *,
⚠ `point` wird nicht mehr benutzt (SP kommt bereits in Preis), bleibt aber in
der Signatur, damit bestehende Aufrufe unveraendert funktionieren.
⚠ `on_entry(j, d, lvl, atr, komprimiert)` ist ADDITIV (2026-08-08): ein
Rueckruf je Ausbruch, der auch sagt, ob er aus einer KOMPRESSION kam. Ohne
ihn ist `exit_fn` der einzige Haken — und der wird bei JEDEM Box-Ausbruch
gerufen, nicht nur bei den komprimierten. Wer darueber eine Entry-Liste
sammelt, bekommt still die Kontrollgruppe statt des Squeeze (real passiert:
519 statt 132 Einstiege). Verhalten ohne `on_entry` unveraendert.
"""
ex = exit_fn or (lambda e, d, a, h, l, c, j: exit_legacy(
e, d, a, h, l, c, j, max_hold=max_hold))
@@ -118,6 +126,8 @@ def scan(H, L, C, A, SP, point, lo_i, hi_i, squeeze_mult, *,
i += 1
continue
j, d, lvl = treffer
if on_entry is not None:
on_entry(j, d, lvl, atr, komprimiert)
R = ex(lvl, d, atr, H, L, C, j + 1) - kosten(j, atr)
Rs_any.append(R)
if komprimiert: