Phase 1 - Lauffaehigkeit: 87 von 87 Skripten laufen fehlerfrei (8000 Bars, Timeout 900 s). Kein Defekt durch die heutigen wave_rec-Aenderungen. Werkzeug: run_all_backtests.py, probiert die uneinheitlichen Aufruf-Signaturen adaptiv durch (Bar-Zahl / TF+Bars / zwei TFs+Bars). Phase 2a - breakout_k: die dokumentierte Begruendung REPRODUZIERT NICHT. k=0,3 war mit dem LEGACY_BE10-Exit (Breakeven 1,0) validiert. Erstmals mit dem kanonischen Exit nachgerechnet (backtest_breakout_canon.py, echte _confirm_breakout-Mechanik, 80k M5, 2 Halbjahre, Echtkosten): k=0,0 H1 -0,180/-569 H2 -0,054/-250 k=0,3 H1 -0,170/-356 H2 -0,069/-217 (LIVE) k=0,5 H1 -0,142/-262 H2 -0,074/-208 k=0,8 H1 -0,112/-166 H2 -0,032/ -73 k=1,0 H1 -0,101/-130 H2 -0,004/ -8 k=0,5/0,8/1,0 schlagen den Live-Wert in BEIDEN Haelften (bis +226/+209 SR). TROTZDEM keine Aenderung empfohlen: (a) alle Werte negativ (PF 0,70-0,99), es ist eine Wahl zwischen Verlusten; (b) Optimum lag am Rand -> Gegenprobe bis k=3,0 zeigt kein sauberes Optimum (H2: -8 -> -17 -> -20 -> -19); (c) gemessen wurde das UNGEGATETE Signal, live laufen min_conf/HTF/Entry-Raum davor. Naechster Schritt waere eine gegatete Neumessung, bevor ein Gate angefasst wird, das 63 % der WARTEN erzeugt. Phase 2b - verworfene Modelle bei vollen Bars: ALLE bleiben verworfen. 14 Klassen (ORB, Doppeltop, Volume Profile, Liquidity Sweeps, Konsolidierung, Handbuch-Level, Marktstruktur, Momentum, Inter-Market, Chop-Gate, Reversal-Lockout, S/R-Close-Signal, MACD/ADX/RSI, HTF-Winkel) - keine kippt ins Positive. Bei ~14 Tests waere bei 5 % Fehlalarmquote ein falsch-positives Ergebnis zu erwarten gewesen; es gab keins. Regel vorab fixiert. Kein Freibrief fuer regelmaessiges Nachrechnen - die Begruendung gegen woechentliche Laeufe steht unveraendert. Anlass war die einmalige Signalaenderung. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
104 lines
3.7 KiB
Python
104 lines
3.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Führt ALLE Backtest-/Analyse-Skripte aus und protokolliert das Ergebnis.
|
|
|
|
Zweck (User-Auftrag 2026-08-04): nach den Änderungen am Signal (Reversal aus,
|
|
`angle=`-Befund) prüfen, ob die Skripte noch laufen UND ob ihre Aussagen halten.
|
|
|
|
⚠ Die Aufruf-Signaturen sind NICHT einheitlich. Die meisten nehmen eine Bar-Zahl
|
|
als erstes Argument, einige aber einen Timeframe (`backtest_signal.py M5 3000`,
|
|
`backtest_htf_filter.py M5 M30 3000`). Ein Aufruf mit der falschen Signatur
|
|
quittiert mit `KeyError`/`ValueError` — das ist KEIN Defekt und darf nicht als
|
|
solcher gezählt werden. Deshalb probiert der Runner adaptiv mehrere Varianten
|
|
und meldet erst dann einen Fehler, wenn KEINE davon durchläuft.
|
|
|
|
Aufruf: python run_all_backtests.py [bars] [--only muster]
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import pathlib
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
|
|
PY = sys.executable
|
|
HERE = pathlib.Path(__file__).parent
|
|
|
|
# Skripte, die kein eigenständiger Lauf sind (Bibliothek/Helfer) oder extrem lange
|
|
# laufen und separat gefahren werden.
|
|
SKIP = {
|
|
"run_all_backtests.py",
|
|
}
|
|
|
|
|
|
def varianten(n: int) -> list[list[str]]:
|
|
"""Aufruf-Varianten in der Reihenfolge, in der sie probiert werden."""
|
|
return [
|
|
[str(n)], # Standard: Bar-Zahl
|
|
["M5", str(n)], # TF + Bar-Zahl
|
|
["M5", "M30", str(n)], # zwei TFs + Bar-Zahl
|
|
[], # ohne Argumente (Defaults)
|
|
]
|
|
|
|
|
|
def main() -> None:
|
|
n = 6000
|
|
only = None
|
|
args = sys.argv[1:]
|
|
if args and args[0].isdigit():
|
|
n = int(args[0]); args = args[1:]
|
|
if len(args) >= 2 and args[0] == "--only":
|
|
only = args[1]
|
|
|
|
files = sorted(set(list(HERE.glob("backtest_*.py")) + list(HERE.glob("analyze_*.py"))))
|
|
files = [f for f in files if f.name not in SKIP]
|
|
if only:
|
|
files = [f for f in files if only in f.name]
|
|
|
|
print(f"{len(files)} Skripte · {n} Bars · Start {time.strftime('%H:%M:%S')}")
|
|
print("=" * 96)
|
|
ok, fail, slow = [], [], []
|
|
for i, f in enumerate(files, 1):
|
|
t0 = time.time()
|
|
erfolg = None
|
|
letzter_fehler = ""
|
|
for v in varianten(n):
|
|
try:
|
|
r = subprocess.run([PY, "-X", "utf8", f.name] + v, cwd=HERE,
|
|
capture_output=True, text=True, timeout=900,
|
|
encoding="utf-8", errors="replace")
|
|
except subprocess.TimeoutExpired:
|
|
letzter_fehler = "TIMEOUT (>900 s)"
|
|
continue
|
|
if r.returncode == 0:
|
|
erfolg = (v, r.stdout or "")
|
|
break
|
|
tail = [x for x in (r.stderr or "").splitlines() if x.strip()]
|
|
letzter_fehler = tail[-1][:110] if tail else f"exit {r.returncode}"
|
|
dt = time.time() - t0
|
|
if erfolg:
|
|
v, out = erfolg
|
|
zeilen = [x for x in out.splitlines() if x.strip()]
|
|
sig = " ".join(v) or "(ohne)"
|
|
print(f"[{i:>2}/{len(files)}] OK {f.name:<36} {dt:>6.1f}s args={sig}")
|
|
ok.append((f.name, sig, dt, zeilen[-1][:100] if zeilen else ""))
|
|
if dt > 120:
|
|
slow.append((f.name, dt))
|
|
else:
|
|
print(f"[{i:>2}/{len(files)}] FEHLER {f.name:<36} {dt:>6.1f}s {letzter_fehler}")
|
|
fail.append((f.name, letzter_fehler))
|
|
|
|
print("=" * 96)
|
|
print(f"OK: {len(ok)} FEHLER: {len(fail)} (Ende {time.strftime('%H:%M:%S')})")
|
|
if fail:
|
|
print("\nFEHLGESCHLAGEN:")
|
|
for name, err in fail:
|
|
print(f" {name:<36} {err}")
|
|
if slow:
|
|
print("\nLangsam (>120 s):")
|
|
for name, dt in sorted(slow, key=lambda x: -x[1]):
|
|
print(f" {name:<36} {dt:.0f}s")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|