"""Beweist, dass `core/sr_levels.py` bitgenau das rechnet, was bis 2026-08-07 inline in `engine._draw_levels` stand — und dass der Backtest-Nachbau ABWICH. ⚠ `_draw_levels` ist LIVE-Code: er bestimmt die gezeichneten S/R-Linien, den Dashboard-Wert UND das Level, das der P(break)-Auto-Close bewertet. Eine Refaktorierung dort darf das Verhalten nicht anfassen. """ import random from core.sr_levels import cluster, halten, linien, naechste # ── eingefrorene Fassung, wörtlich aus `engine._draw_levels` (vor dem Umbau) ── def _alt_cluster(vals, atr): 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] def _alt_hold(new, old, bid, atr, 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 if (new is not None and abs(old - bid) > 1.5 * atr and abs(new - bid) < abs(old - bid) - 0.5 * atr): return new return old # ── der ABWEICHENDE Backtest-Nachbau (gemischter Pool, ohne Rundung) ───────── def _nachbau_cluster(levels, atr): if not levels: return [] out = []; grp = [levels[0]] for x in levels[1:]: if x - grp[-1] <= 0.5 * atr: grp.append(x) else: out.append(sum(grp) / len(grp)); grp = [x] out.append(sum(grp) / len(grp)) return out def _pivots(seed, n=40): rnd = random.Random(seed) basis = 75.0 + rnd.uniform(-3, 3) ph = sorted(round(basis + rnd.uniform(0.0, 3.0), 3) for _ in range(n)) pl = sorted(round(basis - rnd.uniform(0.0, 3.0), 3) for _ in range(n)) return ph, pl def test_cluster_bitgenau(): for seed in range(300): ph, pl = _pivots(seed) atr = 0.12 + (seed % 30) * 0.01 assert cluster(ph, atr) == _alt_cluster(ph, atr), f"seed {seed}" assert cluster(pl, atr) == _alt_cluster(pl, atr), f"seed {seed}" def test_linien_und_auswahl_bitgenau(): """Der ganze Live-Pfad: getrennt clustern → zusammenführen → nächste wählen.""" for seed in range(300): ph, pl = _pivots(seed) atr = 0.12 + (seed % 30) * 0.01 px = 75.0 + (seed % 11) * 0.2 alt = _alt_cluster(ph, atr) + _alt_cluster(pl, atr) r_alt = min((p for p in alt if p > px), default=None) s_alt = max((p for p in alt if p < px), default=None) r_neu, s_neu = naechste(linien(ph, pl, atr), px) assert (r_neu, s_neu) == (r_alt, s_alt), f"seed {seed}" def test_hysterese_bitgenau(): rnd = random.Random(7) faelle = 0 for seed in range(400): atr = 0.1 + (seed % 25) * 0.02 px = 75.0 + rnd.uniform(-2, 2) for alt_l in (None, px + rnd.uniform(-3, 3)): neu = px + rnd.uniform(-3, 3) for is_res in (True, False): a = halten(neu, alt_l, px, atr, is_res) b = _alt_hold(neu, alt_l, px, atr, is_res) assert a == b, f"seed {seed}" faelle += 1 assert faelle > 1000 def test_nachbau_wich_wirklich_ab(): """⚠ Gegenprobe: der Backtest-Nachbau (gemischter Pool) liefert NICHT dasselbe. Ohne diesen Test wäre nicht belegt, dass es überhaupt etwas zu beheben gab — und ein Test, der nicht scheitern kann, ist wertlos.""" abweichungen = 0 for seed in range(300): ph, pl = _pivots(seed) atr = 0.12 + (seed % 30) * 0.01 live = sorted(linien(ph, pl, atr)) nachbau = sorted(_nachbau_cluster(sorted(ph + pl), atr)) if [round(x, 3) for x in nachbau] != live: abweichungen += 1 assert abweichungen > 0, "Nachbau und Live sind gleich — dann gab es nichts zu beheben" def test_cluster_leer(): assert cluster([], 0.2) == [] assert linien([], [], 0.2) == [] assert naechste([], 75.0) == (None, None)