# 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 ``**: ```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 `; 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 # 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)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_` (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. - **⚠⚠ 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)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. ## 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 `