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>
90 lines
4.1 KiB
Python
90 lines
4.1 KiB
Python
#!/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()
|