Files
AH-Oil-Trader/backtest_breakout_squeeze.py
T
Axel HocksandClaude Opus 5 fd3c7f57a5 Live-Code von Backtest-Skript entkoppelt: core/squeeze_scan.py
core/engine.py (squeeze_monitor) und weekly_review.py importierten
backtest_breakout_squeeze - die Abhaengigkeitsrichtung war verkehrt:
ein Fehler im Backtest konnte den laufenden Bot treffen.

Schwerer wog, WIE die Parameter hineinkamen:

    BQ._N = _SQ_N; BQ._K = _SQ_K

Das mutiert Modul-Globale eines Backtests, wirkt prozessweit und damit auf
jeden anderen Nutzer desselben Moduls im selben Prozess. Die Werte weichen
real ab (Backtest _N=12, live _SQ_N=10).

Geloest ueber core/squeeze_scan.py - Parameter stehen in der Signatur.
backtest_breakout_squeeze.py leitet nur noch weiter, damit es EINE
Implementierung gibt statt zweier (der sim_run/_ema_series-Fehler).

Der Exit ist bewusst NICHT mitrepariert: exit_legacy ist eine bitgenaue
Kopie inkl. seiner Abweichungen vom kanonischen Exit (Trail 1,5 statt 1,0,
kein Lock/Time-Stop/TP, max_hold 288 statt 200). Damit misst der B4-Monitor
gegen einen veralteten Exit - separater, offener Befund. Ihn hier still
mitzuaendern haette die Vergleichsgrundlage verschoben.

Bitgenauigkeit bewiesen (tests/test_squeeze_scan.py): die alte Fassung liegt
eingefroren im Test und laeuft gegen die neue ueber 500 synthetische Reihen
x 3 Multiplikatoren. Ein Vorher/Nachher-Lauf waere ungueltig gewesen - der
Backtest zieht Bars live aus MT5, das Fenster verschiebt sich staendig.

Test selbst per Mutationsprobe geprueft: 5 von 6 eingebauten Fehlern schlagen
an, der sechste (d>0 -> d>=0) ist beweisbar aequivalent, da d nur +-1 ist.

Live-Gegenprobe auf 9000 M5-Bars: n=112, Treffer 44%, OeR +0,178, PF 1,26.

Aktivierung beim naechsten Neustart - bei offener Position kein Neustart fuer
eine verhaltensneutrale Aenderung.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 07:21:45 +02:00

