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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
a35a0e7006
commit
fd3c7f57a5
@@ -19,30 +19,27 @@ _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):
|
||||
t = [0.0]
|
||||
for i in range(1, len(C)):
|
||||
t.append(max(H[i]-L[i], abs(H[i]-C[i-1]), abs(L[i]-C[i-1])))
|
||||
out = [None]
|
||||
for i in range(1, len(C)):
|
||||
seg = t[max(1, i-p+1):i+1]
|
||||
out.append(sum(seg)/len(seg))
|
||||
return out
|
||||
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):
|
||||
eff = entry - d*sl_atr*atr; hw = entry
|
||||
end = min(j0+_MAXH, len(C)-1); exit_px = C[end]
|
||||
for j in range(j0, end+1):
|
||||
hi, lo = H[j], L[j]
|
||||
if (lo <= eff) if d > 0 else (hi >= eff): exit_px = eff; break
|
||||
hw = max(hw, hi) if d > 0 else min(hw, lo)
|
||||
prof = (C[j]-entry)*d
|
||||
if prof >= trail_on*atr:
|
||||
cand = hw - d*trail*atr
|
||||
if prof >= be_on*atr: cand = max(cand, entry) if d > 0 else min(cand, entry)
|
||||
eff = max(eff, cand) if d > 0 else min(eff, cand)
|
||||
return (exit_px-entry)*d/atr
|
||||
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):
|
||||
@@ -55,29 +52,11 @@ def rep(name, Rs):
|
||||
|
||||
|
||||
def scan(H, L, C, A, SP, point, lo_i, hi_i, squeeze_mult):
|
||||
"""Ein Durchlauf: sammelt R (netto) je Breakout. Rs_any = ALLE Box-Ausbrüche,
|
||||
Rs_sq = nur die mit Squeeze (Box<=squeeze_mult×ATR). i springt nach jedem
|
||||
Breakout um _COOL vor (Überlappungsschutz) — für beide Listen identisch."""
|
||||
def cost(i, atr): return (SP[i] if SP[i] > 0 else 0.0225)/atr
|
||||
Rs_sq = []; Rs_any = []
|
||||
i = max(lo_i, _N+15)
|
||||
while i < min(hi_i, len(C)-_MAXH-1):
|
||||
atr = A[i]
|
||||
if not atr or atr < _ATRMIN: i += 1; continue
|
||||
boxHi = max(H[i-_N:i]); boxLo = min(L[i-_N:i]); box = boxHi-boxLo
|
||||
squeezed = box <= squeeze_mult*atr
|
||||
hit = None
|
||||
for j in range(i, min(i+_W, len(C)-_MAXH-1)):
|
||||
up = boxHi + _K*atr; dn = boxLo - _K*atr
|
||||
if H[j] >= up: hit = (j, 1, up); break
|
||||
if L[j] <= dn: hit = (j, -1, dn); break
|
||||
if hit is None: i += 1; continue
|
||||
j, d, lvl = hit
|
||||
R = sim(lvl, d, atr, H, L, C, j+1) - cost(j, atr)
|
||||
Rs_any.append(R)
|
||||
if squeezed: Rs_sq.append(R)
|
||||
i = j + _COOL
|
||||
return Rs_sq, Rs_any
|
||||
"""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():
|
||||
|
||||
Reference in New Issue
Block a user