Vollpruefung aller 87 Skripte + Nachrechnung der verworfenen Modelle
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>
This commit is contained in:
co-authored by
Claude Opus 5
parent
9e6e81dd34
commit
80a5be7fe5
@@ -50,3 +50,8 @@ desktop.ini
|
|||||||
|
|
||||||
# Reminder-Marker (gemeldete Messungen)
|
# Reminder-Marker (gemeldete Messungen)
|
||||||
.reminders/
|
.reminders/
|
||||||
|
|
||||||
|
# Laufprotokolle der Skript-Vollpruefung (2026-08-04, wegwerfbar)
|
||||||
|
run_all.log
|
||||||
|
run_all.err
|
||||||
|
rerun_rejected.*
|
||||||
|
|||||||
@@ -2804,6 +2804,59 @@ Copiloten aktiv verschlechtern. Offener Kandidat: sie MIT der gemessenen Lesart
|
|||||||
mitgeben („langer Docht oben; gemessen läuft der Kurs danach eher GEGEN die Kerze") —
|
mitgeben („langer Docht oben; gemessen läuft der Kurs danach eher GEGEN die Kerze") —
|
||||||
noch nicht gebaut.
|
noch nicht gebaut.
|
||||||
|
|
||||||
|
## Vollprüfung aller Skripte + Nachrechnung der verworfenen Modelle (2026-08-04)
|
||||||
|
|
||||||
|
**Auftrag:** „alle Skripte ausgiebig testen und die verworfenen Modelle erneut
|
||||||
|
gegenrechnen." Werkzeug: `run_all_backtests.py` (probiert die Aufruf-Signaturen
|
||||||
|
adaptiv durch — die sind nicht einheitlich, s. Audit unten).
|
||||||
|
|
||||||
|
**Phase 1 — Lauffähigkeit: ✅ 87 von 87 Skripten laufen fehlerfrei** (8 000 Bars,
|
||||||
|
Timeout 900 s je Skript). Kein Defekt durch die heutigen `wave_rec`-Änderungen.
|
||||||
|
|
||||||
|
**Phase 2a — `breakout_k`: die dokumentierte Begründung REPRODUZIERT NICHT.**
|
||||||
|
Offener Punkt aus dem Stufe-3-Refactor: 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 | H1 ØR / ΣR | H2 ØR / ΣR |
|
||||||
|
|---|---|---|
|
||||||
|
| 0,0 | −0,180 / −569 | −0,054 / −250 |
|
||||||
|
| **0,3 (LIVE)** | **−0,170 / −356** | **−0,069 / −217** |
|
||||||
|
| 0,5 | −0,142 / −262 | −0,074 / −208 |
|
||||||
|
| 0,8 | −0,112 / −166 | −0,032 / −73 |
|
||||||
|
| 1,0 | −0,101 / −130 | −0,004 / −8 |
|
||||||
|
**k=0,5 · 0,8 · 1,0 schlagen den Live-Wert 0,3 in BEIDEN Hälften** (ΣR-Differenz
|
||||||
|
bis +226/+209). Die Aussage „0,3 ist in beiden Hälften das Beste" gilt mit dem
|
||||||
|
Live-Exit **nicht mehr**.
|
||||||
|
⚠ **Trotzdem KEINE Änderung empfohlen**, aus drei Gründen: (a) **alle** Werte sind
|
||||||
|
negativ (PF 0,70–0,99) — es ist eine Wahl zwischen Verlusten, kein Edge;
|
||||||
|
(b) das Optimum lag am Rand des Suchbereichs → Gegenprobe bis k=3,0 zeigt **kein
|
||||||
|
sauberes Optimum** (H2: −8 → −17 → −20 → −19, H1 schwankt) — es ist also weder
|
||||||
|
reine Handelsvermeidung noch ein echter Bestwert; (c) gemessen wurde das
|
||||||
|
**UNGEGATETE** Signal (ohne min_conf, HTF-Filter, Entry-Raum), live laufen die
|
||||||
|
davor. **Nächster Schritt wäre eine GEGATETE Neumessung**, bevor ein Live-Gate
|
||||||
|
angefasst wird, das 63 % der WARTEN-Zustände erzeugt.
|
||||||
|
|
||||||
|
**Phase 2b — verworfene Modelle bei vollen Bars nachgefahren: ALLE bleiben verworfen.**
|
||||||
|
14 Klassen, keine einzige kippt ins Positive:
|
||||||
|
| Klasse | Ergebnis |
|
||||||
|
|---|---|
|
||||||
|
| ORB (EU/US, 15/30 min) | H1 −0,018…−0,177 · H2 +0,013…+0,280 → Regime-Kippen |
|
||||||
|
| Doppeltop (3 Toleranzen) | 3/3 fallen durch |
|
||||||
|
| Volume Profile (POC/HVN/LVN) | 3/3 fallen durch |
|
||||||
|
| Liquidity Sweeps (SMC) | alle Varianten fallen durch |
|
||||||
|
| Konsolidierung (False Break) | alle Varianten fallen durch |
|
||||||
|
| Handbuch (PDH/PDL, Asian Range, Discount/Premium) | alle fallen durch |
|
||||||
|
| Marktstruktur | 5/5 fallen durch |
|
||||||
|
| Momentum · Inter-Market · Chop-Gate · Reversal-Lockout · S/R-Close-Signal · MACD/ADX/RSI · HTF-Winkel | unverändert verworfen |
|
||||||
|
⚠ **Bemerkenswert:** Bei ~14 Wiederholungstests wäre bei 5 % Fehlalarmquote rund
|
||||||
|
**ein** falsch-positives Ergebnis zu erwarten gewesen — es gab **keins**. Das
|
||||||
|
stützt die Ablehnungen zusätzlich. Die Regel wurde VOR dem Lauf fixiert (ØR>0 und
|
||||||
|
PF>1 in BEIDEN Hälften, Nachbarparameter müssen mithalten).
|
||||||
|
⚠ Das ist ausdrücklich **kein Freibrief für regelmäßiges Nachrechnen** — die
|
||||||
|
Begründung gegen wöchentliche Läufe (Mehrfachvergleich, ~47 Fehlalarme/Jahr)
|
||||||
|
steht unverändert. Anlass war hier die *einmalige* Signaländerung.
|
||||||
|
|
||||||
## Backtest-Audit 2026-08-04 — rechnen die Skripte noch korrekt?
|
## Backtest-Audit 2026-08-04 — rechnen die Skripte noch korrekt?
|
||||||
|
|
||||||
**Anlass:** User-Frage nach dem `angle=`-Fund in `backtest_auto_signal.py`. Prüfung
|
**Anlass:** User-Frage nach dem `angle=`-Fund in `backtest_auto_signal.py`. Prüfung
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""`breakout_k` mit dem KANONISCHEN Exit nachgerechnet (2026-08-04).
|
||||||
|
|
||||||
|
Offener Punkt aus CLAUDE.md: „**`breakout_k=0,3` ruht weiter auf dem
|
||||||
|
`LEGACY_BE10`-Exit** (Breakeven 1,0 statt live 1,3). Der Legacy-Recheck hat EIA
|
||||||
|
und Dead-Hours nachgerechnet, `breakout_k` nicht. Kandidat für den nächsten
|
||||||
|
Recheck." — Das wiegt schwer, weil es ein AKTIVES Gate ist: die erste
|
||||||
|
`block_reason`-Auswertung zeigte `breakout_pending` als Ursache von **63 %**
|
||||||
|
aller WARTEN-Zustände.
|
||||||
|
|
||||||
|
Aufbau: echte `_confirm_breakout`-Mechanik (stateful `_pend`, Neuverankerung bei
|
||||||
|
Richtungswechsel/Timeout) auf dem EMA12/50-Signal, Entry erst nach Bestätigung.
|
||||||
|
Exit = `core/exit_model.LIVE` (SL 2,0 · Trail 1,0 · BE 1,3 · Time-Stop · TP),
|
||||||
|
Echtkosten = Bar-Spread/ATR, sequentielle 1-Positions-Sim, 2 Halbjahre.
|
||||||
|
|
||||||
|
⚠ VORAB festgelegte Regel (gegen Mehrfachvergleich): ein anderer k-Wert wird nur
|
||||||
|
dann empfohlen, wenn er in BEIDEN Hälften mehr ΣR liefert als der Live-Wert 0,3
|
||||||
|
UND seine Nachbarwerte mithalten. Ein einzelner Ausreißer zählt nicht.
|
||||||
|
|
||||||
|
Aufruf: python backtest_breakout_canon.py [bars]
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import MetaTrader5 as mt5
|
||||||
|
|
||||||
|
from core.exit_model import LIVE, simulate
|
||||||
|
from core.wave_rec import _EMA_FAST, _EMA_SLOW, _TREND_DEADBAND, _ema_series
|
||||||
|
|
||||||
|
_ATRMIN = 0.06
|
||||||
|
_COOL = 3
|
||||||
|
_PEND_TIMEOUT = 24 # Bars, bis eine unbestätigte Anwartschaft verfällt
|
||||||
|
|
||||||
|
|
||||||
|
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])))
|
||||||
|
return [(sum(t[max(1, i - p + 1):i + 1]) / max(1, len(t[max(1, i - p + 1):i + 1])))
|
||||||
|
if i else None for i in range(len(C))]
|
||||||
|
|
||||||
|
|
||||||
|
def st(Rs):
|
||||||
|
if not Rs:
|
||||||
|
return None
|
||||||
|
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)
|
||||||
|
return dict(n=n, wr=100 * w / n, oR=s / n, pf=(up / dn if dn > 0 else 9.99), sum=s)
|
||||||
|
|
||||||
|
|
||||||
|
def run(H, L, C, A, SP, EF, ES, lo, hi, k):
|
||||||
|
"""k = geforderte Bewegung in ATR, bevor das Signal gehandelt wird. k=0 → aus."""
|
||||||
|
def cost(i, atr):
|
||||||
|
return (SP[i] if SP[i] > 0 else 0.0225) / atr
|
||||||
|
|
||||||
|
Rs = []
|
||||||
|
pend = None # {"dir": ±1, "level": float, "bar": int}
|
||||||
|
i = max(lo, _EMA_SLOW + 5)
|
||||||
|
while i < hi:
|
||||||
|
atr = A[i]
|
||||||
|
if not atr or atr < _ATRMIN:
|
||||||
|
i += 1; continue
|
||||||
|
sep = (EF[i] - ES[i]) / atr
|
||||||
|
d = 0 if abs(sep) < _TREND_DEADBAND else (1 if sep > 0 else -1)
|
||||||
|
if d == 0:
|
||||||
|
pend = None; i += 1; continue
|
||||||
|
|
||||||
|
if k <= 0: # Bestätigung aus → sofort handeln
|
||||||
|
trigger = True
|
||||||
|
else:
|
||||||
|
if pend is None or pend["dir"] != d:
|
||||||
|
pend = {"dir": d, "level": C[i], "bar": i}
|
||||||
|
i += 1; continue
|
||||||
|
if i - pend["bar"] > _PEND_TIMEOUT: # Timeout → neu verankern
|
||||||
|
pend = {"dir": d, "level": C[i], "bar": i}
|
||||||
|
i += 1; continue
|
||||||
|
fortschritt = (C[i] - pend["level"]) * d / atr
|
||||||
|
if fortschritt <= -k: # gegen die Richtung → neu
|
||||||
|
pend = {"dir": d, "level": C[i], "bar": i}
|
||||||
|
i += 1; continue
|
||||||
|
trigger = fortschritt >= k
|
||||||
|
if not trigger:
|
||||||
|
i += 1; continue
|
||||||
|
|
||||||
|
r, xj = simulate(C[i], d, max(atr, _ATRMIN), H, L, C, i + 1, LIVE,
|
||||||
|
timestop=True, use_tp=True, ret_bar=True)
|
||||||
|
Rs.append(r - cost(i, max(atr, _ATRMIN)))
|
||||||
|
pend = None
|
||||||
|
i = xj + _COOL
|
||||||
|
return Rs
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
n = int(sys.argv[1]) if len(sys.argv) > 1 else 80000
|
||||||
|
mt5.initialize()
|
||||||
|
sym = next((c for c in ("SpotCrude", "USOIL", "WTI", "XTIUSD")
|
||||||
|
if mt5.symbol_info(c)), None)
|
||||||
|
bars = mt5.copy_rates_from_pos(sym, mt5.TIMEFRAME_M5, 0, n)
|
||||||
|
point = mt5.symbol_info(sym).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)
|
||||||
|
EF = _ema_series(C, _EMA_FAST); ES = _ema_series(C, _EMA_SLOW)
|
||||||
|
N = len(C); mid = N // 2
|
||||||
|
|
||||||
|
print("=" * 92)
|
||||||
|
print(f" breakout_k — KANONISCHER Exit — {sym} M5 ({N} Bars, 2 Halbjahre, Echtkosten)")
|
||||||
|
print(f" Exit: SL {LIVE.sl_atr}× · Trail {LIVE.mult}× · BE {LIVE.be}× · Time-Stop · TP")
|
||||||
|
print(f" ⚠ Die Validierung von k=0,3 stammt aus `backtest_breakout.py` mit BE 1,0")
|
||||||
|
print(f" (LEGACY_BE10) — hier erstmals mit dem Live-Exit gegengerechnet.")
|
||||||
|
print("=" * 92)
|
||||||
|
erg = {}
|
||||||
|
for k in (0.0, 0.2, 0.3, 0.5, 0.8, 1.0):
|
||||||
|
s1 = st(run(H, L, C, A, SP, EF, ES, 0, mid, k))
|
||||||
|
s2 = st(run(H, L, C, A, SP, EF, ES, mid, N - LIVE.max_hold - 1, k))
|
||||||
|
erg[k] = (s1, s2)
|
||||||
|
tag = " ← LIVE" if abs(k - 0.3) < 1e-9 else ""
|
||||||
|
print(f"\n k = {k:.1f}{tag}")
|
||||||
|
for lbl, s in (("H1 (alt)", s1), ("H2 (neu)", s2)):
|
||||||
|
if s:
|
||||||
|
print(f" {lbl} n={s['n']:>5} WR={s['wr']:>3.0f}% ØR={s['oR']:+.3f} "
|
||||||
|
f"PF={s['pf']:>4.2f} ΣR={s['sum']:>+7.0f}")
|
||||||
|
else:
|
||||||
|
print(f" {lbl} — keine Trades")
|
||||||
|
live = erg.get(0.3)
|
||||||
|
print("\n" + "=" * 92)
|
||||||
|
print(" Vergleich gegen den Live-Wert k=0,3 (ΣR-Differenz je Hälfte):")
|
||||||
|
for k, (s1, s2) in sorted(erg.items()):
|
||||||
|
if abs(k - 0.3) < 1e-9 or not (s1 and s2 and live[0] and live[1]):
|
||||||
|
continue
|
||||||
|
print(f" k={k:.1f}: H1 {s1['sum'] - live[0]['sum']:+7.0f} "
|
||||||
|
f"H2 {s2['sum'] - live[1]['sum']:+7.0f}"
|
||||||
|
+ (" << in BEIDEN besser" if (s1['sum'] > live[0]['sum']
|
||||||
|
and s2['sum'] > live[1]['sum']) else ""))
|
||||||
|
print("\n Regel (vorab): Wechsel nur, wenn ein k in BEIDEN Hälften besser ist")
|
||||||
|
print(" UND seine Nachbarn mithalten. Ein einzelner Ausreißer zählt nicht.")
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
#!/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()
|
||||||
Reference in New Issue
Block a user