Level-Auswahl: Nachbau behoben (core/sr_levels.py) - Luecke bleibt offen

Auftrag zum benannten naechsten Schritt: die Backtests bauten die
Live-Level-Auswahl nach, statt sie zu benutzen. Drei echte Abweichungen:

  1. Der Nachbau clusterte den GEMISCHTEN Rohpool (sorted(ch+cl)); live werden
     ph und pl GETRENNT geclustert. Das ist exakt der Fehler, den der Live-Code
     als behoben dokumentiert ("nicht vorher zusammenlegen!" - Ketten-Mega-
     Cluster in dichten Zonen). Gemessen: 4,5 % aller Zeitpunkte hatten einen
     anderen Widerstand.
  2. Live rundet jede Linie auf 3 Stellen, der Nachbau nicht.
  3. Live fuehrt Widerstand und Unterstuetzung mit getrennter Hysterese.

Geloest ueber core/sr_levels.py (cluster/linien/naechste/halten).
engine._draw_levels ruft es auf, Verhalten bitgenau unveraendert
(tests/test_sr_levels.py, 6 Tests, je 300-400 Zufallsfaelle). Der Test enthaelt
die Gegenprobe, dass der Nachbau wirklich abwich - sonst waere nicht belegt,
dass es etwas zu beheben gab.

backtest_pbreak_retrain.build_levels bleibt unveraendert (Migrations-Regel: die
daraus gefitteten Live-Gewichte duerfen sich nicht rueckwirkend verschieben).
Daneben neu: build_levels_live fuer kuenftige Fits.

ABER die Korrektur schliesst die Basisraten-Luecke nicht: 38,0 -> 38,3 %,
live 52,7 %.

Damit fuenf Hypothesen geprueft und alle widerlegt:
  Niveau/Achsenabschnitt   Roll-Achse faellt live durch
  Vola-Regime              ueber 5 Quintile flach (37,0-39,2 %)
  Sammelart                -1,7 Pp, falsche Richtung
  ATR-Floor-Artefakt       +2,8 Pp; bei live-gleichem ATR erst 40,1 %
  Level-Auswahl            +0,4 Pp

Nebenbefund: 53,9 % aller Backtest-Bars sitzen auf dem ATR-Floor 0,12, live nur
5,4 % - der Backtest-Zeitraum ist deutlich ruhiger. Der ATR selbst stimmt aber
ueberein (Median live 0,2167 gegen letzte 7 Tage 0,2174).

Kein Notfall: auto_sr_close ist false, das Modell schliesst keine Trades.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Axel Hocks
2026-08-07 11:30:07 +02:00
co-authored by Claude Opus 5
parent 2e89724368
commit aefac274d3
5 changed files with 328 additions and 25 deletions
+50 -1
View File
@@ -105,8 +105,57 @@ def build_levels(H, L):
return lv
def build_levels_live(H, L):
"""Wie `build_levels`, aber hält ph und pl GETRENNT — so wie live.
⚠⚠ WARUM ES DIESE ZWEITE FASSUNG GIBT (2026-08-07). `build_levels` wirft
Hochs und Tiefs in EINE sortierte Liste, und `_cluster` läuft die
Nachbar-Kette darüber. Genau das ist im Live-Code als behobener Fehler
dokumentiert („ph/pl bleiben GETRENNT geclustert — nicht vorher
zusammenlegen!"): über den gemischten Pool verdoppelt sich die Punktdichte
und die Kette läuft durch dichte Zonen durch (Ketten-Mega-Cluster).
Gemessen: **4,5 % aller Zeitpunkte** hatten dadurch einen anderen
Widerstand; auf die Basisrate wirkt es mit **+0,4 Pp**.
⚠ Die ALTE `build_levels` bleibt unverändert — die dokumentierten Zahlen
dieses Skripts (und die daraus gefitteten Live-Gewichte `_PB_*`) dürfen sich
nicht rückwirkend verschieben (Migrations-Regel). **Neue Fits gehören auf
diese Fassung**, zusammen mit `core.sr_levels.linien` statt `_cluster`.
"""
nm = len(H) // _M30
mh = [max(H[i * _M30:(i + 1) * _M30]) for i in range(nm)]
ml = [min(L[i * _M30:(i + 1) * _M30]) for i in range(nm)]
bh = [[] for _ in range(nm)]; bl = [[] for _ in range(nm)]
for p in range(_PIV_K, nm - _PIV_K):
if mh[p] == max(mh[p - _PIV_K:p + _PIV_K + 1]) and p + _PIV_K < nm:
bh[p + _PIV_K].append(mh[p])
if ml[p] == min(ml[p - _PIV_K:p + _PIV_K + 1]) and p + _PIV_K < nm:
bl[p + _PIV_K].append(ml[p])
out = []
wh = deque(); wl = deque(); ch = []; cl = []
for m in range(nm):
wh.append(bh[m]); ch.extend(bh[m])
wl.append(bl[m]); cl.extend(bl[m])
while len(wh) > _PIV_LOOKBACK:
for x in wh.popleft():
try: ch.remove(x)
except ValueError: pass
while len(wl) > _PIV_LOOKBACK:
for x in wl.popleft():
try: cl.remove(x)
except ValueError: pass
out.append({"ph": sorted(ch), "pl": sorted(cl)})
return out
def _cluster(levels, atr):
"""Nahe Pivots zu einer Linie zusammenfassen (~0,5×ATR) — wie `_draw_levels`."""
"""Nahe Pivots zu einer Linie zusammenfassen (~0,5×ATR).
⚠ NICHT „wie `_draw_levels`" — das stand hier jahrelang und war falsch: live
wird je Seite geclustert und auf 3 Stellen gerundet, hier über den gemischten
Pool und ohne Rundung. Für neue Arbeit `core.sr_levels.linien` benutzen;
diese Funktion bleibt nur, damit die dokumentierten Zahlen reproduzierbar
sind."""
if not levels:
return []
out = []; grp = [levels[0]]