90 lines
4.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""Volatilitäts-Squeeze-Breakout (B2, User-Idee 'Breakouterkennung'):
Struktureller Ausbruch aus einer KOMPRESSION — NICHT aus dem EMA-Trend.
Setup: die Spanne der letzten N Bars (Box) ist eng relativ zum ATR (Squeeze);
danach bricht der Kurs k×ATR über/unter die Box → Einstieg in Ausbruchsrichtung.
Exit = live-Modell (SL 2,0×ATR + Trailing 1,5, Breakeven 1,3), pessimistisch.
Kosten = echter Bar-Spread/ATR (Ø ~0,265). 2 Halbjahre. R = Profit/ATR.
Kernfrage (B3): Trägt der SQUEEZE-Filter netto in BEIDEN Hälften — schlägt er den
Ausbruch aus einer BELIEBIGEN Box (ohne Kompression)? Sonst raus.
"""
import sys
import MetaTrader5 as mt5
_MAXH = 288; _ATRMIN = 0.12
_N = 12 # Box-Länge (Bars) = 1 h auf M5
_W = 24 # Fenster nach der Box, in dem der Breakout erfolgen muss (2 h)
_COOL = 12 # Cooldown nach einem Trade (Bars) gegen Überlappung
_K = 0.1 # Ausbruch k×ATR über/unter die Box-Grenze
# ⚠⚠ DIE MECHANIK LIEGT SEIT 2026-08-07 IN `core/squeeze_scan.py`.
# Grund: `core/engine.py` und `weekly_review.py` haben sie von HIER importiert —
# LIVE-Code, der an einem Backtest-Skript haengt, und die Live-Parameter wurden
# ueber MUTIERTE Modul-Globale (`BQ._N = ...`) eingeschleust. Beides ist behoben.
# Hier bleiben nur duenne Weiterleitungen, damit es EINE Implementierung gibt und
# nicht zwei, die auseinanderdriften — der Fehler, den dieses Projekt bei
# `sim_run`/`_ema_series` (Kopien in >=6 Skripten) schon einmal gemacht hat.
# ⚠ Die Zahlen dieses Backtests bleiben unveraendert: bitgenau geprueft in
# `tests/test_squeeze_scan.py` (500 synthetische Kursreihen).
from core.squeeze_scan import atr_series as _core_atr
from core.squeeze_scan import exit_legacy as _core_exit
from core.squeeze_scan import scan as _core_scan
def _atr_series(H, L, C, p=14):
return _core_atr(H, L, C, p)
def sim(entry, d, atr, H, L, C, j0, sl_atr=2.0, trail=1.5, trail_on=0.3, be_on=1.3):
return _core_exit(entry, d, atr, H, L, C, j0, sl_atr=sl_atr, trail=trail,
trail_on=trail_on, be_on=be_on, max_hold=_MAXH)
def rep(name, Rs):
if not Rs: print(f" {name:<32} -"); return
n = len(Rs); w = sum(1 for x in Rs if x > 0); s = sum(Rs)
up = sum(x for x in Rs if x > 0); dn = -sum(x for x in Rs if x < 0)
pf = up/dn if dn > 0 else 9.99
print(f" {name:<32} Trades={n:>4} Treffer={100*w/n:>3.0f}% "
f"ØR={s/n:+.3f} PF={pf:.2f} ΣR={s:+.0f}")
def scan(H, L, C, A, SP, point, lo_i, hi_i, squeeze_mult):
"""Weiterleitung auf `core.squeeze_scan.scan` mit den Modul-Globalen dieses
Skripts als Parameter (`main()` variiert `_N`/`_K` beim Sweep)."""
return _core_scan(H, L, C, A, SP, point, lo_i, hi_i, squeeze_mult,
box_n=_N, k=_K, atr_min=_ATRMIN, fenster=_W,
cooldown=_COOL, max_hold=_MAXH)
def main():
n = int(sys.argv[1]) if len(sys.argv) > 1 else 80000
mt5.initialize()
sym = None
for c in ("SpotCrude", "USOIL", "WTI", "XTIUSD"):
if mt5.symbol_info(c): sym = c; break
bars = mt5.copy_rates_from_pos(sym, mt5.TIMEFRAME_M5, 0, n+_MAXH+30)
si = mt5.symbol_info(sym); point = si.point
mt5.shutdown()
H = [float(b["high"]) for b in bars]; L = [float(b["low"]) for b in bars]
C = [float(b["close"]) for b in bars]; SP = [float(b["spread"])*point for b in bars]
A = _atr_series(H, L, C)
N = len(C); mid = N//2
print("="*88)
print(f" Volatilitäts-Squeeze-Breakout — {sym} M5 ({N} Bars · Box={_N} · Ausbruch={_K}×ATR)")
print(f" Exit=live(SL2,0/Trail1,5/BE1,3) · Kosten=Bar-Spread/ATR · 2 Halbjahre")
print("="*88)
for label, lo, hi in (("H1 (alt)", 0, mid), ("H2 (neu)", mid, N)):
print(f"\n{label}:")
_, any_R = scan(H, L, C, A, SP, point, lo, hi, 99.0)
rep("Ausbruch OHNE Squeeze-Filter", any_R)
for sm in (2.5, 2.0, 1.5):
sq_R, _ = scan(H, L, C, A, SP, point, lo, hi, sm)
rep(f"Squeeze Box<={sm}×ATR", sq_R)
print(f"\n Squeeze trägt nur, wenn ØR & PF in BEIDEN Hälften > 'ohne Filter' UND ØR netto > 0.")
if __name__ == "__main__":
main()