Files
AH-Oil-Trader/CLAUDE.md
T
Axel HocksandClaude Opus 5 b8705e297f Kegel mit Adaptive Conformal Inference nachkalibriert (v=158)
Der Kegel war seit dem 31.07. dokumentiert 3-5 Pp ZU ENG - 80 % nominal gegen
real 76,7 / 76,6 / 75,3 % - und blieb es, weil Nachfitten das Problem nur
verschiebt. ACI justiert stattdessen ONLINE nach jedem aufgeloesten Fenster und
konvergiert nachweislich gegen die Zielabdeckung, auch unter Drift.

EHRLICH ZUR BAUART: die Lehrbuch-Form justiert alpha und schlaegt das Quantil
nach. Hier liegt nur eine feste Quantil-Tabelle vor, kein Verteilungsobjekt -
umgesetzt ist die multiplikative Variante:
    s <- s * exp(gamma * (1{verfehlt} - alpha_ziel)),  gamma 0,02, Deckel 0,7-2,0
Dieselbe Regelungsidee, aber die Konvergenz-GARANTIE der Originalform ist damit
nicht woertlich uebertragen.

Verifiziert: bei exakt 20 % Verfehlern bleibt die Skala bei 1,0000 - der
Fixpunkt stimmt. Simulation aus der dokumentierten Lage (76,6 %): nach ~50
Checks um 80 %. Die Skala pendelt um das Ziel statt exakt zu treffen - das ist
das bekannte ACI-Verhalten (garantiert ist die Langfrist-Abdeckung).

NICHT-UEBERLAPPEND geloggt (neue Tabelle cone_checks): ein 120-min-Fenster alle
5 min waere 24-fach ueberlappend - genau die Cluster-Verzerrung, an der
analyze_hl_funding.py aufgelaufen ist und die die Faelligkeitsbedingung
pbreak_accuracy_v2 um das Zehnfache danebenliegen liess. Je Horizont startet ein
neuer Check erst, wenn der vorige ablief -> 48/24/12 Checks je Tag.

Neustart-fest ueber runtime_state.json. build() ohne skalen liefert bitgenau die
alten Werte (additiv geprueft).

ZWEI EIGENE PLATZIERUNGSFEHLER, beide gefangen: der Lade-Block landete zuerst
HINTER den except-Klauseln von _load_runtime_state, wo st nicht mehr existiert;
und beim M15-Setup landete ein Block mitten in einem mehrzeiligen Aufruf. Der
erste waere stillschweigend nie gelaufen - gefunden nur, weil ich die Funktion
danach gelesen habe statt dem gruenen py_compile zu trauen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 23:45:26 +02:00

434 KiB
Raw Blame History

CLAUDE.md — Oil Trading Bot (MT5, WTI)

Projektleitfaden für Claude Code. Antworten/UI-Texte auf Deutsch.

🔄 Pflege-Regel: Diese Datei nach jeder inhaltlichen Änderung mitaktualisieren (geänderte Logik/Werte, neue Module/Endpoints, UI-/Alarm- Verhalten, Config-Schalter, Workflows, Asset-Version v=N). Kein separater Auftrag nötig — Teil jeder Aufgabe.

🧱 5-Layer-Prozess bei jeder Änderung (Daueregel, in dieser Reihenfolge): L1 Kontext, Befunde & Risiko/Auswirkung (offene Befunde hier · „erst messen" bei Strategie · was kostet/ändert es — EUR-Risiko/Trade, reversibel?) · L2 Code-Analyse & Design (Datenquelle, Quelle der Wahrheit, wo lebt die Logik) · L3 UI-Design (Module klar in Text und Grafik, auf dem geklärten Datenmodell) · L4 Implementieren, Verifizieren & Rollout (py_compile · JS-Klammern · Backtest/Test · Version-Bump v=N · Aktivierungsweg restart_server.bat · Backup vor destruktiven Schritten, v. a. DB) · L5 Doku aktualisieren (docs/ und CLAUDE.md, zuletzt).

Layer an die Größe der Änderung skalieren — nicht jede Aufgabe braucht alle 5. Strategie-Änderung = alle 5 (inkl. Backtest); reine Anzeige = L2L5 light (kein Backtest); Doku-Korrektur = nur L5. Prozess soll helfen, nicht zur Bürokratie werden.

🔬 Track B — Strategie-Lebenszyklus (ZUSÄTZLICH zu L1L5, für ALLES, was den Edge berührt: Signal, Risk/Sizing, Exit, Gate, Filter). Track A = L1L5 (Code/UI/Infra) bleibt unverändert; Track B läuft parallel für Strategie-Ideen: B1 Hypothese + Hebel-Check — welcher Hebel? Signal = klein (6× belegt: Filter tragen kaum → höhere Beweislast, im Zweifel gar nicht bauen) vs. Sizing/Exit = groß (dort liegt das Geld). · B2 2-Stichproben-Backtest — IMMER ≥2 Zeiträume/ Regime, nie eine (Dead-Hour-Lektion: Std 13 war in einer Stichprobe „profitabel", in der zweiten Rauschen; backtest_hourly_split.py). · B3 Kosten-Check — Ø-Edge netto > ~0,1×ATR (real Spread+Komm.)? sonst raus. · B4 Live-klein + Monitor — ausrollen, dann Live-WR/PF/Verhältnis (Statistik-live-Kachel stats_compact + Flip-Zähler) gegen die Backtest-Erwartung halten; Kadenz wöchentlich (Termin, keine Stufe). · B5 Behalten oder Verwerfen — driftet es live weg (Regimewechsel) → zurückbauen, kein Ego. B4/B5 = die Verfall-Erkennung, die im reinen „Änderung machen"-Prozess fehlt (Trading ≠ Software: Edge kann verfallen).

🧾 B0 — Handels-Checkliste (Mensch, vor jedem manuellen Trade): (1) nicht gegen das Signal (gemessen WR 14 %); (2) Notfall-Stop gesetzt?; (3) Gewinner läuft das Trailing (nicht früh kappen — Ø-Verlust ≫ Ø-Gewinn ist die Kern-Leckage). Steuert die eigentliche Verlustquelle (diskretionäre Abweichungen), die kein Code-Layer erreicht.

Was das ist

Algorithmischer WTI-Öl-Trading-Bot auf MetaTrader 5. (Brent-Umstellung am 2026-07-09 auf User-Wunsch noch am selben Tag zurückgenommen — falls je wieder Brent: Pepperstone-Symbol heißt SpotBrent, kein „UKOIL"; breakout_k=0,3 wurde dort bereits nachvalidiert, ØR +0,305.) Eine Oberfläche:

  • server.py + core/engine.py — headless FastAPI-Web-Backend für die Mobile-PWA in web/ (Zugang vom Handy via WireGuard, 192.168.188.100:8000).

Das frühere Tkinter-Widget (oil_trading_agent.py, ui/, single_instance.py) wurde entfernt — nur noch der Server. ⚠ Immer nur EINE Server-Instanz gegen dasselbe MT5-Terminal laufen lassen (zwei TrailingManager = Konflikt).

Architektur (core/)

  • mt5data.py Preis/Trend/Konto · trader.py Orders+SL/TP · trailing.py Trailing/Teil-Exit · wave_rec.py Empfehlung · elliott.py EW/FVG · tu_rating.py TradersUnion · agent.py KI-Copilot (LLM) · daily_levels.py Tages-Prognose (Web) · gaps.py D1-Kurslücken · mailer.py Graph-E-Mail · structure.py Marktstruktur (M30: HH/HL/LH/LL, BOS, Regressionskanal — reine Anzeige, kein Signal) · candle_logger.py M1-Candle-Log (Daten-Sammlung → candles_m1) · market_hours.py Sessions · history.py SQLite · config.py Konstanten+ oil_widget_config.ini-Loader · mt5_utils.py globaler mt5_lock.
  • engine.py = headless Motor: instanziiert die core-Objekte, fährt Loops als Threads, liefert snapshot() für /api/snapshot + WebSocket /ws. _verdict() aggregiert die Module zur Gesamtempfehlung (verdict im Snapshot) — Doku: docs/gesamtempfehlung.md.

Harte Fakten / Stolpersteine

  • MT5 ist Windows-only (MetaTrader5-Wheel). Backend läuft auf Windows neben dem Terminal; nur Reverse-Proxy/HTTPS könnte auf Linux/Docker.
  • Brokerzeit = UTC+3. deal.time/tick.time immer via _broker_offset_s() bzw. Live-Offset in UTC/Lokalzeit umrechnen.
  • mt5_lock (global, nicht reentrant): alle MT5-Calls darüber, mit Timeout. Nicht aus einem bereits-gehaltenen Lock erneut close() (Deadlock). ⚠ Auch Thread-Pool-Jobs müssen den Lock nehmen: M15Analyzer.analyze() läuft im Executor → eigener mt5_lock-Wrapper in mt5data._fetch_trend_locked (sonst Race). sr.detect() läuft inline unter dem bereits gehaltenen Lock. Ebenso: mt5data.reconnect() (shutdown/initialize) und trader._log_open-delayed_log (positions_get im Thread) laufen jetzt unter mt5_lock (waren Races).
  • ⚠⚠ STATISTIK-PRÜFUNG 2026-08-05 (User-Auftrag): alle Aggregate KORREKT — aber die NETTO-Rechnung überschätzt die Steuerlast um Faktor 4,7 und dreht das Vorzeichen des Gesamtergebnisses. Geprüft und exakt: /api/stats gegen unabhängige SQL-Rechnung, ~50 Werte über today/week/all — n_trades, wins/losses/breakeven, total_pnl, avg_win, avg_loss, gross_win/loss, winrate, profit_factor, dazu die inneren Konsistenzen (wins+losses+be = n · gross_wingross_loss = total_pnl · Σ closed_by = n · Σ hour_stats = wins/losses) und 8 Setup-Zeilen. Alles stimmt bitgenau. Ebenso stats_compact (Dashboard-Kachel) und day_pl im Header. ⚠⚠ DER DEFEKT liegt in server._add_net / _add_net_setup: net_pnl = total_pnl gross_win × 26,375 %. Das unterstellt, die Quellensteuer auf JEDEN Gewinn-Trade sei endgültig verloren. Sie wird aber größtenteils erstattet — genau das steht zwei Absätze weiter unten („erstattet via tägl. Tax settlement"), fließt in die Rechnung aber nicht ein. Empirisch aus den echten Broker-Buchungen (history_deals_get, 08.05.05.08., derselbe Zeitraum wie die Statistik):
    WHT einbehalten (685 Buchungen) 2.236,10 €
    erstattet (63 Tax-Buchungen) +1.789,65 €
    tatsächlich verblieben 446,45 €
    Modell der Statistik 2.097,10 €
    angezeigtes Netto 1.104,67 €
    reales Netto +545,98 €
    **Abweichung +1.650,65 € — die Anzeige macht aus einem profitablen Konto ein
    verlustreiches.** Real verbleiben nur 21 % der modellierten Last.
    Richtige Lösung: die TATSÄCHLICHEN Buchungen verwenden statt zu schätzen.
    Die WHT-/Tax-Deals sind über mt5.history_deals_get + Kommentar-Filter
    (WHT #… / Tax settlement) exakt abrufbar; die Schätzung kann ganz entfallen.
    ⚠ Zu cachen (MT5-Abfrage gehört nicht in den 1-s-Snapshot) und unter mt5_lock.
    Der Header-G/V ist ein anderer Fall (app.js: raw × (1 wht/100) für die
    OFFENE Position): dort ist der Abzug im Moment des Schließens sachlich richtig —
    die Erstattung kommt erst später. Das ist eine Cashflow-Sicht, also vertretbar,
    aber ebenfalls pessimistisch.
    Nicht gebaut — Umfang war die Prüfung.
  • Steuer: Broker behält WHT 26,375 % (DE-Abgeltungst.+Soli) je Gewinn ein (WHT #…-Buchungen), erstattet via tägl. „Tax settlement". Stats sind brutto; [trading] wht_pct zeigt eine Netto-Schätzung.
  • Python: C:\Users\ah\AppData\Local\Programs\Python\Python312\python.exe (hat fastapi/uvicorn/MetaTrader5). System32 fehlte mal in der PATH → Skripte nutzen absolute Pfade.
  • ⚠⚠ LLM-PROVIDER-WECHSEL 2026-08-05: [agent] provider=zai (war deepseek). DeepSeek-Guthaben ist leer — HTTP 402 "Insufficient Balance". z.ai/GLM (glm-4.5-flash) ist kostenlos, antwortet in ~2,3 s (DeepSeek ~6 s) und wird im Projekt ohnehin für daily_levels genutzt; live verifiziert („[zai] NEUTRAL (30%)"). Kimi (kimi-k2.6) antwortet ebenfalls (7,6 s), ist aber kostenpflichtig. ⚠⚠ Der eigentliche Befund: die Fallback-Kette hatte NICHT gegriffen. _active_ provider prüfte in avail nur das Key-FORMAT (startswith("sk-")), nicht ob der Key noch funktioniert. Ein Provider ohne Guthaben galt damit als „verfügbar" und blockierte die gesamte Kette (deepseek→kimi→zai→local) — der Copilot lief ~18 h still ins Leere (29 Fehlversuche ab 04.08. 14:52), ohne dass irgendetwas außer einer WARNING-Zeile passierte. BEHOBEN am selben Tag (agent._mark_dead/_provider_chain/_chain_raw): ein hart gescheiterter Provider wird zeitweise übersprungen und analyze() nimmt den nächsten aus der Kette. Hart = 401/402/403/404 (Key/Guthaben/Modell) → 30 min; nicht erreichbar (z. B. Ollama aus) → 5 min. Bewusst NICHT hart: Timeout, 429, 5xx — das sind vorübergehende Zustände; wer darauf wechselt, verliert bei jeder Lastspitze den bevorzugten Anbieter und zahlt pro Zyklus die volle Timeout-Summe ALLER Anbieter (90120 s je Stück). Sichtbar im Snapshot als agent.provider_dead {provider: Restminuten} — der Ausfall stand vorher nur im Log, genau deshalb blieb er 18 h unbemerkt. _dead läuft absichtlich ohne self._lock (snapshot() hält ihn bereits, nicht reentrant → Deadlock; dict-Zugriffe sind unter dem GIL atomar). Mit 9 Szenarien getestet (402→Fallback · zweiter Zyklus überspringt direkt · zwei tote Anbieter · Timeout/429 lösen KEINEN Wechsel aus · ConnectionError 5 min · alle tot → sauberer Fehler · Erholung nach Ablauf · Snapshot). Zurück auf DeepSeek nach Aufladen: provider = deepseek.
  • LLM-Provider (Historie): DeepSeek ([deepseek]) war der aktive Copilot-Provider ([agent] provider=deepseek, 2026-07-20 bis 2026-08-05). api.deepseek.com, Modell deepseek-v4-flash (/models listet flash + pro; alte Namen deepseek-chat/-reasoner gibt es NICHT mehr). ⚠ v4-flash ist ein Reasoning-Modell (content nach reasoning_contentmax_tokens=4000 in _call_deepseek, sonst leer; temperature 0.3 ok, schnell ~6 s). ⚠ KEINE Web-Suche → NICHT für daily_levels nutzbar. Fallback-Kette kimi→zai→local greift, wenn der Key ausfällt. Kimi/Moonshot ([kimi], kimi-k2.6) bleibt als Provider verfügbar. Key nur auf dem internationalen Endpoint api.moonshot.ai gültig (.cn = „Invalid Authentication"). Modell kimi-k2.6 (/v1/models listet auch k2.7-code/k3; k3 = thinking-only). ⚠ Zwei Fallstricke: (1) temperature MUSS 1 sein (andere Werte → HTTP 400); (2) k2.6 ist ein Reasoning-Modell → schreibt VARIABLE ~10001200 reasoning_tokens (in message.reasoning_content) VOR dem contentmax_tokens großzügig (4000, _call_kimi), sonst frisst das Reasoning das Budget und content bleibt leer (2000 war teils leer). Moonshot cached den System-Prompt (cached_tokens) → günstiger bei Wiederholung. Kimi ist CN-Modell → CJK-Drift möglich, vom _has_cjk-Retry abgedeckt. Frühere Option z.ai/GLM ([zai], glm-4.5-flash, kostenlos) bleibt reaktivierbar; ⚠ GLM ist Thinking-Modell → IMMER "thinking":{"type":"disabled"}. ⚠ _active_provider-Fix 2026-07-20: zai/kimi waren vorher NICHT in der avail/orders-Map → provider=zai fiel still auf local (qwen) zurück (die [local]-Logzeilen!). Jetzt beide routbar. OpenAI/Gemini-Keys = kein Guthaben (429).
    • KI-Copilot (agent.py): [agent] provider=deepseek. CN-/lokale Modelle (DeepSeek, Kimi, GLM, qwen) driften ins Chinesische → _has_cjk-Erkennung + 1× Retry deckt local, zai, kimi UND deepseek ab. (alle bleiben als Provider- Optionen wählbar.)
    • daily_levels.py bleibt auf z.ai (2026-07-20, gemessen): Die Tagesprognose BRAUCHT Live-Web-Suche. DeepSeek hat keine; Kimi-Web-Suche ($web_search-builtin) läuft nur über einen Tool-Loop, ist >2 min langsam und lief im Test in finish=length (Web-Ergebnisse+Reasoning sprengen selbst 4000 Tokens → leerer Content). z.ai macht es in EINEM sauberen web_search-Call → kein Wechsel (Kontext-Feature, kein Edge). ⚠ S/R-Kontext-Fix 2026-07-15: _tool_zones füttert dem LLM jetzt die echten M5-Pivots (wave.pb_levels, geclustert, nächste 3 je Seite) statt der stale [zones]-Config — plus Prompt-Leitplanke „nutze NUR die übergebenen Level, erfinde keine runden Marken". Reiner Kontext-Fix (keine Strategie): macht die Kommentar-Level aktuell/konsistent zum Chart, macht die LLM-Einschätzung aber NICHT validierter (Copilot bleibt unbelegte Verdict-Stimme, Gewicht nur bei Bias).
    • Tages-Prognose (daily_levels.py): z.ai + Web-Suche, 1×/Tag 07:30 Berlin.

Workflows / Befehle

  • Änderungen aktivieren: restart_server.bat (lädt core neu). ⚠ Der Batch startet das Oil-Backend im VORDERGRUND (Zeile 40) — also nicht mit Output-Umleitung aus PowerShell aufrufen (& .\restart_server.bat *>&1 | …): der Aufruf blockiert bis zum Timeout und der abgeschnittene Tail zeigt ein irreführendes „===== Server beendet =====", obwohl der Server läuft. ⚠⚠ NACHTRAG 2026-08-02: der Batch hat ZWEIMAL still NICHT neu gestartet (auch als Start-Process cmd /c restart_server.bat -WindowStyle Minimized). Der alte Prozess lief weiter — und weil statische Dateien bei jedem Request frisch von der Platte gelesen werden, belegt eine korrekt ausgelieferte app.js?v=N GAR NICHTS über den geladenen Python-Code. Zuverlässig ist nur: Port-8000-Prozess killen und direkt starten (Start-Process python -ArgumentList '-X','utf8','server.py' -WorkingDirectory …), danach an einem NEUEN Snapshot-Feld prüfen, dass der Python-Code wirklich neu ist (hier: hl_live.basis_stale fehlte → alter Code entlarvt). Zum Schluss verifizieren, dass genau eine Instanz je Port läuft. Auto-Start: Aufgabe OilTradingServer (Login, versteckt via VBS). ⚠ Startet seit 2026-07-30 BEIDE Server (User-Vorgabe): erst das Hyperliquid-Dashboard (Port 8001, versteckt via dessen start_hidden.vbs, wartet max. 10 s aufs Lauschen), dann das Oil-Backend (8000) im Vordergrund. Grund + behobener Bug: die Kill-Zeile trifft mit *server.py* BEIDE Prozesse — vorher wurde der HL-Server bei jedem MT5-Neustart still mit-erschlagen und nie wieder gestartet. Das riss die Lücken in die Lead-Lag-/Order-Flow-Datensammlung (72 h Spanne, aber nur 2292 Samples) UND ließ die Liquiditätslinien im Chart verschwinden. Fail-safe: fehlt das HL-Projekt (..\HyperLiquid-WTI Trader), startet nur 8000 mit Hinweis. Auto-Start des HL-Servers separat: Aufgabe HyperliquidDashboard (Login).
  • ⚠ MQL5-Indikator: das RICHTIGE Terminal treffen (Fallstrick, 2026-07-31). Unter %APPDATA%\MetaQuotes\Terminal\ liegen mehrere Instanz-Ordner. Wer das Ziel über „hat MQL5\Indicators" + Select-Object -First 1 bestimmt, erwischt leicht den falschen — real passiert: v1.30 und v1.31 landeten in 0BA5CADA…, während der Bot in D0E8209F… schreibt und der Chart dort läuft. Folge: der Kegel und die Konsolidierungs-Beschriftung waren einen halben Tag lang „gebaut und kompiliert", aber unsichtbar — der Chart lief weiter auf v1.29. Richtig: das Ziel über die CSV bestimmen, die der Bot tatsächlich schreibt:
    $term = Get-ChildItem "$env:APPDATA\MetaQuotes\Terminal" -Directory |
            Where-Object { Test-Path "$($_.FullName)\MQL5\Files\sr_levels.csv" } |
            Sort-Object { (Get-Item "$($_.FullName)\MQL5\Files\sr_levels.csv").LastWriteTime } -Descending |
            Select-Object -First 1
    
    Gegenprobe nach jedem Kompilieren: #property version im Terminal-.mq5 und der Zeitstempel der .ex5 müssen zum erwarteten Stand passen.
  • ⚠ AUFFANG-ELEMENT statt Disziplin (Fix 2026-08-07, v=150) — die Regel direkt darunter wird jetzt vom CODE durchgesetzt. $() gibt bei fehlendem Element nicht mehr null zurück, sondern ein detachiertes <span>:
    const _FEHLT = document.createElement("span");
    const $ = (id) => document.getElementById(id) || _FEHLT;   // + 1× console.warn je ID
    const $streng = (id) => document.getElementById(id);       // gibt null
    
    ⚠⚠ Anlass ist eine Messung, kein Gefühl: die Regel „neue Elemente per const el = $(…); if (el) {…}" war bei 0 von 95 direkten Zugriffen befolgt. 95 Stellen, an denen ein fehlendes Element den GESAMTEN Render abbricht; 0 davon abgesichert. Eine Regel mit 0 % Befolgung ist keine Regel — deshalb löst es der Code an EINER Stelle statt an 95. ⚠ ?. war NICHT die Lösung (mein erster Vorschlag, und er war falsch): 80 der 95 Zugriffe sind ZUWEISUNGEN, und $("x")?.y = v ist ein Syntaxfehler — Optional Chaining ist links vom = verboten. ⚠⚠ $streng ist der wichtige Teil. Alle Eingabefelder sind type="number", und leer heißt dort ausdrücklich „Stop entfernen" (sl: null). Mit dem Auffang-Element wäre .value undefinedparseFloat → NaN → JSON.stringify macht daraus wieder null — eine fehlende Eingabemaske hätte den Broker-SL einer laufenden Position stillschweigend gelöscht. Ein lauter Absturz ist dort das bessere Verhalten. Betroffen war genau eine Funktion (sendSltp); setSrMin/setEmg/setManualMargin/ setMarginPct fangen NaN bereits ab (geprüft, nicht angenommen). ⚠ Der Fehler wird nicht verschluckt, sondern 1× je ID in die Konsole gemeldet — ein stiller Fallback wäre derselbe Fehler wie der catch {} in pollSnapshot, der Render-Fehler lautlos gefressen hat (behoben 01.08.). Beide Richtungen verifiziert an einem Minimal-DOM, das eine alte index.html simuliert: mit der alten $-Definition bricht der Render beim zweiten Feld ab, mit der neuen läuft er durch und nur die fehlenden Felder bleiben leer; der Order-Pfad bricht in beiden Fällen sauber ab. ⚠ Die 40 vorhandenen if (!el) return;-Wächter feuern jetzt nicht mehr. Sie bleiben stehen (kein Nutzen im Entfernen, und sie greifen weiter, wenn jemand $streng benutzt) — beim Lesen aber mitdenken.
  • G/V-LAG: 1-s-FALLBACK + EHRLICHER VERBINDUNGSPUNKT (2026-08-07, v=151). User: „G/V wird extrem lagged — 1 Sekunde". ⚠⚠ ZUERST GEMESSEN, DANN GEDREHT — und das Backend war NICHT das Problem:
    Stufe gemessen
    /api/snapshot (75 Abrufe / 20 s) 21 verschiedene ts = ~1/s
    WebSocket-Push, direkt am Socket Median 1,04 s (min 1,00 · max 1,06)
    G/V-Änderungen Ø 1,44 s — das ist der Markt, keine Verzögerung
    Die ganze Server-Kette liefert bereits 1 s. Der Lag entsteht erst am Handy:
    bricht der WebSocket still weg (WireGuard-Aussetzer, App-Wechsel), trägt nur
    noch der REST-Poll — und der stand auf 4 s.
    POLL_MS 4000 → 1000, aber MIT Sperre: solange der WS liefert
    (lastWsMs, Fenster WS_FRISCH_MS=2200 ms), überspringt der Poll den Abruf.
    Kein zusätzlicher Verkehr im Normalfall (~14 KB/Abruf über WireGuard).
    Denkfehler, den erst die Simulation zeigte: der erste Entwurf las die
    WS-Frische aus lastRecvMs (jede Quelle) — damit **drosselte sich der Poll
    selbst** und kam bei totem WS nur auf 2,9 s statt 1 s. Jetzt zwei getrennte
    Uhren: lastWsMs (nur sock.onmessage) entscheidet über das Überspringen,
    lastRecvMs (jede Quelle) über die Stale-Anzeige.
    Simuliert: WS gesund → 0 Zusatz-Abrufe, max. Alter 0,9 s · WS tot →
    1/s, max. Alter 0,9 s · WS träge (5 s) → max. 2,9 s. Vorher bei totem WS:
    4,0 s.
    Der Verbindungspunkt log — behoben. Er wurde nur bei sock.onclose
    auf „off" gesetzt; genau der Fall, der den Lag erzeugt, feuert aber kein
    onclose: der Socket hängt still, der Punkt blieb grün. Neu .dot.stale
    (bernstein, pulsierend) ab 3,5 s ohne frischen Snapshot — gemessen wird,
    was ANKOMMT, nicht was der Socket über sich behauptet. Tooltip nennt das Alter.
    ⚠ Kein Konflikt mit render(), das className komplett überschreibt: läuft
    render(), ist gerade ein Snapshot angekommen und stale gehört ohnehin weg.
    .dot.stale steht in style.css nach .on/.off — gleiche Spezifität,
    die Reihenfolge entscheidet.
  • ⚠ NEUE HTML-Elemente IMMER null-sicher ansprechen (Fix 2026-08-01). index.html und app.js werden vom Browser unabhängig gecacht. Trifft neue JS auf eine alte, gecachte HTML, wirft ein direkter Zugriff ($("neu").textContent = …) eine TypeError — und weil das mitten in render() passiert, bricht der GESAMTE Render ab: das Dashboard friert ein und zeigt Altwerte, obwohl Server und Snapshot korrekt sind. Real passiert beim HL-Kurs-Einbau (User: „ich sehe die Änderungen nicht"); Server lieferte nachweislich v=129 samt #hdr-hl aus, Cache-Control: no-store war gesetzt, kein Service-Worker — die Ursache lag allein in dieser Kombination. Regel: neue Elemente per const el = $("id"); if (el) { … } ansprechen, dann läuft der Rest weiter und nur das neue Feld fehlt, bis die HTML nachgeladen ist. (Das ?v=N-Bump bustet nur app.js/style.css — NICHT die index.html selbst.)
  • Web-Assets: Browser cacht hart → in web/index.html ?v=N hochzählen bei jeder JS/CSS-Änderung; Server sendet Cache-Control: no-store. Hängt der Handy-Cache: Inkognito oder Seite mit frischem ?xN öffnen.
  • Empfehlungs-Edge messen: python backtest_signal.py [TF] [N] [K] (ereignisbasiert, nutzt die echte _build-Logik). Ø-Edge>0 = trägt.
  • Ausführungs-Kalibrierung: python analyze_execution.py — (A) echtes Spread-Profil je Berlin-Stunde aus dem Bar-spread-Feld, (B) Slippage bei SL-Closes, (C) MAE/MFE der Live-Trades. Kern-Befund 2026-07-13: Spread ist absolut konstant (0,0225), aber relativ Ø 0,265×ATR — die alte Kostenannahme 0,1×ATR war v. a. in Niedrig-Vola-Phasen 2,5× zu optimistisch (erklärt Live-PF < Backtest-Erwartung). Stunden 1517 am günstigsten (0,13), Nacht 07 = Kostenfalle (0,320,50×ATR). Slippage-Tail real: die 35/54- Desaster hatten 0,75 Slippage ÜBER den Initial-SL hinaus (harter SL deckelt in schnellen Märkten nicht). MAE/MFE live: Exits validiert (nur 7 % der Verlierer waren je ≥1,3×ATR im Plus → Breakeven-Schwelle passt; Gewinner-MAE Median 0,28). ⚠ Künftige B3-Checks mit stunden-echten Kosten rechnen, nicht pauschal 0,1. (WR/PF/Verhältnis/Worst je KW, Ausrichtungs-Split mit/gegen/ohne) gegen die Backtest-Erwartung (WR68 % · PF1,3 · Verh0,55) und flaggt Leckagen (Gegen-Signal- Anteil, WR-Verfall, PF<1, Einzelverlust). Wöchentlich laufen lassen (B4/B5).
  • Vor „fertig": immer python -m py_compile <datei>; JS via node --check (Stufe B in tools/check_nfalle.py, läuft im pre-commit-Hook mit). ⚠⚠ KORREKTUR 2026-08-07: „kein node im Env" stimmte nicht mehr — Node v24.18.0 ist installiert. Die Zeile hat die Frage „können wir Node nutzen?" jahrelang falsch beantwortet. Gemessen, warum das zählt: die alte Klammern-Balance findet 1 von 5 realistischen Fehlern — const x = ;, doppeltes let/const und return ausserhalb einer Funktion laufen glatt durch; node --check findet 5 von 5 in 86 ms. Ein Syntaxfehler in app.js bricht den GESAMTEN Render (Dashboard friert auf Altwerten ein) — bei 95 direkt dereferenzierten $("id")-Zugriffen ist das kein theoretisches Risiko. ⚠ Geprüft wird als MODUL (.mjs), nicht als CommonJS: als CJS hält Node ein top-level return für legal, der Browser lädt app.js aber als klassisches Script, wo es ein Syntaxfehler ist. Verifiziert, dass das echte app.js den strengeren Modus ohne Anpassung besteht. ⚠ Kein npm, keine package.json, kein node_modulesnode --check ist die Laufzeit selbst. Damit entsteht NICHT die Divergenz „Prüfumgebung ≠ Laufumgebung" (die beim .ps1-BOM und beim pre-commit-Framework das Problem war): Node und Chrome benutzen denselben Parser (V8). Drei Richtungen verifiziert: sauber → grün · mit eingebautem Fehler → Exit 1, Commit gestoppt · ohne Node im PATH → Rückfall auf die alte Prüfung, Exit 0 (eine frische Maschine darf am Hook nicht hängenbleiben).
  • BOOT-LÜCKE GESCHLOSSEN: Auto-Logon AKTIV (2026-08-07). OilTradingServer und HyperliquidDashboard hängen an einem Logon-Trigger — nach einem Update-Reboot läuft nichts bis zur Anmeldung (real 20.07.: Reboot 01:30, Server bis 07:42 tot, 95,48 €). ⚠ Ein Task „ohne angemeldete Sitzung" hilft nicht: der Bot braucht das laufende MT5-Terminal, und das ist eine GUI-Anwendung — es braucht eine echte Desktop-Sitzung. Auto-Logon ist hier also tatsächlich das richtige Mittel, nicht nur das bequeme. Vorbereitet: tools/autologon_einrichten.ps1 + Sysinternals Autologon (Microsoft-signiert, Signatur geprüft; tools/autologon/ ist gitignored wie tools/piper/). ⚠⚠ Bewusst NICHT der Registry-Weg. Der verbreitete Weg legt das Passwort als DefaultPassword im Klartext unter HKLM\…\Winlogon ab — lesbar für jeden lokalen Admin und in jedem Backup-Image. Sysinternals Autologon speichert es als verschlüsseltes LSA-Secret. Das Skript prüft danach nach, dass DefaultPassword NICHT im Klartext steht. ⚠⚠ Das eigentliche Restrisiko ist nicht das Passwort, sondern die offene Sitzung: nach dem Reboot steht ein angemeldeter Desktop mit MT5 und Live-Broker-Verbindung da. Deshalb legt das Skript zusätzlich die Aufgabe OilAutoLogonLock an — 30 s nach der Anmeldung LockWorkStation. Die Sitzung läuft (MT5 und beide Server arbeiten), der Bildschirm ist gesperrt. ⚠ Muss ELEVATED ausgeführt werdenHKLM ist aus einer normalen Sitzung nicht schreibbar (getestet: „Requested registry access is not allowed"). Das Passwort steht nicht im Skript und wird nirgends gespeichert; es wird im Autologon-Dialog eingegeben. Zurück: Autologon /accepteula → Disable, und schtasks /Delete /TN OilAutoLogonLock /F. ⚠⚠ ZWEI FEHLER IM ERSTEN VERSUCH (07.08., beide behoben) — beide von der Sorte, die Erfolg meldet, wo keiner war: (1) $ErrorActionPreference = 'Stop' PLUS schtasks /Delete auf eine noch nicht existierende Aufgabe → native Programme schreiben nach stderr, PowerShell macht daraus unter Stop einen Abbruch. Das Skript endete, BEVOR /Create lief; die Sperr-Aufgabe existierte nie. Jetzt wird vorher per /Query geprüft. (2) Die Erfolgsmeldung log: ein fehlendes DefaultPassword wurde als „LSA-Secret " ausgegeben — dabei war schlicht gar nichts gesetzt (der Autologon-Dialog war ohne Enable geschlossen worden). Jetzt ist AutoAdminLogon = 1 die Bedingung, und erst danach wird über die Speicherart geurteilt; ohne aktives Auto-Logon bricht das Skript mit Code 1 ab und überspringt Schritt 2. ⚠⚠ Der GUI-Weg führte zweimal zu nichts. Diagnostiziert: der Dialog öffnet sich (Fenstertitel „Autologon - Sysinternals", Prozess verifiziert) — er muss aber mit ENABLE bestätigt werden; nur schließen tut nichts. Deshalb gibt es jetzt -Direkt: Autologon 3.10 nimmt Benutzer/Domäne/ Passwort als Argumente, das Skript fragt es per Read-Host -AsSecureString ab (unsichtbar, nicht in der History) und ruft es ohne Dialog auf. powershell -ExecutionPolicy Bypass -File toolsutologon_einrichten.ps1 -Direkt ⚠ Abwägung: bei -Direkt steht das Passwort für den Moment des Aufrufs in der Kommandozeile des Kindprozesses (für Admins kurz sichtbar). Es landet nicht in einer Datei, nicht in der Shell-History — und anschließend als verschlüsseltes LSA-Secret, nicht im Klartext in der Registry. ⚠ Und ein Design-Fehler: die Sperre hätte bei JEDER Anmeldung gefeuert. Ein ONLOGON-Trigger unterscheidet nicht. Deshalb prüft tools/autologon_lock.ps1 jetzt die Systemlaufzeit — gesperrt wird nur, wenn der Rechner < 3 min läuft (= Auto-Logon nach Boot). Nach manueller Anmeldung passiert nichts. ⚠⚠ ENDSTAND — der SICHERE Weg hat NICHT funktioniert. Sysinternals Autologon beendete sich mit ExitCode 0, schrieb aber nichts (weder über den Dialog noch über die Argument-Form). Ausgeschlossen wurden: der Windows-11-Blocker DevicePasswordLessBuildVersion (nicht gesetzt), fehlende Elevation (Admin-Prüfung bestand), ein hängender Prozess. Ursache ungeklärt. Aktiv ist deshalb der Registry-Weg — verifiziert:
    AutoAdminLogon 1
    DefaultUserName / DefaultDomainName ah / HOMEBASE
    DefaultPassword KLARTEXT (Länge 4)
    Aufgabe OilAutoLogonLock Bereit, läuft als ah, ruft autologon_lock.ps1
    ⚠ Das Passwort ist damit für **jeden lokalen Administrator und jedes
    Backup-Image** lesbar. Entfernen (deaktiviert zugleich den Auto-Logon):
    Remove-ItemProperty -Path 'HKLM:\…\Winlogon' -Name DefaultPassword.
    Offen: die Wirkung ist erst beim nächsten Reboot belegt — bis dahin
    ist nur die Konfiguration verifiziert, nicht das Verhalten.
    ⚠⚠ DRITTER Fall derselben Fehlerklasse in diesem einen Skript (Erfolg
    melden, wo keiner ist): die Speicherart wurde aus einer stale Variable
    gelesen, die VOR dem Registry-Schreiben gefüllt war — die Ausgabe widersprach
    sich direkt („steht jetzt im KLARTEXT" … zwei Zeilen später „nicht im Klartext
    "). Behoben: der Zustand wird nach jedem Schreibvorgang frisch gelesen.
    Die drei Fälle zusammen sind die Lehre: **eine Erfolgsmeldung muss den Zustand
    NACH der Aktion prüfen, nicht den davor.**
  • ⚠⚠ .ps1 MIT NICHT-ASCII BRAUCHT EIN BOM — sonst bricht powershell.exe 5.1 (2026-08-07, real passiert). Das Autologon-Skript ließ sich nicht ausführen: „Schließende ) fehlt", „Zeichenfolge hat kein Abschlusszeichen". Es war kein Syntaxfehler: Windows PowerShell 5.1 liest .ps1 als CP1252, wenn kein BOM da ist. Aus dem Gedankenstrich werden drei Zeichen (â€"), das enthaltene " schließt den String vorzeitig, und es kaskadiert bis zum Dateiende. ⚠⚠ Warum es beim Schreiben nicht auffiel — und das ist der eigentliche Punkt: das hiesige PowerShell-Werkzeug ist pwsh 7, und das liest UTF-8 ohne BOM klaglos. Die Datei war also in meiner Umgebung fehlerfrei und beim Nutzer kaputt. Dieselbe Divergenz-Klasse wie der Deployment-Drift, nur eine Ebene tiefer: die Prüfumgebung war nicht die Laufumgebung. Behoben (UTF-8 mit BOM) und mit dem echten 5.1-Parser gegengeprüft (Parser::ParseFile, 0 Fehler). Dabei ein zweiter Fall gefunden: scripts/send_daily_report.ps1 (Umlaute, kein BOM). Dauerhaft abgesichert: tools/check_nfalle.py prüft es als Stufe C2. ⚠ .bat ist NICHT betroffen (cmd.exe verzeiht das in echo/Kommentaren — restart_server.bat enthält zwar /, bricht aber nicht).
  • STUFE 5 DER PIPELINE IST JETZT EIN SKRIPT (tools/deploy.py, 2026-08-07). Ersetzt das von Hand zusammengesetzte „killen, starten, warten, nachsehen" und macht die dokumentierte Schwachstelle prüfbar.
    python tools/deploy.py --feld <snapshot_feld>   # Neustart + Beweis
    python tools/deploy.py --nur-pruefen            # nichts anfassen, nur Zustand
    python tools/deploy.py --hl                     # HL-Dashboard (8001) mitnehmen
    
    Fünf Schritte, Rückgabecode 0 nur wenn alle durchlaufen (verkettbar): (1) Prozess gezielt am Port beenden — ⚠ NICHT per *server.py*-Muster wie restart_server.bat, das trifft auch das HL-Dashboard auf 8001 (real: es wurde bei jedem MT5-Neustart still mit-erschlagen und riss die Lücken in die Datensammlung). (2) starten und warten, bis /api/snapshot wirklich antwortet. (3) genau EINE Instanz je Port. (4) --feld prüfen. (5) Log ab der Startposition auf ERROR/Traceback. ⚠⚠ Schritt 4 ist der Kern. restart_server.bat hat zweimal still nicht neu gestartet, und weil statische Dateien je Request frisch von der Platte gelesen werden, belegt eine korrekt ausgelieferte app.js?v=N GAR NICHTS über den geladenen Python-Code. Ein neues Snapshot-Feld ist der einzige belastbare Beweis — genau daran fiel der Fehler 2026-08-02 auf (hl_live.basis_stale). Ohne --feld sagt das Skript das ausdrücklich dazu. Beide Richtungen geprüft: mit --feld rec_track → „Python-Code ist neu", Exit 0; mit einem erfundenen Feld → „FEHLT — der alte Code läuft weiter!", Exit 1. Eine Prüfung, die nicht scheitern kann, wäre wertlos.
  • ⚠⚠ DER ENGPASS DES PROJEKTS WAR NIE DIE MATHEMATIK — er war eine quadratische Schleife (Profil 2026-08-07, ~9 min → 8,9 s). Anlass war die Frage nach numpy. Statt zu vermuten wurde profiliert, und das Ergebnis war ein anderes als erwartet: von 10,07 s Gesamtlaufzeit steckten 8,17 s in der EIGENZEIT der Backtest-Schleife, nur 0,44 s in _build. Ursache:
    [C[j] for j in PH if i - _PIV_LOOK <= j <= i - _PIV_K]   # je BAR, ganze Liste
    
    Das ist O(Bars × Pivots) — bei 80k Bars hunderte Millionen Vergleiche, und genau daher kamen die 920-Minuten-Läufe. PH/PL sind sortiert, also genügen zwei Binärsuchen (bisect_left/bisect_right) für dieselbe Menge in O(log n). Gemessen: backtest_signal_touchfill.py über 80k Bars von ~9 min auf 8,9 s (~60×), bei 8k Bars 10,07 s → 1,43 s. ⚠⚠ Die Migrations-Regel verlangt Bit-Gleichheit — und ein Vorher/Nachher- Vergleich zweier LÄUFE taugt dafür NICHT: copy_rates_from_pos ankert am neuesten Bar, zwischenzeitlich eintreffende Live-Bars verschieben das Fenster (real ein Trade Unterschied, s. 06.08.). Die Gleichheit wird deshalb direkt auf der Datenstruktur bewiesen — tests/test_pivotfenster.py (4 Tests: 4.000 Bars × 400 Pivots, Randfälle, leere Liste, beidseitig inklusive Grenzen). Angewandt auf 7 Skripte (auto_signal_v3, breakout_gated, dist_gated, htf_vola, metalabel, sl_width, trail_be) — mechanisch identische Transformation, alle kompilieren, Stichprobe gegengelaufen. ⚠ Lehre: die naheliegende Antwort („numpy") war die falsche. Ein Profil kostet zwei Minuten und hätte diese Bremse jederzeit gezeigt — sie lag seit Monaten in jedem gegateten Backtest.
  • ⚠⚠ PIPELINE-STUFE 1 IST JETZT AUTOMATISCH — pre-commit-Hook (2026-08-07). Nach einer Web-Recherche zum 2026er-Python-Standard umgesetzt, aber gefiltert: von uv/Ruff/pyproject/CI ist hier nur das sinnvoll, was zu einer Einzelmaschine mit GUI-gebundenem MT5 und Live-Geld passt. (1) Abhängigkeiten festgehaltenrequirements.txt (Laufzeit) und requirements-dev.txt (Werkzeuge/Messung), Versionen gepinnt. ⚠ Bis dahin gab es gar keine Liste; nach einem Maschinenausfall wäre die Rekonstruktion Raten gewesen. Ermittelt per AST über core/, server.py, tools/ — nicht geraten. (2) tools/pre_commit.py fährt vor jedem Commit check_nfalle + pytest (~5 s). Einrichten: python tools/pre_commit.py --install. Notausgang git commit --no-verify ist Absicht — eine Prüfung, die einen dringenden Fix blockiert, wird sonst deinstalliert. ⚠ Bewusst OHNE das pre-commit-Framework: dessen Nutzen ist Werkzeug- Versionierung über mehrere Maschinen. Hier gibt es eine. Es würde stattdessen eine ZWEITE ruff-Version in einer isolierten Umgebung installieren — genau die Divergenz „Prüfumgebung ≠ Laufumgebung", die heute schon einmal zugeschlagen hat. (3) Ruff statt pyflakes (ruff check --select F821) — gleiche Befunde, ganzer Scan in 2,1 s. ⚠ Kein ruff format: das würde 161 Dateien umformatieren, darunter alle Backtests → Migrations-Regel verletzt. ⚠⚠ VIERTER Fall der Fehlerklasse „Erfolg melden, wo nichts geprüft wurde" — und diesmal fiel er nur durch einen Test auf. --select F821,E999 lässt ruff 0.16 komplett abbrechen („Rule E999 was removed", Exit 2); die Meldung geht nach stderr, und weil ich den Rückgabecode nicht prüfte, meldete die Stufe „keine undefinierten Namen" — obwohl sie gar nichts geprüft hatte. Ein absichtlich eingebauter F821-Fehler kam glatt durch den Hook. Behoben: nur F821, und Exit 2 = Werkzeugfehler löst jetzt den pyflakes-Rückfall aus statt als „sauber" zu gelten. Beide Richtungen verifiziert (wie beim Deploy-Skript): mit eingebautem Fehler → Hook stoppt den Commit; ohne → grün. Der Test-Commit wurde zurückgenommen (war nur lokal). ⚠ Nicht umgesetzt und warum: Gitea Actions — der Runner liefe auf derselben Maschine wie der Live-Bot und konkurrierte um CPU, bei 5 s Prüfzeit ohne Mehrwert gegenüber dem Hook. Container/Blue-Green — unmöglich, MT5 braucht eine Desktop-Sitzung, und es gibt EINE Broker-Verbindung. mypy/ty — kaum Annotationen vorhanden, grosser Aufwand für wenig Ertrag. ⚠ Struktureller Fund dabei: core/engine.py:3527 importiert backtest_breakout_squeeze (ebenso weekly_review.py). Ein Backtest ist damit Teil des Live-Abhängigkeitsgraphen — „Backtests sind nur Doku" stimmt nicht, ein Fehler dort kann den laufenden Bot treffen.
  • ⚠⚠ TEST-SUITE (pytest, tests/, seit 2026-08-07) — 17 Tests, ~1 s. Im Projekt sind über Monate dutzende Szenario-Tests entstanden („mit 10 Szenarien getestet", „mit 18 synthetischen Szenarien"), alle als Wegwerf-Skripte. Keiner davon lief je ein zweites Mal. Bei einem System, das man vor jeder Änderung neu starten und live verifizieren muss, ist das der teuerste Teil der Schleife. Aufruf: python -m pytest (Konfiguration in pytest.ini). Erfasst sind zunächst die beiden jüngsten Pfade: _check_pending_fill (6 Fälle, u. a. „fremde Position NICHT taggen" und „derselbe Fill zählt nur einmal") und die Selbst-Kalibrierung (7 Fälle: Episoden-Dedup, WARTEN re-armt, Auswertung in beide Richtungen inkl. Broker-Offset, fehlende Bar bleibt offen, Aggregation). ⚠ Drei harte Regeln in tests/conftest.py, damit die Suite jederzeit gefahrlos läuft: keine Live-DB (jeder Test bekommt eine frische Temp-Datei), kein MT5, kein laufender Server (alles Broker-Nahe gestubbt), kein Netz. ⚠ Das Schema wird über die echten Konstruktoren angelegt — HistoryLogger UND CandleLogger (candles_m1 gehört nicht zum History-Schema). Ein von Hand nachgebautes Test-Schema würde irgendwann vom Produktivstand abweichen. ⚠ Warum die Temp-DB und nicht eine Kopie der Live-DB: am 06.08. haben ECHTE candles_m1-Zeilen in einem vermeintlich leeren Fenster einen Test verfälscht — zweimal hintereinander sah es nach einem Code-Fehler aus, es waren Testfehler. Mutations-geprüft (eine Suite, die immer grün ist, ist wertlos): eine absichtlich eingebaute „fremde Position wird doch getaggt"-Regression wird gefangen. ⚠ Nebenbefund dabei: der Fill-Dedup ist doppelt gesichert (_pending_tagged UND der pop aus _pending_tickets) — nur eines von beiden zu entfernen fällt nicht auf, beides zusammen bricht sofort zwei Tests. Das ist gewollte Redundanz, kein ungetesteter Zweig.
  • ⚠⚠ KONFIDENZINTERVALLE statt Bauchgefühl (backtest_cost_gate.ci95, seit 2026-08-07). Das Projekt entschied bis dahin über Punktschätzungen plus die 2-Stichproben-Regel; ob ein ØR von +0,12 bei n=30 überhaupt von null zu unterscheiden war, musste jedes Mal im Kopf abgeschätzt werden. kennzahlen() liefert jetzt zusätzlich ci = 95-%-Bootstrap-Intervall des Mittelwerts (Perzentil, 2000 Resamples, random_state fest → reproduzierbar; ab n≥20, sonst None). ⚠ Rein additiv — alle 11 Aufrufer lesen weiter nur n/wr/oer/sr/pf, keine dokumentierte Zahl verschiebt sich. Sofortiger Ertrag: der erste Lauf hat das Ergebnis vom 06.08. korrigiert (s. „Verworfene Setups auf der Ausführungs-Achse" — aus „3 von 7 tragen" wird „1 gesichert, 2 unklar"). ⚠⚠ GRENZE, die man kennen muss: der Bootstrap unterstellt unabhängige Ziehungen. Für die sequentielle Sim mit EINEM Slot stimmt das näherungsweise (jeder Trade beginnt nach dem Exit des vorigen). Für überlappende Beobachtungen ist er zu optimistisch — stündliche Werte mit 24-h-Horizont teilen 23 von 24 Stunden, das effektive n ist ~n/24. Genau daran ist analyze_hl_funding.py am 06.08. aufgelaufen (dort wird entüberlappt statt gebootstrappt). Für solche Fälle bräuchte es einen Block-Bootstrapci95 ist dafür NICHT gedacht.
  • ⚠⚠ DIE \n-FALLE — und der Scan dagegen (tools/check_nfalle.py, 2026-08-07). Wird Code über ein Shell-Heredoc geschrieben, macht die Shell aus einem gemeinten \n einen echten Zeilenumbruch mitten im String-Literal. Real an EINEM Tag dreimal passiert: Telegram-Text in engine.py, Reminder-Eintrag in measurement_reminder.py, und beim Dokumentieren dieser Regel selbst (dort zusätzlich an den deutschen Anführungszeichen). ➜ Regel: mehrzeilige Strings mit \n oder Text mit „ " NIE per Heredoc schreiben — den Edit-Weg nehmen. Der Scan prüft in BEIDE Richtungen: (A) kompiliert alle .py · (B) sucht in JS unabgeschlossene String-Literale · (C) den UMGEKEHRTEN Fall, literales \n als sichtbarer Text in HTML-title= und Markdown · (D) die Signatur der Fälle (Zeile endet offen, nächste besteht nur aus einem Quote). ⚠ Fehlalarm-Falle im Scanner selbst, behoben: Quotes zu ZÄHLEN meldet '"' als offenes Literal (gültiges JS mit einem Anführungszeichen darin — real in app.js). Der Scan verfolgt jetzt zeichenweise den AKTIVEN Quote-Typ. Erster vollständiger Lauf: 161 Python-Dateien, app.js, alle HTML/MD sauber.
  • ⚠⚠ LINTER: pyflakes (seit 2026-08-07 installiert, Stufe A2 in tools/check_nfalle.py) — und er hat beim ERSTEN Lauf zwei echte Fehler gefunden. py_compile prüft nur die Syntax; ein Tippfehler in einem Namen ist syntaktisch einwandfrei. Dieses Projekt ist dafür besonders anfällig, weil es sehr viele breite except Exception-Blöcke hat (bewusst, als Fail-safe) — ein NameError darin ist unsichtbar: keine Meldung, kein Logeintrag, nur ein Zweig, der nichts tut. Genau das war am 04.08. schon einmal fast passiert (wave_snap), gefunden damals nur durch Lesen. Die beiden Funde in core/trader.py — beide nutzten log., den Namen gibt es dort NICHT (nur log_trade/log_hist): · _broker_offset_s() (seit dem Fix vom 01.08. drin): der NameError lief in das except Exception: pass und nahm die darunter stehende Zuweisung self._boff = int(off) mit → ein echter Broker-Zeitzonen-Wechsel wäre nie übernommen worden, dauerhaft und lautlos. Ausgerechnet der Zweig, der als Deployment-Drift Fall 4 gebaut wurde (falscher Offset → open_time in der Zukunft, Time-Stop-Alter negativ). · modify_sltp() (seit dem Initial-Commit): steht nicht in einem try → der Fehler lief bis in /api/sltp durch. Folge: der Broker hatte SL/TP bereits geändert, der User bekam trotzdem eine Fehlermeldung, und set_sltp kam nie bis trail.deactivate()das Trailing blieb an und hätte die Handeingabe beim nächsten Tick zurückgezogen, also genau das, was die User-Vorgabe verhindern soll. Aufgefangen hat das nur die separate Extern-Erkennung in trailing._do_modify, einen Tick später. Beide behoben (log_trade), dazu die Annotation 'HistoryLogger | None' über if TYPE_CHECKING sauber importiert. Projektweit jetzt 0 undefinierte Namen.Schweregrad-Trennung ist Absicht: gemeldet werden nur undefined name / syntax error; die ~105 kosmetischen Hinweise (ungenutzte Importe, f-Strings ohne Platzhalter) werden unterdrückt. Auch redefinition of unused steht bewusst NICHT in der Liste — es trifft das legitime Muster hook = None + bedingtes def hook (geprüft in zwei Backtests, beide korrekt). Eine Prüfung mit Dauer-Treffern wird ignoriert, und mit ihr die eine echte Meldung. ⚠ Backtest-Dateien wurden nicht angefasst — eine Änderung dort könnte ihre dokumentierten Zahlen verschieben.
  • Konsole: UTF-8 erzwingen (PYTHONIOENCODING=utf-8, -X utf8).
  • Uvicorn-Access-Log AUS (access_log=False in uvicorn.run, 2026-07-22): seit dem 4-s-REST-Polling + WS-Push flutete GET /api/snapshot … 200 OK die Konsole. Betrifft NUR Uvicorns eigenes Zugriffs-Log, nicht die oil.*-Logger (eigene Hierarchie, core/logger.py, unverändert in Konsole + Datei).

Empfehlungs- & Trade-Logik (Stand aktuell)

  • Richtung = EMA12 vs EMA50 (wave_rec.py), NICHT mehr ZigZag-Momentum (das hatte ~0 Edge). Totband (Chop→WARTEN), Anti-Überdehnung (_STRETCH_MAX=3,5×ATR), +S/R-Konfidenz, +Session-Konfidenz. (TU entfernt — s. u.)

  • ABER SIE TAUGT ALS VETO — und DAS hält der 2-Stichproben-Regel stand (2026-08-06, Ausrichtungs-Split über die GANZE Historie, 1.216 geschlossene Trades). Bisher stand dazu nur alignment_stats(40) = die letzten 40 Trades im Order-Dialog. Über alles, je Lot normiert (Positionsgrößen schwanken stark, s. die Lehre vom 04.08.):

    n WR ΣP&L je Lot H1 / H2 je Lot
    MIT Signal 354 56,5 % +555,66 € +3,22 +0,37 / +5,62
    GEGEN Signal 87 40,2 % 184,08 € 6,60 7,01 / 5,68
    OHNE Signal (WARTEN) 775 56,6 % +186,17 € +0,36 2,68 / +0,76
    ⚠⚠ **Die schärfste Zeile ist der Vergleich MIT gegen OHNE: 56,5 % zu 56,6 % —
    identisch.** Der Empfehlung zu FOLGEN bringt gegenüber „ohne Empfehlung handeln"
    gemessen nichts. Nur sie zu VERLETZEN kostet, und das **robust in beiden
    Hälften** (7,01 / 5,68 je Lot; die einzige Richtungsaussage über die
    Gesamtempfehlung, die den 2-Stichproben-Test besteht).
    **Damit ist die Rolle präzise: die Gesamtempfehlung ist ein VETO, keine
    Prognose.** Sie grenzt ein, was man nicht tun sollte; die Auswahl innerhalb des
    Erlaubten leistet der Mensch (dessen Vorteil separat belegt ist).
    ⚠ Grenzen: gegen hat nur n=87 (73/14), und die Zuordnung ist konfundiert —
    der Mensch wählt ja aus, wann er gegen das Signal geht. Die Richtung des Effekts
    ist über beide Hälften konsistent und groß (~10 € je Lot Unterschied), die
    Punktschätzung ist es nicht.
  • ⚠⚠ DIE RICHTUNGS-AUSSAGE DER LIVE-EMPFEHLUNG IST EIN MÜNZWURF — an den LIVE ausgegebenen Zeilen gemessen (analyze_signal_live.py, 2026-08-06, User-Frage „die Empfehlungen heute waren fast immer gegenläufig, sollten wir das Konzept überdenken?"). Kein Backtest: ausgewertet wurden die tatsächlich ausgegebenen recommendations-Zeilen (also nach ALLEN Gates) gegen den echten Kursverlauf. 20.365 Zeilen → 1.071 Episoden (08.05.06.08.; die Rohzeilen zählen dieselbe Marktlage dutzendfach, deshalb Episoden), gegen die Drift der jeweiligen Hälfte gelesen.

    Horizont H1 Überschuss / Treffer H2 Überschuss / Treffer
    30 min +0,079×ATR / 49,7 % +0,003×ATR / 51,0 %
    60 min +0,085×ATR / 50,7 % +0,034×ATR / 51,1 %
    120 min +0,002×ATR / 50,5 % 0,259×ATR / 48,3 %
    Die Trefferquote liegt über alle sechs Felder zwischen 48,3 und 51,1 %. Der
    Überschuss über die Drift ist im besten Fall +0,085×ATR — das liegt **unter der
    B3-Kostenhürde (0,1×ATR)** und weit unter den realen M5-Kosten (0,265×ATR). Als
    Richtungsprognose bezahlt das Signal nicht einmal seinen eigenen Spread.
    Der Tag, um den es ging, war kein Ausreißer, sondern typisch: 11 gerichtete
    Episoden, auf 30 min 8 von 10 gegenläufig. Die fünf Vormittags-SHORTs liefen
    gegen einen Tag, der von 75,5 auf 78 stieg (11:54 SHORT: 5,09×ATR auf 120 min).
    Das ist die nachlaufende EMA im Trendtag — sie shortet die Rücksetzer.
    ⚠⚠ **DAS HEISST NICHT, DASS DAS SYSTEM WERTLOS IST — es verdient nur nicht mit
    der Richtung.** Dasselbe Signal ist mit ruhender Order am Bestätigungs-Level
    in BEIDEN Hälften klar positiv (backtest_auto_signal_v3.py: ØR +0,35/+0,32,
    PF 2,2), am Markt dagegen negativ (0,09/0,11). Der Ertrag kommt aus
    Einstiegspreis und Exit-Asymmetrie, nicht aus der Trefferquote — genau wie
    beim Squeeze (4346 % Treffer, trotzdem positiv, weil die Gewinner laufen).
    Konsequenz für die Lesart: die Headline ist kein Kursurteil, sondern
    ein Auslöser, an einem Level eine Order zu platzieren. Der Konfidenz-Ring
    darf erst recht nicht als P(Erfolg) gelesen werden (unabhängig belegt:
    analyze_verdict_calibration.py, in H2 sogar invertiert).
    Was daraus NICHT folgt: die Suche nach einem besseren Richtungssignal.
    Die ist mit 23 verworfenen Eingriffen über jede orthogonale Indikatorklasse
    hinweg beantwortet (Momentum · Inter-Market · Volume Profile · SMC/Liquidity ·
    Chartmuster · Fibonacci · Kerzen · Marktstruktur · Meta-Labeling).
  • Volatilitäts-Squeeze-Breakout = ADDITIVER SETUP, UMGESETZT (User-Idee „Breakouterkennung" 2026-07-14, gemessen backtest_breakout_squeeze.py): struktureller Ausbruch aus einer Kompression (Box = Spanne der letzten _SQ_N=10 abgeschlossenen M5-Bars ≤ _SQ_MULT=2,5×ATR) um _SQ_K=0,1×ATR — NICHT der EMA-Trend-Breakout. 2-Stichproben-positiv (ØR +0,227/+0,141 netto Echtkosten, PF 1,35/1,20, 173/413 Trades; schlägt den Ausbruch aus beliebiger Box 0,045/+0,161 in BEIDEN Hälften) & robust über Box 1020 / k 0,10,2 (alle 8 Kombis positiv, kein Parameter-Zufall). Andere Signalklasse als die Welle → komplementär. Umsetzung: wave._squeeze_one(highs,lows,closes,atr) (M5). Erkennungs-Latenz ~5 s (Variante A, 2026-07-21): der Squeeze läuft auf einem EIGENEN, schnellen 60-Bar-M5-Fetch (_sq_due/_SQ_REFRESH_S=5.0, entkoppelt von der 15-s-TF-Ampel-Drossel) → Ausbruch wird ~15 s→5 s schneller erkannt, dann ~1 s Handeln (_pos_loop). Gleiche validierte M5-Regel, reine Infra (kein Backtest nötig). Variante B (Squeeze auf M1) = OFFEN/UNBELEGT — erst backtesten, wenn der M1-Logger (candles_m1, seit 2026-07-21) ab ~2027-01 genug History hat.

  • ⚠⚠⚠ VERWORFENE SETUPS AUF DER AUSFÜHRUNGS-ACHSE NEU GEMESSEN — und das Ergebnis stellt den LIVE-Squeeze mit in Frage (backtest_verworfene_v2.py, 2026-08-06, User: „backteste alle verworfenen Signale"). ⚠ Bewusst kein erneuter Komplettlauf — der lief am 04.08. (14 Klassen, alle blieben verworfen). Neu ist genau eine Achse, der Einstiegspreis, und sie wurde mit der heutigen Korrektur angewandt: Fill bei BERÜHRUNG, nicht bei Schluss-Bruch. Geprüft wurde nur, wo die Achse überhaupt greifen kann — stehendes Level + Ausbruchsrichtung (Momentum scheitert, weil sein Level wandert; Reversions-Einstiege werden von einer ruhenden Order sogar schlechter, backtest_candle_fade2.py).

    Setup A) Markt B) Close-Bruch C) Berührung (real) n
    KONTROLLE Squeeze 0,180/0,017 +0,560/+0,755 +0,133/+0,293 1094
    ORB EU 15/30 · US 15/30 negativ +0,45…+0,73 alle 4 ~200
    PDH / PDL Bruch 0,280/+0,044 +0,906/+1,368 +0,246/+0,464 147
    Asian Range Bruch +0,021/0,116 +0,928/+0,718 +0,142/+0,134 218
    Equal H/L Bruch 0,279/+0,228 +0,574/+0,905 +0,218/+0,170 168
    Die Kontrolle reproduziert backtest_squeeze_touchfill.py (+0,133/+0,293
    gegen +0,124/+0,292) — Pipeline in Ordnung. ORB bleibt verworfen in allen
    vier Varianten (bestätigt 2026-07-17). Die drei Überlebenden sind
    nachbar-robust (PDH/PDL über die Gültigkeitsdauer, Asian Range über das
    Fenster, Equal H/L über Toleranz und Gültigkeit — 11 Zellen, keine kippt).
    ⚠⚠ **NACHTRAG 2026-08-07 — mit 95-%-Konfidenzintervallen relativiert sich das
    „3 von 7" deutlich.** Seit backtest_cost_gate.ci95 (Bootstrap) hängt an jeder
    ØR-Zahl ihr Intervall. Auf der entscheidenden Spalte C:
    Setup H1 95-%-KI H2 95-%-KI
    --- --- ---
    KONTROLLE Squeeze [+0,037 … +0,229] [+0,225 … +0,366]
    PDH / PDL [0,032 … +0,544] ⚠ enthält 0 [+0,155 … +0,764]
    Equal H/L [+0,001 … +0,425] (haarscharf) [0,035 … +0,356] ⚠ enthält 0
    Asian Range [0,067 … +0,357] ⚠ [0,060 … +0,332] ⚠
    Nur der Squeeze hat in BEIDEN Hälften ein Intervall klar über null. Bei
    Asian Range enthalten beide die Null — als „Überlebender" war es zu stark
    formuliert.
    Zwei verschiedene Fragen, beide nötig: die 2-Stichproben-Regel prüft, ob
    das Vorzeichen über Regime hinweg stabil ist; das KI prüft, ob eine einzelne
    Schätzung überhaupt von Rauschen zu unterscheiden ist. Hier war Ersteres
    erfüllt und Letzteres nicht. Und: zwei Hälften, die beide in dieselbe Richtung
    zeigen, sind zusammen mehr Evidenz als jede für sich — „KI enthält 0" heißt
    also nicht widerlegt, sondern nicht belegt.
    An der Schlussfolgerung ändert das nichts (es wurde ohnehin nichts gebaut),
    aber die Formulierung „3 von 7 tragen" ist auf „1 gesichert, 2 unklar" zu
    korrigieren.
    ⚠⚠ Das sind KEINE drei neuen Signale. PDH/PDL, Asian-Range-Extrem, Equal
    Highs/Lows und die Squeeze-Box sind dasselbe: ein waagerechtes Level aus
    früheren Kursextremen, gebrochen, mit ruhender Stop-Order betreten und mit dem
    kanonischen Trailing verlassen. Deckt sich mit der Multi-Instrument-Kontrolle
    („beliebige Box" ist überall positiv, die Kompression trägt +0,009…+0,155).
    **Der Edge liegt in der Kombination Ausführung + Exit, nicht in der Regel, die
    das Level auswählt.**
    ⚠⚠⚠ **UND JETZT DER TEIL, DER AUCH DAS LAUFENDE SYSTEM BETRIFFT — mit der
    gemessenen Exit-Slippage (analyze_slippage_holdtime.py) bleibt fast nichts
    übrig:**
    Setup 0 $ 0,0124 $ (Tagfenster) 0,0370 $ (alle Std)
    --- --- --- ---
    Squeeze (LIVE!) +0,133/+0,293 0,002/+0,221 0,270/+0,080
    PDH / PDL +0,246/+0,464 +0,099/+0,386 0,192/+0,231
    Equal H/L +0,218/+0,170 +0,077/+0,092 0,202/0,062
    Asian Range +0,142/+0,134 0,011/+0,065 0,316/0,071
    **Die Edges dieser ganzen Familie liegen in derselben Größenordnung wie die
    Exit-Slippage.** Beim günstigsten gemessenen Wert hält nur PDH/PDL beide
    Hälften klar; Equal H/L ist dünn, Asian Range fällt — und der **live laufende
    Auto-Squeeze steht in H1 bei 0,002, also auf null**.
    ⚠ Die Slippage ist dabei eine Untergrenze (s. Herleitung dort) — der wahre
    Abzug ist größer, nicht kleiner.
    Konsequenz: KEIN neues Setup bauen. Drei weitere Module derselben Klasse
    addieren Frequenz zu einem Edge, der von den Ausstiegskosten aufgezehrt wird.
    Der nächste Hebel ist die Ausführungsqualität am EXIT (dort ist der Posten,
    der alles frisst), nicht ein weiteres Einstiegs-Signal.
    Nicht mitgemessen, ehrlich benannt: (a) Volume-Profile-LVN — stehendes
    Level, aber das Profil müsste je Bar neu gebinnt werden (Laufzeit); (b)
    Chartmuster-/Doppeltop-Nackenlinie — stehendes Level und Ausbruchsrichtung,
    qualifiziert sich also, braucht aber den Muster-Detektor kausal getrieben.
    Beide bleiben offen. Alle übrigen Ablehnungen (Inter-Market, Chop-Gate,
    Meta-Labeling, RSI/MACD/ADX, HTF-Winkel, H1-Stärke, Konfluenz-Strafe) haben gar
    kein Level — die Achse kann dort nichts ändern.
  • ⚠⚠ DER SQUEEZE TRÄGT AUCH AUF ANDEREN INSTRUMENTEN — 7 von 9 (gemessen backtest_squeeze_multi.py, 2026-08-06). Anlass: analyze_signal_live.py hat gezeigt, dass die Richtung ein Münzwurf ist; das Grundproblem ist also nicht die Qualität des Squeeze, sondern seine Frequenz (23×/Woche auf einem Instrument). Auf dieses Problem hat das Projekt bisher zweimal reagiert — mehr Signale auf demselben Instrument (23 Fehlschläge) oder feinere Zeitebene (Kosten erledigen es). Die dritte Achse war unberührt: dasselbe Signal, mehr Märkte.

    Symbol Spr/ATR H1 ØR / PF H2 ØR / PF
    GER40 0,086 +0,668 / 4,23 +0,595 / 3,82
    SpotBrent 0,184 +0,503 / 2,90 +0,653 / 3,85
    XAUUSD 0,028 +0,559 / 3,74 +0,556 / 3,95
    NAS100 0,072 +0,552 / 3,32 +0,481 / 2,94
    SpotCrude (Kontrolle) 0,181 +0,460 / 2,67 +0,605 / 3,51
    XAGUSD 0,261 +0,395 / 2,19 +0,379 / 2,30
    Copper 0,327 +0,300 / 1,83 +0,375 / 2,11
    NatGas 0,651 +0,229 / 1,52 +0,240 / 1,56
    Gasoline 0,972 0,610 / 0,35 0,148 / 0,76
    USDX 1,972 1,269 / 0,13 1,317 / 0,14
    Die Kontrolle reproduziert (SpotCrude +0,460/+0,605 gegen bekannt
    +0,456/+0,611) — die Pipeline ist in Ordnung. Alle 7 sind nachbar-robust
    (Box 10/15 × k 0,1/0,2, 28 Zellen, keine kippt) und halten Einstiegs-Slippage bis
    +0,20×ATR.
    Die beiden Durchfaller sind der beste Beleg, dass es kein Zufall ist: es
    sind exakt die zwei mit Spread ≥ ATR — dieselbe Kosten-Arithmetik, die auch
    M1 erledigt hat. Der Test scheitert dort, wo er scheitern MUSS.
    ATR-Floor relativ gesetzt (0,542 × Median-ATR statt der WTI-Dollar-Zahl
    0,06) — ein absoluter Floor wäre auf Gold wirkungslos und auf NatGas ein
    Selektions-Artefakt (Lehre aus analyze_tageszeit.py).
    ⚠⚠ DREI Vorbehalte, die vor einem Bau geklärt gehören:
    (1) „7 von 9" sind NICHT 7 unabhängige Tests. Crude/Brent korrelieren ~0,9,
    Gold/Silber und NAS100/GER40 ebenso — effektiv sind es eher 34 unabhängige
    Fälle, und alle im selben Makro-Zeitraum. Mehrere Slots gleichzeitig wären
    dann dasselbe Risiko doppelt, keine Diversifikation.
    (2) Die Kompression trägt weniger als der Ausbruch selbst. Die Kontrolle
    „beliebige Box" ist überall ebenfalls positiv (+0,144…+0,533); der Beitrag der
    Kompression liegt bei +0,009…+0,155 und ist bei XAGUSD (+0,009/+0,040) und
    NAS100 (+0,034/+0,013) kaum vorhanden. Gemessen wird hier also zu einem großen
    Teil „Ausbruch + unser Exit-Stack", nicht der Squeeze im engeren Sinn.
    (3) **Swap/Kommission je Symbol, Handelszeiten und Mindest-Lots sind NICHT
    modelliert** — nur der Bar-Spread.
    ⚠ Und der Fill-Befund direkt darunter gilt hier genauso: die Zahlen stehen in
    Variante (a), real ist (b) — also rund 0,3 R niedriger anzusetzen.
  • ⚠⚠⚠ FILL-ANNAHME: „am Level einsteigen" hat ZWEI Bedeutungen — und die teurere ist die richtige (gemessen backtest_squeeze_touchfill.py + backtest_momentum_v2.py, 2026-08-06). Aufgefallen beim Momentum-Nachtest.

    (a) Projekt-Standard einsteigen, WO das Signal ausgelöst hat — also dort, wo der Bar-CLOSE das Level gebrochen hat, zum Level-Preis
    (b) Realität einsteigen, WANN die liegende Order gefüllt hätte — bei der ersten Berührung, egal wo der Bar schließt
    Live gilt (b). (a) konditioniert auf den Bar-Schluss, den eine ruhende Order
    nicht kennen kann — das ist Look-ahead, kein Modellierungsdetail.
    Gemessen auf dem Squeeze (80k M5, 2 Halbjahre, kanonischer Exit, Echtkosten):
    Fill-Annahme H1 ØR / PF / n
    --- ---
    (a) Close bricht Level +0,461 / 2,68 / 801
    (b) Berührung (LIVE) +0,124 / 1,24 / 1092
    Aufschlag +0,337 (H1) / +0,312 (H2) R je Trade — und **291 bzw. 527
    zusätzliche Fills**, im Schnitt Verlierer (Dochte, die wieder in die Box
    zurückfallen; passt zu „54 % aller Box-Ausbrüche kehren binnen 3 Bars zurück",
    backtest_consolidation.py).
    Der Squeeze hält trotzdem — beidhälftig positiv, PF > 1. Und die
    Umstellung auf ruhende Orders bleibt richtig: (b) +0,124/+0,292 gegen
    Market 0,158/0,006. Bemerkenswert: (b) trifft fast genau das ORIGINAL
    validierte Band +0,14…+0,23 aus backtest_breakout_squeeze.py — der
    B4-Maßstab war also die ganze Zeit der realistischere.
    ⚠⚠ **KONSEQUENZ: die heute mehrfach zitierten +0,456/+0,611 sind als
    Live-Erwartung ~0,32 R zu hoch.** Sie bleiben als Vergleich zwischen Market und
    Level gültig (dafür waren sie gerechnet), taugen aber nicht als Ertragsprognose.
    **ERLEDIGT — der SIG-Pending-Pfad FÄLLT DURCH (backtest_signal_touchfill.py,
    2026-08-06, 80k M5, voller Live-Gate-Stack, kanonischer Exit, Echtkosten).**
    Konf A) Markt am Bar-Close
    --- ---
    ≥55 0,015 / 0,172
    ≥65 0,024 / 0,085
    ≥75 0,148 / 0,310
    C ist in ALLEN SECHS Feldern negativ (PF 0,380,71). Der Look-ahead-Aufschlag
    beträgt 0,770,85 R — grösser als beim Momentum (0,45) und beim Squeeze (0,32).
    ⚠⚠ C ist sogar schlechter als der MARKT-Einstieg (0,392 gegen 0,015). Der
    Mechanismus ist im Trade-Zähler sichtbar: C hat mehr Fills als B (441 gegen
    328 · 659 gegen 455) — die zusätzlichen sind genau die Berührungen, die
    zurückfallen, und die sind die Verlierer. Eine Order an einem wandernden
    Level fängt systematisch die Fehlausbrüche ein.
    Damit ist die Vorhersage bestätigt: entscheidend ist nicht „Level statt
    Markt", sondern ob das Level STILLSTEHT. Squeeze-Box → hält (+0,124/+0,292);
    _pend und Momentum → wandern → fallen durch.
    ⚠ **Die im Bau-Eintrag vom 05.08. zitierten +0,39/+0,36 sind damit als
    Live-Erwartung ungültig** — sie stehen in Variante B. Der Eintrag bleibt als
    Historie stehen, gilt aber nur noch für den Vergleich Markt↔Level.
    ABGESCHALTET (User-Entscheidung 2026-08-06): signal_pending_entry = false.
    Backup oil_widget_config.ini.bak-2026-08-06-sigpending. ⚠ Der Schlüssel lebt
    nicht in runtime_state.json (kein UI-Toggle) — die ini greift direkt.
    Live verifiziert: signal_pending=False, squeeze_pending=True.
    Neu im Config-Wächter (validiert = false), damit ein Zurückschalten
    auffällt: es hiesse, SIG über den gemessen schlechtesten Einstiegsweg zu handeln
    — schlechter als Market.
    squeeze_pending_entry bleibt begründet — dort steht die Box still, und
    der Touchfill-Test ist bestanden.
    ⚠ Zur Migrations-Prüfung: lauf() hat einen fill-Parameter bekommen
    (Default "close"). Der Vorher/Nachher-Vergleich war nicht bitgenau
    Abweichung: EIN Trade in H2 (454→455). Ursache ist nicht der Patch (mit
    fill="close" sind die ausgeführten Anweisungen identisch), sondern
    zwischenzeitlich eingetroffene Live-Bars: copy_rates_from_pos(0, 80000)
    ankert am neuesten Bar, das Fenster verschiebt sich zwischen zwei Läufen.
    H1 war in beiden Läufen identisch.
  • ⚠⚠ MOMENTUM-CONTINUATION MIT LEVEL-ORDER NEU GEMESSEN = BLEIBT VERWORFEN (backtest_momentum_v2.py, 2026-08-06, User-Wunsch). Kein bloßes Wiederholen: der Lauf von 2026-07-17 stieg am Bar-Close ein, und genau diese Annahme hat am 05.08. Squeeze und Auto-Signal von negativ auf positiv gedreht. Momentum hat ein sauber definiertes Level (mom_N kreuzt X×ATR ⇔ Preis erreicht C[iN] ± X×ATR), die Frage war also offen.

    Variante H1 H2
    A) Bar-Close (der alte Lauf) 0,10…−0,19 0,08…+0,00
    B) am Level, Fill bei Kreuzung +0,35…+0,47 +0,47…+0,56
    C) am Level, Fill bei BERÜHRUNG (echt) 0,04…−0,09 0,02…+0,07
    Alle neun N/X-Kombinationen sehen in B großartig aus und sind in C
    beidhälftig ohne Edge. Der Aufschlag von ~0,45 R ist reines Look-ahead.
    ⚠⚠ Der strukturelle Grund — und er ist die eigentliche Lehre: das
    Momentum-Level wandert jede Bar (C[iN] rutscht mit). Es ist damit kein Ort,
    an dem eine Order liegen bleiben könnte; sie müsste im 5-Minuten-Takt umgesetzt
    werden und fängt dabei jede Zuckung ein. Die Squeeze-Box steht — deshalb hält
    sie (+0,124/+0,292) und Momentum nicht. **Ein Level ist nur dann ein
    Ausführungs-Vorteil, wenn es STILLSTEHT.**
    Zwei eigene Fehler auf dem Weg, beide dokumentiert: (1) der erste Entwurf
    prüfte nicht, ob das Level als Stop-Order überhaupt auf der **richtigen Seite des
    Marktes** liegt — dadurch „füllte" die Sim zu längst passierten Preisen und
    lieferte ØR +2,2, was schlicht unmöglich ist und den Fehler verriet.
    (2) Variante B war überhaupt erst nötig, weil ich den Unterschied
    Kreuzung/Berührung zunächst nicht gesehen hatte. **Merksatz: eine Zahl, die zu
    gut ist, ist ein Befund über den Code, nicht über den Markt.**
    Der Befund bestätigt zugleich die heutige Regel aus dem Kerzen-Fade — „ruhende
    Order hilft bei Momentum-Entries" — in präzisierter Form: sie hilft bei
    Ausbrüchen an stehenden Strukturen, nicht bei Momentum allgemein.
  • ⚠⚠ SCALPING-MODUL AUF M1 = ABGERATEN, bevor überhaupt gebaut wird (gemessen analyze_scalping_costs.py, 2026-08-06, User-Frage „wie sinnvoll wäre ein zusätzliches Modul Scalping auf 1M-Basis, über das Dashboard steuerbar?"). Es ist zuerst eine Kosten-, keine Signalfrage: der Spread ist bei WTI absolut ~konstant und skaliert NICHT mit der Zeitebene, die Bewegung schon.

    TF Ø ATR Ø Spread Median Sp/ATR Ø Sp/ATR
    M1 0,0870 $ 0,0254 $ 0,319 0,416
    M5 0,1677 $ 0,0226 $ 0,193 0,256
    M15 0,2269 $ 0,0250 $ 0,133 0,171
    Der Spread ist auf allen drei Ebenen praktisch derselbe Betrag — **eine mittlere
    M1-Kerze ist nur 3,4× so groß wie der Spread.** Nötige Trefferquote bei 1:1
    (Ziel/Stop je 1×ATR_M1): 65,9 % statt 50 %; bei 0,5:0,5 sogar 81,9 %.
    Zum Vergleich: nichts in diesem Projekt hat je über ~46 % geliefert.
    Nachts ist es arithmetisch unmöglich: um **01:00 beträgt der Spread
    1,051×ATR_M1** — er ist größer als die durchschnittliche Bar-Spanne (00:00 0,801 ·
    06:00 0,750). Handelbar wäre allenfalls 1017 Uhr (0,2100,265), also dort, wo
    M1 ungefähr die heutigen M5-Kosten erreicht.
    ⚠⚠ DREI Gründe, die schwerer wiegen als die Kosten selbst:
    (1) Ein Backtest nach Projektstandard ist NICHT möglich. Die M1-Historie des
    Brokers ist hart bei 80.000 Bars = 80 Tage gedeckelt (copy_rates_from_pos
    liefert darüber None, copy_rates_range ebenso). Zwei Stichproben à 40 Tage
    liegen im selben Regime — genau die Konstellation, an der ORB und die
    Dead-Hour-Stunde 13 gescheitert sind. Der eigene candles_m1-Logger steht bei
    19 Tagen und erreicht die 180 erst ~2027-01.
    (2) Das Auflösungs-Artefakt trifft M1 am härtesten. analyze_trail_resolution.py
    (05.08.) hat gezeigt: eine Bar-Simulation überschätzt enge Stops systematisch,
    Kipp-Punkt bei ~0,3×ATR. Ein Scalp lebt in 23 Bars — eine M1-Bar-Sim hat dort
    praktisch keine Auflösung mehr. Ein positives Ergebnis wäre nicht überprüfbar; es
    bräuchte Tick-Daten.
    (3) Der Slippage-Schwanz ist ABSOLUT und frisst deshalb den kleinen Stop.
    Gemessen (analyze_execution.py): Median 0,017 (unkritisch), **Ø 0,234, Max
    4,192 $**. Ein 2×ATR_M1-Stop ist 0,174 $ breit — der MITTLERE Slippage-Wert
    übersteigt ihn bereits. Der reale 0,751-$-Fall vom 30.06. entspricht 8,6× einem
    M1-Stop gegen 2,2× einem M5-Stop; er löscht rund 11 gewonnene Scalps statt 4.
    Was FÜR die Idee spricht (fairerweise): der Bau wäre billig — Pending-Stop-
    Orders, _auto_guard, _pending_ziel mit Vorrang, Dashboard-Toggle (Muster
    🚀 BRK / 🎯 SIG), runtime_state.json, B4-Monitor und die Setup-Kennzeichnung
    (seit 06.08.) existieren alle. Und höhere Frequenz = schnellere Validierung:
    der Squeeze braucht 20 Trades und hat nach 5 Tagen 8.
    **Empfehlung: KEIN eigenständiges Scalping-Modul, aber eine engere Variante ist
    messbar** — der Squeeze auf M1 (= die schon offene Variante B), beschränkt auf
    1017 Uhr, mit ruhender Stop-Order. Das ist kein neues Signal, sondern eine
    validierte Regel auf einer feineren Ebene; die 80 Tage reichen für ein
    Ausschluss-Urteil (nicht für eine Bestätigung).
  • ⚠⚠ SQUEEZE AUF M1 (Variante B) GEMESSEN — NICHT ausgeschlossen, aber in JEDER Variante schlechter je Trade als M5; die Entscheidung hängt an EINER ungemessenen Zahl (backtest_squeeze_m1.py, 2026-08-06). 80.000 M1-Bars (18.05.06.08.), echte _squeeze_one-Regel, Einstieg als ruhende Stop-Order am Level, kanonischer Exit, Echtkosten, sequentiell EIN Slot, M5-Kontrolle auf DEMSELBEN Fenster (weil beide Hälften im selben Regime liegen — der interne Vergleich ist der belastbare Teil, nicht die Absolutwerte).

    Variante H1 ØR / ΣR H2 ØR / ΣR n (H1)
    M5-Kontrolle (live laufende Ebene) +0,605 / +163 +0,491 / +132 270
    A) M1, Stop aus ATR_M1, alle Stunden +0,405 / +473 +0,385 / +476 1168
    C) A + nur 1017 Uhr (beste M1) +0,474 / +201 +0,387 / +189 424
    B) M1-Einstieg, Stop aus ATR_M5 +0,207 / +176 +0,137 / +118 848
    D) B + nur 1017 Uhr +0,231 / +73 +0,130 / +44 316
    A ohne ATR-Floor (Selektions-Kontrolle) +0,385 / +457 +0,342 / +442 1186
    Nichts fällt durch: alle Varianten beidhälftig positiv, alle 5 Nachbar-Kombis
    (Box 10/15/20 × k 0,1/0,2) ebenfalls, Einstiegs-Slippage hält bis 0,20×ATR
    (+0,254/+0,130). Auch die Selektions-Kontrolle ohne ATR-Floor bestätigt — der
    massstabsgerecht skalierte Floor (0,024 statt 0,06) erzeugt den Befund nicht.
    Kein Broker-Hindernis: trade_stops_level = 0, ein 2×ATR_M1-Stop (0,159 $) ist
    platzierbar.
    ⚠⚠ **DER ENTSCHEIDENDE BEFUND IST DIE EXIT-SLIPPAGE — und sie trifft M1 doppelt
    so hart, genau wie vorhergesagt.** Slippage ist eine absolute Größe; sie frisst
    von einem kleinen M1-Stop einen viel größeren Anteil. In DOLLAR angesetzt, auf
    beide Ebenen gleich:
    Exit-Slippage M1 (Variante C) M5-Kontrolle
    --- --- ---
    0,000 $ +0,474 / +0,387 +0,605 / +0,491
    0,017 $ (gemessener Median) +0,309 / +0,196 +0,511 / +0,384
    0,050 $ 0,012 / 0,175 +0,330 / +0,176
    0,100 $ 0,499 / 0,737 0,139 (H2)
    M1 kippt bei ~0,04 $, M5 erst bei ~0,09 $ — Faktor ~2,2, praktisch das
    ATR-Verhältnis (0,198/0,079 = 2,5). Gemessen (analyze_execution.py):
    Median 0,017 $ (beide halten) · Ø 0,234 $ (beide fallen, M1 weit
    deutlicher) · Max 4,192 $.
    **DIE FEHLENDE ZAHL IST NACHGEMESSEN (analyze_slippage_holdtime.py,
    2026-08-06, 317 SL-Closes).** Die bisher zitierten „Median 0,017 · Ø 0,234"
    waren die Statistik nur über die Ausreißeranalyze_execution.py zählt
    ausschließlich Fälle, die schlechter als der Initial-SL endeten (if gap > 0).
    Sauber ist die Untergrenze **E[Slippage] ≥ P(gap>0) × Ø(gap gap>0)**; sie ist
    unkontaminiert, weil das Trailing den gap nur negativer machen kann (es zieht
    den SL in Gewinnrichtung) — ein positiver gap ist damit zwingend Slippage.
    Haltedauer alle Stunden nur 1017 Uhr
    --- --- ---
    015 min 0,0470 $ 0,0058 $
    1530 min 0,0164 $ 0,0238 $
    60120 min 0,0502 $ 0,0449 $
    >120 min 0,1691 $ 0,0464 $
    <30 min gewichtet 0,0370 $ 0,0124 $
    ⚠⚠ Der Schwanz sitzt AUSSERHALB von 1017 — bei kurzen Trades ist die
    Slippage dort 8× kleiner (0,0058 gegen 0,0470). Damit ist die
    Stunden-Beschränkung nicht bloß eine Kosten-Optimierung: sie entfernt auch den
    Slippage-Schwanz. Beides hat dieselbe Ursache (Buchtiefe), das ist ein
    Mechanismus und kein Data-Mining.
    ENDVERGLEICH — jede Variante mit dem gemessenen Wert IHRES Fensters:
    Variante H1 ØR / ΣR H2 ØR / ΣR n (H1)
    --- --- --- ---
    M1 A, alle Stunden · 0,0370 $ 0,107 / 125 0,161 / 199 1168
    M1 C, 1017 Uhr · 0,0124 $ +0,354 / +150 +0,247 / +121 424
    M5, alle Stunden · 0,0370 $ +0,401 / +108 +0,261 / +71 270
    M5, 1017 Uhr · 0,0124 $ +0,751 / +68 +0,444 / +45 90
    M1 über alle Stunden fällt jetzt HART durch (PF 0,83/0,76) — die
    Nachtstunden fressen es auf, exakt wie die Kosten-Arithmetik vorhergesagt hat.
    Der ΣR-Vorsprung von Variante A war reines Artefakt der Annahme „keine
    Exit-Slippage".
    Auf demselben Fenster (1017) bleibt es ein echter Zielkonflikt: M5 liefert
    je Trade rund das Doppelte (+0,751/+0,444 gegen +0,354/+0,247), M1 in der
    Summe rund das Doppelte (+150/+121 gegen +68/+45) — weil es 4,7× so oft
    handelt. Beide konkurrieren um denselben EINEN Positions-Slot.
    Urteil: NICHT umstellen. Drei Gründe: (a) der entscheidende Parameter ist
    nur als Untergrenze bekannt, und M1 C hat davon bis zur Kippgrenze nur
    ~3× Luft (0,0124 → ~0,04), M5 dagegen ~7×; (b) die Stichprobe hinter „M5 ist
    je Trade doppelt so gut" ist mit n=90/101 dünn, die hinter der Slippage-Schätzung
    im 1017-Fenster mit 17 Ereignissen ebenso; (c) 80 Tage = EIN Regime.
    Die Bedingung, unter der es kippen würde, ist benennbar (für später): bleibt
    die gemessene Exit-Slippage für <30-min-Trades im Fenster 1017 bei größerer
    Stichprobe unter ~0,02 $, dann ist der Hybrid (M1 während 1017, M5 sonst)
    rechnerisch ~1,9× der reinen M5-Variante (ΣR +190/+147 gegen +108/+71) und einen
    kleinen Live-Test wert. Vorher nicht.
    Zwei Methodik-Lehren aus dem Lauf selbst:
    (1) Der Härtetest lief zuerst auf der SCHWÄCHSTEN Variante. beste = "D" if …
    wählte nach Reihenfolge statt nach Ergebnis — D (+0,231/+0,130) statt C
    (+0,474/+0,387). Ein Robustheitstest auf der falschen Variante beweist nichts.
    (2) **Die vorab fixierte Regel „nicht schlechter als die M5-Kontrolle" war wieder
    unterspezifiziert** — M1 verliert in ØR und gewinnt in ΣR um das Dreifache.
    Dieselbe Falle wie am 05.08. beim Kosten-Gate. Die Regel wurde **nicht
    nachträglich gelockert**; stattdessen zeigt zeig() jetzt beide Spalten, damit
    die Auswahl nicht stillschweigend passiert.
    80 Tage = EIN Regime (Broker-Deckel). Ein heißt hier „nicht
    ausgeschlossen", nicht „bestätigt".
    Snapshot wave.squeeze {state, dir, level, box_atr}: active = Ausbruch (echtes
    Signal LONG/SHORT) · armed = komprimiert, Ausbruch steht bevor · None = keine
    Kompression (transient — der Ausbruch weitet die Box, Signal klärt sich selbst).
    Verdict-Modul „Squeeze" (_verdict, Gewicht 2,0, nur bei active; unter der
    Welle=3,0). UI Welle-Kachel (#wave-squeeze): grün/rot bei active
    („🚀 Squeeze-Breakout ▲ LONG @ …"), blau bei armed („🎯 Kompression … Ausbruch
    steht bevor"), grau sonst. ⚠ Nur der ENTRY — der User-Teil „Gegen-Trade schließen
    • Stop-&-Reverse" ist NICHT gebaut (nicht validiert, verdoppelt Kosten, Chop-Risiko). Läuft normal über SL/Trailing/Notfall-Stop. Caveats: modeste Stichprobe (Box>12 = 1222 Trades = Rauschen), Frequenz ~23/Woche → B4-Wochenmonitor (Live-ØR/PF gegen +0,14…+0,23), Runner-abhängig (43 % Treffer). Verwerfen, wenn Live wegdriftet (B5).
  • Momentum-Continuation als Auto-Setup = VERWORFEN (gemessen backtest_momentum.py, 2026-07-17): Kandidat für Autonomie (Einstieg in Richtung eines frischen Momentum-Schubs, mom_N kreuzt ±X×ATR; seq. Sim, Live-Exit, Echtkosten). Fällt durch den 2-Stichproben-Test: fast alle N/X in H1 negativ (0,03…−0,22), in H2 positiv (+0,02…+0,07) → regime-abhängig. Einzige „beide positiv"-Kombi (N=24/X=2,0) ist H1 +0,002 / PF 1,00 = Breakeven-Rauschen, nicht der Squeeze-Maßstab (+0,14…+0,23). Grund: Momentum-Continuation IST Trendfolge — erbt die Regime- Anfälligkeit des Wave-Signals, kein distinkter Edge wie die Kompression→Ausbruch- Klasse. Autonomie-Fazit: der Squeeze bleibt der EINZIGE validierte Auto-Entry. (P(break)-Level-Bounce als Entry ist separat schon verworfen, s. „Range-Einstieg".)

  • Opening Range Breakout (ORB) als Auto-Setup = VERWORFEN (gemessen backtest_orb.py, 2026-07-17, Recherche-Kandidat): Box = erste 15/30 min nach EU-Open (10:00 Brk) bzw. US-Open (16:30 Brk), Ausbruch ±0,1×ATR, one-shot, Live- Exit, Echtkosten. 3 von 4 Varianten kippen zwischen den Hälften (US-Open H1 0,19…−0,27!); einzig EU-15min ist beidseitig positiv, aber nur ØR +0,069/+0,027 (PF 1,11/1,04) = Rauschen unter dem Kosten-Puffer, und die Nachbar-Parameter kippen (keine Robustheit — beim Squeeze waren ALLE 8 Kombis positiv). ⚠ Lehrstück: erster Lauf zeigte EU-Open ØR +1,3/PF 10 — war ein Selektions-Artefakt (ATR- Skip statt Floor ließ in der Niedrig-Vola-H1 nur 9 Morgen durch); mit Floor verschwand der „Edge". Zeit-Anker ersetzt keinen Struktur-Anker.

  • ATR-Breakout-Bestätigung (wave._confirm_breakout, live [trading] breakout_k=0.3 — Split-validiert, s. u.; 0=aus): ein Richtungssignal wird erst durchgelassen, wenn der Kurs k×ATR in Signalrichtung gelaufen ist („X dynamisch"); läuft er vorher k×ATR DAGEGEN/ Timeout → neu verankern, solange WARTEN (Grund „warte auf Breakout …", snap.breakout). Stateful (_pend), reset bei TF-Wechsel. Gemessen (backtest_breakout.py): hebt Ø-Edge/Trade ~2× (+0,185→+0,36 bei k=1,0), Treffer 43→47 %, ~halb so viele Trades → netto klar besser (halbe Spread-/ Komm.-Kosten; lohnt ab Kosten >0,037×ATR, real ~0,1×ATR). = die „Zwei-Konten- Hedge"-Idee des Users, auf EINEM Konto ohne doppelte Kosten. k=0,3 seit 2026-07-08 (Split-validiert, 80k Bars in 2 Halbjahren): k=0,3 ist in BEIDEN Hälften das Beste — H1: ØR +0,119/netto +339 (0,5: +0,055/831; 1,0: +0,046/805 — beide netto negativ!), H2: +0,362/+4239 (0,5: +3308; 1,0: +3838). Grund: 0,3 filtert Fehlstarts, behält aber den besseren Einstiegspreis (höheres k = späterer Einstieg, Anfang des Moves verschenkt). ⚠ Ein-Stichproben-Lauf hatte „1,0 besser" gezeigt — Split-Prüfung drehte das (B2!). Nebeneffekt: kürzere „warte auf Breakout"-Phasen.

  • Entry-Raum-Gate (AKTIV, [trading] entry_room_atr=0.6 — kurz 1,0, am selben Tag zurück auf 0,6, s. u.; gemessen backtest_entryroom.py 2026-07-16): Richtungssignal → WARTEN, wenn das Gegenlevel (M5-Pivot in Trade-Richtung, trainingsgleich zum S/R-Auto-Close) näher als X×ATR_M5 liegt. Befund (seq. Sim MIT S/R-Close-Exit, Echtkosten, 2 Halbjahre): monoton in BEIDEN Hälften — Raum <0,3: ØR 0,119/0,067 (PF 0,42/0,66!) · 0,30,6: 0,100/0,059 (⚠ 7982 % WR und trotzdem PF<1 = Klein-Close-Falle) · 0,61,0: 0,123/0,060 · ≥1,0 bzw. „kein Gegenlevel" positiv. Kosten-Arithmetik, kein Signal-Timing (deshalb robust, anders als die 9 verworfenen Filter): Ertrag am Level gedeckelt (63 % Containment) 0,265×ATR Kosten < 0. Auslöser: Live-Trades mit Sofort-/Klein-Close (0-min-Trade +0,08 direkt am Level; 4 der 5 sr_closes desselben Tages waren dagegen RETTER — Exit NICHT anfassen). Umsetzung: wave._room_gate (in refresh_market VOR _confirm_breakout; set_entry_room, Grund „kein Raum: Widerstand X nur 0,4×ATR entfernt …"). Backtests via _build bleiben gate-frei (kein pb_levels-State). 0,6→1,0→0,6 (2026-07-24): kurz auf 1,0 erhöht (gemessen in BEIDEN Hälften besser, H1 +415/ H2 +266 R; Live-Kurzcheck: S/R-Closes seit 16.07. +925 € bei nur 28 % Klein-Closes) — aber der User empfand im gemischten Regime (M15/M30 ab, M5/H1 auf) zu viel WARTEN und stellte noch am selben Tag auf 0,6 zurück. ⚠ Lehr-Fall dabei festgehalten: „kein LONG trotz steigendem Kurs" lag NICHT am Raum-Gate, sondern am M30-Gegen-Trend-Filter (M30 ab → LONG unterdrückt, gewollt); das Raum-Gate mutete nur den daraus resultierenden SHORT (Support 0,09×ATR entfernt). 0,6 vs 1,0 ist reiner Frequenz-gegen-Edge-Kompromiss (gemessen 1,0 netto besser). ⚠ P(break)-Freigabe des Gates = VERWORFEN (gemessen backtest_entryroom_pbreak.py, 2026-07-24): Idee war, Raum<1,0-Signale durchzulassen, wenn P(break)@Entry hoch (Level bricht eh → Ertrag nicht gedeckelt). Das Modell TRENNT zwar auch am Entry (Freigabe-Gruppen klar besser als der Rest), aber JEDE Schwelle kippt zwischen den Hälften (p≥0,5: 0,016/+0,083 · p≥0,6: +0,089/0,006 · p≥0,7: +0,076/0,029) → nicht robust, reines Distanz-Gate bleibt. 13. verworfener Signal-Eingriff. (Nebenbefund: geblockte Gruppe in beiden Hälften klar negativ, 436/251 ΣR — bestätigt die 1,0-Erhöhung.)

  • Mindest-Konfidenz-Gate (_MIN_CONF=55): Signal < 55 % → WARTEN. Gemessen (backtest_conf.py): Band 4054 % = negativer Edge (0,025), ≥55 % = +0,058; Gate hebt Ø-Edge ~+50 % und den Gesamtertrag (entfernt die schwachen Setups, bei denen zu viele Strafen gestapelt sind — typisch der Lagging-Short am Boden).

  • ⚠⚠ DER 43-%-MASSSTAB WAR UNGÜLTIG — der richtige liegt bei ~80 % WARTEN (gemessen backtest_dist_gated.py, 2026-08-05). backtest_dist.py misst _build ALLEIN, ohne Breakout-Bestätigung und ohne Entry-Raum-Gate. Genau die beiden sind live die größten Blocker. Über 80k M5-Bars:

    gerichtet WARTEN
    nur _build (die alte Referenz) 77,9 % 22,1 %
    voller Live-Stack 19,5 % 80,5 %
    LIVE (seit 01.08., ohne stale) 7,2 % 92,8 %
    Aufschlüsselung im vollen Stack: entry_room 41,4 % · breakout_pending 17,0 % ·
    stretch 13,8 % · deadband 7,4 % · min_conf 0,9 %.
    Damit ist der „93 % statt 43 %"-Alarm entschärft, der den TF-Churn-Fix
    (2026-07-31) ausgelöst hat: verglichen wurden zwei verschiedene Systeme. Der reale
    Abstand zum gültigen Maßstab ist 12,3 Pp, nicht 50.
    Ein Rest bleibt (19,5 → 7,2 %) und ist NICHT wegerklärt. Kandidaten, die eine
    Bar-Simulation prinzipiell nicht abbildet: der TF-Wechsel (live wandert die
    Basis-TF M5…M30, die Sim ist fix M5), Cooldowns, EIA-/News-Fenster, und die
    Abtastung — live wird pro Sekunde gemessen, ein breakout_pending-Zustand
    hält über eine ganze Bar an und ist deshalb live überrepräsentiert (26,5 % gegen
    17,0 %). Der auffälligste Einzelposten ist min_conf: live 16,3 %, Sim 0,9 %
    das ist der nächste konkrete Ansatzpunkt, wenn jemand dem Rest nachgehen will.
    ⚠⚠ NACHGEPRÜFT AM 05.08. (User: „heute nur sehr wenige Empfehlungen")
    1.427 Zeilen geloggt (Soll ~1.440, die Erfassung selbst ist also intakt),
    5,0 % gerichtet. Auffällig ist nicht die Menge, sondern die **Verteilung
    über den Tag**: alle 71 gerichteten Empfehlungen fielen vor 10:00, danach
    16 Stunden lang genau eine.
    Block 05.08. live Backtest (voller Stack)
    --- --- ---
    entry_room 36,5 % 41,4 %
    breakout_pending 19,4 % 17,0 %
    min_conf 16,1 % 0,9 %
    htf_counter 7,8 % — (aus M5 aggregiert)
    stretch 6,7 % 13,8 %
    eia / stale 4,1 / 4,1 % — (nicht modelliert)
    gerichtet 5,0 % 19,5 %
    entry_room ist NICHT der Unterschied — es ist live sogar seltener als im
    Backtest. Die Lücke von 14,5 Pp wird von den live-only-Gates getragen, allen
    voran min_conf (+15,2 Pp), dazu htf_counter, EIA und stale. Zusammen sind
    das 31 Punkte, also mehr als genug, um die Differenz zu erklären.
    ⚠ Tageszeitlich dominierte nachmittags/abends entry_room (21:00: 59 von 60
    Minuten), ab 22:00 min_conf (100 %). Strukturelle Mitursache beim Raum-Gate:
    es nutzt die M5-Pivots, deren Median-Abstand mit 0,61×ATR praktisch auf
    der Gate-Schwelle von 0,60 liegt — es blockt damit rund die Hälfte der Zeit
    schon per Konstruktion (s. backtest_level_tf.py).
    TELEMETRIE-LÜCKE GEFUNDEN UND GESCHLOSSEN: min_conf-Zeilen schrieben
    conf_pct = 0 — die verworfene Konfidenz stand NUR als Fließtext in den Gründen
    und war nicht auswertbar. Damit liess sich beim zweitgrössten Blocker nicht
    sagen, ob die Signale knapp (5054) oder weit (2030) unter der Schwelle
    lagen. wave_rec._build gibt den Wert jetzt mit (reine Telemetrie, das Signal
    bleibt WARTEN). Erste Live-Zeile: conf 52 — drei Punkte zu wenig.
    Was das noch NICHT beantwortet: ob 55 die richtige Schwelle ist.
    backtest_conf.py hat das Band 4054 % als negativ gemessen (0,025 gegen
    +0,058), eine Senkung ist also nicht angezeigt. Die neue Zahl sagt nur, WIE VIEL
    Signalmenge unmittelbar hinter der Schwelle steht — nach ein paar Tagen
    auswertbar.
    ⚠ **Die im TF-Churn-Fix dokumentierte Erfolgskontrolle („WARTEN-Anteil Richtung
    43 % bewegen") ist damit hinfällig** und sollte nicht weiterverfolgt werden.
  • Signalhäufigkeit (backtest_dist.py): ~57 % der Zeit LONG/SHORT, ~43 % WARTEN. WARTEN-Treiber: M30-Gegen-Trend (~27 % der Bars, gewollt, Edge×2) + Überdehnung

    • Totband. _STRETCH_MAX 2,5→3,5 angehoben (backtest_stretch.py: +26 % Signale, Gesamtertrag steigt; ab 3,5×ATR kippt der Edge negativ).
  • ⚠ M30-FILTER NACH VOLA-REGIME GEPRÜFT = KEINE ÄNDERUNG (gemessen backtest_htf_vola.py, 2026-08-04). Anlass: Am 04.08. fiel WTI 4,12 $ (davon 4,16 $ in EINER Stunde), der Bot stand zu 93 % auf WARTEN, htf_counter blockte 160-mal — scheinbar genau die Richtung, in die der Markt lief. Hypothese: die nachlaufende M30-EMA sperrt in schnellen Umkehrungen das Richtige. Die Messung invertiert die Hypothese. Geblockte Signale, kanonischer Exit, Echtkosten, nach ATR-Terzil × 2 Halbjahre:

    ATR-Terzil geblockt H1 geblockt H2 durchgelassen H1 durchgelassen H2
    niedrig 0,086 +0,107 0,136 0,134
    mittel 0,172 +0,067 0,198 0,069
    hoch 0,625 0,329 0,061 +0,014
    Im HOHEN Terzil sind die geblockten Signale **mit Abstand die schlechtesten des
    gesamten Datensatzes** (PF 0,15 / 0,42) — der Filter ist dort am wertvollsten, nicht
    am schädlichsten. Die durchgelassenen Hoch-Vola-Signale in H2 sind das **einzige
    positive Feld** der ganzen Tabelle. In den unteren Terzilen kippen die geblockten
    Signale zwischen den Hälften = Rauschen. **Den Filter in schnellen Bewegungen zu
    lockern wäre die schlechtestmögliche Änderung gewesen. 22. verworfener Eingriff.**
    ⚠⚠ Auflösung des scheinbaren Widerspruchs — und eine Lehre über Populationen:
    Die 160 geblockten Signale und die 17 manuellen Gewinn-Trades desselben Tages sind
    verschiedene Grundgesamtheiten. Die manuellen Trades liefen ALLE auf
    rec=WARTEN, stammen also gar nicht aus der Signal-Logik — der Filter hat den User
    nie gebremst, er handelte unabhängig. „Das Gate blockte X" und „der Mensch verdiente
    Y" gehören nur dann zusammen, wenn Y aus X stammt. Die Ausgangsvermutung hat genau
    das verwechselt.
  • Higher-TF-Gegen-Trend-Filter: Signal wird verworfen (→WARTEN), wenn der übergeordnete M30-Trend (EMA12/50, Totband _HTF_DEADBAND=0,15×ATR) klar dagegen steht — kein Short im M30-Aufwärtstrend & umgekehrt. Nur für Basis-TF < M30 aktiv (_htf_sign, holt M30-Bars im selben mt5_lock). Per Backtest belegt: Ø-Edge ×2 (+0,012→+0,027), Treffer 52→54 %. Mess-Tool: python backtest_htf_filter.py M5 M30 4000 0.15.

  • Einstiegsqualität (nur Konfidenz/Score, NICHT Richtung): nach gemessenem Edge (backtest_pullback.py) gestaffelt — tiefer Pullback (Kurs durch die EMA zurück, near<0) = bestes CRV (+15, „ tiefer Pullback"); laue Zone direkt an der EMA (00,3×ATR) = schwächster Edge (3); Trend-Momentum (12×ATR) +8. Kein Spitzen-Gate: ein hartes „nur an Wellenspitze"-Filter senkte den Gesamtertrag (80 % Trades), darum verworfen. Konfidenz blockt NICHT das Ordern (nur WARTEN/Gegen-Signal tut das).

  • Multi-TF-Konfluenz (nur Konfidenz/Anzeige): M30 und H1 bestätigen die Richtung → +12, „ Konfluenz M30+H1"; H1 dagegen → 8. Per Backtest belegt: Ø-Edge ×3,5 (+0,016 nur-M30 → +0,056 M30+H1). _htf_sign holt zusätzlich H1-Bars. Mess-Tool: python backtest_improve.py 8000.

  • „Morgens fällt der Kurs, nachmittags steigt er" = WIDERLEGT (gemessen analyze_tageszeit.py, 2026-08-04, User-Beobachtung): ROHE Kursdrift je Berliner Stunde (nicht Signal-Edge wie backtest_hourly_split.py), 80k M5-Bars (17.06.2504.08.26), DST-korrekt via zoneinfo, 2 Hälften, Überschuss gegen den jeweiligen Tagesschnitt (sonst färbt das Regime alle Stunden ein: H1 fiel, H2 stieg). ALLE VIER Tagesblöcke kippen zwischen den Hälften — es gibt überhaupt kein Tageszeit-Muster (Zehntel-Cent je Bar):

    Block H1 H2
    Vormittag 0812 +0,06 0,43 kippt
    Nachmittag 1418 1,26 +1,96 kippt
    Abend 1923 0,39 +1,67 kippt
    Nacht 0007 +0,42 1,30 kippt
    Robust auf: 2/5/20/21/23 Uhr · robust ab: 14/18/19 Uhr — verstreute Einzelstunden
    aus 24 Tests, kein Vormittag/Nachmittag-Muster.
    Größenordnung macht es ohnehin unhandelbar: die stärkste belastbare Stunde
    (20:00, +1,9/+3,3) sammelt über eine volle Stunde ~0,020,04 $ — der Spread allein
    ist 0,0225 $. Selbst wenn die Drift echt wäre, ist sie kleiner als die Kosten.
    ⚠ Std 23 sticht mit +33,0 in H2 hervor, hat aber nur n=648 (22 Uhr: 1062, sonst
    ~1750) — das ist der Marktschluss-Rand, kein Befund.
    ⚠⚠ **ZWEI eigene Messfehler auf dem Weg dorthin — beide dokumentiert, weil sie
    Fehlerklassen sind, keine Ausrutscher:**
    (1) Selbst gebauter ATR-Filter (04.08.). Der erste Lauf meldete „Vormittag
    robust negativ" — Artefakt von atr < 0,06 übersprungen + ATR-Normierung. In den
    ruhigen Morgenstunden der älteren Periode liegt der ATR unter dem Floor → von
    1.740 Bars je Stunde blieben 138391 übrig, und zwar die ungewöhnlich
    volatilen Morgen. Gleiche Fehlerklasse wie das ORB-Selektions-Artefakt 2026-07-17.
    **Regel: ein Filter, der Bars nach einer Eigenschaft verwirft, die mit der
    untersuchten Achse korreliert, ERZEUGT den Befund.**
    (2) Zeitzone um 3 h verschoben (05.08., beim Squeeze-Nachgang aufgefallen).
    datetime.fromtimestamp(rates["time"], UTC+3) behandelt den Bar-Zeitstempel als
    echten UTC-Epoch — MT5 liefert dort aber bereits die Broker-Wallclock. Ergebnis:
    jede Stunde war 3 Stunden zu spät einsortiert; der zweite Lauf meldete deshalb
    „Nachmittag robust negativ", was in Wahrheit der Abend war. Richtig:
    fromtimestamp(ts, UTC).replace(tzinfo=UTC+3).astimezone(Berlin) (Helfer _berlin
    im Skript). ⚠ Der Rest des Projekts war NICHT betroffen (geprüft) — core/gaps.py
    & Co. nutzen die richtige Konvention; der Fehler steckte nur in diesem neuen Skript.
    **Plausibilitätstest, der ihn entlarvt hätte: die dünnen Stunden (n≈650/1060) müssen
    am Marktschluss liegen (22/23 Uhr), nicht mitten in der Nacht.**
  • Tageszeit-Gate = AUS geschaltet (User-Vorgabe 2026-07-22, [trading] dead_hours= leer — bewusst GEGEN die Messung). Jetzt config-steuerbar (wave.set_dead_hours, Komma-Liste von Stunden; leer=aus). Gemessener Default im Code = 0,1,2,3,4,5,6,7,12,16 (die ini überschreibt auf leer). ⚠ 12/16 waren zuletzt am 2026-07-21 (backtest_hourly_split.py) noch beidhälftig negativ — der User schaltet das Gate trotzdem ab. Reaktivieren = ini-Stunden eintragen. Der EIA-Blackout (Mi 15:3016:30) bleibt separat aktiv (eigenes Event-Gate, nicht Teil des dead_hours). Historie/ Messung (Gate war 1114 → 12+16 → aus → zurück → jetzt wieder aus): Nach der Echtkosten-Messung (backtest_realcosts.py, Kosten = Bar-Spread/ATR statt pauschal 0,1×ATR — Ø real 0,265!): Nacht 07 = Kostenfalle (konstanter Spread ÷ niedriger Nacht-ATR = 0,320,50×ATR, in beiden Hälften negativ; reine Arithmetik) + 12/16 Uhr 3× unabhängig negativ. Politik „07+12" verbessert BEIDE Hälften (H1 4038→−1870, H2 +100→+1191). Robust positiv nur 2122 Uhr. ⚠ Die „15 robusten Negativ-Stunden" NICHT blocken — in-sample-zirkulär (Overfit). breakout_k=0,3 auch unter Echtkosten bestätigt (H1 einziges positives k). Historie (Gate war 1114, dann 12+16, dann auf User-Wunsch aus, jetzt gezielt zurück):

    • Von 1114 auf 12+16 umgestellt (gemessen+verifiziert, backtest_hourly.py + backtest_hourly_split.py, 80k M5 in 2 Halbjahren): Netto-R je Stunde zeigt 12 Uhr (0,11/0,12) und 16 Uhr (0,17/0,10) in BEIDEN Hälften negativ; Std 13 kippt das Vorzeichen (0,048/+0,039 = Rauschen), 11/14 ~breakeven. Das alte Pauschal-Gate 1114 blockte harmlose Stunden und ließ die real schlechte 16 laufen. Gate 12+16 schlägt 1114 im Gesamt-Netto-R in beiden Zeiträumen (H1 1357→−1253, H2 +1562→+1799) und handelt dabei mehr Trades. Intraday-Edge generell dünn; Löwenanteil in US-Session 2122 Uhr (+0,25/+0,49).
    • Reversal-Ausnahme vom Gate = VERWORFEN (gemessen, backtest_deadhour.py): Reversal in der Dead-Hour nur +0,062×ATR brutto (vs +0,190 außerhalb) → nach Kosten (~0,1×ATR) negativ. Trade-①-Gewinner war Varianz. Gate gilt auch für Reversal.
  • EIA-Blackout (kausales Event-Gate, AKTIV): Mittwoch 15:3016:30 Berlin (Vorlauf der EIA-Lagerdaten 16:30 = 10:30 ET, Offset ganzjährig 6 h) → hartes WARTEN („EIA-Blackout …"). Gemessen (backtest_events.py, 2 Halbjahre): dieses Fenster in BEIDEN Hälften netto negativ (0,147/0,088 vs rest) — deckt sich mit dem alten „16 Uhr"-Befund. eia_post/api_pre/api_post NICHT robust → nicht geblockt. Nur im Live-Pfad (hour≠None), Backtests bleiben gate-frei. Best-Practice „Event-Blackout" statt Pauschal-Gate.

  • Wirtschaftskalender-Blackout (core/econ_calendar.py, 2026-07-30, [trading] news_blackout=true, ±15 min): Erweiterung der EIA-Idee auf ALLE High-Impact- Termine (FOMC/Zinsentscheid/NFP/CPI/GDP/PCE + alle Oil-/OPEC-Titel). Quelle Forex Factory JSON (nfs.faireconomy.media, kostenlos, kein Key, mit impact-Feld); geholt werden beide Wochen (thisweek+nextweek — „thisweek" endet samstags, der Markt öffnet aber Sonntag 23:00 → sonst blinder Fleck), 1×/h im Daemon-Thread (ein hängender HTTP-Call darf den Trend-Loop nicht blockieren), dedupliziert über (ts, title). Filter: impact=High + country=USD, ODER Titel enthält crude oil/opec/oil inventories/petroleum (der EIA-Report läuft bei FF teils als Low/Medium). ⚠ NICHT gemessen — Events sind zu selten für eine 2-Stichproben-Aussage, das ist die Handbuch-Regel („keine Trades 15 Min. vor High-Impact-News"), kein belegter Edge. Deshalb bewusst asymmetrisch verdrahtet: sperrt nur die AUTONOMEN Entries (_check_auto_squeeze + _check_auto_signal, jeweils OHNE Dedup-Marker → nach dem Fenster feuert das Setup, falls noch aktiv) und erscheint in der Entry-Checkliste nur als „warn", nicht „fail" (soll den Verdict nicht allein auf stop kippen); manuelle Orders bleiben frei. Der gemessene EIA-Blackout bleibt unabhängig davon aktiv — er greift auch, wenn der Kalender kein Netz hat (Fail-safe: ohne Daten → kein Blackout, Log 1×, dann still). Snapshot calendar {enabled, n, blackout{title,impact,ccy,minutes}, next, age_min}, Frontend #cal-note (Meldungen-Karte, bernstein: laufendes Fenster bzw. Vorwarnung ≤60 min). Abschalten news_blackout=false; Fenster via news_blackout_before_min/ _after_min. Mit 4 synthetischen Szenarien getestet (5 min vor/nach Event, 50 min davor = kein Blackout, disabled) + Live-Fetch verifiziert (EIA lag korrekt auf Mi 16:30 Berlin).

  • ⚠⚠ WINKEL-BESCHRIFTUNG WAR ÜBERALL INVERTIERT — Text korrigiert 2026-08-04, ZAHLEN unverändert. Beim Spiegeltest (User: „prüfe die Logik im umgekehrten Fall, fallender Kurs") kam heraus: calc_trend_angle liefert 0°=AUFWÄRTS … 180°=ABWÄRTS, also ist ad = angle90 negativ bei STEIGENDEM Kurs. Damit sagten die Texte das Gegenteil dessen, was passierte — ein LONG in einem steigenden Markt bekam „⚠ Winkel gegen EMA" (10), ein LONG in einem fallenden „Winkel bestätigt" (+5). ⚠ Und backtest_angle.py benutzt DIESELBE verdrehte Beschriftung (a5_dis = a5 < 90-dead für LONG heißt „dagegen", ist aber STEIGEND). Der dokumentierte Befund „Winkel dafür/neutral +0,055 vs dagegen +0,025" bedeutet richtig gelesen: LONG bei FALLENDEM Kurzfrist-Winkel (= Pullback) trägt doppelt so gut wie bei bereits steigendem. Das ist der im Projekt mehrfach belegte Pullback-Effekt (vgl. „ tiefer Pullback" und den RSI-Befund: bestätigende Oszillatoren sind schlechter). Konsequenz: die Gewichtung war von Anfang an NUMERISCH RICHTIG — nur hieß der Bonus „bestätigt", obwohl er den Gegen-Winkel belohnt. Nicht umdrehen. Geändert wurden ausschließlich die Reason-Texte („ Pullback (Kurzfrist-Winkel gegen den Trend)" / „kein Pullback — Kurs läuft dem Signal schon davon") und die Kommentare. Verifiziert: Konfidenzwerte vorher/nachher identisch (60/70/75 je Richtung). ⚠ Lehre: Der erste Verdacht lautete „systematisch invertierte Logik" — er war falsch. Die Zahlen stimmten, die Sprache log. Bei einer verdrehten Konvention muss man prüfen, ob die MESSUNG dieselbe Verdrehung teilt, bevor man den Code „repariert". (Beim Reversal war es anders: dort hat die eigenständige Messung das Setup unabhängig von der Beschriftung als negativ ausgewiesen.) Spiegeltest bestanden: Mit reversal_enabled=false ist das Verhalten über die ganze Überdehnungs-Spanne exakt symmetrisch (steigend/LONG ↔ fallend/SHORT): gleiche Signale, gleiche Konfidenz, gleicher Anti-Überdehnungs-Schnitt bei 3,5.

  • Winkel-vs-EMA-Warnung (nur Konfidenz): Regressions-Winkel der Basis-TF gegen die EMA-Richtung → 10 „⚠ Winkel gegen EMA (mögliche Wende)" (nachlaufende EMA!); dafür → +5. backtest_angle.py: dafür/neutral +0,055 vs dagegen +0,025; als Gate aber Gesamtertrag-negativ → bewusst nur Konfidenz/Warnung (Hilfe fürs manuelle Überspringen der Lagging-Shorts).

  • Höher-TF-Winkel-Filter (M30+H1-Winkel) = VERWORFEN (gemessen, backtest_htf_angle.py): Signal verwerfen, wenn M30/H1-Regressionswinkel gegen die Richtung steht → in JEDER Schwelle/Variante schlechter. Die entfernten Signale (Short bei aufwärts-Winkel) haben höheren Edge (+0,040,05) als die behaltenen (+0,02) — es ist der profitable Pullback-Setup. Filter kostet ⅔ des Gesamtertrags (+287→+70). Gegen-Winkel-Shorts NICHT filtern; einzelne große Verluste (z. B. 37 an einem H1-Auf-Tag) sind Varianz, Schutz über Notfall-Stop/ Sizing, nicht über Signal-Filter.

    • Auch als Konfidenz-STRAFE verworfen (backtest_confluence_angle.py): statt Hard-Filter den Konfluenz-Bonus streichen/Konf senken, wenn M30/H1-Winkel dagegen → borderline-Signale fallen unters 55-Gate. Ergebnis: in JEDER Variante schlechter2500…−3467 R); die entfernten Gegen-Winkel-Signale haben ØR +0,26…+0,32 (die BESTEN), die behaltenen +0,12. 3× gemessen (M5-Winkel, Höher-TF-Winkel-Filter, Konfluenz-Strafe): „nicht gegen den Winkel shorten" macht den Bot IMMER schlechter. Nicht mehr am Signal drehen — Edge ist klein & varianzlastig; der Hebel ist Sizing (risiko-basiert) + viele Trades, nicht der Filter.
    • H1-STÄRKE-Filter (nicht Winkel!) = VERWORFEN (gemessen backtest_short_h1filter.py, 2 Halbjahre, 2026-07-15): Motiviert durch Live-Befund „SHORT-Empfehlungen 31 % WR in Aufwärts-Regime" (LONG 62 %, SHORT 31 %, letzte 40 MIT-Trades). Idee: Signal verwerfen, wenn es gegen einen starken H1-Trend (|H1 EMA12EMA50| > thr×ATR_H1) läuft. Fällt durch: die weggefilterten Signale flippen das Vorzeichen zwischen den Hälften — thr 0,5: ØR H1 0,198 / H2 +0,001; thr 1,0: 0,069 / +0,013; thr 1,5: +0,029 / 0,063. In KEINER Schwelle robust negativ in BEIDEN → Filter hätte in H2 (positive/breakeven drops) Geld gekostet. 7× belegt: die 31 %-SHORT- Schwäche ist REGIME (aktueller Uptrend), kein Signalfehler — in H2 tragen dieselben Gegen-H1-Shorts. Kein Filter; der Hebel bleibt Sizing + Notfall-Stop + Diskretion (User-WARTEN-Übersteuerungen liefen 68 % WR / +259 €).
    • ER-Chop-Gate = VERWORFEN (gemessen, backtest_chopgate.py, 2 Halbjahre): Signal→WARTEN wenn Efficiency Ratio der letzten N Bars < X. Chop-Signale sind NICHT robust schlechter — Effekt kippt: H1 Trend leicht besser (Ø-R +0,070 vs +0,053), H2 umgekehrt (Chop +0,138 vs +0,129). Gate wirft zudem ~70 % (profitables) Volumen weg (ΣR +2299→+836). Das Totband filtert Extrem-Chop schon (ER-0,02-Tag = 71 % WARTEN); Rest-Signale = Varianz. 5× belegt: Signal-Filter tragen nicht.
  • Vola-Abzug (nur Konfidenz): hohes ATR (oberes Terzil, _ATR_HIGH≈0,27) = schwächster/negativer Edge → 8 „hohe Vola". Schwelle regime-abhängig → kein Gate.

  • Reversal-Lockout/-Persistenz = VERWORFEN (gemessen, backtest_revlock.py): Gegen-Signale kurz nach einem Reversal sind sogar besser als normale (+0,072 vs +0,018 bei N≤6) → Reversal-Signale NICHT sperren/halten. Einzelne Verluste (Bottom-Short nach REV) sind Varianz, kein Muster.

  • Rec-Logging: engine._run_analysishistory.log_recommendation (Throttle intern) füllt die recommendations-Tabelle (Signal/Score/Conf/Winkel/Setup) für Tages-Reviews (analyze_today.py rekonstruiert sonst aus M5-Bars).

  • Reversal-/BOUNCE-Einstieg (antizyklisch, markiertes ZWEITsignal): überdehnt (|stretch|≥_REVERSAL_STRETCH=3,0, entkoppelt von Anti-Überdehnung _STRETCH_MAX=3,5) UND Regressions-Winkel hat gedreht → Einstieg in Winkel-Richtung, gegen die EMA (überverkauft + Winkel auf → LONG; überkauft + Winkel ab → SHORT). Setup WAVE_REV_*, Grund „🔄 Reversal"; hebt Anti-Überdehnung und M30-Filter bewusst auf. Schwelle 3,5→3,0 gesenkt (User wollte mehr Bounces; backtest_bounce.py): Ø-R +0,185, PF 1,35, Treffer 70 %, Worst 2×ATR (SL-gedeckelt), ~1,5× mehr Bounces als 3,5. ⚠⚠ WINKEL-BEDINGUNG LÄUFT GEGEN IHRE EIGENE DOKUMENTATION (gefunden 2026-08-04, User-Frage „warum SHORT im starken M5-Aufwärtstrend?"). calc_trend_angle liefert laut Docstring 0° = starker AUFWÄRTStrend, 90° = flach, 180° = ABWÄRTStrend — also invertiert. Damit heißt ad = angle90: ad < 0 = STEIGEND, ad > 0 = fallend. Der Code triggert aber stretch >= +3.0 and ad <= -2 → SHORT, kommentiert als „überkauft + Winkel ab". Tatsächlich feuert er bei überkauft UND WEITER STEIGENDEM Winkel — also GEGEN den Winkel, während CLAUDE.md „Einstieg IN Winkel-Richtung" beschreibt. Dasselbe Vorzeichen steckt im Konfidenz-Block (Zeile ~790): ein SHORT bekommt „Winkel bestätigt" (+5), während der Kurs steigt. Mit synthetischen Reihen direkt nachgestellt: überkauft + steigend → WAVE_REV_SHORT, Grund „Winkel gedreht" + „Winkel bestätigt". Erklärt zwei dokumentierte Befunde: die ~50 % Richtungstreffer der Bounce-Anzeige (backtest_bounce_reliability.py) und „blutet in starken Trends". ⚠⚠ GEMESSEN (backtest_reversal_angle.py, 80k M5, kanonischer Exit, Echtkosten, 2 Halbjahre) — ALLE drei Varianten fallen durch:

    Variante H1 ØR H2 ØR PF n
    IST (Live) überkauft + Winkel steigt → SHORT 0,122 0,031 0,79/0,94 1100/1615
    KORRIGIERT (Doku-Absicht) Winkel fällt → SHORT 0,173 0,061 0,68/0,89 106/163
    KONTROLLE ohne Winkelbedingung 0,099 0,023 0,82/0,96 1153/1693
    Drei Schlüsse: (a) Die Vorzeichenkorrektur ist SCHLECHTER — gut, dass sie nicht
    blind eingebaut wurde; sie feuert zudem kaum (106 statt 1100 Trades, weil bei 3×ATR
    Überdehnung die 14-Bar-Regression fast immer noch steigt). (b) **Die Kontrolle ohne
    Winkel ist die BESTE der drei** — die Winkelbedingung entfernt Trades, die im Schnitt
    besser waren als die behaltenen; sie trägt mit KEINEM Vorzeichen etwas bei.
    (c) Der Trigger ist in jeder Variante beidhälftig negativ.
    ⚠ Gemessen wurde der ROHE Trigger (ohne Konfidenz-/Raum-Gate); der Legacy-Recheck der
    gegateten Variante kam auf ~Breakeven (PF 0,93/1,06) — dieselbe Richtung. **Keine
    Messung stützt die dokumentierten „+0,185 / PF 1,35" mehr.**
    ABGESCHALTET (User-Entscheidung 2026-08-04, [trading] reversal_enabled=false).
    Ausschlag gab nicht nur die negative Zahl, sondern dass das Setup **zwei gemessen
    POSITIVE Schutzmechanismen aushebelt** (Anti-Überdehnung UND den M30-Gegen-Trend-
    Filter, Edge ×2) — ein Setup ohne eigenen Edge, das validierte Filter umgeht, ist der
    ungünstigste Fall.
    Wichtig für Backtests: der Modul-Default in wave_rec bleibt True. Sämtliche
    Backtests rufen dasselbe _build; ein False-Default hätte alle bestehenden Messungen
    still verändert. Abgeschaltet wird ausschließlich im Live-Pfad
    (enginewave.set_reversal_enabled(...), Config-Default dort false).
    Verhaltensänderung nur im Band ** stretch 3,03,5**: dort kam vorher ein
    antizyklisches REV-Signal, jetzt das normale Trendsignal (und ab 3,5 greift wie
    gehabt die Anti-Überdehnung). Mit 3 Szenarien getestet — überkauft+steigend:
    SHORT/WAVE_REV_SHORTLONG/WAVE_LONG; nicht-überdehnter Trend unverändert.
    Live verifiziert: bei 3,18×ATR Überdehnung jetzt `WARTEN · setup=WAVE ·
    reversal=None` statt REV_SHORT; Log „Reversal-Setup AUS".
    Überwacht via measurement_reminder.py (CONFIG_DEPS, 11 Werte).
    Zurückholen: reversal_enabled = true. Die Bounce-Anzeige (wave.bounce,
    Kachel „Überdehnung") bleibt unverändert — sie war ohnehin nur Warn-Kontext, kein
    Trade-Trigger.
    ⚠⚠ DIESE ZAHLEN GELTEN NICHT MEHR (Legacy-Recheck 2026-07-31, hier eingetragen
    am 2026-08-02): backtest_bounce.py nutzt den vereinfachten Exit OHNE Kosten
    (LEGACY_SIMPLE: kein Breakeven, kein Lock, kein Time-Stop). Mit dem echten
    Exit + Echtkosten liegt das Setup bei ~Breakeven (PF 0,93/1,06) — es ist
    kein +0,185-Ertragsbringer. Was HÄLT, ist die relative Aussage: Reversal
    schlägt Trend in beiden Hälften (+0,140/+0,102). Deshalb bleibt es ein sinnvolles
    nachrangiges, markiertes Zweitsignal (so ist es live auch eingebaut), aber
    nicht mehr als das. Details im Abschnitt „LEGACY-RECHECK".
    Weiterhin gültig: schwächer als Trend & regime-anfällig (trägt in
    Seitwärts/Mean-Reversion, blutet in starken Trends). Nachrangiges, markiertes Setup. Bounce-Status — Multi-TF im Wave-
    Snapshot (wave.bounce = {state, dir, tf}): scannt M1/M5/M15/M30 (_bounce_one,
    Aggregat in refresh_market, ~15 s-Throttle via TF-Ampel-Fetch), nimmt den
    stärksten (aktiv>erwartet, dann größte Überdehnung). expected = überdehnt
    (≥_REVERSAL_STRETCH), Winkel noch NICHT gedreht · active = + Winkel gedreht.
    dir LONG (überverkauft) / SHORT (überkauft), tf = Quelle. Frontend #wave-bounce
    als „Überdehnung"-Warn-Gauge (NICHT „Bounce/Signal"): grau „keine" · bernstein
    „⚠ überdehnt … Reaktion nach oben ↑ / nach unten ↓ möglich" · kräftiger bei
    active (+ TF). **Kein
    grün/rot** (kein Trade-Trigger). **Reversal-Konfidenz-Boni = GEPRÜFT & BEHALTEN
    (gemessen backtest_revconf.py, 2 Halbjahre, 2026-07-15):** Befund war, dass
    „starker Trend" (+10) und „ tiefer Pullback" (+15) beim Reversal semantisch
    GEGEN das Signal stehen und Reversals so leichter durchs 55er-Gate kommen.
    Korrektur (Boni beim REV streichen) fällt durch: die dadurch rausfallenden
    Reversals kippen das Vorzeichen — H1 ØR 0,323 (127 St., Fix ok), H2 ØR
    +0,109/PF 1,16 (1608 St., Fix hätte die BESSEREN gekappt, WR 45 %). Nicht
    robust → Konfidenz-Berechnung bleibt. 8. verworfener Signal-Eingriff. Die
    hohe REV-Conf-Anzeige (~7580 %) ist also kein Gate-Fehler; wer will, liest die
    Reason-Liste („Reversal" + „hohe Vola") als Risiko-Kontext. **Gemessen (backtest_bounce_reliability.py):
    Richtungs-Trefferquote der „aktiv"-Anzeige ~50 % (Münzwurf), Ø-Vorlauf ~0×ATR über
    alle TF & H1-Regime → reiner Überdehnungs-Kontext, KEIN Signal.** (Die früheren
    „70 %" aus backtest_bounce.py waren exit-getrieben, nicht Richtungs-Vorhersage.)
  • ⚠⚠ TF-CHURN-FIX (2026-07-31, zwei Eingriffe) — Ursache für „93 % WARTEN statt 43 %": Auslöser war die User-Frage „warum hat der Bot die Treppe nicht erkannt und long eröffnet?" zur Rally am 31.07. (82,5 → 84,4 in 2 h). Befund: in dem Fenster gab es 99× WARTEN und 1× SHORT — und dieser SHORT wurde am Tief autonom eröffnet (AUTOSIG, 26,31 €, per adverse15 geschlossen); den LONG machte der User um 11:56 von Hand gegen die Empfehlung. Der WARTEN-Anteil live ist strukturell zu hoch:

    WARTEN
    Backtest-Erwartung (backtest_dist.py) ~43 %
    Live letzte 24 h 93,2 %
    Live letzte 7 Tage 88,0 %
    Live gesamt (93.599 Zeilen) 78,6 %
    MECHANISMUS (im Log belegt): _choose_tf setzt den Score einer TF **hart auf
    0,0**, sobald sie überdehnt ist (`raw = sep if (sep>=deadband and stretch<=_STRETCH_MAX)
    else 0.0`) — genau das passiert M5 IM Trend. Real am 31.07.: M5 sprang zwischen
    0,00 und 1,93, die TF wechselte 33× am Tag, Median-Abstand 5 min. Die
    vorhandene 1,2×-Hysterese ist dagegen wirkungslos (gegen 0,0 kommt jeder Wert durch).
    Und set_timeframe() verwarf bei JEDEM Wechsel die laufende Breakout-Bestätigung
    (_pend = None) und verankerte sie beim AKTUELLEN Kurs neu. Bei breakout_k=0,3 und
    ATR_M30≈0,53 braucht sie ~0,16 , der Kurs lief ~0,08 je 5 min → **die Bestätigung
    konnte rechnerisch nie fertig werden.** Dazu: landet die Heuristik im Trend auf M30,
    braucht das EMA-Kreuz dort Stunden — eine 2-h-Bewegung wird nie zum Signal.
    Gleicher Fehlertyp wie beim P(break)-Modell am selben Tag: backtest_breakout.py
    hat k=0,3 auf einer FESTEN Zeitebene validiert; live wandert sie — die Bedingung,
    unter der die Messung gilt, existiert im Betrieb nicht.
    FIX 1 (wave_rec.set_timeframe): _pend wird NICHT mehr zurückgesetzt. Der Anker
    gehört zum Signal, nicht zur Zeitebene; dreht die Richtung, verankert
    _confirm_breakout ohnehin neu (p.get("dir") != d). Das Level behält den ATR der
    alten TF — bewusst, eine Neuberechnung wäre wieder ein Reset.
    FIX 2 (engine._tf_loop): Mindest-Verweildauer [trading] tf_min_dwell_s=900
    (0 = aus). Gibt einer TF Zeit, ihr Signal überhaupt fertig zu bestätigen.
    Verifiziert mit synthetischen Szenarien: Fix 1 — Bestätigung überlebt den Wechsel
    M5→M30 und feuert LONG, Richtungswechsel verankert korrekt neu; Fix 2 — an der ECHTEN
    Score-Folge vom 31.07. nachgespielt: 4 Wechsel → 2.
    Ehrlich zur Beweislage: das ist NICHT backtestbar — Backtests laufen
    grundsätzlich auf einer festen TF, das Churning existiert dort per Konstruktion nicht.
    Begründung ist also nicht „gemessen besser", sondern „stellt die Bedingung her, unter
    der die vorhandene Messung gilt". Erfolgskontrolle = der WARTEN-Anteil: er muss
    sich Richtung der gemessenen ~43 % bewegen (Abfrage s. Tabelle oben).
    Nicht behoben (bewusst): der 0,0-Einbruch des Scores bei Überdehnung selbst —
    das wäre eine Änderung der TF-Bewertung und damit messpflichtig. Fix 2 halbiert das
    Churning, verhindert aber nicht den ersten Absturz auf M30.
  • Timeframe: [trading] tf_select=heuristicengine._choose_tf wählt jede Minute die TF mit klarstem Trend aus dem Band [tf_min … tf_max] (leichter Höher-TF-Bias). Einstellung: tf_min=M5, tf_max=M30 (M1 raus: gemessen ~0 Edge; M30 zugelassen, damit der Bot in einem M30-Trend nicht festhängt, wenn M5/M15 gegen-Bouncen — sonst Patt: Long geblockt, Short fehlt). H1 bleibt nur Kontext (Filter/Konfluenz). Start-TF wird ins Band geklemmt. agent = LLM wählt; M1..H1 = fest. Der Agent steuert die TF nicht mehr.

  • Positionsgröße = margin-basiert 95 % (User-Vorgabe 2026-07-28, risk_pct=0, margin_buffer_pct=95): calc_lots setzt die Lots auf ~95 % der freien Margin. Verlauf 90→95→75→(1,5 % risiko, wenige Stunden am 2026-07-20)→80 %→95 % margin (2026-07-28) — der User hat die Margin-Wahl damit mehrfach bewusst getroffen (nicht ungefragt umstellen). ⚠ 95 % ist noch aggressiver als die 80 %-Referenz unten. ⚠ Aggressiv: der 95,48-€-Nacht-Trade 2026-07-20 (News-ATR → SL ~1,0 $ breit × 1,03 Margin-Lots ≈ 11 % Konto) ist der Referenzfall; mit risk_pct=1.5 wären es ~0,15 Lots / ~14 € gewesen (backtest_sizing.py: Margin-Sizing bis 36 % Quasi-Ruin, 12 % = 0 %). Risiko-Modus bleibt gebaut & abgesichert (Guards s. u.), Umschalten = risk_pct=1.5. Mechanik: trader._send_locked bestimmt erst den SL; bei risk_pct>0 → risiko-basiert (calc_lots_risk, Verlust ≈ risk_pct % Equity, Margin als Deckel) = der gemessene Fix gegen die Verlust-Asymmetrie. Umschalten via [trading] risk_pct (z. B. 1.5 = überlebbar).Risiko-Modus-Guards (Fix 2026-07-19): calc_lots_risk rundet NICHT mehr auf volume_min auf (unter Mindestlot → 0.0 statt still mehr Risiko), und _send_locked fällt bei risk_pct>0 NICHT mehr still auf Margin-Sizing zurück (75 % wäre ein Vielfaches des gewollten Risikos) — der Trade wird stattdessen mit Fehlermeldung abgelehnt. Bei risk_pct=0 (aktuell) unverändert margin-basiert. Harter Broker-SL immer mitgeschickt; adoptierte Fremd-Trades bekommen Schutz-SL.

    • Sizing-Simulation (backtest_sizing.py): 90 % Margin = Wachstums-optimal, ABER 36 % P(DD>80 %) = Ruin-Risiko. Risiko 12 %/Trade = überlebbar (0 % Quasi-Ruin). Exit-Asymmetrie gemessen backtest_exit2.py (Live-Exit Trail 1,5 ist OK; Problem war Sizing + manuelles Gewinner-Kappen, NICHT die Exit-Logik).
  • SL: Band 1,82,2×ATR (Ziel 2,0; INIT_SL_MIN/MAX_ATR). Breakeven ab +1,3×ATR (_BREAKEVEN_ATR; war 0,6 → 21 % Breakeven-Scratches, gemessen backtest_exit.py be: 1,3 = Plateau-Optimum → Scratch 5 %, Ø-R +15 %, kein Tail-Risiko). Früh-Ausstieg = AUS (_ADVERSE_EXIT_ATR=0). Trailing Init/Trail/Lock. Teil-Exit AUS (_PARTIAL_TP_FRAC=0, User-Vorgabe) → am TP wird der komplette Trade geschlossen (kein 50 %-Runner mehr).

    • Früh-Ausstieg NUR bei Trades OHNE Signal-Deckung = VERWORFEN (gemessen backtest_adverse_nosignal.py, 2026-07-23, Auslöser 57,34-€-Trade: manueller LONG gegen WARTEN, KEIN Squeeze/Signal-Flip beteiligt): Hypothese war, dass ein Adverse-Exit bei signal-ungedeckten Trades (Totband/WARTEN-Zone, B0 zeigt live 39 % WR dort) helfen könnte, auch wenn er pauschal auf ALLE Trades schadet. Fällt durch — bei JEDER Schwelle (0,5/0,75/1,0/1,5×ATR), in BEIDEN Populationen (SIG/ NOSIG), fast durchgehend in BEIDEN Hälften negativ (einzige Ausnahme: SIG H1 X=1,5 knapp +11 R, aber H2 dort 46). WR bricht von ~3843 % auf 1826 % ein — der Früh-Ausstieg kappt zwar einige Trades vor dem vollen SL, schneidet aber weit öfter Trades ab, die sich erholt hätten; NOSIG-H2 war ohne Eingriff sogar leicht profitabel (ΣR +80, PF 1,15) und wird durch jede Adverse-Schwelle verschlechtert. Hypothese „schwächere Population profitiert eher" widerlegt. Keine Änderung.
    • ⚠⚠ TRAIL × BREAKEVEN GEMEINSAM GEPRÜFT = KEINE ÄNDERUNG — und ein METHODEN-BEFUND, der einen alten Schluss entwertet (2026-08-05, backtest_trail_be.py + analyze_trail_resolution.py). Anlass: User-Frage zur Kalibrierung „jetzt bei 3 Lot". ⚠ Zur Prämisse: die Positionsgröße ändert das Trail-Optimum NICHT. R ist ATR-normiert; Lots skalieren nur das EUR-Ergebnis. Gemessen wurde deshalb die echte offene Frage: be=1,3 stammt aus backtest_exit.py, als der Trail noch 1,5 war; mult wurde am 31.07. auf 1,0 gesenkt, OHNE das Breakeven neu zu prüfen. 2D-Sweep über beide Populationen (2.365 Squeeze- / 1.566 Wellen-Entries), feste Entry-Listen, kanonischer Exit, Echtkosten, 2 Halbjahre. (1) Das Breakeven ist praktisch wirkungslos. Bei mult ≤ 0,6 sind ALLE be-Zeilen identisch — der enge Trail erreicht den Einstand ohnehin früher als der Breakeven-Boden. Erst ab mult 0,8 wirkt er überhaupt, und dort ist be=0,6 marginal besser (+0,019 bzw. +0,018 ØR). ⚠ Das liegt unter einem Standardfehler (≈0,026 bei n≈1.500) → kein Handlungsbedarf, be bleibt 1,3. (2) Der Trail ist monoton besser, je enger — bis 0,05×ATR. Und genau das ist das Problem: ein Trail von 0,05×ATR (≈3 Cent) wäre vom Spread allein ausgelöst. Ein Optimum, das bis an die physikalische Unmöglichkeit monoton bleibt, misst nicht den Markt, sondern die Methode. ⚠⚠ AUFLÖSUNGS-ARTEFAKT NACHGEWIESEN. simulate() prüft den Stop gegen Bar-Hoch/-Tief. Ein Trail, der ENGER ist als die typische Intrabar-Spanne, ist nicht auflösbar — das Modell nimmt den Bar-Hochpunkt als Gewinn mit und steigt erst im NÄCHSTEN Bar aus. Test mit in DOLLAR fixiertem Trail auf M5 gegen M1 (597 gemeinsame Einstiege):
      Trail M5 ØR M1 ØR Differenz
      0,10×ATR +0,093 +0,066 0,027
      0,20×ATR +0,049 +0,039 0,010
      0,30×ATR +0,013 +0,016 +0,003
      1,00×ATR 0,046 0,010 +0,036
      1,50×ATR 0,038 +0,028 +0,066
      Das Vorzeichen kippt bei ~0,3×ATR: die gröbere Auflösung **überschätzt enge
      Trails und unterschätzt weite**. Eine Tick-Simulation würde denselben Weg noch
      weiter gehen. ⚠ **Damit ist der Befund vom 31.07. („enger ist monoton besser",
      backtest_trailmult.py) teilweise entwertet** — er ruht auf derselben
      M5-Auflösung. Die damalige Entscheidung, NICHT auf 0,5 zu gehen, war trotzdem
      richtig, nur aus dem falschen Grund (genannt wurde „Randwert + Slippage";
      der eigentliche Grund ist die Auflösungsgrenze).
      ⚠ Nebenbefund gegen meine eigene Slippage-Vermutung: die Trade-ZAHL ist über alle
      Multiplikatoren fast konstant (2.108 bei 0,3 gegen 2.083 bei 1,0), nur die
      Haltedauer sinkt (12 → 18 min) und der Worst-Case ist überall identisch (2,44,
      der SL deckelt). Die Slippage-Exposition steigt also NICHT dramatisch — das
      Auflösungs-Argument trägt, das Slippage-Argument kaum.
      Ergebnis: Trail 1,0 / Breakeven 1,3 bleiben. 1,0×ATR entspricht rund einer
      M5-Bar-Spanne (die ATR IST die mittlere Bar-Spanne) und ist damit ungefähr die
      feinste Einstellung, die diese Simulation ehrlich auflösen kann. Wer enger will,
      braucht Tick-Daten — nicht mehr Bars.
  • Trailing-Optimierung geprüft = KEINE Änderung (gemessen, backtest_trailing.py, 2 Halbjahre, echte Phasen-Sim): Die Live-Config (_TRAIL_START_ATR=0,3 · mult 1,5 M5 · Breakeven 1,3) hat die beste Verlustseite (bestes H1, Worst nur 1,5×ATR). Trailing trägt klar: ohne Trailing ΣR 1313/Worst 2,0 vs. Live +1777/ Worst 1,5 (~+3000 R und kleinerer Einzelverlust). Breiter ziehen (mult 2,02,5) oder Trail später starten (0,3→1,0) macht H1 schlechter/Worst größer; der frühe Trail-Start ist netto schützend (holt „läuft-schief"-Trades billiger raus). Einzige mithaltende Variante (start0,6) kippte zwischen den Läufen = Rauschen. → Ausschütteln ist Varianz, nicht Logikfehler.

    • Time-Stop (AKTIV, gemessen backtest_timestop.py): Position wird geschlossen, wenn sie nach 120 min noch in Phase „Init" hängt (HW-Profit nie ≥0,3×ATR = Whipsaw-Opfer; trailing._TIMESTOP_MIN=120, 0=aus, closed_by=timestop). Verbessert BEIDE Hälften (H1 +98→+162, H2 +2191→+2298 R), Worst unverändert; kürzere Fenster (30/60 min) kippten zwischen den Hälften = Rauschen. Dank Phasen-Ratsche ist „Init nach 2 h" exakt „nie Trail-Start erreicht".
    • ATR-Floor 0,12→0,06 gesenkt (gemessen, backtest_atrfloor.py, 2 Halbjahre): Die reale Vola fiel unter den alten Floor (H1: 81 % der Signale ATR<0,12 → Floor band dauernd, Exit-Distanzen künstlich ~1,31,6× breit, dazu ~35k Log-Warnungen je 5-MB-Rotation). Floor 0,06 in beiden Hälften besser (H1 153→−27 Pts, H2 +512→+554); ganz ohne Floor nur marginal besser → 0,06 als Schutz behalten. Floor-Warnung jetzt gedrosselt (max. 1×/5 min).
    • Befund B — umgesetzt (Exit-Simulation backtest_exit.py, Doku docs/exit-simulation.md): altes Band 1,21,5 war zu eng (MAE ~1,7×ATR, 3137 % Frühstopps). 2,0×ATR hebt Ø-R +32 %, PF 1,33→1,39, Frühstopps →25 %, Worst-Case auf 2,0×ATR gedeckelt. Breiter (34×ATR) wäre im Schnitt noch besser, aber Tail-/Drawdown-Risiko → verworfen. ⚠ Da Sizing margin-basiert (90 %, nicht risiko-basiert), schlägt die SL-Breite voll auf den EUR-Verlust durch. Qualität-abhängiger SL NICHT belegt (MAE über alle Buckets gleich).
    • S/R-Close (am Level schließen) = pauschal 2× VERWORFEN, als GEMESSENES Segment aber AKTIV: Pauschal (backtest_srclose.py, Echtkosten, 79k Signale) verdoppelt Close@Level die WR (35→67 %), verschlechtert aber PF/ΣR in BEIDEN Hälften (0,78→0,67 · 0,88→0,76) = Gewinner-Kappen. ABER (backtest_srbreak.py, 47k Touches, User-Idee „P(Durchbruch)>60 % → laufen lassen"): Basisrate P(break)=3637 %; das Anlauf-Momentum trennt robust in beiden Hälften — Kurs KRIECHT ans Level (letzte 6 Bars <0,3×ATR) → P(break) nur 26 %, Close schlägt Laufenlassen Ø +0,12 R; mit Schwung (≥0,3) → laufen lassen (4143 %). Volumen/Touch-Zahl trennen NICHT robust.
    • Kalibriertes P(Durchbruch)-Modell (2026-07-13, analyze_srwaves.py + backtest_srclose_prob.py): Containment-Befund — ~68 % der Wellen erreichen das gegenüberliegende Level, davon brechen nur ~37 % durch → ~63 % prallen ab (robust beide Hälften). P(break) ist vorhersagbar & kalibriert: Logit auf 4 Merkmalen (Anlauf-Momentum 6/3 Bars ·d, mit-Trend, Level-Distanz), auf H1 trainiert, auf H2 AUC 0,71 + Kalibrierung trifft (pred 17→21 %, 49→50 %, 69→76 %). Dominant = 3-Bar-Beschleunigung (_PB_W mom3 +0,78). R-Ertrag (Echtkosten): Regel „close wenn P(break) < X" schlägt reines Trailing in BEIDEN Hälften massiv (+6000/+7600 R); User-Schwelle 0,60 im Optimum-Plateau (0,500,60 ~gleich), validiert. ⚠ Impliziert Strategiewechsel: ~90 % der Level-Touches werden geschlossen (Trendfolger→Level-Scalper) — trägt, weil echte Kosten (0,265× ATR) + 63 % Containment das Laufenlassen teuer machen.
    • S/R-Close SIGNAL-gegated („bei mit-Trend halten statt schließen") = VERWORFEN (gemessen backtest_srclose_signal.py, 2026-07-22, User-Idee): Vorschlag war, den S/R-Close zu unterdrücken, solange die Empfehlung noch in Trade-Richtung zeigt (halten bis WARTEN). Fällt klar durch — in BEIDEN Hälften ~5300…−5700 R schlechter als der reine P(break)-Close. Grund/Datenbeleg: ~85 % ALLER Level- Touches passieren, WÄHREND die (nachlaufende) EMA noch „in Richtung" zeigt (23,8k/20,1k von 27,8k/26,2k Touches) — genau dort HÄLT das Level aber. Diese Touches geschlossen = ΣR +11703/+13606, geHALTEN = nur +5978/+8347. Die Trend-Empfehlung lagt → sie „bestätigt" ausgerechnet am Abpraller. Das kalibrierte P(break)-Modell (hat „mit-Trend" als Merkmal) macht das schon richtig; es mit dem groben EMA-Signal zu übersteuern kostet ~⅔ des S/R-Close-Edges. 12. verworfener Signal-Eingriff.
    • ⚠⚠ AUTO-FLIP-CLOSE = TROTZ NEGATIVER MESSUNG GEBAUT (User-Vorgabe 2026-07-31 „schließe den Trade automatisch beim Close-Signal"), [trading] auto_flip_close=true, auto_flip_close_min_r=0.5. Dreht die Empfehlung GEGEN die offene Position und steht sie ≥0,5×ATR im Plus → trader.close(reason="flip_close"). NEU GEMESSEN mit dem ECHTEN Exit (backtest_flipclose2.py, 80k M5, 2 Halbjahre, Echtkosten, Exit aus core/exit_model.py) — bestätigt das alte Urteil, deutlicher:
      Variante Δ H1 Δ H2
      Flip ab 0,0×ATR 126 221
      Flip ab 0,3×ATR 77 216
      Flip ab 0,5×ATR 92 160 ← mildeste, deshalb Default
      Flip auch im Minus 205 364
      Basis ohne Flip: H1 ΣR 537 · H2 140. **JEDE Variante ist in BEIDEN Hälften
      schlechter.** Mechanik: Trefferquote steigt 39 → 45 %, Ertrag fällt =
      Gewinner-Kappen (die nachlaufende EMA dreht oft mitten im Pullback).
      Warum überhaupt neu gemessen: das alte backtest_flipclose.py nutzte ein
      VIERTES Exit-Modell (Trail fest 1,5 · KEINE Lock-Phase · KEIN Time-Stop ·
      KEIN Initial-TP · _MAXH 288 · Phasen über den Bar-Close statt über das
      High-Water) — auf dieser Grundlage wäre das Urteil für den Live-Betrieb nicht
      bindend gewesen. Für die Neumessung bekam exit_model.simulate() einen
      stop_when-Hook (+ ret_bar), damit die Phasen-Mechanik NICHT wieder kopiert
      werden musste; Rückwärtskompatibilität verifiziert (ohne Hook bitgenau identisch).
      **NACHRANG gegenüber dem S/R-Close (gemessen backtest_exit_combo.py,
      2026-07-31, auto_flip_close_subordinate=true):** Beide Exits konkurrieren um
      dieselben Trades — wer zuerst feuert, gewinnt. Die S/R-Freigabe lief OHNE
      Flip-Close, die Baseline existierte also nach dem Einbau nicht mehr. Gemessen auf
      IDENTISCHEN Entries (5 Varianten, 2 Halbjahre, Echtkosten, Exit-Kern):
      Variante Δ H1 Δ H2
      --- --- ---
      nur S/R-Close +59 14
      nur Flip-Close 1 43
      BEIDE +56 50
      BEIDE, Flip nachrangig +62 49
      Kollision belegt: der Flip nahm dem S/R-Close 28 bzw. 32 Trades weg (511 %) —
      genau die, für die dieser validiert ist. Mit Nachrang ist es in BEIDEN Hälften
      besser als ohne. Umsetzung: liegt ein S/R-ZIEL ≤0,6×ATR entfernt (dieselbe
      Schwelle wie der Live-Hinweis), hält der Flip sich zurück. Mit 4 Szenarien
      getestet (in/außer Reichweite · kein Level · Nachrang aus).
      Ehrlich dazu: auch MIT Nachrang kostet der Flip in H2 rund 35 R gegenüber
      „nur S/R-Close" (49 vs 14). Der Nachrang begrenzt den Schaden, er dreht ihn
      nicht um. Abschalten des Features bleibt auto_flip_close=false.
      Methodik-Fehler beim Bau, korrigiert: der erste Lauf ließ die sequentielle
      Sim nach dem Exit bei xb+1 weiterlaufen — dadurch hatte JEDE Variante eine
      ANDERE Trade-Folge (sobald eine früher aussteigt, verschiebt sich alles
      Nachfolgende) und die Zahlen waren nicht vergleichbar. Jetzt feste, geteilte
      Entry-Liste wie in backtest_pbreak_rvalue.py.
      Umsetzung engine._check_flip_close im _pos_loop: nur im PLUS, 1× je Ticket,
      Startup-Schonfrist beachtet, Log 🔄 + Telegram, Snapshot flip_close/
      flip_close_min_r/flip_close_count. Mit 10 synthetischen Szenarien getestet
      (beide Richtungen · aus · unter Schwelle · gleichgerichtet · WARTEN · im Minus ·
      flat · Schonfrist · Ticket-Dedup). Der Alarm (Blinken/Ton/Telegram) bleibt
      unabhängig davon. **B4/B5-Pflicht: Live-Ertrag gegen die Erwartung halten —
      driftet es weg, auto_flip_close=false.**
    • Auto-Flip-Close = VERWORFEN (gemessen backtest_flipclose.py, 2 Halbjahre, sequentielle 1-Positions-Sim, 2026-07-16): User-Taktik-Kern „Position im Plus automatisch schließen, wenn die Empfehlung dreht" (heute nur Alarm). KEINE Variante (minR 0/0,3/0,5×ATR, auch-im-Minus) hält in beiden Hälften ØR & ΣR ≥ Basis: H1 ΣR 278→−280…−417, H2 +174→+119…+144 (kostet 1732 % des Ertrags). Mechanik: WR steigt 43→50 %, Ø-Gewinn schrumpft = Gewinner-Kappen — die nachlaufende EMA dreht oft mitten im Pullback, der Trade wäre weitergelaufen. Flip bleibt Alarm + menschliche Entscheidung (User-Übersteuerungen 68 % WR). Der Rest der User-Taktik (Ziel=nächstes S/R, weiterlaufen wenn stark) IST der P(break)-Auto-Close; der „3 %-Margin-Anker" = EUR-Schwelle, Klasse „verliert".
    • ⚠ NACHGEMESSEN 2026-08-04 mit dem NACHTRAINIERTEN Modell — Ablehnung BESTÄTIGT, deutlicher als zuvor (User-Frage „es werden immer noch Mini-Trades mit geringen Gewinnen geschlossen, wie verhindern wir das?"). Δ ΣR gegen minR=0:
      minR H1 H2
      0,1×ATR 6 +10
      0,2×ATR 720 42
      0,3×ATR 1136 157
      0,5×ATR 1998 683
      0,8×ATR 2887 1577
      Monoton schlechter in BEIDEN Hälften.
      ⚠⚠ Und die Prämisse der Frage stimmt so nicht. Auswertung der 295 Trades seit
      20.07.: die 28 Gewinne ≤2 € bringen zusammen +21,11 € = 0,8 % der
      Brutto-Gewinne (2.770 €) — sie sind kein Ertragsproblem. Entscheidend ist, WOHER
      sie kommen:
      Schließgrund Gewinner Median
      --- --- ---
      sr_close (Bot) 81 +14,65
      manual (User) 64 +6,37
      Zwei Drittel der Mini-Gewinne sind manuelle Closes, und der manuelle
      Median-Gewinn ist weniger als die Hälfte des Bot-Medians. Die Klein-Closes des
      BOTS sind gemessen richtig; die eigentliche Asymmetrie liegt im diskretionären
      Früh-Kappen — genau die dokumentierte Kern-Leckage (B0-Checkliste: „Gewinner läuft
      das Trailing"). Ein Code-Gate am Bot-Exit adressiert das falsche Ende.
      ⚠⚠ **KONTRAFAKTISCH NACHGERECHNET (analyze_manual_close.py, 2026-08-04) — DIE
      „FRÜH-KAPPEN"-HYPOTHESE IST WIDERLEGT.** Für jeden real geschlossenen Trade wurde
      ab dem echten Einstieg der kanonische Exit simuliert (EUR-Faktor 87,6 €/$/Lot,
      robust aus 251 Trades geschätzt — trader.pnl enthält Swap, eine direkte
      Rückrechnung wäre verfälscht):
      Schließgrund n IST
      --- --- ---
      manual Gewinn 143 +1428
      sr_close Gewinn 106 +1591
      emergency Verlust 98 1750
      sl Verlust 69 1926
      Das diskretionäre Früh-Kappen kostet NICHTS — es bringt +467 € MEHR als der
      kanonische Exit gebracht hätte; in 62 % der Fälle war der manuelle Close
      besser. Dasselbe gilt für den S/R-Auto-Close (530). Beide realisieren Gewinne
      besser, als ein reines Trailing es täte.
      Die Leckage liegt auf der VERLUSTSEITE: die 69 per Broker-SL geschlossenen
      Trades hätten mit dem kanonischen Exit 1.229 € weniger verloren (697 statt
      1926), die 98 Notfall-Closes 515 € weniger. Über ALLE Trades summiert wäre der
      kanonische Exit rund +700 € besser gewesen — praktisch vollständig aus der
      Verlustseite.
      Confounder, der das Ergebnis begrenzt: simulate() setzt den Initial-SL auf
      2,0×ATR_M5, live kommt er aus ATR_M15 (_calc_sl_tp) und ist damit oft
      deutlich weiter — unter Margin-Sizing schlägt das voll auf den EUR-Verlust durch.
      Der +1.229-Wert misst also zu einem erheblichen Teil SL-Breite, nicht
      Exit-Logik. backtest_sl_tf_mismatch.py hat die Umstellung auf M5-ATR bereits
      geprüft und wegen H2 verworfen — dieser Befund gibt Anlass, das mit dem
      kanonischen Exit ZU WIEDERHOLEN.
      ⚠ Zweite Grenze: der Vergleich gilt pro Trade, nicht als Kontostand-
      Hochrechnung — ein länger laufender Trade hätte den Positions-Slot belegt und
      Folge-Trades verhindert.
      **NACHGEMESSEN (backtest_sl_width.py, 2026-08-04): der Confounder war die
      Erklärung — aber das Geld ist NICHT einfach zu holen.** Gegatete Live-Population
      (1.224 Entries, Reversal aus, k=0,3), kanonischer Exit, variiert wird
      AUSSCHLIESSLICH die Breite des Initial-SL:
      ATR_M15 / ATR_M5 = 1,80 im Median → der Live-SL (2,0×ATR_M15) ist rund
      3,6×ATR_M5 breit, nicht 2,0. Genau daraus entstanden die 1.229 €.
      SL-Basis H1 ØR / ΣR H2 ØR / ΣR
      --- --- ---
      2,0×ATR_M5 0,092 / 44 0,024 / 18
      2,0×ATR_M15 (LIVE) 0,145 / 69 +0,064 / +48
      1,5×ATR_M5 0,081 / 38 0,038 / 28
      2,5×ATR_M5 0,121 / 58 0,013 / 10
      3,0×ATR_M5 0,130 / 62 +0,018 / +13
      KEINE engere Variante schlägt den Live-SL in BEIDEN Hälften — H2 bevorzugt den
      weiten Stop klar (+48 gegen 18). Die vorab fixierte Regel („nur wenn beidhälftig
      mehr ΣR UND Worst-Case nicht schlechter") ist damit **nicht erfüllt: keine
      Änderung.** Das reproduziert backtest_sl_tf_mismatch.py (2026-07-23, dort auf
      Squeeze-Entries mit eigenem Exit-Nachbau) jetzt auf der gegateten Population mit
      dem kanonischen Exit — der alte Schluss war korrekt.
      ⚠⚠ Der echte Unterschied ist das TAIL-RISIKO, und das ist eine SIZING-Frage:
      Worst-Case 5,64×ATR (live) gegen 1,85×ATR (bei 1,5×ATR_M5) — Faktor 3.
      Bei ATR 0,30 sind das ~1,7 $ Bewegung; unter 95 %-Margin-Sizing auf ~800 € Konto
      ist ein einzelner solcher Trade rund 19 % des Kontos. Der weite Stop kauft die
      H2-Performance mit genau diesem Tail. Damit landet die Frage dort, wo CLAUDE.md sie
      ohnehin verortet: „Der Hebel ist Sizing, nicht der Exit." Wer den Tail begrenzen
      will, senkt margin_buffer_pct — nicht die SL-Breite (die kostet gemessen H2).
    • Mindestgewinn für den S/R-Close = VERWORFEN (gemessen backtest_srclose_prob.py, Mindestgewinn-Sektion): „nur closen wenn ≥minR im Plus" verliert monoton in BEIDEN Hälften (minR 0,2: 444/213 · 0,5: 1425/854 R vs minR=0). Die vielen kleinen Gewinne SIND der Edge — übersprungene Klein-Closes prallen am (haltenden!) Level ab und enden schlechter. Kleine EUR-Beträge = Positionsgröße, kein Exit-Thema. 2 Halbjahre, Echtkosten):** LONG an Support / SHORT an Resistance, gegated mit P(Level hält), Ziel = Gegenlevel — verliert in BEIDEN Hälften (FIX H1 3393/ H2 2242, +Trailing/-nur-Trailing ebenso negativ; WR 4653 %, PF<1). Asymmetrie: P(break) taugt zum EXIT (Trade schon mit-Trend im Plus am Gegenlevel → kassieren), nicht zum ENTRY (antizyklisch am Extrem = Münzwurf-Richtung, 6× belegt). Einstieg bleibt mit-Trend (Pullback). Trailing bleibt — nicht mehr Standard-Exit (das ist der S/R-Close), aber Manager für die ~10 % Durchbruch-Runner (P≥Schwelle) + Fallback für Trades ohne erreichbares Gegenlevel.
    • Umsetzung: engine._p_break(mom6,mom3,wt,dist) (eingebettete _PB_MU/SD/W); wave_rec.snapshot liefert pb_feats {mom6, mom3, ema_diff, atr} — FEST aus M5 (aus dem ~15-s-Turn-Fetch gecacht, ATR-Floor 0,12 wie im Training). ⚠ Bugfix 2026-07-13: vorher kamen die Features aus der Wave-TF (Heuristik bis M30 → mom6 war 3-h- statt 30-min-Momentum, ATR 23× → P falsch skaliert) und das Level aus dem Misch-Set _sr_levels (Zonen/Gaps/Tageslevel). 2. Bugfix gleicher Tag: die M15-Cluster-Level (market.sr, SR_MIN_TOUCHES) sind in frischen Trends oft LEER → Hint verschwand komplett. Jetzt trainingsgleiche Level: wave.pb_levels = rohe M5-Pivots (k=3, ~300 Bars) aus dem Turn-Fetch (M5 dafür 320 Bars tief), Highs in _m5_hl gecacht. Am Rally-Hoch gibt es für LONGs korrekt KEIN Gegenlevel → kein Hint (nichts zu closen). ⚠ 3. Bugfix 2026-07-15: das Close-Level kommt jetzt aus _draw_levels (dieselbe geclusterte+Hysterese-Linie wie Chart & Dashboard) statt aus den rohen pb_levels — sonst feuerte die Meldung an einem anderen Level als die gezeichnete Linie (User: „passt nicht zusammen"). P(break)- Features (M5) unverändert, nur level/dist verschieben sich um Cents. _sr_close_hint (Anzeige ≠ Auslösung, Fix 2026-07-14 — das 0,15×ATR_M5-Fenster ≈ 0,03 $ war als reine Anzeige unsichtbar klein): Hinweis erscheint ab ≤0,6×ATR Annäherung (blaue „nähert sich"- Vorwarnung mit Live-P(break) + gap_atr); Auslösung close=true nur AM Level (≤0,15×ATR = gemessener Touch) und p_break < [trading] sr_close_pbreak (Code-Default 0,60, Band 0,300,90; ini seit 2026-07-24 = 0,55 auf User-Wunsch — liegt im validierten Optimum-Plateau 0,500,60, Hysterese-Zone damit 50/60). Snapshot sr_close_hint {level, p_break, at_level, gap_atr, close, confirm}. Hinweis existiert NUR mit offener Position. confirm = Bruch-BESTÄTIGUNGS-Preis (Fix 2026-07-22, User-Frage nach einem Trade, der am Level geschlossen wurde, obwohl der Kurs „durchgebrochen" war): level ± 0,5×ATR in Trade-Richtung — dieselbe Definition wie beim Modell- TRAINING (_BRK_ATR=0,5/_BRK_W=12 in backtest_srclose_prob.py/ analyze_srwaves.py). Ein Docht, der die nackte Levelzahl nur um wenige Cent überschreitet, zählt NICHT als Durchbruch — reales Fallbeispiel 2026-07-22: Close bei Level 85,943 (P(break) 31 %), Kurs wischte auf 85,994 (< Bestätigung 86,017 = Level+0,5×ATR) und fiel zurück → nach Modell-eigener Definition eine ABLEHNUNG, keine Bestätigung; die Vorhersage war korrekt. confirm jetzt in der Log-Zeile („— Bestätigung erst ab X") UND im Frontend-Hinweistext (#pos-srhint, alle 3 Zweige: Vorwarnung/Close/Laufen) sichtbar — macht den Unterschied „Kurs > Levelzahl" vs. „strukturell bestätigter Bruch" auf einen Blick klar. Live-Prognose-Tracking (2026-07-23, User-Wunsch „mitloggen wie oft richtig/ falsch"): jeder frische Level-Touch (at_level False→True) loggt EINMAL die aktuelle Vorhersage (bounce wenn P(break)<Schwelle, sonst break) in die neue Tabelle pbreak_predictions (history.log_pbreak_prediction, Felder u. a. predicted, confirm_price, reject_price, atr). Auswertung läuft separat, self-throttled ~5 min im _trend_loop (engine._evaluate_pbreak_predictions): prüft gegen candles_m1, ob der Kurs binnen 60 min zuerst confirm_price (→ outcome break) oder reject_price (→ bounce) erreicht — exakt dieselbe Definition wie beim Modell-TRAINING (_BRK_ATR=0,5/_BRK_W=12 M5-Bars=60 min). Kein Treffer binnen 60 min → Timeout, zählt wie im Training als bounce. correct = predicted==outcome. Broker-Zeit-Offset (trader._broker_offset_s()) korrekt berücksichtigt (pbreak_predictions.ts = lokale Epoch, candles_m1.time = rohe Broker-Zeit — Ausnahme, s. „Auch beachten"). Abfrage: history.pbreak_accuracy (period) bzw. GET /api/pbreak_accuracy?period=today|week|all (read-only, kein Token) → {n, accuracy, break_accuracy, bounce_accuracy, pending, …}. Getestet mit synthetischen Szenarien (break/bounce/Gegenteil-falsch/Timeout) — alle 4 exakt wie erwartet. Reine Messung, kein Signal-/Exit-Eingriff — noch keine UI-Karte (Datensammlung; B4-artige Auswertung später möglich, wenn genug Zeilen vorliegen). Gegen-/Stop-Seiten-Anzeige stop_approach (2026-07-24, User-Frage „Kurs nähert sich Support, aber keine Durchbruchwahrscheinlichkeit angezeigt"): Der reguläre sr_close_hint beobachtet nur das Ziel-Level (LONG→Resistance, SHORT→Support) und wirkt nur im Plus — nähert sich der Kurs dem Gegen-/Stop-Level (LONG→Support darunter, SHORT→Resistance darüber, typ. Verlust-Trade), war die App bewusst still. Neu: engine._stop_approach_hint liefert für GENAU dieses Level eine reine Anzeige von P(Durchbruch) — Break-Richtung -d (gegen den Trade), Feature-/ 60-s-Glättung (_stop_pb_state) identisch zu _sr_close_hint. KEIN Auto-Close. Snapshot stop_approach {level, side, p_break, at_level, gap_atr, confirm, reject}, Frontend #pos-stophint (grün „Level hält wahrscheinlich" bei P<50, rot „bricht wahrscheinlich durch" bei ≥50; nur wenn KEIN Ziel-sr_close_hint läuft; v=109). Vorher gemessen (backtest_pbreak_calibration.py, 2026-07-24, Gate „erst messen, dann bauen"): Das kalibrierte P(break)-Modell (auf Ziel-Seite trainiert) hält auch auf der Gegen-/Stop-Seite — AUC 0,68/0,72 out-of-sample in BEIDEN Hälften (≈ Ziel-Seite 0,67/0,71), im niedrigen P-Bereich sauber kalibriert (der praktisch wichtige „hält der Support?"-Fall). ⚠ Einzige Schwäche: im HOHEN Bereich (P≥0,5) GEGEN den Trend unterschätzt es die Bruch-Rate ~1013 Pp (predicted 58 % → echt 71 %) — also konservativ (warnt eher zu wenig vor einem Bruch), kein Kalibrierungs-Bruch. Deshalb reine Anzeige, kein Trade-Trigger. Anti-Flattern (2026-07-14, gemessen analyze_pbreak_flicker.py: mom3 der laufenden M5-Kerze ließ P 715 Pp/Bar springen, Max 77 Pp; 8 % der Annäherungen hatten ≥2 Schwellen-Flips): (1) P zeit-geglättet (~60-s-EMA in _pb_state, Reset bei Level-Wechsel/flat; angezeigtes p_break = geglättet) · (2) Hysterese ±5 Pp um die Schwelle — close↔laufen wechselt erst bei klarem Durchbruch (55/65 bei Default 0,60, liegt im Optimum-Plateau 0,500,60). Dashboard-Schalter in der Position-Kachel (#srclose-btn, grün=AN) → POST /api/srclose (Toggle, engine.set_sr_autoclose); Zustand snapshot.auto_sr_close. ⚠⚠ AUTOMATISCHER S/R-CLOSE = ABGESCHALTET (User-Vorgabe 2026-08-06, [trading] auto_sr_close = false UND runtime_state.json). Das ist eine Abweichung von der Messung, kein Nachvollzug einer: mit dem am 31.07. nachtrainierten P(break)-Modell schlägt der gegatete S/R-Close die Trailing-Baseline in BEIDEN Hälften (Live-Schwelle 0,35 — H1 149 gegen 506 = +357 R, H2 +1308 gegen 110 = +1418 R, backtest_pbreak_rvalue.py). ⚠ Nicht mit dem pauschalen S/R-Close verwechseln, der 2× verworfen wurde — das P(break)-Gate ist genau der Unterschied zwischen „Gewinner-Kappen" und dem gemessenen Edge. Was abgeschaltet ist: nur die automatische Ausführung (_check_sr_close steigt in Zeile 1 aus). Was bleibt: der Hinweis in der App (#pos-srhint), die P(break)-Chart-Linien, der Copilot-Kontext und das Live-Prognose-Tracking (pbreak_predictions) — _sr_close_hint wird in snapshot() unabhängig vom Schalter berechnet. Was weiter schützt: Broker-SL 2×ATR, Trailing, Time-Stop. ⚠ Beide Stellen mussten gesetzt werden — runtime_state.json überschreibt die ini beim Start (die Falle, durch die auto_signal am 31.07. unbemerkt weiterlief). Neu im Config-Wächter (measurement_reminder.py, validiert = true), damit die Abweichung sichtbar bleibt. Backups: oil_widget_config.ini.bak-2026-08-06-srclose, runtime_state.json.bak-…. Zurück: Dashboard-Schalter #srclose-btn (setzt beide Stellen) oder auto_sr_close = true in ini und runtime_state.json. Historie: AUTO-Close (_check_sr_close im _pos_loop, auto_sr_close=true): schließt nur im PLUS (pnl>0) wenn close=True, 1×/ Ticket, wirkt bei gesperrtem Handy. Optionaler Mindestgewinn ([trading] sr_close_min_gain Default 0=aus · UI #srmin-input · POST /api/srclose_min · set_sr_close_min_gain · Snapshot sr_close_min_gain): unter der EUR-Schwelle wird am Level NICHT geschlossen. ⚠ Gemessen ist OHNE besser (Verworfen-Eintrag) — Opt-in auf User-Wunsch, UI-Status warnt. Automatisch auf X % des Einsatzes ([trading] sr_close_min_gain_pct) — Stand 2026-07-29 wieder AUS (=0, User-Wunsch „gewinn close und notfall stop wieder auf leer beim Eröffnen") (Verlauf: 1,0→3,0 am 07-23 · 0 am 07-24 früh „nichts beim Öffnen setzen" · 3,0 am 07-24 abends zurück · 0 am 07-29 — gilt NUR für den Gewinn-Close, der Auto-Notfall-Stop bleibt AUS). Bei pct>0 wird bei JEDER echt neuen Position (nicht beim Wiedererkennen nach Neustart) sr_close_min_gain frisch auf pct% × Margin dieser Position gesetzt (_check_auto_close, retry-sicher via _sr_close_min_gain_armed_ticket — Margin aus trader.refresh() im selben _pos_loop-Tick, jeden Tick erneut versucht bis Margin>0; persistiert via _save_runtime_state, Log „Gewinn-Close armiert … +X (N% Margin Y)"). Bei pct=0 wird beim Öffnen NICHTS gesetzt — dann bleibt der manuell im UI gesetzte/gemerkte sr_close_min_gain maßgeblich. ⚠ Umstellen auf 0 braucht Neustart: _sr_close_ min_gain_pct lebt im Speicher; solange der alte Wert (3,0) läuft, re-armt JEDER neue Trade erneut — die ini-Änderung greift erst nach restart_server.bat. Beim 0-Stellen zusätzlich den persistierten Alt-Wert in runtime_state.json nullen (sr_close_min_gain) UND emergency_state.json loss (Erb-Falle, sonst restauriert der Neustart den Alt-Wert). Gate verifiziert (2026-07-23, User-Nachfrage „darf nicht schließen bevor der Wert erreicht wurde"): _check_sr_close prüft pnl < sr_close_min_gain → return bevor _sr_close_hint/trader.close überhaupt aufgerufen werden — isoliert mit 6 synthetischen Szenarien getestet (unter/exakt-auf/über Schwelle, P(break) hoch trotz Profit, Schwelle aus, Verlust-Trade) → alle korrekt. Dabei echten Bug gefunden & gefixt: die 1%-Berechnung lief nur EINMALIG beim Ticket-Wechsel (if ticket != self._last_pos_ticket); war order_calc_margin genau in diesem einen Tick 0/nicht verfügbar (Broker-Race direkt nach Order-Fill), blieb sr_close_min_gain für die GESAMTE Laufzeit der Position auf dem STALEN Wert der vorherigen Position hängen (der Ticket-Wechsel-Zweig feuert nur 1×, keine zweite Chance) — im ungünstigen Fall ein zu NIEDRIGER Alt-Wert, der den Trade dann exakt zu früh schließt. Fix: neuer State _sr_close_min_gain_armed_ticket (getrennt vom Notfall-Stop/TP-Restore-Zweig) — die %-Berechnung läuft jetzt JEDEN _pos_loop-Tick erneut, bis sie für das aktuelle Ticket einmal erfolgreich war (Margin>0), statt nur im Ticket-Wechsel-Moment. Wiederhergestellte Positionen nach Neustart markieren sich sofort als „armiert" (kein Überschreiben des persistierten Werts) — mit Test verifiziert. Log 🎯 S/R-AUTO-CLOSE + Telegram + Toast (sr_close_count), closed_by=sr_close. UI Position-Kachel: bernstein „Durchbruch nur X% → Bot schließt" bzw. grün „Durchbruch X% wahrscheinlich → läuft weiter". Abschalten auto_sr_close=false / set_sr_autoclose(False); Schwelle via sr_close_pbreak. Kein pauschaler S/R-Close (2× verworfen) — nur mit P(break)-Gate.
    • S/R-verankertes TRAILING = GEMESSEN, NICHT gebaut (backtest_trailing_sr.py, 2026-07-31, User-Idee „Trailing TP und SL auf die S/R-Linien anpassen"): Die Idee zerfällt in drei Teile — Initial-SL an S/R (schon verworfen, s. u.), fester TP am Gegenlevel (schon 2× verworfen, backtest_srclose.py) und Trailing-SL ans Level nachziehen = der einzig offene Teil (bisher wurden nur ATR-Abstände variiert). Aufbau identisch zu backtest_trailing.py (echte Phasen-Mechanik, Breakeven 1,3), Level = M30-Pivots k=3, kausal (Pivot erst 3 M30-Bars später bekannt), Deckel [0,5…2,0]×ATR gegen den 11,4×ATR-Fall; 80k Bars, ~19,7k Signale je Hälfte. Ergebnis: schadet nicht, trägt aber zu wenig. Basis (ATR-Trail 1,5) H1 0,020 / H2 +0,064; 3 von 5 Varianten schlagen sie in BEIDEN Hälften (Puffer 0,10: +0,009/+0,004 · Puffer 0,50: +0,003/+0,014 · Hybrid „engerer von beiden": +0,006/+0,006, WR 37→43 % bzw. 40→43 %). Trotzdem nicht gebaut, zwei Gründe: (a) Effektgröße 0,0060,014 R/Trade = eine Größenordnung unter der B3-Hürde (~0,1×ATR netto); (b) nicht parameter-robust — Puffer 0,10 besteht, 0,25 fällt durch (H1 0,023), 0,50 besteht wieder; ein Parameter, dessen Nachbar das Vorzeichen kippt, ist Rauschen (dasselbe Kriterium, an dem ORB und die SMC-Sweeps starben — beim Squeeze waren ALLE 8 Kombis positiv). Positiv-Befund: der Worst-Case bleibt in ALLEN Varianten 2,00×ATR — der Deckel funktioniert, S/R-Verankerung fügt kein Tail-Risiko hinzu (das war die Sorge aus backtest_sl_method.py). ⚠ Die KONTROLLVARIANTE „TP am Gegenlevel" reproduziert den alten Befund exakt (WR 40→54 %, ΣR H2 +1261→+740 = Gewinner-Kappen) — damit ist die Simulation gegen backtest_srclose.py validiert und der TP-Teil endgültig erledigt. NACHTEST auf NUR Squeeze-Entries (--squeeze, 2026-07-31, Echtkosten): ERLEDIGT — dort ist der S/R-Trail nicht nur zu klein, sondern klar SCHLECHTER. Basis H1 +0,102/H2 +0,108; keine einzige Variante besteht (S/R 0,10: +0,096/+0,093 · 0,25: +0,083/+0,098 · 0,50: +0,052/+0,092 · Hybrid: +0,093/+0,122 = kippt · Hybrid weit: +0,091/+0,084). Anders als im Wave-Lauf ist das kein Rauschen: der reine S/R-Trail wird monoton schlechter, je breiter der Puffer (in BEIDEN Hälften) — sauberer monotoner Verlauf, nur gegen die Idee. Mechanismus: der Squeeze lebt von wenigen Läufern (43 % Treffer, Ertrag im Tail); ein an das nächstgelegene Level gebundener Stop holt genau dort raus, wo der Lauf beginnt. Bei Wave-Signalen fällt das kaum auf (selten große Runner) — daher das umgekehrte Vorzeichen zwischen beiden Läufen. ⚠ Stärkster Gewinner-Kappen-Beleg des Projekts: die Kontrollvariante (fester TP am Gegenlevel) dreht den Squeeze von +0,102/+0,108 auf 0,066/0,004 = Edge vollständig zerstört. ⚠ Abgrenzung gegen den laufenden S/R-Auto-Close: der ist P(break)-gegated (schließt nur, wenn das Level wahrscheinlich HÄLT) und war auf Squeeze-Trades separat gemessen ein Wash (backtest_squeeze_srclose.py, +0,190→+0,224 / +0,394→+0,377). Das Gate ist der Unterschied zwischen „Wash" und „Edge zerstört" — ein ungegateter Level-TP ist etwas grundlegend anderes. Damit ist die User-Idee in allen DREI Teilen durchgemessen; der Live-Exit-Stack bleibt unverändert.
    • ⚠⚠ SL-BASIS M5 vs. M15 auf der NEUEN Level-Entry-Population (gemessen backtest_sl_basis.py, 2026-08-05, User-Frage „wäre M5-ATR vertretbar?") — DIFFERENZIERT, und dabei eine Mess-Betriebs-Lücke gefunden.Zuerst der strukturelle Fund: exit_model.simulate() nimmt EINEN atr für SL, TP, Trail und Breakeven — und alle Backtests übergeben ATR_M5. Live kommt der SL dagegen aus ATR_M15 (mt5_utils.atr_value mit SL_TF), im Median 1,80× so groß. Praktisch jede Projekt-Messung modelliert damit einen SL von 2,0×ATR_M5, während live ~4,0×ATR_M5 gilt. Einzige bisherige Ausnahme: backtest_sl_width.py (04.08.), das genau das explizit verglichen hat. Ergebnis auf Level-Entries (80k, 2 Halbjahre, nur die SL-Basis variiert):
      Population Variante H1 ØR / Worst H2 ØR / Worst
      SQUEEZE LIVE (≈4,1×ATR_M5) +0,455 / 5,87 +0,614 / 8,83
      2,5×ATR_M5 +0,459 / 2,82 +0,620 / 2,94
      2,0×ATR_M5 +0,452 / 2,32 +0,610 / 2,44
      WELLE LIVE (≈3,9×ATR_M5) +0,371 / 4,97 +0,356 / 6,00
      2,5×ATR_M5 +0,362 / 2,79 +0,343 / 2,72
      3,0×ATR_M5 +0,358 / 3,29 +0,352 / 3,22
      Ertragsseitig ist es ein WASH auf der Squeeze-Population — alle Varianten
      liegen innerhalb eines Standardfehlers (≈0,035 bei n≈800), und die einzige Zelle,
      die den Live-Stand in ØR beidhälftig schlägt (2,5), hat schlechtere Nachbarn
      → nach der Nachbar-Regel Rauschen. **Auf der Wellen-Population ist der weite
      Live-SL dagegen in BEIDEN Hälften der beste** — jede Verengung kostet dort.
      ⚠⚠ **Der EINZIGE robuste Unterschied ist das TAIL-RISIKO — und der ist groß und
      monoton:** Worst-Case 8,83×ATR (live, Squeeze H2) gegen 2,94 bei
      2,5×ATR_M5, also Faktor 3. Bei 3,5 Lots und ATR_M5 0,33 sind das ~890 €
      gegen ~300 € im einzelnen schlechtesten Trade.
      **Antwort: „vertretbar" ja — aber es ist keine Ertrags-, sondern eine
      Risiko-Entscheidung.** Für den Squeeze kostet die Verengung gemessen nichts und
      drittelt den Tail; für Wellen-/Signal-Trades kostet sie Ertrag in beiden Hälften.
      Eine saubere Umsetzung wäre quellen-abhängig (_calc_sl_tp nutzt M5-ATR nur
      bei source="auto_squeeze") — strukturell begründet (Breakout- gegen
      Pullback-Einstieg haben verschiedene Nachlauf-Dynamik), nicht data-mined.
      GEBAUT & LIVE (2026-08-05, User-Entscheidung „für Squeeze-Trades: ja").
      trader._calc_sl_tp(sym, otype, entry, atr_tf=None) bekommt die ATR-Zeitebene als
      Parameter (Default SL_TF = M15); _send/_send_locked/open_long/open_short
      und place_stop reichen sie durch. Gesetzt wird sie an genau zwei Stellen:
      engine._open (source == "auto_squeeze" → M5) und _manage_squeeze_pending
      (quelle == "squeeze" → M5). **Alles andere — manuelle Trades, Auto-Signal —
      bleibt unverändert bei M15.**
      Die Multiplikatoren sind NICHT angefasst (1,82,2×ATR): nur die Zeitebene
      wechselt, aus der der ATR kommt. Der Log macht es sichtbar („… ATR aus M5").
      ⚠⚠ DER TP IST BEWUSST NICHT BETROFFEN (User-Vorgabe: „nur für SL, der TP soll
      mit dem Runner mitwachsen"). Geprüft statt angenommen: der von _calc_sl_tp
      zurückgegebene TP wird von KEINEM Aufrufer gesendet — _send_locked und
      place_stop setzen ausschliesslich req["sl"]. Das Ziel verwaltet
      core/trailing.py dynamisch (_TP_INIT_ATR=3,5 × ATR der Wellen-Zeitebene)
      und zieht es mit dem Lauf nach. Ein Warnkommentar im Code hält fest, warum der TP
      dort nicht mit in die Order darf: er wäre sonst an die SL-Breite gekoppelt und
      würde genau die Runner kappen, von denen der Squeeze lebt (43 % Treffer, Ertrag
      im Tail).
      Mit 4 Szenarien getestet (M5 kappt auf 2,2×ATR_M5=0,66 · M15 greift auf den
      Mindest-Deckel 1,8×ATR=0,99 · M5 enger als M15 · SHORT spiegelbildlich).
      ⚠ Beim Testen eine eigene Fehlannahme korrigiert: ohne Pivot greift der
      1,2-%-Fallback, der beim M15 vom Mindest-Deckel aufgeweitet wird — die
      2,2×-Obergrenze wird dort gar nicht erreicht.
      Zurück: die beiden mt5.TIMEFRAME_M5-Zuweisungen in engine.py auf None.
      ⚠ Das reproduziert NICHT backtest_sl_tf_mismatch.py (23.07., dort war M5 in H2
      schlechter): dort wurde am Bar-Close eingestiegen, hier am Level. Der
      Einstiegspreis hat den Befund gedreht.
  • S/R-verankerter SL = KEIN Edge (gemessen, backtest_sl_method.py): Pivot+Band vs. „S/R ∓ ATR/0,10-Puffer" vs. fest 2,0×ATR — gedeckelt alle identisch (Ø-R 0,1960,201, PF 1,381,39, Worst 2,2×ATR): der Band-Cap [1,82,2]×ATR dominiert, die Verankerung ist Rauschen. Nur ungedeckelt sieht S/R besser aus (Ø-R 0,249) — aber Worst-Case 11,4×ATR (Kontokiller bei 90 %-Sizing) → verworfen. Aktuelle SL-Logik bleibt.

    • SL-ATR-Timeframe bei Squeeze-Trades (M5-Signal, aber SL_TF fix M15) = GEPRÜFT, KEINE Änderung (gemessen backtest_sl_tf_mismatch.py, 2026-07-23, Auslöser 89,36-€-Nacht-Trade): _calc_sl_tp sizt den Initial-SL IMMER aus M15-ATR, unabhängig vom Signal-TF — bei Squeeze (M5-Signal) kann das stark divergieren (real: M5-ATR fiel 0,29→0,10 — genau die Kompression, die den Squeeze auslöste — während M15-ATR noch bei ~0,36 lag → SL 0,787 statt ~0,22 bei M5-Sizing). Der validierte Squeeze-Backtest (backtest_breakout_squeeze.py) nutzt durchgehend M5-ATR für den SL — Live weicht hier vom Backtest ab. 2-Stichproben-Test der Umstellung auf M5-ATR-SL bei Squeeze-Trades: GEMISCHT — H1 leicht besser (ØR +0,167 vs +0,064, ΣR +35 vs +13), H2 (größere, aktuellere Stichprobe) klar SCHLECHTER (WR 51 % vs 59 %, ΣR +281 vs +323) → fällt nach der beidhälftig- robust-Regel durch. Der breitere M15-SL kassiert gelegentlich einen vollen Verlust wie diese Nacht, verhindert aber mehr vorzeitige Ausstoppungen bei normalen Rücksetzern — netto vorteilhaft. Keine Änderung.
  • Inter-Market (Brent-Bestätigung / DXY-Gegenwind) = VERWORFEN (gemessen backtest_intermarket.py, 2026-07-19, letzte ungetestete Indikator-Klasse): 1366 Trades, seq. Sim, Live-Exit, Echtkosten, 2 Halbjahre; Buckets nach Zustand am Entry (Symbole: SpotBrent, USDX). Beide Hypothesen kippen zwischen den Hälften: (A) Brent-Trend dagegen H1 0,002 vs H2 0,187 (H1 sogar BESSER als „dafür"!), Brent-Mom-1h dagegen H1 +0,311/H2 0,233 = Flip; (B) DXY-Gegenwind H1 0,13…−0,19 (schlechter, wie vermutet) aber H2 +0,13…+0,17 = BESSER als Rückenwind — komplett invertiert, klassische Regime-Abhängigkeit (wie H1-Stärke- Filter/Momentum). Einzig Brent-Mom-3h ist richtungs-konsistent, aber winzig (Δ~0,060,14) und die Schwester-Varianten kippen → nach der vorab festgelegten Verdict-Regel (beide Hälften UND Varianten-robust) kein Einbau. Damit ist auch die letzte orthogonale Indikator-Klasse durch: am Signal ist nichts mehr zu holen — Hebel bleiben Sizing/Exit/Verhalten (nicht gegen das Signal handeln: live gemessen 39 % WR / 166 € vs. mit 55 % / +65 €). backtest_confluence.py — MACD/ADX-Richtung praktisch neutral, ADX-Stärke leicht negativ, ein MACD+ADX-Gate senkt den Edge (78 % Trades). RSI bestätigend ist kontraproduktiv (RSI gegen Richtung = doppelter Edge = der Pullback-Effekt, den der „ tiefer Pullback"-Bonus schon nutzt). Standard-Oszillatoren also nicht draufsatteln. Mehrquellen-Redundanz läuft bereits über die Gesamtempfehlung.

  • TradersUnion (TU) aus der Empfehlung ENTFERNT (User-Vorgabe 2026-07-06): TU ist Standard-Indikator-Konsens (MA/Oszillator-Ratings einer Website) — dieselbe Klasse wie MACD/ADX/RSI = kein Edge, plus Latenz (5m „Strong Buy" während 4h/1d „Strong Sell") und nicht backtestbar (Live-Scrape, keine History). Floss vorher als Konfidenz-Faktor (_tu_check ±6/8 je 5m/15m) UND als Verdict-Stimme ein — beides neutralisiert (wave_rec._build zieht _tu_check nicht mehr; _verdict ohne TU-Vote). ⚠ Nebenbefund: der Konfidenz-Pfad hatte keinen _STALE_S-Check (nur trade_signal hatte ihn) — mit der Entfernung erledigt. TU bleibt reine Anzeige (Snapshot tu); _tu_check/_TU_* als Code erhalten (reaktivierbar). Fetch war auf 5 min gedrosselt (_TU_S=300); Loop seit 2026-07-19 ganz stillgelegt (_tu_loop nicht mehr in start() — TU ist aus Empfehlung/Verdict/Copilot raus UND die Kachel ausgeblendet, der Scrape lief für niemanden; reaktivieren = Loop wieder in die Liste). Elliott loggt nur noch bei Änderung (vorher ~6k identische Zeilen/Logrotation).

  • Order ohne Empfehlung = bestätigbarer HINWEIS, kein Block: Bei WARTEN/ Gegen-Signal zeigt das Frontend einen Bestätigungs-Dialog („… trotzdem eröffnen?") — bestätigen = Trade läuft, Abbrechen = kein Trade. Kein hartes Blockieren (überschreibbar). engine._open blockt nicht (loggt nur). ⚠ Log-Quellen-Attribution (Fix 2026-07-29): _open(fn, want, source) bekommt jetzt eine source (Default "manuell"); der Auto-Squeeze ruft open_long/short (source="auto_squeeze"). Vorher schrieb _open bei JEDEM Auftrag gegen die Empfehlung „— vom User bestätigt" — auch bei autonomen Squeeze-Entries (der User fragte, ob ein Trade auto/manuell war; die Zeile log fälschlich „User"). Jetzt: autonome Orders → „autonom vom Auto-Squeeze eröffnet", manuelle → wie bisher. Die 🚀 AUTO-SQUEEZE-ENTRY-Zeile bleibt die eindeutige Quelle der Wahrheit.

  • Entry-Checkliste im Order-Dialog (engine._entry_checklist, User-Vorgabe 2026-07-30 „blende einen Hinweis ein wenn die Checkliste gegen meine Eröffnung ist", v=120): Vor JEDER manuellen Eröffnung prüft der Server 7 Punkte mit LIVE-Daten und zeigt die verletzten im Bestätigungs-Dialog. Blockt nicht, macht aber sichtbar, was dagegen spricht — und zwar auch dann, wenn ein Signal vorliegt (der alte Dialog kam nur bei WARTEN/Gegen-Signal). Punkte: (1) Signal-Deckung (die gemessene Kern-Leckage), (2) HTF-Trend M30+H1 („nur mit dem HTF-Trend, niemals dagegen"), (3) Entry-Raum zum Gegenlevel (<0,6×ATR = gemessen negativ), (4) News-Konflikt (|Sentiment|≥0,5 gegen die Richtung), (5) Kosten (Spread/ATR, ab 0,32 = Kostenfalle), (6) Mental-Check: Verlust-Serie heute („nach 2 Losses Session beenden" — zeigt Anzahl, Tages-P&L und %-Anteil am Konto), (7) Nacht-Stunde 07 Uhr. verdict: stop (≥2 „fail") · warn · ok. Snapshot checklist {long, short} — nutzt nur bereits berechnete Snapshot-Teile; die DB-Abfrage für die Verlust-Serie ist ~120 s gecacht (_loss_streak_today). ⚠ Die Gewichtung ist NICHT backtest-optimiert, sondern aus den im Projekt gemessenen Leckagen + der User-Checkliste abgeleitet (Entscheidungshilfe, kein validiertes Signal). Real beim Einbau (30.07. abends): beide Richtungen stop — „keine Empfehlung" · „HTF unklar/dagegen" · „11 Verluste heute (261 € = 80 % des Kontos)".

  • Liquiditäts-Sweeps (SMC: Equal Highs/Lows, Swing Liquidity) = VERWORFEN (gemessen backtest_liquidity_sweep.py, 2026-07-30, User-Vorgabe mit Chart- Beispielen + WTI-Praxisregeln): Getestet, weil der Sweep (Fehlausbruch: Level kurz durchstochen, Close zurück in der Zone) eine ANDERE Mechanik ist als der schon verworfene Nackenlinien-Bruch (backtest_doubletop.py). Achsen: 3 Muster × Level-TF (M5/M30/H1, die „Liquidität auf höherem TF"-Regel) × 2 Exits × 2 Halbjahre, plus Session-Filter (NY 1416 / Asia 24 Uhr Berlin). Kernbefunde: (a) Der SMC-eigene Exit („Stop über dem Sweep-Docht, Ziel = Gegen-Liquidität") ist in ALLEN 9 Kombinationen negativ — die Lehre steht und fällt mit ihm, er trägt nicht; mit dem generischen Live-Trailing sieht dasselbe Setup besser aus (derselbe Exit-Artefakt wie bei backtest_patterns.py). (b) Equal Highs/Lows fallen fast überall durch (Vorzeichen kippt zwischen den Hälften). (c) Einziger Treffer: „Swing-Liquidity, M30-Level, Live-Exit" (+0,241/+0,358) — aber die Nachbar-TFs kippen (M5 0,152/+0,247 · H1 0,111/+0,692) = nicht parameter-robust, exakt das ORB-Muster von 2026-07-17. (d) Session-Filter: zu wenige Trades (n=331) für eine Aussage. Kein Einbau in die Gesamtempfehlung. 16. verworfener Signal- Eingriff. ⚠ Die DXY-Korrelations-Regel wurde bewusst NICHT erneut getestet — die Inter-Market-Klasse ist 2026-07-19 gemessen durchgefallen.

  • Fibonacci-Retracements = VERWORFEN (gemessen backtest_fibonacci.py, 2026-08-04, User-Frage zu einem USOIL-Kommentar „ein Fibonacci-Retracement sollte den Kurs auf 79 $ drücken"): Fib-Level waren im Projekt bis dahin nie gemessen — weder als S/R noch als Einstiegszone. Aufbau wie beim P(break)-Training: Schwünge aus M15-Pivots (die genannte Zeitebene), Berührung ≤0,15×ATR, Ausgang ±0,5×ATR in 12 M5-Bars, 80k Bars, 2 Halbjahre — mit ZWEI Kontrollgruppen.

    Haltequote H1 H2
    Fibonacci 54,3 % 54,5 %
    (a) Zufalls-Level gleicher Distanz 55,7 % 53,8 %
    (b) M30-Pivots (Live-Quelle) 56,6 % 55,4 %
    Handelbarkeit (Bounce am Level, kanonischer Exit, Echtkosten): H1 ØR 0,168 /
    PF 0,71 · H2 ØR 0,082 / PF 0,85 — beidhälftig negativ.
    ⚠⚠ Der aufschlussreichste Wert ist die Zufalls-Kontrolle: ~55 % Haltequote.
    Ein BELIEBIGES Level hält in diesem Markt in gut der Hälfte der Fälle — das ist die
    Basisrate kurzfristiger Mean-Reversion, keine Eigenschaft von Fibonacci. Die Frage
    war nie „hält das Level?", sondern „hält es BESSER als eine willkürliche Linie?" —
    und Fib tut das nicht. Damit ist auch das Selbsterfüllungs-Argument entkräftet
    („so viele schauen darauf, also wirkt es"): dann müsste Fib über der Zufallslinie
    liegen; es liegt darunter bzw. gleichauf.
    Bestätigt umgekehrt die Struktur-These: M30-Pivots — Stellen, an denen real
    gehandelt wurde — halten in BEIDEN Hälften besser als beide gerechneten Linien.
    Dieselbe Trennlinie wie beim Regressionskanal (backtest_structure_tf.py, 24/24
    negativ) und beim P(break)-Modell: „Linie im Chart" ist nicht „Struktur im Markt".
    21. verworfener Signal-Eingriff. ⚠ Grenzen: eine Schwung-Definition (M15-Pivots
    k=3, ≥2×ATR); die Pivot-Kontrolle ist dünner (n=173/224). Der Vergleich ist aber
    design-intern, die relative Aussage damit robuster als die Absolutwerte.
  • Meta-Labeling (López de Prado) auf dem Wellensignal = VERWORFEN (gemessen backtest_metalabel.py, 2026-08-05, aus der Web-Recherche „Best Practices"): Kein 23. Filter, sondern ein Zweitmodell, das für ein bereits erzeugtes Signal P(Gewinn) schätzt — dieselbe Machinerie wie das erfolgreiche P(break)-Modell (12 kausale Merkmale: Konfidenz · Überdehnung · HTF/H1-Übereinstimmung · Kosten · Vola-Regime · mom3/mom6 · Efficiency Ratio · Nacht · Entry-Raum · Winkel; logistische Regression, Fit auf einer Hälfte, Test auf der anderen, in BEIDEN Richtungen). Population = der volle Live-Gate-Stack (929 bzw. 1316 Signale). Fällt klar durch:

    Fit H1 → Test H2 Fit H2 → Test H1
    AUC oos 0,508 0,479
    Kalibrierung 49,3 % vorhergesagt vs. 38,6 % real 37,2 vs. 38,0 %
    Top-Bucket (p≥0,55) 74,1 % vorhergesagt, 39,1 % real (existiert nicht)
    Münzwurf in beiden Richtungen (P(break) erreicht 0,65), und **8 von 12
    Merkmalsgewichten kippen das Vorzeichen** zwischen den Fits — das Modell lernt
    nichts Stabiles. Die im H1→H2-Lauf scheinbar „besseren" Schwellen (p≥0,45…0,55)
    sind reine Handelsvermeidung (ΣR steigt nur, weil n fällt; ØR bleibt negativ)
    und reproduzieren in der Gegenrichtung nicht. 23. verworfener Signal-Eingriff.
    ⚠ Deckt sich exakt mit dem Caveat der Quelle (Hudson & Thames): *„if the algorithm
    is bad then meta-labeling would likely only reduce the downside."* Das Wellensignal
    ist in H1 gemessen negativ — ein Zweitmodell rettet ein schwaches Primärsignal nicht.
    Was das positiv zeigt: die Trennung „am Signal ist nichts zu holen, der Hebel
    liegt in Ausführung und Verhalten" hält auch gegen die aktuelle Lehrmeinung.
    Am selben Tag hat der Ausführungs-Hebel (Stop-Order statt Market) +0,61 ØR
    gebracht — mehr als alle 23 Signal-Ideen zusammen.
  • „Erst messen, dann umbauen": Strategie-Änderungen per Backtest belegen, nicht raten. Gegen-Trend verliert; nicht bei WARTEN handeln. Stichproben ~28 Tage — ein Trend-Regime; Befunde später auf mehr/andere History gegenprüfen.

  • Wave-Auto-Trader (Dry-Run) KOMPLETT ENTFERNT (2026-07-23, Code-Review-Fund): war seit Langem Verlierer (WR 29 % / PF 0,64) vs. manuelle Trades (WR 5676 %); UI-Toggle bereits vorher entfernt (Dashboard-Button steuert seitdem Auto-Squeeze), lief aber weiter dormant bei jedem Tick mit (toter Ballast). core/auto_trader.py gelöscht (Sicherung: .removed_backup/), engine.self.auto + alle 4 Aufrufstellen (_price_loop, _run_analysis, toggle_auto, Snapshot-Keys auto/min_conf) sowie die tote auto-Referenz in TradingAgent.__init__ entfernt, /api/auto gelöscht. min_conf (Frontend-Warnschwelle „Order ohne Signal") lebt jetzt als eigene Konstante _MIN_CONF_DISPLAY=70 in engine.py. Die intended_trades-DB- Tabelle + history.intended_*-Methoden bleiben (historische Daten, kein Schreiber mehr aktiv). Manuelles Handeln bleibt der validierte Edge auf dem Wave-Signal.

    • RE-TEST 2026-07-30 (User-Wunsch „Trades bei Empfehlung automatisch eröffnen") = ERNEUT VERWORFEN, gemessen backtest_auto_signal.py: Auto-Entry auf dem Wave- Signal mit den HEUTIGEN Gates, echte _build-Logik (kein Nachbau), M30-/H1-Trend aus M5 aggregiert und in _build gefüttert, sequentielle 1-Positions-Sim, Live-Exit (SL 2,0/Trail 1,5/BE 1,3/Time-Stop 120 min), Echtkosten (Bar-Spread/ATR), 80k M5-Bars in 2 Halbjahren. ALLE 8 Varianten fallen durch (Konf-Schwelle 55/65/70/75 × Nacht-Sperre an/aus): H1 durchgehend NEGATIV (ØR 0,076…−0,182, PF 0,780,90), H2 durchgehend positiv (+0,112…+0,167) → klassisches Regime-Kippen, n=7181167 je Hälfte (keine Stichproben-Frage). ⚠ Kontroll-Beleg, dass es am SIGNAL liegt und nicht an der Sim: der Squeeze läuft durch DIESELBE Exit-/Kosten-Maschinerie und ist in H1 +0,187/PF 1,29 — die Simulation funktioniert also, nur das Wave-Signal trägt in H1 nicht. Deckt sich exakt mit dem Live-Dry-Run (WR 29 %/PF 0,64). Nebenbefunde: (a) höhere Konfidenz hilft NICHT monoton (75 schlechter als 65) — bestätigt analyze_verdict_calibration.py (conf_pct ist NICHT kalibriert, in H2 invertiert), ein „nur bei hoher Konfidenz"-Auto-Entry ist damit auch erledigt; (b) die Nacht-Sperre verbessert konsistent (H1 0,172→−0,123), reicht aber nicht; (c) ⚠ Methodik-Lehre: ein Vorlauf mit nur 6k Bars zeigte „Konf≥65+Nacht TRÄGT" — der 80k-Lauf drehte das komplett (n war 6099/Hälfte = Rauschen). Kleine Stichprobe hätte hier zum Fehl- Einbau geführt. Warum manuelle „MIT Signal"-Trades trotzdem profitabel sind (+118 €/WR 56 %, analyze_entries-Auswertung 2026-07-30): menschliche AUSWAHL unter den Signalen (Kontext/Tageszeit/Marktlage), nicht „alle nehmen" — die Grundgesamtheit ALLER Signale ist der Verlierer. 14. verworfener Signal-Eingriff.
  • Auto-Signal-Entry = TROTZDEM GEBAUT auf ausdrücklichen User-Wunsch (2026-07-30, „probieren wir es trotzdem mal"), [trading] auto_signal, Default FALSE.Der Backtest direkt darüber sagt NEIN (H1 durchgehend negativ) — der User hat das nach vollständiger Information (inkl. Live-Dry-Run-Historie WR 29 %/PF 0,64) bewusst entschieden. Gebaut mit den gemessen BESTEN Parametern, nicht mit Default-Raten: auto_signal_min_conf=75 (mit Nacht-Sperre H1 0,053 = mildester Verlust, H2 +0,141 = bestes Netto; ⚠ NICHT weil „mehr Konfidenz besser" — conf_pct ist unkalibriert, 75 ist empirisch) und auto_signal_skip_night=true (verbessert JEDE Variante). Mechanik (engine._check_auto_signal im _pos_loop, 1 s, NACH den Close-Checks): eröffnet nur FLAT, nur bei wave.signal() LONG/SHORT mit conf_pct ≥ min_conf, 1× je Signal-Episode (Dedup _signal_last_sig, re-armt bei WARTEN/Flip — nicht über den Preis wie beim Squeeze, weil das Signal keine Level-Marke hat); erbt die Squeeze-Guards: Circuit-Breaker, Nacht-Sperre, S/R-Close-Cooldown, allgemeine Wiedereinstiegs-Sperre. Kein Nachkauf bei gleichgerichteter Position, kein Drehen einer Gegen-Position (unbelegt, wie Squeeze-Reverse). Log 🎯 AUTO-SIGNAL- ENTRY + Telegram, Setup-Tag AUTOSIG_<dir> (macht die Trades in der DB separierbar → B4-Monitor). Snapshot auto_signal/auto_signal_min_conf/signal_entry_count, Toggle POST /api/autosignal = Dashboard-Button 🎯 SIG (bernstein statt blau, weil gemessen nicht tragfähig; zeigt die Schwelle an), neustart-fest via runtime_state.json. ⚠⚠ ABGESCHALTET am 2026-08-01 (auto_signal=false, via Toggle → runtime_state.json). Zwei Gründe, KEINER davon die Live-Bilanz (n=4 = +21,79 € sagt nichts): (1) gemessen in H1 durchgehend negativ, gebaut gegen die Messung; (2) beide Auto-Pfade konkurrieren um den EINEN Positions-Slot — ein AUTOSIG-Trade blockiert einen Squeeze-Einstieg und umgekehrt. Solange beide laufen, ist die B5-Abbruchprüfung des Auto-Squeeze (s. u.) nicht sauber messbar. ⚠ Er lief bis dahin unbemerkt, obwohl die ini false sagte: ein Dashboard-Toggle hatte true in runtime_state.json persistiert, und das überschreibt die ini beim Start. Der Config-Wächter prüft das jetzt mit. Reaktivieren = Button 🎯 SIG; dann gilt wieder: ≥20 AUTOSIG-Trades, Live-ØR/PF gegen die Erwartung halten (H2-Regime +0,14 vs H1-Regime 0,05). ⚠⚠ NEU GEMESSEN 2026-08-04 nach dem Reversal-Abschalten (backtest_auto_signal_norev.py, 80k M5, 2 Halbjahre, Nacht-Sperre an, keine 15-Min-Regel): Frage des Users — trägt der Auto-Signal-Entry mit den heutigen Änderungen? Antwort: nein, aber deutlich näher dran.

    conf ≥ 75 (LIVE-Schwelle) ØR PF n
    H1 0,047 0,94 678
    H2 +0,120 1,17 710
    H1 bleibt negativ → 2-Stichproben-Regel weiterhin NICHT erfüllt, und kein
    Konfidenz-Schwellwert rettet das (55: 0,103 · 65: 0,111 · 75: 0,047).
    Das Reversal-Abschalten wirkt aber konsistent — Δ ØR durch die Abschaltung:
    conf 55 +0,015/+0,099 · conf 65 +0,001/+0,047 · conf 75 +0,099/+0,087.
    An der Live-Schwelle verbessern sich BEIDE Hälften um ~+0,09; H1 geht von 0,145
    auf 0,047, PF von 0,82 auf 0,94. Erste Bestätigung der Abschaltung auf dem
    GESAMTEN Signal (bisher nur der isolierte Trigger).
    ⚠⚠ Dabei gefunden — die Messung vom 30.07. war strukturell unvollständig:
    backtest_auto_signal.py ruft _build ohne angle= auf → Default 90,0 →
    ad = 0. Damit konnte dort **weder der Reversal-Zweig feuern noch der
    Winkel-Konfidenzterm (±15 Punkte) greifen** — obwohl die Docstring „echte
    _build-Logik inkl. Reversal-Zweig" behauptet. Wirkung: der Winkelterm allein hebt
    H1 bei conf 75 von 0,123 auf 0,047. Deployment-Drift Fall 5 (s. Tabelle).
    Die Originaldatei bleibt unverändert (ihre Zahlen sind dokumentiert); die live-treue
    Variante steht als backtest_auto_signal_norev.py daneben und sollte künftig die
    Referenz sein.
  • ⚠⚠ AUTO-SIGNAL NEU GEMESSEN 2026-08-05 (backtest_auto_signal_v3.py) — ZUM ERSTEN MAL BESTEHT ER, ABER NUR MIT EINEM ANDEREN EINSTIEGSPREIS. Anlass: User-Wunsch nach einer ausführlichen Neumessung „mit den neuen Änderungen". Geändert seit dem 04.08. waren Nacht-Fenster 07→36 und — der eigentliche Punkt — der Ausführungs-Befund vom selben Tag: _confirm_breakout hat ein LEVEL (_pend["level"] = Anker ± k×ATR), an dem eine ruhende Stop-Order liegen könnte. ALLE bisherigen SIG-Messungen sind am Bar-Close eingestiegen. Voller Live-Gate-Stack (echte _build/_confirm_breakout/_room_gate), sequentiell EIN Slot, Live-Dedup (1× je Episode), kanonischer Exit, Echtkosten, 80k M5, 2 Halbjahre:

    Konf A) Market (Bar-Close) H1 / H2 B) am Bestätigungs-Level H1 / H2
    ≥55 0,098 / 0,093 +0,349 / +0,331
    ≥65 0,092 / 0,112 +0,357 / +0,317
    ≥75 (live) 0,050 / 0,189 +0,380 / +0,272
    ≥85 0,118 / 0,234 +0,351 / +0,279
    A ist in ALLEN acht Feldern negativ (bestätigt die drei Vormessungen).
    B besteht in ALLEN acht — Trefferquote 39,6 → 53,2 %, PF 0,83 → 2,20, und
    alle vier Nachbar-Schwellen halten mit (Parameter-Robustheit, an der ORB und
    die SMC-Sweeps gescheitert sind).
    HÄRTETEST Fill-Slippage (Konf≥65) — eine Stop-Order füllt zum Markt, der
    Idealpreis ist die Obergrenze: +0,05 → +0,315/+0,249 · +0,10 → +0,273/+0,197 ·
    +0,20 → +0,148/+0,090 (noch beidseitig positiv) · +0,31 → +0,026/0,031.
    Die letzte Zeile ist die interne Kontrolle: bei Slippage = dem gemessenen
    Median-Abstand (0,311×ATR) konvergiert B gegen A — das Modell prüft sich selbst.
    Der Kipp-Punkt liegt bei ~0,250,30×ATR Fill-Slippage.
    Abstand Bar-Close ↔ Bestätigungs-Level: Median +0,311×ATR (Ø +0,422, Max
    +5,29) — praktisch derselbe Wert wie beim Squeeze live (+0,275). Gleicher
    Mechanismus, gleiche Größenordnung.
    Nacht-Fenster ist hier nahezu irrelevant: 36 (+0,380/+0,272) · 07
    (+0,371/+0,226) · keine Sperre (+0,359/+0,302) — alle drei bestehen, ohne
    Sperre ist H2 sogar am besten. Deckt sich mit backtest_cost_gate.py.
    ⚠⚠ GRENZEN — bewusst NICHTS umgeschaltet:
    (1) Wo LIVE zwischen A und B liegt, ist für SIG NICHT verifizierbar — beim
    Squeeze gab es 34 echte Entries zum Nachrechnen, hier sind es 4. Die Empfehlung
    macht das zwar gegenstandslos (eine ruhende Order IST B per Konstruktion), aber
    behauptet ist es damit nicht.
    (2) Variante A ist STRENGER als live: für die Vergleichbarkeit steigen beide
    Varianten nur im Bar der frischen Bestätigung ein (frisch); live darf
    _check_auto_signal auch später feuern. A ist ohnehin negativ, die Folgerung
    bleibt — aber A ist nicht exakt das Live-Verhalten.
    (3) Operativ deutlich schwieriger als beim Squeeze: die Squeeze-Box steht über
    viele Bars still, _pend dagegen verankert sich neu, sobald der Kurs k×ATR
    dagegen läuft, nach 12 Bars Timeout ODER bei Richtungswechsel. Eine ruhende Order
    müsste ständig nachgezogen werden (Broker-Last, mehr Ablehnungen am
    Mindestabstand) — und wenn sie nicht platzierbar ist, landet man wieder bei A,
    also im gemessen negativen Fall. Wie oft das passiert, ist ungemessen.
    (4) _pend wird aktuell nicht im Snapshot veröffentlicht — das wäre die erste
    Bauarbeit.
    **PENDING-PFAD FÜR SIG GEBAUT & LIVE (2026-08-05, User-Vorgabe),
    [trading] signal_pending_entry=true.** SIG läuft damit auf Variante B statt A.
    ⚠⚠ **Der heikelste Punkt war NICHT die Order, sondern die Kollision der beiden
    Pfade.** trader.pending_orders() liefert ALLE magic-gleichen Orders — zwei
    getrennte Manager (Squeeze/Signal) hätten sich **gegenseitig die Orders
    storniert**, sporadisch und im Log kaum erkennbar. Deshalb **EIN gemeinsamer
    Ziel-Zustand**: engine._pending_ziel() liefert {order_type: preis} + Quelle,
    _manage_squeeze_pending gleicht nur noch ab. VORRANG: Squeeze > Signal — der
    Squeeze ist unabhängig validiert und selten (~23×/Woche), das Signal feuert
    dauernd; bei EINEM Positions-Slot ist die Reihenfolge damit eindeutig.
    Zwei additive Ergänzungen in wave_rec._confirm_breakout waren nötig:
    (1) Das Bestätigungs-Level wird jetzt auch nach der Bestätigung veröffentlicht
    (snap["breakout"]["level"], confirmed=True) — die Nachjagd-Bremse im
    Market-Fallback braucht es genau dann. pending bleibt False, das Frontend rendert
    nur bei pending und ist unberührt. (2) conf = die Konfidenz VOR dem
    Stummschalten: der Rückgabewert setzt conf_pct auf 0 (das Signal gilt ja noch
    nicht), die ruhende Order müsste sonst blind platziert werden. Damit wird die
    Mindest-Konfidenz jeden Tick geprüft und die Order storniert, sobald sie fällt —
    strenger als der Live-Check von vorher (der prüfte nur im Moment des Entries).
    Nachjagd-Bremse auch für SIG (squeeze_max_chase_atr, 0,20): greift die
    Pending nicht, wird nur noch bis 0,20×ATR über dem Bestätigungs-Level nachgekauft —
    der Härtetest zeigt dort +0,148/+0,090, ab ~0,250,30 kippt es.
    Der wandernde Anker ist die operative Hauptlast: _pend verankert sich bei
    jeder k×ATR-Gegenbewegung, nach Timeout und bei Richtungswechsel neu — die Order
    wird dann nachgezogen (Toleranz 0,02×ATR gegen Order-Churn). **Mit 12 Szenarien
    getestet** (Vorrang · reiner Signal-Fall · SHORT · Konfidenz zu niedrig · Wechsel
    Signal→Squeeze mit Storno · wanderndes Level · Toleranz · Gate · Fill · Feature aus
    · Bestätigung eingetreten · Schonfrist). Live verifiziert am Snapshot:
    signal_pending=true, breakout liefert level/conf/confirmed;
    pending_levels leer, weil eine Position offen war = korrektes Verhalten.
    Snapshot-Felder umbenannt: squeeze_pending_levelspending_levels +
    pending_quelle. Zurück: signal_pending_entry=false.
    B4-Pflicht: dieselbe Erfolgskontrolle wie beim Squeeze — der Abstand
    Einstieg↔Level muss gegen 0 gehen. AUTOSIG-Trades sind über setup=AUTOSIG_*
    separierbar.
    ⚠⚠ **ZWEITER Methodik-Fehler, am selben Tag gefunden und die Messung WIEDERHOLT:
    set_entry_room(0.6) fehlte.** WaveRecommender.__init__ setzt
    _entry_room_atr = 0.0, und _room_gate gibt dann rec unverändert zurück —
    der Aufruf sieht im Code aus wie ein Gate, ist aber ein No-op. Betroffen waren
    DREI am 05.08. neu gebaute Skripte (backtest_auto_signal_v3, _metalabel,
    _dist_gated); die Referenz backtest_breakout_gated.py (04.08.) macht es richtig.
    Aufgefallen ist es erst, weil entry_room in der Verteilung mit 0 % auftauchte,
    live aber 35 % blockt. **Neu gerechnet ändert sich die Population stark (n 967→295
    bzw. 1327→524), die Schlussfolgerung aber NICHT:** Market bleibt in 7 von 8 Feldern
    negativ, am Level bestehen weiterhin alle vier Konfidenz-Schwellen
    (ØR +0,392/+0,356 · PF 2,37/2,39), und der Slippage-Härtetest hält bis 0,20×ATR
    (+0,177/+0,118). Die heutige Bau-Entscheidung ist damit gedeckt.
    ⚠ Lehre: ein aufgerufenes Gate ist nicht dasselbe wie ein aktives Gate. Beim
    Kopieren einer Backtest-Struktur gehört die Konfiguration mitkopiert — sonst
    misst man einen Stack, den es nicht gibt.
    Methodik-Fehler beim Bau, gefunden und behoben: der erste Entwurf liess
    pend["level"] nach der Bestätigung über weitere Bars stehen — bei verzögertem
    Einstieg (Slot belegt, Konfidenz zu niedrig) wäre er zu einem Preis eingestiegen,
    den der Markt längst verlassen hatte. Kein Look-ahead, aber unerreichbar, und
    sichtbar an einem unplausiblen Bruch zwischen Konf 65 und 75 (+0,33 → 0,12).
    Behoben über das frisch-Flag. **Lehre: ein Preis, der einmal galt, ist keine
    Einstiegsmöglichkeit — nur der Moment der Auslösung zählt.**
  • ⚠⚠ 15-MINUTEN-REGEL GANZ ABGESCHALTET (adverse_15min_atr = 0, 2026-07-31, User-Entscheidung nach der Messung). Auf die User-Frage „vielleicht die Auto-Close auf 30 min setzen?" wurde die ZEIT-Achse mitgemessen (backtest_adverse15_squeeze.py, 15/30/45/60 min × 0,5/0,8×ATR, BEIDE Populationen, kanonischer Exit, Echtkosten, 2 Halbjahre):

    Zeitpunkt Schwelle Squeeze Δ H1/H2 Wave Δ H1/H2
    15 min 0,5 18 / 34 ← war live 25 / 41
    30 min 0,5 20 / 35 22 / 13
    45 min 0,8 12 / 18 3 / 7
    60 min 0,8 8 / 10 3 / 3
    ALLE 16 Kombinationen sind in BEIDEN Hälften negativ — die Regel wird nur
    monoton weniger schädlich, je später und lockerer sie prüft. Die „beste" Variante
    ist praktisch die, die nie feuert. Es gibt keine Einstellung, bei der sie hilft
    → 30 min hätte den Schaden halbiert, nicht beendet. Deshalb ganz aus.
    ⚠ **Damit ist auch die frühere Einschätzung „auf dem Auto-Signal-Pfad ~neutral"
    korrigiert**: sie stützte sich auf backtest_auto_signal.py (eigenes Exit-Modell,
    +0,001 vs +0,026); mit dem kanonischen Exit sind es 25/41. Sechster Fall desselben
    Musters an einem Tag (gemessen unter X, betrieben unter Y).
    Der Schutz-Stack bleibt vollständig: Broker-SL 2×ATR + Trailing + Time-Stop 120 min.
    Die Squeeze-Ausnahme (unten) bleibt im Code — falls die Regel je reaktiviert wird,
    gilt sie weiterhin nicht für den Squeeze.
  • ⚠ 15-MINUTEN-REGEL: SQUEEZE-TRADES AUSGENOMMEN (gemessen backtest_adverse15_squeeze.py, 2026-07-31). Die Schwelle 0,5×ATR wurde in backtest_auto_signal.py auf WAVE-SIGNAL-Entries kalibriert und danach auf ALLE Bot-Trades angewandt — wieder ein Kalibrierungs-Bruch (gemessen unter X, betrieben unter Y). Auf SQUEEZE-Entries schadet die Regel bei JEDER Schwelle in BEIDEN Hälften: Δ gegen „ohne Regel" 0,3 → 25/38 · 0,5 → 18/34 · 0,8 → 12/39 · 1,2 → 5/18. Monoton — also ist die Regel selbst das Problem, nicht ihre Einstellung. Grund: der Squeeze ist runner-abhängig (43 % Treffer, Ertrag im Tail), ein Schnitt nach 15 min trifft genau den Tail. Ohne Regel H1 54 / H2 +220, mit 0,5 H1 72 / H2 +186. Anlass (real, 2026-07-31): drei Bot-Trades wurden per adverse15 geschlossen — der Auto-Signal-Trade um 11:44 (26,31 €) und die beiden Squeeze-Trades um 20:15/20:36 (13,77 / 28,09 €). ⚠⚠ DIE URSPRÜNGLICHE ANEKDOTE DAZU WAR FALSCH — korrigiert 2026-08-01. Hier stand, der 11:44-Trade sei „gerettet" worden (Kurs lief 32 € weiter dagegen) und die beiden Squeeze-Trades seien „zu früh gekappt" worden (+19,28 / +17,60 € möglich). Beide Zahlen waren maximale Auslenkung (bester bzw. schlechtester danach berührter Kurs) — also das, was der Markt hergab, NICHT das, was der Exit gefangen hätte. Mit dem kanonischen Exit nachgerechnet (core/exit_model.py, M5 aus candles_m1):

    Trade IST (mit Regel) ohne Regel, Trail 1,5
    11:29 AUTOSIG 26,31 21,43 (also NICHT gerettet)
    20:00 SQUEEZE 13,77 23,27 (also NICHT zu früh)
    20:21 SQUEEZE 28,09 25,02
    Über alle 7 Bot-Trades des Tages hätte das Abschalten der Regel 16,42 € gekostet;
    die Regel hat an DIESEM Tag also geholfen. ⚠ **Die Entscheidung, sie abzuschalten,
    bleibt trotzdem richtig** — sie ruht auf backtest_adverse15_squeeze.py (alle 16
    Kombinationen in BEIDEN Hälften negativ), nicht auf einem Tag mit n=3. Lehre: eine
    Einzeltag-Anekdote taugt nicht als Beleg, auch nicht als bestätigende — und
    „maximale Auslenkung" ist keine erzielbare Zahl. Der Gewinn des Tages kam woanders her:
    die Umstellung Trail 1,5 → 1,0 brachte auf denselben 7 Trades +24,86 €.
    Umsetzung: _bot_open_source merkt sich beim Öffnen, WOMIT der Bot eröffnet hat
    (auto_squeeze auto_signal); _check_adverse15 steigt bei auto_squeeze aus.
    Mit 7 Szenarien getestet (Signal/Squeeze/manuell/altes Ticket · zu früh · unter
    Schwelle · im Plus). Für den Auto-Signal-Pfad bleibt sie aktiv — dort ist sie
    gemessen ~neutral (ØR +0,001 mit vs +0,026 ohne), also eine **Versicherung mit
    kleiner Prämie**: hilft im schlechten Regime, kostet im guten. =0 schaltet sie ganz aus.
  • 15-Minuten-Regel ([trading] adverse_15min_atr=0.5, AKTIV, User-Wunsch 2026-07-30 „wenn der Trade nach 15 Minuten gegen die Richtung läuft schließe ihn"): steht ein vom BOT eröffneter Trade 15 min nach Entry ≥ X×ATR_M5 gegen die Richtung → schließen (engine._check_adverse15, closed_by=adverse15, Log ⏱ + Telegram). ⚠ Schwelle GEMESSEN kalibriert, NICHT die ursprüngliche Vorgabe (backtest_auto_ signal.py, 15-Min-Achse): „sobald im Minus" (0,0) ist die SCHLECHTESTE Variante (WR bricht 39→31 %, Netto-ØR 0,067 vs +0,026 ohne Regel) — sie kappt Trades, die sich erholt hätten. 0,5×ATR ist die beste Regel-Variante (H1 0,111→−0,079). Netto über beide Hälften bringt die Regel ~nichts (+0,001 vs +0,026 ohne) → sie ist ein REGIME-Schutz (hilft im schlechten Regime, kostet im guten), bewusst als Sicherheit akzeptiert. 2. unabhängige Bestätigung von backtest_adverse_nosignal.py (dort fiel der Adverse-Exit bei JEDER Schwelle durch). Greift NICHT bei manuellen Trades (_bot_open_ticket-Marker — dort bleibt die Diskretion beim User, gemessen sein Vorteil) und nicht in der Startup-Schonfrist; 1× je Ticket geprüft (_adverse15_done_ticket) genau beim 15-min-Zeitpunkt. Snapshot adverse15_atr/ adverse15_count. 0 = aus. Mit 18 synthetischen Szenarien getestet (9 Entry: Konf-Gate, Dedup, WARTEN-Re-Arm, Nachkauf, Gegen-Position, Cooldown, Toggle, Nacht-Sperre · 9 Adverse: über/unter Schwelle, zu früh, im Plus, SHORT-Richtung, manueller Trade, aus, 1×-je-Ticket, Startup-Grace) — alle korrekt.

  • ⚠⚠ AUTO-SQUEEZE B4-BEFUND 2026-08-01: der EINSTIEG ist intakt, der EXIT hat den Edge gefressen — deshalb NICHT zurückgebaut, aber mit vorab fixierter Abbruchregel. Anlass war die User-Frage zu den negativen Auto-Trades vom 31.07. Der B4-Monitor trennt beides sauber:

    Trefferquote ØR PF
    Mechanik (Regel neu gerechnet, Backtest-Exit = isolierter Einstieg) 46 % +0,102 1,15
    Live (n=31, 17.31.07., Gesamtsystem mit echten Exits) 45 % 0,242 0,49
    Die Trefferquote stimmt praktisch überein (46 vs 45 %) — das Setup findet also
    weiterhin, was es finden soll. Die Lücke von 0,34 R/Trade entsteht komplett im
    Exit: Ø-Gewinn +11,02 € gegen Ø-Verlust 19,83 € (Verhältnis 0,56, ΣEUR
    162,99). Der Squeeze ist runner-abhängig (43 % Treffer tragen nur, wenn die
    Gewinner deutlich größer sind) — genau die Läufer wurden gekappt.
    Alle drei Ursachen sind am 31.07. behoben worden, der Live-Zeitraum liegt also
    fast vollständig VOR den Reparaturen: (a) das P(break)-Modell schloss 91 % aller
    Level-Berührungen = faktisch pauschaler S/R-Close, und der ist 2× als Gewinner-Kappen
    gemessen → nachtrainiert; (b) die 15-Min-Regel schnitt Squeeze-Trades, auf dieser
    Population in beiden Hälften schädlich → aus; (c) Trailing 1,53,0 je nach TF
    statt der gemessenen 1,5 (Deployment-Drift Fall 3) → einheitlich 1,0.
    Ehrlich: die Mechanik liegt mit ØR +0,102 unter dem validierten Band
    +0,14…+0,23 — der Einstieg ist also auch selbst schwächer geworden (Regime). Das ist
    kein Freibrief, sondern der Grund für eine harte Latte statt unbegrenztem Zuwarten.
    B5-ABBRUCHREGEL, VORAB FESTGELEGT (User-Zustimmung 2026-08-01): nach **≥20
    Squeeze-Trades ab dem 01.08.** — **Verhältnis (ØGewinn/ØVerlust) < 1,0 ODER PF < 1
    auto_squeeze=false.** Bewusst VOR der Beobachtung fixiert, damit die Latte
    hinterher nicht verschoben wird; überwacht als Messung squeeze_b5 im
    measurement_reminder.py. Nacht-Anteil der Altdaten: 12 der 31 Trades / 78,92 € —
    die stammen aus der Zeit vor dem Nacht-Guard (seit 24.07. wieder an).
  • ⚠⚠ DEPLOYMENT-DRIFT FALL 7 — DER SQUEEZE WIRD ZU EINEM PREIS GEHANDELT, DEN DIE VALIDIERUNG NIE UNTERSTELLT HAT (gemessen backtest_squeeze_entry.py, 2026-08-05). Anlass: User-Frage zu einem Auto-Trade, der „am höchsten Punkt einer aufsteigenden Kerze" eröffnet wurde (05.08. 08:33:20 — Level 75,757, Einstieg 75,822, also über dem Hoch 75,808 der laufenden M5-Kerze; die schloss dann bei 75,693). backtest_breakout_squeeze.py steigt AM LEVEL ein (sim(lvl, …)). Live schickt _check_auto_squeeze eine MARKET-Order, sobald state=='active' — der Kurs ist dann schon ein Stück weiter. Aus 34 echten Live-Entries (Logs 16.07.05.08., Level aus der Log-Zeile × Einstiegspreis aus der DB): Median +0,275×ATR über dem Level, Mittel +0,686 · 75 % +0,744 · Max +4,339; nur 18 % liegen innerhalb 0,10×ATR, 53 % über 0,30×ATR. Was dieser Preisunterschied kostet (80k M5, 2 Halbjahre, sequentiell EIN Slot, kanonischer Exit, Echtkosten):

    Variante H1 ØR / ΣR H2 ØR / ΣR WR
    A Einstieg AM LEVEL (Backtest-Annahme) +0,456 / +369,8 +0,611 / +777,8 56/62 %
    B Einstieg am Bar-Close (≈ live) 0,158 / 125,9 0,006 / 7,6 37/42 %
    **Allein der Einstiegspreis dreht das Setup — 0,61 ØR in BEIDEN Hälften, WR bricht
    von 56 auf 37 %.** Modell B ist dabei nicht pessimistisch: sein Median-Abstand
    (0,38×ATR) liegt nah am LIVE gemessenen (0,28×ATR); der Live-Betrieb liegt also
    ~80 % des Weges von A nach B, also grob bei breakeven bis leicht negativ
    nicht bei den dokumentierten +0,14…+0,23.
    ⚠⚠ Das ordnet den B4-Befund vom 01.08. NEU ein. Dort stand: „Mechanik +0,102 vs.
    Live 0,242 → die Lücke entsteht im EXIT". Die Mechanik-Zahl unterstellt aber
    denselben Level-Einstieg. Der Einstiegspreis erklärt den Großteil der 0,34-R-Lücke
    es war überwiegend der ENTRY, nicht der Exit.
    BEIDE naheliegenden Reparaturen fallen durch (Regel vorab fixiert: ΣR und ØR
    in beiden Hälften > B, Nachbarn ebenso):
    · Abstands-Bremse („überspringen, wenn der Kurs schon > X×ATR über dem Level
    steht", X = 0,10…0,40) — alle 5 Schwellen durch. Kontraintuitiv, aber konsistent:
    die nahen Ausbrüche sind pro Trade die SCHLECHTEREN (0,10×ATR → ØR 0,254), weil ein
    gerade eben überschrittenes Level oft ein Fehlausbruch ist (54 % der Box-Ausbrüche
    kehren binnen 3 Bars zurück, backtest_consolidation.py). Die Bremse senkt den
    Gesamtverlust nur, indem sie weniger handelt.
    · LIMIT-Order am Level (Retest, 3/6/12 Bars Gültigkeit, ±0,1×ATR Puffer) —
    alle 5 Varianten SCHLECHTER als Market (ØR 0,14…−0,27 gegen 0,006…−0,158);
    151430 Ausbrüche werden dabei verpasst. Deckt sich mit dem Retest-Befund aus
    backtest_consolidation.py.
    Schlussfolgerung: Variante A war nie erreichbar — sie unterstellt einen Fill zu
    einem Preis, den der Markt bereits verlassen hat. Die Frage ist damit nicht „wie
    steigt man klüger ein", sondern ob der Auto-Squeeze überhaupt bleibt. Das ist die
    bereits fixierte B5-Abbruchregel (≥20 Squeeze-Trades ab 01.08.; Verhältnis < 1,0
    ODER PF < 1 → auto_squeeze=false) — dieser Befund stärkt sie erheblich.
    Nichts abgeschaltet — Autonomie-Schalter sind User-Entscheidungen.
    ⚠ Grenzen: die Live-Stichprobe ist n=34 mit genäherter ATR-Zuordnung; A und B sind
    zwei Modell-Enden, der Live-Betrieb liegt dazwischen (näher an B).
    **LÖSUNG GEFUNDEN — RUHENDE STOP-ORDER STATT MARKET (Variante E, gemessen
    2026-08-05; Anstoß aus der Web-Recherche: „don't chase breakouts with market orders;
    place orders just beyond the breakout level").** Eine PENDING BUY_STOP/SELL_STOP
    liegt bereits am Ausbruchs-Level, WÄHREND der Squeeze armed ist (das Level ist da
    schon bekannt = Box-Grenze), und wird vom Ausbruch selbst ausgelöst — anders als die
    Limit-Order (D) wartet sie NICHT auf einen Rücklauf, verpasst also nichts.
    Fill-Annahme H1 ØR / PF / ΣR H2 ØR / PF / ΣR
    --- --- ---
    Level exakt (= A) +0,456 / 2,65 / +370 +0,611 / 3,56 / +778
    Level +0,05×ATR +0,397 / 2,28 / +322 +0,553 / 3,06 / +697
    Level +0,10×ATR +0,355 / 2,07 / +288 +0,509 / 2,76 / +639
    Level +0,20×ATR (pessimistisch) +0,244 / 1,62 / +196 +0,426 / 2,25 / +531
    Market/Live (= B) 0,158 / 0,74 / 126 0,006 / 0,99 / 8
    **Selbst mit 0,20×ATR unterstellter Fill-Slippage — dem ~9-fachen des Median-Spreads
    — bleibt es in BEIDEN Hälften klar positiv.**
    ⚠⚠ Der Schlüssel ist nicht der Median, sondern der SCHWANZ. Live ist der
    Median-Abstand +0,275×ATR, der Mittelwert aber +0,686 (Max +4,339). Eine
    Stop-Order deckelt den Einstiegspreis konstruktiv; die Market-Order auf dem
    5-s-Poll tut das nicht. Die Extremfälle (+3,5 / +4,3×ATR) stammen erkennbar aus
    blockiert-dann-freigegebenen Ausbrüchen: feuert der Squeeze, während eine
    Position offen ist (oder Nacht-/News-Fenster), wird KEIN Dedup-Marker gesetzt — der
    Entry kommt später zum dann gültigen Marktpreis, u. U. weit hinter dem Level.
    **GEBAUT & LIVE (2026-08-05, User-Vorgabe „setze alle Vorschläge um"),
    [trading] squeeze_pending_entry=true.** engine._manage_squeeze_pending (im
    _pos_loop, VOR dem Market-Pfad) hält beide Box-Grenzen mit ruhenden
    BUY_STOP/SELL_STOP bestückt, solange der Squeeze armed ist:
    trader.place_stop/cancel_pending/pending_orders (Größe und SL aus denselben
    Funktionen wie die Market-Order, nur bezogen auf den Trigger-Preis — sonst wäre der
    SL-Abstand ein anderer als validiert). OCO-Ersatz: füllt eine Seite, existiert
    eine Position → die Bedingung „soll liegen" wird falsch und die Gegenseite wird im
    selben Tick storniert (MT5 kennt kein natives OCO).
    Der kritische Punkt bei einer RUHENDEN Order: die Gates. Nacht-Fenster,
    News-Blackout, Circuit-Breaker und der An/Aus-Schalter waren bisher Laufzeit-Checks
    im Market-Pfad; eine liegende Order würde sie schlicht ignorieren. Deshalb neu
    engine._squeeze_guard() als EINE Quelle für beide Wege — jeden Tick neu
    bewertet, und sobald ein Gate zuschlägt, werden die Orders storniert. Zwei
    Kopien derselben Gates wären genau die Divergenz, die hier siebenmal als
    Deployment-Drift dokumentiert ist.
    Market bleibt nur noch FALLBACK — mit Nachjagd-Bremse (squeeze_max_chase_atr
    = 0,20): greift keine Pending (Broker-Mindestabstand, Squeeze sprang direkt auf
    active, Order-Fehler), wird nur noch eingestiegen, solange der Kurs **höchstens
    0,20×ATR** über dem Level steht — genau der Bereich, der gemessen in beiden Hälften
    positiv ist (+0,244/+0,426). Darüber: Ausbruch auslassen, Log „⏭ Auto-Squeeze
    übersprungen". _squeeze_last_sig wird dabei NICHT gesetzt → läuft der Kurs zum
    Level zurück, darf der Entry noch kommen.
    ⚠ Weitere Fallstricke, alle behandelt: Broker-Mindestabstand
    (trade_stops_level) wird VOR dem Senden geprüft und der Fehler nur bei ÄNDERUNG
    geloggt (sonst 1 Zeile/Sekunde, wenn der Kurs an der Box-Grenze klebt) · Order auf
    der falschen Marktseite wird abgelehnt · Toleranz 0,02×ATR, damit nicht jede
    ATR-Zuckung die Orders löscht und neu setzt · Startup-Schonfrist gilt auch hier.
    Mit 10 Szenarien getestet (setzen · Idempotenz beim zweiten Tick · Fill →
    Gegenseite weg · Squeeze klärt sich · Gate schlägt zu · Schonfrist · Feature aus ·
    Box verschiebt sich · Broker lehnt ab · Nachjagd-Bremse in beide Richtungen).
    Live verifiziert am Snapshot: squeeze_pending=true, squeeze_max_chase_atr=0.2,
    squeeze_pending_levels (leer, weil eine Position offen war = korrektes Verhalten).
    Zurück zum alten Verhalten: squeeze_pending_entry=false.
    B4-Pflicht + Werkzeug: der Live-Abstand Einstieg↔Level muss jetzt gegen ~0
    gehen (vorher Median +0,275×ATR). Dafür gibt es jetzt
    analyze_squeeze_entry_gap.py (führt die zwei nötigen Quellen zusammen: das
    Level steht NUR im Log, der Fill NUR in trades) — mit Vorher/Nachher-Split am
    Umbau-Stichtag. ⚠ Die Erfolgsmeldung ist paradox: eine per Pending gefüllte
    Order erzeugt keine AUTO-SQUEEZE-ENTRY-Logzeile (die entsteht nur im
    Market-Fallback). Squeeze-Trades ohne Log-Treffer sind also der Erfolg, nicht
    ein Fehler — das Skript weist sie deshalb getrennt aus. Überwacht als Messung
    squeeze_entry_gap im measurement_reminder.py (≥12 Squeeze-Trades ab dem
    Umbau; Stichtag exakt 05.08. 08:55, nicht Mitternacht — der 08:33-Trade lief noch
    über Market). Bleibt der Abstand bei ~0,3×ATR, füllt die Pending nicht → Broker-
    Mindestabstand und die Logzeile „Squeeze-Pending nicht platzierbar" prüfen.
    Zahlen-Korrektur (05.08.): zuerst standen hier Median +0,306 / Ø +0,741 —
    das war eine Scratchpad-Auswertung mit FESTEM Stundenversatz. `analyze_squeeze_
    entry_gap.pyrechnet DST-korrekt überzoneinfo` und liefert +0,275 / +0,686
    (n=34 unverändert). Gültig sind die zweiten Werte; die Schlussfolgerung ändert
    sich nicht.
    • ⚠⚠ EIN PENDING-FILL LIEF AN DER GESAMTEN BUCHFÜHRUNG VORBEI (Fix 2026-08-06, User-Auftrag „überprüfe die heutigen autotrades"). Der Befund kam nur zustande, weil die Frage überhaupt gestellt wurde: die DB meldete für den 06.08. null Bot-Trades, das Log dagegen vier gefüllte SQZ-STOP-Orders. Beides stimmte. Mechanismus: eine ruhende Stop-Order füllt im Broker, nicht über engine._open. Die Position wird erst später von trader._refresh_locked per magic-match adoptiert — und dieser Pfad ruft history.log_trade_open() nur mit Ticket/Symbol/Richtung/Lots/Preis auf. Es fehlten damit alle Felder, die der Market-Pfad setzt: setup, sl_at_entry und die 16 ctx_*-Spalten der Telemetrie-Stufe 1. Vier reale Trades waren so von manuellen nicht unterscheidbar. ⚠ Die Tragweite ist größer als „ein NULL-Feld": genau die Messpipeline, die den Pending-Umbau vom 05.08. kontrollieren soll, war blind für ihn — B4-Monitor, squeeze_b5 (Abbruchregel!) und squeeze_entry_gap zählten null Trades, und sl_at_entry fehlte ausgerechnet bei den Trades, an denen die M5-SL-Umstellung zu prüfen war. Deployment-Drift Fall 8, und die erste Variante, bei der nicht die Strategie driftet, sondern ihre Beobachtbarkeit. Ein Umbau, der den Bestellweg ändert, ändert auch, welcher Code die Bücher führt — das gehört beim Umbau geprüft. Fix: history.tag_bot_trade(ticket, setup) (UPDATE nur wenn setup IS NULL, stiller Fail-open — core/history.py hat bewusst keinen Logger) + engine._pending_tickets (Ticket → Quelle, beim Platzieren gesetzt, beim Stornieren/Taggen entfernt) + _check_pending_fill() im _pos_loop VOR dem Pending-Manager. Die Zuordnung ist eindeutig, weil bei einem Pending-Fill die Positions-Nummer identisch mit der Order-Nummer ist (an allen vier Broker-Datensätzen des 06.08. verifiziert) — kein Zeitfenster-Raten. Der Fix setzt zusätzlich _bot_open_ticket/_bot_open_source und die Entry-Zähler, damit 15-Min-Regel, B4-Monitor und Snapshot wieder dieselbe Wahrheit sehen. Mit 6 Szenarien getestet, darunter die beiden gefährlichen: fremde/manuelle Position wird NICHT getaggt, und derselbe Fill zählt bei wiederholten Ticks nur einmal. Die vier Trades des 06.08. wurden nachgetragen (DB-Backup oil_widget_history.db.bak-2026-08-06). ⚠ Bewusst NICHT nachgetragen: ctx_* und sl_at_entry. Die stehen für den Zustand im Moment des Fills und lassen sich nachträglich nur rekonstruieren, nicht messen — eine rekonstruierte Zahl in einer Telemetrie-Spalte wäre später von einer gemessenen nicht mehr unterscheidbar. Ab jetzt sind sie für Pending-Fills weiterhin leer; wer sie braucht, muss den Kontext beim Platzieren der Order einfrieren (offen, nicht gebaut).
    • ⚠ OFFEN: Pending-Churn im SIGNAL-Pfad (gefunden 06.08., nicht behoben). Am 06.08. wurde dieselbe SELL_STOP viermal in 76 s gesetzt und storniert — bei identischem Level 74,981, es wandert also nichts. Die Ursache steht in recommendations.block_reason: der Grund pendelt zwischen entry_room und breakout_pending. Sobald das Raum-Gate den Kurs kurz stummschaltet, liefert _pending_ziel() {} und der Manager storniert; eine Sekunde später ist das Gate wieder offen und die Order wird neu gelegt. ⚠ Das ist wieder Deployment-Drift, nicht nur Log-Lärm: backtest_auto_signal_v3.py hat den Pending-Vorteil (ØR +0,35/+0,32 gegen 0,09/0,11 am Markt) unter der Annahme gemessen, dass die Order durchgehend am Level liegt. Flackert sie im 5-s-Takt, geht genau der Ausbruch verloren, der in eine Storno-Lücke fällt — und das ist der Fall, für den sie da ist. Lösungsrichtung (ungebaut): harte von weichen Storno-Gründen trennen. HART (sofort stornieren) = Position offen · Startup-Schonfrist · Guard (Nacht/Circuit/ News) · Richtungswechsel · Level wandert über die Toleranz · Feature aus. WEICH (Order liegen lassen) = kurzzeitiges Stummschalten durch entry_room/min_conf/ breakout_pending. Argument dafür: die Order füllt nur, wenn der Kurs das Level bricht — dann hat er sich vom Gegenlevel wegbewegt, die Raum-Bedingung ist im Fill-Moment also besser als im Flacker-Moment. Argument dagegen: das ist eine Aussage über den Einstiegs-Filter und damit nicht rein infrastrukturell — vor dem Bau messen (Variante „Order bleibt liegen" gegen „Order flackert").
  • Auto-Squeeze-Entry = LIVE AUTONOM (User-Vorgabe 2026-07-16, [trading] auto_squeeze=true, Default FALSE): der Bot eröffnet selbständig eine echte Order, sobald der gemessen-validierte Squeeze-Ausbruch feuert (wave.squeeze. state=='active') UND flat ist — NICHT auf dem Wave-Signal (das ist der Dry-Run-Verlierer), sondern nur auf dem 2-Stichproben-positiven Squeeze. engine._check_auto_squeeze im _pos_loop (1 s): 1× je Ausbruch (Dedup über (dir, level); re-armt, wenn der Squeeze klärt), öffnet via open_long/short (→ Trailing an, Emergency-Stop armiert 2 % beim nächsten Tick), Log 🚀 AUTO-SQUEEZE- ENTRY + Telegram + Fehlschlag-Push. Snapshot auto_squeeze/squeeze_entry_count; Toggle POST /api/autosqueeze (set_auto_squeeze) = Dashboard-Button 🚀 BRK (blau=AN, ersetzt den alten Dry-Run-AUTO-Button). Dead-Hour-Guard = AN (User-Vorgabe 2026-07-24, [trading] auto_squeeze_skip_night=true) — Fenster seit 2026-08-05 auf 3,4,5,6 VERKÜRZT ([trading] auto_squeeze_night_hours, User-Vorgabe „verkürze auf die wirklich toten Stunden"; war fest 07): der Bot eröffnet in diesen Stunden KEINE Squeeze-Trades (_squeeze_night_hours_check_auto_squeeze return; kein Entry UND kein Reverse; leere Liste = aus). ⚠ _SQUEEZE_NIGHT=(0..7) bleibt bestehen, ist aber NICHT mehr das Gate — es ist jetzt nur noch die STATISTISCHE Nacht-Definition für die Entry-Checkliste (dort gilt die Kostenfalle weiterhin, s. u.) und den Tag/Nacht-Split im B4-Monitor (damit der Vergleich mit den Altdaten stabil bleibt). Der Auto-Signal-Pfad nutzt dasselbe neue Fenster (ist ohnehin aus). ⚠⚠ Die Verkürzung ist NICHT ertrags-begründet, sondern preis-begründet — gemessen ist keine einzige Nachtstunde in beiden Halbjahren negativ, jede geblockte Stunde kostet Ertrag (Details beim Fallbeispiel unten). 36 ist schlicht das günstigste Fenster mit noch ≥4 h Schutz: Preis ΣR 12,5/62,9 R gegen 42,4/242,6 bei 07 = ~27 % der bisherigen Kosten. Freigegeben sind 0, 1, 2 und 7 Uhr. ⚠ Std 0 ist dabei die beste Stunde des ganzen Tages für den Squeeze (ØR +1,48/+1,51) — sie zu blocken war der teuerste Einzelposten. Live verifiziert am neuen Snapshot-Feld squeeze_night_hours + Startup-Log „Auto-Squeeze-Nachtsperre: 3:00, 4:00, 5:00, 6:00". Zurück auf den alten Stand: auto_squeeze_night_hours = 0,1,2,3,4,5,6,7. Doppelt begründet: (1) gemessen unrentabel (backtest_realcosts.py, Spread÷Nacht- ATR 0,320,50×ATR, beide Halbjahre negativ = Kostenfalle) und (2) kein Trade soll unbeaufsichtigt nachts feuern, während der User schläft — Auslöser: „müsste automatisch schließen wenn ich nicht da bin, z. B. nachts". Der Guard LÖST das, indem er den Trade gar nicht erst öffnet; tagsüber (User eher erreichbar) läuft der bestehende Auto-Close-Stack (Broker-SL 2×ATR broker-seitig + Trailing + Time-Stop + S/R-Close). ⚠ Historie: bis 2026-07-17 war der Guard AUS (User wollte rund um die Uhr), am 2026-07-24 auf User-Wunsch zurück auf AN. =false schaltet ihn wieder aus (dann auch nachts, unbeaufsichtigt). Einen engen Schutz-%-Stop NUR für Squeeze wurde bewusst NICHT gebaut (enger %-Stop unter Margin-Sizing wird vom Squeeze- Rauschen ausgelöst, 48,94-€-Tag — der Squeeze braucht Raum). ⚠⚠ FALLBEISPIEL 05.08.2026 — der Guard hat 5 Ausbrüche geblockt, darunter den Einstieg direkt vor einem 1,8-$-Rutsch (User-Frage „warum ist der Squeeze nicht eingestiegen?"). Nachgerechnet mit der ECHTEN _squeeze_one auf den M5-Bars (nicht aus dem Log geschlossen — der Squeeze-ZUSTAND wird nirgends geloggt, nur der Entry): zwischen 02:00 und 07:30 Berlin feuerte 5× active, alle in 07 Uhr. Der Bot war ab 01:15 flat, die Nachtsperre war also der ALLEINIGE Blocker (kein Cooldown, keine offene Position). Kursverlauf: Hoch 76,373 um 03:30 → Tief 74,548 um 04:40 = 1,825 $ in 70 min; der Ausbruch 04:00 SHORT @ 76,11 lag praktisch auf dem Hoch. Mit dem kanonischen Exit (exit_model.LIVE, Echtkosten aus dem Bar-Spread — bewusst NICHT „maximale Auslenkung", s. die Korrektur bei der 15-Minuten-Regel): 02:00 SHORT +3,28 R · 03:00 LONG 0,24 · 04:00 SHORT +1,36 R · 06:15 LONG 0,60 · 07:30 SHORT 0,24 → Σ +3,56 R ≈ +95 € bei 2,66 Lots. ⚠ Trotzdem kein Argument gegen den Guard: n=5 an EINEM Tag, und 92 % davon stecken in zwei Trades — ohne den 02:00er bleiben +0,29 R ≈ +12 €. Ein Einzeltag taugt nicht als Beleg, auch nicht als widerlegender (dieselbe Lehre wie am 01.08.). Die Guard-Begründung ruht auf 2 Halbjahren; dazu kommt der nicht-ökonomische Grund (keine unbeaufsichtigten Nacht-Trades), der von Zahlen gar nicht berührt wird. Ein messbarer Ansatz ergibt sich aber: die Nacht-Begründung ist eine Kosten-Aussage (Spread÷ATR 0,320,50). In dieser Nacht lagen die realen Kosten bei 0,110,22×ATR, also klar UNTER der Falle — weil der ATR hoch war. Ein kosten-basiertes statt stunden-basiertes Gate („blocke, wenn Spread/ATR > X") würde genau solche Nächte durchlassen und ruhige weiter sperren. ⚠⚠ GEMESSEN am selben Tag (backtest_cost_gate.py, 80k M5, 2 Halbjahre, kanonischer Exit, Echtkosten, ECHTE _squeeze_one) — und das Ergebnis fällt anders aus als die Frage lautete. (a) Kein Kosten-Gate ersetzt das Stunden-Gate nach der vorab fixierten Regel (ΣR und ØR in beiden Hälften ≥ Stunden-Gate, plus Nachbar-Schwellen): alle 7 Schwellen 0,150,50 fallen durch. (b) Der eigentliche Befund: das STUNDEN-Gate selbst kostet Geld — in BEIDEN Hälften. Sequentiell mit EINEM Positions-Slot (die konto-relevante Rechnung; ein geblockter Nacht-Trade gibt den Slot frei):

    Variante H1 ΣR H2 ΣR
    ohne Gate +369,5 +778,1
    Stunden-Gate 07 (live) +327,1 +535,5
    Kosten-Gate 0,30 +356,3 +760,1
    Kosten-Gate 0,35 +369,3 +781,9
    → das Live-Gate kostet 42 R (H1) und 243 R (H2). Die von ihm GEBLOCKTEN
    Ausbrüche sind in beiden Hälften positiv (ØR +0,496/+0,589) — in H1 sogar
    besser als die behaltenen (+0,382). Es wirft gute Trades weg.
    ⚠⚠ **MECHANISMUS BELEGT — die Nachtsperre ist auf der FALSCHEN Population
    begründet (Deployment-Drift, Fall 6).** Die Nacht-Kostenfalle ist echt und
    reproduziert (backtest_realcosts.py): über ALLE Nacht-Bars Median 0,333 /
    Ø 0,387 ×ATR gegen Tag 0,163/0,185. Aber der Squeeze feuert dort gar nicht
    er verlangt einen Ausbruch, also anziehenden ATR. Seine Nacht-Ausbrüche kosten nur
    Median 0,183 / Ø 0,191 (Tag 0,156/0,162), liegen also praktisch auf Tag-Niveau
    und weit unter der Falle. **Der Squeeze selektiert sich aus der Kostenfalle
    heraus.** Gemessen wurde die Falle am Wellensignal, angewandt wird das Gate auf den
    Squeeze — dieselbe Fehlerklasse wie bei der 15-Minuten-Regel (auf Wave-Entries
    kalibriert, auf alle Bot-Trades angewandt, auf Squeeze gemessen schädlich).
    ⚠ Nebenbefund: ein Kosten-Gate TRENNT tatsächlich in die richtige Richtung
    (bei 0,30 hat die geblockte Gruppe H1 WR 44,9 % / ØR +0,175 gegen +0,485 behalten)
    — nur ist auch sie noch positiv, deshalb kostet jedes Blocken ΣR. Bester ΣR-Wert
    ist 0,35, und der blockt praktisch nichts mehr (1 bzw. 10 Trades) = faktisch „kein
    Gate".
    Methodik-Lehre zur eigenen Regel: „ΣR UND ØR ≥ Referenz" ist als
    Entscheidungsregel schlecht spezifiziert, wenn die Varianten unterschiedlich viele
    Trades handeln — ØR und ΣR laufen dann mechanisch gegeneinander (0,30 schlägt das
    Stunden-Gate im ΣR um +29/+225, verfehlt das ØR in H2 um 0,004). Die Regel wurde
    nicht nachträglich gelockert; stattdessen ist der Vergleich „Gate gegen KEIN
    Gate" separat ausgewiesen — er stand ohnehin in derselben Tabelle.
    ⚠⚠ DREI Gründe, warum daraus NICHT automatisch „Nachtsperre aus" folgt:
    (1) Slippage ist nicht modelliert — nachts ist das Buch dünner, der gemessene
    Tail liegt bei 0,75×ATR über den Stop hinaus (analyze_execution.py). Das trifft
    Nacht-Trades selektiv und ist die größte Bedrohung des Befunds. (2) **Live sagt das
    Gegenteil**, wenn auch dünn: von den 31 Squeeze-Trades des B4-Fensters waren 12
    Nacht-Trades mit 78,92 € — allerdings alle VOR den Exit-Reparaturen vom 31.07.
    (P(break) nachtrainiert, 15-Min-Regel aus, Trail 1,5→1,0). (3) Der
    Beaufsichtigungs-Grund („kein Trade soll feuern, während ich schlafe",
    User-Vorgabe 2026-07-24) ist eine Präferenz und wird von keiner Zahl berührt.
    **Konsequenz (User-Vorgabe 2026-08-05 „verkürze auf die wirklich toten
    Stunden"): Fenster von 07 auf 36 verkürzt** — Details oben beim Guard.
    ⚠⚠ **Die Prämisse der Vorgabe hält der Messung nicht stand: es gibt KEINE toten
    Stunden.** Je Stunde einzeln, sequentiell, beide Halbjahre — **alle 24 Stunden
    sind in beiden Hälften positiv**, keine einzige ist beidhälftig negativ. Die
    ruhigsten Nachtstunden (n über beide Hälften): 5 (41 Ausbrüche) · 4 (56) · 3 (61)
    · 6 (62); die aktivsten 7 (75) · 0/2 (72) · 1 (71). ⚠ **Std 0 ist die stärkste
    Stunde des gesamten Tages** (ØR +1,483/+1,510, ΣR +22,2/+86,1) — ausgerechnet sie
    lag im alten Gate.
    Damit ist die Auswahl **keine Ertrags-Optimierung, sondern eine Preis-Minimierung
    für eine Präferenz** — und deshalb auch nicht overfitting-gefährdet: es wird keine
    prädiktive Behauptung aufgestellt. Preisliste (ΣR gegen „kein Gate"):
    Fenster Std Preis H1
    --- --- ---
    45 2 3,9
    46 3 6,8
    36 (gewählt) 4 12,5
    25 4 12,8
    07 (alt) 8 42,4
    Die 4-Stunden-Untergrenze ist gesetzt, nicht gemessen — darunter wäre der
    Schutzzweck hinfällig. Wer weniger Schutz will, nimmt 45 (fast gratis); wer den
    alten Stand will, trägt 0,1,2,3,4,5,6,7 ein.
    Was die Verkürzung NICHT ausräumt: die drei Vorbehalte oben gelten
    unverändert — vor allem die nicht modellierte Slippage (dünnes Nacht-Buch,
    Tail 0,75×ATR über den Stop). Die freigegebenen Stunden 0/1/2/7 liegen mitten in
    der dünnsten Zeit. **B4-Pflicht: die Squeeze-Trades aus 0/1/2/7 Uhr getrennt
    mitzählen** — bleiben sie hinter der Erwartung (ØR ~+0,5 R), war die Slippage
    der Grund und das Fenster gehört zurück auf 07.
    **Stop-&-Reverse = AUS (Default, [trading] auto_squeeze_reverse=false,
    _squeeze_reverse; User-Vorgabe 2026-07-17):** War kurz scharf, dann abgeschaltet —
    nur der flat-Entry ist gemessen-validiert, der Reverse ist unbelegt, verdoppelt
    Kosten und dreht am Ausbruch-Extrem (real 2026-07-17 18:55: WAVE_REV_SHORT gegen
    einen News-Rally-LONG-Ausbruch bei 29,64 € geschlossen, dann LONG am Erschöpfungs-Top
    82,32 — Elliott „impulse_up/5 exhausted" im selben Moment). Bei false bleibt ein
    Gegen-Trade unangetastet (Drehen = manuelle Entscheidung); _squeeze_last_sig
    wird dann NICHT gesetzt → schließt der Gegen-Trade (SL/manuell), kann der flat-Entry
    auf denselben Ausbruch noch feuern. Bei true (historisch, reaktivierbar): läuft ein
    Trade GEGEN den Ausbruch (auch manueller/Fremd-Trade!) → sofort `close(reason=
    "squeeze_reverse")(Log 🔄 + Telegram, Dedup_squeeze_reverse_sig`), nächster flat-Tick
    dreht. ⚠ NICHT gemessen (der Squeeze-Backtest nahm flat-Entry an) — deshalb Default aus.
    **Exit-only-Variante („Gegen-Position beim Ausbruch schließen, OHNE Re-Entry") =
    VERWORFEN (gemessen backtest_squeeze_exit.py, 2026-07-22, User-Idee „Breakout
    schneller schließen"):** die sicherere Halb-Variante (nur schließen, nicht drehen)
    fällt ebenfalls durch — in BEIDEN Hälften schlechter als der Live-Exit (SL 2×ATR+Trailing):
    ΔΣR H1 15 (immer)/5 (nur im Minus), H2 27/29. Grund: der Gegen-Ausbruch ist oft ein
    Fehlausbruch/Whipsaw → die Position prallt zurück, der frühe Close kappt Trades, die
    sich erholt hätten. Der 07:38-Fall (LONG lief zum SL während SHORT-Ausbruch) war Varianz.
    Fazit: Squeeze taugt als ENTRY (validiert), NICHT als Gegen-EXIT. Der einzige
    Speed-Hebel bleibt die schnellere Erkennung (Variante A, ~5 s).
    ⚠⚠ **DRITTE MESSUNG 2026-08-05 (backtest_squeeze_exit3.py) — jetzt mit dem
    KANONISCHEN Exit, und der Grund für das Nein ist ein anderer als gedacht.**
    Anlass: Trade T=49423144 (manueller SELL 3,49 @ 75,012) lief in 14 min 1,135 $
    ins Minus und endete am Initial-SL bei 343 €; User-Wunsch: „der Breakout
    hätte den Trade früher schließen sollen", nachgeschärft zu „bei einem Squeeze den
    SL sofort eng setzen".
    Was an dem Tag wirklich passierte: beim Einstieg war der Squeeze **active
    SHORT** — er stand FÜR den Trade. Die Empfehlung war WARTEN (block=entry_room),
    der Einstieg manuell. Der Gegen-Ausbruch (armed LONG @ 75,729) wurde erst ~35 min
    später aktiv; ein Close dort hätte ≈219 € statt 343 € bedeutet (~124 €).
    **Gemessen (beide Varianten, kanonischer Exit, Echtkosten, 2 Halbjahre, zwei
    Populationen — 2.365 Squeeze- und 1.566 Wellen-Entries):**
    Variante Auslösungen H1/H2
    --- ---
    sofort schließen (Squeeze-Entries) 1 / 1 von 809 bzw. 1.274
    enger SL 0,10,5×ATR (Squeeze-Entries) 0 / 0
    sofort schließen (Wellen-Entries) 8 / 8 von 386 bzw. 835
    enger SL (Wellen-Entries) 23 / 23
    DAS IST DER EIGENTLICHE BEFUND: der Mechanismus feuert praktisch NIE.
    Nicht weil die Idee schlecht wäre, sondern weil sie **konstruktionsbedingt zu spät
    kommt**: ein Gegen-Ausbruch verlangt eine NEUE Kompression (Box über 10 M5-Bars =
    50 min), der Ausbruch selbst weitet die Box aber gerade erst. Die mittlere
    Haltedauer liegt bei 1218 min. Der Gegen-Ausbruch trifft also in rund **0,1 %
    (Squeeze) bzw. 1 % (Welle)** der Trades ein — der Fall des Users war einer davon.
    Die Kennzahl-Unterschiede in der Tabelle sind entsprechend Rauschen aus einer
    Handvoll Fällen; keine Variante besteht auf beiden Populationen.
    Auch die abgeschwächte Form („enger SL statt Close") ändert nichts — sie
    adressiert zwar genau den dokumentierten Einwand (Gegen-Ausbrüche sind oft
    Fehlausbrüche), kann ihn aber nicht ausspielen, wenn sie 0-mal auslöst.
    ⚠ Und sie wäre von dieser Simulation ohnehin überschätzt: ein enger Stop wird
    von einer Bar-Simulation systematisch zu gut gerechnet (s. Auflösungs-Artefakt,
    analyze_trail_resolution.py, 05.08.).
    **Damit zum DRITTEN Mal verworfen — und diesmal mit einer Begründung, die nicht
    vom Exit-Modell abhängt.**
    **Re-Test unter Vola-Regimen = ERNEUT VERWORFEN (gemessen
    backtest_squeeze_exit_vol.py, 2026-07-24, User-Frage „Markt gerade sehr volatil —
    wirklich nicht profitabel?"):** Verschärfter Test mit echten SQUEEZE-Entries (statt
    EMA-Crosses), 4 Varianten (immer/nur-im-Minus/nur-Verlust>0,5×ATR), ATR-Terzil-Split
    je Hälfte + Extra-Fenster letzte ~30 Tage. Ergebnis: Effektgröße überall ~0
    (±0,0020,005 R/Trade bei ~1150 Trades/Hälfte), **Vorzeichen kippt zwischen den
    Regimen** (MODL: H1-Hochvola 1,6 / H2-Hochvola +3,0 / letzte 30 Tage 0,3) → nach
    der vorab festgelegten Regel (beide Hälften UND Hoch-Vola-Terzil ≥ BASE) fällt JEDE
    Variante durch. Kernbefund: Worst-Case ist in ALLEN Varianten identisch
    (2,33/2,47 = SL-Deckel) — der Gegen-Squeeze-Exit reduziert das Tail-Risiko um
    exakt 0, weil bei echten schnellen Brüchen immer der SL zuerst greift; er kappt nur
    langsame Kriecher, von denen sich >50 % erholt hätten. (Methodik-Hinweis: absolute
    ØR pessimistischer als im validierten Squeeze-Backtest — Entry am Bar-Close statt
    am Level, keine Flat-Bedingung; für den Varianten-VERGLEICH neutral, da alle Exits
    denselben Entries folgen.) 3. Messung derselben Idee (Reverse · Exit-only ·
    Vola-Regime-Re-Test) — gleiche Antwort.
    **S/R-Close kappt Squeeze-Runner? = GEPRÜFT, KEINE Änderung (gemessen
    backtest_squeeze_srclose.py, 2026-07-24, User-Beobachtung „Squeeze wird direkt am
    nächsten S/R geschlossen"):** Der Log bestätigt, dass Auto-Squeeze-Trades regelmäßig
    vom P(break)-S/R-Auto-Close am nächsten Gegen-Level geschlossen werden (der Squeeze
    wurde mit reinem Trailing validiert, ist runner-abhängig — Verdacht: Runner gekappt).
    Direkter Vergleich NUR auf Squeeze-Entries (Box≤2,5×ATR, k=0,1): **Exit A = nur
    Trailing (validiert)** vs. Exit B = +S/R-Close (aktuell live) = **Wash, kein
    robuster Schaden** — H1 ØR +0,190→+0,224 (ΣR +40→+47, B besser), H2 ØR
    +0,394→+0,377 (ΣR +290→+278, B marginal schlechter) = Δ ±0,02 R/Trade (Rauschen).
    Warum unkritisch: der S/R-Close ist P(break)-gegated (schließt nur wenn Level
    wahrscheinlich HÄLT, P<60 %) → echte, starke Ausbrüche haben hohe P(break) und
    laufen weiter; PF/Runner-Tail bleiben erhalten (H2 PF 1,58=1,58), nur die WR
    steigt (50→55 %, Abpraller früher als Klein-Gewinn kassiert). Squeeze-Trades vom
    S/R-Close auszunehmen ist also NICHT belegt. (Reaktivierbar per Toggle, falls der
    User trotzdem die Runner nie kappen will — kostet gemessen ~nichts, ist aber
    Präferenz, kein Edge.)
    S/R-Close-Cooldown (2026-07-17, Koordinations-Fix, kein Backtest): nach einem
    S/R-Auto-Close eröffnet der Auto-Squeeze **~3 min (_SQUEEZE_SR_COOLDOWN_S=180)
    nicht in GLEICHER Richtung** wieder. Grund: der S/R-Close ging von „Level hält"
    (P(break)<Schwelle) aus, ein sofortiger Gleichrichtungs-Squeeze („Level bricht") war
    der widersprüchliche 1-s-Roundtrip (LONG-Close +15 → 1 s später neuer LONG, doppelter
    Spread; real beobachtet 2026-07-17 14:17). _check_sr_close merkt Zeit+Richtung
    (_sr_close_ts/_sr_close_dir), der Entry-Pfad prüft sie; _squeeze_last_sig wird
    NICHT gesetzt → nach Ablauf feuert der Ausbruch, falls noch aktiv. **Der Reverse
    (Gegenrichtung) bleibt unberührt** — nur der Gleichrichtungs-Wiedereinstieg wird
    verzögert.
    **Allgemeine Wiedereinstiegs-Sperre nach JEDEM Close (2026-07-28, Koordinations-Fix,
    kein Backtest):** nach dem Schließen eines Trades durch **Notfall-Stop / Broker-SL /
    Time-Stop / manuell** eröffnet der Auto-Squeeze **~3 min (`_SQUEEZE_REENTRY_COOLDOWN_S=
    180`) nicht in GLEICHER Richtung** wieder. Grund: real am 2026-07-27 15:39 killte
    ein zu enger manueller 3-€-Notfall-Stop (Sub-Spread bei Margin-Sizing) den Auto-
    Squeeze-Entry #1 nach 16 s; der Squeeze war noch active → sofortiger Re-Entry #2
    (Open→Instant-Close-Kaskade, doppelter Spread). _check_auto_close erfasst den Flat-
    Übergang (_pos_close_ts/_pos_close_dir, Richtung via _open_pos_dir solange offen),
    der Squeeze-Entry-Pfad prüft ihn NACH dem S/R-Cooldown; _squeeze_last_sig wird NICHT
    gesetzt → nach Ablauf feuert der Ausbruch, falls noch aktiv. Gegenrichtung/Reverse
    unberührt. Deckt alle Close-Arten außer dem S/R-Close ab (der hat seinen eigenen
    Marker). ⚠ Behebt das Symptom; der eigentliche Auslöser war der Sub-Spread-Stop
    (emergency_min_eur=3-Guard fängt <3 € ab, 3 € genau auf der Grenze war zulässig).
  • TF-Wechsel nicht mehr persistiert: engine._apply_wave_tf merkt den Heuristik-TF nur im Speicher (kein save_config mehr) — sonst würde die Secrets-ini bei jedem Switch neu geschrieben (Kommentar-Verlust/Churn). Beim Neustart wählt die Heuristik den TF binnen 1 min neu.

  • Circuit Breaker (Tagesverlust-Stopp) = gebaut, aktuell AUS [trading] daily_loss_limit_pct=0 (User schaltete ihn am 2026-07-27 wenige Stunden nach dem Einbau wieder ab). Kurz auf 8 aktiv (Überlebens-Basis, mit dem Autonomie-Ziel reaktiviert nach jahrelanger Ablehnung), dann =0. Reaktivieren = ini auf z. B. 8. Mechanik bei >0: erreicht der Tages-P&L (realisiert heute + offene Position) pct % der Balance, schließt der Bot die Position (closed_by=circuit_breaker) und sperrt neue Auto-Trades bis zum nächsten Tag (_check_auto_squeeze prüft _circuit_halted()). _check_circuit_ breaker im _pos_loop (1 s, unter Startup-Grace pausiert), realisierter Tages-P&L ~30 s gecacht (_day_realized_cached), Berlin-Datum als Trip-Marker (_circuit_ tripped_date) → automatischer Reset am neuen Tag. Log 🛑 + Telegram (eigener, nicht vom Close-Gate betroffen). Snapshot circuit_breaker {enabled, limit_pct, limit_eur, day_total, tripped}, Frontend #circuit-banner (rot bei ausgelöst, bernstein ab 60 % des Limits, v=115). ⚠ 80 %-Margin-Sizing bleibt (User- Entscheidung, 3.+ Mal bestätigt; risk_pct=0) → der 8 %-Tagesstopp schützt v. a. gegen die Verlust-Serie/-Kaskade, NICHT vollständig gegen den Einzeltrade (dessen 2×ATR-SL unter 80 % Margin allein schon >8 % verlieren kann). 0 = aus. Mit 5 synthetischen Szenarien getestet (nicht-zu-früh · Trip+Close · kein Doppel-Close · aus · Auto-Squeeze-Block). ⚠ Der frühere „NICHT einbauen"-Grund gilt nicht mehr — Ziel hat sich zu autonomem Handel verschoben, und ein unbeaufsichtigter Agent braucht die Notbremse im Code.

  • Notfall-Stop = AUS beim Öffnen (Stand 2026-07-24, auto_emergency_margin_pct=0, auto_emergency_pct=0, auto_emergency_loss=0). User-Vorgabe 2026-07-24: „gewinn close und notfall close … nicht setzen beim Eröffnen" → alle drei Auto-Arm-Modi 0, beim Öffnen einer Position wird KEIN Notfall-Stop gesetzt. Nur ein manuell im UI-Feld eingegebener Wert armiert (bleibt bis zum nächsten Positionswechsel). ⚠ Vertipper-Schutz [trading] emergency_min_eur=3 (Fix 2026-07-27): ein manuell gesetzter Notfall-Stop UNTER diesem Minimum wird als „aus" behandelt (nicht scharfgestellt) + Log-Warnung. Grund: ein Sub-Spread-Wert schließt den Trade sofort UND vererbt sich per _emergency_remember auf ALLE Folge-Trades — real am 2026-07-27 setzte der User versehentlich 1 € → jeder neue Trade wurde in Sekunden per Notfall-Close gekillt (Tickets 48504360/48504814 u. a., „sofort geschlossen"). Guard in set_emergency (greift NUR auf die manuelle Eingabe; die %-Auto-Arm-Pfade sind eh 0). 0 = kein Floor. Kurz-Historie des margin-%-Modus: 2026-07-17 komplett raus (2 % ≈ 0,76×ATR zerschoss die Squeeze-Trades, 48,94-€-Tag) · 2026-07-20 wenige Stunden als 3-%-GAP-NETZ (Balance-Basis) mit risk_pct=1.5 · 2026-07-23 kurz margin-basiert =3.0 (dritter Modus, ausgelöst durch zwei Notfall-Closes am selben Abend 11,20 €/20,47 €, wobei die Schwelle beim ersten Trade noch auf einem händischen UI-Restwert von 5 € hing) · 2026-07-24 auf User-Wunsch wieder ganz AUS. Der margin-%-Modus bleibt im Code (retry-sicheres Armieren via _emergency_margin_armed_ticket, hat Vorrang vor Balance-%/Fixwert wenn >0), reaktivierbar per ini. ⚠ Kopplungs-Regel (falls reaktiviert): unter Margin-Sizing kann ein enger %-Stop schneller greifen als der 2×ATR-SL. Schutz-Stack aktuell: Broker-SL 2×ATR + Trailing + Time-Stop (der Auto-Notfall-Stop ist inaktiv). UI-Feld (#emg-input, User-Vorgabe 2026-07-22, v=105) in der Trade-Leiste (rot, neben Gewinn-Close), Enter blurrt nur (kein Doppel-Send/ungewolltes 0, gleicher Fix wie Mindestgewinn 2026-07-22) — ein am Handy gesetzter Wert überschreibt den Auto-Arm für den laufenden Trade und bleibt bis zum nächsten echten Positionswechsel maßgeblich (mit Test verifiziert). ⚠ Server-seitig: wirkt nur bei laufendem Server (Update-Reboot-Lücke → „Auch beachten"). Der Server schließt die Position sofort, sobald P&L ≤ Wert (engine._check_emergency im _pos_loop, 1 s; wirkt auch bei gesperrtem Handy). Auto-Arm — drei Modi (Priorität: margin-% > Balance-% > Fixwert): (a) NEU — % der Einsatz-Margin dieser Position (aktiv, User-Vorgabe 2026-07-23): [trading] auto_emergency_margin_pct=3.0 → Schwelle = pct% × Margin (dieselbe Margin wie beim Gewinn-Close, aus trader.refresh()). Retry-sicher (Fix 2026-07-23, gleiches Muster wie beim Gewinn-Close): läuft NICHT nur einmalig beim Ticket-Wechsel, sondern jeden _pos_loop-Tick erneut, bis order_calc_margin einmal >0 liefert (_emergency_margin_armed_ticket-Retry-Schutz) — verhindert, dass bei einem Broker-Race im ersten Tick nach dem Öffnen dauerhaft ein stalen Wert der vorherigen Position hängen bleibt. Mit 4 synthetischen Szenarien verifiziert (sofort verfügbare Margin · verzögerte Margin/Retry · manuelles Überschreiben bleibt erhalten · Neustart überschreibt NICHT). Snapshot emergency_margin_pct. (b) % des Kontos (Fallback, wenn margin-%=0): [trading] auto_emergency_pct=2.0 → bei jeder neuen Position wird die Schwelle auf pct% × Balance gesetzt (_emergency_pct, skaliert mit dem Konto; z. B. 2 % von 831 € = 16,62 € ≈ 0,4×ATR; 1× auf 1 % getestet, war zu eng → 2 %). UI-Feld zeigt im Flat-Zustand den dynamischen Wert (Frontend rechnet pct%×balance, Snapshot emergency_pct). (c) Fixwert (Fallback, wenn beide %-Modi aus): Schwelle = zuletzt genutzter Wert (_emergency_remember, Startwert [trading] auto_emergency_loss Default 10, 0 = aus). Setzt der User im UI einen neuen Wert, wird der gemerkt und ab dem nächsten Trade übernommen. Aktiver Stop verfällt nach Auslösen UND bei Positions-Close, der gemerkte Wert bleibt. Neustart-fest (Fix 2026-07-17, emergency_state.json): Der aktive Notfall-Stop/TP wird mit dem Ticket persistiert (_save/_load_emergency_ state, atomarer Write; bei Auto-Arm UND manuellem Set). Erkennt der Server dieselbe offene Position nach einem restart_server.bat wieder, stellt er den persistierten Wert wieder her (_check_auto_close: ticket == _emergency_state_ticket → restore, Log „Auto-Close wiederhergestellt") statt auf die pct-Automatik zurückzuarmieren. Grund: real am 2026-07-17 14:30 setzte ein Neustart bei offenem Trade die von Hand gesetzten 25 € auf 19,42 € (2 %) zurück → Notfall-Close bei 19,64 statt bei 25. Eine echt neue Position (anderes Ticket) armiert normal aus pct/Merkwert und persistiert neu. ⚠ Trailing-Zustand ebenfalls neustart-fest (Fix 2026-07-29): emergency_state.json persistiert jetzt zusätzlich trail (= trail.enabled) per-Ticket. Erkennt der Server dieselbe Position wieder und war das Trailing vor dem Neustart AN, reaktiviert er es automatisch (_check_auto_close-Restore-Zweig → trail.toggle, Log „Trailing wiederhergestellt … nach Neustart"). Grund: real am 2026-07-29 lief eine Auto-Squeeze- Position nach mehreren Server-Neustarts nur mit dem Broker-SL (Trailing/Breakeven/ Time-Stop weg), weil trail.enabled nur im Speicher lebte und die Wiedererkennung (magic-match) es NIE reaktivierte — der User musste es von Hand einschalten. War das Trailing AUS (z. B. nach manuellem SL/TP via /api/sltptrail.deactivate), bleibt es aus (der persistierte Zustand entscheidet — kein Überschreiben eines manuellen TP). Der Zustand wird bei jeder Änderung persistiert (Change-Detection im _pos_loop, fängt auch die Selbst-Deaktivierung des Trailings bei manueller SL/TP-Erkennung). „aus"-Button schaltet auch den Auto-Arm ab (setzt _emergency_remember=0 — vorher blieb der gemerkte Wert und re-armte beim nächsten Trade, „aus" wirkte nur bis dahin; Bug gefixt 2026-07-07). Gilt genauso fürs Gewinn-Ziel. (Auto-Arm 1× auf manuell-only gestellt, nach Fehl-Trade wieder reaktiviert — echter Schutz gegen falsche Empfehlungen; Signal-Filter dagegen 4× gemessen wirkungslos/schädlich, s. backtest_rev_htf.py.) engine.set_emergency() · POST /api/emergency {value} · snapshot.emergency_loss. Das ist KEIN automatischer Circuit-Breaker, sondern ein manueller Hard-Stop (vom User gewünscht). Benachrichtigung IMMER (Ausnahme vom „Close-Push nur im Plus"): bei Erfolg „🚨 Notfall-Close", bei Fehlschlag „⚠ Notfall-Close FEHLGESCHLAGEN — manuell schließen!" (Schwelle bleibt dann aktiv, nächster Tick versucht erneut). Eigener send_telegram, nicht vom pnl>0-Gate (history) noch vom 5-Min-Cooldown betroffen.

Mobile-UI (web/) & Alarme

  • Aufbau: Tab-Navigation ganz oben (sticky: Dashboard/SIG Live/SIG Anzeige/Logs/Statistik/News), darunter Header.

  • Design-System (style.css, Pass 2026-07-15, KEIN Framework — Tailwind bewusst verworfen wegen WireGuard/Offline + kein Node): Tokens in :root — Flächen-Leiter --bg < --surface-2 < --card, --border/--line, Text --dim/--bright (+--text-*-Aliasse, waren undefiniert = Bug gefixt), Radien --r-sm/-r/-r-lg, --shadow(-sm), --mono. Font: UI = System-Sans + tabular-nums; Zahlen-Werte (Kurs/PnL/Felder/Logs) = --mono. Karten mit Elevation-Schatten, Fokus-Ringe auf Feldern, Transitions auf Tabs/Buttons. Neutrale Hex-Literale → Tokens (Konsistenz).

  • Trade-Leiste (#trade-bar, über der Gesamtempfehlung, nur bei offener Position, 2026-07-15): Laufzeit-Uhr (2026-07-30, User-Wunsch „Uhr … wie lange diese schon läuft"): trader.snapshot() liefert open_time = Öffnungszeit als echte Epoch (Broker-Offset korrigiert, aus position.time); Frontend zeigt sie als Feld #tb-dur und kompakt im Kachel-Titel (#tb-runtime, „⏱ 1:23:45"). Der Ticker läuft eigenständig 1×/s (_tickTradeDuration), zählt also flüssig zwischen den Snapshots; ab 2 h bernstein (Time-Stop-Nähe: 120 min in Phase „Init"). ⚠ Zwei Fallen beim Bau: (1) _broker_offset_s() liest self.symbol, die beim Adoptieren einer Position noch NICHT gesetzt ist → Offset 0 → Zeit lag 3 h in der Zukunft; die Funktion nimmt jetzt sym als Parameter (_broker_offset_s(pos_sym)). (2) Selbst- heilung: liegt open_time >60 s in der Zukunft, wird sie neu berechnet; beim Close auf 0 zurückgesetzt (kein stale Wert für den Folge-Trade). Berechnung nur 1× je Position (nicht je 1-s-Refresh). Weiter in der Zeile: Richtung (▲LONG/▼SHORT) · Lots · Einsatz · 3 Eingabefelder .tb-field (Label über Feld, rot=.stop/grün=.gain): SL · TP · Gewinn-Close (#srmin-input) (Notfall-Close-Feld 2026-07-17 entfernt — Notfall-Stop ganz aus). SL/TP: Enter/Verlassen → POST /api/sltp → setzt am Broker und schaltet Trailing ab (trail.deactivate); Vorbefüllung aus position.sl/tp, Broker-Ablehnung → Toast. Gewinn-Close (2026-07-15 aus der Position-Kachel hierher verschoben, User-Vorgabe — gleiches Design) behält ID/Handler (/api/srclose_min). Auch der Auto-Close-Schalter (#srclose-btn, jetzt .tb-btn-Box, AN/AUS grün/grau) sitzt in der Zeile. Titel „aktueller Trade läuft"; Richtung/Lots/Einsatz = Anzeige-Boxen (.tb-val), Rest editierbar — alle im selben .tb-field-Look. Position-Kachel hält nur noch cur-near-Warnung + pos-srhint. (.emg*/.sr-toggle-CSS entfernt.)

  • ⚠⚠ CHARTS-TAB KOMPLETT ENTFERNT (User-Vorgabe 2026-08-05, v=142). Weg sind: Tab-Button, #view-charts, der gesamte app.js-Chart-Block (Kartenaufbau, renderChart, loadCharts, 20-s-Refresh, Resize-Handler), die Chart-CSS-Regeln, die vendorte Bibliothek web/lightweight-charts.standalone.production.js (164 KB) samt <script>-Tag — und die Backend-Kette dahinter: /api/bars und engine.get_bars (60 Zeilen). Beide existierten ausschliesslich für dieses Tab (geprüft: kein anderer Aufrufer). ⚠ Nicht betroffen — sie sahen nur ähnlich aus: die Chartmuster-Karte (#card-patterns, Verdict-Gewicht 0,25, jetzt im Tab „SIG Live"), der MQL5-Indikator samt sr_levels.csv-Export (eigener Pfad über _write_levels_file, unabhängig von get_bars) und alle Analyse-Module (structure.py, patterns.py, cone.py) — die speisen den Snapshot, nicht das Chart. Der MT5-Chart bleibt also vollständig bedient. Verifiziert am laufenden Server: /api/bars404, Bibliothek → 404, data-view="charts" und id="view-charts" nicht mehr im ausgelieferten HTML, 6 Tabs zu 6 Views paarig, keine verwaisten JS-Referenzen, Klammern-Balance ok. Zurückholen: alles liegt in der Git-Historie (Commit davor).

  • Gesamtempfehlung (#card-verdict, ganz oben): grafische Aggregation aller Module — Konfidenz-Ring (conic-gradient = conf_pct), Bias-Balken mit Nadel (SHORT↔LONG) und Modul-Chips (Welle/M30/H1/KI/Elliott/Squeeze, je grün/rot/grau; TU entfernt). ⚠ Bias-Fix 2026-07-15: stimmberechtigt nur Module MIT Aussage — Squeeze (nur active), Elliott (nur mit Ziel), KI (nur mit Bias) haben sonst Gewicht 0 (vorher dämpfte das fast immer neutrale Squeeze-w=2,0 die Nadel permanent ~×0,8); agree/total zählt nur noch die ANDEREN stimmberechtigten Module (Welle zählte sich selbst mit). ⚠ Bias-Fix ZWEITER TEIL 2026-07-30 (Review „Gesamtempfehlung komplett prüfen"): Die Regel „ohne Aussage = Gewicht 0" galt bis dahin für Squeeze/Elliott/KI, NICHT für die Welle selbst — sie stand mit w=3,0 permanent im Nenner, auch bei WARTEN. Das wog schwer: ~91 % der letzten 2000 Empfehlungen sind WARTEN (Range-Regime), die Nadel war also fast immer künstlich zur Mitte gezogen (realer Fall: bias +0,312 statt +0,625, Faktor ~2). Jetzt weight=0 bei WARTEN. Zweiter Fix: agree nutzte als Referenz immer die Wellen-Richtung → bei WARTEN stand dort zwangsläufig „0/N", obwohl sich die anderen Module einig waren (real „0/4" bei 2 einigen Modulen). Jetzt Referenz = Wellen-Richtung, ersatzweise die Bias- Richtung (real: „2/3"). Beides REINE ANZEIGE — Order-Logik unberührt. Mit 6 synthetischen Szenarien verifiziert (alle einig · Rest dagegen · alles neutral · WARTEN+alle SHORT · Welle+Squeeze). ⚠ Bias-Fix DRITTER TEIL 2026-07-31 (Anlass: User-Frage „H1 aus der Gesamt- empfehlung entfernen, da wir jetzt mit M30 arbeiten?"): Antwort NEIN — die Prämisse vermischt zwei Dinge (auf M30 umgestellt wurde die S/R-Level-Quelle, nicht die Zeitebenen-Logik; H1 war nie Basis-TF, tf_max=M30). Und die vermutete Doppelzählung ist gemessen schwach (verdict_votes, n=7.074): bei gerichteter Welle stimmt M30 nur zu 14,9 % mit ihr überein, H1 zu 47 %, M30 und H1 untereinander nur zu 33,8 % — keine redundanten Stimmen (Reversals heben den M30-Filter bewusst auf, und M30 ist meist gar nicht gerichtet). H1 fließt zwar auch als Konfluenz-Bonus in die Konfidenz — aber in eine ANDERE Kennzahl (Konfidenz-Ring vs. Bias-Nadel), nicht zweimal in dieselbe. DER ECHTE DEFEKT lag woanders und ist behoben: add("M30", …)/add("H1", …) hatten Gewicht 1,5 UNBEDINGT — auch bei Stimme 0. Derselbe Fehler wie beim Wellen-Modul (Teil 2), hier übersehen. Eine 0-Stimme heißt „EMA-Abstand im Totband" = keine Aussage, nicht „neutral". Gemessen: M30 ist in 57,9 % der Fälle ohne Aussage (H1 nur 1,8 %) → Ø-Bias-Betrag 0,371 → 0,441, Median-Verstärkung 1,30×, in 33,3 % der Zeilen deutlich stärkere Nadel. Jetzt 1.5 if vote != 0 else 0.0; others filtert bereits auf weight>0, die „x/y einig"-Zählung zieht mit. Live verifiziert: M30 „flach" → Gewicht 0,00, bias 1,5/4,5 = 0,333 statt 1,5/6,0 = 0,250. REINE ANZEIGE. ⚠ MODUL-AUDIT 2026-07-31 (User: „überprüfe alle Module auf Inkonsistenzen"): systematisch alle Verdict-Module gegen die Regel „keine Aussage → Gewicht 0" geprüft, dazu Config-, Frontend- und Level-Quellen. 5 Befunde, alle behoben: (1) KI-Copilot — der größte Rest des Musters. Ein explizites „NEUTRAL" zählte als echte Stimme mit Gewicht 1,0. Gemessen (verdict_votes, n=7.084): das ist in 93,1 % der Fälle der Zustand (Elliott: 1,3 %) — also der Normalzustand des Copiloten, kein abgewogenes Urteil. Damit hatte ausgerechnet das Modul mit der schwächsten Beleglage (analyze_verdict_calibration.py: nicht robust prädiktiv) den GRÖSSTEN dämpfenden Einfluss. Jetzt 1.0 if av != 0 else 0.0. (2) Elliott analog: Gewicht nur bei gerichtetem Ziel (1,3 % der Zeilen). Kumulierte Wirkung aller Gewichts-Fixes des Tages: Ø|bias| 0,370 → 0,441 → 0,569 (= 1,54×), betrifft 79,5 % der Zeilen. Live verifiziert: KI „NEUTRAL" → Gewicht 0,00, bias 2,5/4,5 = 0,556 statt 2,5/5,5 = 0,455. (3) Stale [zones] speisten die Wellen-Konfidenz — entfernt. _sr_levels zog beide Kanten jeder [zones]-Zone als S/R-Linien heran: 16 Kanten aus einem ~10 $ TIEFEREN Regime (72,9077,00 bei Kurs 85,3). Aus dem CHART waren sie am 14.07. schon entfernt worden, WEIL sie stale sind — in die Konfidenz liefen sie weiter. Realer Audit-Fall: market.sr hatte GAR KEINE Widerstände (M15-Cluster sind in frischen Trends oft leer) → der „nächste Widerstand" kam aus der fvg-Zone bei 88,00 (2,66 weg), der echte lag bei **85,315** (0,03 weg). Der Abzug „dicht unter Widerstand" (12) greift nur innerhalb 0,5×ATR und feuerte deshalb nie. Entfernen ist keine Strategieänderung, sondern das Streichen von Fremdregime-Daten; parse_zones bleibt in config.py, die ini-Sektion bleibt (reaktivierbar). (4) Tote Config entfernt (Backup oil_widget_config.ini.bak-2026-07-31): [trading] trail_timeframe = M1 (nirgends gelesen, Name suggeriert fälschlich, er steuere die Trailing-TF) und die komplette [setups]-Sektion (11 Schlüssel, vollständig tot). (5) raise StopIteration als Sprung innerhalb eines breiten except Exception (Trade-Marker-Export) durch ein if ersetzt — funktionierte, wäre aber fragil, sobald dort jemand Logging ergänzt (Phantom-Fehler im ~5-s-Takt). Sauber: Frontend↔Backend (0 verwaiste Element-IDs), alle übrigen Verdict-Gewichte, alle anderen Config-Schlüssel. ⚠ KONSENS-ZEILE korrigiert (2026-07-31, User-Fund „4/4 Module — dabei haben wir doch 9"): In vd-consensus steckten ZWEI Fehler. (a) Falscher Bezug: der Text lautete … Module für ${sig} mit der HEADLINE als Bezug. Seit dem ref-Fix vom 30.07. zählt agree bei WARTEN aber die Übereinstimmung mit der Bias-Richtung — deshalb stand real dort „stark LONG · 4/4 Module für WARTEN". Die Richtung kommt jetzt als verdict.ref_dir vom Backend, statt im Frontend aus bias nachgebaut zu werden (dort galt ein ±0,05-Totband, im Backend bias > 0 — die Nachbildung wäre am Rand auseinandergelaufen; dieselbe Sorte Divergenz wie die fünf Deployment-Drift-Fälle desselben Tages). (b) Anderer Nenner als die sichtbaren Chips: total zählt nur Module MIT Aussage (Gewicht > 0, ohne die Welle). Nach den Gewichts-Fixes des Tages schweigen deutlich mehr Module → „4/4" bei 9 sichtbaren Chips. Neu ist verdict.silent (Module mit Gewicht 0) dabei, sodass die Summe aufgeht. Live: „3 von 5 stimmberechtigten für LONG (4 ohne Aussage)" — 5 + 4 = 9 . Bei gar keiner Aussage: „kein Modul mit klarer Aussage". v=125. ⚠ „WARUM WARTEN?" jetzt an der Headline (2026-07-31, v=126, User-Fund „WARTEN, aber Konsens stark LONG"): Der scheinbare Widerspruch entstand, weil die Welle in aller Regel eine RICHTUNG hat und nur an einem Gate hängt — der Grund stand aber ausschliesslich als Textzeile in der Meldungen-Karte, also weit weg von der Stelle, an der die Frage aufkommt. wave.breakout {pending, dir, level, need} wurde berechnet und gespeichert, vom Frontend jedoch nirgends gerendert. Jetzt hängt es direkt an der Headline: „◌ WARTEN · M15 · LONG, noch 0.02 bis zur Bestätigung". ⚠ Erste Auswertung der neuen block_reason-Telemetrie (3 h): breakout_pending 63 % · entry_room 20 % · min_conf 18 % — und kein einziges Mal Totband oder HTF-Gegen-Trend. Das beantwortet die offene Frage aus dem TF-Churn-Fix: die 93 % WARTEN kommen NICHT daher, dass die Welle richtungslos wäre, sondern von den Gates — allen voran der Breakout-Bestätigung (die bis zum Fix desselben Tages gar nicht fertig werden konnte). ⚠ Offen (dokumentiert, nicht geändert): (0) Wellen-Konfidenz aus _draw_levels speisen statt aus dem Misch-Set — würde den 12-Abzug tatsächlich auslösen, also Konfidenz senken und das 55%-Gate verschieben. Strategieänderung → messpflichtig. (a) Drei divergierende Level-Quellen seit der M30-Umstellung: _draw_levels (M30 → Chart/S/R-Close), pb_levels (M5 → Entry- Raum-Gate), _sr_levels (Misch-Set → Wellen-Konfidenz); real wichen die Supports um 0,65 $ ab. Bewusst so (jedes Gate ist auf seine Quelle kalibriert), aber eine Stolperfalle. (b) tu wird noch als Parameter durchgereicht, seit 2026-07-06 ungenutzt. ⚠⚠ ERSTE AUSWERTUNG DER verdict_votes (2026-08-05, analyze_verdict_modules.py, 13.568 Zeilen 24.07.05.08.) — DIE GEWICHTUNG IST IN DER PRAXIS INVERTIERT. Nominal-Gewicht sagt fast nichts darüber, wie stark ein Modul die Nadel bewegt; entscheidend ist, wie oft es überhaupt mitredet. Gemessener Anteil am Gesamtgewicht, gemittelt über alle Zeilen:

    Modul Nominal stumm realer Einfluss
    H1 1,5 2 % 38,5 %
    Elliott 1,0 1 % 26,0 %
    M30 1,5 62 % 12,2 %
    Squeeze 2,0 81 % 7,0 %
    Liq-Trend 0,5 45 % 6,1 %
    Orderbuch 0,5 62 % 4,1 %
    Welle 3,0 91 % 3,8 %
    Muster 0,25 77 % 1,8 %
    KI-Copilot 1,0 97 % 0,5 %
    ⚠⚠ DIESE TABELLE ALLEIN FÜHRT IN DIE IRRE — Korrektur am selben Tag. Der
    Anteil „über alle Zeilen" mischt zwei Dinge: die Gewichtung UND die Häufigkeit.
    Entscheidend für die Frage „ist die Gewichtung kaputt?" ist der Anteil **wenn das
    Modul spricht**:
    Modul alle Zeilen wenn es spricht
    --- --- ---
    Welle 3,8 % 44,4 % ← höchster Wert im Feld
    H1 38,5 % 39,3 %
    Squeeze 7,0 % 37,3 %
    M30 12,1 % 31,9 %
    Elliott 26,0 % 26,2 %
    KI-Copilot 0,5 % 16,2 %
    Liq-Trend / Orderbuch / Muster 6,2 / 4,1 / 1,8 % 11,1 / 10,8 / 8,1 %
    Die Gewichtung ist NICHT invertiert. Wenn die Welle spricht, dominiert sie
    mit 44,4 % — genau wie entworfen; ihre 3,8 % im Alltag sind schlicht die 91 %
    gewollte Stille der Gates. Dasselbe beim Squeeze (37,3 % wenn aktiv).
    Was BLEIBT, ist ein anderer Befund: H1 und Elliott haben in beiden Spalten
    fast denselben Wert (38,5/39,3 bzw. 26,0/26,2 %), weil sie **praktisch nie
    schweigen**. Sie bilden einen permanenten Hintergrund von ~65 %, gegen den die
    episodischen Module anschieben müssen. Der Ruhezustand der Nadel ist damit
    faktisch „was H1 und Elliott sagen" — und Elliott ist über 1.163 Episoden flach.
    ⚠⚠ DOKUMENTATIONS-FEHLER KORRIGIERT: hier stand, Elliott habe „Gewicht nur bei
    gerichtetem Ziel (1,3 % der Zeilen)". Die Daten sagen das Gegenteil —
    elliott_w > 0 in 13.558 von 13.568 Zeilen (99,9 %). Der Fix vom 31.07.
    („keine Aussage → Gewicht 0") greift bei Elliott praktisch nie, weil praktisch
    immer ein Ziel existiert. Elliott ist damit die **zweitlauteste Stimme im
    gesamten Verdict**.
    Prädiktivität — Sichtung, KEIN Urteil (Forward-Return 60 min, gegen die
    Drift-Null, Minutentakt zu Episoden zusammengefasst, sonst zählt man dieselbe
    Marktlage hundertfach): Elliott **0,031/0,020 bei 49 % Treffern über 1.163
    Episoden** — die mit Abstand größte Stichprobe, und sie ist flach. Welle und M30
    kippen zwischen den Hälften; KI-Copilot und Squeeze sind beidseitig positiv, aber
    mit 53 bzw. 100 Episoden dünn. H1 hat nur 19 Episoden (es wechselt selten) —
    prädiktiv nicht beurteilbar, sein Einfluss-Anteil ist aber reine Arithmetik und
    damit belastbar.
    Grenzen: 12 Tage = EIN Regime; die Reminder-Schwelle steht bewusst bei
    25.000 Verdicts. Die Einfluss-Tabelle ist der robuste Teil (Auszählung über
    13.568 Zeilen), die Return-Spalten sind der verrauschte.
    Nichts geändert — die Bias-Nadel ist reine Anzeige; die Order steuert
    die Welle über die Headline, nicht der Konsens. Eine Umgewichtung wäre eine
    Anzeige-Änderung mit Wirkung auf die MANUELLE Entscheidung und damit
    messpflichtig, sobald genug Verdicts vorliegen.
    Formel/Details: docs/gesamtempfehlung.md.
    Headline = validierte Welle (steuert Order) + Zeitebene (verdict.tf,
    z. B. „▲ LONG · M5" — TF, für die die Empfehlung gilt); Rest = nur Konsens-Anzeige.
    Backend engine._verdictverdict im Snapshot; Render renderVerdict
    (app.js). Details: docs/gesamtempfehlung.md.
    **⚠ Konfidenz ist NICHT kalibriert (gemessen analyze_verdict_calibration.py,
    2026-07-24, „Gesamtempfehlung verbessern"-Paket):** conf_pct gegen Forward-Return
    (2 h, ×ATR, 1604 gesampelte Signale aus recommendations, 2 Hälften) — H1 (MaiJun)
    perfekt monoton (conf 85100: WR 68 %/ØR +1,13; <55 klar negativ), **H2 (JunJul)
    komplett INVERTIERT** (conf 85100: WR 40 %/ØR 0,90; mittlere Bins besser). Hohe
    Konf = starker Trend + Konfluenz — im Chop-/Hochvola-Regime sind genau das die
    Überdehnungen vor der Wende. Konsequenzen: conf-Anzeige NICHT als P(Erfolg)
    aufwerten; Konfidenz-Sizing-Idee VERWORFEN (hätte in H2 die schlechtesten
    Signale am größten gesized); Ring bleibt, was er ist (Score-Aggregat). Copilot-Bias
    in derselben Messung nicht robust prädiktiv (n klein bzw. ØR ~0) — für die
    datenbasierte Entscheidung (behalten/Gewicht/raus wie TU) loggt jetzt
    verdict_votes (neue DB-Tabelle, history.log_verdict_votes, 1×/60 s aus
    _run_analysis: headline/conf/bias/tf + Modul-Votes Welle/M30/H1/KI(+w)/
    Elliott(+w)/Squeeze + news_score) — Auswertung nach ein paar Wochen analog
    analyze_verdict_calibration.py.
    Live-Kosten-Chip (2026-07-24): Snapshot cost_ratio = (askbid)/ATR_M5
    (pb_feats.atr, Floor 0,06) → in der vd-consensus-Zeile „Kosten 0,14×ATR
    günstig / · normal / ⚠ teuer" (Schwellen 0,20/0,32 aus backtest_realcosts.py:
    Ø 0,265, Nacht 0,320,50 = Falle, 1517 Uhr ~0,13). Reine Info — die ehrliche
    Alternative zum abgeschalteten Dead-Hours-Gate.
    Ausrichtungs-Split im Order-Dialog (2026-07-24, Verhaltens-Hebel/B0): Der
    „trotzdem eröffnen?"-Dialog zeigt jetzt die EIGENEN Zahlen aus der DB
    (history.alignment_stats(40) → Snapshot alignment, Engine-Cache ~120 s):
    📊 Deine Trades OHNE Signal: 42 % Trefferquote · 423 € (n36) / Mit Signal: …"
    (Gruppen <5 Trades werden ausgeblendet). Real bei Einbau: 36 der letzten 40
    Trades liefen OHNE Signal-Deckung mit 423 € — exakt die dokumentierte
    Kern-Leckage, jetzt im Moment der Entscheidung sichtbar.
    **News-Konflikt-Chip (2026-07-24, Auslöser 23.07.: zwei Shorts gegen einen
    +0,98-Sentiment-Burst):** Snapshot news_sentiment (Score 1..+1 aus
    news.sentiment); Frontend #news-conflict (Meldungen-Karte, bernstein) warnt
    bei Score ≥0,5 gegen offene Position bzw. aktives Signal („📰 News-Flow stark
    bullisch (+0,98) — steht gegen deine SHORT-Position"). Reiner Kontext —
    Prädiktivität unbelegt (Kalibrier-Messung: H2 nur n=4/6); dafür wird
    recommendations.news_score seit 2026-07-24 wieder BEFÜLLT (war auf None
    degradiert) → in ein paar Monaten messbar.
  • ⚠⚠ ZWEI NEUE TABS: „SIG Live" und „SIG Anzeige" (User-Vorgabe 2026-08-05, v=141). Sortiert wird nach WIRKUNG, nicht nach Thema — und die Zuordnung ist am Code prüfbar, nicht Geschmackssache: engine._verdict ruft add(name, vote, gewicht, …); wer ein Gewicht > 0 bekommt, verschiebt die Bias-Nadel und gehört nach „SIG Live".

    Tab Karten Begründung
    SIG Live KI-Copilot · Chartmuster Verdict-Gewicht 1,0 bzw. 0,25 (_PAT_W)
    SIG Anzeige Marktstruktur · Kerzen-Anatomie gemessen ohne Einfluss, als Signal verworfen
    „Gewicht" heißt NICHT „belegt". Der KI-Copilot hat Stimmrecht, ist aber
    gemessen nicht robust prädiktiv — genau deshalb ist die Trennung nach Wirkung
    sinnvoll: sie zeigt, was die Empfehlung TATSÄCHLICH bewegt, unabhängig davon, wie
    gut es belegt ist. Chartmuster steht aus demselben Grund in „Live", obwohl die
    Klasse als Signal verworfen wurde — seit dem 2026-08-01 hat sie 5 % Stimmanteil.
    Auf dem Dashboard BLEIBEN Trade-Leiste, Gesamtempfehlung, Meldungen und
    Statistik (live). Die „Meldungen"-Karte enthält zwar einflussreiche Teile
    (Squeeze-Stimme, S/R-Close-Hinweis, Flip-Close), ist aber der **Live-Meldungsstrom
    zum laufenden Trade** — sie gehört an die Stelle, an der gehandelt wird, nicht in
    einen Modul-Tab. Wer das anders will, verschiebt #card-pos.
    Umgesetzt als reiner Markup-Umzug (showView bildet data-view="siglive" auf
    #view-siglive ab, keine JS-Änderung nötig); CSS-Regel #view-siglive > .card
    analog zum Statistik-Tab, weil beide außerhalb des <main>-Grids liegen.
    Verifiziert am ausgelieferten HTML: 7 Tabs, 7 Views, keine doppelten IDs, keine
    verwaisten JS-Referenzen.
  • Karten (Dashboard, von oben): Gesamtempfehlung · Meldungen · Marktstruktur · KI-Copilot (jetzt im Tab „SIG Live") · Statistik (live) ganz unten. ⚠ Drei Karten am 2026-08-05 in den Statistik-Tab verschoben (User-Vorgabe, v=138): „Manuelle Trades" (#card-manual), „Letzte Trades" (#card-trades) und „Kurslücken" (#card-gaps) — sie sind Rückschau, das Dashboard soll das Handlungsrelevante zeigen. Reiner Umzug im Markup, keine Logik berührt: die drei werden weiterhin von render() aus dem Snapshot gefüttert und hängen NICHT an loadStats(); ein ausgeblendeter View ist nur display:none, die Elemente bleiben im DOM (sonst wären ihre IDs beim Rendern weg — genau die Null-Falle aus der Cache-Regel). ⚠ Dazu nötig: #view-stats > .card{margin:11px 10px} in style.css — das Dashboard ist ein <main>-GRID mit eigenem Padding, #view-stats eine schlichte <section>; ohne die Regel klebten die Karten randlos aneinander und .card.wide{grid-column:1/-1} liefe ins Leere. Verifiziert am ausgelieferten HTML (alle drei nach id="view-stats", #card-stats-live bleibt im Dashboard), keine doppelten IDs, Tag-Balance geprüft. (Tagesziele-Kachel entfernt 2026-07-13. Welle-Karte komplett entfernt 2026-07-15 — TF-Ampel wanderte in die Gesamtempfehlung, die Wellen-Meldungen [Squeeze/Überdehnung/Setup/Lauf/Gründe] in die „Meldungen"-Karte (#card-pos, ex-„Position"); das Wave-Signal ist eh die Verdict-Headline. Trailing-Karte ausgeblendet 2026-07-22#card-trail.hidden per CSS, User-Vorgabe; Backend/ Trailing-Logik + Snapshot-Felder trail.* unverändert aktiv, nur die Anzeige raus, wieder einblendbar durch Entfernen der hidden-Klasse in index.html.)

  • Auto-Squeeze-Monitor (B4) (#sqm-box, oben im Statistik-Tab, 2026-07-20): zwei Blöcke — Entry-Edge · Mechanik (engine.squeeze_monitor rechnet die Squeeze- Regel mit Live-Params auf ~30 Tagen M5 NEU, Backtest-Exit → isoliert den Einstieg, ØR/PF gegen Erwartung +0,14…+0,23 · PF>1) und Live-Trades · echt (SQUEEZE_*- Trades aus der DB, Gesamtsystem inkl. realer Exits: n/WR/Ø-Gew/-Verl/Verhältnis/ΣEUR/ Tag-Nacht; ØR-Näherung via SL-Distanz). Verdict-Badge (trägt/dünn/driftet→B5) aus der Mechanik. Kernaussage der Ansicht: klaffen Mechanik (Edge lebt) und Live (echtes Geld) auseinander → es liegt am Exit/Verhalten, nicht am Signal (real 2026-07-20: Mechanik ØR +0,414/PF 1,64, Live ØR 0,07/2,41 € — der alte Notfall- Stop + Nacht-Trades fraßen den Edge, jetzt behoben → sollte konvergieren). Endpoint /api/squeeze_monitor (Cache 60 s), lädt beim Öffnen des Statistik-Tabs (loadStats).

  • Statistik-live-Kachel (#card-stats-live): kompakt Woche + Gesamt — Trades, WR, PF, Ø-Gewinn/-Verlust, Verhältnis (Øgew/Øverl, <1 = Verlust- Schiefe), PnL. Quelle engine._compact_statssnapshot.stats_compact. Zum objektiven Verfolgen der Stichprobe (statt Bauchgefühl pro Einzeltrade). TradersUnion- und Elliott/FVG- Einzelkacheln ausgeblendet. Elliott/FVG fließt weiter in die Gesamtempfehlung (_verdict); TU ist ganz raus aus Empfehlung, Verdict UND KI-Copilot-Kontext (agent._tool_market ohne tradersunion/tu_consensus — war die letzte Hintertür, da der Copilot eine Verdict-Stimme ist; nur noch Snapshot-Anzeige).

  • TF-Wende-Ampel (#wave-turns, jetzt IN der Gesamtempfehlung): Chips M1/M5/M15/M30/H1 (H1 neu 2026-07-15 — im turn_data-Fetch, aber NICHT im Bounce-Scan, der bleibt M1M30), je grün(auf)/rot(ab) nach EMA12/50-Stand; frische Kreuzung (≤3 Bars) blinkt + zeigt ⟳ + Richtungspfeil + Bars-seit-Wende. Quelle: wave.tf_turns (Backend WaveRecommender._turn_state mit Totband _TREND_DEADBAND×ATR → kein Geblinke bei seitwärts laufenden EMAs). 4-TF-Fetch ist gedrosselt (~15 s) via _turn_due (spart Lock-Zeit; letzter Stand in _last_tf_turns gehalten). REINE Anzeige — ändert die Empfehlung NICHT.

  • Marktstruktur-Kachel (#card-structure, 2026-07-20, User-Idee „Pro-Chart- Setup-Erkennung"): zeigt die Price-Action-Struktur auf M30 — Swing-Folge HH/HL/LH/LL (Chips grün=bullisch/rot=bärisch), letzter BOS (Break of Structure, Richtung + gebrochenes Level + Bars her) und den Regressionskanal (Richtung + Position des Kurses im Kanal 0100 % als Balken+Punkt). Quelle core/structure.py (MarketStructure, Pivots k=3, Kanal = LS-Regression über 60 Bars ± max. Abweichung, ~30 s gedrosselt im Trend-Loop unter mt5_lock), Snapshot structure. REINE ANZEIGE — kein Trade-Trigger, kein Verdict-Gewicht, KEINE Prognose/Pfeil. Signalvariante DIREKT gemessen & VERWORFEN (backtest_structure.py, 2026-07-20, 60k M30-Bars/5 J, 2 Halbjahre, dieselbe structure.py-Logik kausal): ALLE Varianten in BEIDEN Hälften negativ — (A) Kanal-Pullback „Kauf am unteren Kanaldrittel/Demand-Zone" = der orange Pfeil: ØR 0,05…−0,11, PF<1, WR 4043 % (der Kanalrand HÄLT nicht zuverlässig — antizyklisch am Extrem = Münzwurf, P(break)- Klasse); (A2) +Swing-Filter (HH/HL) macht es nicht besser (0,06/0,07); (B) BOS- Continuation = breakeven-negativ (0,011/0,005, Momentum-Klasse). Damit ist auch die Struktur-Idee empirisch durch — die Kachel bleibt Ist-Zustand-Anzeige (wie TF-Ampel/Squeeze), steuert nichts.

    • NACHTEST über ALLE Zeitebenen = ERNEUT VERWORFEN (backtest_structure_tf.py, 2026-08-03, User-Beobachtung im M5-Chart: „der Kurs folgt dem Kanal und bricht die untere Linie nicht — können wir das nutzen?"): Die alte Messung lief NUR auf M30, M5 war streng genommen ungetestet. Jetzt mit dem kanonischen Live-Exit (exit_model.LIVE: Trail 1,0 + Time-Stop + TP — die alte Datei nutzt eine Exit-KOPIE mit Trail 1,5 OHNE Time-Stop) über M5/M15/M30, zwei Lesarten des M5-Kanals und drei Regel-Varianten, 80k Bars je Zeitebene, 2 Hälften, Echtkosten:
      Zeitebene / Kanal X=0,20 X=0,30 +Swing
      M5, 60 Bars (=5 h, „was man im Chart sieht") 0,201/0,017 0,170/0,054 0,161/0,082
      M5, 360 Bars (=30 h, zeitgleich zu M30) 0,206/0,017 0,179/0,054 0,159/0,082
      M15, 120 Bars 0,032/0,003 0,068/0,034 0,043/0,033
      M30, 60 Bars (die ALTE Messung) 0,103/0,019 0,094/0,013 0,090/0,024
      24 Zellen, ALLE negativ, PF 0,660,99. Drei Befunde:
      (a) Trefferquote 3743 % über 1.2002.400 Berührungen je Hälfte — der Kanalrand
      hält in WENIGER als der Hälfte der Fälle. Die im Chart sichtbaren „Halter" sind
      Selektion: die Durchbrüche sehen hinterher nicht mehr nach Kanal aus.
      (b) M5 ist die SCHLECHTESTE Zeitebene (0,201 vs. 0,103 auf M30) — feiner
      auflösen erzeugt mehr Berührungen, und jede kostet Spread.
      (c) Keine Parameterfrage: 60-Bar- und 360-Bar-Kanal liefern praktisch IDENTISCHE
      Zahlen. Zwei so verschiedene Fenster mit gleichem Ergebnis → es liegt am Mechanismus.
      Legacy-Recheck-Bonus: die M30-Zeile reproduziert den Befund vom 20.07.
      (0,103/0,019 gegen damals 0,05…−0,11) — der alte Schluss war NICHT durch das
      abweichende Exit-Modell verzerrt.
      Mechanismus: Der Kanalrand ist eine Regressionslinie durch die letzten N Closes —
      dahinter liegt nichts: keine Order, kein Pivot, keine Struktur. Er hält nur,
      solange der Trend ohnehin läuft. Genau darin liegt der Unterschied zu **P(break) auf
      einem echten S/R-Level** (Pivot = Stelle, an der real gehandelt wurde), das
      out-of-sample kalibriert ist (AUC 0,65 bzw. 0,680,72). „Linie im Chart" ist nicht
      „Struktur im Markt". 19. verworfener Signal-Eingriff.
  • ⚠⚠ P(break) LIVE-AUSWERTUNG (analyze_pbreak_live.py, 2026-07-31, 2275 ausgewertete Vorhersagen seit 23.07.): DAS MODELL HÄLT LIVE NICHT, WAS DER BACKTEST VERSPRACH — B5-Fall. Fällige Messung aus dem Reminder, jetzt erledigt.

    Gesamt M5-Level (bis 29.07.) M30-Level (ab 30.07.) entkoppelt
    n 2275 1850 425 307
    AUC 0,539 0,570 0,399 0,443
    Treffer Modell 58,5 % 58,7 % 57,6 % 56,7 %
    „immer Abprall" 59,0 % 57,7 % 64,7 % 59,9 %
    Überschuss 0,5 Pp +1,0 Pp 7,1 Pp 3,3 Pp
    Backtest-Erwartung war AUC 0,71. Kalibrierung systematisch nach unten verzerrt:
    Bucket 019 % (n=1234, über die Hälfte!) sagt 8 % voraus, real 39,1 %; Bucket
    65100 % sagt 74,5 %, real 48,0 % (invertiert). Im Mittel prognostiziert das Modell
    23 % Durchbruch, real sind es 41 %, und es sagt nur in 8 % der Fälle „break".
    DER KERNBEFUND — das Gate gatet nicht: die **Bruchrate der GESCHLOSSENEN Gruppe
    ist bei JEDER Schwelle 3840 %**, also identisch zur Basisrate (thr 20 → 39,1 % ·
    thr 35 → 37,8 % · thr 55 → 40,5 %). Es findet live keinerlei Auswahl statt. Bei der
    ini-Schwelle 0,55 werden 91,2 % aller Level-Berührungen geschlossen → der
    „P(break)-gegatete Close" ist faktisch zum pauschalen S/R-Close degeneriert, und
    DER ist 2× gemessen und verworfen (backtest_srclose.py: WR verdoppelt, PF/ΣR in
    beiden Hälften schlechter = Gewinner-Kappen). **Das erklärt die kontrafaktische
    Live-Messung vom Vortag** (analyze_srclose_live.py: 8 €/Trade, 43 % klar zu früh
    geschlossen, nur 26 % klar richtig) — Symptom und Ursache passen zusammen.
    ⚠⚠ **URSACHE GEFUNDEN (2026-07-31, backtest_pbreak_touches.py + Rekonstruktion aus
    candles_m1): das Modell ist NICHT verfallen — es bekommt live eine ANDERE
    Merkmalsverteilung, als es trainiert wurde.** Beweiskette in drei Schritten:
    (1) Der Backtest reproduziert die 0,71 (M30-Level, 4 Merkmale, Fit H1 → **AUC
    out-of-sample H2 0,715**) → Modell und Pipeline sind in Ordnung.
    (2) Die Populations-Hypothese ist widerlegt: live nach Berührungszahl
    aufgeschlüsselt ist ausgerechnet die 1. Berührung (= die trainierte Situation) mit
    AUC 0,445 die SCHLECHTESTE Gruppe, die Chop-Fälle (5.+) mit 0,577 die beste.
    Der Versatz „Modell sagt 2325 %, real 4042 %" ist in JEDER Teilmenge gleich groß
    (~17 Pp) — Signatur eines Merkmals-Problems, nicht eines Edge-Verfalls.
    (3) Merkmale aus candles_m1 rekonstruiert (n=2275) — der Versatz ist messbar:
    Merkmal Live-Mittel Training-µ Differenz
    --- --- --- ---
    mom3 0,149 1,4523 1,303
    mom6 0,181 1,6565 1,476
    Daraus rechnet sich der z-Versatz exakt: mom3 → 1,433×(1,303)/2,418 = 0,772,
    mom6 → 0,275, zusammen 1,05. Aus der Trainings-Basisrate 39,4 % (z=0,429)
    wird damit ~19 % — beobachtet werden 23 %. **Erklärt praktisch die gesamte
    Fehlkalibrierung.**
    DER MECHANISMUS: Im Training ist der Touch-Bar jt der Moment, in dem der Kurs
    nach einem ECHTEN ANLAUF (Level bei Entry ≥0,3×ATR entfernt fixiert) zum ersten Mal
    ans Level stößt → mom3 ≈ 1,45×ATR = ein Schub. **Live wählt _draw_levels das
    nächstgelegene Level JEDE SEKUNDE NEU** (mit Hysterese) — der Kurs steht also oft
    neben einem Level, das gerade erst zum nächstgelegenen WURDE, ohne je darauf
    zugelaufen zu sein → mom3 ≈ 0,15. Gleicher Code, gleiche Formeln, aber **Training =
    Level bei Entry fixiert, Live = Level wandert mit.** Konsequenz: das Modell antwortet
    live konsistent „kein Schub → kein Durchbruch → 23 %", liegt damit IMMER unter der
    Schwelle 0,55 → das Gate war seit Inbetriebnahme faktisch nie aktiv.
    Zwei Reparaturwege (beide ungemessen, erst prüfen): (a) Ziel-Level beim Öffnen
    der Position FIXIEREN statt laufend neu zu wählen (macht Live = Training), oder
    (b) das Modell auf einer live-spiegelnden Stichprobe NEU trainieren (bei jedem Bar
    im 0,15×ATR-Band um das dynamisch nächste Level sampeln, ohne Anlauf-Bedingung).
    TOUCH-ZAHL im Backtest (2 Halbjahre, backtest_pbreak_touches.py): hebt die AUC
    nur von 0,715 auf 0,725, Gewicht +0,176 für die 2.4. Berührung (User-Vorzeichen
    bestätigt), aber der R-Ertrag ist nicht robust (H1 +131, H2 43). ⚠ Im Trainings-
    Datensatz sind 76 % Erst-Berührungen und es gibt KEINE 5+-Fälle (live sind 63 %
    fünfte oder später) — das live beobachtete umgekehrte U lässt sich dort nicht einmal
    abbilden. Kleine Verbesserung, nicht der Hebel.
    Pipeline gegen das Training geprüft, KEIN Bug gefunden: dist ist in beiden
    entry-basiert (abs(levelentry)/atr, backtest_srclose_prob.py:86), mom6/mom3
    in beiden am Touch-Bar jt, confirm/reject dieselbe ±0,5×ATR-Definition, Timeout
    zählt in beiden als Abprall. Plausibelste Ursache = andere Stichproben-Population:
    live werden 2275 Berührungen geloggt, entkoppelt (max. 1 je Level und Stunde) bleiben
    nur 307 — dieselbe Position pendelt vielfach um dasselbe Level, live dominieren
    also Chop-am-Level-Fälle. ⚠ Das entlastet das Modell NICHT: entkoppelt ist die AUC mit
    0,443 sogar schlechter. ⚠ Der M30-Teil (AUC 0,399) ist mit n=425 über 2 Tage zu klein,
    um die Umstellung als Ursache zu belegen — die Wiedervorlage (≥30 S/R-Closes auf
    M30-Leveln) läuft weiter.
    **TOUCH-ZAHL als fehlendes Merkmal (User-Idee 2026-07-31 „je öfter der Kurs die Linie
    berührt, desto wahrscheinlicher der Durchbruch") — auf den Live-Daten BESTÄTIGT, aber
    mit Umkehr:** Bruchrate nach n-ter Berührung desselben Levels im 30-min-Fenster, in
    BEIDEN Live-Hälften dasselbe Muster — 1. Berührung 38,3/41,7 % · 2. 40,8/45,6 % ·
    3. 43,2/45,1 % · 4. 45,1/47,4 % (monoton, +6/+4 Pp über Basis) · **5.+ fällt
    zurück auf 37,8/41,8 %** (UNTER Basis, und mit n≈1400 die größte Gruppe). Also ein
    umgekehrtes U, kein monotoner Anstieg: 24 Berührungen = Level wird mürbe, ab 5 in
    30 min = Range, das Level HÄLT. ⚠ Das Modell sieht davon nichts (sagt über alle
    Berührungszahlen konstant ~23 % voraus) → echte orthogonale Information. ⚠ Belastbarkeit
    begrenzt: die beiden „Hälften" liegen nur 4 Tage auseinander (kein Regimewechsel),
    Buckets n=82139; und backtest_srbreak.py fand die Touch-Zahl früher „nicht robust"
    (allerdings ohne Fenster-Definition und ohne die 5+-Umkehr). **Nächster Schritt: als
    5. Merkmal in backtest_srclose_prob.py aufnehmen und über die 34.771 Touches / 2
    echten Halbjahre neu fitten** — dort ist messbar, ob die AUC steigt.
    BEHOBEN am selben Tag (Weg b, User-Entscheidung): MODELL NEU TRAINIERT.
    backtest_pbreak_retrain.py erzeugt eine live-spiegelnde Stichprobe — Level
    dynamisch mit Hysterese wie _draw_levels, gesampelt an JEDEM Bar im 0,15×ATR-Band
    (KEINE Anlauf-Bedingung), Ausgang/Fenster wie live. Kontrolle: die Merkmalsmittel
    dieser Stichprobe (mom6 0,203 · mom3 0,202) treffen die live rekonstruierten Werte
    (0,181 · 0,149) — sie bildet den Live-Pfad also wirklich ab.
    ALT-Modell NEU-Modell
    --- --- ---
    AUC auf live-spiegelnder Stichprobe (H2, oos) 0,368 0,654
    Ø-Vorhersage vs. real 19,2 % vs 39,4 % kalibriert
    R-Ertrag (backtest_pbreak_rvalue.py, Echtkosten, Baseline = nur Trailing): NEU
    schlägt ALT in BEIDEN Hälften bei JEDER Schwelle; gegen die Baseline gewinnt es
    bei 0,35/0,45/0,55 in beiden (bei 0,60 kippt H1). Bestwert 0,35 — H1 149 vs
    Baseline 506 (+357), H2 +1308 vs 110 (+1418). ⚠ Nebenbefund: das ALTE
    Modell war in H1 schlechter als gar kein Auto-Close (653 vs 506), in H2 besser
    (+576 vs 110) — genau die Regime-Abhängigkeit, die live als 8 €/Trade sichtbar war.
    Eingebaut: neue _PB_MU/_SD/_W in engine.py (final auf allen 80k Bars gefittet,
    n=76.542, Basisrate 37,8 %, nachdem H1→H2 validiert war; alte Werte als Kommentar für
    den Rückweg) + sr_close_pbreak=0.35 (Config-Wächter-Anker mitgezogen).
    Verifiziert: Chart-Linien zeigen jetzt 43 %/36 % statt 815 %.
    ⚠⚠ DAS NEUE MODELL INVERTIERT EINE ALTE PROJEKT-REGEL — bewusst so: das Gewicht
    von mom3 dreht das Vorzeichen (+1,4330 → 0,5420). Der alte Befund
    (backtest_srbreak.py) lautete „Kurs KRIECHT ans Level → P(break) nur 26 %, mit
    Schwung → laufen lassen (4143 %)". Das neue Modell sagt das Gegenteil: Anlauf-Schub
    → 26,5 % (das Level ABSORBIERT den Schub und hält), Schwung weg vom Level → 51 %.
    Beides gilt in seiner eigenen Population — die alte Messung betraf den Anlauf zu
    einem FIXIERTEN Level, die neue das Verweilen an einem DYNAMISCH gewählten. Für den
    Live-Pfad gilt die neue Lesart; die alte Faustregel weiter oben im Text bezieht sich
    auf die Anlauf-Situation und ist dort nicht widerlegt.
    Weiterhin offen: auch das neue Modell schließt bei 0,35 noch ~9497 % der
    Positionen (es wählt vor allem den BESSEREN Moment, ist also kein wirklich selektives
    Gate). Und die Touch-Zahl bringt im neuen Modell nichts mehr (AUC 0,652 vs 0,654)
    — die dynamische Level-Wahl erfasst denselben Effekt bereits; die User-Idee hat auf
    den Fehler GEZEIGT, die Ursache lag eine Ebene tiefer. B4/B5-Pflicht: Live-AUC in
    ~2 Wochen gegen 0,65 halten (Reminder pbreak_accuracy_v2, 800 Vorhersagen ab 01.08.).
  • P(Trendumkehr) = GEMESSEN, NICHT BERECHENBAR (analyze_reversal.py, 2026-07-31, User-Frage „können wir die Trendumkehr-Wahrscheinlichkeit berechnen?"): Aufgebaut exakt wie das erfolgreiche P(break)-Modell — mechanisch scharfes Barriere-Rennen (erreicht der Kurs zuerst X×ATR GEGEN oder +X×ATR MIT dem Trend?), logistische Regression auf 8 kausalen Merkmalen (Überdehnung · Regressionswinkel · mom3/mom6 · Trendstärke · Trendalter · Abstand zum M30-Gegenlevel · Vola-Regime), Fit auf H1, AUC + Kalibrierung gemessen auf H2. 80k M5-Bars, ~18,3k/18,5k Stichproben. Ergebnis: Münzwurf in JEDER von 5 Ereignis-Definitionen.

    Barriere / Fenster Basisrate AUC oos AUC in-sample
    0,5×ATR / 60 min 54,0 % 0,509 0,521
    1,0×ATR / 120 min 50,8 % 0,506 0,523
    1,0×ATR / 360 min 51,0 % 0,507 0,523
    1,5×ATR / 180 min 49,7 % 0,499 0,530
    2,0×ATR / 240 min 47,8 % 0,501 0,524
    Die diagnostisch wichtigste Zahl ist die IN-SAMPLE-AUC (0,520,53): das Modell
    erklärt nicht einmal seine eigenen Trainingsdaten. Das ist NICHT Overfitting (da wäre
    in-sample 0,70+ und oos bräche ein) — es ist schlicht keine Information vorhanden.
    Einzelmerkmale allein: alle zwischen AUC 0,489 und 0,515. Basisrate ~50 % → der Trend
    hat auf diesen Skalen keine Persistenz. Kleines kohärentes Muster: kurz (0,5×ATR/
    60 min) leichte Mean-Reversion (54 %), lang (2,0×ATR/240 min) leichte Trendpersistenz
    (47,8 %) — aber 24 Pp und unbedingt (das WANN ist nicht vorhersagbar).
    Warum P(break) funktioniert und das hier nicht — die Kern-Lehre: P(break) fragt
    nicht „wohin geht der Kurs", sondern „hält diese konkrete Struktur?" — bedingt auf
    ein reales Objekt (Level mit Orders dahinter). „Trendumkehr" ist die UNBEDINGTE
    Richtungsfrage, überall im Chart gestellt, ohne Struktur zum Festmachen. Genau die
    Klasse, an der das Projekt 18× gescheitert ist. **Die kalibrierte umkehr-nahe Größe
    existiert bereits: stop_approach** (P(break) auf dem Gegen-/Stop-Level, AUC 0,68/
    0,72) — strukturgebunden statt frei schwebend. Kein Modul gebaut.
  • RICHTUNGS-PFEILE im MT5 statt des Kegels (AR;<art>;<von>;<nach>;<text>, Indikator v1.32, 2026-07-31, User: „anstatt des Trichters hätte ich gerne einen Pfeil, der die wahrscheinlichste Kursentwicklung anzeigt").Randbedingung, die den Entwurf bestimmt: die FREIE Richtungsfrage ist im Projekt gemessen ein Münzwurf (analyze_reversal.py: AUC 0,4990,509 out-of-sample, in-sample nur 0,52 — das Modell erklärt nicht mal seine eigenen Trainingsdaten). Ein frei schwebender Prognose-Pfeil wäre unbelegt — und gefährlicher als der Kegel, weil er überzeugender aussieht. Deshalb DREI Pfeile mit jeweils EIGENER, benannter Beleglage statt einer Prognose:

    Art Farbe Aussage Beleg
    L blau am NÄCHSTEN Level: Durchbruch oder Abprall P(break), AUC 0,65 oos, live kalibriert (27 % vs 28 %)
    S neongrün Squeeze-Ausbruch das EINZIGE 2-Stichproben-validierte Richtungssignal (ØR +0,14…+0,23)
    K grau Konsens der Verdict-Module NICHT kalibriert (in H2 sogar invertiert) → heißt „Konsens", nicht „Prognose"
    L im Detail: genommen wird das NÄHERE der beiden Level. P(break) > 55 % → Pfeil
    DURCH das Level (Ziel = Level ± 0,5×ATR = die Bestätigungsmarke); < 45 % → Pfeil WEG
    vom Level (Abprall); 4555 % → WAAGERECHTER Pfeil „unentschieden" — dort ist die
    Zahl ein Münzwurf, und daraus eine Richtung zu zeichnen wäre genau der Fehler, den
    dieser Entwurf vermeiden soll. S erscheint nur bei state=='active' und wird
    dicker gezeichnet (Breite 3 statt 2) — der validierte Pfeil ist der auffälligste.
    K erst ab bias ≥ 0,2, Länge proportional (max 2×ATR).
    ⚠⚠ **KORREKTUR NOCH AM SELBEN ABEND (v1.33, User: „Trichter anstatt Pfeil wird
    angezeigt"):** L und K starten am SELBEN Punkt (aktueller Kurs) und zeigten real in
    ENTGEGENGESETZTE Richtungen (AR;L;…;85.110;63% Abprall nach unten,
    AR;K;…;85.479;Konsens +1.00 nach oben). Zwei divergierende Linien aus einem Punkt
    sehen aus wie genau der Kegel, den sie ersetzen sollten — der Nutzer sah keinen
    Pfeil, sondern wieder einen Trichter. Gefragt war EIN Pfeil, gebaut waren drei.
    Behoben: InpShowConsensus per Default FALSE (der Konsens steht ohnehin als
    Bias-Nadel im Dashboard) und InpArrBars 14 → 8 (kürzer, damit die Pfeilspitze
    im sichtbaren Bereich liegt). Normalfall jetzt: EIN Pfeil (L, der kalibrierte);
    S kommt nur beim Squeeze-Ausbruch dazu. InpShowConsensus=true holt K zurück.
    ⚠ Lehre: „sie messen Verschiedenes, also dürfen sie auseinanderzeigen" ist logisch
    richtig und als BILD trotzdem falsch — zwei Linien aus einem Ursprung liest niemand
    als zwei Aussagen.
    Umsetzung MQL5: OBJ_ARROWED_LINE vom aktuellen Bar über InpArrBars=14 Bars nach
    rechts, Label an der Spitze. ⚠ Das Label heißt ALBL_* und NICHT T* — sonst würde
    RepositionLabels() es an den rechten Rand ziehen und von der Pfeilspitze lösen.
    Schalter InpShowArrows, Farben einzeln. Der Kegel bleibt im Code und in der
    Dashboard-Kachel; der Bot exportiert ihn nur nicht mehr ins Chart
    ([trading] export_cone=false, true holt ihn zurück).
  • KACHEL „Erwartete Spanne" ENTFERNT, Kernwerte in die Gesamtempfehlung gezogen (User-Vorgabe 2026-08-05, v=140). #card-cone/#cone-list sind raus; neu ist die einzeilige #vd-cone unter der Bias-Nadel in #card-verdict: „📐 erwartete Spanne · 30 75,9976,77 · 60 75,8276,94 · 120 75,5577,21 · trifft 77 %". Gezeigt wird nur noch das 80 %-Band je Horizont; das enge 50 %-Band und der Erklär-Absatz sind entfallen und stecken im title. ⚠ Platzierung und Farbe sind Absicht: die Zeile steht UNTER der Nadel und ist neutral/randlos — sie zeigt Streuung, nicht Richtung, und darf optisch nicht mit dem Bias konkurrieren. Beschriftet bleibt sie mit der real gemessenen Abdeckung (~77 %), nicht mit dem Nennwert 80 %. Backend unverändert aktiv (core/cone.py, Snapshot cone, MT5-Export CN;…). Verifiziert am ausgelieferten HTML (card-cone weg, vd-cone da, keine verwaisten JS-Referenzen).

  • **Wahrscheinlichkeits-Kegel „Erwartete Spanne" (core/cone.py + ehem. Kachel #card-cone

    • MT5 CN;…, 2026-07-31): STREUUNG, NICHT RICHTUNG — und out-of-sample kalibriert.** Der User wollte ein „Kursvorhersagemodul, das mögliche Verläufe in MT5 einzeichnet". Eingezeichnete PFADE wurden bewusst NICHT gebaut (suggerieren eine Präzision, die es nicht gibt — die Lehre aus analyze_verdict_calibration.py, wo conf_pct in H2 invertiert war). Stattdessen: wie WEIT der Kurs in 30/60/120 min plausibel gelaufen sein kann. Gemessen (analyze_cone.py, 80k M5, r(h)=(C[i+h]C[i])/ATR[i], Fit auf H1 → Abdeckung gemessen auf H2):
      Band 30 min 60 min 120 min
      80 % nominal 76,7 % 76,6 % 75,3 %
      90 % nominal 87,5 % 87,2 % 86,5 %
      ⚠ Die Bänder sind konsistent ~35 Pp ZU ENG (H2 war relativ zum ATR volatiler).
      Das wird NICHT weggefittet — Kachel und Tooltip nennen die REAL gemessene
      Abdeckung („trifft 77 %"), nicht den Nennwert. Damit ist der Kegel nach dem
      P(break)-Modell die zweite kalibriert geprüfte Komponente im Projekt. Quantile
      final auf allen 80k Bars gefittet (_CONE in core/cone.py; gleiches Vorgehen wie
      beim P(break)-Modell: erst out-of-sample validieren, dann voll fitten), ATR-normiert
      → regime-übergreifend gültig. **Zentriert auf den AKTUELLEN Kurs, KEINE Drift
      addiert** (Median lag bei ±0,0…0,16×ATR = klein gegen die Bandbreite; eine
      Drift-Korrektur wäre eine Richtungsaussage und genau die ist nicht belegt).
      Snapshot cone {atr, price, levels[{minutes,bars,band,lo,hi,coverage}]}; MT5-Export
      CN;<minuten>;<lo>;<hi> (nur 80 %-Band) → Indikator v1.31 zeichnet zwei sich
      öffnende, gepunktete Pfade, verkettet über 30→60→120 (g_coneT/g_coneLo/g_coneHi,
      je Redraw zurückgesetzt; Startpunkt aus der PX-Zeile, die deshalb g_conePx auch
      dann setzt, wenn InpShowPrice aus ist). Farbe neutral grau (InpConeColor) —
      bewusst keine Richtungsfarbe. InpShowCone=false schaltet ab. **Kein Signal, kein
      Verdict-Gewicht.**
  • Kerzen-Anatomie (core/candles.py + Kachel #card-candles, 2026-07-31, User-Vorgabe „Kerzengröße und Dochtlänge … im Kontext"): REINE ANZEIGE — und die Kachel widerspricht bewusst der Lehrbuch-Deutung. Erkennt mechanisch aus M5-OHLCV: Marubozu · Langer Körper · Langer Docht oben/unten · Spinning Top · Doji · Engulfing (Schwellen identisch zum Backtest, damit Anzeige und Messung dieselbe Sprache sprechen); Refresh ~20 s im Trend-Loop unter mt5_lock, Snapshot candle_anat {tf, items[], vol_hot_x}. GEMESSEN (backtest_candles.py, 80k M5, 2 Halbjahre, Forward-Return 60 min): die gängige Deutung ist in BEIDEN Hälften INVERTIERT. ⚠ Methodik-Kern: gerichtete Klassen werden gegen die DRIFT-NULL (bm×E[d]) gelesen, nicht gegen die Baseline — sonst liest man reine Regime-Drift als Edge (H1 war Abwärts-, H2 Aufwärts-Regime). Überschuss-Befunde: „Langer Körper = Fortsetzung" → 0,059/0,076 (Kurs läuft GEGEN die Kerze); „im Trend = Fortsetzung" → 0,132/0,042 (es ist die KLIMAX-, nicht die Fortsetzungskerze); „2× Volumen = echt" → 0,117/0,204 vs 0,065/0,035 bei normalem Volumen (monoton in die falsche Richtung — mehr Volumen = stärkere Gegenbewegung); „Docht am Level zuverlässig" → in BEIDEN Hälften SCHLECHTER als in der Range-Mitte; „mit/gegen Trend" beim Docht kippt (0,102/+0,089) = Rauschen. Folgetest als Handelssignal = ERNEUT VERWORFEN (backtest_candle_fade.py, Live-Exit SL 2,0/Trail 1,5/BE 1,3/Time-Stop 120', Echtkosten): der Fade der Klimax-Kerze fällt durch — Kontrolle (ohne Volumen-Filter) 0,080/0,046; Vol≥2,0 0,059/+0,103; Vol≥2,5 +0,144/0,080; Vol≥3,0 +0,113/0,064 → benachbarte Schwellen exakt gegenläufig = Rauschmuster. Einzige beidseitig positive Zelle (Vol≥2,0+Marubozu +0,036/+0,043) hat n=91/118 bei 7 getesteten Varianten = Mehrfachvergleich-Artefakt. ⚠ Methodisch die wichtigste Lehre des Laufs: Informations-Überschuss ≠ handelbarer Edge. Dieselbe 2,0-Schwelle liefert im Forward-Fenster 0,117/0,204 (also +0,12/+0,20 beim Fade), im echten Trade aber 0,059/+0,103 — die Gegenbewegung ist diffus, der 2×ATR-Stop wird unterwegs getroffen (WR nur 3540 %). Der Weg zum Ziel zählt, nicht nur das Ziel. 18. verworfener Signal-Eingriff.Konsequenz für die UI: die Kachel ist bewusst NEUTRAL gefärbt (keine grün/rot-Ampel) und der Hinweistext nennt die gemessene Lesart („⚠ Klimax-Kerze — der Kurs läuft danach eher GEGEN die Kerze"), nicht die Lehrbuch-Lesart — eine Richtungsampel würde hier systematisch falsch stupsen. Gleiche Korrektur wie bei der Bounce-Anzeige (heißt deshalb „Überdehnung", bernstein statt grün/rot). Kein Verdict-Gewicht, kein Trade-Trigger. ⚠⚠ DRITTE MESSUNG 2026-08-05 (backtest_candle_fade2.py, User-Wunsch „messe erneut") — ALLE 40 ZELLEN NEGATIV. Bewusst wurde nur EINE neue Achse geprüft, die am selben Tag Squeeze UND Signal von negativ auf positiv gedreht hat: der Einstiegspreis (Market am Bar-Close gegen ruhende Limit-Order am Kerzen-Extrem), dazu der kanonische Exit (Trail 1,0 statt der alten Kopie mit 1,5). Sequentiell EIN Slot, Echtkosten, 80k M5, 2 Halbjahre, Kontrollgruppe „ohne Volumen", Regel vorab fixiert (ØR>0 UND PF>1 beidhälftig · schlägt die Kontrolle · Nachbar-Schwellen kippen nicht · n≥100).

    Volumen Market H1/H2 Limit am Extrem H1/H2
    Kontrolle 0,142 / 0,095 0,191 / 0,103
    ≥2,0× 0,134 / 0,077 0,177 / 0,058
    ≥2,5× 0,051 / 0,111 0,096 / 0,072
    Keine einzige Variante ist irgendwo positiv, auch die Gültigkeitsdauer der
    Limit-Order (3/6/12 Bars) ändert nichts.
    ⚠⚠ DER LEHRREICHE TEIL — der Ausführungs-Hebel ist NICHT universell. Beim
    Squeeze und beim Signal dreht die ruhende Order das Vorzeichen; hier macht sie es
    schlechter. Der Grund ist strukturell: ein Breakout-Einstieg wird von einer
    Stop-Order genau im Moment der Bestätigung gefüllt — ein Fade-Einstieg per Limit
    am Extrem füllt nur, wenn die Klimax sich weiter ausdehnt, also **genau in den
    Fällen, in denen man falsch liegt**. Sichtbar an der Kennzahl-Schere: die
    Trefferquote STEIGT (38,0 → 40,1 %, bei Vol≥2,5 sogar 43,2 → 48,5 %), der ØR fällt
    trotzdem. Der bessere Preis wird mit der schlechteren Auswahl bezahlt.
    Merksatz: ruhende Order hilft bei Momentum-Entries, schadet bei Reversion-Entries.
    ⚠ Nebenbefund zur Stichprobengröße: im 12k-Vorlauf gab es noch positive Zellen
    (Vol≥2,5 H1 +0,207, Vol≥3,0 Limit H1 +0,291) — über 80k verschwinden sie
    restlos. Dasselbe Muster wie beim Auto-Signal-Vorlauf am 30.07. **Ein kleiner Lauf
    hätte hier zum dritten Mal zum Fehl-Einbau geführt.**
    Kachel bleibt unverändert reine Anzeige. Kein neuer Zähl-Eintrag in der
    Verworfen-Liste — es ist dieselbe Klasse wie #18, jetzt zum dritten Mal bestätigt.
  • Chart-Muster (Doppeltop/-boden) als Signal = VERWORFEN (gemessen backtest_doubletop.py, 2026-07-20, User-Idee „Mustererkennung SKS/Doppeltop"): das am saubersten mechanisch definierbare Umkehrmuster (2 ~gleiche Swing-Pivots + Nackenlinien-Bruch, aus derselben Pivot-Erkennung; Einstieg beim Bruch, Live-Exit, 60k M30-Bars, 2 Halbjahre). ALLE Toleranzen (0,3/0,6/1,0×ATR) fallen durch: WR nur 4045 %, ØR 0,01…−0,15, PF<1; die einzige leicht positive Zelle (H1 tol 0,3 +0,044) kippt in H2 auf 0,150 = Rauschen/ Regime, kein Edge. Deckt sich mit der schwachen Empirie (Lo/Mamaysky/Wang 2000: Effekt klein & kaum handelbar; NY Fed: SKS nicht profitabel; out-of-sample schwach) UND der Struktur-Klasse (Muster = Swings+Trendlinien = die gerade verworfene Klasse). 11. verworfener Signal-Eingriff — am Signal ist nichts zu holen.

  • Konsolidierungs-Setups (False Breakout · Distribution · Accumulation · Retest) = VERWORFEN (gemessen backtest_consolidation.py, 2026-07-30, User-Vorgabe „lerne für die Gesamtempfehlung die Konsolidierungen", 3 Chart-Beispiele): Alle auf DERSELBEN Box wie der validierte Squeeze (10 M5-Bars ≤2,5×ATR), 80k Bars, 2 Halbjahre, Live-Exit + Echtkosten. · A) False Breakout / Liquidity Sweep (Ausbruch kehrt zurück → Gegenrichtung): fällt in JEDER Variante durch (Rückkehr-Fenster 1/2/3 Bars × Live-/Setup-Exit × Session) — H1 durchgehend negativ (0,127…−0,252), H2 leicht positiv = Regime-Kippen. Der Setup-eigene Exit („Stop hinter dem Docht, Ziel = Gegenseite der Range") ist in ALLEN 6 Kombinationen negativ — dieselbe Beobachtung wie bei den SMC-Sweeps und den Chartmustern: der Lehrbuch-Exit trägt nicht, der Ertrag kommt (wenn überhaupt) vom generischen Trailing. ⚠ Wertvoller Nebenbefund: 54 % aller Box-Ausbrüche kehren binnen 3 Bars in die Box zurück — der Squeeze-Ausbruch scheitert also mehrheitlich (deckt sich mit seiner 43 %-Trefferquote). Die Gegenrichtung zu handeln rettet das aber NICHT. · B/C) Distribution/Accumulation (Ausbruchsrichtung aus dem Trend VOR der Box vorhersagbar?): die Basisrate widerlegt es direkt — nur 48 % der Boxen nach Aufwärtstrend brechen nach unten, 52 % bei Accumulation (Zufall = 50 %). „Distribution" sah mit ØR +0,168/+0,270 zunächst tragfähig aus; der KONTROLLTEST (derselbe Vor-Trend, aber Bruch in die GEGENrichtung = Trendfortsetzung) liefert in H2 mit +0,364 sogar MEHR als die Distribution-Hypothese (H1: 0,021). Beide Varianten sind Box-Ausbrüche mit Trailing; welche führt, wechselt mit dem Regime. Kein Richtungs-Edge aus dem Vor-Trend. · Retest-Einstieg (statt beim Bruch am zurücklaufenden Level einsteigen): in BEIDEN Hälften schlechter als der direkte Bruch (Distribution 0,071/+0,010, Accumulation 0,184/0,131) — der Retest kostet den besseren Einstiegspreis. Fazit: nichts davon in die Gesamtempfehlung. Der Squeeze bleibt die einzige validierte Box-Verwertung. 17. verworfener Signal-Eingriff. ⚠ Methodisch wichtig: Der Kontrolltest („trägt die Hypothese mehr als ein beliebiger Ausbruch?") ist genau der, der bei backtest_patterns.py noch fehlt — hier hat er die Idee gekippt.

  • Volume Profile / „Liquiditätslinien" (POC/HVN/LVN, Value Area) = VERWORFEN (gemessen backtest_volume_profile.py, 2026-07-30, User-Frage „System zur besseren Vorhersage — Liquiditätsnachfragelinien / wahrsch. Verkaufslinie?"): Getestet, WEIL es die einzige Klasse mit einer anderen Datenquelle war (nicht „wo war ein Docht" = Pivots/Swings, sondern wo wurde tatsächlich gehandelt = Volumen) — SMC/ICT- „Order Blocks"/Liquidity-Linien selbst sind strukturell Pivot-Zonen und damit dieselbe 5× verworfene Klasse. Aufbau: feste Preis-Bins 0,05 $, rollierendes 24-h-Fenster (288 M5-Bars), Bar-Volumen (tick_volume — CFD liefert real_volume=0) gleichmäßig über [low, high], Value Area 70 %; 80k M5-Bars, 2 Halbjahre, Live-Exit + Echtkosten. Ergebnis: nichts trägt. Am eindeutigsten fällt genau die Kern-Idee durch: Reversion am POC („der Kurs prallt an der Liquiditätslinie ab") ist in BEIDEN Hälften negativ (ØR 0,178/0,121, PF 0,79/0,84, n≈400 je Hälfte) — der POC hält NICHT. HVN-Reversion kippt (0,067/+0,044), LVN-Momentum ist Breakeven-Rauschen (0,031/+0,003). Informations-Test (Teil 1, Forward-Return 2 h ohne Exit/Kosten, relativ zur mid-Baseline gelesen — H1 war Abwärts-, H2 Aufwärts-Regime, absolute Werte sind daher Drift-verzerrt): KEIN Bucket weicht konsistent ab — LVN H1 0,11 / H2 +0,09 relativ, HVN H1 +0,08 / H2 0,06 → beide kippen. Tiefere Einsicht: die Volume-Profile-Position ist ein TREND-PROXY, keine unabhängige Information („außerhalb Value Area" = Ausbruch läuft = folgt dem Regime: H2 +0,304 vs +0,092 in VA, H1 umgekehrt 0,122 vs 0,057) — sie erbt damit exakt die Regime-Anfälligkeit von Momentum/Struktur. 15. verworfener Signal-Eingriff. ⚠ Was BLEIBT: das bereits validierte P(break)-Modell ist die einzige belegt-prädiktive „Liquiditäts"-Aussage im Projekt (AUC 0,71 out-of-sample, kalibriert; ~63 % der Level-Touches prallen ab) — und auch das trägt nur als EXIT, nicht als Entry. Einzige noch ungetestete Klasse wäre echter Order-Flow/L2-Orderbuch (Pepperstone = Market Maker, liefert keins; Hyperliquid hätte l2Book — bräuchte erst wochenlange Datensammlung UND einen Beleg, dass HL-Orderbuch-Signale auf den CFD übertragbar sind).

  • Chartmuster-Kachel #card-patterns (core/patterns.py, 2026-07-27, User-Idee „Visuelle Muster-Erkennung"): REINE ANZEIGE, KEIN Signal. Erkennt mechanisch aus den M30-Swing-Pivots (k=3, wie structure.py): Doppeltop/-boden, Kopf-Schulter/ inverse SKS, Tasse+Henkel/invers (heuristisch), auf-/absteigendes + symmetrisches Dreieck. Je Muster: Richtung (bull/bear/neutral), Nackenlinie/Trigger, Measured- Move-Ziel, Status (bildet sich / bestätigt / ungültig→raus), Güte (Extrem-Symmetrie), Bars-her. Refresh ~30 s im Trend-Loop unter mt5_lock; Snapshot patterns {patterns[], atr, cur}. Nur AKTIVE, kursnahe Muster (2026-07-27, _detect-Filter): abgearbeitete Muster (Ziel bereits erreicht) UND zu weit entfernte (_MAX_DIST_ATR=5,0, Trigger >5×ATR vom Kurs) werden ausgeblendet — sonst zeigte die Kachel alte, nicht mehr handelbare Muster. Grafische Darstellung (2026-07-27, User „grafisch darstellen"): (a) Muster-Karte zeigt je Item eine kompakte SVG-Skizze der Form (_patternSvg, viewBox 56×32, farbcodiert grün/rot/grau) — Form auf einen Blick; (b) im Charts-Tab (M30) werden die 2 stärksten Muster als Preislinien gezeichnet: Trigger = kräftige Linie (grün bull/rot bear, Label „▶ bestätigt"/„◌ bildet sich"), Ziel = gepunktete Linie gleicher Farbe. Dazu liefert get_bars bei M30 jetzt patterns (aus patterns.snapshot). Frontend zeigt Karte + Disclaimer „kein Signal". ⚠ Signal- Einfluss ERST nach Backtest (backtest_patterns.py, noch zu bauen) — die Muster-KLASSE ist bereits 2× als Signal verworfen (backtest_doubletop.py oben + backtest_structure.py), also hohe Beweislast; die Kachel macht das Muster nur sichtbar (was ein Kommentator meint), steuert nichts. v2-Kandidaten (Flags/Wimpel, Rechteck, Rundung) dokumentiert, noch nicht gebaut. Web-Recherche (strike.money, IG, Axi): Umkehr (Doppel-, SKS, Rundung) vs. Fortsetzung (Dreieck, Flag, Tasse); Kursziel stets = Muster-Höhe ab Ausbruch projiziert — überall mit dem Caveat „keine Garantie, nur Indikation". Backtest gebaut & gemessen (backtest_patterns.py, 2026-07-27, 80k M30, 2 Halbjahre, Einstieg beim Trigger-Bruch, Live-Exit+Echtkosten): Mehrere Typen sehen ØR-positiv in BEIDEN Hälften aus (Doppeltop +0,43/+0,23 · Tasse +0,35/+0,26 · inv. Tasse +0,36/+0,58 · aufst. Dreieck +0,16/+0,10). ⚠ ABER das ist ein Exit-Artefakt, KEIN Muster-Edge: die Ziel-Trefferquote (Measured-Move vor SL) ist nur 1338 % — die eigene Muster-Prognose trifft also in ~⅔ der Fälle NICHT. Der positive ØR kommt vom Trailing-Exit auf einem Momentum-Bruch (Trigger-Bruch = Breakout, und Breakout+Trailing hat generisch Edge — s. Squeeze), nicht von der Muster-Vorhersage. Deckt sich mit backtest_doubletop.py (dort mit Ziel-/Fix-Exit → negativ) — der Unterschied ist NUR der Exit. Fazit: NICHT reif für Signal-Einfluss — es fehlt die Kontrolle „generischer Breakout + gleicher Exit" (isoliert den Muster-Beitrag, wie Squeeze vs. beliebige Box); ohne die ist „Muster wirkt" nicht belegt. Muster bleiben reine Anzeige. Ziel%-Spalte = der ehrliche Muster-Check (1338 % = Muster sagen den Kursverlauf kaum voraus).

  • MQL5-Chart-Erweiterungen 2026-07-30 (Indikator v1.16v1.18, alles REINE ANZEIGE): · Trade-Marker TR;<O|C>;<broker_time> — die Kerze der Eröffnung wird HELLGRÜN, die des Close DUNKELGRÜN umrandet (OBJ_RECTANGLE über Bar-Spanne + High/Low, iBarShift → sitzt auf jeder Chart-TF korrekt). Quelle: offene Position (trader.open_time) + die letzten _TRADE_MARKS=6 geschlossenen Trades. · Liquiditätszonen EQ;<H|L>;lo;hi;<broker_time>Equal Highs / Equal Lows aus den M30-Pivots (zwei Pivots innerhalb _EQ_TOL_ATR=0,15×ATR, 240 Bars auseinander) als goldenes Band + gestrichelte Kante, nach rechts verlängert (Darstellung nach User-Referenzbild). Berechnung in wave_rec (_liq_zones, Snapshot liq_zones), Mindesthöhe _EQ_MIN_H_ATR=0,10×ATR. ⚠ Als SIGNAL gemessen und verworfen (backtest_liquidity_sweep.py) — nur Kontext. · Aktueller Kurs PX;bid — fett oben rechts (OBJ_LABEL, CORNER_RIGHT_UPPER, Arial Black, InpShowPrice/InpPriceSize/InpPriceColor). · ⚠ Zwei Fallen beim Pfeil-Bug („Pfeile werden nicht angezeigt", v1.17): (1) ANCHOR_LEFT ist für OBJ_ARROW UNGÜLTIG — erlaubt sind nur ANCHOR_TOP/ ANCHOR_BOTTOM; (2) die Pfeile sitzen rechts NEBEN der letzten Kerze → ohne CHART_SHIFT=true gibt es dort keinen sichtbaren Bereich. Beides im Theme/ Handler behoben. · ⚠ Blink-Fix in core/hl_walls.py („Pfeile blinken nur manchmal auf"): bei einem Fehlschlag (HTTP-Timeout, HL-Server kurz busy) wurde der Cache auf None gesetzt → LQ-Zeilen fehlten in der CSV → der Indikator löschte die Pfeile beim Redraw. Jetzt wird der letzte gute Stand _STALE_GRACE_S=120 s weitergereicht; erst danach verschwinden die Pfeile (echter Ausfall).

  • Kurslücken-Kachel (#card-gaps): offene D1-Gaps des laufenden Jahres (gaps.GapAnalyzer, Refresh ~10 min im Trend-Loop unter mt5_lock). Je Gap: Richtung, Zone, Fill-Level, Abstand zum Kurs. Die Fill-Level fließen als S/R-Magnete in _sr_levels → damit in die Wellen-Konfidenz (set_sr_context, _SR_BONUS/_SR_PENALTY), Close-Alarme und Verdict-Kontext — und so indirekt in den Autotrader (der auf die Welle hört). Keine eigene Handelsrichtung. Snapshot gaps. Mess-/Einzeltool: find_gaps.py.

    • ⚠⚠ ERKENNUNG WAR DEFEKT — zwei Fehler (Fix 2026-08-03, User-Meldung „die Kurslückenerkennung funktioniert nicht richtig"): (1) Die größte Lücke des Jahres wurde um 3 Cent verfehlt. Die Definition verlangte ein lückenloses Vakuum zwischen den TAGESSPANNEN. Am Wochenende 31.07.→03.08. fiel WTI von Schluss 86,33 auf Eröffnung 79,76 = 6,57 $ — unsichtbar, weil Montags Hoch (81,67) das Freitags-Tief (81,64) um 0,03 $ überlappte. In einem 24/5-Markt überlappen die Tagesspannen fast immer; die strenge Regel fand im ganzen Jahr nur 5 Gaps. (2) Alle Gap-Datumsangaben waren einen Tag zu früh. int(b["time"]) _BROKER_OFFSET_S zog 3 h von einer Bar-Zeit ab, die MT5 bereits als naive Broker-Zeit liefert → jeder D1-Bar rutschte auf den Vortag (die gemeldete Lücke „2026-07-26" war real der 27.07.). Am Jahreswechsel hätte es zusätzlich den year-Filter verschoben. Fix: zwei getrennte Gap-Begriffe im Snapshot (kind): · vacuum = die alte, strenge Definition — NUR diese speisen zone_lines()_sr_levels → Wellen-Konfidenz, denn nur auf ihnen wurde gemessen. Der Strategie-Eingang ist damit bitgenau unverändert (verifiziert: zone_lines() liefert vorher wie nachher [88.288]). · close_open = Schluss→Eröffnung, also das, was ein Mensch „Kurslücke" nennt. REINE ANZEIGE, ab min_close_open=0,50 $, füllt auch am selben Tag. Ergebnis live: 4 offene Lücken statt 1 (1 Vakuum + 3 Eröffnungslücken), darunter endlich die 6,57-$-Wochenendlücke mit Fill bei 86,33. Frontend kennzeichnet beide Arten (Vakuum / Eröffnung) mit Tooltip, v=133. ⚠ Bewusst NICHT getan: die close_open-Lücken in die Konfidenz einzuspeisen. Das wäre ein ungemessener Strategie-Eingriff — und der gemessene Befund unten beruht auf der Vakuum-Definition. Wer sie einbeziehen will, misst vorher.
    • Gap-Magnet als Handelssignal = VERWORFEN (gemessen, backtest_gaps.py): Signale WEG vom nächsten offenen Gap haben +0,094 Ø-Edge, ZUM Gap nur +0,014 — der Magnet zieht NICHT (das Gap blieb offen, weil der Trend von ihm weglief). „Richtung Gap handeln" wäre schädlich; „weg" ist nur Trendbestätigung (macht die Welle schon). Gaps bleiben reiner Kontext.
  • Tagesziele — Dashboard-Kachel ENTFERNT (2026-07-13): Das Frontend zeigt die Web-Prognose nicht mehr. Das Modul daily_levels läuft unverändert weiter: die Level fließen als S/R-Kandidaten in _sr_levels (Anzeige/Close/Verdict-Kontext) und in den 07:30-Tagesreport. snapshot.daily_levels bleibt befüllt (nur ohne UI).

  • Täglicher Report (07:30 Berlin, engine._send_daily_report): Web-Prognose + Vortags-Bilanz (stats_overview("yesterday")) → Telegram-Push + E-Mail (via mailer.send_graph_mail, [graph]-Creds, Absender [graph] sender= mailagent@hocks.eu). Empfänger: [graph] report_to (kommagetrennt, Default trading@hocks.eu, axel@hocks.eu). Ersetzt scripts/send_daily_report.ps1 (das hier nie lief — Microsoft.Graph-PS-Modul fehlt; Python nutzt Graph-REST direkt). ⚠ Graph/Exchange-Zustellung kann mehrere Minuten verzögert sein (202 = nur angenommen). Fail-safe: kein Provider/Graph → Report übersprungen. Versand-Status persistiert (report_state.json, _load/_save_report_state) — vorher schickte jeder Server-Neustart nach 07:30 den Report ERNEUT (real 3× am 07.07.); gilt für Tages- UND Wochenreport.

  • B4-Wochenreport (automatisch, engine._send_weekly_report): montags ab 07:30 Berlin, 1×/Woche (gleicher _daily_loop, _weekly_last_run=(ISO-Jahr,KW)) → E-Mail + Telegram. Ruft weekly_review.build_report(m5=…) (Live-Stats vs. Backtest-Erwartung, Leckage-Flags — Track B/B4) und schickt es als <pre> (Betreff „Oil Wochenreport (B4) — KW…", Empfänger = [graph] report_to). Squeeze-Breakout- B4-Monitor (_squeeze_section): rechnet die Squeeze-Regel mit den Live-Params auf den letzten ~30 Tagen M5 NEU (mechanischer Setup → Live-Perf. = Neuberechnung, keine Trade-Attribution nötig) und hält ØR/PF gegen +0,14…+0,23. M5-Bars holt die Engine unter mt5_lock und reicht sie durch (build_report(m5=…); standalone lädt via _fetch_m5, schließt MT5 nur bei Eigen-Init). Fail-safe wie der Tagesreport. Manuell jederzeit: python weekly_review.py.

  • Position-Kachel zeigt SL/TP + Einsatz (pos-sl/pos-margin; trader.snapshot() liefert sl/tp/margin). Kopfzeile der Kachel (.pos-top, 2026-07-15, User-Vorgabe): Kurs · G/V in EINER Zeile (18-px). Das G/V (#pnl) zeigt NUR NETTO (Brutto entfernt, kein „n."-Präfix): bei Gewinn P&L × (1 [trading] wht_pct/100) = abzgl. WHT-Quellensteuer, bei Verlust unverändert (keine Steuer). Snapshot wht_pct; Tooltip nennt den Satz. Swap ist bereits im P&L (trader.pnl = profit + swap), Kommission beim Standard-Konto 0. Einsatz/SL/TP/Richtung stehen in der Trade-Leiste, S/R im Chart (aus der Kachel entfernt); emg-lbl-Beschriftungen ausgeblendet (Felder self-labeln via Platzhalter/Button-Text). Fehlender SL → rot „⚠ kein SL gesetzt!". „Einsatz" = gebundene Margin (order_calc_margin). Notfall-Stop- Feld (#emg-input + setzen/aus): manueller Verlust-Wert → Server schließt sofort (s. „Notfall-Stop"). Feld wird mit aktivem bzw. gemerktem Wert (emergency_loss/emergency_remember) vorbefüllt (außer beim Tippen); Enter/„Go" löst „setzen" wie der Button aus. Status #emg-state: „aktiv: schließt bei −…" (rot) bzw. „vorgemerkt −… (aktiv beim Öffnen)". Gewinn-Feld („Gewinn mitnehmen bei") aus dem Dashboard ENTFERNT (User-Vorgabe 2026-07-14) — das Backend bleibt vollständig erhalten (engine.set_takeprofit, kombinierter _check_auto_close, POST /api/takeprofit, Telegram „💰 Gewinn mitgenommen", Config [trading] auto_takeprofit Default 0=aus), ist ohne UI aber schlafend; der P(break)-S/R-Auto-Close übernimmt die Gewinnmitnahme-Rolle.

  • Blinken: LONG/SHORT-Button blinkt bei jeder Richtungs-Empfehlung (nicht mehr an 70 % gekoppelt). Ton bei Empfehlung (beepRecommendation): sofort beim Signal-Wechsel (WARTEN→LONG/SHORT oder Flip, 3-s-Flacker-Schutz) + Erinnerungs- Ton alle 30 s (_REC_REBEEP_MS), solange der Button blinkt — nur wenn flat (keine Position). LONG = aufsteigend, SHORT = absteigend (unterscheidbar vom Close-Alarm). Audio erst nach 1× Nutzer-Tap aktiv — ist es noch gesperrt, zeigt ein einmaliger Toast „🔇 Ton gesperrt — einmal tippen". Kein Ton bei offener Position. Mute-Button (#mute-btn, 🔊/🔇 rechts in der Tab-Leiste) schaltet ALLE Töne (Gate in _tone + beiden beep-Funktionen), persistiert via localStorage("oil_muted"). CLOSE blinkt + Ton NUR wenn pnl > sr_close_min_gain (Feld leer/0 → pnl>0 wie bisher; deckt sich mit dem Auto-Close-Gate — unter der Schwelle schließt der Bot ohnehin nicht, also auch kein Alarm; Frontend gainOK, gilt für Flip-Close UND S/R-Close-Blink/Ton). Der Telegram-Flip-Push (_check_close_ alert, Backend) bleibt bei pnl>0. Flip-Close = Signal-Flip gegen die Position. S/R-Ziel-Close-Hinweis ENTFERNT (stupste zum verfrühten Gewinner-Kappen → Verlust-Schiefe; Gewinner führt das Trailing). Bei Verlust-Trade: kein Close-Alarm. Jeder echte Auslöser loggt 🔔 Flip-Close-Alarm #N + Zähler snapshot.close_alert_count (zum objektiven Nachzählen, statt aus Trades/Recs zu rekonstruieren). Engine: _check_close_alert (Telegram max. 1×/5 min, _CLOSE_ALERT_COOLDOWN_S — kein Spam, wenn der Kurs ums Level pendelt). ⚠ Cooldown ist ticket-basiert (_close_alert_ticket): Reset NUR bei echter neuer Position; ein kurz flackerndes Ticket (transient leeres positions_get) setzt den Cooldown NICHT mehr zurück (war die Spam-Ursache). Frontend: flipClose || srTarget. Kein Auto-Close an S/R (Backtest).

  • Trade-Abschluss-Telegram ENTFERNT (history.log_trade_close sendet kein Telegram mehr; Import bereinigt). Close-bezogene Telegrams kommen jetzt nur noch aus der Engine: 🔔 CLOSE-Signal (echter Signal-Flip, _check_close_ alert) + 🚨 Notfall-Close / 💰 Gewinn-Close (armierter Auto-Close, _check_auto_close). Frontend-Blink/Ton weiterhin nur bei Flip im Plus.

  • Order-Bestätigungen: Kein hartes Blockieren mehr. Eröffnen bei WARTEN/Gegen- Signal → Hinweis-Dialog „trotzdem eröffnen?" (überschreibbar). CLOSE bei negativem P&L → Sicherheitsabfrage; im Plus/Breakeven direkt. Sonst keine Token-Dialoge (require_token=false). Audio braucht 1× Nutzer-Tap.

  • UI-Vorbehalt an der Gesamtempfehlung (2026-08-06, v=143): Unter der Headline steht jetzt eine feste Zeile (#vd-note) — „Richtung ≈ Münzwurf (gemessen 4851 % Treffer) … lies das als Auslöser für eine Order am Level, nicht als Kursprognose". Der Konfidenz-Ring ist mit „Score" beschriftet (.vd-ring-lbl) statt nackt in Prozent. Grund: analyze_signal_live.py (4851 % Treffer über 1.071 Live-Episoden) und analyze_verdict_calibration.py (conf_pct nicht kalibriert, in H2 invertiert). Solange dort „▲ LONG · 89 %" stand, lud die Anzeige zu einer Erwartung ein, die die Messung nicht deckt. ⚠ Bewusst statisches Markup, kein JS — es ist ein Dauerhinweis, kein Zustand; damit entfällt auch jedes Null-Risiko beim Render (s. Cache-Regel oben).

  • ⚠⚠ VIER VERBESSERUNGEN AN DER GESAMTEMPFEHLUNG (User-Vorgabe 2026-08-06, v=144) — alle aus dem Gemessenen abgeleitet, KEIN neues Signal. Ausgangspunkt: die Karte hat genau eine belegte Funktion, das Veto (s. Ausrichtungs-Split); als Prognose ist sie ein Münzwurf. Verbessert wird also das Veto und die Sichtbarkeit — nicht die Vorhersage. (1) SELBST-KALIBRIERUNG — die Karte zeigt ihre eigene Trefferquote. Neue Tabelle rec_outcomes (DB-Backup …bak-2026-08-06-recoutcomes): history.log_rec_outcome schreibt eine Zeile je EPISODE (Richtungswechsel, nicht je Minute — sonst zählt man dieselbe Marktlage dutzendfach); engine._evaluate_rec_outcomes trägt ~alle 5 min im Trend-Loop den Forward-Return nach 30/60 min aus candles_m1 nach (⚠ Broker-Offset, wie bei _evaluate_pbreak_predictions). history.rec_accuracy(200) → Snapshot rec_track → Zeile #vd-selfcal. ⚠ Warum das der grösste Hebel ist: die Qualität der Empfehlung war bis heute unsichtbar — sie musste auf Nachfrage rückwirkend gemessen werden. Dasselbe Muster (pbreak_predictions) hat den Ausfall des P(break)-Modells LIVE aufgedeckt, was kein Backtest konnte. Die Zeile ist gegen 50 % zu lesen, nicht gegen 0. Fehlen Bars (Wochenende), bleibt die Zeile offen statt falsch bewertet; nach 6 h wird sie geschlossen. Mit 4 Szenarien getestet (tests_rec_outcomes.py): Episoden-Dedup · WARTEN re-armt · Auswertung inkl. Offset in beide Richtungen · fehlende Bar → bleibt offen · Aggregation. ⚠ Zwei Fehler dabei waren MEINE Testfehler, nicht Code-Fehler: die Test-Kerzen aus dem Vorabschnitt lagen im Fenster des nächsten, und die DB-Kopie enthält echte candles_m1 — der Fall „keine Bar" entsteht erst, wenn das Fenster wirklich geleert wird. (2) AUSRICHTUNGS-SPLIT auf die volle Historie + je Lot. alignment_stats lief auf den letzten 40 Trades und zeigte absolute Euro. Jetzt über alle 1.216 (Default last_n=0) und zusätzlich pnl_lot — absolute Euro-Vergleiche sind bei wechselnder Positionsgrösse ungültig (Lehre 04.08.). Der Order-Dialog zeigt beides. Damit steht die einzige beidhälftig robuste Aussage der Karte auf ihrer vollen Stichprobe: gegen das Signal 6,60 €/Lot. (3) BLOCK-GRUND SICHTBAR (engine._block_mix_cached → Snapshot block_mix#vd-blocks): „heute X % ohne Block · entry_room … · min_conf …". Auf die Frage „warum gab es so wenige Empfehlungen?" war bisher eine DB-Abfrage nötig. ~120 s gecacht, reine Anzeige. (4) ELLIOTT ENTMACHTET — Verdict-Gewicht 1,0 → 0 (gemessen analyze_verdict_modules.py, 15.544 Zeilen). Elliott hatte mit 1.384 Episoden die grösste Stichprobe der ganzen Tabelle und ist darin flach: Überschuss 0,075/+0,032 ×ATR, 50 % Treffer. Gleichzeitig war es mit 25,9 % die zweitlauteste Stimme — weil es in nur 1 % der Zeilen schweigt. Mit H1 (38,1 %) bildete es einen Dauerhintergrund von ~64 %. Der Chip bleibt (das Ziel ist als Kontext nützlich), nur die Stimme entfällt. ⚠ Sichtbare Folge: die Nadel schlägt stärker aus. Real beim Umbau +0,33 → +1,00, weil nur noch H1 (1,5) und Liq-Trend (0,5) sprachen. Mit wenigen sprechenden Modulen kann ein einzelnes die Nadel auf ±1 ziehen — das ist ehrlicher als eine künstlich gedämpfte Nadel, sieht aber dramatischer aus. ⚠ Reine Anzeige-Änderung — die Order steuert die Welle über die Headline, nicht der Konsens. Zurück: ew = 1.0 if ev != 0 else 0.0 in _verdict. ⚠ Nicht angefasst, aber auffällig (für die 25.000-Verdict-Messung): Orderbuch ist über 2.688 Episoden beidhälftig NEGATIV (0,054/0,066, 49 % Treffer) bei Gewicht 0,5 — der einzige konsistent falsche Beitrag. Muster (+1,27/+0,67, 58 %, n=88) und Liq-Trend (+0,20/+0,32, n=132) sehen gut aus, sind aber zu dünn. Alle übrigen kippen zwischen den Hälften.

  • ⚠⚠ HYPERLIQUID: WAS ES LIEFERT UND WAS DAVON TRÄGT (2026-08-06, User-Frage „können wir mehr HL-Daten nutzen?"). Direkt gegen die API geprüft, nicht aus dem Code geschlossen. Angebot: vier Öl-Märkte, zwei mit echter Liquidität — xyz:CL (unser Feed, OI 3,07 Mio, Tagesvolumen 316 Mio) und xyz:BRENTOIL (OI 2,25 Mio); flx:OIL und cash:WTI sind tot (OI 0). Je Markt mark_px · oracle_px · funding · open_interest · day_volume, dazu l2Book mit 20 Levels je Seite. Historie: fundingHistory gibt 5.095 Stundenwerte über 7 Monate (inkl. premium), candleSnapshot H1-Kerzen, predictedFundings venue-übergreifend. ⚠ Open Interest hat KEINE Historie — nur der Momentanwert; OI wäre sammelpflichtig und bleibt offen. Netz-Frage geklärt (sah zuerst nach einem Defekt aus): config.ini sagt network = testnet, und Testnet hat ein LEERES Buch (0 Bids/0 Asks, OI 2,55 gegen 3,07 Mio). Es sind aber zwei Schalter: [hyperliquid] network steuert das Handeln (bewusst aus), [sim] price_network die Daten und steht per Default auf mainnet — live bestätigt (/api/market meldet network: mainnet). ⚠ Bleibt eine Falle: ein Wort in der falschen Sektion, und der Bot zeichnet stillschweigend Wände aus einem leeren Buch. (1) MEHR ORDERBUCH-DATEN = NEIN, zweimal unabhängig gemessen. Das HL-Projekt selbst (bookflow_report.txt): Bid/Ask-Druck auf den CFD 0,61/0,52 bp und KIPPT; Wände halten in 4750 %; die eigene Schwelle dort lautet „< ~3 bp ist von den Kosten gefressen" — gemessen sind es 0,21,0 bp. Dazu der Lead-Lag-Befund: Pepperstone führt (Peak k=1 ≈ 6 s), HL-Richtung trifft nach 6 s zu 51 % (n=545). Alles Preis-Abgeleitete von HL läuft hinterher. (2) MODUL „ORDERBUCH" ENTMACHTET — Gewicht 0,5 → 0. Unabhängige Bestätigung aus analyze_verdict_modules.py: über 2.688 Episoden beidhälftig NEGATIV (0,054/0,066 ×ATR, 49 % Treffer) — der einzige konsistent falsche Beitrag im Feld. Chip bleibt, Stimme entfällt. Zurück: ow = 0.5. (3) FUNDING/PREMIUM GEMESSEN = TRÄGT NICHT (analyze_hl_funding.py). Positionierung statt Preis, also nicht vom Lead-Lag-Einwand betroffen — und dank fundingHistory sofort prüfbar. 5.095 Stundenwerte, 3.626 mit passender H1-Bar, gegen den Pepperstone-CFD, 2 Hälften, gegen die Drift. ⚠⚠ Der erste Lauf meldete mehrere „robuste" Buckets — und war falsch. Zwei Fehler in meinem eigenen Skript: (a) überlappende Forward-Fenster (stündliche Beobachtungen, 4/12/24-h-Horizont → aufeinanderfolgende Zeilen teilen fast ihr ganzes Fenster; aus n=506 werden ~21 unabhängige Fälle) und (b) global gebildete Quintile, wodurch die Bucket-Zugehörigkeit mit der Zeit konfundiert war (Q5 hatte 506 Fälle in H1, aber 219 in H2 = Regimewechsel, nicht Merkmal). ⚠ Dritter Hinweis, der stutzig machte: Funding- und Premium-Tabelle waren nahezu identisch — HL rechnet das Funding AUS dem Premium; es war eine Messung, doppelt berichtet. Entüberlappt und mit Quintilen je Hälfte hält KEIN Bucket die Regel; die verbleibenden sitzen bei n=1530 und sind Rauschen. Kein Verdict-Gewicht. (4) HL-KONTEXT IN DEN MELDUNGEN (reine Anzeige, v=145). hl_walls.market() (lokales Dashboard, keine neue externe Abhängigkeit) → Snapshot hl_ctx → Zeile #hl-note: „Perp 0,03 $ zum Oracle · Funding 0,0083 %/h · OI 3,07 Mio". Kein Gewicht, kein Trigger; der Tooltip nennt die Messung. ⚠ Zwei Bau-Fallen, beide behoben: der Aufruf lief zunächst immer ins Leere, weil er den 2-s-Timeout der Wände erbte — /api/market fragt HL live ab und braucht real 7,2 s (eigener _MARKET_TIMEOUT_S=12); und der stille except machte daraus ein wortloses None. Danach hätte er den Trend-Loop 7 s blockiert → läuft jetzt im Daemon-Thread, wie der Wirtschaftskalender und aus demselben Grund.

  • Gesamtempfehlung aufgeräumt + Tooltips überall (User-Vorgabe 2026-08-06, v=147): · Nur noch Module MIT Stimmrecht als Chip. _verdict.add() hat ein used-Flag; used=False heisst dauerhaft entmachtet (Elliott, Orderbuch — beide heute gemessen ohne Inhalt), das Frontend filtert sie aus. ⚠ Sie bleiben im Snapshot und in verdict_voteslog_verdict_votes speist daraus die Telemetrie, und die 25.000-Verdict-Messung braucht die Reihe weiter. Ausgeblendet wird die Anzeige, nicht die Messung. ⚠ Module mit weight=0 aus situativer Stille (Welle bei WARTEN, Squeeze ohne Ausbruch) bleiben sichtbar — die sprechen wieder. · #vd-note entfernt (der Münzwurf-Hinweistext) samt CSS. Die Aussage steht weiter im Tooltip der Karte und in dieser Datei. · Tooltips auf allen Dashboard-Anzeigen (59 title-Attribute): Karten- Überschriften, Header-Boxen, Trade-Leiste und die Anzeige-Elemente. Wo eine Messung existiert, steht sie im Tooltip (z. B. Kerzen-Anatomie: „Lehrbuch-Lesart gemessen INVERTIERT"; Bounce: „~50 % Trefferquote, KEIN Signal"). Gesetzt über set_tooltips.pyidempotent, ergänzt nur fehlende title=, damit ein erneuter Lauf nichts überschreibt.

  • „Setup" und „Lauf" aus der Meldungen-Karte entfernt (User-Vorgabe 2026-08-06, v=148). Beide .kv-Zeilen samt der JS-Zuweisungen (wave-setup, wave-move) sind raus. Begründung: das Setup steht ohnehin in der Verdict-Headline und in den Gründen, der „Lauf" beschrieb die Vergangenheit ohne Handlungsbezug. ⚠ Fallstrick beim Entfernen: die Zeile const w = d.wave_signal || {}, ws = … stand direkt über der Setup-Zuweisung und wäre mit rausgefallen — w wird aber weiter unten noch achtmal gebraucht (Gründe-Zeile, Blinken der Order-Buttons, Close-Alarm, News-Konflikt). Ein ReferenceError mitten in render() bricht den GESAMTEN Render ab (s. Cache-Regel oben), das Dashboard wäre eingefroren. Gegenprobe eingebaut: alle $("id")-Zugriffe der app.js gegen die IDs der index.html geprüft — 0 verwaiste Referenzen.

  • ⚠⚠ „stark LONG" bei WARTEN — der scheinbare Widerspruch aufgelöst (User-Fund 2026-08-07, v=149). Reale Anzeige: Headline ◌ WARTEN · M30, darunter „Modul-Konsens: stark LONG · 2 von 2 stimmberechtigten für LONG (7 ohne Aussage)". Beide Zahlen stimmten, die Kombination war trotzdem irreführend. Zwei Ursachen, beide behoben: (1) Die Headline nannte den Grund nicht. Der Block stand im Snapshot (wave_signal.block = min_conf, conf 49 gegen die 55-%-Schwelle — die Welle HATTE eine LONG-Richtung, ihr fehlten sechs Punkte), gerendert wurde er aber nur bei breakout_pending. Bei jedem anderen Gate blieb ein nacktes „WARTEN" stehen. ⚠ Das war meine Lücke vom 06.08.: ich habe die Tages-Verteilung (#vd-blocks) gebaut und den aktuellen Grund vergessen. Jetzt steht er an der Headline: „◌ WARTEN · M30 · Konfidenz 49 % < 55 % — Setup zu schwach" (Mapping für alle neun Gate-Codes). (2) „2 von 2" las sich wie Einstimmigkeit. Es waren 2 von 7 sichtbaren Modulen — H1 (1,5) und Liq-Trend (0,5) — und die Welle, die als einzige die Order steuert, war nicht dabei. bias = 1,0 ist arithmetisch korrekt (das gesamte sprechende Gewicht war einig), aber die Basis war winzig. Seit dem Entmachten von Elliott und Orderbuch (06.08.) tritt das häufiger auf — die Änderung hat das Problem nicht erzeugt, aber verschärft. Neu: die Zeile führt mit der Basis („2 von 7 Modulen sprechen, alle für LONG"), das Wort „stark/leicht" entfällt, solange weniger als die Hälfte der Module spricht, und eine stumme Welle wird ausdrücklich genannt („⚠ die Welle (steuert die Order) schweigt"). Live gegengerechnet, beide Zeilen erscheinen wie beabsichtigt. ⚠ Reine Anzeige — an Gewichten, Gates und Order-Logik ist nichts geändert.

  • Asset-Version aktuell v=158 (in web/index.html hochzählen, siehe Workflows). Schriftgrößen 2026-07-24 global +4px (2× je +2px auf User-Wunsch; Body-Basis 14→18px). ⚠ Betrifft in style.css sowohl font-size:Npx (71×) ALS AUCH die font:<weight> Npx/…-Shorthand (3×: .ms-chip/.ms-bos-lbl/.sqm-badge — die wurden zuerst übersehen, weil das erste Bump-Skript nur font-size erfasste)

    • Chart-fontSize in app.js. Bei künftigen globalen Font-Änderungen die Shorthand nicht vergessen.

Hyperliquid-Kurs neben dem Broker-Kurs (2026-08-01)

User-Wunsch: „wenn Pepperstone geschlossen ist trotzdem den aktuellen Kurs und die Liquidität von Hyperliquid einbinden" → nachgeschärft zu „blende den HL-Kurs neben den Pepperstone-Kurs ein" (ehrlicher: man sieht die Abweichung, statt dass still getauscht wird) → plus „bei geschlossenem Markt auch den G/V mit HL berechnen".

⚠⚠ Die Basis-Korrektur war am Wochenende ZIRKULÄR (Fix 2026-08-02)

User-Fund: „Auf app.hyperliquid.xyz steht WTIOIL-USDC bei 81,392, im Dashboard 86,424." Beides war erklärbar — und dahinter steckten zwei Fehler.

(1) Falsches Label (mein Fehler vom 01.08.). Angezeigt wurde mid_mt5 = HL + Basis, also der auf Pepperstone-Niveau umgerechnete Wert — unter der Beschriftung „HYPERLIQUID KURS". Vorher hieß das Feld nur „HL" (unklar, aber nicht falsch); die Ausschreibung machte daraus eine unzutreffende Behauptung. Jetzt steht dort der ROHE HL-Kurs (exakt die Zahl von der HL-Seite), das CFD-Äquivalent wanderte in den Tooltip.

(2) Der eigentliche Bug: die Basis saugt bei stehendem Broker den HL-Move auf. Die Basis ist Median(mt5_mid hl_mid) über die letzten 60 Snapshots — ohne Prüfung, ob mt5_mid überhaupt noch lebt. Steht Pepperstone (Wochenende/Feiertag), friert mt5_mid ein, während HL 24/7 weiterläuft → die „Basis" wächst exakt um den HL-Move, und hl_mid + basis rekonstruiert wieder den EINGEFRORENEN Broker-Kurs. Die Korrektur wird zirkulär — also genau dann wertlos, wenn man sie braucht. Beleg aus leadlag.db (beide Kurse nebeneinander, 46k Ticks):

Zeitraum Basis
27.31.07., Broker live +0,42 … +0,58 (bemerkenswert stabil)
ab 31.07. 22:55 (Freeze), Broker steht 1,07 → +5,03
Real angezeigt: 86,414 statt der echten ~81,89 — Abstand zum eingefrorenen Broker-Bid
nur noch 0,081. Der Wochenend-Move war dabei ECHT, kein dünnes Buch: So 03:00→05:00
von 85,51 auf 80,84, danach 13 h stabil um 81 bei ~600 Messungen/Stunde.

Fix (HL-Projekt, server.py /api/walls): Der Endpoint sucht jetzt die letzte echte Broker-Bewegung (Rückwärtslauf über _BASIS_LOOKBACK=40 000 Zeilen ≈ 2,3 Tage — muss ein ganzes Wochenende überspannen) und bildet die Basis aus dem Fenster davor. Steht der Broker länger als _MT5_STALE_S=180 s (gleiche Schwelle wie engine._TICK_STALE_S), ist die Basis eingefroren und der Endpoint liefert basis_stale: true + mt5_still_s. Verifiziert an den Echtdaten: letzte Bewegung 31.07. 22:55:06 (= der eingefrorene Tick), Basis +0,4765 statt +5,0285, Antwortzeit 109 ms (Timeout des Oil-Bots ist 2 s).

Materielle Folge, nicht nur Kosmetik: Die offene SHORT-Position (0,57 Lots ab 86,163, TP 84,123) wurde mit 15,02 € angezeigt; mit eingefrorener Basis sind es +211 €, und der TP lag 2,3 $ ÜBER dem echten Niveau. Die HL-G/V-Schätzung war vorher zwangsläufig falsch, weil sie auf demselben zirkulären Wert rechnete. ⚠ Vorbehalt bleibt: HL ist ein Oracle-basierter HIP-3-Perp — dass er 81,3 zeigt, heißt nicht, dass Pepperstone dort öffnet. Richtung belastbar, Höhe nicht. Deshalb weiter .

  • Snapshot: hl_live {mid_mt5, hl_mid, basis, basis_stale, age_s, bid, ask, bid_sz, ask_sz, imb5, trend, slope_per_h, vs_mt5} — IMMER befüllt, nicht nur am Wochenende. mid_mt5 = HL-Mid + gemessene Basis (HL xyz:CL notiert wegen anderem Kontrakt ~0,45 $ unter dem CFD; die Basis kommt serverseitig aus dem HL-Projekt als Median über 60 Snapshots) → direkt mit dem Broker-Kurs vergleichbar. Dazu market_closed, tick_age_s, pnl_hl.
  • Dashboard (v=129): eigene Header-Box HL neben KURS; Tooltip nennt HL-Rohkurs, Basis, Alter und Abweichung. Bei geschlossenem Markt heißt das Kurs-Label „KURS · ZU", die Session-Zeile zeigt „🔒 MARKT ZU", und das G/V wechselt auf die HL-Schätzung (mit markiert, Tooltip nennt den eingefrorenen Broker-Wert).
  • MT5 (Indikator v1.34): HL;<mt5_aequiv>;<alter_s>;<bid>;<ask>;<U|D|-> → bernsteinfarbenes Label unter Kurs und G/V (InpShowHL/InpHLColor).

DREI Sackgassen bei der „Markt zu"-Erkennung — dokumentiert, damit sie niemand wiederholt:

  1. tick_local_ts ist die lokale ABHOLZEIT, nicht das Tick-Alter — sie erneuert sich bei jedem Abruf, auch wenn der Broker denselben alten Tick liefert.
  2. tick_server_ts trader._broker_offset_s() ist ZIRKULÄR: die Offset-Funktion leitet den Zeitzonen-Versatz selbst aus dem letzten Tick ab (off = tick.time now). Bei geschlossenem Markt misst sie die Tick-Veraltung statt der Zeitzone (real 7,41 h statt +3 h) — das Alter hebt sich rechnerisch auf (Ergebnis: 347 s).
  3. pnl / Kursbewegung als EUR-je-Punkt-Faktor ergab 155 statt ~86 EUR je 1,00 und Lot, weil trader.pnl = profit + swap den über das Wochenende aufgelaufenen Swap enthält. Verwendet wird stattdessen: Markt zu = der Kurs hat sich seit _TICK_STALE_S (180 s) nicht BEWEGT (_last_bid/_last_bid_move_ts) — kein Zeitzonen-, Kalender- oder Feiertagswissen nötig, deckt Wochenende, Feiertag und Broker-Ausfall gleich ab. Und der G/V nutzt die vorhandene, korrekte Umrechnung trader.live_pnl(bid, ask) (tick_size/tick_value inkl. Swap und Währung), gefüttert mit dem HL-Kurs ± halbem Spread. ⚠ Bleibt eine Schätzung (anderer Kontrakt, Montags-Open-Spread unbekannt) — deshalb das in der Anzeige.

⚠⚠ _broker_offset_s() lieferte bei geschlossenem Markt 9,5 h statt +3 h (Fix 2026-08-01)

Sackgasse (2) oben war nur halb richtig — die Zirkularität war nicht das eigentliche Problem, sondern ein echter Bug in trader._broker_offset_s(), der weit über die Anzeige hinausreichte. Gefunden bei der Frage „warum steht die Laufzeit auf 0:00?".

Mechanismus: off = round((tick.time now)/1800)*1800 misst die Zeitzone nur, solange der Tick FRISCH ist. Steht der Markt, friert tick.time ein und die Differenz misst die Veraltung. Real gemessen am 01.08. um 11:25 (letzter Tick Fr 23:54:59):

tick.time  now = 34210 s  →  gerundet 34200 (9,5 h)   statt  +10800 (+3 h)

Die alte Plausibilitätsprüfung [12 h … +14 h] ließ das durch — eine Wochenend-Veraltung fällt genau in dieses Fenster. Ein noch längerer Stillstand (Mo früh, ~49 h) wäre rausgefallen und hätte 0 geliefert, also auch falsch.

Was still betroffen war (die Laufzeit-Uhr war nur das Sichtbare): open_time = p.time off landete 12,5 h in der ZUKUNFT → Laufzeit 0:00 · Time-Stop-Alter in trailing.py:504 wurde negativ → der 120-min-Time-Stop hätte nicht ausgelöst · deal.time-Umrechnung beim externen Close · alle MQL5-Chart-Anker (Kanal, Liquiditäts-Trendlinie, Trade-Marker).

Fix: Der Offset wird nur noch von einem frischen Tick (neu) bestimmt, sonst gilt der zuletzt gültige Wert (self._boff, vorbelegt mit _BROKER_OFF_DEFAULT_S = UTC+3 — dieselbe Konstante nutzt engine.get_bars schon als broker_utc_offset). Die Frische wird am bereits bekannten Offset gemessen (stale = now + _boff tick.time), nicht am Tick selbst — damit nicht zirkulär. Schwelle _BROKER_TICK_FRESH_S = 900 liegt unter dem halben Rundungsraster (1800/2), sodass die 30-min-Rundung immer auf den echten Offset fällt. Ein echter Zeitzonen-Wechsel (Broker-DST) wird beim ersten frischen Tick übernommen und geloggt (statt still zu passieren).

Einordnung: Deployment-Drift (Fall 4 in der Tabelle unten) — entworfen unter „Tick ist frisch", betrieben unter „Tick kann beliebig alt sein". Kein Backtest hätte das gefunden; Backtests rechnen nie mit Broker-Wallclock. Bestätigt die dortige Erwartung, dass die Liste nicht vollständig war.

Startwert der Stillstands-Uhr (gleicher Fix): Weil der Offset jetzt verlässlich ist, wird _last_bid_move_ts beim ersten Snapshot aus dem ECHTEN Tick-Alter vorbelegt (_now + _broker_offset_s() tick_server_ts, Plausibilität 0…14 Tage) statt bei null zu beginnen. Vorher brauchte der Bot nach jedem restart_server.bat volle 180 s, um ein längst geschlossenes Wochenende zu bemerken — real lief der User genau in dieses Blindfenster („KURS" statt „KURS · ZU", G/V auf dem eingefrorenen Broker-Wert). Im laufenden Betrieb bleibt die Erkennung bewusst bewegungs-basiert: das fängt auch einen eingefrorenen Feed, dessen Zeitstempel weiterlaufen. Verifiziert nach dem Neustart: market_closed=True sofort, tick_age_s 56 844 s (= Rohwert-Gegenrechnung auf 0 s genau), open_time → 31.07. 22:54, Laufzeit +15,8 h.

⚠ Stiller catch im REST-Poll verschluckte JEDEN Render-Fehler (Fix 2026-08-01)

pollSnapshot() hatte try { fetch → _applySnapshot(d) } catch {} — der catch sollte Netz-Aussetzer schlucken, umschloss aber auch render(). Ein Render-Fehler wurde damit lautlos verschluckt: der Poll feuerte weiter alle 4 s, jeder Durchlauf starb still, das Dashboard fror auf dem letzten guten Stand ein und die Konsole blieb leer. Genau diese Kombination (eingefrorene Anzeige ohne jede Fehlermeldung) hat die Fehlersuche unnötig lang gemacht. Jetzt ist nur noch der fetch im try; Netzfehler bleiben still, Render-Fehler werden laut. Ergänzt die Null-Sicher-Regel von oben: die verhindert den Absturz, das hier macht ihn sichtbar, falls er doch passiert.

Beschriftung (User 2026-08-01): Die Header-Labels heißen ausgeschrieben „PEPPERSTONE KURS" und „HYPERLIQUID KURS" (vorher „KURS"/„HL" — ließ offen, welcher Kurs woher kommt). Bei geschlossenem Markt: „PEPPERSTONE KURS · ZU".

Web-API (server.py)

GET /api/snapshot (inkl. verdict-Aggregat) /api/health /api/logs /api/stats /api/news /api/bars?tf=&n= (Charts, read-only) /api/squeeze_monitor (B4-Monitor, read-only, Engine-Cache 60 s) · WS /ws · POST /api/order|auto|trail|emergency|takeprofit|sltp|srclose|srclose_min|autosqueeze|autosignal|auth/check. /api/sltp {sl,tp} = manuelles SL/TP der offenen Position → engine.set_sltptrader.modify_sltp (TRADE_ACTION_SLTP, prüft Seite/Mindestabstand) und deaktiviert das Trailing (trail.deactivate — sonst zöge es SL/TP beim nächsten Tick zurück, User-Vorgabe). ⚠ Extern-Erkennung (Fix 2026-07-17): Setzt der User SL/TP direkt im MT5-Terminal (nicht über die App), greift deactivate() nicht — das Trailing überschrieb die Handeingabe (real: TP 90,000 → 81,484). trailing._do_modify vergleicht jetzt den Broker-SL/TP mit dem zuletzt selbst gesetzten (_last_sl/_last_tp, tol = Modify-Schwellwert): weicht er ab → Trailing schaltet sich ab, Handeingabe bleibt stehen (Log „Manuelle SL/TP-Änderung erkannt"). _last_sl/_last_tp werden bei Aktivierung genullt (kein Altwert-Fehlalarm); Erkennung erst ab dem 2. Modify. Token via [web] api_token; bei require_token=false ist die Token-Pflicht aus (Zugang nur via WireGuard).

  • Snapshot-Cache: /api/snapshot UND /ws liefern engine.snapshot_cached() (TTL _SNAP_TTL_S=0,8 s) — EIN berechneter Snapshot wird über alle Geräte geteilt, SQLite/Verdict-Last bleibt unabhängig von der Client-Anzahl.
  • WS-Wakeup nach Sperrbildschirm/Hintergrund (Fix 2026-07-22, User-Meldung „G/V großer Lag"): Backend-Pipeline ist schnell (_pos_loop 1 s · Snapshot-Cache 0,8 s · WS-Push 1 s; gemessen: MT5-Lock-Last selbst im Worst-Case nur ~25 ms, 0 Lock-Timeouts/_pos_loop-Fehler im Log) — die Ursache lag im Frontend: mobile Browser pausieren WS+Timer beim Sperren/Backgrounding, ohne visibilitychange-Handling zeigte die Seite bis zu einem zufälligen Reconnect den STALEN Stand (Minuten alt). Fix: visibilitychange/pageshow/focus-Listener (_wakeRefresh in app.js) holen beim Zurückkommen sofort einen frischen REST-Snapshot UND bauen die WS-Verbindung hart neu auf (statt auf den evtl. hängenden alten Socket zu warten). REST-Polling als Sicherheitsnetz (2026-07-22, User-Wunsch „JSON-Feed"): zusätzlich zum WS-Push pollt app.js alle POLL_MS=4000 ms /api/snapshot — fängt den Fall auf, dass der WS still hängt (kein onclose, keine Zustellung mehr, z. B. schwaches WireGuard-Signal) OHNE auf einen Reconnect zu warten; pausiert bei document.hidden (Akku/Daten). lastTs-Dedup (_applySnapshot, vergleicht engine.snapshot_cached().ts — dieselbe Quelle für WS UND REST): nur die jeweils NEUERE Quelle rendert → kein Flackern/Doppel-Render, wenn beide fast gleichzeitig eintreffen. Alle Snapshot-Konsumenten (initialer Load, Wake-Refresh, Poll, WS) laufen jetzt über _applySnapshot statt direkt render. v=108. GET /api/snapshot ist ohnehin öffentlich/token-frei nutzbar — externe Tools (Shortcuts, Home Assistant, …) können ihn direkt pollen.

⚠ Deployment-Drift — die strukturelle Schwachstelle (2026-07-31)

Track B schützt gegen OVERFITTING (ein Edge existiert gar nicht). Es schützt nicht gegen DEPLOYMENT-DRIFT: ein real existierender Edge wird unter anderen Bedingungen BETRIEBEN als VALIDIERT. An EINEM Tag wurden drei Fälle gefunden:

# Komponente Gemessen unter Betrieben unter Schaden
1 P(break) Anlauf zu fixiertem Level dynamisch gewähltes Level AUC 0,715 → 0,368
2 breakout_k=0,3 feste Zeitebene TF wechselt 33×/Tag, löscht den Anker 43 % → 93 % WARTEN
3 Trailing mult 1,5 1,53,0 je nach TF H1 ΣR 249 → 1367, Worst 1,50 → 2,50
4 _broker_offset_s() frischem Tick Tick beliebig alt (Wochenende) Offset 9,5 h statt +3 hopen_time 12,5 h in der Zukunft, Time-Stop-Alter negativ
7 Auto-Squeeze-Einstiegspreis Fill AM Ausbruchs-Level (backtest_breakout_squeeze.py) MARKET-Order, live median +0,275×ATR über dem Level (34 echte Entries) ØR +0,456 → 0,158 (H1) bzw. +0,611 → 0,006 (H2); WR 56 → 37 %
6 auto_squeeze_skip_night Nacht-Kostenfalle am Wellensignal (alle Nacht-Bars: 0,333×ATR) Squeeze-Ausbrüche, die nur bei anziehendem ATR feuern (0,183×ATR = Tag-Niveau) Gate kostet ΣR 42 (H1) / 243 (H2); geblockte Ausbrüche sind in beiden Hälften positiv
5 backtest_auto_signal.py ohne Winkel (_build ohne angle= → Default 90 → ad=0) Winkel steuert einen Signalzweig (Reversal) UND ±15 Konfidenzpunkte Urteil vom 30.07. beschrieb weder das alte noch das neue System; der Winkelterm allein hebt H1 bei conf 75 von 0,123 auf 0,047
8 Buchführung der Bot-Trades (nicht die Strategie, ihre Beobachtbarkeit) Einstieg läuft über engine._open → schreibt setup + sl_at_entry + 16 ctx_* Pending-Fill füllt im Broker, trader._refresh_locked adoptiert und loggt nur 5 Felder 4 reale Squeeze-Trades am 06.08. als setup=NULL = von manuellen ununterscheidbar; B4-Monitor, squeeze_b5 (Abbruchregel!) und squeeze_entry_gap zählten null
9 Pending-Order liegt am Level (offen) backtest_auto_signal_v3.py: Order liegt durchgehend (ØR +0,35/+0,32) entry_roombreakout_pending flackern → 4× setzen/stornieren in 76 s bei identischem Level Ausbruch in einer Storno-Lücke = Entry ganz verloren; Ausmaß ungemessen

Keiner wäre durch MEHR Backtesting gefunden worden — die Backtests waren korrekt. Ursachen (strukturell): (a) Backtests sind NACHBAUTEN, keine Nutzer des Live-Codes (sim_run/_ema_series/_atr_series liegen in ≥6 Skripten als Kopie); (b) es gibt Live-only-Gates, die kein Backtest modelliert (Entry-Raum, EIA, Dead-Hours, News-Blackout, Cooldowns, Startup-Grace); (c) Live-Parameter sind kontextabhängig (Trail-mult je TF, SL-ATR fix M15, TF selbst per Heuristik), Backtest-Parameter konstant; (d) es gab keine Telemetrie über die Entscheidung selbst.

Behebung in 3 Stufen (User-Entscheidung 2026-07-31):

  • Stufe 1 — ERLEDIGT: _MULT_BY_TF auf durchgehend 1,5 und _MULT_BREAKOUT_ADD auf 0,0 (core/trailing.py) — der einzige gemessene Wert. ⚠ BEGRÜNDUNG NACHTRÄGLICH KORRIGIERT (beim Stufe-3-Refactor): die zuerst genannte „67 % größerer Einzelverlust" war ein Artefaktbacktest_trailing.py setzt den Initial-SL auf mult×ATR statt fix 2,0, dort war der Worst-Case also per Konstruktion gleich dem Multiplikator. Sauber nachgemessen mit fixem SL (backtest_trailmult.py, core/exit_model.py, 80k Bars, 2 Halbjahre):
    Trail H1 ΣR H2 ΣR Worst
    1,0 73 +1346 2,00
    1,5 (live) 325 +1294 2,00
    2,0 446 +1195 2,00
    2,5 1174 +1223 2,00
    3,0 1088 +1397 2,00
    Der Worst-Case ist bei JEDEM Multiplikator identisch 2,00 (der SL deckelt).
    Die Entscheidung bleibt richtig: 1,5 schlägt 2,0 UND 2,5 in beiden Hälften.
    **➜ TRAIL AUF 1,0 GESETZT (User-Vorgabe „setze alle Vorschläge um", 2026-07-31),
    in core/exit_model.py (LIVE.mult), live verifiziert (AKTIVIERT … mult=1.0x).**
    Voller Sweep (backtest_trailmult.py, fixer SL 2,0, 8 Werte 0,53,0): **enger ist
    MONOTON besser** — Wave-Signal H1/H2 ΣR: 0,5 +1258/+2418 · 0,8 +852/+1668 ·
    1,0 +80/+1360 · 1,5 220/+1305 · 2,0 394/+1198 · 2,5 1072/+1327.
    Optimum am RAND des Suchbereichs = Warnsignal („misst man besser oder nur
    weniger?"). Gegentest auf SQUEEZE-Entries mit Echtkosten (--squeeze) — das
    Setup lebt von LÄUFERN, müsste also dagegenhalten: tut es nicht, auch dort ist
    enger monoton besser (1,0: H1 0,202/H2 +0,010 · 1,5: 0,219/0,032). Deshalb der
    Schritt auf 1,0 — besser als 1,5 in beiden Hälften auf beiden Signalmengen —
    aber nicht auf 0,5 (Randwert). ⚠ Zweiter Grund gegen 0,5: die Sim modelliert
    keine Exit-Slippage; ein engerer Trail löst viel häufiger aus und ist stärker
    betroffen (real gemessen bis 0,75×ATR über den Stop hinaus).
  • Stufe 2 — ERLEDIGT: Entscheidungs-Telemetrie. (a) Jede Empfehlung loggt jetzt einen maschinenlesbaren block_reason (recommendations.block_reason, DB migriert mit Backup): deadband · dead_hour · eia · htf_counter · stretch · min_conf · breakout_pending · entry_room · no_data · stale; NULL = kein Block. Ohne den war nicht feststellbar, WELCHES Gate die 93 % erzeugt. (b) analyze_ divergence.py hält vier Dinge gegen die Backtest-Erwartung: A Signal-Verteilung (Soll ~43 % WARTEN), B Gate-Anteile inkl. Kennzeichnung „im Backtest modelliert?", C TF-Wechsel-Rate (Soll: Median ≥15 min), D0 Modell-Kalibrierung (Ø-Vorhersage vs. echte Rate) und D Merkmals-Drift gegen _PB_MU/_SD. ⚠ Lehre aus dem Bau selbst: die Merkmals-Drift (D) hätte den realen Fehler NICHT gefangen — mom3 lag nur 0,50σ unter µ, bei Gewicht 1,43 wurden daraus aber 0,71 im Logit (23 % statt 41 %). Deshalb ist D0 der schärfere Test und die D-Schwelle auf 0,5σ gesetzt. Erster Lauf nach dem Fix: D0 zeigt 27,0 % vorhergesagt vs. 28,0 % real (vorher 23 vs 41) — die Reparatur ist damit live bestätigt.
  • Stufe 3 — ERLEDIGT (core/exit_model.py): geteilter Exit-Kern. Enthält LIVE: ExitParams (sl 2,0 · trail 1,5 · start 0,3 · be 1,3 · lock 3,5/0,6/1,2 · timestop 24 · tp 3,5 · max_hold 200) als einzige Quellecore/trailing.py importiert sie jetzt von dort (_TRAIL_START_ATR, _BREAKEVEN_ATR, _PHASE4_*, _MULT_BY_TF sind abgeleitet, keine eigenen Zahlen mehr). Ändert jemand LIVE.mult, ändern sich Live-Verhalten UND Messung gemeinsam → Fall 3 ist konstruktiv unmöglich geworden. Dazu die kanonische simulate() für Backtests, mit Flags timestop/use_tp, um Teilmodelle EXPLIZIT zu machen statt zu verstecken. ⚠⚠ BEFUNDE BEIM REFACTOR — schlimmer als angenommen: (1) Es gibt mindestens DREI materiell verschiedene Exit-Modelle in den Backtests: (A) Phasen-Modell ≈ live (_trailing/_exit/_atrfloor/_candle_fade) · (B) EINFACH_TPTRAIL=0.5, kein Breakeven, kein Lock, kein Time-Stop, _MAXH=240 (_hourly, _hourly_split, _bounce, _events, _deadhour, _chopgate) · (C) Phasen mit be_on=1.0 statt 1,3 (_breakout, _confluence_angle). ⇒ Mehrere dokumentierte Befunde (Dead-Hours, EIA-Blackout, Bounce, Chop-Gate, Stunden-Analyse, breakout_k) ruhen auf einem Exit, der dem Live-System NICHT entspricht. Bewusst NICHT stillschweigend umgestellt — das würde historische Schlüsse rückwirkend ändern; markiert als LEGACY_SIMPLE / LEGACY_BE10 in exit_model.py, Neubewertung = eigene Entscheidung. (2) Selbst die „Phasen"-Skripte weichen voneinander ab: _trailing hat TP aber keinen Time-Stop, _candle_fade Time-Stop aber kein TP, _atrfloor liefert Punkte statt R. Live hat BEIDES → nur timestop=True, use_tp=True ist live-treu. (3) backtest_trailing.py koppelt den Initial-SL an mult (sl = entry d*mult*atr) — dadurch war die Stufe-1-Begründung teilweise falsch (s. o.). Äquivalenz verifiziert (500 synthetische Zufalls-Kursreihen je Fall): _candle_fade (timestop=True, use_tp=False) und _atrfloor (timestop=False, use_tp=True, ×ATR) sind bitgenau identisch zur neuen simulate(). Migrations-Regel (in exit_model.py festgehalten): ein Backtest darf nur auf simulate() umgestellt werden, wenn er danach DIESELBEN Zahlen liefert — sonst ist es keine Refaktorierung, sondern eine unbemerkte Änderung der Messgrundlage. LEGACY-RECHECK (backtest_legacy_recheck.py, 2026-07-31): die beiden Befunde nachgerechnet, die live ECHTE Gates steuern. · EIA-Blackout BESTÄTIGT — auch mit dem echten Exit + Echtkosten ist das Fenster Mi 15:3016:30 in BEIDEN Hälften schlechter als der Rest (ØR-Differenz 0,097 / 0,119). Das aktive Gate ist gedeckt. · DEAD-HOURS reproduzieren NICHT als selektiver Befund — mit dem echten Exit sind 19 von 22 Stunden in BEIDEN Hälften negativ, es gibt keine robust positive Stunde. Die alte Analyse hatte 07 sowie 12 und 16 als die schlechten Stunden herausgehoben; wenn fast alles negativ ist, ist diese Auswahl nicht mehr gestützt. (Praktisch folgenlos, weil dead_hours ohnehin leer ist — aber eine Reaktivierung auf der alten Begründung wäre nicht gedeckt.) · ⚠ BOUNCE/Reversal — relative Aussage hält, absolute NICHT. Reversal schlägt Trend in BEIDEN Hälften (H1 0,034 vs 0,174 · H2 +0,029 vs 0,073, also +0,140/+0,102) — robust. Aber die dokumentierte Kennzahl „ØR +0,185 · PF 1,35 · 70 % Treffer" REPRODUZIERT NICHT: mit echtem Exit + Echtkosten liegt das Setup bei ~Breakeven (PF 0,93/1,06). Die alten Werte stammen aus dem vereinfachten Exit OHNE Kosten. Das Setup bleibt sinnvoll als nachrangiges, markiertes Zweitsignal (so ist es live auch eingebaut), aber es ist kein +0,185-Ertragsbringer. · ER-CHOP-GATE — Ablehnung BESTÄTIGT. Alle vier ER-Bänder liegen gleichauf (H1 0,137…−0,172 · H2 0,049…−0,067); die Efficiency Ratio trennt auch mit dem echten Exit nicht. Gate bleibt zu Recht verworfen. ⚠ Einschränkung des Rechecks (selbst benannt): er handelt die ungegatete EMA-Richtung — ohne Mindest-Konfidenz, Breakout-Bestätigung, HTF-Filter und Raum-Gate. Für den VERGLEICH (Fenster gegen Rest, Stunde gegen Stunde) ist das neutral, die absoluten Werte sind aber NICHT das Live-Signal. ⚠ Konsequenz für die Erfolgskontrolle des TF-Churn-Fixes: „WARTEN-Anteil Richtung 43 % bewegen" ist als Zielgröße mit Vorsicht zu lesen — die 43 % stammen aus backtest_dist.py (Signal-HÄUFIGKEIT), sie sind kein Ertragsversprechen. Mehr Signale sind nur dann besser, wenn die freigegebenen Setups auch tragen.

Erwartung: die drei Fälle sind nicht vollständig. Sie wurden bei gezielter Suche in ~20 min gefunden; die Trefferquote spricht für weitere. analyze_divergence.py regelmäßig laufen lassen (Kandidat für den Wochenreport).

⚠⚠ News-Sentiment war GESÄTTIGT — Fix 2026-08-03

Anlass: User-Frage „können wir den X-Account anbinden, um den Copiloten mit mehr Daten zu versorgen?" → erst prüfen, ob der vorhandene Textstrom überhaupt trägt.

Befund: er trug nichts, weil der Score kaputt war. calc_news_sentiment bildete ein reines Verhältnis (bullbear)/totalohne jeden Bezug zur Beweismenge. Eine einzige bullische Schlagzeile bei null bearischen ergab damit +1,00.

Diagnose Wert
Log-Einträge auf ±1,00 gesättigt 173 von 173 = 100 %
davon mit einer Seite exakt 0 100 %
Median-Beweismenge (bull+bear) 2,8 gewichtete Headlines
DB gesamt (54.548 Zeilen) auf exakt +1,00 63,9 %
Real am 03.08.: Sentiment +1,00 („bull=1.8, bear=0.0"), während WTI 5,9 %
stand. Der Wert trug faktisch ein Bit (das Vorzeichen) — und löste im Entry-Check
dauerhaft falsche „News-Konflikt"-Warnungen aus (Schwelle s
⚠ Die Keyword-Listen sind NICHT die Ursache (56 bullisch / 48 bärisch, keine
Überschneidung) — es war allein die Formel.

Fix: Shrinkage score = raw × total/(total+_EVIDENCE_K), _EVIDENCE_K=4,0 (gewählt, weil die reale Median-Beweismenge 2,8 beträgt → eine typische einseitige 3-Headline-Lage bleibt UNTER der 0,5-Warnschwelle; ~10 Headlines ergeben 0,71). Zusätzlich liefert der Rückgabewert jetzt raw_score und evidence, damit im UI/Copilot unterscheidbar ist, ob ein schwacher Score „ausgewogene Lage" oder „kaum Daten" bedeutet. Der Leer-Fall gibt dieselben Felder zurück (sonst KeyError genau im „keine News"-Fall). Mit 6 Szenarien getestet, live verifiziert: bei identischer Beweislage (bull=1.8, bear=0.0) +1,00 → +0,31.

⚠⚠ EHRLICH: Das repariert die KALIBRIERUNG, nicht die Aussagekraft. Gemessen wurde parallel die Prädiktivität (Forward-Return 120 min, 38.213 Paare über 84 Tage, 2 Hälften, gegen die Regime-Drift gelesen — H1 +0,005 , H2 0,079 ):

Bucket H1 Überschuss H2 Überschuss
bearisch ≤−0,5 0,029 ✓ +0,113 ✗ (Vorzeichen kippt)
leicht bear +0,118 ✗ +0,031 ✗
leicht bull +0,106 ✓ +0,044 ✓
bullisch ≥+0,5 (grösster Bucket) 0,003 0,023 ✗
Der stark-bullische Bucket (16.241 / 11.818 Zeilen) liegt in BEIDEN Hälften bei null
bis negativ, der bearische kippt das Vorzeichen. **News-Sentiment ist nicht robust
prädiktiv** — auch mit korrigierter Skala. Es bleibt Kontext-Chip ohne Verdict-Stimme.
Methodik-Falle beim Messen selbst: der erste Lauf nutzte candles_m1 (deckt nur
~14 Tage), wodurch bisect für alle älteren Zeitstempel denselben Randwert lieferte →
alle Forward-Returns exakt 0,0000. Erst der Lauf mit MT5-M5-Bars (JanAug) und einer
Deckungsprüfung ( Δt ≤ 15 min) war gültig.

Konsequenz für die X-Anbindung: einen ZWEITEN Textstrom in eine Pipeline zu legen, deren erster gemessen nichts vorhersagt, ist nicht begründbar — zumal X seit 2023 keinen kostenlosen Lesezugang mehr hat (Basic ≈ 200 $/Monat, Pro ≈ 5.000 $/Monat; Scraping = ToS-Verstoß). Bei ~800 € Konto wären das ~25 % des Kontos pro Monat für einen unvalidierten Input. Erst müsste der reparierte Score über ein paar Wochen zeigen, dass Textdaten hier überhaupt etwas beitragen.

KI-Copilot: warum er neutral ist, und was 2026-08-02 repariert wurde

User-Frage: „Der Bias ist fast immer neutral — können wir konkretere Empfehlungen bekommen? Weitere Quellen (Kerzen, Muster)?"

Diagnose — es ist NICHT der Prompt, es ist der Provider. Der Bruch ist exakt datierbar (recommendations.ai_sentiment je Tag): bis 19.07. war der Copilot zu 4290 % gerichtet, ab 21.07. nur noch zu 1,59,3 %. Dazwischen liegt der Wechsel auf DeepSeek (2026-07-20). Der Prompt war die ganze Zeit unverändert. ⚠ Und er hedged aus korrekter Schlussfolgerung, nicht aus Schwäche: die Begründungen sind konkret und zutreffend, und er bekommt zu ~90 % ein WARTEN-Signal, einen negativen Track-Record UND die ausdrückliche Prompt-Anweisung, genau dann NEUTRAL zu empfehlen. Er tut, was dort steht.

Sind seine gerichteten Rufe etwas wert? Unentschieden — die Stichprobe trägt nicht. Forward-Return über verdict_votes × candles_m1: KI-LONG n=286 → +0,29 $ in Richtung / 76 % Treffer (60 min), KI-SHORT n=93 → 0,02 $ / 55 %. ⚠ Das ist KEIN Beleg: die 286 Stimmen sind 20 Episoden an 5 Tagen (Minutentakt-Logger), die 93 sind 16 Episoden — effektiv n≈20 gegen n≈16, alles in einem Regime. Dasselbe Cluster-Muster, an dem ORB gescheitert ist. Deshalb bewusst NICHT am Prompt gedreht, um mehr Meinung zu erzwingen — das würde eine korrekte Schlussfolgerung überschreiben und einer unbelegten Stimme mehr Gewicht geben. Verdict-Gewicht bleibt 1,0 bei gerichteter Aussage, 0 bei NEUTRAL.

Zwei echte Prompt-FEHLER behoben (Korrektheit, keine Meinungsmache):

  1. _SYS beschrieb das Wellen-Signal als „ATR-ZigZag" — es ist seit Langem EMA12 vs. EMA50; ZigZag wurde verworfen (~0 Edge). Das Modell missverstand also die Natur des Hauptsignals.
  2. _SYS nannte „TradersUnion-Tachos" als Input — TU ist seit 2026-07-19 gar nicht mehr im Copilot-Kontext (_tool_market). Das Modell wurde auf Daten hingewiesen, die es nie sieht.

Die zwei kalibrierten Größen ergänzt. Befund beim Nachsehen: _tool_market gab dem Copiloten nur Wellen-Signal + Session — er war das schlechtest informierte Modul im System und bekam ausgerechnet die einzigen beiden Komponenten nicht, deren Kalibrierung out-of-sample nachgewiesen ist. Neu im Kontext: p_break_target / p_break_stop (AUC 0,65 bzw. 0,680,72) und cone (80 %-Bänder, mit der REAL gemessenen Abdeckung 77 %, nicht dem Nennwert). Dazu eine Lese-Anleitung im Prompt — ohne sie liest ein LLM „P(break) 30 %" als schwaches Signal statt als klare Aussage „Level hält mit 70 %"; 4555 % ist ausdrücklich als Münzwurf markiert. ⚠ Zwei Fallen beim Bau, beide an Live-Daten korrigiert: (a) p_break steht im Snapshot bereits in Prozent (3 = 3 %, deckungsgleich mit der Chart-Zeile „97 % Abprall") — die zuerst gebaute ×100-Umrechnung hätte 300 erzeugt; (b) die Engine-Felder _last_sr_close_hint/_last_stop_approach/_last_cone und die engine-Referenz im Agent existierten gar nicht — der Fail-safe hätte still nichts geliefert. Beides verdrahtet, der Snapshot rechnet die Werte ohnehin und weist sie jetzt nur zu (kein zweites Rechnen, anders als beim Konsens-Pfeil). Mit Stub-Test verifiziert (Werte unverändert durchgereicht, nur 80 %-Bänder, ohne Engine kein Absturz).

Kerzen/Muster bewusst NICHT eingespeist. Ein LLM wendet darauf die Lehrbuch-Lesart an — und die ist hier gemessen invertiert: „langer Körper im Trend = Fortsetzung" → 0,132/0,042 (Klimax, läuft dagegen); „2× Volumen = echt" → 0,117/0,204 (monoton falsch herum); Muster-Kursziel trifft nur zu 1338 %. Roh eingespeist würden sie den Copiloten aktiv verschlechtern. Offener Kandidat: sie MIT der gemessenen Lesart mitgeben („langer Docht oben; gemessen läuft der Kurs danach eher GEGEN die Kerze") — 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,700,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 2a-II — die GEGATETE Messung, nachgeholt (backtest_breakout_gated.py): Voller Live-Stack über die ECHTEN Methoden — _build (Totband · M30-Filter · Anti-Überdehnung · Winkel · min_conf 55 · Reversal AUS), Breakout-Bestätigung 1:1 aus _confirm_breakout, _room_gate mit echten kausalen M5-Pivots.

k H1 ØR / ΣR H2 ØR / ΣR
0,0 (Gate aus) 0,173 / 193 +0,003 / +7
0,2 0,185 / 149 0,012 / 16
0,3 (LIVE) 0,160 / 122 0,036 / 47
0,5 0,127 / 85 0,041 / 47
0,8 0,101 / 54 0,012 / 11
1,0 0,096 / 45 0,016 / 12
Bestätigt: k=0,5/0,8/1,0 schlagen 0,3 in BEIDEN Hälften, die Nachbarn halten
mit (0,8 und 1,0 nebeneinander) — die vorab fixierte Regel ist erfüllt. Auch die
ØR verbessert sich in H1 monoton (0,185 → 0,096), es ist also nicht nur
Handelsvermeidung.
⚠ **TROTZDEM keine Umstellung empfohlen — der Grund ist kein Messfehler, sondern
eine Zweckfrage:** (a) alle Werte bleiben negativ (PF 0,701,01); das Gate
begrenzt Schaden, es erzeugt keinen Edge. (b) Der Auto-Signal-Pfad ist ohnehin
AUS — das Signal ist ein Vorschlag für den Menschen, und dessen gemessener
Vorteil (+118 €/WR 56 % bei MIT-Signal-Trades) entstand auf der k=0,3-Population.
(c) k=0,8 kürzt die Empfehlungen um ~39 % (H1 n=766 → 531) und treibt den
WARTEN-Anteil weiter hoch — der steht mit 93 % live ohnehin schon im Fokus, und
breakout_pending ist bereits die Ursache von 63 % aller Blocks.
Vor einer Änderung ist zu klären, WOFÜR das Signal da ist: als mechanischer
Auto-Entry wäre k=0,8 die belegte Wahl; als Auswahl-Angebot für den Menschen ist
weniger Auswahl nicht automatisch besser. Die Zahlen liegen vor, die Entscheidung
ist eine Nutzungs-, keine Messfrage.
Zwei benannte Abweichungen der Simulation (beide unvermeidbar):
(1) _confirm_breakout misst seinen Timeout mit time.time() gegen
_breakout_timeout_s=3600 s — in einem Bar-Loop vergeht keine Wall-Clock-Zeit,
der Timeout würde NIE feuern. 3600 s sind auf M5 exakt 12 Bars, so wird hier
gezählt. **Merke: jede time.time()-Messung im Live-Code ist in einer
Bar-Simulation stumm.** (2) hour=None, weil die EIA-Prüfung in _build über
datetime.now(_BERLIN) läuft — mit Bar-Stunde würde ein Lauf am Mittwoch
15:3016:30 alle Bars blocken. dead_hours ist live leer, es fehlt also nur
das EIA-Fenster.

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.

Feste Einsatz-Margin + lokale Sprachausgabe (2026-08-04)

Einsatz-Feld (User-Wunsch „Feld, mit dem ich meine eingesetzte Margin selber eingeben kann"): [trading]-unabhängiger Laufzeitwert MANUAL_MARGIN in core/config.py (set/get_manual_margin, Muster wie set_margin_buffer). 0 = automatisch (bisheriges Verhalten: margin_buffer_pct % der freien Margin), > 0 = die Position wird auf genau diesen Margin-Einsatz gerechnet. · calc_lots setzt es um und deckelt auf die freie Margin × Buffer — ein zu hoher Wunschwert wird begrenzt und geloggt, nicht an den Broker durchgereicht (sonst Reject mit kryptischem retcode statt sauberer Meldung). Reicht der Betrag nicht fürs Mindestlot → 0.0 + Warnung, der Trade wird sauber abgelehnt. · Vorrang vor dem Risiko-Modus (trader._send_locked): eine ausdrückliche Größenvorgabe schlägt die Rechenregel. Bei risk_pct=0 (live) ohnehin der Fall. · ⚠ Wirkt auf ALLE neuen Positionen — auch die autonomen (Squeeze/Signal). · UI: Feld „Einsatz" in der Order-Leiste (#margin-input, v=136), bernstein umrandet solange gesetzt. ⚠ Bewusst NICHT in der Trade-Leiste — die ist nur bei OFFENER Position sichtbar, der Einsatz muss aber VOR dem Öffnen einstellbar sein. Enter blurrt nur, change sendet einmal (gleicher Fix wie beim Mindestgewinn). · POST /api/manualmargin {value} · Snapshot manual_margin · neustart-fest über runtime_state.json. Ende-zu-Ende getestet (250 → Snapshot → 0 → persistiert).

Einsatz in PROZENT (2026-08-05, v=137): das Gegenstück zum festen Betrag — margin_buffer_pct (live 95) hatte bisher keine UI und war nur in der ini änderbar. Neu: Feld „Einsatz %" (#marginpct-input) direkt neben „Einsatz €" in der Order-Leiste · POST /api/marginpct {value}engine.set_margin_pctconfig.set_margin_buffer · Snapshot margin_pct · neustart-fest. ⚠ VORRANG ist sichtbar gemacht: steht links ein fester €-Betrag, wird das Prozent-Feld ausgegraut (.mg-field.muted, gestrichelter Rand) und der Tooltip sagt, dass der Prozentsatz dann nur noch die Obergrenze ist — zwei scheinbar gleichrangige Felder ohne Hinweis wären die schlechtere Lösung. ⚠ Geklemmt auf 199 % (set_margin_buffer); 100 % ließe keinen Puffer für Spread/Swap. Ende-zu-Ende getestet: 50 → 50 · 150 → auf 99 geklemmt · 0 und 5 abgelehnt · 95 → 95, in runtime_state.json persistiert.

Sprachausgabe: Piper, lokal (tools/speak.py, tools/piper/). Anlass: die Windows-Sprachausgabe funktioniert zwar, aber auf diesem Rechner ist keine deutsche Stimme installiert — weder SAPI5 noch OneCore (nur David/Zira/Mark, alle en-US). Deutscher Text käme mit englischer Aussprache. Add-WindowsCapability scheitert ohne Adminrechte (elevated Fenster nötig). Piper gewählt, weil es lokal läuft (passt zum Rest: WireGuard-only, Secrets in der ini, keine Cloud-Abhängigkeit), nichts kostet (nach der 200-$/Monat-Absage bei X relevant) und aus PowerShell/Python aufrufbar ist. Stimme thorsten-medium (de_DE), Real-Time-Faktor 0,08 — 4,3 s Audio in 0,36 s. ⚠ Das ZIP entpackt eine Ebene tiefer als erwartet (tools/piper/piper/piper.exe) — speak.py sucht die Binärdatei und das Modell deshalb, statt Pfade fest zu verdrahten. Der erste Entwurf scheiterte genau daran; der Fail-safe hat die Pfade sauber gemeldet statt stumm zu bleiben. Aufruf: python tools/speak.py "Text" · Pipe · --wav datei (nur erzeugen). Rückgabe 1 bei fehlendem Piper → ein Aufrufer im Bot läuft weiter, statt abzustürzen. ⚠ tools/piper/ ist gitignored (Binaries + 60-MB-Modell gehören nicht ins Repo); speak.py selbst ist versioniert.

Modul „Manuelle Trades" + voller Entscheidungskontext (Stufe 1, 2026-08-04)

User-Frage: „Kann der Bot aus meinen manuellen Trades lernen — evtl. ein neues Modul?" Auslöser: am 04.08. machten 18 manuelle Trades +728 €, der Bot 16,53 €, bei 93 % WARTEN.

Die exploratorische Vorprüfung ergab: noch NICHT lernbar — und zwar aus drei Gründen, die alle benannt gehören:

  1. Die entscheidenden Merkmale wurden nie gespeichert. Beim Einstieg schrieb der Bot 5 Felder; davon war ai_sentiment in 1,4 % und news_score in 34 % der Trades befüllt. Die Marktlage eines manuellen Einstiegs war damit nicht rekonstruierbar.
  2. Das naive Lernziel ist untauglich. „Wann steigt er ein" bräuchte Gegenbeispiele (Bars ohne Einstieg) — Millionen unbeschriftete Negative, und „nicht eingestiegen" heißt nicht „hätte man nicht sollen". Die brauchbare Frage lautet: welche seiner Einstiege liefen gut (saubere Labels über P&L).
  3. Die scheinbaren Muster sind Einzeltage. Stunde 13:00 zeigt +478 € über n=69 — davon stammen +382 € (80 %) aus den fünf Trades des 04.08. Genau die Falle, an der ORB und die Dead-Hours gescheitert sind.

Gebaut wurde deshalb Stufe 1 — Telemetrie, kein Verhalten: trades um 16 ctx_*-Spalten erweitert (DB-Backup …bak-2026-08-04, 1.148 Zeilen unverändert): p_break_target/-stop · dist_res_atr/dist_sup_atr · structure · squeeze · htf_trend/h1_trend · spread_atr · session · block_reason · conf_pct · atr_m5 · bias · cone_pos · hour. Alle Werte werden im Snapshot ohnehin berechnet und nur eingesammelt (engine._run_analysistrader.set_open_context(ctx=…)history.log_trade_open). log_trade_open hängt die Spalten dynamisch an und ignoriert unbekannte Schlüssel — eine ältere DB ohne Migration läuft weiter (fail-open). ⚠ Fallstrick beim Bau: wave_snap existiert in _run_analysis NICHT (nur in snapshot()). Der erste Entwurf hätte einen NameError erzeugt, den das umgebende except still geschluckt hätte — der Kontext wäre dauerhaft leer geblieben, ohne dass es auffällt. Jetzt self.wave.snapshot(); vd zusätzlich per locals()-Guard abgesichert (es entsteht in einem eigenen try). Verifiziert auf einer DB-Kopie: alle 16 Spalten korrekt geschrieben, unbekannter Schlüssel ignoriert, Basisfelder unberührt.

Dashboard-Karte „Manuelle Trades" (#card-manual, v=134): heute / 30 Tage / gesamt / Bot-Vergleich, dazu der Split mit vs. ohne Signal-Deckung. Backend history.manual_stats(30)engine._manual_stats_cached() (~120 s Cache, wie _alignment_cached — die Aggregation läuft über die ganze Tabelle und hat im 1-s-Snapshot nichts zu suchen). Bewusst neutral gefärbt (keine Ampel) und ausdrücklich deskriptiv: die Karte zeigt eine Reifegrad-Zeile mit der ctx-Abdeckung (heute 0 %), damit niemand sie für aussagekräftiger hält, als sie ist.

Stufe 2 (später, NICHT gebaut): wenn genug ctx-Daten vorliegen (~23 Monate), messen was Gewinner von Verlierern unterscheidet — Fit auf H1, validiert auf H2, AUC und Kalibrierung berichtet, wie beim P(break)-Modell. Ergebnis wäre ein Hinweis im Order-Dialog („deine Trades in dieser Konstellation: X % WR, n=Y"), analog zum bestehenden Ausrichtungs-Split. ⚠ Ausdrücklich KEIN Auto-Entry daraus — 22 Signal-Eingriffe sind gescheitert, und die gemessene Stärke des Users liegt in der AUSWAHL, nicht in einer Regel. Ein Modul, das ihm seine eigene Historie zur aktuellen Lage zeigt, spielt auf diese Stärke; eines das für ihn einsteigt, ersetzt sie.

⚠⚠ P(break) v2 LIVE GEPRÜFT (2026-08-07) — KALIBRIERUNG DANEBEN, TRENNSCHÄRFE UNENTSCHIEDEN

Fällige B4-Messung nach dem Nachtrainieren vom 31.07. (analyze_pbreak_live.py, 878 ausgewertete Vorhersagen ab 01.08.). Zwei Befunde, die auseinandergehalten gehören — und einer davon betrifft die Messung selbst.

⚠⚠ ZUERST: DAS SKRIPT MISCHTE ZWEI MODELLE. Es trennte bei der M30-Level-Umstellung (30.07.), nicht beim Nachtrainieren (31.07.); „GESAMT" las damit altes und neues Modell zusammen. Genau der Fehler, der am 02.08. in analyze_divergence.py schon behoben wurde (_EPOCHS) — hier stand er noch. Neu: MODELL_V2 mit demselben Stichtag wie die Fälligkeitsbedingung (01.08.), plus eigener Abschnitt „NACHTRAINIERTES MODELL". Die alten Abschnitte bleiben.

(1) KALIBRIERUNG — messbar daneben, und die Ursache ist benennbar.

P(break) n vorhergesagt real
019 56 15,1 % 53,6 %
2034 199 29,8 % 43,2 %
3549 414 42,2 % 56,8 %
5064 180 54,1 % 61,7 %
65100 29 69,0 % 17,2 % ⚠ invertiert
Die Basisrate ist gewandert — Training 37,8 %, live 53,2 %, und das
stabil über vier volle Tage (03.08. 51,4 % · 04.08. 54,1 % · 05.08. 56,4 % ·
06.08. 53,8 %, je n=122276). Das ist kein Rauschen. Ein Modell, dessen Niveau an
einer 37,8-%-Basisrate verankert ist, muss in einem 53-%-Regime systematisch zu
niedrig sagen — genau das steht in der Tabelle. Dieselbe Signatur wie beim alten
Modell (alle unteren Klassen „zu niedrig", oberste invertiert).

(2) TRENNSCHÄRFE — NICHT entscheidbar, und das ist der wichtigere Befund.

AUC roh (n=878) 0,541
AUC entkoppelt (n=87) 0,556
95-%-Bootstrap-KI [0,434 … 0,674]
Das Intervall enthält den Zufall (0,50) UND die Messlatte (0,654). Es ist
also nicht belegt, dass das Modell schlechter rankt als beim Nachtrainieren —
es ist nur nichts belegt. „AUC 0,54" als Durchfall zu lesen wäre falsch.

⚠⚠ DIE FÄLLIGKEITSBEDINGUNG ZÄHLTE DIE FALSCHE GRÖSSE — behoben. Sie stand auf 800 ROHEN Vorhersagen; die sind nicht unabhängig (dieselbe Position pendelt vielfach um dasselbe Level). Aus 878 rohen werden 87 entkoppelte — die Bedingung hat die Beweismenge um rund das Zehnfache überschätzt und die Messung als „fällig" gemeldet, obwohl sie nichts entscheiden konnte. Die Abhängigkeit war im Skript seit jeher dokumentiert („entkoppelte Sicht"), nur die Fälligkeit zählte daran vorbei. Jetzt: 350 ENTKOPPELTE (halbiert die KI-Breite 0,12 → 0,06, erst dann sind 0,50 und 0,65 trennbar; bei ~17/Tag ≈ 3 Wochen). Stand: 87/350. ⚠ Verwandt mit der Bootstrap-Grenze bei backtest_cost_gate.ci95 („überlappende Beobachtungen → effektives n ist ~n/24") — dieselbe Klasse Fehler, diesmal in einer Fälligkeitsbedingung statt in einem Konfidenzintervall.

Operativ kein Notfall: auto_sr_close = false seit 06.08. — das Modell schliesst keine Trades. Es speist nur den Hinweis in der App, die Chart-Linien, den Copilot-Kontext und stop_approach. Die Fehlkalibrierung kostet damit aktuell kein Geld, sie macht nur die Anzeige zu pessimistisch (sie sagt „Level hält", wo es in 53 % der Fälle bricht). ⚠ Vor einem erneuten Nachtrainieren: der letzte Versuch hat live auch nicht gehalten. Der Befund zeigt auf das Niveau (Basisrate/Achsenabschnitt), nicht zwingend auf die Rangfolge — ein drittes Fitten derselben Bauart würde denselben Weg gehen. Naheliegender wäre, das Niveau laufend an die jüngste Basisrate zu koppeln oder ein Vola-/Regime-Merkmal aufzunehmen. Nicht gebaut, nicht gemessen.

⚠⚠ P(break): NIVEAU-REPARATUR GEMESSEN — BEIDE VARIANTEN FALLEN DURCH (2026-08-07)

Folgeauftrag zur B4-Prüfung: die Vermutung war „das Modell RANKT brauchbar, nur sein NIVEAU hängt an einer Basisrate, die wandert" — also entweder ein Regime-Merkmal oder ein laufend nachgeführter Achsenabschnitt. backtest_pbreak_regime.py, Regel vorab fixiert (beide Fit-Richtungen besser kalibriert · AUC nicht schlechter, Toleranz 0,005 · Nachbarn kippen nicht). ⚠ collect() in backtest_pbreak_retrain.py hat dafür einen additiven REG-Parameter bekommen (Default None) — Ausgabe ohne ihn bitgenau wie vorher geprüft (4.371 Zeilen, 0 Abweichungen).

(1) Auf BACKTEST-Daten besteht die Roll-Achse, das Regime-Merkmal nicht:

Variante H1→H2 Kalib. / AUC H2→H1 Kalib. / AUC
A) Basis (fester Achsenabschnitt) 2,61 Pp / 0,659 10,51 Pp / 0,599
B) + Regime (ATR/Median) 2,51 / 0,658 11,20 / 0,599
C) Roll-Achse (n=400) 1,95 / 0,661 3,25 / 0,612
Kontrolle 1: Modell VOR dem 31.07. 25,81 / 0,364 20,25 / 0,406
Kontrolle 2: Modell wie HEUTE live 1,63 / 0,659 2,30 / 0,612
Kontrolle 2 ist IN-SAMPLE (die Live-Gewichte wurden final auf allen 80k
Bars gefittet) — sie muss gut aussehen und ist kein Verdienst.
Kontrolle 1 reproduziert exakt, warum am 31.07. nachtrainiert wurde
(AUC unter 0,5 = invertiert).

(2) AUF LIVE-DATEN FÄLLT DIE ROLL-ACHSE DURCH — und das ist die Messung, die zählt (n=921 ab 01.08., echte Bruchrate 52,7 %):

AUC Ø-Vorhersage Kalib.-Fehler
IST 0,531 41,8 % 14,16 Pp
Roll n=400 0,516 48,7 % 10,64 Pp
Roll n=800 0,510 42,8 % 15,14 Pp schlechter als IST
Sie senkt den Fehler auf ~10,6 Pp — repariert ihn also nicht —, verliert AUC
über der Toleranz und kippt beim Nachbarn n=800. Regel verfehlt, **kein
Einbau**.
⚠ **Der Grund steht in der Klassentabelle: die oberste Klasse bleibt
INVERTIERT** (69 % vorhergesagt, 17,2 % real, n=29). Eine Niveau-Verschiebung
kann eine invertierte Klasse prinzipiell nicht reparieren. Die Ausgangshypothese
(„nur das Niveau") ist damit widerlegt.

(3) DREI ERKLÄRUNGEN FÜR DIE BASISRATEN-LÜCKE GEPRÜFT — ALLE WIDERLEGT. Live 52,7 % liegt ausserhalb der gesamten 9-Monats-Spanne des Backtests (Zehntel-Blöcke: 33,6 … 41,1 %).

Hypothese Ergebnis
Sammelart — live loggt 1× je frischer Berührung, der Backtest JEDEN Bar in der Zone 37,5 % gegen 39,1 % = 1,7 Pp, falsche Richtung
Vola-Regime — Bruchrate steigt mit ATR über 5 Quintile flach: 37,0 · 37,0 · 39,2 · 38,9 · 37,4 %
aktuelles Regime ungewöhnlich letzte 7 Tage Median 0,96 = normal (Gesamt 0,99)
Der flache Vola-Befund erklärt zugleich, warum B nichts bringt: in dieser
Achse steckt keine Information.

FAZIT: nichts gebaut. Der nächste Schritt ist kein drittes Fitten — der Fehler sitzt nicht im Niveau. Die einzige nicht geprüfte Divergenz ist die Level-Auswahl selbst: der Backtest bildet sie mit pick_level/_cluster nach, live entscheidet _draw_levels. Ein Nachbau ist kein Nutzer desselben Codes — genau die Fehlerklasse, die am 31.07. schon einmal zugeschlagen hat („Training = Level bei Entry fixiert, Live = Level wandert mit"). Dort wäre als Nächstes zu messen. ⚠ Operativ weiterhin kein Notfall: auto_sr_close = false seit 06.08., das Modell schliesst keine Trades. ⚠ Und die Live-AUC bleibt unentschieden (entkoppelt n=87, KI [0,434 … 0,674]) — „die Rangfolge ist kaputt" ist nicht belegt; belegt ist nur die Basisraten-/Kalibrierungslücke.

KEGEL MIT ADAPTIVE CONFORMAL INFERENCE (2026-08-08, v=158)

Der Kegel war seit dem 31.07. dokumentiert 35 Pp ZU ENG — 80 % nominal gegen real 76,7 / 76,6 / 75,3 % — und blieb es, weil Nachfitten das Problem nur verschiebt: die nächste Periode hat wieder eine andere Vola. ACI ist die Standardmethode dafür (Web-Recherche 08.08.): sie justiert das Niveau online nach jedem aufgelösten Fenster und konvergiert nachweislich gegen die Zielabdeckung, auch unter Verteilungsdrift.

EHRLICH ZUR BAUART: die Lehrbuch-Form justiert α und schlägt das zugehörige Quantil nach. Hier liegt nur eine feste Quantil-Tabelle vor, kein Verteilungsobjekt — umgesetzt ist deshalb die multiplikative Variante:

s ← s · exp(γ · (1{verfehlt}  α_ziel))      γ = 0,02 · Deckel [0,7 … 2,0]

Dieselbe Regelungsidee, aber die Konvergenz-Garantie der Originalform ist damit NICHT wörtlich übertragen — es ist ACI im Geiste.

Verifiziert: bei exakt 20 % Verfehlern bleibt die Skala bei 1,0000 — das ist der Fixpunkt, und er stimmt. Simulation aus der dokumentierten Lage (76,6 % Abdeckung): nach ~50 Checks um 80 %. ⚠ Die Skala pendelt um das Ziel statt exakt zu treffen. Das ist das bekannte ACI-Verhalten: garantiert ist die Langfrist-Abdeckung, nicht der Einzelwert.

⚠⚠ NICHT-ÜBERLAPPEND geloggt (cone_checks): ein 120-min-Fenster alle 5 min wäre 24-fach überlappend — genau die Cluster-Verzerrung, an der analyze_hl_funding.py (06.08.) aufgelaufen ist und die die Fälligkeitsbedingung pbreak_accuracy_v2 um das Zehnfache danebenliegen liess (07.08.). Je Horizont startet ein neuer Check erst, wenn der vorige abgelaufen ist → 48/24/12 Checks je Tag. Neustart-fest über runtime_state.json — ohne Persistenz liefe die erlernte Kalibrierung nach jedem Neustart wieder bei 1,0 los. ⚠ build(price, atr) OHNE skalen liefert bitgenau die alten Werte (additiv geprüft).

⚠⚠ ZWEI EIGENE PLATZIERUNGSFEHLER beim Bau, beide gefangen: der Lade-Block landete zuerst hinter den except-Klauseln von _load_runtime_state, wo st gar nicht mehr existiert; und beim M15-Setup landete ein Block mitten in einem mehrzeiligen Aufruf. Der erste wäre stillschweigend nie gelaufen (except Exception hätte ihn geschluckt) — gefunden nur, weil ich die Funktion danach gelesen habe statt dem grünen py_compile zu trauen.

DIE M15-KARTE MISST SICH JETZT SELBST + AUSFÜHRUNGSQUALITÄT SICHTBAR (2026-08-08, v=157)

Nach Web-Recherche umgesetzt (User-Wahl aus drei Vorschlägen). ⚠ Der dritte (Kegel mit Adaptive Conformal Inference nachkalibrieren) bleibt OFFEN — der Kegel ist mit 76,7 / 76,6 / 75,3 % gegen 80 % Nennwert dokumentiert zu eng, und ACI ist die Standardmethode dafür (adjustiert das Niveau online, garantiert Konvergenz auch unter Drift). Nicht gebaut.

② SELBSTMESSUNG (m15_states). Die Karte hatte keine Rückkopplung — geprüft, es gab kein Logging. Das war die auffälligste Lücke: jeder Ausfall, der in diesem Projekt je aufgedeckt wurde, kam aus einer Rückkopplung (pbreak_predictions entlarvte den Modellausfall LIVE, was kein Backtest konnte; der B4-Monitor die Squeeze-Drift). Die Karte hätte monatelang falsch stehen können. Neu: eine Zeile je Zustandswechsel (Status + Lesart + Level), nach 30/60 min gegen candles_m1 ausgewertet, Ergebnis als Zeile ⑥ in der Karte. ⚠ Gewertet wird die LESART, nicht der Status — und nur die gerichtete (4555 % zählt nicht mit). Gegen 50 % zu lesen. ⚠ Nur bei Wechsel, nie je Tick: sonst entsteht dieselbe Cluster-Verzerrung, die am 07.08. elf widerlegte Hypothesen ausgelöst hat (roh 922 Zeilen 52,6 %, entkoppelt 93 Zeilen 45,2 %) — und die mein erster Tagestest reproduzierte (46 fortlaufende Bars als 46 Trades). Live verifiziert: 1 Zeile trotz laufendem Sekundentakt.

③ AUSFÜHRUNGSQUALITÄT (Zeile ⑦). analyze_squeeze_entry_gap.py existiert seit dem 05.08. und misst die TCA-Kennzahl (Arrival-Price-Slippage) — niemand hat je hineingesehen. Dabei war das der einzige Erfolgsmaßstab des Stop-Order-Umbaus (Abstand Einstieg↔Level muss gegen 0, vorher Median +0,275×ATR) und zugleich der grösste je gemessene Hebel (+0,32 R gegenüber Market). Jetzt steht der Zählstand in der Karte: 3 von 12 Bot-Trades seit dem Umbau.

⚠⚠ BEIM BAU EIN NAMEERROR IM 1-SEKUNDEN-PFAD — vom LINTER gefangen. Der erste Entwurf berechnete _m15 in _run_analysis und benutzte es in snapshot(); dort gibt es weder market noch wave_snap. py_compile sieht das NICHT, ruff F821 meldete 4 Befunde, darunter _m15 selbst. Genau die Fehlerklasse, für die der Linter am 07.08. eingebaut wurde (die beiden log.-NameError in core/trader.py sassen Monate in except-Zweigen) — und der erste echte Fang. ⚠ Zweiter Fehler beim Verschieben: der Block landete mitten in einem mehrzeiligen Aufruf (_cone = _cone_build() → SyntaxError. Beide behoben.

⚠⚠ DIE ZWEI GERATENEN M15-SCHWELLEN GEMESSEN — beide NICHT kalibrierbar (2026-08-08)

Auftrag: die Reichweite (0,5×ATR) und die Kosten-Schwelle (0,32, von M5) messen statt raten. backtest_m15_gates.py, 2 Halbjahre, Level-Berührungen auf M15, Einstieg AM Level in HTF-Richtung, Fill bei Berührung, kanonischer Exit, Echtkosten, Episoden-entkoppelt, mit 95-%-Bootstrap je Zelle.

⚠⚠ ZUERST EIN ARTEFAKT IM EIGENEN CODE — und es hätte zu einem absurden Rat geführt. Der erste Lauf stieg sofort zum Level-Preis ein, auch wenn der Kurs 0,9×ATR entfernt stand — die Sim schenkte sich damit genau diesen Abstand. Ergebnis: „je WEITER weg vom Level, desto besser" (oberster Bucket +0,293 in H2, KI schloss die Null aus). Mit Fill bei Berührung verschwindet es:

Abstand vorher H2 mit Fill
0,300,50 +0,142 +0,216
0,751,00 +0,293 +0,179
Derselbe Fehler wie beim Momentum-Test (ØR +2,2 entlarvte den Code).

② ERGEBNIS: KEINE der beiden Schwellen ist kalibrierbar.

Abstand ×ATR H1 ØR H2 ØR
0,000,10 0,227 +0,100
0,100,20 0,130 0,115
0,200,30 0,306 0,129
0,300,50 0,112 +0,216
0,500,75 0,039 +0,042
0,751,00 0,040 +0,179
H1 ist über ALLE sechs Abstands-Buckets negativ. Kein Bucket besteht die
vorab fixierte Regel (beidhälftig positiv + KI ohne Null). Beim Kosten-Gate
dasselbe: nur H2 0,060,10 schliesst die Null aus (+0,254/+0,307), H1 liegt dort
bei +0,016/0,040.

Der eigentliche Befund ist nicht „welche Schwelle", sondern: es gibt nichts zu optimieren. Die Population selbst trägt in H1 nicht — Level-Berührungen auf M15 in HTF-Richtung sind dort durchgehend negativ. Eine Schwelle kann keinen Edge erzeugen, den die Grundgesamtheit nicht hat. ⚠ Konsequenz für die Karte: die 0,5×ATR bleiben eine gesetzte Zahl (ehrlich so benannt), und das Kosten-Gate bleibt auf M15 dekorativ — beides ist jetzt gemessen, nicht mehr nur vermutet. Einzige schwach gestützte Richtung: teuer ist schlechter (H2 0,160,20 → 0,482, KI [0,949 … +0,008]).

⚠⚠ M15-KARTE AN 7 TAGEN GETESTET — zwei eigene Fehler, einer davon fast veröffentlicht (2026-08-08)

(1) BIAS WAR EIN RICHTUNGSGEBER — korrigiert zum VETO (v=155). Der Tagestest an 07.08. (test_m15_karte_tag.py) zeigte: 46 von 92 Bars grün, entkoppelt 13 Episoden — ALLE LONG, an einem Tag, der von 78,399 auf 77,383 fiel. Trefferquote 0 %, ØR 0,753. ⚠ Ursache strukturell, nicht Pech: gemessen ist „gegen den M30-Trend halbiert den Edge" — eine VERBOTS-Aussage. Ich hatte daraus „M30+H1 einig → Bias LONG" gemacht, also einen Richtungsgeber. Exakt der Fehler, wegen dem die Gesamtempfehlung entfernt wurde (Richtung dort 4851 % Treffer). Behoben: bias.richtung ist dauerhaft None, das Feld liefert nur noch die verbotene Richtung; das Banner nennt keine Richtung mehr.

(2) FAST EINEN FEHLALARM VERÖFFENTLICHT. Der Wochentest (test_m15_karte_woche.py, 30.07.07.08., 93 Episoden) meldete das Veto als falsch herum: MIT dem HTF ØR 0,273 / PF 0,56, GEGEN den HTF +0,258 / PF 1,83 — Differenz 0,532 R gegen die erlaubte Richtung. Über 2 Halbjahre auf DERSELBEN Population (Level-Berührungen, M15) hält das Veto aber:

MIT dem HTF GEGEN den HTF Differenz
H1 (n=1504) 0,072 / PF 0,87 0,116 / PF 0,80 +0,044 R
H2 (n=1617) +0,028 / PF 1,06 0,012 / PF 0,98 +0,040 R
Beidhälftig konsistent, gleiche Größenordnung. Die Woche war Rauschen
n=93 gegen n=3121. Dieselbe Falle wie bei der 15-Minuten-Regel (31.07.) und dem
Nacht-Guard (05.08.): ein Zeitraum, der zum Beleg erhoben wird.
⚠ Der Verdacht, der mich zur Gegenprüfung brachte, war trotzdem berechtigt: der
HTF-Filter ist auf Signal-Entries validiert (backtest_htf_filter.py), nicht
auf Level-Berührungen. Auf der Level-Population trägt er +0,04 R statt des
dokumentierten „Edge ×2" — richtig, aber klein.

(3) WAS DER TEST SONST ZEIGT — die Karte ist zu permissiv:

grüne Bars je Tag 2256 von 92 (2461 %)
Episoden je Tag 14,7
Kosten-Gate blockte 0 von 644 Bars
Das Kosten-Gate ist auf M15 dekorativ: Spread/ATR liegt dort bei ~0,10, die
Schwelle bei 0,32. Und die Level-Einstiege selbst tragen kaum (ØR 0,072 / +0,028
über 2 Halbjahre) — das Veto macht sie nur weniger schlecht.
⚠ Die einzige diskriminierende Schwelle (Reichweite 0,5×ATR) ist **gesetzt, nicht
gemessen**.

⚠⚠ Und ein Fehler im TEST selbst: der erste Lauf zählte 46 fortlaufende grüne Bars als 46 Trades und meldete 7 % Trefferquote — eine Bewegung 46-mal gezählt. Erst die Entkopplung auf zusammenhängende Blöcke macht die Zahl lesbar. Dieselbe Klasse wie die Clusterung bei pbreak_predictions (07.08.).

M15-KARTE „SETUP-BEREITSCHAFT" GEBAUT (2026-08-08, v=153) — Ersatz der Gesamtempfehlung

Nach dem Entfernen der Gesamtempfehlung und drei negativen M15-Messungen (Squeeze-Einstieg, VWAP-Bänder, Exit-ATR) steht fest: auf M15 trägt keine Richtungsquelle. Gebaut ist deshalb bewusst keine Empfehlung, sondern eine sequenzielle Gate-Kette nach dem Schema des Users:

[① HTF-BIAS M30/H1] → [② KOSTEN Spread/ATR_M15] → [③ LEVEL & KEGEL] → Order AM LEVEL

Der Unterschied zur alten Karte ist grundsätzlich: dort wurden Stimmen zu einem Bias verrechnet (gemessen 4851 % Treffer über 1.071 Episoden), hier muss jedes Gate einzeln passieren. Ein gefallenes Gate lässt sich nicht durch ein anderes ausgleichen.

Nur BELEGTE Bausteine: ① HTF-Trend (einziger robuster Filter, Edge ×2) · ② Kosten (M15 gemessen 0,104 gegen M5 0,193 — hier gewinnt M15 wirklich) · ③ nächstes STEHENDES Level aus _draw_levels + P(break) · ④ Kegel (out-of-sample kalibriert, 80 %-Band trifft 77 %) · ⑤ Veto (gegen das Signal 6,60 €/Lot, beidhälftig). Status-Banner: 🟢 SETUP BEREIT @ Level · 🟡 WARTEN AUF LEVEL · 🔴 KEIN TRADE. ⚠ Das Banner bestätigt ausdrücklich auch das Nicht-Handeln — die gemessene Kern-Leckage sind diskretionäre Abweichungen, nicht verpasste Gelegenheiten. ⚠⚠ Die Order-Buttons werden BEWUSST NICHT gesperrt (gegen den Vorschlag „ausgrauen"): hartes Blockieren wurde im Projekt verworfen, und User-Übersteuerungen liefen gemessen 68 % WR. Sichtbar machen, nicht bevormunden.

Umsetzung: engine._m15_setup() setzt nur zusammen, was der Snapshot ohnehin erzeugt; NEU gerechnet wird allein das M15-Kosten-Verhältnis und P(break) für das nächste Level. ⚠ Dafür veröffentlicht wave_rec jetzt tf_atr (ADDITIV) — der ATR je Zeitebene wurde im Turn-Fetch längst berechnet, war aber nirgends abrufbar; ihn neu zu holen wäre ein zweiter MT5-Fetch für vorhandene Zahlen (derselbe Fehler wie beim Konsens-Pfeil). ⚠ P(break)-Merkmale bleiben auf M5 — das Modell ist darauf trainiert (Fix 2026-07-13); auf M15 umzustellen würde es falsch skalieren. Mit 5 Szenarien getestet (alles passt · Bias uneinig · Kosten teuer · Level zu weit · Kaltstart ohne ATR_M15 → kein Absturz), live verifiziert über deploy.py --feld m15_setup.

⚠⚠ EXIT-ABSTAND AUS M15-ATR = FÄLLT DURCH (2026-08-08)

Zweiter Teil der M15-Idee: M5-Einstieg behalten, nur den EXIT gröber führen. Gemessen (backtest_exit_atr_tf.py, 80k M5, feste geteilte Entry-Liste, kanonischer Exit, Echtkosten). Schliesst zugleich den seit dem 31.07. in core/trailing.py offenen Punkt („bisher misst er nur M5").

H1 ØR / PF / Worst H2 ØR / PF / Worst
M5-ATR (heute) +0,219 / 1,41 / 2,17 +0,117 / 1,25 / 2,23
M15-ATR (1,86× breiter) +0,096 / 1,14 / 4,26 +0,019 / 1,02 / 6,73
Schlechter in BEIDEN Hälften — und das Tail-Risiko verdreifacht sich.
Dasselbe Muster wie bei backtest_sl_basis.py: ein breiterer Exit kauft nichts
und kostet Tail. Der Exit bleibt am M5-ATR.

⚠⚠ METHODEN-FALLE, die die Messung fast entwertet hätte — zweimal: (1) simulate() normiert R mit DEMSELBEN ATR, den es für SL/Trail/TP nutzt. Mit dem 1,86× grösseren M15-ATR wäre dieselbe Dollar-Bewegung ein kleineres R — die M15-Variante hätte mechanisch schlechter ausgesehen. Beide werden deshalb in M5-Risikoeinheiten zurückgerechnet (R × ATR_verwendet / ATR_M5). (2) Der erste Lauf sammelte die Entry-Liste über exit_fn — das wird von scan() bei JEDEM Box-Ausbruch gerufen, nicht nur bei den komprimierten: 519 statt 132 Einstiege, also die Kontrollgruppe statt des Squeeze. Behoben über den additiven Rückruf on_entry(j, d, lvl, atr, komprimiert) in core/squeeze_scan.py (Bitgleichheit weiter durch tests/test_squeeze_scan.py belegt). ⚠ Ein Haken, der mehr liefert als gedacht, ist eine stille Populations-Änderung.

⚠⚠ SQUEEZE AUF M15 = FÄLLT DURCH — kein M15-Modul gebaut (2026-08-08)

Anlass: User „baue mir das M15-Empfehlungsmodul". Der Squeeze ist der einzige beidhälftig validierte Einstieg; gemessen auf M5 und M1, nie auf M15. Die Live-Parameter ungeprüft zu übernehmen wäre Deployment-Drift, also erst gemessen (backtest_squeeze_m15.py, 26.000 M15-Bars, kanonischer Exit, Echtkosten, Touch-Fill, 2 Hälften).

⚠⚠ DIE KONTROLLE HAT ZUERST VERSAGT — und das war der wichtigste Teil. Meine M5-Kontrolle lieferte 0,133/+0,044 statt der dokumentierten +0,124/+0,292. Ursache diagnostiziert: allein der ATR-Floor.

Floor Floor/Median M5 H1 / H2
mein Ansatz (0,542×Median) 0,060 0,54× 0,108 / +0,036
dokumentiert 0,120 1,08× +0,224 / +0,087
max_hold war irrelevant (bitgleich). Ich hatte auf M15 also einen **halb so
strengen** Filter angelegt wie auf M5 — der „relative Floor" aus
backtest_squeeze_multi.py diente dort der Vergleichbarkeit zwischen
INSTRUMENTEN, nicht zwischen Zeitebenen. **Ein Filter, dessen Strenge man frei
wählt, erzeugt den Befund** (die Lehre aus analyze_tageszeit.py).

Deshalb über die ganze Floor-Spanne gemessen statt einen Wert zu wählen:

Floor/Median H1 ØR / PF / n H2 ØR / PF / n
0,50 0,253 / 0,59 / 143 0,024 / 0,96 / 193
0,75 0,290 / 0,56 / 108 +0,047 / 1,08 / 184
1,00 0,030 / 0,96 / 39 +0,086 / 1,15 / 164
1,08 (M5-äquivalent) +0,216 / 1,35 / 33 +0,080 / 1,14 / 170
1,25 0,309 / 0,47 / 16 +0,097 / 1,18 / 163
1,50 0,279 / 0,70 / 7 +0,151 / 1,28 / 148
**Die einzige beidhälftig positive Zelle hat n=33 in H1 — und BEIDE Nachbarn
kippen** (0,030 und 0,309). Vorab fixierte Regel (2) verfehlt: Rauschen, exakt
das ORB-Muster. Dazu: alle sechs Nachbar-Kombis Box 10/15/20 × k 0,1/0,2 sind
mit den Live-Parametern in mindestens einer Hälfte negativ, die Kontrolle
„beliebige Box" ist BESSER als der Squeeze (0,052/+0,054 gegen 0,121/0,022),
und der Slippage-Härtetest verschlechtert monoton.

Der strukturelle Grund ist in der n-Spalte sichtbar: bei einem Filter, der streng genug ist, um zu wirken, bleiben in H1 nur 3339 Ausbrüche. Der Squeeze feuert auf M5 23×/Woche; M15 ist für dieses Setup schlicht zu grob. Kein M15-Modul gebaut.

EIN echter Positiv-Befund bleibt: die Kosten. Median Spread/ATR auf M15 gemessen 0,104 — noch besser als die erwarteten 0,133 (M5 0,193 · M1 0,319). Die Kosten-Arithmetik spricht weiterhin für gröbere Zeitebenen; es fehlt nur ein Signal, das dort trägt.

⚠⚠ ECHTES CME-VOLUMEN (CL=F) TRÄGT NICHT MEHR ALS DIE CFD-TICK-ZÄHLUNG (2026-08-08)

Anlass: User-Idee eines Telegram-Alerts auf WTI-Volumen-Spikes (CL=F via yfinance). Die berechtigte Frage dahinter: das Projekt kennt bisher nur tick_volume vom CFD — eine Zählung von Preisänderungen bei einem Market-Maker-Broker, kein gehandeltes Volumen. Echtes NYMEX-Kontraktvolumen wäre die erste Datenquelle, die nicht aus dem Preis abgeleitet ist.

Gemessen (backtest_cme_volumen.py): beide Quellen auf DEMSELBEN Fenster (28.05.07.08.26, 13.855 CME- gegen 14.178 CFD-Bars), gleiche Methode, Überschuss über die Drift, zwei Hälften. ⚠ Bewusst NICHT gegen den dokumentierten Wert 0,117/0,204 gestellt — der stammt aus einem anderen Zeitraum. Genau dieser Vergleich („kurzes Fenster gegen Langfrist-Mittel") war am 07.08. der Messfehler, der elf Hypothesen ausgelöst hat.

Volumen CME H1 / H2 CFD H1 / H2
1,52,0× +0,116 / 0,203 0,011 / +0,152
2,02,5× 0,221 / 0,282 0,101 / +0,589
≥2,5× +0,102 / +0,096 0,267 / 0,881
Trefferquote in ALLEN Zellen 4453 % — Münzwurf, in beiden Quellen.
Kein monotoner Zusammenhang, und die Vorzeichen kippen zwischen den Hälften.
Die einzige beidhälftig konsistente Zelle (CME ≥2,5×) ist mit
[0,234 … +0,428] bzw. [0,265 … +0,461] klar Rauschen.

⚠⚠ EHRLICHE GRENZE: meine CFD-Kontrolle REPRODUZIERT den alten Befund NICHT — und der Grund ist ein Populations-Unterschied, kein Widerspruch. backtest_candles.py maß „2× Volumen" bedingt auf ein Kerzen-MUSTER (langer Körper / Marubozu), hier läuft es unbedingt über alle Kerzen. Der alte Befund ist damit weder bestätigt noch widerlegt; er gilt weiter für seine Population. ⚠ 60 Tage = EIN Regime (yfinance deckelt 5-min-Bars). Ein hieße hier „nicht ausgeschlossen"; das ist belastbar.

Konsequenz: kein Volumen-Spike-SIGNAL. Die Datenquelle funktioniert (CL=F liefert echte Kontrakte, ~4 s je Abruf), sie trägt nur keine Richtungsinformation. Ein Alert ist allenfalls als Aufmerksamkeits-Hinweis vertretbar („hier passiert etwas"), ausdrücklich NICHT als Richtungsaussage — und dann mit derselben Ehrlichkeit beschriftet wie die Kerzen-Anatomie-Karte. ⚠ Telegram-KANÄLE als Eingabe (Bookmap/ATAS) = verworfen, ohne Messung: sie transportieren Screenshots. Ein Orderbuch-Zustand ist vorbei, sobald ihn jemand abfotografiert. Dasselbe Argument wie bei der X-API — und der vorhandene Textstrom (News-Sentiment) ist bereits gemessen nicht robust prädiktiv. ⚠ Nebenbefund zum eingereichten Skript: das .iloc[0]-Muster ist korrekt (yfinance liefert MultiIndex-Spalten), nicht wie zunächst von mir vermutet ein Fehler. Echte Mängel wären: kein Dedup (die „letzte geschlossene Kerze" bleibt 5 min stehen → 5 Alerts je Spike), kein Handelszeit-Gate, voller Tagesabruf im 60-s-Takt.

⚠⚠ VWAP-BÄNDER AUF M15 = VERWORFEN (2026-08-08) — die letzte ungetestete Zutat

Anlass: User-Wunsch nach einem M15-Daytrading-Modul auf Profi-Methodik (Order-Flow, Volume Profile, VWAP, Session-Struktur, Liquidity Sweeps, MSS, Mean-Reversion). Abgleich mit dem Gemessenen ergab: fast alles davon ist in diesem Projekt bereits durchgefallen — Volume Profile (POC-Reversion beidhälftig negativ), Liquidity Sweeps (alle 9 Kombis), Market Structure (24/24 Zellen), ORB (mit realer Fill-Annahme alle 4), RSI/MACD, Inter-Market. Footprint/CVD/DOM existieren hier nicht (Market-Maker-Broker ohne Orderbuch; das HL-Orderbuch ist gemessen 0,21,0 bp gegen ~3 bp Kostenhürde und läuft dem CFD hinterher). Kpler/Bloomberg scheitern am Preis, CoT und Cushing an der wöchentlichen Frequenz. VWAP war die EINZIGE nie gemessene Zutat (calc_vwap existiert in core/analysis/indicators.py, taucht in CLAUDE.md nirgends als Signal auf). Jetzt gemessen: backtest_vwap_m15.py, 26.000 M15-Bars (02.07.2507.08.26), session-verankert auf den Berliner Kalendertag, 2 Hälften, kanonischer Exit, Echtkosten, Fill bei Berührung.

⚠⚠ ZUERST DER EIGENE FEHLER — er ist die halbe Lehre. Der erste Lauf zeigte Reversion bei k=2,03,0 mit ØR +0,13 bis +0,27 und PF bis 1,71 in BEIDEN Hälften — verdächtig sauber. Ursache: das Band an Bar i wird mit Bar i berechnet (der VWAP kumuliert sie), und getestet wurde, ob genau diese Bar es berührt. Ein Ausschlag zieht das Band zu sich hin und erzeugt seine eigene Berührung. Mit Band aus i1 (live zwangsläufig der Fall) bleibt:

k vorher H1/H2 nach dem Fix
2,0 +0,130 / +0,130 +0,041 / 0,009
2,5 +0,189 / +0,235 +0,046 / +0,087
3,0 +0,162 / +0,272 +0,024 / +0,010
Der Look-ahead war 0,100,25 R wert — dieselbe Größenordnung wie beim
Squeeze (0,32), Momentum (0,45) und SIG (0,770,85).

ALLE DREI vorab fixierten Kriterien verfehlt: · (b) Haltequote gegen Zufallskontrolle — eine willkürliche Linie gleicher Distanz hält in 9 von 10 Zellen genauso gut oder besser (VWAP 70,375,1 %, Zufall 73,976,6 %). Exakt der Fibonacci-Befund reproduziert: die Frage ist nie „hält das Level?", sondern „hält es BESSER als eine beliebige Linie?". · (c) Ertrag — k=2,0 kippt in H2 (0,009), also kippt der Nachbar der besten Zelle. Und alle sechs 95-%-Bootstrap-Intervalle enthalten die Null (k=2,5 H2, die beste Zelle: +0,087 [0,012 … +0,185]). · (a) Beweglichkeit — ⚠ die Regel war von mir unterspezifiziert („≤ Squeeze-Box-Niveau" = 0 ist für ein gleitendes Band unerfüllbar). Gemessen rückt das Band im Median nur 0,030,05×ATR je Bar, im 90-Perzentil aber bis 0,34×ATR — mehr als die Einstiegstoleranz von 0,15. Eine ruhende Order müsste also laufend nachgezogen werden: dasselbe operative Problem wie bei _pend.

Kohärenz-Kontrolle: der Ausbruch an den Bändern ist spiegelbildlich negativ (H1 0,107…−0,230 über alle k). Reversion leicht positiv, Ausbruch klar negativ — ein stimmiges Bild, kein Zufallsrauschen. 24. verworfener Signal-Eingriff. Damit ist aus dem eingereichten Profi-Werkzeugkasten keine ungetestete Zutat mehr offen.

⚠⚠ VERLUSTBEGRENZUNG BEI „FALSCHEN" EINSTIEGEN — BEIDE IDEEN GEMESSEN, BEIDE DURCHGEFALLEN (2026-08-07)

User: „um meine Verluste bei falschen Einstiegen zu begrenzen würde ich gerne den Notfall-Close automatisch setzen — 30 %?" und „oder doch lieber ein Flip-Close bei entgegengerichtetem Trend?"

(1) AUTOMATISCHER NOTFALL-STOP — 30 % wäre ohnehin wirkungslos gewesen. Maßstab zuerst: Einsatz 670 €/Lot, Median-Verlust 20 €/Lot = 3 % der Margin. 30 % (=201 €/Lot) erreichen 1 % aller Verluste. Kontrafaktisch über 1.022 echte Trades (MAE aus M5-Bars zwischen Ein- und Ausstieg; 87 €/$/Lot als Median aus den Trades selbst geschätzt):

Schwelle gefeuert Σ P&L Δ gegen IST
ohne Stop (IST) +358 €
3 % 492 +65 € 293 €
5 % 317 280 € 639 €
8 % 165 +345 € 14 €
15 % 70 58 € 416 €
30 % 28 +27 € 331 €
Keine Schwelle schlägt „gar kein Stop". Nach Einstiegsart (Δ gegen IST):
mit Signal 162…−278 · gegen Signal 69…−148 · **ohne Signal 292 / +298 / +290
/ 106** (5/8/10/15 %). Die +298 sehen gut aus, aber beide Nachbarn kippen
Rauschen, dieselbe Regel, an der ORB und die SMC-Sweeps gescheitert sind.

(2) FLIP-CLOSE AUF DEN HTF-TREND — war nie gemessen, jetzt schon (backtest_flipclose_htf.py). Abzugrenzen vom Flip auf das Empfehlungs-Signal (gemessen, am 05.08. abgeschaltet): der HTF ist träger, dreht seltener, und als FILTER ist er belegt (Edge ×2). Als EXIT nicht:

Variante H1 ΣR (Δ) H2 ΣR (Δ)
BASIS (kein Flip) 548 154
M30 dagegen, ab +0,0R 629 (80) 247 (93)
M30 dagegen, ab +0,5R 574 (25) 234 (79)
M30 dagegen, ab +1,0R 549 (0) 183 (29)
M30 dagegen, auch im Minus 616 (68) 327 (172)
M30+H1, ab +0,5R 570 (22) 222 (67)
M30+H1, auch im Minus 616 (68) 290 (136)
Alle sechs Varianten in BEIDEN Hälften schlechter. Die „mildeste" ist die,
die kaum feuert (ab +1,0R, Δ 0/29) — exakt das Muster der 15-Minuten-Regel.
Kontrolle: die Basis (548/154) reproduziert backtest_flipclose2.py
(537/140) — die Pipeline ist in Ordnung.
⚠ Methodik: feste, geteilte Entry-Liste über alle Varianten.
backtest_flipclose2.py läuft nach jedem Exit bei xb+1 weiter, wodurch jede
Variante eine ANDERE Trade-Folge bekommt (der am 31.07. bei
backtest_exit_combo.py korrigierte Fehler). Hier von vornherein vermieden.

⚠⚠ DER MECHANISMUS IST IN ALLEN FÄLLEN DERSELBE: Trefferquote STEIGT (38→44 %, 42→51 %), Ertrag FÄLLT = Gewinner-Kappen. Ein Früh-Ausstieg kappt einige Trades vor dem vollen SL, schneidet aber weit öfter Trades ab, die sich erholt hätten. Damit ist es die fünfte unabhängige Messung dieser Klasse (15-Minuten-Regel · backtest_adverse_nosignal · Flip auf das Signal · Notfall-Stop · Flip auf den HTF-Trend) — alle negativ.

UMGESETZT auf User-Entscheidung (2026-08-07): auto_emergency_margin_pct = 8 — bewusst gegen die Messung, wie beim Auto-Squeeze. Gewählt wurde die mildeste Variante: Δ 14 € über 1.022 Trades ist praktisch neutral, sie feuert bei ~16 % der Trades. Sie schützt wenig und kostet wenig — eine Versicherung mit kleiner Prämie, die im schlechten Regime hilft und im guten kostet. Sizing bleibt bei 95 % (ausdrücklich nicht mitgeändert). Backup oil_widget_config.ini.bak-2026-08-07-emergency. ⚠ Bezugsgröße ist die MARGIN DIESER Position, nicht das Konto — bei ~670 €/Lot sind 8 % rund 54 €/Lot (bei den aktuellen 2,33 Lots ≈ 124 €). Der Modus hat Vorrang vor auto_emergency_pct (Konto-%) und auto_emergency_loss (Fixwert); beide bleiben 0. Der Vertipper-Schutz emergency_min_eur=3 gilt nur für manuelle UI-Eingaben, nicht für diesen Pfad. ⚠⚠ AKTIVIERUNG braucht einen NEUSTART (_emergency_margin_pct lebt im Speicher) — und die bereits offene Position bekommt ihn auch danach NICHT. Grund (verifiziert an engine.py:884): der Wiederherstellungs-Zweig setzt für ein wiedererkanntes Ticket _emergency_margin_armed_ticket = ticket und _emergency_loss auf den persistierten Wert (hier null) — bewusst so, damit ein Neustart keine gesetzte Entscheidung überschreibt (Fix 2026-07-17). Der 8-%-Stop greift also ab der NÄCHSTEN neuen Position. Daraus folgt: den Neustart am besten fahren, wenn das Konto flat ist — er nützt der laufenden Position ohnehin nichts und vermeidet das Risikofenster. Neu im Config-Wächter (measurement_reminder.py, validiert = 0), damit die Abweichung sichtbar bleibt. Zurück: auto_emergency_margin_pct = 0.

Was stattdessen belegt ist: der Verlust-Hebel liegt in der SL-Breite (live 2,0×ATR_M15 ≈ 3,6×ATR_M5, Worst bis 5,6×ATR; per Broker-SL geschlossene Trades hätten mit dem kanonischen Exit 1.229 € weniger verloren) und damit im Sizing, nicht im Exit. Gegen „falsche Einstiege" ist die gemessene Bremse das Veto (gegen das Signal 6,60 €/Lot, beidhälftig) — es steht bereits im Order-Dialog. Nichts gebaut.

⚠⚠ DIE PRÜFUNG HING AM COMMIT, NICHT AM DEPLOYMENT — behoben 2026-08-07

User-Frage „wird die Pipeline bei jedem Deployment ausgeführt?". Antwort war NEIN, und die Lücke war größer als sie aussah:

Weg zum Deployment Prüfung vorher
git commit check_nfalle + pytest (Hook installiert)
tools/deploy.py ⚠ prüfte nur den laufenden Server — nicht den Code
restart_server.bat gar nichts
Aufgabe OilTradingServer (Logon/Reboot) → start_server_hidden.vbs gar nichts
**Commit und Deployment sind zwei Ereignisse, und sie fallen beliebig
auseinander:** eine geänderte, nie committete Datei ging über
restart_server.bat ungeprüft live; nach einem Reboot startet die Aufgabe, was
auf der Platte liegt.
„Der Server kommt hoch" ist KEIN Ersatz für die Prüfung — die beiden
log.-NameError in core/trader.py sassen Monate in except-Zweigen
(einer seit dem Initial-Commit), und der Server startete jedes Mal sauber.

GEBAUT: tools/deploy.py bekommt Schritt 0check_nfalle + pytest VOR dem Kill; bei Rot Abbruch mit Rückgabecode 1. Die Reihenfolge ist der Punkt: ein laufender, funktionierender Server darf für kaputten Code nicht abgeschossen werden. Notausgang --ohne-pruefung (Philosophie wie git commit --no-verify). Beide Richtungen verifiziert: mit eingebautem F821-Fehler → „ ABBRUCH", Exit 1, und die Server-PID vorher/nachher identisch (20892) — der laufende Prozess wurde nachweislich nicht angefasst; ohne Fehler läuft es durch.

AUCH restart_server.bat prüft jetzt (Korrektur am selben Tag): mein erster Entwurf hatte es als „Wiederanlauf-Pfad" ausgenommen — falsch. Die Aufgabenplanung startet start_server_hidden.vbsstart_server.bat; restart_server.bat wird von nichts automatisch aufgerufen (geprüft) und ist damit ein absichtliches Deployment. Es prüft jetzt vor dem Kill, Notausgang restart_server.bat --ohne-pruefung. ⚠⚠ NUR start_server.bat bleibt ungeprüft — das ist der echte Wiederanlauf-Pfad (Reboot/Logon). Würde ein fehlschlagender Test dort den Start verhindern, stünde eine offene Position ohne Trailing, Time-Stop und Notfall-Stop da — nur mit dem Broker-SL. Eine Prüfung, die den Bot offline lässt, ist schlimmer als der Fehler, den sie sucht. Regel: absichtliches Deployment blockieren, Wiederanlauf niemals.

⚠⚠ ZWEI cmd-FALLEN DABEI, beide nur durch den POSITIV-Test gefunden: (1) if errorlevel 1 innerhalb eines geklammerten if/else-Blocks wird zur PARSE-Zeit ausgewertet, nicht zur Laufzeit — der erste Entwurf brach deshalb auch bei sauberem Code ab, der Neustart hätte nie mehr funktioniert. Behoben mit Sprungmarken statt Klammer-Blöcken. (2) echo ... ^(x^) maskiert im Block anders → „Der Befehl [0] ist … falsch geschrieben". Text jetzt ohne runde Klammern. ⚠ Der Negativ-Test allein sah in BEIDEN Fällen völlig in Ordnung aus — kaputter Code → Abbruch, Server unangetastet, Exit 1. Erst der Positiv-Test (sauberer Code muss DURCHLAUFEN) hat es entlarvt. Eine Sperre muss man in beide Richtungen prüfen; „blockiert korrekt" ist nur die Hälfte. Alle drei Zweige verifiziert (sauber → weiter/rc 0 · kaputt → Abbruch/rc 1 · --ohne-pruefung → weiter/rc 0), gegen eine Attrappe, deren Kill/Start durch ein echo ersetzt ist — so liess sich der Durchlauf-Zweig prüfen, ohne den laufenden Server anzufassen. ⚠ Auch der Testaufbau selbst hatte einen Fehler: die Attrappe lag zuerst in %TEMP%, dort macht cd /d "%~dp0" das Temp-Verzeichnis zum Arbeitsordner und %~dp0tools\pre_commit.py existiert nicht → scheinbarer Fehlschlag. Der Test gehört ins Projektverzeichnis.

LIVE↔BACKTEST-VERGLEICH IST JETZT TEIL DER PIPELINE (2026-08-07)

Konsequenz aus dem Messfehler direkt darunter: der Vergleich lief nur, wenn jemand daran dachte — und als er lief, war er falsch gebaut. Beides behoben.

(1) core/stichprobe.py — die Prävention ist KONSTRUKTIV, nicht ermahnend. entkoppeln() · anteil_ci() · unterscheidbar() · schluessel_level_stunde(). ⚠ Regel: vor jeder berichteten Rate entkoppeln, jede Aussage über einen Unterschied mit Konfidenzintervall belegen. Ein Punktschätzer ist auf pbreak_predictions/recommendations keine Aussage — die Zeilen sind nicht unabhängig. 8 Tests, darunter einer, der die reale Konstellation vom 07.08. nachspielt (roh 77 %, entkoppelt 33 %). ⚠ Fallstrick beim Bau, gefunden weil ich es laufen liess: ein sqlite3-Cursor liefert Tupel, keine dicts (TypeError: tuple indices must be integers) — die Feldangabe akzeptiert deshalb Namen und Spaltennummern.

(2) analyze_divergence.sec_d0 war selbst der Fehler — behoben. Es mittelte AVG(p_break) über ALLE Rohzeilen und flaggte bei Δ > 8 Pp.

vorher jetzt
Grundmenge 1.058 rohe Zeilen 110 entkoppelte
Urteil Δ +8,7 Pp → ALARM 37,6 % liegt in [37,3 … 55,5] %OK
Der Wächter hätte heute also einen Fehlalarm produziert — genau den, dem ich
elf Hypothesen lang nachgegangen bin.

(3) NEU: Abschnitt E „GLEICHES FENSTER" (--backtest). Rechnet den Backtest über genau den Live-Zeitraum und hält ihn gegen das Intervall der entkoppelten Live-Rate. Erster Lauf: live 46,4 % [37,3 … 55,5] gegen Backtest 43,9 % → im Intervall, kein Befund. ⚠ Braucht MT5 und ~12 min, deshalb opt-in — der Rest des Wächters bleibt DB-only und schnell.

(4) EINGEBAUT IN DEN WOCHENREPORT (weekly_review._divergenz_section, montags 07:30 per Mail + Telegram). CLAUDE.md nannte den Wächter seit dem 31.07. als „Kandidat für den Wochenreport" — jetzt ist er drin, mit demselben Fail-safe wie der Squeeze-Abschnitt (schlägt er fehl, fehlt der Abschnitt, der Report läuft weiter). ⚠ Bewusst NICHT in den pre-commit-Hook: der braucht MT5 und Minuten; die Stufe-1-Prüfung muss bei ~5 s bleiben, sonst wird sie deinstalliert.

Damit deckt die Pipeline drei verschiedene Fehlerklassen ab:

Stufe schützt gegen Takt
pre-commit (check_nfalle + pytest) Syntax, undefinierte Namen, \n-Falle jeder Commit
Track B (2 Stichproben, Nachbarn, KI) Overfitting — Edge existiert nicht je Strategie-Idee
Divergenz-Wächter D0/E Deployment-Drift — Edge existiert, läuft aber anders wöchentlich
tools/deploy.py --feld Aktivierung ohne Wirkung jeder Rollout

⚠⚠ DIE „BASISRATEN-LÜCKE" WAR EIN MESSFEHLER — MEINER (2026-08-07, aufgelöst)

Nach elf geprüften Hypothesen ist der Befund: es gab keinen Fehler im System. Die 14,1 Pp („Backtest 38,6 % gegen live 52,7 %") entstanden dadurch, dass ich eine geclusterte 6-Tage-Stichprobe gegen ein 208-Tage-Mittel gehalten habe.

Ursache Beitrag
Zeitraum — Backtest auf dieselben Tage (03.07.08.) eingeschränkt: 38,6 → 43,4 % +4,8 Pp = 34 %
Clusterung — live entkoppelt (max. 1 je Level und Stunde): 52,7 → 45,2 % 7,5 Pp = 53 %
Rest +1,8 Pp
**Entkoppelt: live 45,2 % [95-%-KI 35,5 … 54,8] gegen Backtest 43,4 % — der
Backtest-Wert liegt IM Intervall. Kein belegbarer Unterschied.**

⚠⚠ Das ist exakt der Fehler, den ich am selben Tag in der Fälligkeitsbedingung korrigiert hatte (dort zählte sie 800 ROHE statt entkoppelte Vorhersagen) — und ich habe ihn in der Auswertung selbst noch einmal gemacht. Regel: bei pbreak_predictions IMMER entkoppeln, bevor irgendeine Rate berichtet wird. Dieselbe Klasse wie die Bootstrap-Grenze bei ci95 (überlappende Beobachtungen).

WAS DAVON ÜBRIG BLEIBT — und es ist deutlich weniger dramatisch:

Befund Stand nach der Entkopplung
Basisrate „gewandert 37,8 → 53,2 %" zurückgezogen — entkoppelt 45,2 %, Zeitraum-Effekt
Kalibrierungsfehler bleibt: 14,3 Pp entkoppelt (roh 14,1) — KI [6,8 … 25,5], also klar > 0. Das Modell sagt im Mittel 38,1 %, real sind es 45,2 %
„oberste Klasse invertiert" (69 % → 17,2 %) NICHT belegt — beruhte auf n=29 ROHEN Zeilen ≈ wenige unabhängige Fälle; entkoppelt bleibt die Klasse unter der Mindestbesetzung
AUC unverändert unentschieden (entkoppelt n=93, KI [0,434 … 0,674])

Konsequenz: nichts umbauen. Es bleibt eine modeste Unterschätzung von ~7 Pp auf einer Stichprobe, die für eine Reparatur zu klein ist — genau deshalb steht die Fälligkeitsbedingung jetzt auf 350 entkoppelten Vorhersagen (Stand 93). Vorher ist jede Korrektur Kurvenanpassung an Rauschen.

DIE ELF GEPRÜFTEN HYPOTHESEN (alle einzeln gemessen, keine trägt):

# Hypothese Ergebnis
1 Niveau/Achsenabschnitt driftet Roll-Achse fällt live durch
2 Regime-Merkmal (ATR/Median) H2→H1 schlechter
3 Vola-Regime treibt Bruchrate über 5 Quintile flach (37,039,2 %)
4 aktuelles Regime ungewöhnlich Median 0,96 = normal
5 Sammelart (je Berührung ↔ je Bar) 1,7 Pp, falsche Richtung
6 ATR-Floor-Artefakt +2,8 Pp (⚠ 53,9 % der Backtest-Bars liegen auf dem Floor, live 5,4 %)
7 Level-Auswahl (gemischter Pool) +0,4 Pp — echter Bug, s. u., aber nicht die Ursache
8 Berührung per Close statt Bar-Spanne 3,9 Pp, falsche Richtung
9 Timeout zählt als Abprall nur 2,3 % im Backtest
10 Auswertungs-Auflösung M1 ↔ M5 M5 überschätzt Brüche um +8,0 Pp (falsche Richtung)
11 Richtung (Signal ↔ diskretionär) 38,0 / 37,6 / 37,5 % — irrelevant
Zwei Kontrollen, die die Werkzeuge bestätigen: mein M1-Nachspiel
reproduziert die Live-Auswertung zu 100 % (die Auswertung ist korrekt), und
pb_levels30 ist live befüllt (ph=6/pl=5) — der dokumentierte M5-Fallback ist
nicht aktiv.
⚠ Der ATR-Floor-Befund bleibt als Nebenwirkung bemerkenswert: **53,9 % aller
Backtest-Bars sitzen auf dem Floor 0,12**, live nur 5,4 %. Der Backtest-Zeitraum
ist also viel ruhiger als die Live-Woche — genau die Verzerrung, die den
Zeitraum-Effekt (Punkt „Zeitraum" oben) erzeugt.

⚠⚠ LEVEL-AUSWAHL: NACHBAU BEHOBEN (2026-08-07)

Auftrag „behebe das" zum benannten nächsten Schritt: die Backtests bauten die Live-Level-Auswahl nach, statt sie zu benutzen.

DREI ECHTE ABWEICHUNGEN GEFUNDEN — und die erste ist ein Lehrstück:

# Nachbau (backtest_pbreak_retrain.py) LIVE (_draw_levels)
1 clustert den GEMISCHTEN Rohpool (sorted(ch+cl)) ph und pl GETRENNT, dann zusammenführen
2 keine Rundung round(…, 3) je Linie
3 EINE Hysterese je Trade-Richtung Widerstand und Unterstützung getrennt
⚠⚠ Punkt 1 ist genau der Fehler, den der Live-Code als BEHOBEN dokumentiert:
„ph/pl bleiben GETRENNT geclustert (nicht vorher zusammenlegen!) — ein erster
Versuch clusterte den kombinierten Rohpool direkt, was in dichten Zonen zu
Ketten-Mega-Clustern führte." Der Nachbau machte also weiter das, was live
erkannt und repariert worden war. **Gemessen: 4,5 % aller Zeitpunkte hatten einen
anderen Widerstand** (13.283 Vergleichspunkte, Ø-Linienzahl 8,1 statt 8,4).

GELÖST: core/sr_levels.py (cluster · linien · naechste · halten). engine._draw_levels ruft es auf; Verhalten bitgenau unverändert (tests/test_sr_levels.py, 6 Tests über je 300400 Zufallsfälle inkl. Hysterese in beide Richtungen). ⚠ Der Test enthält bewusst auch die Gegenprobe, dass der Nachbau wirklich abwich — ohne sie wäre nicht belegt, dass es etwas zu beheben gab. ⚠ backtest_pbreak_retrain.build_levels bleibt unverändert (Migrations-Regel: seine Zahlen und die daraus gefitteten Live-Gewichte _PB_* dürfen sich nicht rückwirkend verschieben). Daneben steht jetzt build_levels_liveneue Fits gehören dorthin, zusammen mit core.sr_levels.linien statt _cluster.

⚠⚠ ABER: DIE KORREKTUR SCHLIESST DIE LÜCKE NICHT — +0,4 Pp.

Basisrate
Nachbau (gemischter Pool) 38,0 %
korrigiert (live-treu) 38,3 %
LIVE ab 01.08. 52,7 %

⚠⚠⚠ FÜNF HYPOTHESEN, FÜNF WIDERLEGUNGEN — die 15-Pp-Lücke ist OFFEN:

Hypothese Ergebnis
Niveau/Achsenabschnitt driftet Roll-Achse fällt live durch, oberste Klasse bleibt invertiert
Vola-Regime Bruchrate über 5 Quintile flach (37,039,2 %)
Sammelart (je Berührung ↔ je Bar) 1,7 Pp, falsche Richtung
ATR-Floor-Artefakt +2,8 Pp; bei live-gleichem ATR (≥0,20) erst 40,1 %
Level-Auswahl (dieser Fix) +0,4 Pp
⚠ Der ATR-Befund ist trotzdem bemerkenswert: **53,9 % aller Backtest-Bars sitzen
auf dem Floor 0,12**, live nur 5,4 % — der Backtest-Zeitraum ist also viel
ruhiger als die Live-Woche. Der ATR selbst stimmt dagegen überein (Median live
0,2167 gegen letzte 7 Tage 0,2174, Verhältnis 1,00).

Stand: der Fix ist richtig und gebaut, erklärt den Live-Fehler aber nicht und das musste er auch nicht: die Lücke war ein Messfehler in meiner eigenen Auswertung (s. Abschnitt darüber). Der Bug im Nachbau ist davon unberührt echt und bleibt behoben. ⚠ (erledigt) Die M1-gegen-M5-Auflösung bei der Ausgangsbestimmung (live wertet gegen candles_m1 aus, der Backtest gegen M5-Bars — innerhalb eines M5-Bars prüft der Backtest confirm VOR reject, was ihn zugunsten von „break" verzerrt, also in die falsche Richtung), und die Frage, ob wave.pb_levels30 live überhaupt dieselben Pivots liefert wie build_levels. Beides nicht gemessen. ⚠ Weiterhin kein Notfall: auto_sr_close = false, das Modell schliesst keine Trades.

⚠⚠ LIVE-CODE HING AN EINEM BACKTEST-SKRIPT — behoben 2026-08-07

Befund: core/engine.py (squeeze_monitor) und weekly_review.py importierten backtest_breakout_squeeze. Die Abhängigkeitsrichtung war damit verkehrt herum: „Backtests sind nur Dokumentation" stimmte nicht — ein Fehler dort konnte den laufenden Bot treffen.

Der schwerere Teil war nicht der Import, sondern wie die Parameter hineinkamen:

BQ._N = _SQ_N; BQ._K = _SQ_K        # Modul-Globale eines Backtests mutieren

Das wirkt prozessweit und damit auf jeden anderen Nutzer desselben Moduls im selben Prozess. Dass die Werte real abweichen, ist keine Theorie: der Backtest-Default ist _N=12, live gilt _SQ_N=**10**. Wer das Skript nach dem Monitor-Aufruf im selben Prozess benutzt hätte, hätte still mit Live- statt Backtest-Parametern gerechnet.

Gelöst: core/squeeze_scan.pyatr_series · exit_legacy · scan, alle Parameter in der Signatur statt in Modul-Globalen. engine/weekly_review rufen core auf; backtest_breakout_squeeze.py leitet nur noch weiter, damit es eine Implementierung gibt statt zweier, die auseinanderdriften (der Fehler, den das Projekt bei sim_run/_ema_series — Kopien in ≥6 Skripten — schon einmal gemacht hat).

⚠⚠ DER EXIT WURDE BEWUSST NICHT MITREPARIERT. exit_legacy ist eine bitgenaue Kopie inklusive seiner Abweichungen vom kanonischen Live-Exit (exit_model.LIVE):

Squeeze-Backtest / B4-Monitor LIVE
Trail 1,5 1,0 (seit 31.07.)
Lock-Phase keine ja
Time-Stop keiner 120 min
Initial-TP keiner 3,5×ATR
max_hold 288 200
Das ist ein FÜNFTES Exit-Modell neben den drei aus dem Stufe-3-Refactor und dem
kanonischen. Der B4-Monitor („Entry-Edge · Mechanik") misst also gegen einen
veralteten Exit. Das hier stillschweigend mitzuändern hätte die Vergleichsgrundlage
des Monitors verschoben, ohne dass es jemand merkt — **offener Befund, eigene
Entscheidung.** ⚠ Beim Umstellen ist die Erwartungsspanne (+0,14…+0,23 ØR) mit
neu zu bestimmen; sie stammt aus Messungen mit genau diesem Exit.

Bitgenauigkeit BEWIESEN, nicht behauptet (tests/test_squeeze_scan.py, 5 Tests): die alte Fassung liegt eingefroren im Test (wörtlich aus git show HEAD:…) und läuft gegen die neue über 500 synthetische Kursreihen × 3 Multiplikatoren. ⚠ Ein Vorher/Nachher-Lauf des Backtests wäre ungültig gewesen: er zieht seine Bars live aus MT5, das Fenster verschiebt sich mit jeder Minute — gleiche Zahlen wären Zufall, verschiedene kein Beleg. Die Reihen wechseln blockweise die Vola, sonst liegt der ATR immer über oder immer unter _ATRMIN und Kompressions-Bedingung, Filter und Cooldown werden nie durchlaufen. Zusätzlich prüft der Test, dass überhaupt >500 Ausbrüche entstehen — „beide finden nichts" ist kein Nachweis von Gleichheit. Der Test wurde selbst geprüft (Mutationsprobe): 6 gezielte Fehler in squeeze_scan.py eingebaut — Trail 1,5→1,4 · Breakeven 1,3→1,2 · Cooldown +1 Bar · Ausbruchs-Level +0,1 % · ATR-Fenster +1 Bar schlagen alle an. Der sechste (d > 0d >= 0) nicht — kein Test-Loch, sondern beweisbar äquivalent: d ist im ganzen Pfad nur ±1, die beiden Vergleiche unterscheiden sich allein bei d == 0. ⚠ Ein grüner Test ohne Mutationsprobe belegt nichts — dieselbe Fehlerklasse („Erfolg melden, wo nichts geprüft wurde") ist am 07.08. viermal aufgetreten.

Live-Gegenprobe auf 9.000 M5-Bars mit den echten Live-Parametern: n=112, Treffer 44 %, ØR +0,178, PF 1,26 — im dokumentierten Erwartungsband. ⚠ Aktivierung steht aus: zum Zeitpunkt des Umbaus lief eine offene Position (2,47 Lots); ein Neustart hätte ihr 1020 s Trailing/Time-Stop/Notfall-Stop genommen für eine Änderung, die per Test nichts am Verhalten ändert. Greift beim nächsten regulären Neustart.

Backtest-Audit 2026-08-04 — rechnen die Skripte noch korrekt?

Anlass: User-Frage nach dem angle=-Fund in backtest_auto_signal.py. Prüfung aller 87 Backtest-/Analyse-Skripte.

Lauffähigkeit: in Ordnung. Alle 87 kompilieren; 12 repräsentative (alle, die WaveRecommender konstruieren) laufen fehlerfrei. Die heutigen wave_rec-Änderungen (_reversal_enabled) brechen nichts — der Modul-Default True hält die Altmessungen stabil. ⚠ Drei Skripte nehmen [TF] [N] statt einer Bar-Zahl als erstes Argument (backtest_signal.py, backtest_pullback.py, backtest_htf_filter.py) — mit einer Zahl als TF quittieren sie mit KeyError. Kein Defekt, nur die Signatur beachten.

Der angle=-Befund: 11 Skripte, nicht 26.Methodik-Fehler bei mir selbst, korrigiert: Die erste Prüfung per Regex (_build\([^)]*angle\s*=) meldete 26 Treffer — die Zeichenklasse [^)]* bricht an der ersten Klammer ab, sodass Aufrufe wie w._build(..., htf_trend=m30s(T[i]), angle=ang) fälschlich als „ohne Winkel" galten. Die AST-Prüfung ergibt 11 echte Fälle (und erkennt zusätzlich Aufrufe, die angle positional übergeben). Regex ist für Aufruf-Analysen das falsche Werkzeug. Betroffen: backtest_angle.py:78 · backtest_auto_signal.py:177 · backtest_confluence.py:144 · backtest_dist.py:63 · backtest_exit.py:150 · backtest_htf_filter.py:106 · backtest_improve.py:91 · backtest_pullback.py:89 · backtest_rev_exit.py:72 · backtest_signal.py:91 · backtest_stretch.py:73.

Wie schlimm ist es? Gemessen über 37.577 M5-Bars:

mit Reversal (= bis 03.08.) ohne Reversal (= live seit 04.08.)
Signal weicht ab 19,1 % 1,1 %
Ø-Konfidenz-Differenz +7,7 4,4
über dem 55 %-Gate 79,4 → 90,6 % 79,4 → 78,5 %
WARTEN-Anteil (ohne HTF) 20,6 → 21,5 % (+1 Pp)
Das Reversal-Abschalten von heute hat die Lücke nebenbei fast geschlossen. Solange
das Reversal aktiv war, sahen diese 11 Skripte eine Signal-Population, die sich in jedem
fünften Bar unterschied und systematisch 7,7 Konfidenzpunkte danebenlag — das betrifft
den Großteil der Projekt-Historie. Ab jetzt sind sie näherungsweise gültig.
⚠ Die WARTEN-Zeile ist NICHT mit der 43-%-Referenz vergleichbar: für die Messung wurde
htf_trend nicht übergeben, der M30-Filter (allein ~27 % der WARTEN) fehlt also. Nur
die Differenz (+1 Pp) ist aussagekräftig.

Bewusst NICHT getan: die 11 Skripte „repariert". Ein angle= zu ergänzen ändert ihre Zahlen (~1 %) — das wäre laut Migrations-Regel keine Refaktorierung, sondern eine Neumessung. Sie bleiben unverändert und reproduzierbar; wo ihre Schlussfolgerung wieder relevant wird, gehört sie live-treu neu gerechnet (Muster: backtest_auto_signal_norev.py).

Gesamtprüfung 2026-08-02 (nach docs/review-prompt.md)

Erster vollständiger Durchlauf des Review-Prompts. Keine Strategie-Änderung — alle Fixes betreffen Telemetrie, Doku und toten Code.

Behoben:

  1. analyze_divergence.py kannte keine Epochen — und war damit selbst driftanfällig. D0 mischte Vorhersagen des ALTEN und des am 31.07. nachtrainierten P(break)-Modells und meldete dessen Fehlkalibrierung als aktuellen Alarm (das neue Modell hatte n=0, weil der Markt seit Fr 22:55 zu ist); B las die Prä-Migrations-NULLs von block_reason als blinden Fleck („None 66,8 %"); C druckte bei 0 Zeilen ein „OK", obwohl es fehlende Daten waren. Neu: _EPOCHS + _clamp() schneiden jede Auswertung auf den Beginn ihrer eigenen Datenlage. ⚠ Beim Nachtrainieren eines Modells das Datum in _EPOCHS mitziehen — sonst schlägt der Wächter wochenlang Fehlalarm, und ein Wächter, der immer warnt, wird ignoriert.
  2. Konsens-Pfeil: doppelte Arbeit für eine unsichtbare Linie. Der MQL5-Export rechnete im ~5-s-Takt ein komplettes zweites _verdict() für AR;K — der Indikator verwirft die Zeile aber seit v1.33 per Default (InpShowConsensus=false). Neu [trading] export_consensus_arrow (Default false, spiegelt den Indikator). Nebeneffekt beseitigt: das zweite Verdict konnte vom Verdict im Snapshot abweichen.
  3. /api/autosqueeze loggt jetzt die Herkunft ([WEB] …) wie /api/autosignal. Vorher war bei einem Zustandswechsel nicht belegbar, ob Dashboard oder Code — bei einem Schalter für autonome Echtgeld-Einstiege zu wenig.
  4. Reversal-Kennzahl an der Fundstelle korrigiert. „ØR +0,185 · PF 1,35 · 70 % Treffer" stand unverändert in der Reversal-Sektion, die Widerlegung 2 000 Zeilen später im Legacy-Recheck. Wer die Sektion liest, sah die Korrektur nie.
  5. Zwei tote import datetime in core/notify.py entfernt (beide Funktionen nutzen _time), funktional nachgetestet.

Geprüft und sauber: 0 fehlende Frontend-IDs (von 86) · nur 2 Config-Schlüssel ohne Leser (beide dokumentiert dormant) · Snapshot-Median 13 ms, alle DB-Abfragen <13 ms, Indizes vorhanden → keine Performance-Maßnahme nötig · 124 Datei- und 83 Funktionsreferenzen in CLAUDE.md stimmen · Asset- und Indikator-Version konsistent.

Zwischenverdacht zurückgezogen:block_reason erklärt nur 33 % der WARTEN" war ein reines Migrations-Artefakt (Spalte kam am 31.07.); seit 01.08. 100 % Abdeckung.

Offen (nicht behoben, bewusst):

  • ERLEDIGT 2026-08-05: auto_flip_close ist jetzt SICHTBAR (#flip-note in der Meldungen-Karte, v=139). Zeigt Schwelle und Auslöse-Zähler und nennt ausdrücklich die Messung („⚠ gemessen in beiden Halbjahren negativ", 92/160 R) — ein blanker Zähler hätte wie ein Erfolg ausgesehen. Der Exit bleibt AN (User-Wunsch), aber die Entscheidung ist jetzt informiert. ⚠ Live-Gegenprobe 2026-08-05 (n=4): die 4 Auslösungen brachten +284,22 €; mit dem kanonischen Exit ab demselben Einstieg wären es +183,51 € gewesen — der Flip war auf diesen Trades also +100,71 € BESSER. ⚠ Das ist KEIN Beleg: n=4, und +101 € stecken in EINEM Trade (04.08. 16:15: +188,64 gegen +87,68). Der 04.08. ist genau der Ausreißertag, der allein die 30-Tage-Bilanz trägt. Gegen ~2.000 Trades je Hälfte im Backtest (jede Variante, beide Hälften negativ) wiegt das nichts — dieselbe Falle wie der 12k-Vorlauf beim Kerzen-Fade, der +0,291 zeigte und bei 80k verschwand. Der Einwand ist geprüft, nicht bestätigt. ABGESCHALTET am 2026-08-05 (User-Entscheidung nach Vorlage der Messung), [trading] auto_flip_close = false. Erwarteter Gewinn 35250 R je nach Messung, Nachteil praktisch keiner — der Alarm bleibt unverändert (Blinken/Ton/Telegram bei Signal-Flip im Plus). Der Alarm-Pfad _check_close_alert hängt an _run_analysis, NICHT am _pos_loop-Aufruf von _check_flip_close — nur die automatische Ausführung entfällt. Damit ist wieder der dokumentierte Ursprungszustand hergestellt: „Flip bleibt Alarm + menschliche Entscheidung" (User-Übersteuerungen liefen gemessen 68 % WR). Live verifiziert nach Neustart: flip_close=False, Alarm-Zähler unberührt. ⚠ Kein runtime_state.json-Override vorhanden (anders als bei auto_signal, wo ein UI-Toggle die ini still überschrieb) — die ini-Änderung greift also direkt. Neu im Config-Wächter (measurement_reminder.py, validiert = false): wird der Schalter je wieder auf true gesetzt, läuft ein gemessen negativer Exit auf echtem Geld, und das soll auffallen. Zurückholen: auto_flip_close = true.
  • 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.
  • Die 5 Zähler-Snapshot-Felder (close_alert_count usw.) werden von nichts abgefragt.

Ausstehende Messungen — Erinnerung per Timer

measurement_reminder.py + Windows-Task OilMeasurementReminder (täglich 18:00) meldet per Popup, welche Messung genug Daten hat. Jeder Punkt hat eine PRÜFBARE Fälligkeitsbedingung (Datenmenge/Datum), nicht nur ein Datum — gemeldet wird je Punkt 1× (Marker in .reminders/, gitignoriert; Marker löschen = Wiedervorlage). Status jederzeit: python measurement_reminder.py --status.

Warum es das gibt: mehrere Module sammeln still Daten für „später" — und das blieb liegen. Die P(break)-Prognose-Genauigkeit war seit 2026-07-23 auswertbar und hatte am 2026-07-30 bereits 2213 ausgewertete Zeilen, ohne dass jemand hinsah.

Messung Bedingung Stand 2026-08-01
P(break) erneut auswerten (nach Nachtraining) ≥800 Vorhersagen ab 01.08. 0
Modul-Audit: zweiter Durchgang ≥28 Tage seit dem 1. Audit 2 (6 %)
Verdict-Module auf Prädiktivität (verdict_votes) ≥25 000 Verdicts 8142 (33 %)

Offene Modul-Fragen für diesen Lauf (Stand 2026-08-07, aus dem Zwischenstand über 15.544 Zeilen): (a) Liq-Trend (0,5) — +0,20/+0,32 über nur 132 Episoden; ⚠ dieselbe Quelle wie das am 06.08. entmachtete Orderbuch-Modul, und HL-Preisdaten laufen gemessen hinter Pepperstone her (51 % Treffer nach 6 s). Als einziges verbliebenes HL-Modul mit Stimmrecht zuerst prüfen. (b) Muster (0,25) — +1,27/+0,67 bei n=88, zu dünn. (c) H1 (1,5) — nur 19 Episoden, trägt aber 38 % des Nadel-Einflusses; prädiktiv nicht beurteilbar. (d) Elliott und Orderbuch sind entmachtet (used=False), werden aber weiter geloggt — die Reihe darf nicht abreißen. | Manuelle Trades: Stufe 2 (was trennt Gewinner von Verlierern?) | ≥300 manuelle Trades mit ctx_* | 0 (Sammlung ab 04.08.) | | Auto-Squeeze: B5-Abbruchprüfung (Latte vorab fixiert) | ≥20 Squeeze-Trades ab 01.08. | 2 (10 %) | | M30-Level-Umstellung live gegenprüfen | ERLEDIGT 04.08. — s. u. | | | M1-Squeeze Variante B | ≥180 Tage candles_m1 | 14 (8 %) | | News-Score-Prädiktivität | ≥90 Tage Sammlung | 9 (10 %) |

Der Config-Wächter liest jetzt auch runtime_state.json (Fix 2026-08-01). Er verglich vorher nur die ini gegen die validierten Werte — die UI-Schalter leben aber in runtime_state.json und werden beim Start über die ini gelegt. Dadurch meldete er „alles auf dem gemessenen Stand", während live etwas anderes lief. Real: auto_signal stand in der ini auf false, war aber durch einen Dashboard-Toggle dauerhaft true — 4 der 7 Bot-Trades vom 31.07. kamen aus diesem gemessen-negativen Pfad. _cfg_now() bildet den Vorrang jetzt genauso ab wie der Engine-Start; mit einem Szenario verifiziert (runtime true gegen validiert false → wird gemeldet). Neu überwacht: auto_signal (soll false) und auto_squeeze (soll true).

M30-Level-Umstellung live gegengeprüft (04.08.) — bestätigt, mit einem Vorbehalt

Zeitraum n Med. Lots EUR/Lot Ø Move
vorher (M5-Level) 89 0,81 +17,53 0,202 $
nachher (M30-Level) 28 0,53 +36,92 0,427 $

Ertrag je Lot mehr als verdoppelt, Ø-Kursbewegung je Trade ebenfalls — deckt sich mit der Backtest-Erwartung („R-Ertrag rund doppelt") und mit dem Mechanismus (M30-Level liegen 1,95×ATR auseinander statt 0,61×ATR, der Trade läuft also länger).

⚠⚠ Die NORMIERUNG war entscheidend, sonst wäre der Schluss falsch gewesen. Unnormiert sah der 04.08. wie der Treiber aus (Ø +55,41 € je Trade gegen +19,30 € im Rest). Je Lot ist heute aber praktisch identisch zum Rest (+38,81 gegen +35,87) — der Unterschied war reine Positionsgröße (Median 1,36 statt 0,46 Lots, weil das Konto an diesem Tag von ~800 auf ~1.400 € wuchs). Ø-EUR-Vergleiche über Zeiträume mit unterschiedlicher Positionsgröße sind ungültig; das gehört bei jeder künftigen Live-Auswertung mitgedacht.

Nicht der Level-Quelle ALLEIN zuzuordnen: am 31.07. kamen das P(break)-Nachtraining UND die Schwellensenkung 0,55 → 0,35 dazu. Getrennt: 30.07. (nur M30-Level, altes Modell) n=8, +19,88 €/Lot · ab 31.07. (M30 + neues Modell) n=20, +43,73 €/Lot. Das Fenster mit nur der Umstellung ist zu klein für eine Zuordnung. Der kombinierte Effekt ist bestätigt, die Ursachenaufteilung nicht. Beide Änderungen waren im Backtest validiert und zeigen live in dieselbe Richtung.

(erledigt und ausgetragen: P(break)-Genauigkeit → analyze_pbreak_live.py 31.07., Modell daraufhin nachtrainiert · Chartmuster-Kontrolltest → 30.07., Muster schlagen die Kontrolle in beiden Hälften, Gewicht 1,0)

Der Task OilMeasurementReminder wurde am 2026-07-31 testweise ausgelöst und lief mit Ergebnis 0 durch (Arbeitsverzeichnis korrekt gesetzt) — ein Erinnerungs- system, das still scheitert, wäre schlimmer als keins.

Verwandte Timer: HyperliquidBookflow (Order-Flow, 2 Stufen ~3 Tage/~2 Wochen), HyperliquidLeadLag (erledigt: Pepperstone führt), Wochenreport montags 07:30 (B4).

WTI-Trading-Handbuch — Abgleich mit dem Gemessenen (2026-07-30)

Der User reichte ein 19-seitiges WTI-Handbuch ein („lerne daraus Kontext für die Empfehlung"). Ergebnis des Abgleichs: der belegbare Teil ist längst umgesetzt, der Rest ist in diesem Projekt bereits gemessen — und fiel durch. Kein Konzept aus dem Handbuch wurde neu ins Signal aufgenommen.

Bereits UMGESETZT (Handbuch bestätigt den Ist-Zustand):

Handbuch im Bot
„Nur mit dem HTF-Trend" M30-Gegen-Trend-Filter (gemessen: Edge ×2) + Entry-Checkliste
„Keine Trades vor EIA" EIA-Blackout Mi 15:3016:30 (gemessen: beidhälftig negativ)
ATR-basierter Stop + Puffer Initial-SL-Band 1,82,2×ATR (gemessen besser als 1,5)
„Nach TP1 trailen" Trailing 1,5×ATR + Breakeven 1,3 (gemessen optimal)
„Nach 3 Losses Pause" Mental-Check in der Entry-Checkliste (Verlust-Serie des Tages)
Journal + Kennzahlen trades-DB, Statistik-Tab, Wochenreport (WR/PF/Verhältnis)
NY-Session am besten gemessen: 2122 Uhr sind die einzig robust positiven Stunden

Bereits GEMESSEN und VERWORFEN (Handbuch empfiehlt es trotzdem): Liquidity Sweeps (Equal Highs/Lows) · Order-Block-Klasse = Pivot-Zonen (6×) · Double Top/Bottom · H&S/Triangle/Flag/Cup (Ziel-Trefferquote nur 1338 %) · Wyckoff Accumulation/Distribution · False Breakout · DXY-Korrelation (backtest_intermarket.py) · Elliott (nur Verdict-Stimme, Gewicht 1,0).

NEU gemessen (die echten Lücken, backtest_handbook.py) — alle durchgefallen:

Konzept H1 H2 Urteil
PDH/PDL Bounce („WTI reagiert extrem stark") 0,335 0,199 beidseitig negativ
PDH/PDL Sweep 0,462 0,184 schlechtestes Ergebnis überhaupt
Asian Range H/L Bounce 0,134 0,005
Asian Range H/L Sweep 0,178 0,040
Discount/Premium-Filter 0,097 (Basis 0,135) +0,041 (Basis +0,053) nicht robust, wirft 70 % Volumen weg
Freitag ab 16:00 CET meiden 0,179 (Basis 0,135) +0,020 (Basis +0,053) verschlechtert BEIDE

⚠ Zwei Befunde sind bemerkenswert: (a) PDH/PDL ist der am klarsten schädliche Level-Typ, den das Projekt je gemessen hat — obwohl das Handbuch ihn als besonders stark bewirbt. (b) Der Freitagnachmittags-Filter verschlechtert beide Hälften, die dort geblockten Trades waren also überdurchschnittlich — dieselbe Lehre wie beim Dead-Hours-Gate: pauschale Zeitfilter kosten mehr, als sie sparen.

Nicht umgesetzt (bewusste User-Entscheidungen, nicht Messfrage): 12 % Risk/Trade und 3 % Tages-Limit (User fährt 95 % Margin, Circuit Breaker am 2026-07-30 erneut abgelehnt) · Mindest-R:R 1:2 (der Bot nutzt Trailing statt festem Ziel — gemessen besser) · Teil-Exit bei 1:1 (_PARTIAL_TP_FRAC=0, User-Vorgabe).

Auch beachten

  • Startup-Schonfrist gegen Sofort-Close nach Neustart (User-Vorgabe 2026-07-20, [trading] startup_close_grace_s=60): In den ersten 60 s nach Bot-Start schließt der Bot keinen laufenden Trade selbst — gilt für S/R-Close (_check_sr_close), Notfall-Stop/Gewinn-Ziel (_check_auto_close, Armieren läuft weiter, nur der Close ist gesperrt), Time-Stop (trailing, via set_no_close_until) und Auto-Squeeze- Reverse. Grund: nach restart_server.bat sind Preis/ATR/P(break)/Level für einen Moment instabil → ein adoptierter Trade, der gerade in S/R-Close- oder Notfall-Nähe liegt, würde sonst in der ersten Sekunde auf halb-initialisierten Daten gekappt. Engine setzt _no_autoclose_until = start + grace, _in_startup_grace() gated die drei _pos_loop-Checks; die Trailing-Instanz bekommt denselben Stichtag. ⚠ Der Broker-SL (2×ATR) bleibt unberührt — er läuft am Broker, kann NICHT verzögert werden (dort ist auch keine Verzögerung erwünscht: ein Stop-Loss muss greifen). 0 = aus.
  • Update-Reboot-Lücke (Root-Cause der Nacht 2026-07-20, 95,48 €): Windows Update rebootete 01:30 (MoUsoCoreWorker/TrustedInstaller, 2×), der Server-Task OilTradingServer hat nur einen Logon-Trigger → Server+MT5 down bis zum Login 07:42; Time-Stop/S/R-Close/Notfall-Stop liefen nicht, nur der Broker-SL (broker-seitig!) schützte. Fix: WU-Policy AUOptions=4, Tag=Sa, 03:00 (HKLM...\WindowsUpdate\AU — Reboots nur noch Samstag früh, Markt zu); Standby/ Hibernate AC+DC=0. ⚠ Offen: nach einem Sa-Reboot startet der Server erst beim nächsten Login — Auto-Logon (netplwiz) wäre die letzte Lücke (So-Nacht-Open), braucht das Windows-Passwort des Users. Server-seitige Schutzschichten sind nur so gut wie die PC-Laufzeit.
  • UI-Einstellungen neustart-fest (runtime_state.json, 2026-07-19): App-gesetzte Werte — 🚀-BRK-Toggle (auto_squeeze), Auto-Close-Schalter (auto_sr_close), Gewinn-Close-Mindestgewinn (sr_close_min_gain) — werden bei jedem set_* atomar persistiert (_load/_save_runtime_state) und überschreiben beim Start die ini-Defaults (nur vorhandene Keys). ⚠ set_sr_close_min_gain vergaß den _save bis 2026-07-22 (Bug, jetzt gefixt) — der am Handy gesetzte Mindestgewinn überlebte Änderungen nicht sauber; zusätzlich schickte Enter auf leerem Feld ungewollt 0 (change+keydown doppelt → Enter blurrt jetzt nur, srclose_min-Endpoint loggt [WEB]).
  • Log-Hygiene: Trail-DEBUG-Zeile (Trail: profit=…) auf 1×/30 s gedrosselt (2026-07-19; vorher ~28k Zeilen je Rotation → 5×5 MB deckten nur ~2 Tage Forensik).
  • DB-Guard: history.log_trade_open verwirft 0-Lot-/Preis-lose Einträge (Reconcile-Artefakte; 6 Alt-Zeilen am 2026-07-19 bereinigt, Backup oil_widget_history.db.bak-2026-07-19).
  • Persönliches Auto-Memory unter C:\Users\ah\.claude\projects\…\memory\ (Beobachtungen/Feedback) ergänzt diese Datei.
  • oil_widget_config.ini enthält Live-Secrets (Keys/Token) — nicht ins Repo/Logs leaken.
  • DB oil_widget_history.db (trades · intended_trades · recommendations · m15_states · cone_checks · candles_m1 · pbreak_predictions · verdict_votes). Vor jedem Eingriff Backup (*.bak-<datum>); Zeitspalten sind lokale Epoch (Broker-Offset bereits korrigiert) — AUSNAHME candles_m1.time = ROHE Broker-Zeit (UTC+3), deckungsgleich mit copy_rates.
  • M1-Candle-Logger (core/candle_logger.py, 2026-07-21, Daten-Sammlung, kein Strategie-Eingriff): schreibt abgeschlossene M1-Kerzen (OHLC + Spread-in-Preis + tick_volume + symbol) nach candles_m1 (PK time, INSERT OR IGNORE = dedupliziert, self-healing). engine.candles.log(sym) im Trend-Loop, self-throttled ~55 s; erster Lauf backfillt ~3000 Bars, danach die letzten 180. Warum: Broker-M1-History rollt weg (kurz); M1 ist die Master-Auflösung → aus M1 jede höhere TF exakt aggregierbar. ⚠ Ticks/Echtzeit bewusst NICHT geloggt (~85k/Tag = 30M/Jahr, kein Bedarf — Strategien sind M5+, Slippage aus echten Fills gemessen). ~370k Bars/Jahr = vernachlässigbar.