From 880bf1075e6b2868de236afce69d96127dfe8bb7 Mon Sep 17 00:00:00 2001 From: Axel Hocks Date: Wed, 19 Aug 2026 17:04:56 +0200 Subject: [PATCH] CLAUDE.md wiederhergestellt + UI/Telegram-Aenderungen dokumentiert Der vorige Commit hat CLAUDE.md mit 0 Bytes eingecheckt: ein Emoji als zwei getrennte \u-Escapes erzeugt in Python lone surrogates, die sich nicht nach UTF-8 kodieren lassen -- und io.open(p,'w') kuerzt die Datei VOR dem Schreiben. Der pre-commit-Hook prueft Python und JS, nicht die Doku, also ging der Commit gruen durch. Inhalt aus HEAD~1 wiederhergestellt. Zweiter Totalverlust derselben Ursache am selben Tag (vorher web/app.js). Regel ab hier ausnahmslos: keine Emoji als getrennte \u-Surrogate; immer erst nach .tmp schreiben, Groesse gegen das Original pruefen, dann os.replace. Eine Syntaxpruefung ersetzt die Groessenpruefung nicht -- eine leere Datei besteht node --check und jeden Hook. Inhaltlich dokumentiert: Einsatz-Felder entfernt, BRK-Schalter durch Circuit-Breaker-Schalter ersetzt, Telegram auf 'Trade geschlossen' reduziert (eine Filterstelle, ein Bypass fuer die FEHLGESCHLAGEN-Meldung). Co-Authored-By: Claude Opus 5 --- CLAUDE.md | 8302 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 8302 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index e69de29..eb0ed93 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -0,0 +1,8302 @@ +# 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. +- **⚠⚠ ZUVERLÄSSIGKEITS-PRÜFUNG 2026-08-11 (`backtest_squeeze_woche.py`, User: + „funktioniert der Breakout-Auto-Trader zuverlässig? simuliere die letzte Woche").** + ⚠⚠ **BEFUND 0 beantwortet die Frage vorweg: DER TRADER IST AUS.** + `runtime_state.json` enthält `"auto_squeeze": false` und **überschreibt die ini** + (`= true`) beim Start — dieselbe Falle wie am 31.07. bei `auto_signal`, nur mit + umgekehrtem Vorzeichen. Letzter Squeeze-Trade **06.08. 13:52**, + `squeeze_entry_count = 0`. ✅ Der Config-Wächter meldet es korrekt („ist 'false' + — validiert gegen 'true'"); es wurde nur nicht gehandelt. Zurück: 🚀 BRK. + ✅ **BEFUND 1 — die MECHANIK arbeitet korrekt.** Die Simulation nutzt die echte + `_squeeze_one` und reproduziert **10 von 11 Live-Trades binnen 10 min und auf + wenige Cent genau** (live 06.08 02:11 SELL 75,127 ↔ Sim 02:10 SHORT 75,132). + Die Erkennung ist nicht das Problem. + **BEFUND 2 — die Woche** (ruhende Stop-Order, Fill bei BERÜHRUNG, Nacht 3–6 + gesperrt, EIN Slot, kanonischer Exit, Echtkosten): + | Fill-Slippage | n | Treffer | ØR | PF | ΣR | + |---|---|---|---|---|---| + | **0,00×ATR** | 47 | 57 % | **+0,196** | 1,37 | +9,20 | + | 0,10×ATR | 47 | 57 % | +0,100 | 1,17 | +4,69 | + | **0,20×ATR** | 46 | 50 % | **−0,013** | 0,98 | −0,58 | + ≈ **+610 €** bei 2,5 Lots, ØR im validierten Band (+0,14…+0,23). ⚠⚠ **Drei + Vorbehalte:** **(1)** die Sim **überhandelt um das 2,4-fache** (26 statt 11 + Einstiege in der An-Phase) — ihr fehlen Live-Dedup (dir, level), 3-min- + Wiedereinstiegssperre und S/R-Cooldown; ΣR ist nach oben verzerrt. **(2)** der + Edge **stirbt bei 0,20×ATR Fill-Slippage**, und live lag der Abstand + Einstieg↔Level vor dem Pending-Umbau bei Median **+0,275×ATR** — alles hängt + daran, ob die ruhenden Orders am Level füllen (`squeeze_entry_gap`: **8/12**). + **(3)** eine Woche ist Rauschen — die Phase, in der der Trader **AUS** war, ist + sogar die bessere (ØR +0,287 gegen +0,122). + ⚠⚠ **BEFUND 3 — der GEPAARTE Vergleich widerspricht dem B4-Befund vom 01.08.** + Auf **denselben 10 Einstiegen**: **LIVE −109,52 €** gegen **kanonischer Exit + −179,27 €** — der Live-Exit war **70 € BESSER**. Am 01.08. hiess es „die Lücke + entsteht im EXIT"; hier ist es umgekehrt. Im Detail rettete der **S/R-Close** + drei Trades (05.08 08:33 **+12,56 gegen −113,64** · 06.08 13:51/52 je + **+0,26/+2,88 gegen −86,38**), während der kanonische Exit bei 06.08 09:04 + (**−46,82 gegen −175,21**) und 02:11 (**+23,83 gegen −43,29**) klar besser war. + **Gemischt, n=10, von wenigen Trades dominiert — kein Urteil, aber ein + Warnschild gegen den umgekehrten Schluss.** + **BEFUND 4 — B5: 11 von 20 Trades (55 %).** Beide Bedingungen sind bereits weit + verletzt (je Lot ØGewinn 5,20 · ØVerlust 31,12 → **Verhältnis 0,17**, **PF + 0,45**) bei **73 % Trefferquote** — die dokumentierte Squeeze-Signatur. ⚠ Formal + **nicht fällig**; die Latte nachträglich zu senken wäre genau der Fehler, gegen + den sie vorab fixiert wurde. + ⚠ **OFFEN, nicht verifiziert:** am 06.08. **13:51 und 13:52** stehen ZWEI + Live-BUYs zum identischen Preis 76,315 auf EINEN Ausbruch (beide Pending-Fills, + beide binnen einer Minute per `sr_close` geschlossen) — sieht nach + **Doppel-Einstieg auf ein Signal** aus. Ungeprüft, ob Defekt im OCO-Ersatz oder + Teilfüllung. +- **⚠⚠ AUTO-SQUEEZE B4-BEFUND 2026-08-01: der EINSTIEG ist intakt, der EXIT hat den + Edge gefressen — deshalb NICHT zurückgebaut, aber mit vorab fixierter Abbruchregel.** + Anlass war die User-Frage zu den negativen Auto-Trades vom 31.07. Der B4-Monitor + trennt beides sauber: + | | Trefferquote | ØR | PF | + |---|---|---|---| + | **Mechanik** (Regel neu gerechnet, Backtest-Exit = isolierter Einstieg) | 46 % | **+0,102** | 1,15 | + | **Live** (n=31, 17.–31.07., Gesamtsystem mit echten Exits) | 45 % | **−0,242** | 0,49 | + Die **Trefferquote stimmt praktisch überein (46 vs 45 %)** — das Setup findet also + weiterhin, was es finden soll. Die Lücke von **0,34 R/Trade** entsteht komplett im + Exit: Ø-Gewinn **+11,02 €** gegen Ø-Verlust **−19,83 €** (Verhältnis 0,56, ΣEUR + **−162,99**). Der Squeeze ist runner-abhängig (43 % Treffer tragen nur, wenn die + Gewinner deutlich größer sind) — genau die Läufer wurden gekappt. + **Alle drei Ursachen sind am 31.07. behoben worden**, der Live-Zeitraum liegt also + fast vollständig VOR den Reparaturen: (a) das **P(break)-Modell** schloss 91 % aller + Level-Berührungen = faktisch pauschaler S/R-Close, und der ist 2× als Gewinner-Kappen + gemessen → nachtrainiert; (b) die **15-Min-Regel** schnitt Squeeze-Trades, auf dieser + Population in beiden Hälften schädlich → aus; (c) **Trailing 1,5–3,0 je nach TF** + statt der gemessenen 1,5 (Deployment-Drift Fall 3) → einheitlich 1,0. + ⚠ **Ehrlich:** die Mechanik liegt mit ØR +0,102 **unter** dem validierten Band + +0,14…+0,23 — der Einstieg ist also auch selbst schwächer geworden (Regime). Das ist + kein Freibrief, sondern der Grund für eine harte Latte statt unbegrenztem Zuwarten. + ✅ **B5-ABBRUCHREGEL, VORAB FESTGELEGT (User-Zustimmung 2026-08-01):** nach **≥20 + Squeeze-Trades ab dem 01.08.** — **Verhältnis (ØGewinn/ØVerlust) < 1,0 ODER PF < 1 + → `auto_squeeze=false`.** Bewusst VOR der Beobachtung fixiert, damit die Latte + hinterher nicht verschoben wird; überwacht als Messung `squeeze_b5` im + `measurement_reminder.py`. Nacht-Anteil der Altdaten: 12 der 31 Trades / −78,92 € — + die stammen aus der Zeit vor dem Nacht-Guard (seit 24.07. wieder an). +- **⚠⚠ DEPLOYMENT-DRIFT FALL 7 — DER SQUEEZE WIRD ZU EINEM PREIS GEHANDELT, DEN DIE + VALIDIERUNG NIE UNTERSTELLT HAT (gemessen `backtest_squeeze_entry.py`, 2026-08-05).** + Anlass: User-Frage zu einem Auto-Trade, der „am höchsten Punkt einer aufsteigenden + Kerze" eröffnet wurde (05.08. 08:33:20 — Level 75,757, Einstieg **75,822**, also + über dem Hoch 75,808 der laufenden M5-Kerze; die schloss dann bei 75,693). + **`backtest_breakout_squeeze.py` steigt AM LEVEL ein** (`sim(lvl, …)`). Live schickt + `_check_auto_squeeze` eine **MARKET**-Order, sobald `state=='active'` — der Kurs ist + dann schon ein Stück weiter. **Aus 34 echten Live-Entries (Logs 16.07.–05.08., + Level aus der Log-Zeile × Einstiegspreis aus der DB): Median +0,275×ATR über dem + Level**, Mittel +0,686 · 75 % +0,744 · Max +4,339; nur **18 %** liegen innerhalb + 0,10×ATR, **53 % über 0,30×ATR**. + **Was dieser Preisunterschied kostet** (80k M5, 2 Halbjahre, sequentiell EIN Slot, + kanonischer Exit, Echtkosten): + | Variante | H1 ØR / ΣR | H2 ØR / ΣR | WR | + |---|---|---|---| + | **A Einstieg AM LEVEL** (Backtest-Annahme) | **+0,456 / +369,8** | **+0,611 / +777,8** | 56/62 % | + | **B Einstieg am Bar-Close** (≈ live) | **−0,158 / −125,9** | **−0,006 / −7,6** | 37/42 % | + **Allein der Einstiegspreis dreht das Setup — −0,61 ØR in BEIDEN Hälften, WR bricht + von 56 auf 37 %.** Modell B ist dabei **nicht pessimistisch**: sein Median-Abstand + (0,38×ATR) liegt nah am LIVE gemessenen (0,28×ATR); der Live-Betrieb liegt also + ~80 % des Weges von A nach B, also grob bei **breakeven bis leicht negativ** — + nicht bei den dokumentierten +0,14…+0,23. + ⚠⚠ **Das ordnet den B4-Befund vom 01.08. NEU ein.** Dort stand: „Mechanik +0,102 vs. + Live −0,242 → **die Lücke entsteht im EXIT**". Die Mechanik-Zahl unterstellt aber + denselben Level-Einstieg. Der Einstiegspreis erklärt den Großteil der 0,34-R-Lücke + — **es war überwiegend der ENTRY, nicht der Exit.** + ⚠ **BEIDE naheliegenden Reparaturen fallen durch** (Regel vorab fixiert: ΣR und ØR + in beiden Hälften > B, Nachbarn ebenso): + · **Abstands-Bremse** („überspringen, wenn der Kurs schon > X×ATR über dem Level + steht", X = 0,10…0,40) — **alle 5 Schwellen durch.** Kontraintuitiv, aber konsistent: + die *nahen* Ausbrüche sind pro Trade die SCHLECHTEREN (0,10×ATR → ØR −0,254), weil ein + gerade eben überschrittenes Level oft ein Fehlausbruch ist (54 % der Box-Ausbrüche + kehren binnen 3 Bars zurück, `backtest_consolidation.py`). Die Bremse senkt den + Gesamtverlust nur, indem sie weniger handelt. + · **LIMIT-Order am Level** (Retest, 3/6/12 Bars Gültigkeit, ±0,1×ATR Puffer) — + **alle 5 Varianten SCHLECHTER als Market** (ØR −0,14…−0,27 gegen −0,006…−0,158); + 151–430 Ausbrüche werden dabei verpasst. Deckt sich mit dem Retest-Befund aus + `backtest_consolidation.py`. + ✅ **Schlussfolgerung: Variante A war nie erreichbar** — sie unterstellt einen Fill zu + einem Preis, den der Markt bereits verlassen hat. Die Frage ist damit nicht „wie + steigt man klüger ein", sondern **ob der Auto-Squeeze überhaupt bleibt**. Das ist die + bereits fixierte **B5-Abbruchregel** (≥20 Squeeze-Trades ab 01.08.; Verhältnis < 1,0 + ODER PF < 1 → `auto_squeeze=false`) — dieser Befund stärkt sie erheblich. + **Nichts abgeschaltet** — Autonomie-Schalter sind User-Entscheidungen. + ⚠ Grenzen: die Live-Stichprobe ist n=34 mit genäherter ATR-Zuordnung; A und B sind + zwei Modell-Enden, der Live-Betrieb liegt dazwischen (näher an B). + ✅✅ **LÖSUNG GEFUNDEN — RUHENDE STOP-ORDER STATT MARKET (Variante E, gemessen + 2026-08-05; Anstoß aus der Web-Recherche: „don't chase breakouts with market orders; + place orders just beyond the breakout level").** Eine PENDING `BUY_STOP`/`SELL_STOP` + liegt bereits am Ausbruchs-Level, WÄHREND der Squeeze `armed` ist (das Level ist da + schon bekannt = Box-Grenze), und wird vom Ausbruch selbst ausgelöst — anders als die + Limit-Order (D) wartet sie NICHT auf einen Rücklauf, verpasst also nichts. + | Fill-Annahme | H1 ØR / PF / ΣR | H2 ØR / PF / ΣR | + |---|---|---| + | Level exakt (= A) | +0,456 / 2,65 / +370 | +0,611 / 3,56 / +778 | + | **Level +0,05×ATR** | **+0,397 / 2,28 / +322** | **+0,553 / 3,06 / +697** | + | Level +0,10×ATR | +0,355 / 2,07 / +288 | +0,509 / 2,76 / +639 | + | **Level +0,20×ATR (pessimistisch)** | **+0,244 / 1,62 / +196** | **+0,426 / 2,25 / +531** | + | Market/Live (= B) | −0,158 / 0,74 / −126 | −0,006 / 0,99 / −8 | + **Selbst mit 0,20×ATR unterstellter Fill-Slippage — dem ~9-fachen des Median-Spreads + — bleibt es in BEIDEN Hälften klar positiv.** + ⚠⚠ **Der Schlüssel ist nicht der Median, sondern der SCHWANZ.** Live ist der + Median-Abstand +0,275×ATR, der **Mittelwert aber +0,686** (Max +4,339). Eine + Stop-Order **deckelt den Einstiegspreis konstruktiv**; die Market-Order auf dem + 5-s-Poll tut das nicht. Die Extremfälle (+3,5 / +4,3×ATR) stammen erkennbar aus + **blockiert-dann-freigegebenen** Ausbrüchen: feuert der Squeeze, während eine + Position offen ist (oder Nacht-/News-Fenster), wird KEIN Dedup-Marker gesetzt — der + Entry kommt später zum dann gültigen Marktpreis, u. U. weit hinter dem Level. + ✅✅ **GEBAUT & LIVE (2026-08-05, User-Vorgabe „setze alle Vorschläge um"), + `[trading] squeeze_pending_entry=true`.** `engine._manage_squeeze_pending` (im + `_pos_loop`, VOR dem Market-Pfad) hält **beide Box-Grenzen** mit ruhenden + `BUY_STOP`/`SELL_STOP` bestückt, solange der Squeeze `armed` ist: + `trader.place_stop/cancel_pending/pending_orders` (Größe und SL aus **denselben** + Funktionen wie die Market-Order, nur bezogen auf den Trigger-Preis — sonst wäre der + SL-Abstand ein anderer als validiert). **OCO-Ersatz:** füllt eine Seite, existiert + eine Position → die Bedingung „soll liegen" wird falsch und die Gegenseite wird im + selben Tick storniert (MT5 kennt kein natives OCO). + ⚠ **Der kritische Punkt bei einer RUHENDEN Order: die Gates.** Nacht-Fenster, + News-Blackout, Circuit-Breaker und der An/Aus-Schalter waren bisher Laufzeit-Checks + im Market-Pfad; eine liegende Order würde sie schlicht ignorieren. Deshalb neu + **`engine._squeeze_guard()` als EINE Quelle für beide Wege** — jeden Tick neu + bewertet, und sobald ein Gate zuschlägt, werden die Orders **storniert**. Zwei + Kopien derselben Gates wären genau die Divergenz, die hier siebenmal als + Deployment-Drift dokumentiert ist. + ⚠ **Market bleibt nur noch FALLBACK — mit Nachjagd-Bremse** (`squeeze_max_chase_atr` + = **0,20**): greift keine Pending (Broker-Mindestabstand, Squeeze sprang direkt auf + `active`, Order-Fehler), wird nur noch eingestiegen, solange der Kurs **höchstens + 0,20×ATR** über dem Level steht — genau der Bereich, der gemessen in beiden Hälften + positiv ist (+0,244/+0,426). Darüber: Ausbruch auslassen, Log „⏭ Auto-Squeeze + übersprungen". `_squeeze_last_sig` wird dabei NICHT gesetzt → läuft der Kurs zum + Level zurück, darf der Entry noch kommen. + ⚠ Weitere Fallstricke, alle behandelt: **Broker-Mindestabstand** + (`trade_stops_level`) wird VOR dem Senden geprüft und der Fehler nur bei ÄNDERUNG + geloggt (sonst 1 Zeile/Sekunde, wenn der Kurs an der Box-Grenze klebt) · Order auf + der falschen Marktseite wird abgelehnt · **Toleranz 0,02×ATR**, damit nicht jede + ATR-Zuckung die Orders löscht und neu setzt · Startup-Schonfrist gilt auch hier. + **Mit 10 Szenarien getestet** (setzen · Idempotenz beim zweiten Tick · Fill → + Gegenseite weg · Squeeze klärt sich · Gate schlägt zu · Schonfrist · Feature aus · + Box verschiebt sich · Broker lehnt ab · Nachjagd-Bremse in beide Richtungen). + Live verifiziert am Snapshot: `squeeze_pending=true`, `squeeze_max_chase_atr=0.2`, + `squeeze_pending_levels` (leer, weil eine Position offen war = korrektes Verhalten). + **Zurück zum alten Verhalten: `squeeze_pending_entry=false`.** + ⚠ **B4-Pflicht + Werkzeug:** der Live-Abstand Einstieg↔Level muss jetzt gegen ~0 + gehen (vorher Median +0,275×ATR). Dafür gibt es jetzt + **`analyze_squeeze_entry_gap.py`** (führt die zwei nötigen Quellen zusammen: das + Level steht NUR im Log, der Fill NUR in `trades`) — mit Vorher/Nachher-Split am + Umbau-Stichtag. ⚠ **Die Erfolgsmeldung ist paradox:** eine per Pending gefüllte + Order erzeugt **keine** `AUTO-SQUEEZE-ENTRY`-Logzeile (die entsteht nur im + Market-Fallback). Squeeze-Trades **ohne** Log-Treffer sind also der Erfolg, nicht + ein Fehler — das Skript weist sie deshalb getrennt aus. Überwacht als Messung + `squeeze_entry_gap` im `measurement_reminder.py` (≥12 Squeeze-Trades ab dem + Umbau; Stichtag exakt 05.08. 08:55, nicht Mitternacht — der 08:33-Trade lief noch + über Market). Bleibt der Abstand bei ~0,3×ATR, füllt die Pending nicht → Broker- + Mindestabstand und die Logzeile „Squeeze-Pending nicht platzierbar" prüfen. + ⚠ **Zahlen-Korrektur (05.08.):** zuerst standen hier Median +0,306 / Ø +0,741 — + das war eine Scratchpad-Auswertung mit FESTEM Stundenversatz. `analyze_squeeze_ + entry_gap.py` rechnet DST-korrekt über `zoneinfo` und liefert **+0,275 / +0,686** + (n=34 unverändert). Gültig sind die zweiten Werte; die Schlussfolgerung ändert + sich nicht. + - **⚠⚠ EIN PENDING-FILL LIEF AN DER GESAMTEN BUCHFÜHRUNG VORBEI (Fix 2026-08-06, + User-Auftrag „überprüfe die heutigen autotrades").** Der Befund kam nur zustande, + weil die Frage überhaupt gestellt wurde: die DB meldete für den 06.08. **null + Bot-Trades**, das Log dagegen vier gefüllte `SQZ-STOP`-Orders. Beides stimmte. + **Mechanismus:** eine ruhende Stop-Order füllt **im Broker**, nicht über + `engine._open`. Die Position wird erst später von `trader._refresh_locked` per + magic-match **adoptiert** — und dieser Pfad ruft `history.log_trade_open()` nur mit + Ticket/Symbol/Richtung/Lots/Preis auf. Es fehlten damit **alle** Felder, die der + Market-Pfad setzt: `setup`, `sl_at_entry` und die **16 `ctx_*`-Spalten** der + Telemetrie-Stufe 1. Vier reale Trades waren so von manuellen **nicht + unterscheidbar**. + ⚠ **Die Tragweite ist größer als „ein NULL-Feld":** genau die Messpipeline, die den + Pending-Umbau vom 05.08. kontrollieren soll, war **blind für ihn** — B4-Monitor, + `squeeze_b5` (Abbruchregel!) und `squeeze_entry_gap` zählten null Trades, und + `sl_at_entry` fehlte ausgerechnet bei den Trades, an denen die M5-SL-Umstellung zu + prüfen war. **Deployment-Drift Fall 8**, und die erste Variante, bei der nicht die + Strategie driftet, sondern **ihre Beobachtbarkeit**. Ein Umbau, der den Bestellweg + ändert, ändert auch, welcher Code die Bücher führt — das gehört beim Umbau geprüft. + **Fix:** `history.tag_bot_trade(ticket, setup)` (UPDATE nur wenn `setup IS NULL`, + stiller Fail-open — `core/history.py` hat bewusst keinen Logger) + + `engine._pending_tickets` (Ticket → Quelle, beim Platzieren gesetzt, beim + Stornieren/Taggen entfernt) + `_check_pending_fill()` im `_pos_loop` VOR dem + Pending-Manager. Die Zuordnung ist eindeutig, weil bei einem Pending-Fill die + **Positions-Nummer identisch mit der Order-Nummer** ist (an allen vier + Broker-Datensätzen des 06.08. verifiziert) — kein Zeitfenster-Raten. Der Fix setzt + zusätzlich `_bot_open_ticket`/`_bot_open_source` und die Entry-Zähler, damit + 15-Min-Regel, B4-Monitor und Snapshot wieder dieselbe Wahrheit sehen. + Mit **6 Szenarien getestet**, darunter die beiden gefährlichen: fremde/manuelle + Position wird NICHT getaggt, und derselbe Fill zählt bei wiederholten Ticks nur + **einmal**. Die vier Trades des 06.08. wurden nachgetragen (DB-Backup + `oil_widget_history.db.bak-2026-08-06`). + ⚠ **Bewusst NICHT nachgetragen: `ctx_*` und `sl_at_entry`.** Die stehen für den + Zustand **im Moment des Fills** und lassen sich nachträglich nur rekonstruieren, + nicht messen — eine rekonstruierte Zahl in einer Telemetrie-Spalte wäre später von + einer gemessenen nicht mehr unterscheidbar. Ab jetzt sind sie für Pending-Fills + weiterhin leer; wer sie braucht, muss den Kontext beim **Platzieren** der Order + einfrieren (offen, nicht gebaut). + - **⚠ OFFEN: Pending-Churn im SIGNAL-Pfad (gefunden 06.08., nicht behoben).** Am + 06.08. wurde dieselbe SELL_STOP viermal in 76 s gesetzt und storniert — **bei + identischem Level 74,981**, es wandert also nichts. Die Ursache steht in + `recommendations.block_reason`: der Grund **pendelt zwischen `entry_room` und + `breakout_pending`**. Sobald das Raum-Gate den Kurs kurz stummschaltet, liefert + `_pending_ziel()` `{}` und der Manager storniert; eine Sekunde später ist das Gate + wieder offen und die Order wird neu gelegt. + ⚠ **Das ist wieder Deployment-Drift, nicht nur Log-Lärm:** `backtest_auto_signal_v3.py` + hat den Pending-Vorteil (ØR +0,35/+0,32 gegen −0,09/−0,11 am Markt) unter der + Annahme gemessen, dass die Order **durchgehend am Level liegt**. Flackert sie im + 5-s-Takt, geht genau der Ausbruch verloren, der in eine Storno-Lücke fällt — und + das ist der Fall, für den sie da ist. + **Lösungsrichtung (ungebaut):** harte von weichen Storno-Gründen trennen. HART + (sofort stornieren) = Position offen · Startup-Schonfrist · Guard (Nacht/Circuit/ + News) · Richtungswechsel · Level wandert über die Toleranz · Feature aus. WEICH + (Order liegen lassen) = kurzzeitiges Stummschalten durch `entry_room`/`min_conf`/ + `breakout_pending`. Argument dafür: die Order füllt nur, wenn der Kurs das Level + **bricht** — dann hat er sich vom Gegenlevel wegbewegt, die Raum-Bedingung ist im + Fill-Moment also besser als im Flacker-Moment. Argument dagegen: das ist eine + Aussage über den Einstiegs-Filter und damit **nicht rein infrastrukturell** — + vor dem Bau messen (Variante „Order bleibt liegen" gegen „Order flackert"). +- **Auto-Squeeze-Entry = LIVE AUTONOM (User-Vorgabe 2026-07-16, `[trading] + auto_squeeze=true`, Default FALSE):** der Bot eröffnet **selbständig eine echte + Order**, sobald der **gemessen-validierte** Squeeze-Ausbruch feuert (`wave.squeeze. + state=='active'`) UND **flat** ist — NICHT auf dem Wave-Signal (das ist der + Dry-Run-Verlierer), sondern nur auf dem 2-Stichproben-positiven Squeeze. + `engine._check_auto_squeeze` im `_pos_loop` (1 s): **1× je Ausbruch** (Dedup über + (dir, level); re-armt, wenn der Squeeze klärt), öffnet via `open_long/short` (→ + Trailing an, Emergency-Stop armiert 2 % beim nächsten Tick), Log `🚀 AUTO-SQUEEZE- + ENTRY` + Telegram + Fehlschlag-Push. Snapshot `auto_squeeze`/`squeeze_entry_count`; + Toggle `POST /api/autosqueeze` (`set_auto_squeeze`) = Dashboard-Button **🚀 BRK** + (blau=AN, ersetzt den alten Dry-Run-AUTO-Button). **Dead-Hour-Guard = AN + (User-Vorgabe 2026-07-24, `[trading] auto_squeeze_skip_night=true`) — **Fenster + seit 2026-08-05 auf `3,4,5,6` VERKÜRZT** (`[trading] auto_squeeze_night_hours`, + User-Vorgabe „verkürze auf die wirklich toten Stunden"; war fest 0–7):** der Bot + eröffnet in diesen Stunden **KEINE** Squeeze-Trades (`_squeeze_night_hours` → + `_check_auto_squeeze` return; kein Entry UND kein Reverse; leere Liste = aus). + ⚠ **`_SQUEEZE_NIGHT=(0..7)` bleibt bestehen, ist aber NICHT mehr das Gate** — es + ist jetzt nur noch die STATISTISCHE Nacht-Definition für die Entry-Checkliste + (dort gilt die Kostenfalle weiterhin, s. u.) und den Tag/Nacht-Split im + B4-Monitor (damit der Vergleich mit den Altdaten stabil bleibt). Der + Auto-Signal-Pfad nutzt dasselbe neue Fenster (ist ohnehin aus). + ⚠⚠ **Die Verkürzung ist NICHT ertrags-begründet, sondern preis-begründet** — + gemessen ist **keine einzige Nachtstunde in beiden Halbjahren negativ**, jede + geblockte Stunde kostet Ertrag (Details beim Fallbeispiel unten). 3–6 ist + schlicht das **günstigste Fenster mit noch ≥4 h Schutz**: Preis ΣR + **−12,5/−62,9 R** gegen **−42,4/−242,6** bei 0–7 = **~27 %** der bisherigen + Kosten. Freigegeben sind **0, 1, 2 und 7 Uhr**. ⚠ Std 0 ist dabei die **beste + Stunde des ganzen Tages** für den Squeeze (ØR +1,48/+1,51) — sie zu blocken war + der teuerste Einzelposten. Live verifiziert am neuen Snapshot-Feld + `squeeze_night_hours` + Startup-Log „Auto-Squeeze-Nachtsperre: 3:00, 4:00, 5:00, + 6:00". Zurück auf den alten Stand: `auto_squeeze_night_hours = 0,1,2,3,4,5,6,7`. + Doppelt begründet: (1) **gemessen** unrentabel (`backtest_realcosts.py`, Spread÷Nacht- + ATR 0,32–0,50×ATR, beide Halbjahre negativ = Kostenfalle) und (2) **kein Trade + soll unbeaufsichtigt nachts feuern, während der User schläft** — Auslöser: „müsste + automatisch schließen wenn ich nicht da bin, z. B. nachts". Der Guard LÖST das, + indem er den Trade gar nicht erst öffnet; tagsüber (User eher erreichbar) läuft der + bestehende Auto-Close-Stack (Broker-SL 2×ATR broker-seitig + Trailing + Time-Stop + + S/R-Close). ⚠ Historie: bis 2026-07-17 war der Guard AUS (User wollte rund um die + Uhr), am 2026-07-24 auf User-Wunsch **zurück auf AN**. `=false` schaltet ihn wieder + aus (dann auch nachts, unbeaufsichtigt). Einen engen Schutz-%-Stop NUR für Squeeze + wurde bewusst NICHT gebaut (enger %-Stop unter Margin-Sizing wird vom Squeeze- + Rauschen ausgelöst, −48,94-€-Tag — der Squeeze braucht Raum). + ⚠⚠ **FALLBEISPIEL 05.08.2026 — der Guard hat 5 Ausbrüche geblockt, darunter den + Einstieg direkt vor einem 1,8-$-Rutsch (User-Frage „warum ist der Squeeze nicht + eingestiegen?").** Nachgerechnet mit der ECHTEN `_squeeze_one` auf den M5-Bars + (nicht aus dem Log geschlossen — der Squeeze-ZUSTAND wird nirgends geloggt, nur der + Entry): zwischen 02:00 und 07:30 Berlin feuerte 5× `active`, **alle in 0–7 Uhr**. + Der Bot war ab 01:15 flat, die Nachtsperre war also der ALLEINIGE Blocker (kein + Cooldown, keine offene Position). Kursverlauf: Hoch **76,373** um 03:30 → Tief + **74,548** um 04:40 = **−1,825 $ in 70 min**; der Ausbruch **04:00 SHORT @ 76,11** + lag praktisch auf dem Hoch. + **Mit dem kanonischen Exit** (`exit_model.LIVE`, Echtkosten aus dem Bar-Spread — + bewusst NICHT „maximale Auslenkung", s. die Korrektur bei der 15-Minuten-Regel): + 02:00 SHORT **+3,28 R** · 03:00 LONG −0,24 · 04:00 SHORT **+1,36 R** · 06:15 LONG + −0,60 · 07:30 SHORT −0,24 → **Σ +3,56 R ≈ +95 € bei 2,66 Lots**. + ⚠ **Trotzdem kein Argument gegen den Guard:** n=5 an EINEM Tag, und **92 % davon + stecken in zwei Trades** — ohne den 02:00er bleiben +0,29 R ≈ +12 €. Ein Einzeltag + taugt nicht als Beleg, auch nicht als widerlegender (dieselbe Lehre wie am 01.08.). + Die Guard-Begründung ruht auf 2 Halbjahren; dazu kommt der nicht-ökonomische Grund + (keine unbeaufsichtigten Nacht-Trades), der von Zahlen gar nicht berührt wird. + ✅ **Ein messbarer Ansatz ergibt sich aber:** die Nacht-Begründung ist eine + **Kosten**-Aussage (Spread÷ATR 0,32–0,50). In dieser Nacht lagen die realen Kosten + bei **0,11–0,22×ATR**, also klar UNTER der Falle — weil der ATR hoch war. Ein + **kosten-basiertes statt stunden-basiertes Gate** („blocke, wenn Spread/ATR > X") + würde genau solche Nächte durchlassen und ruhige weiter sperren. + ⚠⚠ **GEMESSEN am selben Tag (`backtest_cost_gate.py`, 80k M5, 2 Halbjahre, + kanonischer Exit, Echtkosten, ECHTE `_squeeze_one`) — und das Ergebnis fällt + anders aus als die Frage lautete.** + **(a) Kein Kosten-Gate ersetzt das Stunden-Gate** nach der vorab fixierten Regel + (ΣR **und** ØR in beiden Hälften ≥ Stunden-Gate, plus Nachbar-Schwellen): alle 7 + Schwellen 0,15–0,50 fallen durch. + **(b) Der eigentliche Befund: das STUNDEN-Gate selbst kostet Geld — in BEIDEN + Hälften.** Sequentiell mit EINEM Positions-Slot (die konto-relevante Rechnung; + ein geblockter Nacht-Trade gibt den Slot frei): + | Variante | H1 ΣR | H2 ΣR | + |---|---|---| + | **ohne Gate** | **+369,5** | **+778,1** | + | Stunden-Gate 0–7 (live) | +327,1 | +535,5 | + | Kosten-Gate 0,30 | +356,3 | +760,1 | + | Kosten-Gate 0,35 | +369,3 | +781,9 | + → das Live-Gate kostet **−42 R (H1) und −243 R (H2)**. Die von ihm GEBLOCKTEN + Ausbrüche sind in beiden Hälften **positiv** (ØR +0,496/+0,589) — in H1 sogar + besser als die behaltenen (+0,382). Es wirft gute Trades weg. + ⚠⚠ **MECHANISMUS BELEGT — die Nachtsperre ist auf der FALSCHEN Population + begründet (Deployment-Drift, Fall 6).** Die Nacht-Kostenfalle ist echt und + reproduziert (`backtest_realcosts.py`): über ALLE Nacht-Bars Median **0,333** / + Ø **0,387** ×ATR gegen Tag 0,163/0,185. **Aber der Squeeze feuert dort gar nicht** — + er verlangt einen Ausbruch, also anziehenden ATR. Seine Nacht-Ausbrüche kosten nur + **Median 0,183 / Ø 0,191** (Tag 0,156/0,162), liegen also praktisch auf Tag-Niveau + und weit unter der Falle. **Der Squeeze selektiert sich aus der Kostenfalle + heraus.** Gemessen wurde die Falle am Wellensignal, angewandt wird das Gate auf den + Squeeze — dieselbe Fehlerklasse wie bei der 15-Minuten-Regel (auf Wave-Entries + kalibriert, auf alle Bot-Trades angewandt, auf Squeeze gemessen schädlich). + ⚠ Nebenbefund: **ein Kosten-Gate TRENNT tatsächlich** in die richtige Richtung + (bei 0,30 hat die geblockte Gruppe H1 WR 44,9 % / ØR +0,175 gegen +0,485 behalten) + — nur ist auch sie noch positiv, deshalb kostet jedes Blocken ΣR. Bester ΣR-Wert + ist 0,35, und der blockt praktisch nichts mehr (1 bzw. 10 Trades) = faktisch „kein + Gate". + ⚠ **Methodik-Lehre zur eigenen Regel:** „ΣR UND ØR ≥ Referenz" ist als + Entscheidungsregel schlecht spezifiziert, wenn die Varianten unterschiedlich viele + Trades handeln — ØR und ΣR laufen dann mechanisch gegeneinander (0,30 schlägt das + Stunden-Gate im ΣR um +29/+225, verfehlt das ØR in H2 um 0,004). Die Regel wurde + **nicht nachträglich gelockert**; stattdessen ist der Vergleich „Gate gegen KEIN + Gate" separat ausgewiesen — er stand ohnehin in derselben Tabelle. + ⚠⚠ **DREI Gründe, warum daraus NICHT automatisch „Nachtsperre aus" folgt:** + (1) **Slippage ist nicht modelliert** — nachts ist das Buch dünner, der gemessene + Tail liegt bei 0,75×ATR über den Stop hinaus (`analyze_execution.py`). Das trifft + Nacht-Trades selektiv und ist die größte Bedrohung des Befunds. (2) **Live sagt das + Gegenteil**, wenn auch dünn: von den 31 Squeeze-Trades des B4-Fensters waren 12 + Nacht-Trades mit **−78,92 €** — allerdings alle VOR den Exit-Reparaturen vom 31.07. + (P(break) nachtrainiert, 15-Min-Regel aus, Trail 1,5→1,0). (3) Der + **Beaufsichtigungs-Grund** („kein Trade soll feuern, während ich schlafe", + User-Vorgabe 2026-07-24) ist eine Präferenz und wird von keiner Zahl berührt. + ✅ **Konsequenz (User-Vorgabe 2026-08-05 „verkürze auf die wirklich toten + Stunden"): Fenster von 0–7 auf 3–6 verkürzt** — Details oben beim Guard. + ⚠⚠ **Die Prämisse der Vorgabe hält der Messung nicht stand: es gibt KEINE toten + Stunden.** Je Stunde einzeln, sequentiell, beide Halbjahre — **alle 24 Stunden + sind in beiden Hälften positiv**, keine einzige ist beidhälftig negativ. Die + ruhigsten Nachtstunden (n über beide Hälften): 5 (41 Ausbrüche) · 4 (56) · 3 (61) + · 6 (62); die aktivsten 7 (75) · 0/2 (72) · 1 (71). ⚠ **Std 0 ist die stärkste + Stunde des gesamten Tages** (ØR +1,483/+1,510, ΣR +22,2/+86,1) — ausgerechnet sie + lag im alten Gate. + Damit ist die Auswahl **keine Ertrags-Optimierung, sondern eine Preis-Minimierung + für eine Präferenz** — und deshalb auch nicht overfitting-gefährdet: es wird keine + prädiktive Behauptung aufgestellt. Preisliste (ΣR gegen „kein Gate"): + | Fenster | Std | Preis H1 | Preis H2 | + |---|---|---|---| + | 4–5 | 2 | −3,9 | −18,1 | + | 4–6 | 3 | −6,8 | −39,9 | + | **3–6 (gewählt)** | **4** | **−12,5** | **−62,9** | + | 2–5 | 4 | −12,8 | −77,2 | + | 0–7 (alt) | 8 | −42,4 | −242,6 | + Die 4-Stunden-Untergrenze ist **gesetzt, nicht gemessen** — darunter wäre der + Schutzzweck hinfällig. Wer weniger Schutz will, nimmt 4–5 (fast gratis); wer den + alten Stand will, trägt `0,1,2,3,4,5,6,7` ein. + ⚠ **Was die Verkürzung NICHT ausräumt:** die drei Vorbehalte oben gelten + unverändert — vor allem die **nicht modellierte Slippage** (dünnes Nacht-Buch, + Tail 0,75×ATR über den Stop). Die freigegebenen Stunden 0/1/2/7 liegen mitten in + der dünnsten Zeit. **B4-Pflicht: die Squeeze-Trades aus 0/1/2/7 Uhr getrennt + mitzählen** — bleiben sie hinter der Erwartung (ØR ~+0,5 R), war die Slippage + der Grund und das Fenster gehört zurück auf 0–7. + **Stop-&-Reverse = AUS (Default, `[trading] auto_squeeze_reverse=false`, + `_squeeze_reverse`; User-Vorgabe 2026-07-17):** War kurz scharf, dann abgeschaltet — + nur der **flat-Entry ist gemessen-validiert**, der Reverse ist unbelegt, verdoppelt + Kosten und dreht am **Ausbruch-Extrem** (real 2026-07-17 18:55: WAVE_REV_SHORT gegen + einen News-Rally-LONG-Ausbruch bei −29,64 € geschlossen, dann LONG am Erschöpfungs-Top + 82,32 — Elliott „impulse_up/5 exhausted" im selben Moment). Bei `false` bleibt ein + Gegen-Trade **unangetastet** (Drehen = manuelle Entscheidung); `_squeeze_last_sig` + wird dann NICHT gesetzt → schließt der Gegen-Trade (SL/manuell), kann der flat-Entry + auf denselben Ausbruch noch feuern. Bei `true` (historisch, reaktivierbar): läuft ein + Trade GEGEN den Ausbruch (auch manueller/Fremd-Trade!) → sofort `close(reason= + "squeeze_reverse")` (Log 🔄 + Telegram, Dedup `_squeeze_reverse_sig`), nächster flat-Tick + dreht. ⚠ NICHT gemessen (der Squeeze-Backtest nahm flat-Entry an) — deshalb Default aus. + **Exit-only-Variante („Gegen-Position beim Ausbruch schließen, OHNE Re-Entry") = + VERWORFEN (gemessen `backtest_squeeze_exit.py`, 2026-07-22, User-Idee „Breakout + schneller schließen"):** die sicherere Halb-Variante (nur schließen, nicht drehen) + fällt ebenfalls durch — in BEIDEN Hälften schlechter als der Live-Exit (SL 2×ATR+Trailing): + ΔΣR H1 −15 (immer)/−5 (nur im Minus), H2 −27/−29. Grund: der Gegen-Ausbruch ist oft ein + Fehlausbruch/Whipsaw → die Position prallt zurück, der frühe Close kappt Trades, die + sich erholt hätten. Der 07:38-Fall (LONG lief zum SL während SHORT-Ausbruch) war Varianz. + **Fazit: Squeeze taugt als ENTRY (validiert), NICHT als Gegen-EXIT.** Der einzige + Speed-Hebel bleibt die schnellere Erkennung (Variante A, ~5 s). + ⚠⚠ **DRITTE MESSUNG 2026-08-05 (`backtest_squeeze_exit3.py`) — jetzt mit dem + KANONISCHEN Exit, und der Grund für das Nein ist ein anderer als gedacht.** + Anlass: Trade T=49423144 (manueller SELL 3,49 @ 75,012) lief in 14 min 1,135 $ + ins Minus und endete am Initial-SL bei **−343 €**; User-Wunsch: „der Breakout + hätte den Trade früher schließen sollen", nachgeschärft zu „bei einem Squeeze den + SL sofort eng setzen". + ⚠ **Was an dem Tag wirklich passierte:** beim Einstieg war der Squeeze **active + SHORT** — er stand FÜR den Trade. Die Empfehlung war WARTEN (`block=entry_room`), + der Einstieg manuell. Der Gegen-Ausbruch (armed LONG @ 75,729) wurde erst ~3–5 min + später aktiv; ein Close dort hätte ≈**−219 €** statt −343 € bedeutet (~124 €). + **Gemessen (beide Varianten, kanonischer Exit, Echtkosten, 2 Halbjahre, zwei + Populationen — 2.365 Squeeze- und 1.566 Wellen-Entries):** + | Variante | Auslösungen H1/H2 | + |---|---| + | sofort schließen (Squeeze-Entries) | **1 / 1** von 809 bzw. 1.274 | + | **enger SL 0,1–0,5×ATR** (Squeeze-Entries) | **0 / 0** | + | sofort schließen (Wellen-Entries) | 8 / 8 von 386 bzw. 835 | + | enger SL (Wellen-Entries) | 2–3 / 2–3 | + ✅✅ **DAS IST DER EIGENTLICHE BEFUND: der Mechanismus feuert praktisch NIE.** + Nicht weil die Idee schlecht wäre, sondern weil sie **konstruktionsbedingt zu spät + kommt**: ein Gegen-Ausbruch verlangt eine NEUE Kompression (Box über 10 M5-Bars = + 50 min), der Ausbruch selbst weitet die Box aber gerade erst. Die mittlere + Haltedauer liegt bei **12–18 min**. Der Gegen-Ausbruch trifft also in rund **0,1 % + (Squeeze) bzw. 1 % (Welle)** der Trades ein — der Fall des Users war einer davon. + Die Kennzahl-Unterschiede in der Tabelle sind entsprechend Rauschen aus einer + Handvoll Fällen; keine Variante besteht auf beiden Populationen. + ⚠ **Auch die abgeschwächte Form („enger SL statt Close") ändert nichts** — sie + adressiert zwar genau den dokumentierten Einwand (Gegen-Ausbrüche sind oft + Fehlausbrüche), kann ihn aber nicht ausspielen, wenn sie 0-mal auslöst. + ⚠ Und sie wäre von dieser Simulation ohnehin **überschätzt**: ein enger Stop wird + von einer Bar-Simulation systematisch zu gut gerechnet (s. Auflösungs-Artefakt, + `analyze_trail_resolution.py`, 05.08.). + **Damit zum DRITTEN Mal verworfen — und diesmal mit einer Begründung, die nicht + vom Exit-Modell abhängt.** + **Re-Test unter Vola-Regimen = ERNEUT VERWORFEN (gemessen + `backtest_squeeze_exit_vol.py`, 2026-07-24, User-Frage „Markt gerade sehr volatil — + wirklich nicht profitabel?"):** Verschärfter Test mit echten SQUEEZE-Entries (statt + EMA-Crosses), 4 Varianten (immer/nur-im-Minus/nur-Verlust>0,5×ATR), ATR-Terzil-Split + je Hälfte + Extra-Fenster letzte ~30 Tage. Ergebnis: Effektgröße überall ~0 + (±0,002–0,005 R/Trade bei ~1150 Trades/Hälfte), **Vorzeichen kippt zwischen den + Regimen** (MODL: H1-Hochvola −1,6 / H2-Hochvola +3,0 / letzte 30 Tage −0,3) → nach + der vorab festgelegten Regel (beide Hälften UND Hoch-Vola-Terzil ≥ BASE) fällt JEDE + Variante durch. **Kernbefund: Worst-Case ist in ALLEN Varianten identisch** + (−2,33/−2,47 = SL-Deckel) — der Gegen-Squeeze-Exit reduziert das Tail-Risiko um + exakt 0, weil bei echten schnellen Brüchen immer der SL zuerst greift; er kappt nur + langsame Kriecher, von denen sich >50 % erholt hätten. (Methodik-Hinweis: absolute + ØR pessimistischer als im validierten Squeeze-Backtest — Entry am Bar-Close statt + am Level, keine Flat-Bedingung; für den Varianten-VERGLEICH neutral, da alle Exits + denselben Entries folgen.) 3. Messung derselben Idee (Reverse · Exit-only · + Vola-Regime-Re-Test) — gleiche Antwort. + **S/R-Close kappt Squeeze-Runner? = GEPRÜFT, KEINE Änderung (gemessen + `backtest_squeeze_srclose.py`, 2026-07-24, User-Beobachtung „Squeeze wird direkt am + nächsten S/R geschlossen"):** Der Log bestätigt, dass Auto-Squeeze-Trades regelmäßig + vom P(break)-S/R-Auto-Close am nächsten Gegen-Level geschlossen werden (der Squeeze + wurde mit reinem Trailing validiert, ist runner-abhängig — Verdacht: Runner gekappt). + Direkter Vergleich NUR auf Squeeze-Entries (Box≤2,5×ATR, k=0,1): **Exit A = nur + Trailing (validiert)** vs. **Exit B = +S/R-Close (aktuell live)** = **Wash, kein + robuster Schaden** — H1 ØR +0,190→**+0,224** (ΣR +40→+47, B besser), H2 ØR + +0,394→+0,377 (ΣR +290→+278, B marginal schlechter) = Δ ±0,02 R/Trade (Rauschen). + **Warum unkritisch:** der S/R-Close ist P(break)-gegated (schließt nur wenn Level + wahrscheinlich HÄLT, P<60 %) → echte, starke Ausbrüche haben hohe P(break) und + laufen weiter; **PF/Runner-Tail bleiben erhalten** (H2 PF 1,58=1,58), nur die WR + steigt (50→55 %, Abpraller früher als Klein-Gewinn kassiert). Squeeze-Trades vom + S/R-Close auszunehmen ist also NICHT belegt. (Reaktivierbar per Toggle, falls der + User trotzdem die Runner nie kappen will — kostet gemessen ~nichts, ist aber + Präferenz, kein Edge.) + **S/R-Close-Cooldown (2026-07-17, Koordinations-Fix, kein Backtest):** nach einem + S/R-Auto-Close eröffnet der Auto-Squeeze **~3 min (`_SQUEEZE_SR_COOLDOWN_S=180`) + nicht in GLEICHER Richtung** wieder. Grund: der S/R-Close ging von „Level hält" + (P(break)0`: erreicht der **Tages-P&L + (realisiert heute + offene Position)** −pct % der Balance, schließt der Bot die + Position (`closed_by=circuit_breaker`) und sperrt **neue Auto-Trades bis zum + nächsten Tag** (`_check_auto_squeeze` prüft `_circuit_halted()`). `_check_circuit_ + breaker` im `_pos_loop` (1 s, unter Startup-Grace pausiert), realisierter Tages-P&L + ~30 s gecacht (`_day_realized_cached`), Berlin-Datum als Trip-Marker (`_circuit_ + tripped_date`) → automatischer Reset am neuen Tag. Log 🛑 + Telegram (eigener, + nicht vom Close-Gate betroffen). Snapshot `circuit_breaker {enabled, limit_pct, + limit_eur, day_total, tripped}`, Frontend `#circuit-banner` (rot bei ausgelöst, + bernstein ab 60 % des Limits, v=115). ⚠ **80 %-Margin-Sizing bleibt** (User- + Entscheidung, 3.+ Mal bestätigt; `risk_pct=0`) → der 8 %-Tagesstopp schützt v. a. + gegen die **Verlust-Serie/-Kaskade**, NICHT vollständig gegen den Einzeltrade + (dessen 2×ATR-SL unter 80 % Margin allein schon >8 % verlieren kann). 0 = aus. + Mit 5 synthetischen Szenarien getestet (nicht-zu-früh · Trip+Close · kein + Doppel-Close · aus · Auto-Squeeze-Block). ⚠ Der frühere „NICHT einbauen"-Grund + gilt nicht mehr — Ziel hat sich zu **autonomem Handel** verschoben, und ein + unbeaufsichtigter Agent braucht die Notbremse im Code. +- **Notfall-Stop = AUS beim Öffnen (Stand 2026-07-24, `auto_emergency_margin_pct=0`, + `auto_emergency_pct=0`, `auto_emergency_loss=0`).** User-Vorgabe 2026-07-24: „gewinn + close und notfall close … nicht setzen beim Eröffnen" → alle drei Auto-Arm-Modi 0, + beim Öffnen einer Position wird KEIN Notfall-Stop gesetzt. Nur ein **manuell** im + UI-Feld eingegebener Wert armiert (bleibt bis zum nächsten Positionswechsel). + **⚠ Vertipper-Schutz `[trading] emergency_min_eur=3` (Fix 2026-07-27):** ein manuell + gesetzter Notfall-Stop UNTER diesem Minimum wird als „aus" behandelt (nicht + scharfgestellt) + Log-Warnung. Grund: ein Sub-Spread-Wert schließt den Trade + **sofort** UND vererbt sich per `_emergency_remember` auf ALLE Folge-Trades — real + am 2026-07-27 setzte der User versehentlich −1 € → jeder neue Trade wurde in + Sekunden per Notfall-Close gekillt (Tickets 48504360/48504814 u. a., „sofort + geschlossen"). Guard in `set_emergency` (greift NUR auf die manuelle Eingabe; + die %-Auto-Arm-Pfade sind eh 0). 0 = kein Floor. + Kurz-Historie des margin-%-Modus: 2026-07-17 komplett raus (2 % ≈ 0,76×ATR zerschoss + die Squeeze-Trades, −48,94-€-Tag) · 2026-07-20 wenige Stunden als **3-%-GAP-NETZ + (Balance-Basis)** mit `risk_pct=1.5` · **2026-07-23 kurz margin-basiert `=3.0`** + (dritter Modus, ausgelöst durch zwei Notfall-Closes am selben Abend −11,20 €/−20,47 €, + wobei die Schwelle beim ersten Trade noch auf einem händischen UI-Restwert von −5 € + hing) · **2026-07-24 auf User-Wunsch wieder ganz AUS.** Der margin-%-Modus bleibt im + Code (retry-sicheres Armieren via `_emergency_margin_armed_ticket`, hat Vorrang vor + Balance-%/Fixwert wenn >0), reaktivierbar per ini. ⚠ Kopplungs-Regel (falls + reaktiviert): unter Margin-Sizing kann ein enger %-Stop schneller greifen als der + 2×ATR-SL. Schutz-Stack aktuell: **Broker-SL 2×ATR + Trailing + Time-Stop** (der + Auto-Notfall-Stop ist inaktiv). + **UI-Feld** (`#emg-input`, User-Vorgabe 2026-07-22, v=105) in der Trade-Leiste + (rot, neben Gewinn-Close), Enter blurrt nur (kein Doppel-Send/ungewolltes 0, + gleicher Fix wie Mindestgewinn 2026-07-22) — ein am Handy gesetzter Wert + überschreibt den Auto-Arm für den laufenden Trade und bleibt bis zum nächsten + echten Positionswechsel maßgeblich (mit Test verifiziert). ⚠ Server-seitig: wirkt + nur bei laufendem Server (Update-Reboot-Lücke → „Auch beachten"). Der Server + schließt die Position + **sofort**, sobald `P&L ≤ −Wert` (`engine._check_emergency` im `_pos_loop`, 1 s; + wirkt auch bei gesperrtem Handy). **Auto-Arm — drei Modi (Priorität: margin-% > + Balance-% > Fixwert):** + (a) **NEU — % der Einsatz-Margin dieser Position (aktiv, User-Vorgabe 2026-07-23):** + `[trading] auto_emergency_margin_pct=3.0` → Schwelle = **pct% × Margin** (dieselbe + Margin wie beim Gewinn-Close, aus `trader.refresh()`). **Retry-sicher** (Fix + 2026-07-23, gleiches Muster wie beim Gewinn-Close): läuft NICHT nur einmalig beim + Ticket-Wechsel, sondern jeden `_pos_loop`-Tick erneut, bis `order_calc_margin` + einmal >0 liefert (`_emergency_margin_armed_ticket`-Retry-Schutz) — verhindert, + dass bei einem Broker-Race im ersten Tick nach dem Öffnen dauerhaft ein stalen + Wert der vorherigen Position hängen bleibt. Mit 4 synthetischen Szenarien + verifiziert (sofort verfügbare Margin · verzögerte Margin/Retry · manuelles + Überschreiben bleibt erhalten · Neustart überschreibt NICHT). Snapshot + `emergency_margin_pct`. + (b) **% des Kontos (Fallback, wenn margin-%=0):** `[trading] auto_emergency_pct=2.0` + → bei jeder neuen Position wird die Schwelle auf **pct% × Balance** gesetzt + (`_emergency_pct`, skaliert mit dem Konto; z. B. 2 % von 831 € = −16,62 € ≈ + 0,4×ATR; 1× auf 1 % getestet, war zu eng → 2 %). UI-Feld zeigt im Flat-Zustand + den dynamischen Wert (Frontend rechnet `pct%×balance`, Snapshot `emergency_pct`). + (c) **Fixwert (Fallback, wenn beide %-Modi aus):** Schwelle = **zuletzt + genutzter Wert** (`_emergency_remember`, Startwert `[trading] auto_emergency_loss` + Default **10**, 0 = aus). + Setzt der User im UI einen neuen Wert, wird der gemerkt und ab dem nächsten Trade + übernommen. Aktiver Stop verfällt nach Auslösen UND bei Positions-Close, der + gemerkte Wert bleibt. **Neustart-fest (Fix 2026-07-17, `emergency_state.json`):** + Der aktive Notfall-Stop/TP wird mit dem **Ticket** persistiert (`_save/_load_emergency_ + state`, atomarer Write; bei Auto-Arm UND manuellem Set). Erkennt der Server dieselbe + offene Position nach einem `restart_server.bat` wieder, **stellt er den persistierten + Wert wieder her** (`_check_auto_close`: `ticket == _emergency_state_ticket` → restore, + Log „Auto-Close wiederhergestellt") statt auf die pct-Automatik zurückzuarmieren. Grund: + real am 2026-07-17 14:30 setzte ein Neustart bei offenem Trade die von Hand gesetzten + −25 € auf −19,42 € (2 %) zurück → Notfall-Close bei −19,64 statt bei −25. Eine echt + neue Position (anderes Ticket) armiert normal aus pct/Merkwert und persistiert neu. + ⚠ **Trailing-Zustand ebenfalls neustart-fest (Fix 2026-07-29):** `emergency_state.json` + persistiert jetzt zusätzlich `trail` (= `trail.enabled`) per-Ticket. Erkennt der Server + dieselbe Position wieder und war das Trailing vor dem Neustart AN, **reaktiviert er es + automatisch** (`_check_auto_close`-Restore-Zweig → `trail.toggle`, Log „Trailing + wiederhergestellt … nach Neustart"). Grund: real am 2026-07-29 lief eine Auto-Squeeze- + Position nach mehreren Server-Neustarts nur mit dem **Broker-SL** (Trailing/Breakeven/ + Time-Stop weg), weil `trail.enabled` nur im Speicher lebte und die Wiedererkennung + (magic-match) es NIE reaktivierte — der User musste es von Hand einschalten. War das + Trailing AUS (z. B. nach manuellem SL/TP via `/api/sltp` → `trail.deactivate`), bleibt + es aus (der persistierte Zustand entscheidet — kein Überschreiben eines manuellen TP). + Der Zustand wird bei **jeder Änderung** persistiert (Change-Detection im `_pos_loop`, + fängt auch die Selbst-Deaktivierung des Trailings bei manueller SL/TP-Erkennung). + **„aus"-Button schaltet auch den Auto-Arm ab** (setzt + `_emergency_remember=0` — vorher blieb der gemerkte Wert und re-armte beim nächsten + Trade, „aus" wirkte nur bis dahin; Bug gefixt 2026-07-07). Gilt genauso fürs Gewinn-Ziel. (Auto-Arm 1× auf manuell-only gestellt, nach Fehl-Trade + wieder reaktiviert — echter Schutz gegen falsche Empfehlungen; Signal-Filter dagegen + 4× gemessen wirkungslos/schädlich, s. `backtest_rev_htf.py`.) `engine.set_emergency()` · `POST /api/emergency {value}` · + `snapshot.emergency_loss`. Das ist KEIN automatischer Circuit-Breaker, sondern + ein manueller Hard-Stop (vom User gewünscht). **Benachrichtigung IMMER** (Ausnahme + vom „Close-Push nur im Plus"): bei Erfolg „🚨 Notfall-Close", bei **Fehlschlag** + „⚠ Notfall-Close FEHLGESCHLAGEN — manuell schließen!" (Schwelle bleibt dann aktiv, + nächster Tick versucht erneut). Eigener `send_telegram`, **nicht** vom + `pnl>0`-Gate (`history`) noch vom 5-Min-Cooldown betroffen. + +## ⚠⚠ TELEGRAM WAR NICHT KAPUTT — ES SENDETE NUR NICHTS MEHR (2026-08-11) + +User: „ich bekomme keine Telegram-Nachrichten mehr." +✅ **Kanal nachweislich intakt:** `getMe` → `@AH_Oiltrader_bot`, `getChat` → privat +„Axel", **Testnachricht zugestellt** (message_id 1764). Im gesamten Log **keine +einzige** Fehlermeldung; der Tagesreport kam täglich (08.–11.08. je 07:3x, alle +`ok`). Letzte Nicht-Report-Nachricht **07.08. 22:49**, davor am 06.08. 19:33–19:58 +im 5-Minuten-Takt = die Close-Alarm-Sperre. +⚠⚠ **DIE URSACHE: ALLE NEUN AUSLÖSER WAREN AUS** — Auto-Squeeze (über +`runtime_state.json`, unbemerkt), Auto-Signal, S/R-Auto-Close, Flip-Close, +15-Minuten-Regel, Circuit-Breaker und die **drei** Notfall-Stop-Modi. **Jede +Abschaltung war für sich begründet und gemessen; zusammen ergaben sie eine +Stille, die niemand beschlossen hatte.** Übrig blieb genau ein Live-Trigger, der +🔔 Close-Alarm — und der braucht eine **gerichtete** Empfehlung, die es nur in +**5 %** der Zeit gibt (letzte 7 Tage: 508 von 9.976 Zeilen). +⚠ Dazu kam: der Trade-Abschluss-Push war früher bewusst entfernt worden. Deshalb +kam auch der **−87-€-SL vom 10.08. wortlos**. + +✅ **GEBAUT: Close-Benachrichtigung `engine._check_close_notify`** +(`[telegram] close_notify_min_eur = 50`, 0 = aus; Backup +`oil_widget_config.ini.bak-2026-08-11-closenotify`). Feuert bei **jedem** +geschlossenen Trade ab der Schwelle — **hängt an KEINER Automatik**, also auch +bei manuellem Close und Broker-SL. Meldet Betrag, Richtung, Lots, Ein-/Ausstieg, +**Schließgrund** und Dauer. Snapshot-Feld `close_notify_min`. +⚠ **Schwelle aus den Daten gewählt, nicht geraten:** bei ~17 Trades/Tag liefert +50 € rund **2,4–6,1 Meldungen/Tag** und erfasst **54–80 %** des bewegten Geldes. +⚠ **Eine absolute Euro-Schwelle altert mit der Positionsgröße** — Median |P&L| +über 30 Tage 14,65 €, über die letzten 7 Tage 35,80 €. Bei wachsendem Konto +kommen mehr Meldungen. Bewusst so: ein Mensch denkt in Euro. +⚠⚠ **Der Unterschied zum entfernten Push ist die SCHWELLE** (der alte feuerte bei +jedem Close), und sie liegt in der **Engine**, nicht in der DB-Schicht. +⚠ **Vorgemerkt statt sofort gesendet:** der Flat-Zustand kommt aus +`trader.snapshot()` und ist dem DB-Schreiber gelegentlich einen Tick voraus — +`exit_time IS NULL` heisst „noch nicht fertig", NICHT „kein Trade". Ohne diese +Unterscheidung meldete es einen Close **ohne P&L**. Aufgabe nach ~30 s. +✅ **7 Tests** (`tests/test_close_notify.py`, Netz abgefangen) + **Mutationsprobe +in beide kritischen Zweige**: Schwelle entfernt → `test_unter_schwelle` fällt · +Retry-Fall als „kein Trade" behandelt → `test_db_noch_nicht_fertig` fällt. + +✅ **AUTO-SQUEEZE WIEDER AN** (User-Entscheidung 2026-08-11) — über den offiziellen +Toggle `POST /api/autosqueeze`, damit ini UND `runtime_state.json` übereinstimmen +(`{"ok":true,"enabled":true}`). Der Config-Wächter meldet ihn seitdem nicht mehr. +⚠ Damit läuft die **B5-Abbruchregel** weiter (11/20 Trades) — ohne laufenden +Trader wäre sie nie abgelaufen. Latte unverändert: Verhältnis < 1,0 ODER PF < 1. + +## Mobile-UI (web/) & Alarme +- Aufbau: **Tab-Navigation ganz oben** (sticky: Dashboard/**SIG Live**/**SIG Anzeige**/Logs/Statistik/News), + darunter Header. +- **Design-System (`style.css`, Pass 2026-07-15, KEIN Framework — Tailwind bewusst + verworfen wegen WireGuard/Offline + kein Node):** Tokens in `:root` — Flächen-Leiter + `--bg < --surface-2 < --card`, `--border`/`--line`, Text `--dim`/`--bright` + (+`--text-*`-Aliasse, waren undefiniert = Bug gefixt), Radien `--r-sm/-r/-r-lg`, + `--shadow(-sm)`, `--mono`. **Font:** UI = System-Sans + `tabular-nums`; Zahlen-Werte + (Kurs/PnL/Felder/Logs) = `--mono`. Karten mit Elevation-Schatten, Fokus-Ringe auf + Feldern, Transitions auf Tabs/Buttons. Neutrale Hex-Literale → Tokens (Konsistenz). +- **Trade-Leiste** (`#trade-bar`, über der Gesamtempfehlung, nur bei offener Position, + 2026-07-15): **Laufzeit-Uhr** (2026-07-30, User-Wunsch „Uhr … wie lange diese schon + läuft"): `trader.snapshot()` liefert `open_time` = Öffnungszeit als **echte Epoch** + (Broker-Offset korrigiert, aus `position.time`); Frontend zeigt sie als Feld + `#tb-dur` **und** kompakt im Kachel-Titel (`#tb-runtime`, „⏱ 1:23:45"). Der Ticker + läuft **eigenständig 1×/s** (`_tickTradeDuration`), zählt also flüssig zwischen den + Snapshots; ab **2 h** bernstein (Time-Stop-Nähe: 120 min in Phase „Init"). ⚠ Zwei + Fallen beim Bau: (1) `_broker_offset_s()` liest `self.symbol`, die beim **Adoptieren** + einer Position noch NICHT gesetzt ist → Offset 0 → Zeit lag 3 h in der Zukunft; die + Funktion nimmt jetzt `sym` als Parameter (`_broker_offset_s(pos_sym)`). (2) Selbst- + heilung: liegt `open_time` >60 s in der Zukunft, wird sie neu berechnet; beim Close + auf 0 zurückgesetzt (kein stale Wert für den Folge-Trade). Berechnung nur 1× je + Position (nicht je 1-s-Refresh). Weiter in der Zeile: Richtung (▲LONG/▼SHORT) · Lots · Einsatz · **3 Eingabefelder + `.tb-field` (Label über Feld, rot=`.stop`/grün=`.gain`): SL · TP · Gewinn-Close + (`#srmin-input`)** (Notfall-Close-Feld **2026-07-17 entfernt** — Notfall-Stop ganz aus). + SL/TP: Enter/Verlassen → + `POST /api/sltp` → setzt am Broker **und schaltet Trailing ab** (`trail.deactivate`); + Vorbefüllung aus `position.sl/tp`, Broker-Ablehnung → Toast. Gewinn-Close + (2026-07-15 aus der Position-Kachel hierher verschoben, User-Vorgabe — gleiches Design) + behält ID/Handler (`/api/srclose_min`). **Auch der + Auto-Close-Schalter** (`#srclose-btn`, jetzt `.tb-btn`-Box, AN/AUS grün/grau) sitzt in + der Zeile. Titel „aktueller Trade läuft"; Richtung/Lots/Einsatz = Anzeige-Boxen + (`.tb-val`), Rest editierbar — alle im selben `.tb-field`-Look. Position-Kachel hält + nur noch `cur-near`-Warnung + `pos-srhint`. (`.emg*`/`.sr-toggle`-CSS entfernt.) +- ⚠⚠ **CHARTS-TAB KOMPLETT ENTFERNT (User-Vorgabe 2026-08-05, v=142).** Weg sind: + Tab-Button, `#view-charts`, der gesamte `app.js`-Chart-Block (Kartenaufbau, + `renderChart`, `loadCharts`, 20-s-Refresh, Resize-Handler), die Chart-CSS-Regeln, + die **vendorte Bibliothek** `web/lightweight-charts.standalone.production.js` + (164 KB) samt `