# 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 = L2–L5 light (kein
> Backtest); Doku-Korrektur = nur L5. Prozess soll helfen, nicht zur Bürokratie werden.
>
> 🔬 **Track B — Strategie-Lebenszyklus** (ZUSÄTZLICH zu L1–L5, für ALLES, was den
> **Edge** berührt: Signal, Risk/Sizing, Exit, Gate, Filter). Track A = L1–L5
> (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_win−gross_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 (90–120 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_content` → `max_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 ~1000–1200 `reasoning_tokens` (in `message.reasoning_content`) VOR dem
  `content` → `max_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:
  ```powershell
  $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>`**:
  ```js
  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` `undefined` → `parseFloat`
  → 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 15–17 am günstigsten (~0,13),
  **Nacht 0–7 = Kostenfalle (0,32–0,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 (WR~68 % · PF~1,3 · Verh~0,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_modules`** — `node --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 werden** — `HKLM` 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:
  ```python
  [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 9–20-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 festgehalten** — `requirements.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-Bootstrap** —
  `ci95` 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 (43–46 % 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 10–20 / k 0,1–0,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** (2–3×/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 3–4 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 | H2 ØR / PF / n |
  |---|---|---|
  | (a) Close bricht Level | +0,461 / 2,68 / 801 | +0,605 / 3,51 / 1280 |
  | **(b) Berührung (LIVE)** | **+0,124 / 1,24 / 1092** | **+0,292 / 1,64 / 1807** |
  **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 | B) Level, Fill bei Close-Bruch | **C) Level, Fill bei BERÜHRUNG** |
  |---|---|---|---|
  | ≥55 | −0,015 / −0,172 | +0,381 / +0,315 | **−0,392 / −0,338** ❌ |
  | ≥65 | −0,024 / −0,085 | +0,367 / +0,385 | **−0,478 / −0,471** ❌ |
  | ≥75 | −0,148 / −0,310 | +0,288 / +0,136 | **−0,184 / −0,202** ❌ |
  **C ist in ALLEN SECHS Feldern negativ** (PF 0,38–0,71). Der Look-ahead-Aufschlag
  beträgt **0,77–0,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[i−N] ± 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[i−N]` 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 **10–17 Uhr** (0,210–0,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 2–3 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
  10–17 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 10–17 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 10–17 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ßer** — `analyze_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 10–17 Uhr** |
  |---|---|---|
  | 0–15 min | **0,0470 $** | **0,0058 $** |
  | 15–30 min | 0,0164 $ | 0,0238 $ |
  | 60–120 min | 0,0502 $ | 0,0449 $ |
  | >120 min | **0,1691 $** | 0,0464 $ |
  | **<30 min gewichtet** | **0,0370 $** | **0,0124 $** |
  ⚠⚠ **Der Schwanz sitzt AUSSERHALB von 10–17** — 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, 10–17 Uhr · 0,0124 $** | +0,354 / **+150** | +0,247 / **+121** | 424 |
  | M5, alle Stunden · 0,0370 $ | **+0,401** / +108 | **+0,261** / +71 | 270 |
  | M5, 10–17 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 (10–17) 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 10–17-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 10–17 bei größerer
  Stichprobe unter ~0,02 $, dann ist der **Hybrid** (M1 während 10–17, 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 =
  12–22 Trades = Rauschen), Frequenz ~2–3/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,3–0,6: −0,100/−0,059 (⚠ 79–82 % WR und trotzdem PF<1 =
  Klein-Close-Falle) · 0,6–1,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 **40–54 % = 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** (50–54) oder **weit** (20–30) 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 40–54 % 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 (0–0,3×ATR) = schwächster Edge (−3); Trend-Momentum (1–2×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.25–04.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 08–12 | +0,06 | −0,43 | kippt |
  | Nachmittag 14–18 | −1,26 | +1,96 | kippt |
  | Abend 19–23 | −0,39 | +1,67 | kippt |
  | Nacht 00–07 | +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,02–0,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 **138–391** ü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:30–16:30)
  bleibt separat aktiv** (eigenes Event-Gate, nicht Teil des `dead_hours`). Historie/
  Messung (Gate war 11–14 → 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 0–7 = Kostenfalle** (konstanter
  Spread ÷ niedriger Nacht-ATR = 0,32–0,50×ATR, in beiden Hälften negativ; reine
  Arithmetik) + **12/16 Uhr 3× unabhängig negativ**. Politik „0–7+12" verbessert
  BEIDE Hälften (H1 −4038→−1870, H2 +100→+1191). **Robust positiv nur 21–22 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 11–14, dann 12+16, dann auf User-Wunsch aus, jetzt gezielt zurück):
  - **Von 11–14 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 11–14 blockte harmlose Stunden und ließ die real schlechte 16
    laufen. **Gate 12+16 schlägt 11–14 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 21–22 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:30–16: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 = angle−90` **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,04–0,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
    **schlechter** (Δ −2500…−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 EMA12−EMA50| > 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_analysis` → `history.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 = angle−90`: **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
  (`engine` → `wave.set_reversal_enabled(...)`, Config-Default dort `false`).
  Verhaltensänderung nur im Band **|stretch| 3,0–3,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_SHORT` → **`LONG/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 (~75–80 %) 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=heuristic` → `engine._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, 1–2 % = 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 1–2 %/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,8–2,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 ~38–43 % auf 18–26 % 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,0–2,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,3–1,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,2–1,5 war zu eng (MAE ~1,7×ATR,
    31–37 % Frühstopps). 2,0×ATR hebt Ø-R +32 %, PF 1,33→1,39, Frühstopps →25 %,
    Worst-Case auf 2,0×ATR gedeckelt. Breiter (3–4×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)=36–37 %; 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 (41–43 %).
    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,50–0,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 | S/R-Closes H1/H2 |
    |---|---|---|---|
    | nur S/R-Close | +59 | −14 | 535 / 284 |
    | nur Flip-Close | −1 | −43 | — |
    | BEIDE | +56 | −50 | **507 / 252** |
    | **BEIDE, Flip nachrangig** | **+62** | **−49** | **516 / 260** |
    **Kollision belegt:** der Flip nahm dem S/R-Close 28 bzw. 32 Trades weg (5–11 %) —
    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 17–32 % 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 | davon ≤2 € |
    |---|---|---|---|
    | `sr_close` (Bot) | 81 | **+14,65** | 10 (+7,65) |
    | `manual` (User) | 64 | **+6,37** | **18** (+13,46) |
    **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 | Bot-Exit | Δ |
    |---|---|---|---|---|
    | `manual` Gewinn | 143 | **+1428** | +962 | **−467** |
    | `sr_close` Gewinn | 106 | +1591 | +1060 | −530 |
    | `emergency` Verlust | 98 | −1750 | −1235 | +515 |
    | **`sl` Verlust** | **69** | **−1926** | **−697** | **+1229** |
    **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 | Worst H1/H2 |
    |---|---|---|---|
    | 2,0×ATR_M5 | −0,092 / −44 | −0,024 / −18 | −2,33 / −2,29 |
    | **2,0×ATR_M15 (LIVE)** | −0,145 / −69 | **+0,064 / +48** | **−4,77 / −5,64** |
    | 1,5×ATR_M5 | −0,081 / −38 | −0,038 / −28 | **−1,83 / −1,85** |
    | 2,5×ATR_M5 | −0,121 / −58 | −0,013 / −10 | −2,83 / −2,79 |
    | 3,0×ATR_M5 | −0,130 / −62 | +0,018 / +13 | −3,33 / −3,29 |
    **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 46–53 %, 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 2–3× → 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,30–0,90; **ini seit 2026-07-24 = 0,55** auf User-Wunsch —
    liegt im validierten Optimum-Plateau 0,50–0,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 ~10–13 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 7–15 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,50–0,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,006–0,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,8–2,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,196–0,201, PF 1,38–1,39, Worst −2,2×ATR): der **Band-Cap
    [1,8–2,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,06–0,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** 0–7 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 14–16 / Asia 2–4 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=3–31) 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 56–76 %); 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,78–0,90),
    **H2 durchgehend positiv** (+0,112…+0,167) → klassisches Regime-Kippen, n=718–1167
    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 60–99/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 0–7→3–6 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,25–0,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:** 3–6 (+0,380/+0,272) · 0–7
  (+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 (~2–3×/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,25–0,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_levels` → **`pending_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.
- **⚠⚠ ZUVERLÄSSIGKEITS-PRÜFUNG 2026-08-11 (`backtest_squeeze_woche.py`, User:
  „funktioniert der Breakout-Auto-Trader zuverlässig? simuliere die letzte Woche").**
  ⚠⚠ **BEFUND 0 beantwortet die Frage vorweg: DER TRADER IST AUS.**
  `runtime_state.json` enthält `"auto_squeeze": false` und **überschreibt die ini**
  (`= true`) beim Start — dieselbe Falle wie am 31.07. bei `auto_signal`, nur mit
  umgekehrtem Vorzeichen. Letzter Squeeze-Trade **06.08. 13:52**,
  `squeeze_entry_count = 0`. ✅ Der Config-Wächter meldet es korrekt („ist 'false'
  — validiert gegen 'true'"); es wurde nur nicht gehandelt. Zurück: 🚀 BRK.
  ✅ **BEFUND 1 — die MECHANIK arbeitet korrekt.** Die Simulation nutzt die echte
  `_squeeze_one` und reproduziert **10 von 11 Live-Trades binnen 10 min und auf
  wenige Cent genau** (live 06.08 02:11 SELL 75,127 ↔ Sim 02:10 SHORT 75,132).
  Die Erkennung ist nicht das Problem.
  **BEFUND 2 — die Woche** (ruhende Stop-Order, Fill bei BERÜHRUNG, Nacht 3–6
  gesperrt, EIN Slot, kanonischer Exit, Echtkosten):
  | Fill-Slippage | n | Treffer | ØR | PF | ΣR |
  |---|---|---|---|---|---|
  | **0,00×ATR** | 47 | 57 % | **+0,196** | 1,37 | +9,20 |
  | 0,10×ATR | 47 | 57 % | +0,100 | 1,17 | +4,69 |
  | **0,20×ATR** | 46 | 50 % | **−0,013** | 0,98 | −0,58 |
  ≈ **+610 €** bei 2,5 Lots, ØR im validierten Band (+0,14…+0,23). ⚠⚠ **Drei
  Vorbehalte:** **(1)** die Sim **überhandelt um das 2,4-fache** (26 statt 11
  Einstiege in der An-Phase) — ihr fehlen Live-Dedup (dir, level), 3-min-
  Wiedereinstiegssperre und S/R-Cooldown; ΣR ist nach oben verzerrt. **(2)** der
  Edge **stirbt bei 0,20×ATR Fill-Slippage**, und live lag der Abstand
  Einstieg↔Level vor dem Pending-Umbau bei Median **+0,275×ATR** — alles hängt
  daran, ob die ruhenden Orders am Level füllen (`squeeze_entry_gap`: **8/12**).
  **(3)** eine Woche ist Rauschen — die Phase, in der der Trader **AUS** war, ist
  sogar die bessere (ØR +0,287 gegen +0,122).
  ⚠⚠ **BEFUND 3 — der GEPAARTE Vergleich widerspricht dem B4-Befund vom 01.08.**
  Auf **denselben 10 Einstiegen**: **LIVE −109,52 €** gegen **kanonischer Exit
  −179,27 €** — der Live-Exit war **70 € BESSER**. Am 01.08. hiess es „die Lücke
  entsteht im EXIT"; hier ist es umgekehrt. Im Detail rettete der **S/R-Close**
  drei Trades (05.08 08:33 **+12,56 gegen −113,64** · 06.08 13:51/52 je
  **+0,26/+2,88 gegen −86,38**), während der kanonische Exit bei 06.08 09:04
  (**−46,82 gegen −175,21**) und 02:11 (**+23,83 gegen −43,29**) klar besser war.
  **Gemischt, n=10, von wenigen Trades dominiert — kein Urteil, aber ein
  Warnschild gegen den umgekehrten Schluss.**
  **BEFUND 4 — B5: 11 von 20 Trades (55 %).** Beide Bedingungen sind bereits weit
  verletzt (je Lot ØGewinn 5,20 · ØVerlust 31,12 → **Verhältnis 0,17**, **PF
  0,45**) bei **73 % Trefferquote** — die dokumentierte Squeeze-Signatur. ⚠ Formal
  **nicht fällig**; die Latte nachträglich zu senken wäre genau der Fehler, gegen
  den sie vorab fixiert wurde.
  ⚠ **OFFEN, nicht verifiziert:** am 06.08. **13:51 und 13:52** stehen ZWEI
  Live-BUYs zum identischen Preis 76,315 auf EINEN Ausbruch (beide Pending-Fills,
  beide binnen einer Minute per `sr_close` geschlossen) — sieht nach
  **Doppel-Einstieg auf ein Signal** aus. Ungeprüft, ob Defekt im OCO-Ersatz oder
  Teilfüllung.
- **⚠⚠ 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,5–3,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);
  151–430 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.py` rechnet DST-korrekt über `zoneinfo` 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 0–7):** 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). 3–6 ist
  schlicht das **günstigste Fenster mit noch ≥4 h Schutz**: Preis ΣR
  **−12,5/−62,9 R** gegen **−42,4/−242,6** bei 0–7 = **~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,32–0,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 0–7 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,32–0,50). In dieser Nacht lagen die realen Kosten
  bei **0,11–0,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,15–0,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 0–7 (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 0–7 auf 3–6 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 | Preis H2 |
  |---|---|---|---|
  | 4–5 | 2 | −3,9 | −18,1 |
  | 4–6 | 3 | −6,8 | −39,9 |
  | **3–6 (gewählt)** | **4** | **−12,5** | **−62,9** |
  | 2–5 | 4 | −12,8 | −77,2 |
  | 0–7 (alt) | 8 | −42,4 | −242,6 |
  Die 4-Stunden-Untergrenze ist **gesetzt, nicht gemessen** — darunter wäre der
  Schutzzweck hinfällig. Wer weniger Schutz will, nimmt 4–5 (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 0–7.
  **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 ~3–5 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,1–0,5×ATR** (Squeeze-Entries) | **0 / 0** |
  | sofort schließen (Wellen-Entries) | 8 / 8 von 386 bzw. 835 |
  | enger SL (Wellen-Entries) | 2–3 / 2–3 |
  ✅✅ **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 **12–18 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,002–0,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/sltp` → `trail.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.

## ⚠⚠ TELEGRAM WAR NICHT KAPUTT — ES SENDETE NUR NICHTS MEHR (2026-08-11)

User: „ich bekomme keine Telegram-Nachrichten mehr."
✅ **Kanal nachweislich intakt:** `getMe` → `@AH_Oiltrader_bot`, `getChat` → privat
„Axel", **Testnachricht zugestellt** (message_id 1764). Im gesamten Log **keine
einzige** Fehlermeldung; der Tagesreport kam täglich (08.–11.08. je 07:3x, alle
`ok`). Letzte Nicht-Report-Nachricht **07.08. 22:49**, davor am 06.08. 19:33–19:58
im 5-Minuten-Takt = die Close-Alarm-Sperre.
⚠⚠ **DIE URSACHE: ALLE NEUN AUSLÖSER WAREN AUS** — Auto-Squeeze (über
`runtime_state.json`, unbemerkt), Auto-Signal, S/R-Auto-Close, Flip-Close,
15-Minuten-Regel, Circuit-Breaker und die **drei** Notfall-Stop-Modi. **Jede
Abschaltung war für sich begründet und gemessen; zusammen ergaben sie eine
Stille, die niemand beschlossen hatte.** Übrig blieb genau ein Live-Trigger, der
🔔 Close-Alarm — und der braucht eine **gerichtete** Empfehlung, die es nur in
**5 %** der Zeit gibt (letzte 7 Tage: 508 von 9.976 Zeilen).
⚠ Dazu kam: der Trade-Abschluss-Push war früher bewusst entfernt worden. Deshalb
kam auch der **−87-€-SL vom 10.08. wortlos**.

✅ **GEBAUT: Close-Benachrichtigung `engine._check_close_notify`**
(`[telegram] close_notify_min_eur = 50`, 0 = aus; Backup
`oil_widget_config.ini.bak-2026-08-11-closenotify`). Feuert bei **jedem**
geschlossenen Trade ab der Schwelle — **hängt an KEINER Automatik**, also auch
bei manuellem Close und Broker-SL. Meldet Betrag, Richtung, Lots, Ein-/Ausstieg,
**Schließgrund** und Dauer. Snapshot-Feld `close_notify_min`.
⚠ **Schwelle aus den Daten gewählt, nicht geraten:** bei ~17 Trades/Tag liefert
50 € rund **2,4–6,1 Meldungen/Tag** und erfasst **54–80 %** des bewegten Geldes.
⚠ **Eine absolute Euro-Schwelle altert mit der Positionsgröße** — Median |P&L|
über 30 Tage 14,65 €, über die letzten 7 Tage 35,80 €. Bei wachsendem Konto
kommen mehr Meldungen. Bewusst so: ein Mensch denkt in Euro.
⚠⚠ **Der Unterschied zum entfernten Push ist die SCHWELLE** (der alte feuerte bei
jedem Close), und sie liegt in der **Engine**, nicht in der DB-Schicht.
⚠ **Vorgemerkt statt sofort gesendet:** der Flat-Zustand kommt aus
`trader.snapshot()` und ist dem DB-Schreiber gelegentlich einen Tick voraus —
`exit_time IS NULL` heisst „noch nicht fertig", NICHT „kein Trade". Ohne diese
Unterscheidung meldete es einen Close **ohne P&L**. Aufgabe nach ~30 s.
✅ **7 Tests** (`tests/test_close_notify.py`, Netz abgefangen) + **Mutationsprobe
in beide kritischen Zweige**: Schwelle entfernt → `test_unter_schwelle` fällt ·
Retry-Fall als „kein Trade" behandelt → `test_db_noch_nicht_fertig` fällt.

✅ **AUTO-SQUEEZE WIEDER AN** (User-Entscheidung 2026-08-11) — über den offiziellen
Toggle `POST /api/autosqueeze`, damit ini UND `runtime_state.json` übereinstimmen
(`{"ok":true,"enabled":true}`). Der Config-Wächter meldet ihn seitdem nicht mehr.
⚠ Damit läuft die **B5-Abbruchregel** weiter (11/20 Trades) — ohne laufenden
Trader wäre sie nie abgelaufen. Latte unverändert: Verhältnis < 1,0 ODER PF < 1.

## 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/bars` → **404**, 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,90–77,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._verdict` → `verdict` 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 (Mai–Jun)
  perfekt monoton (conf 85–100: WR 68 %/ØR +1,13; <55 klar negativ), **H2 (Jun–Jul)
  komplett INVERTIERT** (conf 85–100: 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` = (ask−bid)/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,32–0,50 = Falle, 15–17 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_stats` → `snapshot.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 M1–M30), 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 0–100 % 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 40–43 %
  (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,66–0,99. Drei Befunde:
    (a) **Trefferquote 37–43 %** über 1.200–2.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,68–0,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 0–19 % (n=1234, über die Hälfte!) sagt **8 % voraus, real 39,1 %**; Bucket
  65–100 % 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 38–40 %**, 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 23–25 %, real 40–42 %" 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(level−entry)/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: 2–4 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=82–139; 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 8–15 %.
  ⚠⚠ **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 (41–43 %)". 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 ~94–97 % 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,52–0,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 2–4 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,499–0,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); **45–55 % → 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,99–76,77 · 60′ 75,82–76,94 · 120′ 75,55–77,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 **~3–5 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 35–40 %). **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 40–45 %, Ø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 13–38 %**
  — 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 (13–38 % = Muster
  sagen den Kursverlauf kaum voraus).
- **MQL5-Chart-Erweiterungen 2026-07-30 (Indikator v1.16–v1.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, 2–40 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.
  - **⚠⚠ ERÖFFNUNGSLÜCKEN (`close_open`) NACHGEMESSEN = AUCH KEIN MAGNET
    (`backtest_gaps_closeopen.py`, 2026-08-11, User: „vielleicht will der Kurs
    erst die Kurslücke schliessen?").** Die Frage war **berechtigt offen**: der
    Befund darüber beruht auf der **`vacuum`**-Definition, und `core/gaps.py`
    sagt ausdrücklich, dass nur die gemessen wurde („wer `close_open` einbeziehen
    will, misst vorher"). Die Klasse, die ein Mensch „Kurslücke" nennt
    (Schluss → Eröffnung, typisch die Wochenendlücke), war **nie geprüft**.
    124 Lücken ≥0,50 $ über 4.359 D1-Bars, 2 Hälften, 95-%-Bootstrap.
    ⚠⚠ **Die Füllquote sieht überzeugend aus — und ist nichtssagend:**
    | Horizont | H1 Füllung / **Kontrolle** | H2 Füllung / **Kontrolle** |
    |---|---|---|
    | 3 Tage | 71,0 % / **69,4 %** | 74,2 % / **74,2 %** |
    | 5 Tage | 80,6 % / **77,4 %** | 75,8 % / **75,8 %** |
    | 10 Tage | 88,7 % / **83,9 %** | 80,6 % / **80,6 %** |
    | 20 Tage | 90,3 % / **87,1 %** | 83,9 % / **87,1 %** ⚠ Kontrolle höher |
    ✅ **Die Kontrolle ist der ganze Befund: dieselbe Distanz, dieselbe Bar, aber
    GEGENrichtung.** Damit sind Volatilität, Regime und Zeitraum automatisch
    gleich — übrig bleibt nur die Frage, ob die Lücke eine RICHTUNG bevorzugt.
    In H2 ist sie bei 3/5/10 Tagen **exakt identisch**. **Alle 24 Differenz-KI
    enthalten die Null**, die vorab fixierte Regel ist in keiner Zelle erfüllt.
    Kontrolle B bestätigt: an Nicht-Lücken-Tagen läuft der Kurs dieselbe Strecke
    binnen 10 Tagen zu **73,5 % nach unten und 75,5 % nach oben** — die ~75–80 %
    sind schlicht die Basisrate eines 1–2-$-Laufs in WTI, keine Eigenschaft der
    Lücke. ⚠ **GROSSE Lücken ≥1,0 $ kippen zusätzlich das Vorzeichen** zwischen
    den Hälften (H1 10/20 Tage: Kontrolle **schlägt** die Füllung 81,8/72,7 bzw.
    86,4/72,7; H2 umgekehrt) — bei n=22/23 das klassische Rauschmuster.
    ⚠ **Fairerweise der einzige konsistente Punkt:** die **Ein-Tages**-Füllung
    („Lücke wird am selben Tag zugehandelt") ist in beiden Hälften UND beiden
    Läufen richtungs-positiv (+4,9/+9,7 bzw. +4,6/+21,7 Pp). Zwei gleichgerichtete
    Hälften sind mehr als eine — „KI enthält 0" heisst **nicht widerlegt, sondern
    nicht belegt**. Für einen Eingriff reicht es bei n=22–62 nicht.
    ✅ **Dritte Bestätigung derselben Lehre**, nach Vakuum-Gaps und Fibonacci:
    die Frage ist nie „füllt/hält es?", sondern **„häufiger als eine willkürliche
    Linie gleicher Distanz?"**. Ohne diese Kontrolle misst man nur, dass Kurse
    sich bewegen. **Keine Änderung** — `close_open` bleibt reine Anzeige und
    fliesst weiterhin NICHT in `zone_lines()`.
    ⚠ **Der Anlassfall entschied sich noch während der Messung dagegen:** die
    Lücke vom 10.08. (Füllung 77,383) war am 11.08. nicht geschlossen — der Kurs
    stand bei **82,849**, also **5,47 $ in der Gegenrichtung**. Ein Einzelfall
    belegt nichts, aber er illustriert den Mechanismus des Ursprungsbefunds: das
    Gap blieb offen, **weil** der Trend von ihm weglief.
- **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 48–51 %
  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` (48–51 %
  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 47–50 %; die eigene Schwelle dort lautet **„< ~3 bp
  ist von den Kosten gefressen"** — gemessen sind es 0,2–1,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=15–30 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_votes`** — `log_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.py` — **idempotent**, 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=169** (in `web/index.html` hochzählen, siehe Workflows).
- **✅ SCHRIFT SKALIERT MIT DEM FENSTER (2026-08-13, v=167).** User: „die Größe der
  Schriftarten im Dashboard auf em einstellen, damit auf jedem Gerät oder je nach
  Fenstergröße die Schriftgröße automatisch angepasst wird."
  ⚠⚠ **Umgesetzt mit `rem` + fluider WURZEL, NICHT mit `em` — der Wunsch war
  richtig, die genannte Einheit nicht.** `em` ist relativ zum **Eltern**element und
  reagiert auf die Fenstergröße **gar nicht**; verschachtelt multipliziert es sich
  sogar (ein `h2` mit 1,2em in einer Karte mit 1,1em landet bei 1,32×). `rem` hängt
  dagegen an genau EINER Stellschraube, und die wird fluide gemacht:
  ```css
  html { font-size: clamp(15px, 14px + 0.5vw, 22px); }   /* v=168, eine Stufe kleiner */
  ```
  ⚠⚠ **EINE STUFE KLEINER am selben Tag (v=168, User: „verkleinere alle
  Schriftarten um 1em").** Wörtlich wäre `1em` = **100 % = null** — gemeint ist
  also eine Stufe. Genommen wurden **−2px**, exakt der Schritt, in dem die Größe
  am 24.07. zweimal HOCHgesetzt wurde; das Handy landet damit auf dem
  Zwischenstand von damals (~16px).
  | Fenster | Wurzel v=167 | **Wurzel v=168** | Basistext | klein (11px) | groß (26px) |
  |---|---|---|---|---|---|
  | 360px | 17,80 | **15,80** | 15,8 | 9,7 | 22,8 |
  | **390px (Handy)** | 17,95 | **15,95** | **15,9** | **9,7** | **23,0** |
  | 768px | 19,84 | **17,84** | 17,8 | 10,9 | 25,8 |
  | 1440px | 23,20 | **21,20** | 21,2 | 13,0 | 30,6 |
  | ab 1600px | 24,00 | **22,00** (Deckel) | 22,0 | 13,4 | 31,8 |
  ✅ **Das war eine EINZIGE Zeile** — die 96 rem-Werte blieben unberührt, alle
  Größenverhältnisse untereinander ebenso. Genau dafür wurde umgestellt: der
  Bump am 24.07. musste noch 96 Einzelwerte anfassen und übersah dabei drei.
  **Eine Stufe = 2px in den beiden Zahlen** (Minimum und Steigungs-Basis), der
  Deckel wandert mit. Zurück auf die 18er-Basis: `clamp(17px, 16px + 0.5vw, 24px)`.
  ⚠ Der ursprüngliche Anker (v=167) war bewusst so gesetzt, dass am Handy
  **nichts** kleiner wird (17,95 gegen 18,00px) — der User hatte die Größe am
  24.07. zweimal selbst hochgesetzt, und eine „Responsive-Umstellung", die das
  still eindampft, wäre eine Rücknahme seiner Entscheidung gewesen. Die
  Verkleinerung auf v=168 ist dagegen **ausdrücklich beauftragt**.
  ✅ **Umrechnung verlustfrei belegt**, nicht behauptet: alle **96** Deklarationen
  gegen die 18px-Referenz zurückgerechnet, größter Fehler **0,0008 px**.
  ⚠ Die drei `font:<weight> Npx/…`-**Shorthands** (`.ms-chip`/`.ms-bos-lbl`/
  `.sqm-badge`) sind **mit** umgestellt — sie waren beim Bump 2026-07-24 zuerst
  übersehen worden, weil ein Skript nur `font-size` erfasste. Prüfsumme: 93
  `font-size` + 3 Shorthand = 96, ausgeliefert **0** px-Schriftgrößen.
  ✅ **Zwei Vorbedingungen geprüft, nicht angenommen:** (a) `<meta name="viewport"
  content="width=device-width…">` ist gesetzt — ohne ihn würde `vw` am Handy den
  Layout-Viewport (~980px) messen und die Schrift wäre dort riesig; (b) die beiden
  Media-Queries (360/560px) stehen in **px** und verschieben sich damit **nicht**
  mit der Schrift. Wären sie in `em`, würde ein größerer Text die Breakpoints
  wandern lassen — die klassische Falle.
  ⚠ **ABSTÄNDE bleiben in px** (Padding/Margin/Grid). Sie mitzuskalieren wäre ein
  weit größerer Eingriff (Karten, Grid, Trade-Leiste) mit echtem Layout-Risiko; die
  Anfrage betraf die Schrift. Folge nach der Verkleinerung: die Schrift sitzt jetzt
  etwas **lockerer** in den unveränderten Polstern (bei v=167 war es umgekehrt —
  auf breiten Fenstern wirkte der Text dominanter). Wer das nachziehen will, muss
  Padding/Margin ebenfalls auf `rem` umstellen — nicht gemacht.
  ⚠ Die clamp-Grenzen stehen in **px**, nicht in `rem` — dadurch ist die Anzeige
  vorhersagbar, respektiert aber die im Browser eingestellte Basis-Schriftgröße
  nicht. Bewusst so, weil der User die Größe zweimal selbst kalibriert hat.
  **Zurück:** `web/style.css.bak-2026-08-13`. Größe global ändern = **eine** Zeile
  (die `clamp`), nicht mehr 96 Einzelwerte — genau das war die Schwäche vorher.
  ⚠ Chart-`fontSize` in `app.js` gibt es **nicht mehr** (mit dem Charts-Tab am
  05.08. entfallen) — der alte Warnhinweis dazu ist damit gegenstandslos.

## 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_sltp` →
`trader.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,5–3,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 h** → `open_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_room`↔`breakout_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 **Artefakt** — `backtest_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,5–3,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 Quelle** — `core/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:30–16: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 0–7 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** `(bull−bear)/total` — **ohne 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|≥0,5).
⚠ 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 (Jan–Aug) 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.
⚠⚠ **PREIS-ARGUMENT ÜBERHOLT (Stand 2026-08-17, nachrecherchiert).** Hier stand
„Basic ≈ 200 $/Monat, Pro ≈ 5.000 $ … ~25 % des Kontos". Seit **Februar 2026** ist X
auf **pay-per-use** umgestellt: **0,005 $ je gelesenem Post** (Deckel 2 Mio./Monat),
die alten Abo-Stufen sind für Neukunden geschlossen. 300 Posts/Tag wären ~45 $/Monat.
**Der Kosten-Einwand trägt damit nicht mehr — der MESSUNGS-Einwand schon.**
(Scraping bleibt ToS-Verstoß.) 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
**42–90 %** gerichtet, ab **21.07.** nur noch zu **1,5–9,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,68–0,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 %";
45–55 % 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 13–38 %. 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,70–0,99) — es ist eine Wahl zwischen Verlusten, kein Edge;
(b) das Optimum lag am Rand des Suchbereichs → Gegenprobe bis k=3,0 zeigt **kein
sauberes Optimum** (H2: −8 → −17 → −20 → −19, H1 schwankt) — es ist also weder
reine Handelsvermeidung noch ein echter Bestwert; (c) gemessen wurde das
**UNGEGATETE** Signal (ohne min_conf, HTF-Filter, Entry-Raum), live laufen die
davor. **Nächster Schritt wäre eine GEGATETE Neumessung**, bevor ein Live-Gate
angefasst wird, das 63 % der WARTEN-Zustände erzeugt.

**Phase 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,70–1,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:30–16: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_pct` →
`config.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 **1–99 %** (`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_analysis` → `trader.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 (~2–3 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 |
|---|---|---|---|
| 0–19 | 56 | 15,1 % | **53,6 %** |
| 20–34 | 199 | 29,8 % | 43,2 % |
| 35–49 | 414 | 42,2 % | 56,8 % |
| 50–64 | 180 | 54,1 % | 61,7 % |
| **65–100** | 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=122–276). 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 **3–5 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
(45–55 % 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,30–0,50 | +0,142 | +0,216 |
| 0,75–1,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,00–0,10 | −0,227 | +0,100 |
| 0,10–0,20 | −0,130 | −0,115 |
| 0,20–0,30 | −0,306 | −0,129 |
| 0,30–0,50 | −0,112 | **+0,216** |
| 0,50–0,75 | −0,039 | +0,042 |
| 0,75–1,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,06–0,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,16–0,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 48–51 % 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 | 22–56 von 92 (**24–61 %**) |
| 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 48–51 % 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`.

### ⚠⚠ GATE ④ ZEIGTE DAS ZIEL IN DER VERBOTENEN RICHTUNG — behoben 2026-08-10

**User-Fund:** „Kurs ist ca. 79 und Ziel ist 78,7 bei Empfehlung LONG — ein
Widerspruch?" **Ja, und zwar ein echter.** Live nachgestellt:
| | |
|---|---|
| Kurs (bid) | **78,918** |
| ② Bias | `verboten: SHORT` → erlaubt ist **LONG** |
| ④ Ziel | **78,768** — 0,15 $ **unter** dem Kurs, `von: 79,033` |

Gelesen heisst das „am Widerstand einsteigen, Support anlaufen" = ein **SHORT**.
⚠ **Die Arithmetik war korrekt — nur für die verbotene Richtung.** Ursache war
eine Zeile:
```python
fern = max(zonen, key=lambda x: x["gap_atr15"])   # rein geometrisch
```
Das Ziel war schlicht das **entferntere** der beiden Level, ohne jede
Richtungsprüfung.

⚠⚠ **Der strukturelle Grund ist der wichtigere Teil: zwei unvereinbare
Handelskonzepte in einer Kette.** Gate ④ unterstellte stillschweigend einen
**Fade-Trade** (Level anlaufen, Gegenlevel als Ziel = Mean Reversion), Gate ② ist
aber ein **Trendfilter**. Damit ist es **derselbe Fehler, wegen dem die
Gesamtempfehlung entfernt wurde**: aus einem **VERBOT** wird über die Hintertür
wieder eine **RICHTUNG** — hier nicht über verrechnete Stimmen, sondern über die
Geometrie. Und es ist der zweite Fall in dieser Karte (der erste war der
Richtungs-Bias, korrigiert 08.08. nach 13 LONG-Episoden an einem fallenden Tag).
**Ein Verbot lässt sich offenbar auf mehr Wegen in eine Richtung verwandeln, als
man beim Bauen sieht.**

✅ **Fix — die gemessene Population ist der Maßstab.** `backtest_m15_gates.py`
misst: Einstieg am **nächstgelegenen** Level, Richtung = die vom HTF **erlaubte**;
der Exit ist der kanonische Trailing-Exit — **ein Level-Ziel war NIE gemessen**
(④ ist ausdrücklich „reine Arithmetik"). Jetzt gilt:
| Lage (LONG erlaubt) | Anzeige |
|---|---|
| nächstes Level = **Support** → Abpraller | Ziel = Widerstand darüber ▲ |
| nächstes Level = **Widerstand** → Ausbruch | **kein Ziel** + Grund |
| **kein Veto** (M30 flach) | **kein Ziel** — ein einzelnes wäre wieder Richtung |
⚠ **„Kein Ziel" ist ein eigener Zustand, keine Lücke** — die Karte nennt den
Grund, statt eine Zahl zu erfinden. Beim Ausbruchs-Einstieg ist das nächste Ziel
schlicht **nicht berechenbar**: `_draw_levels` liefert bewusst nur die nächste
Linie je Seite (Entscheidung 14.07., sonst tauchen beim Kurslauf laufend Linien
auf/ab).
⚠ Die **Richtung steht jetzt IM Text** (`④ Ziel ▲ 79,033 … Einstieg 78,768`).
Ein Ziel ohne genannte Richtung lässt sich still wieder verdrehen — mit genannter
nicht. Neue Felder: `ziel.richtung`, `ziel_grund`.

✅ **Mutationsprobe bestanden** (ein grüner Test belegt sonst nichts): mit der
alten Zeile zurückgebaut fallen **4 von 6** Tests, und die Fehlermeldung
reproduziert den Fund wörtlich (`{'level': 78.768, … 'von': 79.033}`).
⚠ Die zwei übrigen bleiben **korrekterweise** grün — dort liegt das entferntere
Level tatsächlich in der erlaubten Richtung, alt und neu stimmen überein. Ein
Test, der auch die richtigen Fälle rot färbt, wäre zu eng gefasst.
`tests/test_m15_ziel.py` (6 Tests) prüft die Invariante **„ein ausgewiesenes Ziel
liegt IMMER in der erlaubten Richtung"** über 32 Konstellationen × 2 Richtungen.
✅ **Live beide Zweige belegt**, weil der Kurs während der Prüfung lief: bei
bid 78,918 (nächstes = Widerstand) → kein Ziel + „Ausbruch"; bei bid 78,871
(nächstes = Support) → Ziel 79,033 ▲, Einstieg 78,768.

⚠ **Nebenbei behoben: `deploy.py --feld` konnte nur die oberste Snapshot-Ebene**
(`a.feld in snap`). Für `m15_setup.ziel_grund` war der Neuheits-Beweis damit gar
nicht führbar — und Schritt 4 ist der EINZIGE belastbare Nachweis, dass neuer
Python-Code läuft. Jetzt mit **Punktpfaden**; ein Feld mit Wert `None` zählt als
vorhanden (geprüft wird der Schlüssel, nicht der Inhalt).

### ⚠⚠ CHART UND M15-KARTE NANNTEN FÜR DASSELBE LEVEL VERSCHIEDENE ZAHLEN (Fix 2026-08-13, v1.35 / v=169)

User: „die Beschriftung für S/R im Chart stimmt nicht mit der Analyse in der
M15-Karte überein."
✅ **Die LEVEL stimmten — nur die Zahl daneben nicht.** Live gegengeprüft: die
CSV, die der Indikator liest, enthielt `R;82.069;69`, und Gate ③ der Karte
meldete `nah: res 82.069, p_break 69`. Beide kommen aus `_draw_levels()`, sind
also per Konstruktion identisch. Auch die Distanzen sind exakt gerechnet
(nachgerechnet 0,477≈0,48 · 0,909≈0,91 · 0,455≈0,45).
⚠⚠ **DER DEFEKT: das Chart schrieb die NACKTE Zahl, die Karte klappte still auf
den Gegenwert um.**
| P(Bruch) | Chart (v1.34) | M15-Karte (`app.js` `lesart()`) |
|---|---|---|
| 33 % | `Widerstand 82.069 · **33%**` | `Widerstand 82.069 → **67 % Abprall**` |
| 46 % | `… · **46%**` | `… → **unentschieden**` |
| 72 % | `… · **72%**` | `… → **72 % Bruch**` |
Beide Zahlen waren **richtig** — die eine P(Bruch), die andere P(Abprall) — aber
die Chart-Zahl sagte nicht, WAS sie ist. **Gemessen betraf das 88,1 % der Zeit**
(1.797 Vorhersagen/14 Tage: 66,3 % lagen unter 45 %, 21,9 % im unentschiedenen
Band; nur 11,9 % über 55 %, wo beide dieselbe Zahl zeigen).
✅ **Behoben im Indikator (v1.35), wortgleich zu `app.js`:** `69% Bruch` /
`unentschieden` / `67% Abprall`. Über **1.678 echte** P(break)-Werte gegengeprüft:
Übereinstimmung **0 % → 100 %**.
⚠ Die Formulierung lebt jetzt an ZWEI Stellen (MQL5 + `app.js`) — wer eine
ändert, muss die andere mitziehen; beide tragen einen Hinweis darauf. Ein
gemeinsamer Text aus dem CSV wäre sauberer, hätte aber das Format gebrochen.
⚠ **Kompiliert ins RICHTIGE Terminal** (der dokumentierte Fallstrick): es gibt
real **zwei** Ordner, nur `D0E8209F…` bekommt die `sr_levels.csv`
(`0BA5CADA…` nicht). `MetaEditor64.exe /compile` → **0 errors, 0 warnings**,
`.ex5` frisch, `#property version "1.35"` im Terminal gegengeprüft.
✅ **Zwei Verdachtsmomente ausgeschlossen, statt sie zu vermuten:** (a) eine
stehengebliebene Alt-Linie — der Indikator ruft `ObjectsDeleteAll(0, PFX)` bei
jedem Redraw; (b) ein falsch einsortiertes Level — `_draw_levels` wählt seit dem
Fix vom 23.07. nach **Lage zum Kurs**, nicht nach Entstehungstyp. ⚠ Nebenbefund:
82.069 stammt aus `pl` (einem Pivot-**Tief**) und wird korrekt als Widerstand
geführt, weil der Kurs darunter steht — das sieht im Chart nur ungewohnt aus.

**ZWEITER, unabhängiger Befund derselben Klasse — behoben:** Gate ③ und ③b
nannten Level aus **verschiedenen Zeitebenen**, beide nur als „×ATR":
| | Level-Quelle | ATR | im Chart gezeichnet? |
|---|---|---|---|
| **③** | M30-Pivots (`_draw_levels`) | **ATR_M15** | **ja** |
| **③b** | M5-Pivots (`room_info`) | **ATR_M5** | **nein** |
Real standen damit „0,48×ATR" und „0,45×ATR" nebeneinander, die **nicht
vergleichbar** sind — und die Karte nannte einen „Support 81,78", den man im
Chart vergeblich sucht. ⚠ Verschärfend: zum Prüfzeitpunkt lagen **alle 10
M30-Pivots über dem Kurs**, es gab also gar keine Support-Linie im Chart.
✅ Die Quellen-Trennung **bleibt** — `entry_room_atr=0,6` ist über 80k Bars auf
M5 kalibriert, sie umzustellen wäre eine Strategie-Änderung. Geändert wurde nur
die Beschriftung: `③ Widerstand M30 82.069 (Chart · 0,48×ATR_M15)` gegen
`③b … Raum ▲ 0,91×ATR_M5 / ▼ 0,45×ATR_M5`. Die Tooltips nennen den Unterschied
ausdrücklich. **Reine Anzeige — an Gates, Gewichten und Order-Logik nichts.**

### ⚠⚠ M15-KARTE AUTOMATISCH HANDELN = FÄLLT DURCH, und die Lesart ist schlechter als ihr eigenes Gegenteil (`backtest_m15_auto.py`, 2026-08-13)

User: „messe ob wir den Autotrader bei M15-Empfehlung automatisch traden lassen
können." Track-B-Frage (Signal), also hohe Beweislast; Regel **vorab** fixiert.
⚠ **Neu gemessen wurde genau EIN Zusatz.** Die Karte ist als Bedingungs-Anzeige
gebaut und hat keine Richtung — die entsteht erst aus der **`lesart`** (aus
P(break)), und die war als Handelsregel nie geprüft. `backtest_m15_gates.py`
(08.08.) handelt die vom **HTF erlaubte** Richtung, nicht die Lesart.

**80k M5-Bars, 2 Halbjahre, echte `core.sr_levels`/`_p_break`-Gewichte, Status +
Lesart inkl. der Hysterese vom 12.08., kanonischer Exit, Echtkosten, Fill bei
BERÜHRUNG, sequentiell EIN Slot, 1× je Episode:**
| Variante | H1 ØR / PF | H2 ØR / PF |
|---|---|---|
| **A) Karte (Lesart)** | **−0,162 / 0,72** | **−0,094 / 0,84** |
| B) Lesart + Veto-konform | −0,104 / 0,81 | −0,051 / 0,91 |
| **C) KONTROLLE HTF-Richtung** | **−0,085 / 0,85** | **−0,030 / 0,95** |
| D) KONTROLLE Gegenrichtung | −0,070 / 0,87 | +0,007 / 1,01 |
⚠⚠ **Die schärfste Zeile ist der Vergleich A gegen C und D: die Lesart ist in
BEIDEN Hälften schlechter als die HTF-Richtung UND schlechter als ihr eigenes
Gegenteil.** Alle vier Varianten laufen auf **denselben** grünen, gerichteten
Momenten — variiert wird nur die Richtung. Die Lesart zieht damit nicht bloß
nichts bei, sie **kostet** (−0,077 bzw. −0,064 ØR gegenüber C).
⚠ Und A ist nicht „unbelegt", sondern **belegt negativ**: das 95-%-KI schließt
in beiden Hälften die Null aus — [−0,222 … −0,099] und [−0,152 … −0,038].
✅ **Nachbar-robust in die falsche Richtung:** gap 0,40 / 0,50 / 0,60 sind alle
beidhälftig negativ und werden **monoton schlechter**, je weiter die Schwelle.
Kein Rauschmuster, sondern ein konsistenter Mechanismus.
✅ **Unabhängige LIVE-Gegenprobe** (`m15_states`, Selbstmessung seit 08.08.,
3.678 ausgewertete Zustände): der **handelbare** Zustand (grün + gerichtet)
trifft **entkoppelt 46,8 %** auf 30 min [37,9 … 55,6] und **42,6 %** auf 60 min
[33,6 … 51,6] — beide unter dem Münzwurf, beide KI enthalten 50. ⚠ Roh sähe es
mit 51,5 % besser aus; die Entkopplung (1.793 → 124 unabhängige Fälle) ist hier
der ganze Unterschied. Backtest und Live zeigen also in **dieselbe** Richtung.
⚠⚠ **MECHANISMUS — es ist zum VIERTEN Mal derselbe Fehler dieser Karte:** aus
einer Aussage über die **Struktur** („hält dieses Level?", P(break), AUC 0,65)
wird über die Hintertür eine **Richtungsprognose** („Level hält → Kurs fällt").
Das ist eine Mean-Reversion-Annahme am Extrem — und antizyklische Einstiege am
Level sind im Projekt mehrfach als Münzwurf gemessen (P(break) taugt zum
**EXIT**, nicht zum **ENTRY**, 6× belegt). Dazu kommt, dass das Modell live
~7 Pp fehlkalibriert ist. Die drei Vorgänger: Code (08.08.), Geometrie (10.08.),
Lesart im Text (10.08.) — jetzt die Lesart als Handelsregel.
✅ **URTEIL: NICHT bauen.** Bedingung (1) und (4) verfehlt. ⚠ Und **kein
Umschwenken auf C oder D**: die sind nur *weniger schlecht*, ebenfalls negativ
(D ist in H1 −0,070 mit KI ohne Null). Es gibt hier nichts zu handeln, nur etwas
zu lassen. Die Karte bleibt, was sie ist — eine **Bedingungs**-Anzeige.
⚠ Grenzen, ehrlich: eine Schwung-/Level-Definition; die Sim modelliert **keine
Exit-Slippage** (die würde alle Varianten weiter senken, A am stärksten, weil es
am häufigsten handelt); und der Fill am Level ist für den Ausbruchs-Fall leicht
optimistisch (eine Stop-Order löst knapp jenseits aus).
⚠ **Nebenbefund für später:** die Karte steht in **6.035 bzw. 7.329 Episoden**
grün UND gerichtet — sie ist als Auslöser also alles andere als selektiv. Wer
sie schärfen will, setzt dort an, nicht an der Richtung.

### ⚠⚠ DER ENTRY IST REPARIERT — DER DEFEKT IST IN DEN EXIT GEWANDERT (Zwischenstand 2026-08-13)

Erhebung auf die Frage „wie erreiche ich das Ziel Autotrading?". **Nichts
geändert**, aber die Diagnose verschiebt sich, und das ist der Punkt.
✅ **Der Pending-Umbau vom 05.08. hat gewirkt** (`analyze_squeeze_entry_gap.py`):
von 8 Bot-Trades seit dem Umbau füllten **7 per ruhender Order** (keine
Market-Log-Zeile); der Market-Fallback ist praktisch weg — **n=1 bei +0,148×ATR**,
also innerhalb der Nachjagd-Bremse (0,20). Vorher lag der Live-Median bei
**+0,275×ATR** über dem Level. **Deployment-Drift Fall 7 ist damit geschlossen.**
⚠⚠ **Und trotzdem verliert der Squeeze — die Ursache ist also eine andere
geworden.** B5-Stand (ab 01.08., n=11/20): **Trefferquote 73 %**, aber
**Ø-Gewinn 13,62 € gegen Ø-Verlust 94,15 € = Verhältnis 0,14** (Latte 1,0),
**PF 0,39** (Latte 1,0), Summe **−173,46 €**.
⚠ Die Signatur ist im Trade-Protokoll direkt ablesbar: **drei der sieben
Pending-Trades wurden per `sr_close` bei +0,26 / +2,88 / +0,28 € geschlossen**,
während die Verlierer bis zum vollen Stop liefen (−43 / −64 / −175 €).
**Der Squeeze lebt gemessen von LÄUFERN** (43 % Treffer, Ertrag im Tail) — genau
die werden gekappt.
⚠⚠ **Die alte Entlastung des S/R-Closes ist NICHT mehr bindend.**
`backtest_squeeze_srclose.py` (24.07.) maß auf Squeeze-Entries einen **Wash**
(+0,190→+0,224 / +0,394→+0,377) — aber mit dem **alten** P(break)-Modell und
Schwelle **0,55/0,60**. Heute läuft das am 31.07. **nachtrainierte** Modell mit
Schwelle **0,35**, und das schließt **94–97 %** aller Level-Berührungen. Gemessen
unter X, betrieben unter Y — dasselbe Muster wie die acht dokumentierten Fälle.
➜ **NÄCHSTE MESSUNG (billig, hoher Hebel, noch nicht gelaufen):** Squeeze-Trades
**mit gegen ohne** S/R-Auto-Close unter dem HEUTIGEN Modell und 0,35.
⚠ **Kontext, der zur Ehrlichkeit gehört** (seit 01.08., je Lot normiert, weil
absolute Euro bei wechselnder Größe ungültig sind):
| | n | Σ P&L | **je Lot** |
|---|---|---|---|
| **AUTONOM** (`SQUEEZE_*`) | 11 | −173,46 € | **−5,85** |
| **MENSCH** (alles übrige) | 146 | +994,59 € | **+3,20** |
⚠ Die `setup`-Spalte taugt **NICHT** als Bot/Mensch-Trennung — `engine._open`
schreibt die Wellenlage auch bei manuellen Trades mit (`WAVE`, n=124). Nur
`SQUEEZE_*` ist autonom eröffnet. Ein erster Auswertungsversuch hat genau das
verwechselt und dem Bot 152 Trades mit +730 € zugeschrieben.
⚠ **Zustand beim Erheben:** `auto_squeeze=False` (User 11.08. 14:10) — damit
läuft die B5-Regel **nie ab**, der schlechteste Zustand. `auto_sr_close=True`,
Circuit-Breaker **aus** (`limit_pct=0`), Sizing 95 % Margin, Balance 1.279 €.
Bei Ø-Verlust 94 € sind das **7,4 % des Kontos je Verlusttrade**.

### ✅⚠ „DER S/R-CLOSE KAPPT DIE SQUEEZE-LÄUFER" = WIDERLEGT (`backtest_squeeze_srclose2.py`, 2026-08-13)

Meine eigene Hypothese vom selben Tag (drei von sieben Pending-Trades per
`sr_close` bei +0,26 / +2,88 / +0,28 € beendet) — **gemessen falsch, und zwar
doppelt.**

**(1) BACKTEST** (80k M5, 2 Halbjahre, Einstiege über den GETEILTEN
`squeeze_scan.scan(on_entry=…)`, ruhende Order am Level mit Fill bei Berührung,
kanonischer Exit, Echtkosten, EIN Slot; identische Entry-Liste über alle
Varianten, variiert wird NUR der Exit):
| Variante | H1 ØR / PF | H2 ØR / PF | Δ gegen ohne |
|---|---|---|---|
| **A) ohne S/R-Close** | −0,151 / 0,72 | −0,079 / 0,86 | — |
| **B) mit, p<0,35 (LIVE)** | **−0,119 / 0,76** | **−0,049 / 0,91** | **+0,032 / +0,030** |
| B) p<0,45 | −0,088 / 0,82 | −0,046 / 0,92 | +0,063 / +0,033 |
| B) p<0,55 | −0,086 / 0,82 | −0,038 / 0,93 | +0,065 / +0,041 |
**Der S/R-Close ist in BEIDEN Hälften BESSER als ohne** — die vorab fixierte
Regel sagt damit: **anlassen.** ⚠ Und die von mir vorgeschlagene „Abhilfe"
(Close erst ab Mindestgewinn) macht H1 **schlechter** (Δ −0,006 / −0,011 bei
0,5 bzw. 1,0×ATR) — der Eingriff hätte geschadet.

**(2) LIVE, 11 Squeeze-Trades ab 01.08. — die Zerlegung ist eindeutig:**
| Schließgrund | n | Σ | Ø |
|---|---|---|---|
| **`sl`** (Stop/Trailing) | 5 | **−198,17 €** | −39,63 |
| `sr_close` | 4 | **+15,98 €** | +4,00 |
| `manual` | 2 | +8,73 € | +4,37 |
⚠⚠ **Der verdächtigte Exit hat +15,98 € BEIGETRAGEN, nicht gekostet.** Und der
größte Gewinner des Zeitraums (+77,34 €) kam ebenfalls über `sl` — also über das
**Trailing**, nicht trotz ihm.
⚠ Die Größe steckt allein auf der Verlustseite: **−175,21 € bei 2,91 Lots** =
0,695 $ Bewegung — das ist der **planmäßige 2×ATR-Stop** bei dem ATR jenes
Moments, kein Ausreißer und kein Exit-Defekt. Ø-Verlust 94,15 € auf 1.279 €
Konto = **7,4 % je Verlusttrade**.
✅ **Damit zeigt der Hebel auf SIZING, nicht auf den Exit** — genau dorthin, wo
CLAUDE.md ihn ohnehin verortet („Der Hebel ist Sizing, nicht der Exit").

⚠⚠ **GRENZE, die nicht überspielt werden darf: die ABSOLUTEN Werte reproduzieren
den dokumentierten Kontrollwert NICHT.** `backtest_squeeze_touchfill.py` liefert
für denselben nominellen Aufbau **+0,124/+0,292** bei 1.092/1.807 Trades, hier
sind es −0,151/−0,079 bei 273/422. Die Referenz hat also **mehr Trades als ich
Einstiege habe** — ihre Auswahl ist lockerer (Fenster/Cooldown/Slot-Behandlung).
**Deshalb ist der Befund „der Squeeze ist negativ" hier NICHT belegt**; belastbar
ist allein der **relative** Vergleich, weil alle Varianten auf derselben
Entry-Liste laufen und sich nur im Exit unterscheiden. Wer die Absolutwerte
braucht, muss zuerst die Kontrolle reproduzieren (die Projekt-Regel, die
`backtest_verworfene_v2.py` vorbildlich befolgt).

⚠⚠ **EIGENER FEHLER auf dem Weg, dokumentiert weil es eine Fehlerklasse ist:**
der erste Lauf nahm den Modul-Default `squeeze_scan.ATRMIN_STD = 0,12`, während
live und die Referenz **0,06** nutzen (der Floor wurde gemessen gesenkt). Folge:
**H1 127 gegen H2 483 Einstiege** — der hohe Floor filtert die Niedrig-Vola-
Hälfte weg und erzeugt eine ganz andere Population. Aufgefallen nur durch die
Schieflage der Entry-Zahlen. Dieselbe Klasse wie das fehlende
`set_entry_room(0.6)` am 05.08.: **ein aufgerufenes Gate ist nicht dasselbe wie
ein aktives Gate; beim Wiederverwenden eines Kerns gehört die Live-KONFIGURATION
mitkopiert.** ⚠ Die Schlussfolgerung war in beiden Läufen dieselbe.
⚠ Zweiter eigener Fehler: der erste Entwurf hängte den S/R-Close an den
`stop_when`-Haken, der zum **Bar-Close** prüft — bei einem „am Level"-Fenster von
0,15×ATR ≈ 0,018 $ feuerte er fast nie (Δ ≈ 0,000), während er live 94–97 % aller
Berührungen schliesst. Live prüft den **laufenden** Kurs im Sekundentakt; das
Analogon ist die **Bar-Spanne**. Danach feuert er in 21–23 % der Trades (live
4 von 11 = 36 %, bei n=11 kein Widerspruch).

### ⚠⚠ SIZING AUF DER ECHTEN HISTORIE GERECHNET (`analyze_sizing_live.py`, 2026-08-13)

Folge der S/R-Widerlegung: der Hebel zeigt auf die GRÖSSE. Pfad-Simulation über
**1.278 geschlossene Trades (08.05.–13.08.)**, `pnl/lots` als größenunabhängiges
Ergebnis, Lots aus der jeweils simulierten Equity abgeleitet.
⚠ Zwei dokumentierte Zahlen stimmen nicht mehr und wurden **aus den Daten neu
geschätzt**: EUR je $ und Lot = **86,90** (notiert 87,6).
⚠⚠ **KORREKTUR MEINER EIGENEN KORREKTUR (2026-08-17, vom Konstanten-Wächter
gefunden):** hier stand „Margin je Lot **~405 €** (notiert 670)". **Die 405 waren
falsch.** Ich hatte sie aus `Balance × 95 % / Lots` abgeleitet — das unterstellt,
jede Position sei auf volle 95 % gesized. Der **exakte** Wert kommt vom Broker
(`position.margin / position.lots`) und beträgt **~713 €/Lot**; die ursprünglich
notierten 670 lagen also deutlich näher als meine „Korrektur".
**Lehre: eine abgeleitete Zahl ist keine gemessene.** Wo der Broker die Größe
selbst ausrechnet, gehört sie von dort geholt — `tools/check_konstanten.py`
macht das jetzt.

**(1) DER IST-PFAD IST DIE EIGENTLICHE BOTSCHAFT:**
| | |
|---|---|
| Start (rekonstruiert) | 1.115,70 € |
| **Hoch** | **2.724,16 €** (05.08.) |
| **Tief** | **261,36 €** (30.07.) |
| **Max-Drawdown** | **82,8 %** |
| schlechtester Einzeltrade | **−381,16 €** |
| Ende | 1.279,04 € |
⚠⚠ Das Konto hat **zweimal** den Großteil seines Werts verloren — −82,8 % bis
zum 30.07., danach vom Hoch am 05.08. nochmals **−53 %**. **42 von 71 Tagen sind
negativ**; getragen wird alles von wenigen Tagen (04.08. **+1.297 €**). Für
**unbeaufsichtigten** Betrieb ist das die entscheidende Zahl, nicht der Endstand.

**(2) VARIANTEN** (Ende / MaxDD / schlechtester Trade):
| Regel | Ende | MaxDD | schlecht. Trade |
|---|---|---|---|
| **IST** | 1.279 € | **82,8 %** | **−381 €** |
| Lot-Deckel 1,5 | 1.362 € | 82,8 % | −345 € |
| Risiko 1,5 %/Trade | 680 € | **54,4 %** | **−77 €** |
| Risiko 0,5 %/Trade | 960 € | **22,2 %** | **−27 €** |
| **IST + Breaker 8 %/Tag** | **2.018 €** | **59,2 %** | −381 € |
| IST + Breaker 5 %/Tag | 1.976 € | 65,5 % | −381 € |
✅ **Lot-Deckel bringen fast nichts** — die realen Lots lagen meist darunter.
✅ **Risiko-Sizing drittelt den schlechtesten Trade und halbiert den Drawdown**,
kostet aber Endstand. ⚠ **Confounder: die Risiko-Varianten laufen auf 1.118 der
1.278 Trades** (nur die mit `sl_at_entry`) — nicht direkt vergleichbar.
⚠⚠ **Der Breaker verbessert BEIDES** (Ende +739 €, DD −23,6 Pp) — aber:

**(3) ⚠⚠ EIN TAG TRÄGT 70 % DES BREAKER-VORTEILS.** Vermiedene Beträge je
Auslösetag: **07.08. +556,82** · 30.07. +200,48 · 24.07. +106,61 · 28.07. +81,24
· 23.07. +63,23 · Rest klein — und an **drei Tagen kostet er**: 11.08. −106,85 ·
29.07. −96,00 · 27.07. −50,77. Summe +800,19 € über 11 Auslösetage.
⚠ Ohne den 07.08. bleiben **+243 €** über 10 Tage. **Das ist kein Makel der
Messung, sondern das Wesen einer Versicherung** — ihr Wert sitzt im Tail. Aber
die +739 € sind eine **Realisierung**, keine Erwartung.
⚠⚠ **UND EINE GRENZE, die für HEUTE entscheidend ist:** die Sim sperrt nach dem
Auslösen **alle** Trades des Tages. Live sperrt `_check_circuit_breaker` nur die
**autonomen** (`_check_auto_squeeze`), manuelle Orders bleiben frei. Bei einem zu
94 % manuell gehandelten Konto misst die Sim damit eher eine **Disziplin-Regel**
als das heutige Code-Verhalten. **Für das Ziel Autotrading ist sie dagegen das
richtige Modell** — dort greift die Sperre wirklich.
⚠ Weitere Grenzen: EIN Pfad ist keine Verteilung (für die Ruin-Frage bleibt
`backtest_sizing.py` das Werkzeug: Margin-Sizing 36 % P(DD>80 %) — was der
gemessene IST-Pfad mit 82,8 % **bestätigt**); die Sim unterstellt dieselben
Trades bei anderer Größe, was bei **manuellen** Trades eine starke Annahme ist;
und der Breaker wird auf **realisierte** Tagesverluste geprüft, während er live
auch die offene Position kappt → die Sim **unterschätzt** ihn.

### ⚠⚠ ENGER STOP FÜR DIE ERSTEN N BARS = KEINE ÄNDERUNG (`backtest_sl_staged.py`, 2026-08-13)

User-Idee: „SL beim Eröffnen sehr eng, damit falsch laufende Trades früh
geschlossen werden; läuft er richtig, den SL etappenweise nachziehen."
⚠ Der **zweite** Teil ist längst gebaut (Phasen-Ratsche in `core/trailing.py`);
neu und ungemessen war allein der **zeitlich befristete** enge Stop. Nicht zu
verwechseln mit einem durchgehend engen Stop — der ist 2× gemessen
(`backtest_sl_width.py`, `backtest_sl_basis.py`).
✅ **Ohne Exit-Nachbau gerechnet:** Lauf A (`sl_atr=eng`) und Lauf B
(`sl_atr=weit`) laufen auf DEMSELBEN Pfad; innerhalb der ersten N Bars ist der
einzige Unterschied der SL-Abstand, jeder Nicht-SL-Ausstieg fällt identisch aus.
Also exakt `R = R_A wenn A vor/bei Bar N aussteigt, sonst R_B`. Keine Näherung,
keine fünfte Exit-Kopie.

**80k M5, Squeeze-Entries, ruhende Order am Level, kanonischer Exit, Echtkosten,
EIN Slot. Basis = durchgehend 3,4×ATR_M5 (= der Live-Stop 2,0×ATR_M15, aus 121
echten Trades als Median gegengerechnet):**
| Variante | H1 ØR / WR / worst | H2 ØR / WR / worst |
|---|---|---|
| **Basis (heute)** | −0,248 / 37,0 % / **−3,73** | −0,107 / 44,4 % / **−3,74** |
| N=3, eng 0,5 | −0,263 / 23,3 % / −3,66 | −0,043 / 30,4 % / −0,96 |
| **N=6, eng 0,5** | **−0,242** / 22,9 % / **−0,89** | **−0,045** / 30,1 % / **−0,96** |
| N=12, eng 0,5 | −0,240 / 22,9 % / **−0,83** | −0,044 / 30,1 % / −0,96 |
| N=6, eng 0,8 | −0,258 / 27,2 % / −3,15 | −0,066 / 35,0 % / −1,26 |

⚠⚠ **DER ENTSCHEIDENDE BEFUND IST STRUKTURELL: die Staffelung hat gar keinen
Raum.** Die Spalte „früh" zeigt, wie viele Trades schon in Phase 1 aussteigen —
bei **N=12: 269 von 279 (96 %)**. Die mittlere Haltedauer liegt bei 12–18 min
(2–4 M5-Bars); „die ersten 30–60 Minuten" sind also **der ganze Trade**. Wo die
Staffelung überhaupt etwas anderes tut als ein dauerhaft enger Stop (N=3), ist
sie in H1 bei **allen vier** eng-Werten schlechter. Wo sie „gewinnt" (N=6/12),
ist sie **kein gestaffelter Stop mehr, sondern ein permanent enger** — und der
war die Ausgangsfrage nicht.
❌ **Regel verfehlt.** Zwei Kombinationen schlagen die Basis nominell in beiden
Hälften (N=6/12 mit eng 0,5: Δ H1 **+0,007/+0,008**), aber (a) das liegt bei
n≈279 **weit** unter einem Standardfehler (~0,055), (b) die **Nachbarn kippen**
(N=3 gleiche Enge → −0,014; eng 0,8 → −0,009 in H1), (c) es ist der degenerierte
Fall. Der ganze Ertrag steckt in H2 (+0,063), H1 ist ein Wash.
⚠ Und: **alle** Werte sind negativ — verglichen werden Verlustgrade, kein Edge.

✅ **WAS ECHT IST — der Tail.** Worst-Case **−3,73 → −0,83/−0,96**, also **Faktor
4**. Genau das ist für unbeaufsichtigten Betrieb die relevante Größe. Bezahlt
wird es mit der Trefferquote: **37,0 → 22,9 %** bzw. 44,4 → 30,1 %.
⚠⚠ **Aber die Bar-Simulation ÜBERSCHÄTZT enge Stops systematisch** (Kipp-Punkt
~0,3×ATR, `analyze_trail_resolution.py`) — die Verzerrung geht **zugunsten**
dieser Idee. Ein ✅ wäre also abzuschlagen; das ❌ ist umso belastbarer.
⚠ Nebenbefund: `durchgehend 2,0×ATR` schlägt die Basis in beiden Hälften
(Δ +0,019/+0,029) und in H1 sogar die gestaffelten Varianten. Das widerspricht
`backtest_sl_width.py` NICHT — dort war die Population die gegatete Wellen-Seite
mit Bar-Close-Einstieg, hier sind es Level-Einstiege. Falls jemand die SL-Breite
je anfasst, gehört diese Zelle nachgemessen, nicht die gestaffelte.
⚠ **Gleiche Grenze wie beim S/R-Lauf:** die ABSOLUTEN Werte reproduzieren den
dokumentierten Squeeze-Kontrollwert (+0,124/+0,292) nicht. Belastbar ist allein
der **relative** Vergleich — alle Varianten laufen auf derselben Entry-Liste.

### ✅ TELEGRAM BEI GRÜNER M15-KARTE (2026-08-14, `[telegram] m15_alert`)

User: „wenn es eine Empfehlung gibt (grün), schicke eine Telegram-Nachricht."
`engine._check_m15_alert`, aufgerufen direkt hinter `_log_m15_state` — **dieselbe
Quelle** wie Anzeige und Selbstmessung, damit die drei nicht auseinanderlaufen
können (die Divergenz-Falle, die am 12.08. schon einmal zugeschlagen hat).

⚠⚠ **DIE ENTPRELLUNG IST DER GANZE BAU, nicht ein Detail.** Roh springt die
Karte **85× am Tag** auf grün (`m15_states`, 3 Tage nach der Hysterese vom
12.08.) — das wäre Spam und damit nach zwei Tagen stummgeschaltet. Alle Stufen
sind an den echten Daten kalibriert, nicht geraten:
| Stufe | Meldungen/Tag |
|---|---|
| nur Flanke | **85,0** |
| + Level & Richtung als Schlüssel | 23,7 |
| + 30 min Mindestabstand | 13,3 |
| **+ Fenster 8–20 Uhr (Default)** | **7,7** |
| *(Vergleich: Close-Meldung ab 50 €)* | *2,4–6,1* |
⚠ Nach 4 h (`_ALERT_NEU_S`) darf dasselbe Level erneut melden — sonst
verschweigt der Alert ein Setup, das den ganzen Tag wiederkommt.
Schalter: `m15_alert` (true) · `m15_alert_cooldown_min` (30) ·
`m15_alert_hours` (8-20). Backup `oil_widget_config.ini.bak-2026-08-14-m15alert`.

⚠⚠ **DER TEXT SAGT AUSDRÜCKLICH, DASS ES KEINE PROGNOSE IST.** Die Karte
automatisch zu handeln fällt gemessen durch (`backtest_m15_auto.py`, 13.08.:
ØR −0,162/−0,094, KI ohne Null, schlechter als die HTF-Richtung UND als ihr
eigenes Gegenteil). Die Meldung ist ein **Hinweis zum Hinsehen** für den
Menschen, dessen Auswahl-Vorteil unabhängig belegt ist (+3,20 €/Lot über 146
Trades) — und sie schreibt das in die Nachricht. Die P(break)-Formulierung ist
**wortgleich** zu Karte und Chart (v1.35-Fix).
✅ **13 Tests** (`tests/test_m15_alert.py`), Schwerpunkt auf dem, was NICHT
gesendet werden darf. **Mutationsprobe in vier Zweige** — alle gefangen.
⚠⚠ **Die Probe hat ein echtes TESTLOCH aufgedeckt:** das Entfernen der
Flankenerkennung liess zunächst **alle** Tests grün, weil Cooldown und
4-h-Schlüssel dasselbe mit abfangen. Zwei Schutzschichten, die sich gegenseitig
maskieren — dasselbe Muster wie beim Fill-Dedup. Geschlossen durch
`test_flankenerkennung_ISOLIERT` (Cooldown aus, Level wandert je Tick).
✅ Live verifiziert: seit dem Serverstart **genau 1 Alert**, obwohl die Karte
seitdem mehrfach grün↔gelb gewechselt hat.

**⚠⚠ NEBENBEFUND — TESTS SCHRIEBEN INS PRODUKTIONS-LOG (behoben).** Nach den
Testläufen standen **über 150** `📨 M15-Alert`-Zeilen in `oil_widget.log`, dazu
`Close-Push #4711` mit dem synthetischen Ticket aus `test_close_buchung.py`. Ich
habe daraufhin zuerst geglaubt, die Entprellung sei live defekt, und musste die
Herkunft an der Level-Folge (82.00, 82.01, …) nachweisen. In einem Projekt,
dessen Forensik am Log hängt (18-h-Copilot-Ausfall, stille Fehlbuchung), sind
erfundene Zeilen ein echtes Risiko. `tests/conftest.py` entfernt den
`FileHandler` des `oil`-Loggers jetzt für die Sitzung — **vierte Grundregel der
Suite.** Verifiziert: Log-Größe vor und nach einem Komplettlauf **identisch**.

### ⚠⚠ „WARUM WURDEN MEINE TRADES AUTOMATISCH GESCHLOSSEN?" — es war das TRAILING (2026-08-14)

User-Frage. Am Log und an der DB beantwortet, nicht vermutet. 11 Closes mit
Grund `sl` seit dem 13.08., **richtungsrichtig** aufgeschlüsselt (bei SELL zieht
das Trailing den Stop nach UNTEN — ein Exit darüber ist Slippage, kein Trailing):
| Ursache | n | Summe |
|---|---|---|
| **Trailing-Stop (nachgezogen)** | **9** | **−75,90 €** |
| echter Initial-SL | 1 | −113,94 € |
| ⚠ **manuelle SL-Änderung** | 1 | −215,60 € |
⚠ **`closed_by='sl'` heisst nur „Broker-Stop erreicht", nicht „Initial-Stop".**

**DER MECHANISMUS IST ARITHMETIK, KEIN DEFEKT.** Der Trail sitzt `mult`×ATR
(=1,0) hinter dem Bestkurs und erreicht den Einstieg damit erst bei **1,0×ATR
Gewinn** — aktiviert wird er aber schon bei **0,3×ATR** (`trail_start`). Im Band
dazwischen liegt der Stop zwangsläufig auf der VERLUSTSEITE. Real (50023965):
Einstieg 82,283 · Bestkurs 82,141 (0,47×ATR im Plus) · Stop 82,141+0,305 =
**82,456** = 0,173 ÜBER dem Einstieg.
⚠ Der grösste Verlust war **nicht** automatisch: Log 13.08. 16:30:59 „Manuelle
SL/TP-Änderung erkannt (SL 81.468≠81.151) → Trailing abgeschaltet". Ab da
verfolgt die Engine SL-Änderungen nicht mehr — ob der Exit bei 81,758 eine
weitere Handeingabe war oder Slippage, ist aus dem Log **nicht** entscheidbar.

**GEMESSEN: späterer `trail_start`?** (`backtest_trail_start.py`, 80k M5,
Squeeze-Entries, nur `trail_start` variiert)
| trail_start | H1 „war im Plus, schloss im Minus" | H2 |
|---|---|---|
| **0,3 (live)** | **47,6 %** | **38,2 %** |
| 0,8 | 27,7 % | 19,4 % |
| 1,0 | 12,0 % | 6,9 % |
| **1,3** | **0,0 %** | **0,5 %** |
✅ **Das beobachtete Verhalten ist damit bestätigt und quantifiziert** — es ist
eine Auszählung, keine Schätzung, und sie fällt monoton.

⚠⚠ **ABER DER ERTRAG TRÄGT NICHT — und der Weg dahin ist die eigentliche
Lehre.** Sequentiell sahen **vier** Werte in beiden Hälften besser aus (0,5 · 0,8
· 1,3 · 1,5, Δ bis +0,101). **Der GEPAARTE Test dreht das:**
| gegen 0,3 | H1 Δ je Trade | H2 Δ je Trade |
|---|---|---|
| 0,8 | +0,048 [−0,047 … +0,150] | **−0,006** [−0,083 … +0,074] |
| 1,3 | +0,077 [−0,062 … +0,219] | **−0,023** [−0,127 … +0,088] |
| 1,5 | +0,143 [−0,014 … +0,299] | **−0,023** [−0,137 … +0,098] |
**Alle 12 gepaarten Intervalle enthalten die Null**, und in H2 kippt das
Vorzeichen gegenüber dem sequentiellen Lauf.
⚠ **Grund: ein späterer Trail-Start hält Trades länger, also passen ANDERE
Trades in den EINEN Slot.** Der scheinbare Gewinn war ein **Selektionseffekt**,
kein Exit-Effekt — exakt die Falle aus `backtest_exit_combo.py` (31.07.):
verschiedene Trade-Folgen sind nicht vergleichbar. Der gepaarte Vergleich
(gleiche Entries, nur der Exit variiert) ist hier der einzig gültige Test, und
er ist zugleich **trennschärfer** als die Einzel-Intervalle.
✅ **KEINE ÄNDERUNG.** `trail_start` bleibt 0,3.
⚠ Wer die kleinen Verluste trotzdem loswerden will, tauscht sie gegen **weniger
Trefferquote im Ertrag** ein — es ist eine Komfort-, keine Edge-Entscheidung.
Zahlen dafür liegen jetzt vor (1,3 → 0 % solcher Fälle, WR 36 → 56 %).

### ⚠⚠ LÄUFT HYPERLIQUID DEM BROKER VORAUS? NEIN — ES IST UMGEKEHRT (`analyze_leadlag_hl.py`, 2026-08-17)

User-Frage. Im Projekt stand dazu ein Befund aus einer kleinen Stichprobe
(„Pepperstone führt, Peak k=−1 ≈ 6 s, 51 % Treffer, n=545"). Neu gerechnet auf
**245.073 Ticks / 20,4 Tagen** — und er reproduziert, deutlich schärfer.

**KREUZKORRELATION der Renditen** (k>0 = HL führt · k<0 = Pepperstone führt):
| Lag | GESAMT | H1 | H2 |
|---|---|---|---|
| k=−1 (~6 s, **Pepperstone voraus**) | **+0,455** | +0,460 | +0,447 |
| k=0 (gleichzeitig) | +0,505 | +0,507 | +0,503 |
| k=+1 (~6 s, **HL voraus**) | **+0,020** | +0,029 | +0,005 |
⚠⚠ **Die Asymmetrie IST die Antwort: +0,455 gegen +0,020, Faktor 23.** Die
Pepperstone-Bewegung von jetzt sagt die HL-Bewegung 6 s später vorher; umgekehrt
sagt HL praktisch **nichts**. Beidhälftig identisch.
✅ Kontrolle bestanden: k=0 liegt bei +0,505 — beide handeln erkennbar WTI, die
Pipeline funktioniert.

**RICHTUNGSTEST, entkoppelt** (nicht überlappende Fenster — die Cluster-Falle,
die im Projekt schon elf Hypothesen ausgelöst hat): über **12 Zellen**
(Vorlauf 6–30 s × Horizont 6–30 s × Schwelle 0,5–2,0 bp) liegt die Trefferquote
zwischen **47,6 und 49,8 %** — durchweg **am oder unter dem Münzwurf**. Fünf
Zellen haben ein Intervall **komplett unter 50 %**, d. h. wenn überhaupt, läuft
HL leicht GEGENläufig.
⚠ **Und die ökonomische Hürde entscheidet ohnehin:** die Ø-Bewegung in
HL-Richtung beträgt **+0,00 bis +0,14 bp** (bei 30 s sogar −0,09). Der
Pepperstone-Spread ist **~2,7 bp** — das ist **20- bis 270-mal** so groß.

✅ **URTEIL: HL taugt NICHT als Vorlaufindikator.** Das deckt sich mit allem
Übrigen zu dieser Quelle: der Orderbuch-Druck ist mit 0,2–1,0 bp gegen ~3 bp
Kostenhürde gemessen, das Modul „Orderbuch" wurde am 06.08. wegen beidhälftig
negativer Prädiktivität entmachtet, und Funding/Premium tragen ebenfalls nicht.
⚠ **Was HL weiterhin kann und bleibt:** der 24/7-Kurs am **Wochenende**, wenn
Pepperstone geschlossen ist (Header-Anzeige + G/V-Schätzung) — das ist eine
Verfügbarkeits-, keine Vorhersage-Eigenschaft.

⚠ **Drei methodische Punkte, ohne die das Ergebnis anders ausgefallen wäre:**
(1) **32 % der Zeit steht Pepperstone still** (Wochenende). Ein stehender Kurs
erzeugt zwangsläufig den Eindruck, HL laufe voraus — nur die 65 % verwertbaren
Ticks bei lebendem Broker gehen ein. (2) Gerechnet wurde auf **rohen** Kursen:
`hl_mid + basis` ist bei stehendem Broker zirkulär (dokumentiert 02.08.).
(3) Nur **Renditen**, nie Niveaus — zwei kointegrierte Preisreihen korrelieren
immer nahe 1 und sagen nichts über Vorlauf.
⚠ **Grenze:** die Abtastung liegt bei ~6 s, ein Vorlauf **unter** 6 s wäre
unsichtbar. Praktisch aber irrelevant — bei 1-s-Poll, WireGuard und Market-Order
ist er nicht ausbeutbar, und die Asymmetrie bei ±1 zeigt die Richtung bereits.

### ⚠⚠ ORDERBUCH FÜR DIE EMPFEHLUNG? NEIN — ES IST REAKTIV, NICHT PRÄDIKTIV (`analyze_orderbook_pred.py`, 2026-08-17)

User-Frage. Neu gerechnet auf **196.720 Buch-Schnappschüssen / 17,4 Tagen** mit
dem MT5-Kurs in derselben Zeile (64 % davon bei lebendem Broker verwertbar).
Merkmale `imb1`/`imb5`/`imb20`, Horizonte 60 s und 300 s, Quintile **je Hälfte**,
**nicht überlappende** Fenster, 95-%-Bootstrap.

**Ergebnis: alle 12 Zellen haben ein Intervall, das die Null enthält.** Keine
Vorhersagekraft belegt.

⚠⚠ **DER AUFSCHLUSSREICHE TEIL IST DIE RÜCKWÄRTS-KONTROLLE.** Berichtet wird
neben der künftigen auch die **vergangene** MT5-Bewegung je Quintil — in **10 von
12 Zellen ist die RÜCKWÄRTS-Spanne grösser als die vorwärts:**
| Merkmal · Horizont | vorwärts | **rückwärts** |
|---|---|---|
| imb1 · 60 s (H1/H2) | −0,05 / +0,08 bp | **+0,72 / +0,50** |
| imb1 · 300 s (H1/H2) | +0,06 / −0,91 | **+1,31 / +1,73** |
| imb5 · 60 s (H1/H2) | −0,09 / +0,10 | **+0,53 / +0,78** |
| imb20 · 60 s (H1/H2) | +0,05 / +0,06 | **+0,63 / +0,65** |
**Das Buch spiegelt die Bewegung, die auf Pepperstone SCHON PASSIERT ist** — es
ist reaktiv. Die beiden Ausnahmen (imb5/imb20 · 300 s · H1) reproduzieren in H2
nicht (dort +0,44 gegen rückwärts +1,83).
✅ **Damit passt es exakt zum Lead-Lag-Befund vom selben Tag:** Pepperstone führt
HL um ~6 s (r(k=−1)=+0,455 gegen r(k=+1)=+0,020). Ein Orderbuch, dessen Börse
hinterherläuft, kann den Broker nicht vorhersagen — der Befund ist damit nicht
nur gemessen, sondern **mechanistisch erklärt**.

⚠ **Und die ökonomische Hürde entscheidet ohnehin:** die grösste
Vorwärts-Spanne über alle Zellen ist **+1,63 bp**, der Pepperstone-Spread liegt
bei **~2,7 bp**. Selbst die beste Zelle — statistisch nicht von null zu trennen
und in der anderen Hälfte nicht reproduziert — läge **unter** den Kosten.

✅ **URTEIL: Das Orderbuch taugt nicht für die Empfehlung.** Das bestätigt
unabhängig die Entmachtung des Verdict-Moduls „Orderbuch" am 06.08. (Gewicht
0,5 → 0, über 2.688 Episoden beidhälftig negativ) — damals aus der
Verdict-Telemetrie, jetzt aus den Rohdaten der Quelle selbst.
⚠ **Grenzen, ehrlich:** nach der Entkopplung bleiben bei 300 s nur 1.252/1.254
Fälle, die Intervalle sind entsprechend breit; gemessen wurde die **Imbalance**,
nicht die Wände (deren Preis-Zuordnung bräuchte die Basis-Korrektur und wäre
damit zirkulär); und 17,4 Tage sind EIN Regime.

### ⚠⚠ NEUE DATENQUELLEN RECHERCHIERT — der Filter ist die FREQUENZ, nicht der Preis (2026-08-17)

User: „welche Tools (Websites, Newsfeed, Telegram, X) könnten wir noch nutzen, um
die Vorhersage zu verbessern."

⚠⚠ **DER ENTSCHEIDENDE FILTER IST DIE HALTEDAUER: 12–18 Minuten.** Damit fällt
praktisch die gesamte Fundamentaldaten-Welt weg — sie ist **wöchentlich**:
| Quelle | Frequenz | brauchbar für einen 15-Min-Trade? |
|---|---|---|
| EIA-Lagerbestände · API-Bulletin | wöchentlich (Mi/Di) | ❌ nur als Event-Blackout — **gebaut** |
| CFTC Commitments of Traders | wöchentlich (Fr) | ❌ |
| Baker-Hughes Rig Count | wöchentlich (Fr) | ❌ |
| OPEC-Produktion / IEA-Bericht | monatlich | ❌ |
| Tanker-Tracking / Satelliten-Lager | täglich | ❌ zu grob **und** Enterprise-Preis |
✅ Als **Event-Gate** ist genau dieser Teil bereits umgesetzt (EIA-Blackout Mi
15:30–16:30 + `core/econ_calendar.py` über Forex Factory, ±15 min, kostenlos).
Mehr Fundamentaldaten erhöhen dort nichts — die Termine sind schon abgedeckt.

⚠ **`oilpriceapi.com` (10.000 Anfragen/Monat gratis)** bündelt EIA, Baker Hughes
und OPEC bequem — löst aber kein Problem, das wir haben: es sind dieselben
wöchentlichen Zahlen, nur einfacher abzurufen.

⚠⚠ **KORREKTUR EINER PROJEKTZAHL: die dokumentierten „X-API: Basic 200 $/Monat,
Pro 5.000 $" gelten NICHT mehr.** Seit **Februar 2026** ist X auf
**pay-per-use** umgestellt: **0,005 $ je gelesenem Post**, gedeckelt bei 2 Mio.
Lesevorgängen/Monat; die alten Abo-Stufen sind für Neukunden geschlossen. Bei
z. B. 300 relevanten Posts/Tag wären das **~45 $/Monat** statt 200 — der
**Kosten**-Einwand von damals ist damit hinfällig.
✅ **Der MESSUNGS-Einwand steht aber unverändert** und ist der eigentliche: der
bereits vorhandene Textstrom (News-Sentiment) ist **gemessen nicht robust
prädiktiv** (03.08.: der stark-bullische Bucket liegt in BEIDEN Hälften bei null
bis negativ, der bearische kippt das Vorzeichen). Einen ZWEITEN Textstrom in
eine Pipeline zu legen, deren erster nichts vorhersagt, ist erst begründbar,
wenn der erste etwas zeigt. Fälligkeit dafür steht im Reminder
(`≥90 Tage Sammlung`, Zähler läuft seit dem Skalen-Fix vom 03.08.).

⚠ **Telegram-Kanäle (Bookmap/ATAS o. Ä.) bleiben verworfen** — sie transportieren
**Screenshots**. Ein Orderbuch-Zustand ist vorbei, sobald ihn jemand
abfotografiert. Und das Orderbuch selbst ist am 17.08. als **reaktiv** gemessen.

✅ **GESAMTURTEIL: keine neue Quelle empfohlen.** Begründung nicht „keine Lust",
sondern die Beleglage: **25 Signal-Eingriffe** über jede orthogonale
Indikatorklasse sind gescheitert; die Hyperliquid-Quelle ist am 17.08. in allen
drei Ausprägungen durchgemessen (Kurs läuft hinterher, Orderbuch reaktiv,
Funding trägt nicht); und der einzige je gemessene grosse Hebel war die
**Ausführung** (+0,32 R durch die ruhende Order — mehr als alle Signal-Ideen
zusammen). Eine neue Datenquelle müsste diese Latte schlagen, nicht nur „auch
Information enthalten".

### ✅✅ SIEBEN VERBESSERUNGEN UMGESETZT (User-Auftrag 2026-08-17)

**1 · CIRCUIT-BREAKER SCHARF** — `[trading] daily_loss_limit_pct = 0 → 8`
(Backup `…bak-2026-08-17-breaker`). Live verifiziert: `enabled=True`,
`limit_eur` **89,44 €**. ⚠ Steht **nicht** in `runtime_state.json` — die ini
greift direkt (die Falle, durch die `auto_signal` am 31.07. unbemerkt lief).
Begründung: der gemessene IST-Pfad hat **MaxDD 82,8 %** und einen schlechtesten
Einzeltrade von **−381 €**; der Breaker kostet an normalen Tagen nichts und
greift nur in der Serie. ⚠ Ehrlich: 70 % des gemessenen Nutzens stammten aus
EINEM Tag — das ist das Wesen einer Versicherung, aber die +739 € sind eine
Realisierung, keine Erwartung. **Zurück: `= 0`.**

**2 · B5-DEADLOCK RECHNERISCH AUFGELÖST — die Regel ist ENTSCHIEDEN.**
Stand 11/20: ØGewinn 13,62 gegen ØVerlust 94,15 = **Verhältnis 0,14** (Latte
1,0), **PF 0,39** (Latte 1,0). Nachgerechnet, was die 9 Rest-Trades leisten
müssten:
| Rest-Szenario | nötiger Ø-Gewinn | ×Ø bisher |
|---|---|---|
| 9 Gewinner / 0 Verlierer | **165,72 €** | **12,2×** |
| 7 / 2 | 186,17 € | 13,7× |
| 5 / 4 | 222,99 € | 16,4× |
⚠ Der **grösste** Squeeze-Gewinn des Zeitraums war **77,34 €** — selbst der
beste Fall verlangt **2,1× den Rekord**, neunmal in Folge. **Die Latte ist nicht
mehr erreichbar.** Auf 20 zu warten hätte den Zustand nie entschieden, zumal der
Trader seit 11.08. aus ist und der Zähler damit einfror (der Deadlock).
`auto_squeeze` bleibt **FALSE**. ⚠ Eine Wiederaufnahme wäre eine **NEUE**
Entscheidung mit **NEUER, vorab fixierter** Latte — nicht die Fortsetzung dieser.

**3 · `core/kontrolle.py` — Kontroll-Fixture.** Referenzwerte (Squeeze
Touchfill +0,124/+0,292 · Close-Bruch +0,456/+0,611 · Markt −0,158/−0,006) plus
`pruefe_squeeze()`. Anlass: zwei Messungen am 13.08. konnten die Referenz nicht
reproduzieren, wodurch ihre Absolutwerte unbrauchbar waren. ⚠ Es bricht **nicht
ab** — ein Backtest darf abweichen, er muss es nur WISSEN und sagen; ein harter
Abbruch würde legitime Varianten unmöglich machen, eine stille Abweichung
erzeugt genau die Fehlschlüsse.

**4 · `core/live_config.py` — EINE Quelle der Live-Parameter.** Liest ini +
`runtime_state.json` in derselben Vorrangfolge wie der Engine-Start. ⚠ Anlass
ist die **dritte** Instanz derselben Fehlerklasse: `ATRMIN_STD`=0,12 statt live
0,06 (13.08.), `set_entry_room(0.6)` vergessen (05.08.), nachgebauter
Level-Cluster (07.08.). **Ein aufgerufenes Gate ist nicht dasselbe wie ein
aktives.** Aufruf: `python -m core.live_config`.

**5 · `stichprobe.paarweise()` / `zeig_paarweise()`.** Anlass: beim
`trail_start`-Sweep (14.08.) sahen sequentiell vier Werte beidhälftig besser aus
— gepaart kippte in H2 das Vorzeichen, weil ein anderer Exit ANDERE Trades in
den EINEN Slot lässt (Selektions- statt Exit-Effekt). ⚠ Der gepaarte Test ist
nicht nur richtiger, sondern **trennschärfer**: er misst die Streuung des
UNTERSCHIEDS, nicht die der Trades. Selbsttest: identisch → KI [0,0000 … 0,0000],
+0,30 → KI ohne Null, Rauschen → KI mit Null.

**6 · `tools/check_konstanten.py` — Wächter für veraltete Zahlen.** Leitet vier
tragende Grössen aus den Daten ab und hält sie gegen CLAUDE.md.
⚠⚠ **Er hat beim ersten Lauf zwei Befunde geliefert — beide waren FEHLER IN
MEINER EIGENEN ABLEITUNG**, also genau die Fehlalarme, gegen die Punkt 7 gebaut
ist: „Margin je Lot" war aus `Balance × 95 % / Lots` geschätzt (unterstellt volle
Auslastung), „Elliott-Stimmanteil" zählte über ALLE Zeilen statt über das
dokumentierte Fenster vor der Entmachtung. Beide korrigiert.
✅ **Danach fand er einen ECHTEN veralteten Wert — meinen:** die am 13.08. von
mir notierten „~405 €/Lot" sind falsch; der exakte Broker-Wert
(`position.margin/lots`) ist **712,84 €/Lot**, die ursprünglich notierten 670
lagen also näher als meine „Korrektur". **Lehre: eine abgeleitete Zahl ist keine
gemessene.** Stand jetzt: **0 Befunde** über alle vier Grössen.

**7 · Puls-Wächter bedingungs-bewusst.** Er meldete `pbreak_predictions` als
„63,5 h still" — die Tabelle schreibt aber nur bei **frischem Level-Touch auf
der Ziel-Seite einer OFFENEN Position**. `PULS` trägt jetzt je Zeile ihre
**Vorbedingung**; bedingte Tabellen erscheinen als **ⓘ Hinweis** statt als
Ausfall. ⚠ **0 Zeilen bleibt IMMER ein Befund** — „hat nie geschrieben" lässt
sich nicht wegerklären (das war der `rec_outcomes`-Fall). Grund: ein Wächter mit
Fehlalarmen wird ignoriert, und mit ihm die eine echte Meldung — genau das ist
dem Divergenz-Wächter passiert (6 Tage Dauerwarnung).

✅ Pipeline grün (85 Tests), Deployment über `tools/deploy.py --feld
circuit_breaker` verifiziert, genau eine Instanz je Port.
⚠ **Nicht angefasst:** das Sizing (95 % Margin) — Ihre mehrfach bestätigte
Entscheidung; und die 44 Backtests mit eigener Exit-Kopie (Migrations-Regel:
ihre publizierten Zahlen würden sich verschieben).

### ⚠⚠ „DER PFEIL SOLL DIE NÄCHSTE KERZE SCHÄTZEN" — gemessen: geht, zahlt aber nicht (`backtest_naechste_kerze.py`, 2026-08-17)

User: der Pfeil an der aktuellen Kerze zeigt die Abprall-Wahrscheinlichkeit des
S/R — er soll stattdessen die Richtung der nächsten Kerze schätzen.
✅ **Die Beobachtung stimmt und ist Absicht.** Live steht in der CSV
`AR;L;81.815;81.983;62% Abprall` — der **L**-Pfeil, seit v1.32 (31.07.) bewusst
so gebaut: er zeigt eine **bedingte, kalibrierte** Grösse (hält DIESES Level?,
AUC 0,65 oos) statt einer freien Prognose.
⚠ Trotzdem neu gemessen statt abgelehnt: die bisherigen Richtungs-Messungen
liefen über **60–360 Minuten**. Die **nächste M5-Kerze** ist ein anderer,
kürzerer Horizont — auf Ein-Bar-Ebene gibt es Mikrostruktur-Effekte, die dort
anders wirken können.

**80k M5-Bars, 2 Halbjahre, 5 Prädiktoren, 95-%-Intervall je Zelle:**
| Prädiktor | H1 Treffer | H2 Treffer | Ø Bewegung in Richtung |
|---|---|---|---|
| EMA-Trend (Wellen-Signal) | 49,6 % | 49,6 % | −0,0008 / +0,0004 $ |
| Momentum 3 Bars | 48,3 % ❌ | 48,3 % ❌ | ~0 |
| letzte Kerze **setzt fort** | 48,6 % ❌ | 48,8 % ❌ | ~0 |
| **letzte Kerze KEHRT UM** | **51,4 % ✅** | **51,2 % ✅** | **−0,0001 / +0,0002 $** |
| langer Docht (Lehrbuch) | 49,5 % | 49,3 % | +0,0010 / −0,0014 $ |

✅ **Es GIBT einen Effekt — und zwar den umgekehrten:** die 1-Bar-**Mean
Reversion** trifft in BEIDEN Hälften über 50 %, beide Intervalle schliessen die
50 aus. Trendfolge und Momentum sind dagegen **unter** 50.
⚠⚠ **ABER die ökonomische Bedingung fällt katastrophal durch: die Ø-Bewegung in
Vorhersagerichtung ist ±0,0002 $ — der halbe Spread beträgt 0,0100 $.** Der
Effekt ist damit **50–100× zu klein**. Eine M5-Kerze bewegt sich im Median nur
**0,0400 $**, der Spread ist **0,0200 $ = 50 % davon.**
**Trefferquote real, Geld keins:** Mean Reversion fängt viele kleine Rücksetzer
und wird von den wenigen Fortsetzungen überfahren.

✅ **URTEIL: der Pfeil bleibt, wie er ist.** Ein „nächste Kerze"-Pfeil wäre
**technisch wahr und praktisch wertlos** — und gefährlicher als nützlich, weil er
autoritativ aussieht. ⚠ Dazu käme, dass er nach der Messung **gegen** die letzte
Kerze zeigen müsste (Mean Reversion), was jeder Mensch als Fehler lesen und
übersteuern würde.
⚠⚠ **EIGENER MESSFEHLER auf dem Weg, dokumentiert weil er eine Klasse ist:** der
erste Lauf zählte **unveränderte** Schlusskurse als Fehltreffer — dadurch lagen
ein Prädiktor UND sein eigenes Gegenteil beide unter 50 % (43,4 + 45,8 = 89,2),
was arithmetisch unmöglich ist und den Fehler verriet. Real sind **~11 %** der
M5-Kerzen unverändert. **Merksatz: wenn A und Nicht-A sich nicht zu 100 %
addieren, ist die Messung kaputt, nicht der Markt.**

### ⚠⚠ KERZENMUSTER FÜR DIE RICHTUNG DER NÄCHSTEN KERZE = NEIN (`backtest_kerzenmuster_richtung.py`, 2026-08-17)

User-Anschlussfrage. Gemessen mit den **echten Muster-Schwellen** aus
`core/candles.py` (1:1 importiert, kein Nachbau), 80k M5-Bars, 2 Halbjahre,
unveränderte Schlusskurse ausgeschlossen, 95-%-Intervall je Zelle.
Bewertet wird die **Lehrbuch-Lesart** (±1), damit sichtbar wird, ob sie stimmt.

| Muster | H1 Treffer | H2 Treffer | Ø Bewegung |
|---|---|---|---|
| Marubozu | **46,5 % ❌** | **45,2 % ❌** | −0,0019 / +0,0077 $ |
| Engulfing | **47,4 % ❌** | 49,5 % | −0,0018 / −0,0009 |
| Outside Bar | **47,3 % ❌** | 48,9 % | −0,0019 / +0,0055 |
| langer Körper | 48,5 % | **47,6 % ❌** | +0,0009 / +0,0027 |
| langer Docht OBEN | 49,4 % | **47,8 % ❌** | +0,0011 / +0,0020 |
| langer Docht UNTEN | 50,6 % | 50,6 % | +0,0004 / **−0,0037** |

✅ **KEIN Muster erfüllt die vorab fixierte Regel.** Die Trefferquoten liegen
zwischen **45,2 und 50,6 %** — am oder **unter** dem Münzwurf; **fünf Zellen**
haben ein Intervall komplett unter 50, sind in der Lehrbuch-Lesart also
**verlässlich falsch**. Die einzige beidhälftig über 50 (langer Docht UNTEN,
50,6 %) hat in beiden Hälften ein Intervall MIT der 50 und eine Ø-Bewegung, die
das **Vorzeichen kippt**.
✅ **Das reproduziert den 60-Minuten-Befund vom 31.07.** („die Lehrbuch-Lesart
ist in BEIDEN Hälften invertiert") jetzt auch auf dem **1-Bar-Horizont** — mit
den echten Klassen statt der groben Proxys. Damit ist die Kerzen-Klasse zum
**vierten** Mal gemessen (Forward-Return 60 min · Fade als Handelsregel ·
Fade mit Ausführungs-Achse, 40 Zellen · jetzt 1-Bar-Richtung).
⚠ **Und selbst wenn man sie umdreht, zahlt es nicht:** die grösste Ø-Bewegung
über alle Zellen ist **0,0077 $** — und die kippt in der anderen Hälfte auf
−0,0019. Der halbe Spread beträgt **0,0100 $**, eine M5-Kerze bewegt sich im
Median **0,0400 $**.

✅ **URTEIL: der Pfeil bleibt bei der kalibrierten Level-Aussage.** Es gibt
keine Kerzen-Grundlage für eine Richtungsschätzung der nächsten Kerze — weder
in der Lehrbuch- noch in der umgedrehten Lesart.
⚠ Konsistent mit der Kachel-Entscheidung vom 31.07.: die Kerzen-Anatomie ist
bewusst **neutral** gefärbt und nennt die gemessene statt der Lehrbuch-Lesart.

### ✅ MELDUNGEN OBEN LINKS IM MT5-CHART (2026-08-17, Indikator v1.36)

User-Wunsch. Neue CSV-Zeile **`MS;<text>`**, im Indikator als gestapelte
`OBJ_LABEL` an `CORNER_LEFT_UPPER` (oben rechts sind Kurs, G/V und HL).
Inhalt = dieselben Zeilen wie die Dashboard-Karte „Meldungen": Squeeze-Zustand,
Überdehnung, S/R-Ziel mit P(break), Stop-Seite, News-Blackout.

⚠⚠ **NUR ZUSAMMENGESETZT, NICHT NEU GERECHNET.** Alles kommt aus bereits
berechneten Snapshot-Teilen bzw. `_last_sr_close_hint`/`_last_stop_approach`;
`calendar.blackout()` ist ein reiner Speicher-Zugriff. Der Konsens-Pfeil hat im
~5-s-Export-Takt einmal ein **komplettes zweites `_verdict()`** gezogen — doppelte
Last UND die Gefahr, dass Chart und Dashboard auseinanderlaufen.
✅ **Zwei Felder vorher geprüft statt angenommen:** `_last_calendar` und
`_export_meldungen` gab es **nicht** — ohne die Prüfung hätte das umgebende
`except Exception` es still verschluckt und die Meldungen wären dauerhaft leer
geblieben (dieselbe Klasse wie der `wave_snap`-Fallstrick vom 04.08.).

⚠⚠ **ASCII-SICHER — real nötig, nicht vorsorglich.** Der erste Lauf lieferte
`MS; Kompression  Ausbruch steht bevor`: Emoji und Gedankenstrich waren zu
Leerzeichen geworden. Der Indikator liest die CSV **nicht** als UTF-8 — genau
deshalb steht dort auch „Unterstuetzung" statt „Unterstützung". Jetzt werden
Umlaute/Sonderzeichen ersetzt und alles ausserhalb 32–126 entfernt.
Verifiziert: `MS;Squeeze-Ausbruch LONG @ 82.413`, **0 Nicht-ASCII-Zeichen**.

⚠ Deckel **5 Zeilen** (der Chart soll lesbar bleiben), je 70 Zeichen; `;` im Text
wird zu `,`. Eigener Objekt-Präfix `SRB_MSG_*`, damit die Labels beim Redraw
verschwinden, wenn keine Meldung mehr ansteht (die Alt-Objekt-Falle der
HL-Pfeile). Abschaltbar: `[trading] export_meldungen=false` **oder**
`InpShowMeldungen=false`; Größe/Farbe über `InpMeldungSize`/`InpMeldungColor`.
✅ Kompiliert ins **richtige** Terminal (0 errors, 0 warnings), deployt und am
laufenden Server verifiziert.

### ✅ S/R-AUTO-CLOSE WIEDER AN (User-Entscheidung 2026-08-11)

Frage war: „sollten wir Trades bei einem S/R-Bounce automatisch schliessen?"
⚠ Die Funktion existierte bereits und stand seit dem 06.08. auf `false`; der
Config-Waechter flaggte die Abweichung durchgehend.
⚠⚠ **Entscheidend ist die Unterscheidung:** der **pauschale** S/R-Close ist
**2× verworfen** (Trefferquote 35→67 %, aber PF/ΣR in beiden Haelften schlechter
= Gewinner-Kappen); der **P(break)-gegatete** ist positiv (H1 +357 R, H2 +1.418 R
gegen reines Trailing, `backtest_pbreak_rvalue.py`).

✅ **LIVE KONTRAFAKTISCH NACHGERECHNET (43 Trades ab dem Nachtrainieren 31.07.):**
| | |
|---|---|
| IST (sr_close) | **+2.171,72 €** |
| HALTEN (kanonischer Exit ab demselben Einstieg) | +1.820,91 € |
| **Differenz** | **+350,81 €**, besser in **27 von 43** Faellen (63 %) |
⚠⚠ **Die 100-%-Trefferquote der sr_close-Statistik ist KEIN Befund, sondern
Konstruktion** — `_check_sr_close` schliesst ausschliesslich im Plus (`pnl>0`).
Aussagekraeftig ist allein der Vergleich oben.
✅ **Das kehrt `analyze_manual_close.py` (04.08.) um** (dort war sr_close −530 €
schlechter) — jene Messung lief auf dem **alten** Modell, vor dem Nachtrainieren.
⚠ **Vorbehalte:** n=43 in EINEM Regime · EUR-Faktor 87,6 geschaetzt · das
P(break)-Modell ist live **fehlkalibriert** (14,3 Pp zu niedrig, AUC unentschieden
bei 87 entkoppelten Vorhersagen) · und das Gate **gatet kaum** (schliesst 94–97 %
aller Beruehrungen, ist also nah an der pauschalen Variante). Dass es trotzdem
besser abschneidet, spricht dafuer, dass es den besseren **Moment** waehlt, nicht
die besseren Level.
✅ Eingeschaltet ueber `POST /api/srclose`; **alle drei Stellen angeglichen**
(Snapshot · `runtime_state.json` · ini) — sonst bliebe dieselbe latente
Inkonsistenz, durch die `auto_squeeze` unbemerkt aus war. Backup
`oil_widget_config.ini.bak-2026-08-11-srclose`.
⚠ Beim Einschalten stand eine Position bei −55,62 € — der Auto-Close greift nur
im Plus, hatte also keinen Sofort-Effekt.
⚠ **Nicht verwechseln:** `auto_squeeze` wurde am selben Tag um **14:10 vom User
ueber das Dashboard wieder AUSgeschaltet** (`[WEB] Auto-Squeeze-Entry AUS`) —
die Herkunfts-Kennzeichnung im Log (eingebaut 02.08.) hat das eindeutig belegt.

### ⚠ ENTRY-DIALOG ENTFERNT (User-Vorgabe 2026-08-11, v=163)

„Entferne auch die Warnung beim Eröffnen von Trades." LONG/SHORT feuern jetzt
**sofort**, ohne Bestätigung.
⚠ **Was wegfällt:** die 7-Punkte-Checkliste (Signal-Deckung · HTF-Trend ·
Entry-Raum · News · Kosten · Verlust-Serie · Nacht) **und** der
Ausrichtungs-Split im Moment der Entscheidung — letzterer zeigte die **einzige
beidhälftig robuste Zahl des Projekts**: gegen das Signal **−6,60 € je Lot**
über 1.216 Trades. Genau diese diskretionären Abweichungen sind die gemessene
Kern-Leckage; der Dialog war 2026-07-30 dagegen gebaut worden.
⚠ **Bewusste User-Entscheidung, kein Messergebnis.** Sie liegt aber auf der
Projekt-Linie „sichtbar machen, nicht bevormunden": hartes Blockieren war nie
eingebaut, und User-Übersteuerungen liefen gemessen **68 % Trefferquote**.
✅ **Backend UNBERÜHRT:** `engine._entry_checklist` rechnet weiter und steht als
`checklist` im Snapshot (live verifiziert) — die Zahlen bleiben auswertbar.
✅ **Die Verlust-Close-Abfrage bleibt** („Position im Minus schließen?") — sie
schützt vor einem versehentlichen Klick, nicht vor einer Entscheidung.
**Zurück:** `const ENTRY_DIALOG = true` in `app.js` (Einzeiler).

### ✅ KLARTEXT-KOPFZEILE (2026-08-11, v=162) — und warum NICHT „LONG zu 78 %"

User: „kannst du die M15-Empfehlung noch etwas vereinfachen, wie: jetzt
long/short zu xxx Prozent?" ✅ **Der Wunsch ist berechtigt** — die Karte war
sieben Zeilen Technik. ⚠⚠ **Die Form nicht:** eine Richtungs-Prognose ist über
**1.071 Live-Episoden mit 48–51 % Trefferquote** gemessen (frei sogar AUC **0,52
IN-sample** — es steckt keine Richtungsinformation in den Daten), und die
Prozentzahl war als `conf_pct` **nicht kalibriert, in H2 invertiert** (85–100 % →
40 % Trefferquote). **Genau daran ist die Gesamtempfehlung gescheitert** und
wurde am 08.08. auf User-Wunsch entfernt. Es wäre der **vierte** Rückfall dieser
Karte in die Veto→Richtung-Falle gewesen (Code 08.08. · Geometrie 10.08. ·
Lesart 10.08.) — diesmal auf Bestellung.

✅ **Gebaut: eine Klartext-Zeile, die sagt WAS ZU TUN IST**, mit der **einen**
kalibrierten Prozentzahl, die es gibt — P(break) des Levels, an dem eingestiegen
würde (AUC 0,65 oos). Das ist eine Aussage über die **Struktur**, nicht über den
Trade.
| Lage | Kopfzeile |
|---|---|
| Abpraller | `🟢 JETZT: LONG-Order bei 78,768 (Abpraller) · hält zu 75 % · Ziel 79,033 · ⛔ SHORT meiden` |
| Ausbruch | `🟢 JETZT: LONG-Order bei 79,033 (Ausbruch) · Level unentschieden` |
| Level zu weit | `🟡 JETZT: warten auf das Level · erlaubt wäre LONG` |
| **kein Veto** | `🟡 JETZT: kein Trend-Veto — die Karte gibt hier keine Richtung` |
| Spread hoch | `🔴 JETZT: nicht handeln — Spread frisst den Edge (0,35×ATR)` |
⚠ **„Erlaubt" heisst NICHT „empfohlen"** — belegt ist nur, dass die
Gegenrichtung kostet (−6,60 €/Lot, beidhälftig). Die 45–55-%-Zone bleibt
bewusst ohne Richtung („unentschieden").
⚠ Der **kein-Veto**-Fall ist der ehrlichste: bei flachem M30 ist keine Richtung
ausgeschlossen, und eine zu nennen wäre wieder Prognose. Die Karte sagt dann
ausdrücklich nichts.
⚠ **Reine Formulierung, KEINE Änderung der Gate-Semantik.** Ob der Raum (③b) das
Banner auf gelb ziehen soll, ist ungemessen und bleibt offen.
⚠ Frontend-only (`app.js` + `v`-Bump) → **kein Neustart nötig**, die offene
Position behielt Trailing und Time-Stop.
⚠⚠ **Beim Bau in die dokumentierte Anführungszeichen-Falle gelaufen:** ein
ASCII-`"` als schliessendes deutsches Zitat (`„LONG zu 78 %"`) beendet den
JS-String vorzeitig → `Unexpected token ':'`. **`node --check` hat es gefangen**
(Stufe B) — genau dafür wurde es am 07.08. eingebaut. In Fliesstext gehören
einfache Quotes oder `"` als Schlusszeichen.

### ⚠⚠ GATE ③b — DIE KARTE PRÜFTE „LEVEL NAH", NIE „RAUM DAHINTER" (2026-08-10)

**Anlass: Trade #49671601, −87,03 €** (User: „SL geschlossen trotz LONG-Empfehlung").
⚠ **Die Prämisse stimmte nicht — es gab keine LONG-Empfehlung.** 138 Zeilen
zwischen Ein- und Ausstieg, **100 % WARTEN**: `entry_room` 76 % · `breakout_pending`
19 % · `min_conf` 5 %, **kein einziges durchgelassenes Richtungssignal**. Gesehen
wurde `verboten: SHORT` — ein **Veto**.
⚠⚠ Damit ist es der **dritte** Fall derselben Umwandlung in dieser Karte: am 08.08.
machte der **Code** aus dem Verbot eine Richtung (13 LONG-Episoden an einem
fallenden Tag), am 10.08. früh die **Geometrie** (Gate ④), jetzt die **Lesart**.
Wenn eine Anzeige dreimal zur selben Fehldeutung einlädt, liegt es an der Anzeige.

⚠⚠ **DER EIGENTLICHE BEFUND: zwei Bauteile lesen DIESELBE Tatsache mit
umgekehrtem Vorzeichen.**
| Bauteil | bei „Level sehr nah" | Beleg |
|---|---|---|
| **`entry_room`** (Signal-Motor) | **WARTEN** — Ertrag am Level gedeckelt | ØR −0,119/−0,067 · **PF 0,42/0,66** |
| **Gate ③** (M15-Karte) | **GRÜN** — Level in Reichweite | Ausführungs-Edge der ruhenden Order |
✅ **Beide sind gemessen und beide haben recht — über VERSCHIEDENE Trades:**
`entry_room` misst den **Markt**-Einstieg mit dem Level als *Hindernis voraus*,
die Karte die **ruhende Order AM** Level. Der Unterschied ist real.
**Die Karte zeigte nur die grüne Hälfte** — die Block-Grund-Zeile war beim
Entfernen der Gesamtempfehlung mit verschwunden.

**Der Trade war beim Einstieg entschieden:** Widerstand **0,07×ATR = 0,008 $**
voraus, Stop **0,560 $ = 4,7×ATR_M5** zurück (CRV ~1:67), **MFE +0,016 $** — nie
im Plus. `ctx_atr_m5 = 0,12` = **der ATR-Floor**, der Markt stand still; der Stop
kam trotzdem aus ATR_M15 (bei manuellen Trades Absicht, M5 nur für Auto-Squeeze
seit 05.08.). −87 € = `0,568 $ × 1,77 Lots × ~87,6 €` — reine Geometrie, kein
Ausreißer. ⚠ Stunde 6 „vor DE-Open": autonom hätte der Bot dort nicht eröffnet.

**Ist es ein Muster? Gemessen (77 Trades mit `ctx_*` seit 04.08., je Lot):**
| Block beim Einstieg | n | WR | EUR/Lot |
|---|---|---|---|
| **`entry_room`** | **29** | 55 % | **−11,45** |
| `min_conf` | 16 | 44 % | −16,08 |
| `htf_counter` | 10 | 80 % | +22,83 |
| `breakout_pending` | 9 | 89 % | +17,68 |

Direkt auf der Raum-Achse (Abstand zum Level in der **eigenen** Trade-Richtung):
| | n | WR | EUR/Lot | 95-%-KI |
|---|---|---|---|---|
| Raum < 0,6 (geblockt) | 24 | **62 %** | −9,91 | [−29,4 … +6,6] ⚠ |
| Raum ≥ 0,6 (frei) | 52 | **62 %** | +3,24 | [−5,8 … +13,5] ⚠ |
⚠⚠ **Beide Intervalle enthalten die Null — mit n=76 ist das NICHT belegt**, nur
konsistent mit dem über 80k Bars gemessenen Gate. ✅ Die schärfste Zeile ist die
**identische Trefferquote 62 % gegen 62 %**: die Richtungswahl war gleich gut, der
Unterschied liegt **allein in der Asymmetrie** (Gewinn am Level gedeckelt, Verlust
läuft bis zum Stop) — die dokumentierte Kern-Leckage an neuer Stelle.

✅ **GEBAUT: Zeile ③b** (`#m15-motor`, v=160) — zeigt den aktuellen `block_reason`
im Klartext **plus den Raum je Richtung** („③b Motor blockt: gegen den M30-Trend ·
Raum ▲ 0,17×ATR ⚠ / ▼ 0,07×ATR ⚠ (Gate 0,6)"). Beide Richtungen, weil die Karte
bewusst keine vorgibt.
⚠⚠ **Das Status-Banner bleibt BEWUSST unverändert.** Es auf 🟡 zu ziehen wäre eine
Strategie-Änderung an einer gemessenen Gate-Kette — und die einzige Live-Zahl dafür
(−9,91 gegen +3,24 €/Lot) hat ein KI, das die Null enthält. **Sichtbar machen,
nicht bevormunden**; die Order-Buttons bleiben ohnehin bedienbar (Übersteuerungen
liefen gemessen 68 % WR).
✅ **`wave_rec.room_info(cur)` ist EINE Implementierung für Gate UND Anzeige** —
`_room_gate` rechnet jetzt darüber. Ein Nachbau wäre die Divergenz-Falle, die im
Projekt dreimal zugeschlagen hat (`sim_run`/`_ema_series` in ≥6 Skripten · der
nachgebaute Level-Cluster · das fünfte Exit-Modell).
⚠ Quelle ist trainingsgleich **M5**, nicht die M30-Level der Karte — `entry_room_atr
=0,6` ist auf M5 kalibriert (die drei divergierenden Level-Quellen sind Absicht).
✅ **Bitgleichheit bewiesen** (`tests/test_room_info.py`, 6 Tests): die alte Fassung
liegt wörtlich eingefroren im Test und läuft über **2.000 Zufallslagen × 3 Signale**
gegen die neue — identisch, und das Gate feuerte dabei >300× (ein Test, in dem es
nie feuert, belegt nichts). ⚠ Der gefährlichste Fall hat einen eigenen Test:
`frei` MUSS auf dem **ungerundeten** Abstand entschieden werden — bei 0,5951 würde
die gerundete 0,60 den Fall fälschlich durchlassen.

## ⚠⚠ NUR DAS TRAILING BREITER (Richtung M15)? = FÄLLT DURCH, und der
## Auflösungs-Einwand trägt nicht (`backtest_trail_tf.py`, 2026-08-11)

**Anlass:** User „sollten wir das Trailing auf M15 setzen?" nach einem Trade, der
bei Trail 1,0×ATR_M5 nach rund **1×ATR Rücklauf auf Einstand** ausgestoppt wurde
(#49850541 — bei genau diesem Abstand ist das zwangsläufig).

⚠ **Warum die Frage NICHT schon beantwortet war.** `backtest_exit_atr_tf.py`
(08.08.) stellte den **gesamten** Exit auf ATR_M15 — `simulate()` nimmt EINEN
`atr` für SL, Trail, TP **und** Breakeven, dort wurde also alles mitverbreitert
(daher das verdreifachte Tail). `backtest_trailmult.py` (31.07.) traf die Frage
(fixer SL 2,0, nur `mult` gesweept), lief aber auf **M5-Bars** — und genau davor
warnt `analyze_trail_resolution.py` (05.08.): eine Bar-Simulation **überschätzt
enge Trails und unterschätzt weite**, bei 1,5×ATR kippte dort sogar das
Vorzeichen (M5 −0,038 gegen M1 **+0,028**). Der Sweep, auf dem „enger ist besser"
ruht, war also mit einer Verzerrung **GEGEN** weite Trails gemessen.

**Diese Messung kontrolliert das:** dieselben Einstiege, derselbe ATR (aus M5) →
Trail-Abstand in **Dollar identisch**, nur die Exit-Auflösung unterscheidet sich;
Zeitgrenzen **wall-clock gleich** (200 M5-Bars = 1000 M1-Bars, Time-Stop 24 = 120
— der Altbestand liess 200 stehen und kappte M1-Trades nach 200 statt 1000 min).
Zwei Populationen, Echtkosten.
| mult | Squeeze M5 H1/H2 | **Squeeze M1** | Welle M5 | **Welle M1** |
|---|---|---|---|---|
| 0,80 | +0,123 / +0,557 | **+0,077 / +0,292** | −0,131 / +0,022 | −0,237 / −0,183 |
| **1,00 (live)** | +0,019 / +0,495 | **−0,067 / +0,206** | −0,242 / +0,031 | −0,302 / −0,127 |
| 1,50 | −0,155 / +0,624 | −0,095 / +0,446 | −0,275 / +0,063 | −0,405 / −0,112 |
| **1,86 („M15")** | **−0,355 / +0,557** | **−0,327 / +0,379** | −0,297 / +0,053 | **−0,392 / −0,067** |
**1,86 fällt in H1 auf BEIDEN Populationen und BEIDEN Auflösungen klar durch**
(Trefferquote 25–30 %, PF 0,52–0,64). Regel nicht ansatzweise erfüllt.

✅✅ **DER EIGENTLICHE ERTRAG: der Auflösungs-Einwand ist entkräftet — er wirkt
nur bei ENGEN Trails.** Δ (M1 − M5) auf der Welle: **−0,092/−0,236 bei 0,50**,
−0,060/−0,158 bei 1,00, −0,095/−0,120 bei 1,86; auf dem Squeeze bei 2,00
**+0,049/−0,016 = praktisch null**. Bei weiten Trails verschwindet der
Unterschied — logisch, ein weiter Stop ist nicht auflösungsempfindlich.
⚠ **Das PRÄZISIERT den Befund vom 05.08.**: die feinere Auflösung **bestraft enge
Trails**, sie **belohnt weite nicht**. Der dortige Vorzeichenwechsel bei 1,5 kam
aus n=597 **willkürlichen** Einstiegen (jeder 20. Bar, immer LONG) — mit echten
Einstiegen reproduziert er nicht.

⚠ **Grenzen, ehrlich:** Squeeze nur **n=46/47** je Hälfte; die M1-Historie deckt
~55 Tage, die „zwei Hälften" sind also **zwei Hälften EINES Regimes**, nicht zwei
Regime. Ein ✅ wäre hier wenig wert — das ❌ bei 1,86 ist es, weil es in **jeder**
Zelle auftritt. Die Wellen-Population ist in diesem Fenster durchgehend negativ
und kann deshalb gar nicht diskriminieren.
⚠ **Nebenbefund, ausdrücklich KEINE Empfehlung:** auf M1-Auflösung sieht **0,8
besser aus als 1,0** (Squeeze +0,077/+0,292 gegen −0,067/+0,206) — also eher
*enger*, die Gegenrichtung der Frage. Für eine Umstellung reicht das nicht: ein
Regime, dünne Stichprobe, und die Sim modelliert **keine Exit-Slippage**, die
enge Trails härter trifft (real bis 0,75×ATR über den Stop hinaus).
**Keine Änderung — Trail bleibt 1,0×ATR_M5.**

## ⚠⚠ 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 **33–39 Ausbrüche**. Der
Squeeze feuert auf M5 2–3×/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,5–2,0× | +0,116 / **−0,203** | −0,011 / +0,152 |
| 2,0–2,5× | −0,221 / −0,282 | −0,101 / **+0,589** |
| **≥2,5×** | **+0,102 / +0,096** | −0,267 / **−0,881** |
**Trefferquote in ALLEN Zellen 44–53 %** — 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,2–1,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.25–07.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,0–3,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 `i−1` (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,10–0,25 R wert** — dieselbe Größenordnung wie beim
Squeeze (0,32), Momentum (0,45) und SIG (0,77–0,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,3–75,1 %,
Zufall 73,9–76,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,03–0,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 0** — `check_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.vbs` → **`start_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 ~1–2 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,0–39,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 300–400 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_live`** — **neue 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,0–39,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:**
```python
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.py`** — `atr_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 > 0` → `d >= 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 10–20 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`).

## ⚠⚠ TAG P/L WAR FALSCH — Fehlbuchungen beim Schliessen (2026-08-12)

User: „überprüfe ob Tag P/L richtig berechnet wird." **Die Formel war richtig, die
Datenbasis nicht.**
| | |
|---|---|
| Anzeige „TAG P/L" | **+14,26 €** |
| Broker, realisiert heute | **−74,08 €** |
| **Abweichung** | **81,71 €, mit falschem Vorzeichen** |
✅ `realisiert + offen` stimmt bitgenau gegen die DB (0,0000). Der Fehler lag
darunter: **die DB stimmte nicht mit dem Broker überein.**

⚠⚠ **URSACHE — eine Race Condition beim Eröffnen**, im Log im Sekundentakt
sichtbar (#49926460): `20:04:10 SELL eröffnet` → `20:04:10 „extern geschlossen"`
→ `20:04:12 „kein OUT-Deal in 1 Deals"` → Fallback bucht **0,00** →
`20:11:42` der ECHTE Close **lief ins Leere**. Drei Defekte griffen ineinander:
**(1)** `positions_get` sieht die frische Position einen Tick lang nicht.
**(2)** `_log_external_close` buchte **trotz** „kein OUT-Deal" einen Close — dabei
ist genau das der Beweis, dass die Position NOCH OFFEN ist.
**(3)** `log_trade_close` fasste nur `exit_time IS NULL` an → die Fehlbuchung
blockierte den echten Close **dauerhaft**.
Bei #49852362 kostete das **+5,87 statt −95,38 €** und buchte den Trade auf den
**falschen Tag**.

✅ **BEHOBEN:** (1) `_log_external_close` bricht bei „kein OUT-Deal" ab (der
Fallback bleibt nur für den wirklich blinden Fall, in dem gar keine Deals
abrufbar sind — dort ist `reconcile_open_trades` das Netz). (2) `log_trade_close`
darf eine **erkennbare Fehlbuchung** korrigieren — eng gefasst: nur wenn die
Zeile `closed_by='unknown'` trägt UND die neue Buchung echte Deal-Daten hat.
✅ **4 Tests** (`tests/test_close_buchung.py`), inkl. der Gegenprobe „eine gute
Zeile wird NICHT überschrieben" — sonst wäre der Fix schlimmer als der Fehler.
⚠ Zwei Testfehlschläge waren **meine**: synthetische Zeitstempel (1000/2000) vor
der Eröffnungszeit lösten die Defensive „exit < entry → jetzt" aus.

✅ **ALTLAST REPARIERT** — `tools/repair_closes.py` (Trockenlauf ist Standard,
Schreiben nur mit `--schreiben`, Backup `…bak-2026-08-12-repair`). Korrigiert
werden **nur eindeutige Fälle**: `closed_by='unknown'` UND echter OUT-Deal beim
Broker. **18 Zeilen** über 60 Tage. Heute: von **+7,63 auf −72,67 €**, Restfehler
**1,41 €**. Gesamt-P&L (`all`): von **−282,78 auf +169,98 €**.
⚠⚠ **ZEITFALLE dabei, zweimal:** `history_deals_get` filtert nach BROKER-Wallclock
— `datetime`-Objekte verschieben das Fenster; und `fromtimestamp(d.time, BROKER)`
rendert **3 h zu spät**. Aufgefallen, weil eine Deal-Zeit „23:11" lautete, das Log
aber „20:11:42 CLOSE" sagte. ➜ **Epochs übergeben (lokal + Offset), beim Lesen
`d.time − Offset`.**
⚠ **20 weitere Trades** weichen um ±1–4 € ab (DB nutzt den Schwebe-P&L im
Moment des Closes, der Broker den Fill). Die Summe hebt sich fast auf (+1,66 €
über 19 Trades) → nur berichtet, **nicht** automatisch korrigiert.

## ✅✅ NETTO-RECHNUNG AUS DEN ECHTEN STEUER-BUCHUNGEN (2026-08-12)

Der seit dem 05.08. dokumentierte Defekt ist behoben. `server._add_net` schaetzte
`net_pnl = total_pnl − gross_win × 26,375 %` und unterstellte, die Quellensteuer
sei endgueltig verloren. Sie wird aber taeglich per `Tax settlement` erstattet.
| Zeitraum | total_pnl | wht (ECHT) | net_pnl | einbehalten / erstattet |
|---|---|---|---|---|
| today | −32,72 | −68,93 | **+36,21** | −21,03 / **+89,96** |
| week | −1.235,26 | −432,88 | −802,38 | −425,71 / +858,59 |
| **all** | **+209,93** | **+28,93** | **+181,00** | −2.754,56 / **+2.725,63** |
⚠⚠ **Vorher stand bei `all` −2.449,95 €** — eine Verzerrung von rund **2.630 €**,
die aus einem profitablen Konto ein verlustreiches machte.
✅ Die Schaetzung der EINBEHALTENEN Summe war fast exakt (2.620 gegen 2.755);
falsch war allein die Annahme, sie bleibe weg — real werden **99 %** erstattet.

**Umsetzung:** `trader.steuer_buchungen(von, bis)` liest die `WHT #…`- und
`Tax settlement`-Deals direkt (position_id == 0, damit sauber von den Trade-Deals
getrennt — kein Doppelzaehlen). Unter `mt5_lock`, **5 min gecacht** (die Abfrage
ueber lange Zeitraeume braucht Sekunden und gehoert nicht in jeden Aufruf).
⚠ **Fail-safe:** antwortet MT5 nicht, faellt es auf die alte Schaetzung zurueck —
das Feld **`wht_quelle`** sagt, welche Zahl drinsteht („echt" / „geschaetzt").
⚠ **KURZE ZEITRAEUME sind verzerrt, in BEIDE Richtungen:** die Erstattung kommt
erst am Folgetag. Bei „today" oben ist `wht` sogar **negativ** (−68,93), weil die
Erstattung von GESTERN heute einging — das Netto liegt damit ueber dem Brutto.
Das ist korrekt gebucht, aber als Tageszahl irrefuehrend. Das Flag `wht_lag`
faengt bisher nur die eine Richtung (einbehalten, nichts erstattet).
⚠ Zeitfalle beachtet: Epochs mit Broker-Offset uebergeben, beim Lesen abziehen.

## ✅ STATISTIK-MODUL: ARITHMETIK KORREKT, NETTO-RECHNUNG WEITER DEFEKT (2026-08-12)

Gegen unabhängige SQL geprüft, alle drei Zeiträume: **Abweichung 0,00 in jeder
Zelle** (n · wins+losses+be = n · gross_win − gross_loss = total_pnl).
| | today | week | all |
|---|---|---|---|
| n | 6 | 91 | 1.267 |
| total_pnl | −72,67 | −1.259,12 | **+169,98** |
✅ Das Modul rechnet also korrekt — es las nur eine fehlerhafte DB (jetzt repariert).

⚠⚠ **Der am 05.08. dokumentierte NETTO-Defekt besteht unverändert — und ist
grösser als damals gemessen:**
| | |
|---|---|
| Modell-Steuerlast (`gross_win × 26,375 %`) | **2.619,93 €** |
| **angezeigtes `net_pnl`** | **−2.449,95 €** |
| ECHT einbehalten / erstattet (90 Tage) | −2.625,89 / **+2.608,83** |
| **tatsächlich verblieben** | **−17,06 €** |
✅ Das Modell schätzt die *einbehaltene* Steuer fast perfekt (2.620 gegen 2.626).
Der Fehler ist die Annahme, sie sei **verloren** — real werden **99 %** erstattet
(am 05.08. waren es 79 %). Die Anzeige macht damit aus **+153 € rund −2.450 €**.
✅✅ **ERLEDIGT am 2026-08-12** (Stand dieser Zeile korrigiert am 17.08. — sie
stand fälschlich noch auf „Noch nicht gebaut"): `trader.steuer_buchungen()` liest
die echten `WHT #…`/`Tax settlement`-Deals (unter `mt5_lock`, 5 min gecacht),
`server._add_net` nutzt sie mit Fail-safe auf die alte Schätzung und dem Feld
**`wht_quelle`** („echt"/„geschätzt"). Ergebnis: `all` von **−2.449,95 auf
+181,00 €**. Details im Abschnitt „NETTO-RECHNUNG AUS DEN ECHTEN
STEUER-BUCHUNGEN".

## ✅ AUTO-SIGNAL-ENTRY (🎯 SIG) KOMPLETT ENTFERNT (User-Entscheidung 2026-08-12)

User: „den Signal-Autotrader brauchen wir im Moment nicht mehr — sollten wir den
inkl. Button entfernen?" **Ja.** Er war seit dem 01.08. aus und ist auf **beiden**
Ausführungs-Achsen gemessen negativ:
| Variante | H1 / H2 |
|---|---|
| Markt-Einstieg | negativ in **allen 8** Feldern (`backtest_auto_signal.py` 30.07., `_norev` 04.08.) |
| ruhende Order, Fill bei BERÜHRUNG | **−0,392/−0,338**, negativ in **allen 6** Feldern (`backtest_signal_touchfill.py` 06.08.) |
⚠⚠ **Der zweite Punkt gibt den Ausschlag:** die ruhende Order — genau der Umbau,
der den Squeeze gerettet hat — macht SIG sogar **schlechter als den
Markt-Einstieg**. Die +0,35/+0,32 aus `backtest_auto_signal_v3.py` galten in
Variante B (Fill beim Close-Bruch) und waren **Look-ahead**; der Aufschlag betrug
0,77–0,85 R. Ursache ist strukturell und nicht reparierbar: `_pend` **wandert**
(neu verankert bei jeder k×ATR-Gegenbewegung, nach Timeout, bei Richtungswechsel)
— „ein Level ist nur dann ein Ausführungs-Vorteil, wenn es STILLSTEHT".
✅ Nebenbei entfällt der **offene Churn-Defekt** (4× setzen/stornieren in 76 s bei
identischem Level, gefunden 06.08., nie behoben).

**Entfernt:** `_check_auto_signal` · `set_auto_signal` · der Signal-Zweig in
`_pending_ziel` · `POST /api/autosignal` · Button 🎯 SIG (HTML + Handler) · die
vier Snapshot-Felder · `signal_pending_entry` und die `auto_signal*`-Leser.
**Geblieben:** die **AUTOSIG_\*-Trades in der DB** (Historie), die Backtests (sie
dokumentieren das Warum), `wave_rec.breakout` (speist `block_reason`) und
`_confirm_breakout` selbst (das ist das Gate, nicht der Trader).
Sicherung: **`.removed_backup/auto_signal_2026-08-12.py`** + Git-Historie
(Vorbild: der Wave-Auto-Trader, 23.07. genauso entfernt).

⚠⚠ **DAS RISIKO WAR NICHT SIG, SONDERN DER GETEILTE MANAGER.** Squeeze und Signal
teilten sich `_pending_ziel` — bewusst, weil zwei getrennte Manager über
`trader.pending_orders()` die Orders des jeweils anderen sehen und sich
**gegenseitig stornieren** würden. Deshalb wurde das Squeeze-Verhalten **VOR** dem
Eingriff in `tests/test_pending_ziel.py` festgenagelt (7 Tests: beide Box-Grenzen ·
nicht-armed · Guard · Feature aus · kein ATR · fehlende Box · ATR-Skalierung) —
sie liefen vorher grün und laufen **unverändert** weiter.
✅ **Zwei Folgefehler fing die Prüfkette sofort:** `ruff F821` meldete ein
verwaistes `_t` (im entfernten Block definiert, zwei Zeilen später noch benutzt),
und `py_compile` ein dangling `if` im runtime_state-Laden. Beide behoben.
✅ **Zwei Tests mitgezogen statt gelöscht:** `test_signal_fill_long` wurde
**umgedreht** — ein Pending-Fill mit unbekannter Quelle darf jetzt NICHT
stillschweigend als Squeeze durchgehen (das wäre Deployment-Drift Fall 8 mit
umgekehrtem Vorzeichen); und die Systemzustand-Zeile prüft nun ausdrücklich, dass
„SIG" **nicht wieder auftaucht**.
✅ **Live verifiziert:** alle vier Snapshot-Felder weg, `/api/autosignal`
antwortet nicht mehr, `auto_squeeze`/`squeeze_pending` unverändert, 67 Tests grün.

## ⚠⚠ WIEDEREINSTIEG NACH S/R-CLOSE — 2-HALBJAHRES-BACKTEST: FÄLLT DURCH (2026-08-12)

`backtest_srclose_reentry.py`, 80k M5-Bars. Nachfolger des Live-Laufs (n=14, von
einem Crash-Tag getragen). **Live-treu**: Level über `core.sr_levels.linien`
(ph/pl GETRENNT geclustert), P(break) mit den **echten** `_PB_*`-Gewichten aus
`core.engine`, Schliess-Moment = am Level (≤0,15×ATR) UND `p_break < 0,35`,
Bruch = **Barrieren-Rennen**, Wiedereinstieg als ruhende Stop-Order mit Fill bei
Berührung, kanonischer Exit, Echtkosten.
| Bestätigung | H1 ØR / 95-%-KI | H2 ØR / 95-%-KI |
|---|---|---|
| **0,3×ATR** | **+0,119** [−0,138 … +0,387] ⚠ | **+0,196** [+0,065 … +0,332] ✅ |
| 0,5×ATR | −0,043 [−0,289 … +0,212] ⚠ | +0,043 [−0,086 … +0,174] ⚠ |
| 0,7×ATR | +0,079 [−0,161 … +0,334] ⚠ | +0,014 [−0,110 … +0,134] ⚠ |
✅ **Bruchquote 32–45 %** — nahe der Basisrate (~38 %). Die Plausibilitätsprüfung
besteht; die Bruch-Definition ist diesmal richtig.

❌ **Die vorab fixierte Regel ist in KEINER Zeile erfüllt.** Bei 0,3 ist der ØR
zwar beidhälftig positiv, aber H1s Intervall enthält die Null — und der Nachbar
0,5 dreht H1 ins Negative. Keine Parameter-Robustheit.
⚠ **Fairerweise:** zwei Hälften, die in dieselbe Richtung zeigen, sind mehr als
eine — „KI enthält 0" heisst **nicht widerlegt, sondern nicht belegt**. Und H1
ist dünn (285 Closes gegen 1.168 in H2; der ATR-Floor filtert die Niedrig-Vola-
Periode stärker).
✅ **Kohärenter Nebenbefund:** die ENGERE Bestätigung (0,3) ist besser als die
weiteren — dieselbe Mechanik wie bei `breakout_k` („höheres k = späterer
Einstieg, Anfang des Moves verschenkt"). Wer auf einen „richtigen" Bruch wartet,
zahlt ihn.

✅ **URTEIL: nicht bauen.** Bedingung für eine Wiedervorlage: die 0,3-Variante
muss auch in H1 ein Intervall über null liefern — dafür braucht es mehr
Niedrig-Vola-Daten, nicht mehr Ideen.
⚠ Nicht modelliert: der Positions-Slot ist während des Wiedereinstiegs belegt.

## ⚠⚠ WIEDEREINSTIEG NACH EINEM „FALSCHEN" S/R-CLOSE — gemessen, NICHT gebaut (2026-08-12)

User: „falls die Linie trotz Abprall-Berechnung durchbrochen wird — logisch wäre,
den Trade wieder neu zu starten, oder?"
⚠ Der Fall ist **kein Randfall**: P(break) liegt live bei 38,7 %, das Gate
schliesst 94–97 % aller Berührungen — in rund vier von zehn Closes bricht die
Linie doch. `analyze_srclose_reentry.py`, 43 echte Closes ab dem Nachtrainieren.

⚠⚠ **ERST EIN EIGENER MESSFEHLER, und er ist die halbe Lehre.** Der erste Lauf
meldete **93 % Brüche** bei einer Basisrate von 38,7 %. Ursache: ich prüfte nur
EINE Barriere („erreicht der Kurs Level +0,5×ATR?"). Das Training entscheidet per
**Rennen** — Bestätigung *gegen* Ablehnung, wer zuerst kommt. Eine 0,5×ATR-
Bewegung binnen 60 min passiert fast immer. Mit dem Rennen: **33 % Brüche**,
praktisch die Basisrate. ✅ Die Plausibilitätsprüfung („die Bruchquote MUSS nahe
38 % liegen") hat den Fehler gefangen.

**Ergebnis (ruhende Stop-Order an der Bestätigungsmarke, kanonischer Exit):**
| | |
|---|---|
| n | 14 |
| Trefferquote | 71 % |
| ØR | **+0,859** |
| **95-%-KI** | **[−0,084 … +1,788]** ⚠ enthält die Null |
| grob EUR | +501 |
⚠⚠ **ABER: drei Trades tragen alles.** Die drei grössten Gewinne stammen ALLE vom
**04.08. 13:06/13:10/13:44** und liegen exakt am TP-Deckel (+3,44/+3,44/+3,45 R):
| | |
|---|---|
| diese drei | **+529,92 €** |
| **die übrigen 11** | **−28,63 €** |
Der 04.08. ist der dokumentierte Ausreisser-Tag (WTI −4,12 $, davon −4,16 $ in
EINER Stunde). **Ohne ihn ist die Regel negativ.**

✅ **URTEIL: nicht bauen — aber die beste Idee seit Längerem.** Die vorab fixierte
Regel (ΣR positiv UND KI ohne Null) ist verfehlt. ⚠ Dazu die dokumentierte
Gegenevidenz: der **Wiedereinstiegs-Cooldown** (`_SQUEEZE_SR_COOLDOWN_S`=180 s,
seit 17.07.) wurde gebaut, WEIL ein sofortiger Gleichrichtungs-Wiedereinstieg
nach einem S/R-Close real schädlich war (1-Sekunden-Roundtrip, doppelter Spread).
⚠ Fairerweise: das ist nicht dasselbe — gemessen wurde hier der **bestätigte**
Bruch, nicht der ungegatete Sofort-Wiedereinstieg. Der Cooldown widerlegt die
Idee also nicht, er warnt nur vor ihrer schlampigen Variante.
✅ **Der saubere nächste Schritt** ist ein Backtest über 2 Halbjahre statt 43
Live-Closes — die Maschinerie existiert (`backtest_pbreak_rvalue.py` simuliert
den P(break)-Close bereits). Erst dann ist die Frage entscheidbar.

## ⚠⚠ DAS FLACKERN DER M15-KARTE — Ursache gemessen, Hysterese eingebaut (2026-08-12)

Die Karte wechselte den Zustand **alle 12 Sekunden** (~1.030/Tag). Das ist der
Grund, warum ihre Selbstmessung so stark geclustert war (roh 60 %, entkoppelt
53 %).

⚠⚠ **MEIN ERSTER VERDACHT WAR FALSCH — und die Messung war schnell.** Ich hatte
das Level im Wechsel-Schlüssel (`round(level, 3)`) im Verdacht. Ausgezählt über
3.525 Zeilen:
| Was ändert sich zwischen zwei Zeilen? | Anteil |
|---|---|
| **`status`** | 32,9 % |
| **`lesart`** | 32,7 % |
| lesart+level | 20,3 % |
| **level allein** | **7,6 %** |
Das Level aus dem Schlüssel zu nehmen brächte 3.525 → 3.248 Zeilen — praktisch
nichts. **Der Zustand selbst zappelt**: `gap ≤ 0,5` und das 45/55-Band von
P(break) sind stetige Grössen, die um ihre Schwelle pendeln.

✅ **Das Projekt hatte die Lösung schon** (`analyze_pbreak_flicker.py`, 14.07.:
Zeit-Glättung + **Hysterese ±5 Pp**). Die Karte umging sie, weil sie P(break)
selbst neu rechnet. Jetzt dieselbe Hysterese auf beide Schwellen:
· **Status**: grün ab `gap ≤ 0,50`, zurück auf gelb erst **über 0,60**
· **Lesart**: gerichtet erst ausserhalb **40/60**, zurück auf „unklar" erst
  innerhalb **45/55**
Simuliert an 4.000 Ticks direkt auf den Schwellen: **3.084 → 1.103 Wechsel
= 64 % weniger.**

⚠⚠ **ZWEITER, GRÖSSERER BEFUND: Status und Lesart wurden an DREI Stellen
unabhängig gebildet** — Frontend, `_log_m15_state` und die Karte selbst. **Sie
wichen bereits ab**: der Logger kannte den „kein Veto"-Fall des Frontends nicht,
hat also einen anderen Zustand protokolliert als angezeigt wurde. Genau die
Divergenz-Falle, die im Projekt schon dreimal zugeschlagen hat (`sim_run` in ≥6
Skripten · der nachgebaute Level-Cluster · das fünfte Exit-Modell).
✅ Jetzt **EINE Quelle**: `_m15_setup` liefert `status`/`lesart`/`nah` im
Snapshot, Logger und Frontend lesen sie. Anzeige und Messung können nicht mehr
auseinanderlaufen.

⚠ **Die alten 3.525 Zeilen sind mit den neuen NICHT vergleichbar** — sie wurden
unter dem flackernden Regime geschrieben. Die Selbstmessung beginnt faktisch neu;
das ist kein Verlust, weil sie vorher ohnehin kein Urteil zuliess (KI enthielt
die 50). Stichtag: 12.08.

## ⚠⚠ DIE M15-KARTE HAT SICH ZUM ERSTEN MAL SELBST GEMESSEN (2026-08-12)

Anlass: User-Frage „könntest du mir auch eine **M5** · Setup-Bereitschaft bauen?
Ist das sinnvoll?" — die Antwort hängt daran, ob die BESTEHENDE Karte trägt.
Seit dem 08.08. misst sie sich selbst (`m15_states`), 2.346 ausgewertete Zustände.

⚠⚠ **DIE KARTE ZEIGTE 60 % — UND DAS WAR IRREFÜHREND.**
| | |
|---|---|
| Anzeige (roh, letzte 200 Zeilen) | **60 %** |
| **entkoppelt** (max 1 je Level+Stunde) | **53–54 %**, KI **[46 … 63 %]** ⚠ enthält 50 |
| davon **grün + gerichtet** (der handlungsrelevante Zustand) | **45,5 %** |
Zwei unabhängige Gründe:
**(1) Geclustert.** `m15_accuracy` nahm `ORDER BY id DESC LIMIT 200` ohne
Entkopplung. Der Zustand wechselt **~990×/Tag** — 200 Zeilen sind rund fünf
Stunden DERSELBEN Marktlage. Aus 2.302 rohen Zeilen bleiben **114 unabhängige**.
**(2) Regime.** Das Messfenster (10.–12.08.) lief **+4,93 $** (78,74 → 83,68).
Roh liest die Karte „auf" zu **62 %** richtig und „ab" zu **46 %** — das ist der
Trend, nicht die Karte.
✅ **BEHOBEN:** `m15_accuracy` entkoppelt jetzt (Schlüssel Level+Stunde, wie
`core.stichprobe.schluessel_level_stunde`) und meldet beide Zahlen —
„114 unabh. Zustände aus 2302". Angezeigt werden jetzt **53 %** statt 60 %.
⚠ Dieselbe Cluster-Falle hat im Projekt schon die Fälligkeitsbedingung
`pbreak_accuracy_v2` um das ZEHNFACHE danebenliegen lassen und am 07.08. elf
Hypothesen ausgelöst. Sie ist hier dieselbe — und sie steckte in einer Zeile,
die ich am 08.08. selbst gebaut habe.

✅ **ANTWORT AUF DIE M5-FRAGE: NEIN, jetzt nicht.**
· **Die bestehende Karte ist nicht belegt** (KI enthält 50, handlungsrelevanter
  Zustand 45,5 %). Eine zweite nach demselben Muster zu bauen, bevor die erste
  ein Urteil hat, ist genau das Vorgehen, das dieses Projekt sonst zurückweist.
· **M5 wäre teurer**: Spread/ATR **0,193** gegen 0,104 auf M15 — die Kostenseite
  ist der einzige gemessene Vorteil der M15-Karte.
· **Die M5-Information ist grösstenteils schon da**: der Squeeze-Zustand
  (armed/active) steht in der Meldungen-Karte, Gate ③b rechnet **bereits auf M5**
  (`room_info`), ebenso die P(break)-Merkmale. Die „M15-Karte" ist längst ein
  Hybrid (M30-Level · ATR_M15 · M5-Raum/P(break)).
· **Doppelte Drift-Fläche**: allein diese Woche steckten in der EINEN Karte drei
  Defekte (Bias als Richtungsgeber · Ziel in der verbotenen Richtung · fehlende
  Raum-Zeile). Eine zweite verdoppelt die Stellen für die Veto→Richtung-Falle.
⚠ **OFFEN, benannt:** die Karte **flackert** — ~990 Zustandswechsel/Tag, also
alle 1,5 min. Das ist selbst ein Befund und der nächste sinnvolle Schritt.
✅ **Bedingung für ein Ja:** wenn die entkoppelte Trefferquote bei ≳350 Fällen
(dieselbe Latte wie `pbreak_accuracy_v2`) über 50 % liegt und das Intervall die
Null-Linie ausschliesst. Vorher ist eine zweite Karte Zierde, kein Werkzeug.

## ✅✅ SYSTEMZUSTAND IM TAGESREPORT (2026-08-11) — der fehlende letzte Meter

**Selbständige Empfehlung nach der Gesamtprüfung, vom User beauftragt.**

⚠⚠ **DER BEFUND: die Erkennung funktioniert, die ZUSTELLUNG nicht.** Das Projekt
hat sieben Wächter, jeden nach einem eigenen Vorfall gebaut — und jeder tut, was
er soll. Trotzdem blieben allein in dieser Woche **sechs** Zustände tagelang
unbemerkt, **vier davon waren bereits erkannt**:
| Vorfall | unbemerkt | erkannt? |
|---|---|---|
| `auto_sr_close` aus | **5 Tage** | ✅ 1× gemeldet, dann Marker → Stille |
| `auto_squeeze` aus | seit 11.08. | ✅ — Marker von **02.08.** → **gar keine** Meldung |
| `rec_outcomes` tot | **5 Tage** | ❌ (Puls-Wächter heute gebaut) |
| Telegram still | **4 Tage** | ❌ |
| Divergenz-Wächter Dauerwarnung | **6 Tage** | er *war* der Wächter |
| `squeeze_entry_gap` 8/12 | seit 05.08. | ✅ „sammelt noch" |

✅ **BELEGT AN DEN MARKER-DATEIEN:** `cfg_auto_sr_close` trägt **06.08. 18:10**,
`cfg_auto_squeeze` trägt **02.08. 18:00**. Der Config-Wächter meldet je Schlüssel
**genau einmal** und schweigt danach für immer. `auto_sr_close` war damit fünf
Tage aus — und wurde am 11.08. mit **+350,81 € über 43 Trades** gemessen
(grob 150–300 € entgangener Wert, je nach Vergleichbarkeit der Bedingungen).
⚠⚠ **Die Ursache ist ein Kategorienfehler:** die Marker-Logik behandelt einen
**Dauerzustand** wie ein **Einmal-Ereignis**.

✅ **GEBAUT: `engine._system_status()` → Block im Tagesreport (07:30, Telegram +
E-Mail).** Der Report ist der einzige Kanal, der nachweislich **jeden Tag**
ankommt (Telegram-Log lückenlos `ok`); der Popup verstummt nach dem ersten Mal.
**Saubere Trennung: Popup = „etwas Neues ist passiert", Tagesreport = „so steht
das System gerade".**
Realer erster Block:
```
⚙ Systemzustand:
  Automatik: BRK aus · SIG aus · S/R-Close AN · Flip aus
  ⚠ Puls: rec_outcomes: 0 Zeilen — Logger hat NIE geschrieben
  ⚠ Config: auto_squeeze steht auf 'false' (validiert: 'true')
  Messungen: pbreak_accuracy_v2 143/350 · squeeze_b5 11/20 · squeeze_entry_gap 8/12
```
⚠ **Reine Anzeige** — es wird nichts geschaltet und nichts entschieden.
⚠ **`measurement_reminder` wird LAZY und GEKAPSELT importiert** (1×/Tag, nicht im
Live-Pfad): ein Auswertungs-Skript gehört NICHT in den Live-Abhängigkeitsgraphen
— derselbe Fehler wie der `backtest_breakout_squeeze`-Import. Ein Fehlschlag wird
**sichtbar** gemeldet, nicht still verschluckt (ein fehlender Block sähe aus wie
„alles gut" — exakt die Klasse, die `rec_outcomes` fünf Tage verbarg).
✅ **`telemetrie_puls()` liegt in `core/history.py`** (DB-Schicht), **beide**
Nutzer rufen dieselbe Funktion — Skripte importieren `core`, nie umgekehrt.
✅ **7 Tests** (`tests/test_system_status.py`), Schwerpunkt auf den **Ausfallpfaden**:
ein Report, der wegen der Statuszeile gar nicht ankommt, wäre schlimmer als keine
Statuszeile. Geprüft: kein Befund wird ausdrücklich bestätigt · ein Puls-Fehler
wird gemeldet statt verschluckt · ohne `history` kein Absturz · die Methode wirft
**nie** und liefert immer mindestens eine Zeile · die Puls-Tabellenliste ist
vollständig.
⚠ **Verifikation der Zustellung steht aus** — sie erfolgt mit dem Report am
nächsten Morgen um 07:30. Die Bausteine sind einzeln live gegengeprüft.

## Gesamtprüfung 2026-08-11 (nach `docs/review-prompt.md`)

Zweiter vollständiger Durchlauf. **Drei echte Defekte**, alle behoben; keine
Strategie-Änderung.

⚠⚠ **BEFUND 1 — `rec_outcomes` war seit dem Einbau (06.08.) STILL TOT: 0 Zeilen
in 5 Tagen.** `engine._run_analysis` las `s.get("pb_feats")` — `s` ist aber der
**MT5Data**-Snapshot (Preis/Trend/Konto); `pb_feats` lebt im **Wave**-Snapshot.
Ergebnis: `_atr` immer 0,0 → die Schreibbedingung `_atr > 0` nie erfüllt.
✅ **Live belegt:** `market` hat **kein** `pb_feats`, `wave` hat es (ATR 0,12).
⚠⚠ **KORREKTUR 2026-08-12: der Fix hat NICHT gewirkt.** Nach dem Neustart am
11.08. 22:45 gab es bis 09:00 mindestens **10 gerichtete Episoden** — `rec_outcomes`
stand weiter bei **0 Zeilen**. Die Prämisse (falsches dict) ist belegt, die
**Wirkung war es nicht** — ich habe „behoben" geschrieben, ohne sie zu prüfen.
⚠ Warum es von aussen nicht feststellbar war: der `except`-Zweig loggte auf
**DEBUG**, der Logger steht auf INFO — genau die Blindheit, die den Ausfall fünf
Tage getragen hat. **Jetzt instrumentiert** (2026-08-12): Erfolg UND Fehlschlag
melden sich auf INFO/WARNING, inkl. `atr`/`px`/`pb_feats`-Zustand. Es feuert nur
bei einem echten Richtungswechsel, ist also kein Lärm. Die Ursache ist **offen**
bis zur nächsten Episode.
✅ Der **Telemetrie-Puls** meldet den Zustand seitdem täglich — er ist der Grund,
dass die ausbleibende Wirkung überhaupt auffiel.
⚠ **Warum keine Prüfung es fing:** es ist **kein** `NameError` (ruff F821 sieht
nichts), sondern ein falsches dict — syntaktisch einwandfrei. Und das umgebende
`except` loggt auf **DEBUG**, der Logger steht auf INFO. Exakt die Fehlerklasse,
vor der der `wave_snap`-Fallstrick vom 04.08. schon einmal gewarnt hat.
✅ Aufgefallen NUR, weil die Tabelle in Durchgang 4 mit 0 Zeilen **neben ihren
Schwestern** stand (`m15_states` 3.143 · `cone_checks` 263 ·
`pbreak_predictions` 3.775). Damit hat die Selbst-Kalibrierung der Empfehlung
fünf Tage lang nichts gemessen.
✅✅ **GENERALISIERT: `measurement_reminder.telemetrie_puls()`** — jede
Telemetrie-Tabelle muss einen PULS haben; schweigt eine, während die anderen
schreiben, ist der Logger tot. 7 Tabellen überwacht, Toleranz Faktor 20 der
erwarteten Kadenz (ein Wächter, der oft warnt, wird ignoriert). ⚠
`candles_m1.time` ist ROHE Brokerzeit → Offset abgezogen, sonst 3 h Fehlalarm.
**Erster Lauf meldet genau diesen einen Fund**, alle übrigen grün.

⚠⚠ **BEFUND 2 — DER DIVERGENZ-WÄCHTER WAR SELBST DRIFTEND.** Er verglich
Abschnitt A gegen **43 % WARTEN** — ein Maßstab, den CLAUDE.md seit dem
**2026-08-05 ausdrücklich für ungültig erklärt** (`backtest_dist.py` misst
`_build` ALLEIN, ohne Breakout-Bestätigung und Entry-Raum-Gate; mit vollem
Live-Stack sind es **80,5 %**). Zusätzlich zählte er `entry_room` als
„live-only", obwohl es seit dem 04./05.08. in `backtest_breakout_gated.py` mit
dem **echten** `_room_gate` modelliert wird.
| | vorher | nachher |
|---|---|---|
| A) Abweichung WARTEN | **+50,8 Pp ⚠** | **+13,3 Pp OK** |
| B) „Nur-Live-Gates" | **63,9 %** | **5,0 %** |
Der Wächter behauptete also einen Deployment-Drift, den es dort nicht gibt —
und „ein Wächter, der immer warnt, wird ignoriert" ist die eigene Lehre vom
02.08. (damals für die Modell-Epochen, hier für die Konstanten).

✅ **BEFUND 3 — P(break) ist wieder kalibriert.** Δ **−1,7 Pp** (Vorhersage
40,4 %, real 38,7 %, n=106 entkoppelt) nach **+14,3 Pp** am 07.08. Das stützt
den damaligen Schluss, dass die „gewanderte Basisrate" ein **Zeitraum-Effekt**
war, kein Modellversagen.

**Sauber (keine Befunde):** `check_nfalle` · `ruff F821` · 53 Tests ·
`node --check` · **0 fehlende Frontend-IDs** von 93 · der einzige Thread-Aufruf
von `refresh_market` nimmt `mt5_lock` selbst · alle `.time`-Nutzungen sind
Vergleiche unter Broker-Zeiten oder die dokumentierte `candles_m1`-Ausnahme ·
die 3 Ticket-Wechsel-Blöcke haben ihre Retry-Wächter · TF-Churn Median 23 min.

**Performance:** Snapshot-Median **15,9 ms** (p90 19,4), gegen 13 ms am 02.08. —
der Zuwachs deckt sich mit den seither gebauten Feldern und liegt weit unter dem
1-s-Budget. Alle heißen Abfragen laufen über einen Index (`recommendations`
1,2 ms · `candles_m1` über den PRIMARY KEY 1,9 ms · `trades` 0,1 ms).
**Keine Optimierung nötig** — Micro-Tuning ohne Messung wäre hier wertlos.

⚠ **Toter Code: keine Löschempfehlung.** 13 Snapshot-Felder haben keinen Leser
in `app.js` — davon habe ich am selben Tag **vier** benutzt, um Deployments zu
verifizieren (`close_notify_min`, `squeeze_pending`, `squeeze_night_hours`,
`squeeze_max_chase_atr`). Der Snapshot ist die **Diagnose-Oberfläche** und laut
`tools/deploy.py` Schritt 4 der einzige belastbare Beweis, dass neuer Code
läuft — sie zu entfernen würde die Verifikation kaputtmachen. Einzig
`cost_ratio` ist ein echter Waise (speiste den Kosten-Chip in `vd-consensus`,
den es nicht mehr gibt); er kostet eine Division und bleibt.
⚠ **Bekannt und unverändert:** 47 Backtests haben eine eigene Exit-Kopie (36
nutzen `core/exit_model`), `_atr_series` existiert in **86** Dateien. Umstellen
verletzt die Migrations-Regel — ihre publizierten Zahlen würden sich
verschieben. Alle 2026-08-11 neu gebauten Skripte nutzen den geteilten Kern.
⚠ **Benannt, nicht geändert:** `auto_squeeze` steht in `runtime_state.json` auf
`false`, in der ini auf `true`. Verhalten ist korrekt (runtime gewinnt), der
Zustand aber irreführend — wird `runtime_state.json` gelöscht, schaltet sich der
autonome Einstieg wieder ein. **User-Entscheidung vom 11.08., 14:10.**

## 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-`NULL`s 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 35–250 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:30–16:30 (gemessen: beidhälftig negativ) |
| ATR-basierter Stop + Puffer | Initial-SL-Band 1,8–2,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: 21–22 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 13–38 %) ·
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):** 1–2 % 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.
