"""Bringt es etwas, dem TAGESTREND mehr Gewicht zu geben? (2026-08-20) ANLASS (User): "hast du Optimierungsvorschlaege, um den Tagestrend mehr Gewicht zu verleihen?" - nach dem Befund, dass die Empfehlung heute um 09:13 LONG sagte und seit 12:58 wegen Ueberdehnung schweigt, waehrend der Tag +2,89 % lief. ⚠ HOHE BEWEISLAST. Signal-Eingriffe sind in diesem Projekt 28-mal gescheitert, darunter dreimal genau diese Klasse (Hoeher-TF-Winkel-Filter, H1-Staerke-Filter, Konfluenz-Strafe - alle gemessen SCHLECHTER). Deshalb wird hier NICHTS gebaut, sondern zuerst gemessen. VIER VARIANTEN, alle auf DERSELBEN Population (voller Live-Gate-Stack: echtes `_build` + Breakout-Bestaetigung k=0,3 + `_room_gate`, Reversal aus): BASIS wie live A VETO Signal GEGEN den Tagestrend verwerfen - die einzige Form, die im Projekt je robust war (Ausrichtungs-Split: gegen das Signal -6,60 EUR/Lot, beidhaelftig) B NUR-MIT nur Signale MIT dem Tagestrend (strenger als A: auch neutrale Tage fallen weg) C SPAET `_STRETCH_MAX` 3,5 -> 5,0, solange der Tagestrend in Signalrichtung zeigt. Das ist EXAKT die heutige Lage. D PULLBACK nur MIT dem Tagestrend UND nur bei kleiner Ueberdehnung (|stretch| <= 1,5) - nicht spaet kaufen, sondern den Ruecksetzer nehmen. Nutzt den im Projekt BELEGTEN Pullback-Effekt ("tiefer Pullback" +15). TAGESTREND = Kurs gegen die TAGESEROEFFNUNG (Berlin-Kalendertag, DST-korrekt). Bewusst so und nicht als D1-EMA: das ist, was ein Mensch sieht, wenn er sagt "der Tagestrend ist aufwaerts" - und es braucht keinen 16.000-Bar-Vorlauf. Schwelle 0,30 % vom Kurs; 0,15 / 0,60 % werden als Nachbarn mitgeprueft. ⚠ KAUSAL: die Eroeffnung des LAUFENDEN Tages ist jederzeit bekannt, es fliesst nichts aus der Zukunft ein. REGEL VORAB FIXIERT: eine Variante wird nur dann zum Bauen empfohlen, wenn sie (1) in BEIDEN Halbjahren einen hoeheren OeR liefert als BASIS, (2) ihr GEPAARTES 95-%-Intervall (gemeinsame Einstiege - die Falle vom 14.08.) die Null ausschliesst, und (3) die Nachbar-Schwellen nicht kippen. Aufruf: python backtest_tagestrend.py [bars] """ import datetime as dt import sys from bisect import bisect_left, bisect_right from zoneinfo import ZoneInfo import MetaTrader5 as mt5 import core.wave_rec as WR from backtest_breakout_gated import (_ATRMIN, _ATR_FLOOR_PB, _COOL, _PIV_K, _PIV_LOOK, _TIMEOUT_BARS, _TU, _atr_series, _htf_sign) from backtest_cost_gate import kennzahlen from core.analysis import calc_trend_angle from core.exit_model import LIVE, simulate from core.wave_rec import (WaveRecommender, _ANGLE_LR, _EMA_FAST, _EMA_SLOW, _ema_series) BERLIN = ZoneInfo("Europe/Berlin") BROKER = dt.timezone(dt.timedelta(hours=3)) K_BREAKOUT = 0.3 PULLBACK_MAX = 1.5 STRETCH_LOCKER = 5.0 MODI = (("basis", "BASIS (wie live)"), ("veto", "A) VETO gegen Tagestrend"), ("nurmit", "B) NUR MIT Tagestrend"), ("spaet", "C) Spaet-Einstieg bis 5,0"), ("pullback", "D) Pullback im Tagestrend")) def _tagesoeffnung(T, C): """Index -> Eroeffnungskurs SEINES Berliner Kalendertags (kausal). ⚠ MT5 liefert in `time` die Broker-WALLCLOCK, keinen echten UTC-Epoch - genau der Fehler, der `analyze_tageszeit.py` am 05.08. um 3 h verschoben hat. """ out = [None] * len(C) tag = op = None for i, ts in enumerate(T): d = dt.datetime.fromtimestamp(ts, dt.timezone.utc) \ .replace(tzinfo=BROKER).astimezone(BERLIN).date() if d != tag: tag, op = d, C[i] out[i] = op return out def _tagestrend(C, OP, i, schwelle): if not OP[i]: return 0 rel = (C[i] - OP[i]) / OP[i] return 0 if abs(rel) < schwelle else (1 if rel > 0 else -1) def run(w, H, L, C, A, SP, EF, ES, AD, HTF, H1, PH, PL, OP, lo, hi, modus, schwelle): Rs, keys = [], [] pend = None i = max(lo, _PIV_LOOK + _EMA_SLOW + 5) while i < hi: atr = A[i] if not atr or atr < _ATRMIN: i += 1 continue w._pb_feats = {"atr": max(atr, _ATR_FLOOR_PB)} a1 = bisect_left(PH, i - _PIV_LOOK) b1 = bisect_right(PH, i - _PIV_K) a2 = bisect_left(PL, i - _PIV_LOOK) b2 = bisect_right(PL, i - _PIV_K) w._pb_levels = {"ph": [C[j] for j in PH[a1:b1]], "pl": [C[j] for j in PL[a2:b2]]} td = _tagestrend(C, OP, i, schwelle) WR._STRETCH_MAX = 3.5 if modus == "spaet" and td: stretch = (C[i] - ES[i]) / atr if (td > 0 and stretch > 0) or (td < 0 and stretch < 0): WR._STRETCH_MAX = STRETCH_LOCKER rec, _ = w._build(EF[i], ES[i], C[i], atr, "M5", 5, htf_trend=HTF[i], h1_trend=H1[i], angle=AD[i]) WR._STRETCH_MAX = 3.5 sig = rec.get("signal") if K_BREAKOUT > 0 and sig in ("LONG", "SHORT"): d = 1 if sig == "LONG" else -1 if pend is None or pend["dir"] != d: pend = {"dir": d, "level": C[i] + d * K_BREAKOUT * atr, "invalid": C[i] - d * K_BREAKOUT * atr, "confirmed": False, "bar": i} if not pend["confirmed"]: broke = (C[i] >= pend["level"]) if d > 0 else (C[i] <= pend["level"]) gegen = (C[i] <= pend["invalid"]) if d > 0 else (C[i] >= pend["invalid"]) if broke: pend["confirmed"] = True else: if gegen or (i - pend["bar"]) > _TIMEOUT_BARS: pend = {"dir": d, "level": C[i] + d * K_BREAKOUT * atr, "invalid": C[i] - d * K_BREAKOUT * atr, "confirmed": False, "bar": i} i += 1 continue elif sig == "WARTEN": pend = None if sig not in ("LONG", "SHORT"): i += 1 continue rec = w._room_gate(rec, C[i]) if rec.get("signal") not in ("LONG", "SHORT"): i += 1 continue d = 1 if rec["signal"] == "LONG" else -1 if modus == "veto" and td and td != d: i += 1 pend = None continue if modus in ("nurmit", "pullback") and td != d: i += 1 pend = None continue if modus == "pullback" and abs((C[i] - ES[i]) / atr) > PULLBACK_MAX: i += 1 pend = None continue a = max(atr, _ATRMIN) r, xj = simulate(C[i], d, a, H, L, C, i + 1, LIVE, timestop=True, use_tp=True, ret_bar=True) Rs.append(r - ((SP[i] if SP[i] > 0 else 0.0225) / a)) keys.append(i) pend = None i = xj + _COOL return Rs, keys def zeig(lbl, k): if not k: print(" {:<28} --".format(lbl)) return ci = k.get("ci") m = "" if ci: m = (" [{:+.3f} .. {:+.3f}]".format(ci[0], ci[1]) + ("" if ci[0] * ci[1] > 0 else " ~0")) print(" {:<28} n={:>4} WR={:>3.0f}% OeR={:+.3f} PF={:.2f}{}" .format(lbl, k["n"], k["wr"], k["oer"], k["pf"], m)) def main(): n = int(sys.argv[1]) if len(sys.argv) > 1 else 60000 schwellen = [0.0030, 0.0015, 0.0060] mt5.initialize() sym = next((c for c in ("SpotCrude", "USOIL", "WTI", "XTIUSD") if mt5.symbol_info(c)), None) b = mt5.copy_rates_from_pos(sym, mt5.TIMEFRAME_M5, 0, n) point = mt5.symbol_info(sym).point mt5.shutdown() T = [int(x["time"]) for x in b] H = [float(x["high"]) for x in b] L = [float(x["low"]) for x in b] C = [float(x["close"]) for x in b] SP = [float(x["spread"]) * point for x in b] A = _atr_series(H, L, C) EF = _ema_series(C, _EMA_FAST) ES = _ema_series(C, _EMA_SLOW) OP = _tagesoeffnung(T, C) AD = [calc_trend_angle(C[max(0, i - _ANGLE_LR):i + 1]) if i >= _ANGLE_LR else 90.0 for i in range(len(C))] HTF = [_htf_sign(C, i, 6, A[i]) for i in range(len(C))] H1 = [_htf_sign(C, i, 12, A[i]) for i in range(len(C))] PH = [j for j in range(_PIV_K, len(C) - _PIV_K) if C[j] == max(C[j - _PIV_K:j + _PIV_K + 1])] PL = [j for j in range(_PIV_K, len(C) - _PIV_K) if C[j] == min(C[j - _PIV_K:j + _PIV_K + 1])] w = WaveRecommender(_TU(), mt5.TIMEFRAME_M5) w.set_reversal_enabled(False) w.set_entry_room(0.6) w.set_breakout_k(0.0) N = len(C) mid = N // 2 ende = N - LIVE.max_hold - 1 tage = len({dt.datetime.fromtimestamp(t, dt.timezone.utc) .replace(tzinfo=BROKER).astimezone(BERLIN).date() for t in T}) print("=" * 104) print(" TAGESTREND MEHR GEWICHT? - {} · {} M5-Bars · {} Handelstage" .format(sym, N, tage)) print(" voller Live-Gate-Stack (echtes _build · Breakout k=0,3 · _room_gate)" " · kanonischer Exit · Echtkosten") print("=" * 104) for schwelle in schwellen: titel = "Tagestrend-Schwelle {:.2f} %".format(schwelle * 100) if schwelle != schwellen[0]: titel += " (Nachbar-Probe)" print("\n " + titel) print(" " + "-" * 100) erg = {} for modus, lbl in MODI: r1, k1 = run(w, H, L, C, A, SP, EF, ES, AD, HTF, H1, PH, PL, OP, 0, mid, modus, schwelle) r2, k2 = run(w, H, L, C, A, SP, EF, ES, AD, HTF, H1, PH, PL, OP, mid, ende, modus, schwelle) erg[modus] = (dict(zip(k1, r1)), dict(zip(k2, r2))) zeig("H1 " + lbl, kennzahlen(r1)) zeig("H2 " + lbl, kennzahlen(r2)) print() # ⚠⚠ KEIN gepaarter Test hier - er waere DEGENERIERT. Die Varianten # aendern nicht den EXIT, sondern WELCHE Einstiege genommen werden; # auf den gemeinsamen Faellen sind die R-Werte per Konstruktion # IDENTISCH (erster Lauf: Delta +0,000, KI [0,000 .. 0,000] in allen # acht Zellen). `paarweise` ist das Werkzeug fuer Exit-Varianten # (Falle vom 14.08.), nicht fuer Auswahl-Varianten. # Entscheidend ist stattdessen, WAS der Eingriff wegnimmt bzw. # hinzufuegt - Muster wie in `backtest_htf_vola.py`. print(" WAS DER EINGRIFF WEGNIMMT / HINZUFUEGT (die eigentliche Frage):") b1, b2 = erg["basis"] for modus, lbl in MODI[1:]: v1, v2 = erg[modus] for hl, bb, vv in (("H1", b1, v1), ("H2", b2, v2)): weg = [bb[k] for k in bb if k not in vv] neu_ = [vv[k] for k in vv if k not in bb] gruppe, txt = (neu_, "hinzugefuegt") if len(neu_) > len(weg) else (weg, "entfernt") kk = kennzahlen(gruppe) if not kk: print(" {:<28} {} {} n={} - zu wenig" .format(lbl, hl, txt, len(gruppe))) continue ci = kk.get("ci"); m = "" if ci: m = (" [{:+.3f} .. {:+.3f}]".format(ci[0], ci[1]) + ("" if ci[0] * ci[1] > 0 else " ~0")) print(" {:<28} {} {:<12} n={:>4} WR={:>3.0f}% OeR={:+.3f}{}" .format(lbl, hl, txt, kk["n"], kk["wr"], kk["oer"], m)) print("\n " + "=" * 100) print(" REGEL: bauen nur, wenn beidhaelftig besser UND das gepaarte") print(" Intervall die Null ausschliesst UND die Nachbarn nicht kippen.") if __name__ == "__main__": main()