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
+13
-24
@@ -2483,16 +2483,17 @@ class TradingEngine:
|
||||
bid = (self.data.snapshot() or {}).get("bid")
|
||||
if not bid:
|
||||
return {"res": [], "sup": []}
|
||||
# Cluster-Toleranz 0,5×ATR: nahe Pivot-Zwillinge (z. B. 80,50 + 80,59)
|
||||
# verschmelzen zu EINER Linie.
|
||||
# ⚠ Clusterung/Auswahl/Hysterese liegen seit 2026-08-07 in
|
||||
# `core/sr_levels.py` — die Backtests bauten sie vorher NACH und
|
||||
# wichen dabei ab (gemischter Rohpool statt ph/pl getrennt; 4,5 %
|
||||
# der Zeitpunkte hatten einen anderen Widerstand). Verhalten hier
|
||||
# unverändert, bitgenau geprüft in `tests/test_sr_levels.py`.
|
||||
from core.sr_levels import cluster as _cluster_lv
|
||||
from core.sr_levels import halten as _halten_lv
|
||||
from core.sr_levels import naechste as _naechste_lv
|
||||
|
||||
def cluster(vals):
|
||||
out = []
|
||||
for v in sorted(vals):
|
||||
if out and v - out[-1][-1] <= 0.5 * atr:
|
||||
out[-1].append(v)
|
||||
else:
|
||||
out.append([v])
|
||||
return [round(sum(g) / len(g), 3) for g in out]
|
||||
return _cluster_lv(vals, atr)
|
||||
# Fix 2026-07-23 (User-Frage „Widerstand/Support-Retest von der falschen
|
||||
# Seite"): Widerstand/Support werden NACH AKTUELLER KURSPOSITION gewählt
|
||||
# (über/unter bid), NICHT mehr strikt nach Entstehungstyp (Swing-Hoch `ph`
|
||||
@@ -2512,27 +2513,15 @@ class TradingEngine:
|
||||
pl_c = cluster(lv.get("pl") or [])
|
||||
both = ph_c + pl_c
|
||||
# NUR die nächste je Seite (2 Linien total = wie im Dashboard)
|
||||
r = min((p for p in both if p > bid), default=None)
|
||||
s = max((p for p in both if p < bid), default=None)
|
||||
r, s = _naechste_lv(both, bid)
|
||||
# Hysterese „klebrig" (Fix 2026-07-21): die gezeichnete Linie bleibt
|
||||
# stehen, solange der Kurs sie nicht WIRKLICH durchbricht (bid > R+0,1×ATR
|
||||
# bzw. bid < S−0,1×ATR). Ein neuer, näherer Pivot löst KEINEN Wechsel aus
|
||||
# — genau das war die Zitter-Quelle (auf M5 bilden sich laufend neue Pivots).
|
||||
# Ausnahme: ist die alte Linie weit weg (>1,5×ATR) UND ein deutlich näheres
|
||||
# Level existiert, wird sie übernommen (sonst bleibt sie irrelevant hängen).
|
||||
def hold(new, old, is_res):
|
||||
if old is None:
|
||||
return new
|
||||
m = 0.10 * atr
|
||||
broken = (bid > old + m) if is_res else (bid < old - m)
|
||||
if broken:
|
||||
return new # echter Durchbruch → nächste Linie (kann None sein)
|
||||
if (new is not None and abs(old - bid) > 1.5 * atr
|
||||
and abs(new - bid) < abs(old - bid) - 0.5 * atr):
|
||||
return new # alte Linie veraltet/weit weg → näheres Level
|
||||
return old # sonst: Linie HALTEN (kein Springen)
|
||||
r = hold(r, self._draw_last.get("r"), True)
|
||||
s = hold(s, self._draw_last.get("s"), False)
|
||||
r = _halten_lv(r, self._draw_last.get("r"), bid, atr, True)
|
||||
s = _halten_lv(s, self._draw_last.get("s"), bid, atr, False)
|
||||
self._draw_last = {"r": r, "s": s}
|
||||
out = {"res": [r] if r is not None else [],
|
||||
"sup": [s] if s is not None else []}
|
||||
|
||||
Reference in New Issue
Block a user