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:
co-authored by
Claude Opus 5
parent
2e89724368
commit
aefac274d3
@@ -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]]
|
||||
|
||||
Reference in New Issue
Block a user