Files
AH-Oil-Trader/CLAUDE.md
T
Axel HocksandClaude Opus 5 a6c43183e2 SL-Basis M5 vs M15 auf Level-Entries gemessen: Tail-Frage, keine Ertragsfrage
Strukturfund vorweg: exit_model.simulate nimmt EINEN atr fuer SL, TP, Trail und
Breakeven - und alle Backtests uebergeben ATR_M5. Live kommt der SL aus ATR_M15
(Median 1,80x groesser). Praktisch jede Projekt-Messung modelliert damit
2,0xATR_M5, waehrend live ~4,0xATR_M5 gilt. Einzige bisherige Ausnahme war
backtest_sl_width.py.

Gemessen auf der NEUEN Level-Entry-Population (80k, 2 Halbjahre, nur die
SL-Basis variiert):

  SQUEEZE: Ertrag ist ein Wash - alle Varianten innerhalb eines Standardfehlers,
  und die einzige beidhaelftig bessere Zelle (2,5) hat schlechtere Nachbarn.
  WELLE:   der weite Live-SL ist in BEIDEN Haelften der beste, jede Verengung
  kostet.

Der einzige robuste Unterschied ist das Tail-Risiko: Worst-Case -8,83 xATR
(live, Squeeze H2) gegen -2,94 bei 2,5xATR_M5 = Faktor 3. Bei 3,5 Lots sind das
~-890 EUR gegen ~-300 EUR im schlechtesten Einzeltrade.

Antwort auf die Frage: vertretbar ja, aber als Risiko- und nicht als
Ertragsentscheidung. Saubere Umsetzung waere quellen-abhaengig (M5-ATR nur bei
auto_squeeze) - strukturell begruendet, nicht data-mined. Nicht gebaut.

Reproduziert NICHT backtest_sl_tf_mismatch.py (23.07.): dort Einstieg am
Bar-Close, hier am Level - der Einstiegspreis hat den Befund gedreht.

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

4132 lines
312 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CLAUDE.md — Oil Trading Bot (MT5, WTI)
Projektleitfaden für Claude Code. Antworten/UI-Texte auf **Deutsch**.
> 🔄 **Pflege-Regel:** Diese Datei nach **jeder** inhaltlichen Änderung
> mitaktualisieren (geänderte Logik/Werte, neue Module/Endpoints, UI-/Alarm-
> Verhalten, Config-Schalter, Workflows, Asset-Version `v=N`). Kein separater
> Auftrag nötig — Teil jeder Aufgabe.
>
> 🧱 **5-Layer-Prozess bei jeder Änderung** (Daueregel, in dieser Reihenfolge):
> **L1** Kontext, Befunde **& Risiko/Auswirkung** (offene Befunde hier · „erst
> messen" bei Strategie · was kostet/ändert es — EUR-Risiko/Trade, reversibel?) ·
> **L2** Code-Analyse & Design (Datenquelle, *Quelle der Wahrheit*, wo lebt die
> Logik) · **L3** UI-Design (Module klar in Text **und** Grafik, auf dem geklärten
> Datenmodell) · **L4** Implementieren, Verifizieren **& Rollout** (`py_compile` ·
> JS-Klammern · Backtest/Test · Version-Bump `v=N` · Aktivierungsweg `restart_server.bat`
> · **Backup vor destruktiven Schritten**, v. a. DB) · **L5** Doku aktualisieren
> (`docs/` **und** CLAUDE.md, zuletzt).
>
> ⚖ **Layer an die Größe der Änderung skalieren** — nicht jede Aufgabe braucht alle
> 5. Strategie-Änderung = alle 5 (inkl. Backtest); reine Anzeige = L2L5 light (kein
> Backtest); Doku-Korrektur = nur L5. Prozess soll helfen, nicht zur Bürokratie werden.
>
> 🔬 **Track B — Strategie-Lebenszyklus** (ZUSÄTZLICH zu L1L5, für ALLES, was den
> **Edge** berührt: Signal, Risk/Sizing, Exit, Gate, Filter). Track A = L1L5
> (Code/UI/Infra) bleibt unverändert; Track B läuft *parallel* für Strategie-Ideen:
> **B1 Hypothese + Hebel-Check** — welcher Hebel? *Signal = klein* (6× belegt: Filter
> tragen kaum → **höhere Beweislast**, im Zweifel gar nicht bauen) vs. *Sizing/Exit =
> groß* (dort liegt das Geld). · **B2 2-Stichproben-Backtest** — IMMER ≥2 Zeiträume/
> Regime, nie eine (Dead-Hour-Lektion: Std 13 war in einer Stichprobe „profitabel",
> in der zweiten Rauschen; `backtest_hourly_split.py`). · **B3 Kosten-Check** — Ø-Edge
> **netto > ~0,1×ATR** (real Spread+Komm.)? sonst raus. · **B4 Live-klein + Monitor** —
> ausrollen, dann **Live-WR/PF/Verhältnis** (Statistik-live-Kachel `stats_compact` +
> Flip-Zähler) **gegen die Backtest-Erwartung** halten; Kadenz **wöchentlich** (Termin,
> keine Stufe). · **B5 Behalten oder Verwerfen** — driftet es live weg (Regimewechsel)
> → **zurückbauen, kein Ego.** B4/B5 = die Verfall-Erkennung, die im reinen „Änderung
> machen"-Prozess fehlt (Trading ≠ Software: Edge kann verfallen).
>
> 🧾 **B0 — Handels-Checkliste (Mensch, vor jedem manuellen Trade):** (1) **nicht gegen
> das Signal** (gemessen WR 14 %); (2) **Notfall-Stop gesetzt?**; (3) **Gewinner läuft
> das Trailing** (nicht früh kappen — Ø-Verlust ≫ Ø-Gewinn ist die Kern-Leckage). Steuert
> die eigentliche Verlustquelle (diskretionäre Abweichungen), die kein Code-Layer erreicht.
## Was das ist
Algorithmischer **WTI-Öl-Trading-Bot** auf MetaTrader 5. (Brent-Umstellung am
2026-07-09 auf User-Wunsch **noch am selben Tag zurückgenommen** — falls je wieder
Brent: Pepperstone-Symbol heißt `SpotBrent`, kein „UKOIL"; breakout_k=0,3 wurde
dort bereits nachvalidiert, ØR +0,305.) **Eine** Oberfläche:
- **`server.py` + `core/engine.py`** — headless FastAPI-Web-Backend für die
**Mobile-PWA** in `web/` (Zugang vom Handy via WireGuard, `192.168.188.100:8000`).
> Das frühere Tkinter-Widget (`oil_trading_agent.py`, `ui/`, `single_instance.py`)
> wurde **entfernt** — nur noch der Server. ⚠ Immer nur **EINE** Server-Instanz
> gegen dasselbe MT5-Terminal laufen lassen (zwei TrailingManager = Konflikt).
## Architektur (core/)
- `mt5data.py` Preis/Trend/Konto · `trader.py` Orders+SL/TP · `trailing.py`
Trailing/Teil-Exit · `wave_rec.py` **Empfehlung** · `elliott.py` EW/FVG ·
`tu_rating.py` TradersUnion · `agent.py` KI-Copilot
(LLM) · `daily_levels.py` Tages-Prognose (Web) · `gaps.py` D1-Kurslücken ·
`mailer.py` Graph-E-Mail ·
`structure.py` Marktstruktur (M30: HH/HL/LH/LL, BOS, Regressionskanal — reine
Anzeige, kein Signal) · `candle_logger.py` M1-Candle-Log (Daten-Sammlung → `candles_m1`) ·
`market_hours.py` Sessions · `history.py` SQLite · `config.py` Konstanten+
`oil_widget_config.ini`-Loader · `mt5_utils.py` globaler `mt5_lock`.
- `engine.py` = headless Motor: instanziiert die core-Objekte, fährt Loops als
Threads, liefert `snapshot()` für `/api/snapshot` + WebSocket `/ws`.
`_verdict()` aggregiert die Module zur **Gesamtempfehlung** (`verdict` im
Snapshot) — Doku: `docs/gesamtempfehlung.md`.
## Harte Fakten / Stolpersteine
- **MT5 ist Windows-only** (`MetaTrader5`-Wheel). Backend läuft auf Windows neben
dem Terminal; nur Reverse-Proxy/HTTPS könnte auf Linux/Docker.
- **Brokerzeit = UTC+3.** `deal.time`/`tick.time` immer via `_broker_offset_s()`
bzw. Live-Offset in UTC/Lokalzeit umrechnen.
- **`mt5_lock` (global, nicht reentrant):** alle MT5-Calls darüber, mit Timeout.
Nicht aus einem bereits-gehaltenen Lock erneut `close()` (Deadlock). ⚠ Auch
Thread-Pool-Jobs müssen den Lock nehmen: `M15Analyzer.analyze()` läuft im
Executor → eigener `mt5_lock`-Wrapper in `mt5data._fetch_trend_locked` (sonst
Race). `sr.detect()` läuft inline unter dem bereits gehaltenen Lock. Ebenso:
`mt5data.reconnect()` (shutdown/initialize) und `trader._log_open`-`delayed_log`
(positions_get im Thread) laufen jetzt unter `mt5_lock` (waren Races).
- **Steuer:** Broker behält **WHT 26,375 %** (DE-Abgeltungst.+Soli) je Gewinn ein
(`WHT #…`-Buchungen), erstattet via tägl. „Tax settlement". Stats sind **brutto**;
`[trading] wht_pct` zeigt eine Netto-Schätzung.
- **Python:** `C:\Users\ah\AppData\Local\Programs\Python\Python312\python.exe`
(hat fastapi/uvicorn/MetaTrader5). System32 fehlte mal in der PATH → Skripte
nutzen absolute Pfade.
- ⚠⚠ **LLM-PROVIDER-WECHSEL 2026-08-05: `[agent] provider=zai` (war `deepseek`).**
DeepSeek-Guthaben ist leer — `HTTP 402 "Insufficient Balance"`. **z.ai/GLM
(`glm-4.5-flash`) ist kostenlos**, antwortet in ~2,3 s (DeepSeek ~6 s) und wird im
Projekt ohnehin für `daily_levels` genutzt; live verifiziert („[zai] NEUTRAL (30%)").
Kimi (`kimi-k2.6`) antwortet ebenfalls (7,6 s), ist aber kostenpflichtig.
⚠⚠ **Der eigentliche Befund: die Fallback-Kette hatte NICHT gegriffen.** `_active_
provider` prüfte in `avail` nur das **Key-FORMAT** (`startswith("sk-")`), nicht ob der
Key noch funktioniert. Ein Provider ohne Guthaben galt damit als „verfügbar" und
**blockierte die gesamte Kette** (`deepseek→kimi→zai→local`) — der Copilot lief
**~18 h still ins Leere** (29 Fehlversuche ab 04.08. 14:52), ohne dass irgendetwas
außer einer WARNING-Zeile passierte.
**BEHOBEN am selben Tag (`agent._mark_dead`/`_provider_chain`/`_chain_raw`):** ein
hart gescheiterter Provider wird zeitweise übersprungen und `analyze()` nimmt den
nächsten aus der Kette. **Hart = 401/402/403/404** (Key/Guthaben/Modell) → **30 min**;
**nicht erreichbar** (z. B. Ollama aus) → 5 min. **Bewusst NICHT hart: Timeout, 429,
5xx** — das sind vorübergehende Zustände; wer darauf wechselt, verliert bei jeder
Lastspitze den bevorzugten Anbieter und zahlt pro Zyklus die volle Timeout-Summe
ALLER Anbieter (90120 s je Stück). Sichtbar im Snapshot als
**`agent.provider_dead {provider: Restminuten}`** — der Ausfall stand vorher nur im
Log, genau deshalb blieb er 18 h unbemerkt. `_dead` läuft absichtlich **ohne**
`self._lock` (`snapshot()` hält ihn bereits, nicht reentrant → Deadlock; dict-Zugriffe
sind unter dem GIL atomar). Mit **9 Szenarien getestet** (402→Fallback · zweiter Zyklus
überspringt direkt · zwei tote Anbieter · Timeout/429 lösen KEINEN Wechsel aus ·
ConnectionError 5 min · alle tot → sauberer Fehler · Erholung nach Ablauf · Snapshot).
Zurück auf DeepSeek nach Aufladen: `provider = deepseek`.
- **LLM-Provider (Historie):** **DeepSeek (`[deepseek]`) war der aktive
Copilot-Provider** (`[agent] provider=deepseek`, 2026-07-20 bis 2026-08-05). `api.deepseek.com`,
Modell **`deepseek-v4-flash`** (`/models` listet flash + pro; alte Namen
`deepseek-chat/-reasoner` gibt es NICHT mehr). ⚠ v4-flash ist ein **Reasoning-Modell**
(content nach `reasoning_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 ~10001200 `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.
- **⚠ NEUE HTML-Elemente IMMER null-sicher ansprechen (Fix 2026-08-01).**
`index.html` und `app.js` werden vom Browser **unabhängig** gecacht. Trifft neue JS
auf eine alte, gecachte HTML, wirft ein direkter Zugriff (`$("neu").textContent = …`)
eine TypeError — und weil das mitten in `render()` passiert, bricht der **GESAMTE
Render** ab: das Dashboard friert ein und zeigt Altwerte, obwohl Server und Snapshot
korrekt sind. Real passiert beim HL-Kurs-Einbau (User: „ich sehe die Änderungen
nicht"); Server lieferte nachweislich v=129 samt `#hdr-hl` aus, `Cache-Control:
no-store` war gesetzt, kein Service-Worker — die Ursache lag allein in dieser
Kombination. **Regel:** neue Elemente per `const el = $("id"); if (el) { … }`
ansprechen, dann läuft der Rest weiter und nur das neue Feld fehlt, bis die HTML
nachgeladen ist. (Das `?v=N`-Bump bustet nur `app.js`/`style.css` — NICHT die
`index.html` selbst.)
- **Web-Assets:** Browser cacht hart → in `web/index.html` `?v=N` **hochzählen**
bei jeder JS/CSS-Änderung; Server sendet `Cache-Control: no-store`. Hängt der
Handy-Cache: Inkognito oder Seite mit frischem `?xN` öffnen.
- **Empfehlungs-Edge messen:** `python backtest_signal.py [TF] [N] [K]`
(ereignisbasiert, nutzt die echte `_build`-Logik). Ø-Edge>0 = trägt.
- **Ausführungs-Kalibrierung:** `python analyze_execution.py` — (A) echtes
Spread-Profil je Berlin-Stunde aus dem Bar-`spread`-Feld, (B) Slippage bei
SL-Closes, (C) MAE/MFE der Live-Trades. **Kern-Befund 2026-07-13:** Spread ist
absolut ~konstant (0,0225), aber relativ **Ø 0,265×ATR** — die alte
Kostenannahme 0,1×ATR war v. a. in Niedrig-Vola-Phasen ~2,5× zu optimistisch
(erklärt Live-PF < Backtest-Erwartung). Stunden 1517 am günstigsten (~0,13),
**Nacht 07 = Kostenfalle (0,320,50×ATR)**. Slippage-Tail real: die 35/54-
Desaster hatten 0,75 Slippage ÜBER den Initial-SL hinaus (harter SL deckelt in
schnellen Märkten nicht). MAE/MFE live: Exits validiert (nur 7 % der Verlierer
waren je ≥1,3×ATR im Plus → Breakeven-Schwelle passt; Gewinner-MAE Median 0,28).
⚠ Künftige B3-Checks mit **stunden-echten Kosten** rechnen, nicht pauschal 0,1.
(WR/PF/Verhältnis/Worst je KW, Ausrichtungs-Split mit/gegen/ohne) gegen die
Backtest-Erwartung (WR~68 % · PF~1,3 · Verh~0,55) und flaggt Leckagen (Gegen-Signal-
Anteil, WR-Verfall, PF<1, Einzelverlust). **Wöchentlich** laufen lassen (B4/B5).
- **Vor „fertig":** immer `python -m py_compile <datei>`; JS grob via Klammern-
Balance prüfen (kein node im Env).
- **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.)
- **Volatilitäts-Squeeze-Breakout = ADDITIVER SETUP, UMGESETZT (User-Idee
„Breakouterkennung" 2026-07-14, gemessen `backtest_breakout_squeeze.py`):**
struktureller Ausbruch aus einer **Kompression** (Box = Spanne der letzten `_SQ_N`=10
abgeschlossenen M5-Bars ≤ `_SQ_MULT`=2,5×ATR) um `_SQ_K`=0,1×ATR — NICHT der
EMA-Trend-Breakout. **2-Stichproben-positiv** (ØR **+0,227/+0,141** netto Echtkosten,
PF 1,35/1,20, 173/413 Trades; schlägt den Ausbruch aus *beliebiger* Box 0,045/+0,161
in BEIDEN Hälften) & **robust über Box 1020 / k 0,10,2** (alle 8 Kombis positiv,
kein Parameter-Zufall). Andere Signalklasse als die Welle → komplementär. **Umsetzung:**
`wave._squeeze_one(highs,lows,closes,atr)` (M5). ⚡ **Erkennungs-Latenz ~5 s (Variante A,
2026-07-21):** der Squeeze läuft auf einem EIGENEN, schnellen 60-Bar-M5-Fetch
(`_sq_due`/`_SQ_REFRESH_S=5.0`, entkoppelt von der 15-s-TF-Ampel-Drossel) → Ausbruch wird
~15 s→5 s schneller erkannt, dann ~1 s Handeln (`_pos_loop`). Gleiche validierte M5-Regel,
reine Infra (kein Backtest nötig). **Variante B (Squeeze auf M1) = OFFEN/UNBELEGT** — erst
backtesten, wenn der M1-Logger (`candles_m1`, seit 2026-07-21) ab ~2027-01 genug History hat.
Snapshot **`wave.squeeze {state, dir, level, box_atr}`**: `active` = Ausbruch (echtes
Signal LONG/SHORT) · `armed` = komprimiert, Ausbruch steht bevor · None = keine
Kompression (transient — der Ausbruch weitet die Box, Signal klärt sich selbst).
**Verdict-Modul „Squeeze"** (`_verdict`, Gewicht 2,0, nur bei `active`; unter der
Welle=3,0). **UI Welle-Kachel** (`#wave-squeeze`): grün/rot bei `active`
(„🚀 Squeeze-Breakout ▲ LONG @ …"), blau bei `armed` („🎯 Kompression … Ausbruch
steht bevor"), grau sonst. ⚠ **Nur der ENTRY** — der User-Teil „Gegen-Trade schließen
+ Stop-&-Reverse" ist NICHT gebaut (nicht validiert, verdoppelt Kosten, Chop-Risiko).
Läuft normal über SL/Trailing/Notfall-Stop. Caveats: modeste Stichprobe (Box>12 =
1222 Trades = Rauschen), Frequenz ~23/Woche → **B4-Wochenmonitor** (Live-ØR/PF
gegen +0,14…+0,23), Runner-abhängig (43 % Treffer). Verwerfen, wenn Live wegdriftet (B5).
- **Momentum-Continuation als Auto-Setup = VERWORFEN (gemessen `backtest_momentum.py`,
2026-07-17):** Kandidat für Autonomie (Einstieg in Richtung eines frischen
Momentum-Schubs, mom_N kreuzt ±X×ATR; seq. Sim, Live-Exit, Echtkosten). **Fällt
durch den 2-Stichproben-Test:** fast alle N/X in H1 negativ (0,03…−0,22), in H2
positiv (+0,02…+0,07) → regime-abhängig. Einzige „beide positiv"-Kombi (N=24/X=2,0)
ist H1 **+0,002 / PF 1,00** = Breakeven-Rauschen, nicht der Squeeze-Maßstab
(+0,14…+0,23). Grund: Momentum-Continuation IST Trendfolge — erbt die Regime-
Anfälligkeit des Wave-Signals, kein distinkter Edge wie die Kompression→Ausbruch-
Klasse. **Autonomie-Fazit: der Squeeze bleibt der EINZIGE validierte Auto-Entry.**
(P(break)-Level-Bounce als Entry ist separat schon verworfen, s. „Range-Einstieg".)
- **Opening Range Breakout (ORB) als Auto-Setup = VERWORFEN (gemessen
`backtest_orb.py`, 2026-07-17, Recherche-Kandidat):** Box = erste 15/30 min nach
EU-Open (10:00 Brk) bzw. US-Open (16:30 Brk), Ausbruch ±0,1×ATR, one-shot, Live-
Exit, Echtkosten. **3 von 4 Varianten kippen zwischen den Hälften** (US-Open H1
0,19…−0,27!); einzig EU-15min ist beidseitig positiv, aber nur ØR +0,069/+0,027
(PF 1,11/1,04) = Rauschen unter dem Kosten-Puffer, und die Nachbar-Parameter
kippen (keine Robustheit — beim Squeeze waren ALLE 8 Kombis positiv). ⚠ Lehrstück:
erster Lauf zeigte EU-Open ØR +1,3/PF 10 — war ein **Selektions-Artefakt** (ATR-
Skip statt Floor ließ in der Niedrig-Vola-H1 nur 9 Morgen durch); mit Floor
verschwand der „Edge". Zeit-Anker ersetzt keinen Struktur-Anker.
- **ATR-Breakout-Bestätigung** (`wave._confirm_breakout`, live `[trading]
breakout_k=0.3` — Split-validiert, s. u.; 0=aus): ein
Richtungssignal wird **erst durchgelassen, wenn der Kurs k×ATR in
Signalrichtung** gelaufen ist („X dynamisch"); läuft er vorher k×ATR DAGEGEN/
Timeout → neu verankern, solange **WARTEN** (Grund „warte auf Breakout …",
`snap.breakout`). **Stateful** (`_pend`), reset bei TF-Wechsel. Gemessen
(`backtest_breakout.py`): hebt Ø-Edge/Trade **~2×** (+0,185→+0,36 bei k=1,0),
Treffer 43→47 %, ~halb so viele Trades → **netto** klar besser (halbe Spread-/
Komm.-Kosten; lohnt ab Kosten >0,037×ATR, real ~0,1×ATR). = die „Zwei-Konten-
Hedge"-Idee des Users, auf EINEM Konto ohne doppelte Kosten. **k=0,3 seit
2026-07-08 (Split-validiert, 80k Bars in 2 Halbjahren):** k=0,3 ist in BEIDEN
Hälften das Beste — H1: ØR +0,119/netto +339 (0,5: +0,055/**831**; 1,0:
+0,046/805 — beide netto negativ!), H2: +0,362/+4239 (0,5: +3308; 1,0: +3838).
Grund: 0,3 filtert Fehlstarts, behält aber den besseren Einstiegspreis (höheres k
= späterer Einstieg, Anfang des Moves verschenkt). ⚠ Ein-Stichproben-Lauf hatte
„1,0 besser" gezeigt — Split-Prüfung drehte das (B2!). Nebeneffekt: kürzere
„warte auf Breakout"-Phasen.
- **Entry-Raum-Gate (AKTIV, `[trading] entry_room_atr=0.6` — kurz 1,0, am selben
Tag zurück auf 0,6, s. u.; gemessen `backtest_entryroom.py` 2026-07-16):**
Richtungssignal → WARTEN, wenn das
**Gegenlevel** (M5-Pivot in Trade-Richtung, trainingsgleich zum S/R-Auto-Close)
näher als X×ATR_M5 liegt. Befund (seq. Sim MIT S/R-Close-Exit, Echtkosten,
2 Halbjahre): **monoton in BEIDEN Hälften** — Raum <0,3: ØR 0,119/0,067
(PF 0,42/0,66!) · 0,30,6: 0,100/0,059 (⚠ 7982 % WR und trotzdem PF<1 =
Klein-Close-Falle) · 0,61,0: 0,123/0,060 · ≥1,0 bzw. „kein Gegenlevel" positiv.
Kosten-Arithmetik, kein Signal-Timing (deshalb robust, anders als die 9 verworfenen
Filter): Ertrag am Level gedeckelt (63 % Containment) 0,265×ATR Kosten < 0.
Auslöser: Live-Trades mit Sofort-/Klein-Close (0-min-Trade +0,08 direkt am Level;
4 der 5 sr_closes desselben Tages waren dagegen RETTER — Exit NICHT anfassen).
Umsetzung: `wave._room_gate` (in `refresh_market` VOR `_confirm_breakout`;
`set_entry_room`, Grund „kein Raum: Widerstand X nur 0,4×ATR entfernt …").
Backtests via `_build` bleiben gate-frei (kein pb_levels-State). **0,6→1,0→0,6
(2026-07-24):** kurz auf 1,0 erhöht (gemessen in BEIDEN Hälften besser, H1 +415/
H2 +266 R; Live-Kurzcheck: S/R-Closes seit 16.07. +925 € bei nur 28 % Klein-Closes)
— aber der User empfand im gemischten Regime (M15/M30 ab, M5/H1 auf) **zu viel
WARTEN** und stellte noch am selben Tag auf 0,6 zurück. ⚠ Lehr-Fall dabei
festgehalten: „kein LONG trotz steigendem Kurs" lag NICHT am Raum-Gate, sondern am
**M30-Gegen-Trend-Filter** (M30 ab → LONG unterdrückt, gewollt); das Raum-Gate
mutete nur den daraus resultierenden SHORT (Support 0,09×ATR entfernt). 0,6 vs 1,0
ist reiner Frequenz-gegen-Edge-Kompromiss (gemessen 1,0 netto besser). ⚠ **P(break)-Freigabe des Gates = VERWORFEN (gemessen
`backtest_entryroom_pbreak.py`, 2026-07-24):** Idee war, Raum<1,0-Signale
durchzulassen, wenn P(break)@Entry hoch (Level bricht eh → Ertrag nicht gedeckelt).
Das Modell TRENNT zwar auch am Entry (Freigabe-Gruppen klar besser als der Rest),
aber JEDE Schwelle kippt zwischen den Hälften (p≥0,5: 0,016/+0,083 · p≥0,6:
+0,089/0,006 · p≥0,7: +0,076/0,029) → nicht robust, reines Distanz-Gate bleibt.
**13. verworfener Signal-Eingriff.** (Nebenbefund: geblockte Gruppe in beiden
Hälften klar negativ, 436/251 ΣR — bestätigt die 1,0-Erhöhung.)
- **Mindest-Konfidenz-Gate** (`_MIN_CONF=55`): Signal < 55 % → WARTEN. Gemessen
(`backtest_conf.py`): Band **4054 % = negativer Edge** (0,025), ≥55 % = +0,058;
Gate hebt Ø-Edge ~+50 % und den Gesamtertrag (entfernt die schwachen Setups, bei
denen zu viele Strafen gestapelt sind — typisch der Lagging-Short am Boden).
- **⚠⚠ DER 43-%-MASSSTAB WAR UNGÜLTIG — der richtige liegt bei ~80 % WARTEN
(gemessen `backtest_dist_gated.py`, 2026-08-05).** `backtest_dist.py` misst
**`_build` ALLEIN**, ohne **Breakout-Bestätigung** und ohne **Entry-Raum-Gate**.
Genau die beiden sind live die größten Blocker. Über 80k M5-Bars:
| | gerichtet | WARTEN |
|---|---|---|
| nur `_build` (die alte Referenz) | **77,9 %** | 22,1 % |
| **voller Live-Stack** | **19,5 %** | **80,5 %** |
| LIVE (seit 01.08., ohne `stale`) | 7,2 % | 92,8 % |
Aufschlüsselung im vollen Stack: entry_room **41,4 %** · breakout_pending 17,0 % ·
stretch 13,8 % · deadband 7,4 % · min_conf 0,9 %.
✅ **Damit ist der „93 % statt 43 %"-Alarm entschärft**, der den TF-Churn-Fix
(2026-07-31) ausgelöst hat: verglichen wurden zwei verschiedene Systeme. Der reale
Abstand zum gültigen Maßstab ist **12,3 Pp**, nicht 50.
⚠ **Ein Rest bleibt** (19,5 → 7,2 %) und ist NICHT wegerklärt. Kandidaten, die eine
Bar-Simulation prinzipiell nicht abbildet: der **TF-Wechsel** (live wandert die
Basis-TF M5…M30, die Sim ist fix M5), Cooldowns, EIA-/News-Fenster, und die
**Abtastung** — live wird pro Sekunde gemessen, ein `breakout_pending`-Zustand
hält über eine ganze Bar an und ist deshalb live überrepräsentiert (26,5 % gegen
17,0 %). Der auffälligste Einzelposten ist **min_conf: live 16,3 %, Sim 0,9 %** —
das ist der nächste konkrete Ansatzpunkt, wenn jemand dem Rest nachgehen will.
⚠ **Die im TF-Churn-Fix dokumentierte Erfolgskontrolle („WARTEN-Anteil Richtung
43 % bewegen") ist damit hinfällig** und sollte nicht weiterverfolgt werden.
- **Signalhäufigkeit** (`backtest_dist.py`): ~57 % der Zeit LONG/SHORT, ~43 % WARTEN.
WARTEN-Treiber: M30-Gegen-Trend (~27 % der Bars, gewollt, Edge×2) + Überdehnung
+ Totband. `_STRETCH_MAX` 2,5→**3,5** angehoben (`backtest_stretch.py`: +26 %
Signale, Gesamtertrag steigt; ab 3,5×ATR kippt der Edge negativ).
- **⚠ M30-FILTER NACH VOLA-REGIME GEPRÜFT = KEINE ÄNDERUNG (gemessen
`backtest_htf_vola.py`, 2026-08-04).** Anlass: Am 04.08. fiel WTI 4,12 $ (davon
4,16 $ in EINER Stunde), der Bot stand zu 93 % auf WARTEN, `htf_counter` blockte
**160-mal** — scheinbar genau die Richtung, in die der Markt lief. Hypothese: die
nachlaufende M30-EMA sperrt in schnellen Umkehrungen das Richtige.
**Die Messung invertiert die Hypothese.** Geblockte Signale, kanonischer Exit,
Echtkosten, nach ATR-Terzil × 2 Halbjahre:
| ATR-Terzil | geblockt H1 | geblockt H2 | durchgelassen H1 | durchgelassen H2 |
|---|---|---|---|---|
| niedrig | 0,086 | +0,107 | 0,136 | 0,134 |
| mittel | 0,172 | +0,067 | 0,198 | 0,069 |
| **hoch** | **0,625** | **0,329** | 0,061 | **+0,014** |
Im HOHEN Terzil sind die geblockten Signale **mit Abstand die schlechtesten des
gesamten Datensatzes** (PF 0,15 / 0,42) — der Filter ist dort am wertvollsten, nicht
am schädlichsten. Die durchgelassenen Hoch-Vola-Signale in H2 sind das **einzige
positive Feld** der ganzen Tabelle. In den unteren Terzilen kippen die geblockten
Signale zwischen den Hälften = Rauschen. **Den Filter in schnellen Bewegungen zu
lockern wäre die schlechtestmögliche Änderung gewesen. 22. verworfener Eingriff.**
⚠⚠ **Auflösung des scheinbaren Widerspruchs — und eine Lehre über Populationen:**
Die 160 geblockten Signale und die 17 manuellen Gewinn-Trades desselben Tages sind
**verschiedene Grundgesamtheiten**. Die manuellen Trades liefen ALLE auf
`rec=WARTEN`, stammen also gar nicht aus der Signal-Logik — der Filter hat den User
nie gebremst, er handelte unabhängig. „Das Gate blockte X" und „der Mensch verdiente
Y" gehören nur dann zusammen, wenn Y aus X stammt. Die Ausgangsvermutung hat genau
das verwechselt.
- **Higher-TF-Gegen-Trend-Filter:** Signal wird verworfen (→WARTEN), wenn der
übergeordnete **M30**-Trend (EMA12/50, Totband `_HTF_DEADBAND=0,15×ATR`) klar
dagegen steht — kein Short im M30-Aufwärtstrend & umgekehrt. Nur für Basis-TF
< M30 aktiv (`_htf_sign`, holt M30-Bars im selben `mt5_lock`). Per Backtest
belegt: Ø-Edge ×2 (+0,012→+0,027), Treffer 52→54 %. Mess-Tool:
`python backtest_htf_filter.py M5 M30 4000 0.15`.
- **Einstiegsqualität (nur Konfidenz/Score, NICHT Richtung):** nach gemessenem
Edge (`backtest_pullback.py`) gestaffelt — **tiefer Pullback** (Kurs durch die
EMA zurück, `near<0`) = bestes CRV (+15, „⭐ tiefer Pullback"); laue Zone direkt
an der EMA (00,3×ATR) = schwächster Edge (3); Trend-Momentum (12×ATR) +8.
**Kein** Spitzen-Gate: ein hartes „nur an Wellenspitze"-Filter senkte den
Gesamtertrag (80 % Trades), darum verworfen. Konfidenz blockt NICHT das Ordern
(nur WARTEN/Gegen-Signal tut das).
- **Multi-TF-Konfluenz (nur Konfidenz/Anzeige):** M30 **und** H1 bestätigen die
Richtung → +12, „⭐⭐ Konfluenz M30+H1"; H1 dagegen → 8. Per Backtest belegt:
Ø-Edge ×3,5 (+0,016 nur-M30 → +0,056 M30+H1). `_htf_sign` holt zusätzlich
H1-Bars. Mess-Tool: `python backtest_improve.py 8000`.
- **„Morgens fällt der Kurs, nachmittags steigt er" = WIDERLEGT (gemessen
`analyze_tageszeit.py`, 2026-08-04, User-Beobachtung):** ROHE Kursdrift je
Berliner Stunde (nicht Signal-Edge wie `backtest_hourly_split.py`), 80k M5-Bars
(17.06.2504.08.26), DST-korrekt via `zoneinfo`, 2 Hälften, Überschuss gegen den
jeweiligen Tagesschnitt (sonst färbt das Regime alle Stunden ein: H1 fiel, H2 stieg).
**ALLE VIER Tagesblöcke kippen zwischen den Hälften** — es gibt überhaupt kein
Tageszeit-Muster (Zehntel-Cent je Bar):
| Block | H1 | H2 | |
|---|---|---|---|
| Vormittag 0812 | +0,06 | 0,43 | kippt |
| Nachmittag 1418 | 1,26 | +1,96 | kippt |
| Abend 1923 | 0,39 | +1,67 | kippt |
| Nacht 0007 | +0,42 | 1,30 | kippt |
Robust auf: 2/5/20/21/23 Uhr · robust ab: 14/18/19 Uhr — verstreute Einzelstunden
aus 24 Tests, kein Vormittag/Nachmittag-Muster.
⚠ **Größenordnung macht es ohnehin unhandelbar:** die stärkste belastbare Stunde
(20:00, +1,9/+3,3) sammelt über eine volle Stunde ~0,020,04 $ — der Spread allein
ist **0,0225 $**. Selbst wenn die Drift echt wäre, ist sie kleiner als die Kosten.
⚠ Std 23 sticht mit +33,0 in H2 hervor, hat aber nur n=648 (22 Uhr: 1062, sonst
~1750) — das ist der Marktschluss-Rand, kein Befund.
⚠⚠ **ZWEI eigene Messfehler auf dem Weg dorthin — beide dokumentiert, weil sie
Fehlerklassen sind, keine Ausrutscher:**
**(1) Selbst gebauter ATR-Filter (04.08.).** Der erste Lauf meldete „Vormittag
robust negativ" — Artefakt von `atr < 0,06` übersprungen + ATR-Normierung. In den
ruhigen Morgenstunden der älteren Periode liegt der ATR unter dem Floor → von
1.740 Bars je Stunde blieben **138391** übrig, und zwar die ungewöhnlich
volatilen Morgen. Gleiche Fehlerklasse wie das ORB-Selektions-Artefakt 2026-07-17.
**Regel: ein Filter, der Bars nach einer Eigenschaft verwirft, die mit der
untersuchten Achse korreliert, ERZEUGT den Befund.**
**(2) Zeitzone um 3 h verschoben (05.08., beim Squeeze-Nachgang aufgefallen).**
`datetime.fromtimestamp(rates["time"], UTC+3)` behandelt den Bar-Zeitstempel als
echten UTC-Epoch — MT5 liefert dort aber bereits die **Broker-Wallclock**. Ergebnis:
jede Stunde war **3 Stunden zu spät** einsortiert; der zweite Lauf meldete deshalb
„Nachmittag robust negativ", was in Wahrheit der Abend war. **Richtig:**
`fromtimestamp(ts, UTC).replace(tzinfo=UTC+3).astimezone(Berlin)` (Helfer `_berlin`
im Skript). ⚠ Der Rest des Projekts war NICHT betroffen (geprüft) — `core/gaps.py`
& Co. nutzen die richtige Konvention; der Fehler steckte nur in diesem neuen Skript.
**Plausibilitätstest, der ihn entlarvt hätte: die dünnen Stunden (n≈650/1060) müssen
am Marktschluss liegen (22/23 Uhr), nicht mitten in der Nacht.**
- **Tageszeit-Gate = AUS geschaltet (User-Vorgabe 2026-07-22, `[trading] dead_hours=`
leer — bewusst GEGEN die Messung).** Jetzt **config-steuerbar** (`wave.set_dead_hours`,
Komma-Liste von Stunden; leer=aus). Gemessener Default im Code = `0,1,2,3,4,5,6,7,12,16`
(die ini überschreibt auf leer). ⚠ 12/16 waren zuletzt am 2026-07-21
(`backtest_hourly_split.py`) noch beidhälftig negativ — der User schaltet das Gate
trotzdem ab. Reaktivieren = ini-Stunden eintragen. Der **EIA-Blackout (Mi 15:3016:30)
bleibt separat aktiv** (eigenes Event-Gate, nicht Teil des `dead_hours`). Historie/
Messung (Gate war 1114 → 12+16 → aus → zurück → jetzt wieder aus):
Nach der **Echtkosten-Messung** (`backtest_realcosts.py`, Kosten = Bar-Spread/ATR
statt pauschal 0,1×ATR — Ø real 0,265!): **Nacht 07 = Kostenfalle** (konstanter
Spread ÷ niedriger Nacht-ATR = 0,320,50×ATR, in beiden Hälften negativ; reine
Arithmetik) + **12/16 Uhr 3× unabhängig negativ**. Politik „07+12" verbessert
BEIDE Hälften (H1 4038→−1870, H2 +100→+1191). **Robust positiv nur 2122 Uhr.**
⚠ Die „15 robusten Negativ-Stunden" NICHT blocken — in-sample-zirkulär (Overfit).
breakout_k=0,3 auch unter Echtkosten bestätigt (H1 einziges positives k). Historie
(Gate war 1114, dann 12+16, dann auf User-Wunsch aus, jetzt gezielt zurück):
- **Von 1114 auf 12+16 umgestellt (gemessen+verifiziert, `backtest_hourly.py` +
`backtest_hourly_split.py`, 80k M5 in 2 Halbjahren):** Netto-R je Stunde zeigt
**12 Uhr (0,11/0,12) und 16 Uhr (0,17/0,10) in BEIDEN Hälften negativ**;
**Std 13 kippt das Vorzeichen** (0,048/+0,039 = Rauschen), 11/14 ~breakeven. Das
alte Pauschal-Gate 1114 blockte harmlose Stunden und ließ die real schlechte 16
laufen. **Gate 12+16 schlägt 1114 im Gesamt-Netto-R in beiden Zeiträumen**
(H1 1357→−1253, H2 +1562→+1799) und handelt dabei mehr Trades. Intraday-Edge
generell dünn; **Löwenanteil in US-Session 2122 Uhr** (+0,25/+0,49).
- **Reversal-Ausnahme vom Gate = VERWORFEN (gemessen, `backtest_deadhour.py`):**
Reversal in der Dead-Hour nur **+0,062×ATR brutto** (vs +0,190 außerhalb) → nach
Kosten (~0,1×ATR) negativ. Trade-①-Gewinner war Varianz. Gate gilt auch für Reversal.
- **EIA-Blackout (kausales Event-Gate, AKTIV):** **Mittwoch 15:3016:30 Berlin**
(Vorlauf der EIA-Lagerdaten 16:30 = 10:30 ET, Offset ganzjährig 6 h) → hartes
WARTEN („EIA-Blackout …"). Gemessen (`backtest_events.py`, 2 Halbjahre): dieses
Fenster in BEIDEN Hälften netto negativ (0,147/0,088 vs rest) — deckt sich mit
dem alten „16 Uhr"-Befund. eia_post/api_pre/api_post NICHT robust → nicht geblockt.
Nur im Live-Pfad (`hour≠None`), Backtests bleiben gate-frei. Best-Practice
„Event-Blackout" statt Pauschal-Gate.
- **Wirtschaftskalender-Blackout (`core/econ_calendar.py`, 2026-07-30, `[trading]
news_blackout=true`, ±15 min):** Erweiterung der EIA-Idee auf ALLE High-Impact-
Termine (FOMC/Zinsentscheid/NFP/CPI/GDP/PCE + alle Oil-/OPEC-Titel). Quelle
**Forex Factory JSON** (`nfs.faireconomy.media`, kostenlos, kein Key, mit
`impact`-Feld); geholt werden **beide Wochen** (`thisweek`+`nextweek` — „thisweek"
endet samstags, der Markt öffnet aber Sonntag 23:00 → sonst blinder Fleck), 1×/h
**im Daemon-Thread** (ein hängender HTTP-Call darf den Trend-Loop nicht blockieren),
dedupliziert über (ts, title). Filter: `impact=High` + `country=USD`, ODER Titel
enthält crude oil/opec/oil inventories/petroleum (der EIA-Report läuft bei FF teils
als Low/Medium). ⚠ **NICHT gemessen** — Events sind zu selten für eine
2-Stichproben-Aussage, das ist die Handbuch-Regel („keine Trades 15 Min. vor
High-Impact-News"), kein belegter Edge. Deshalb bewusst asymmetrisch verdrahtet:
**sperrt nur die AUTONOMEN Entries** (`_check_auto_squeeze` + `_check_auto_signal`,
jeweils OHNE Dedup-Marker → nach dem Fenster feuert das Setup, falls noch aktiv) und
erscheint in der Entry-Checkliste nur als **„warn"**, nicht „fail" (soll den Verdict
nicht allein auf `stop` kippen); **manuelle Orders bleiben frei.** Der gemessene
EIA-Blackout bleibt unabhängig davon aktiv — er greift auch, wenn der Kalender kein
Netz hat (Fail-safe: ohne Daten → kein Blackout, Log 1×, dann still). Snapshot
`calendar {enabled, n, blackout{title,impact,ccy,minutes}, next, age_min}`,
Frontend `#cal-note` (Meldungen-Karte, bernstein: laufendes Fenster bzw. Vorwarnung
≤60 min). Abschalten `news_blackout=false`; Fenster via `news_blackout_before_min`/
`_after_min`. Mit 4 synthetischen Szenarien getestet (5 min vor/nach Event, 50 min
davor = kein Blackout, disabled) + Live-Fetch verifiziert (EIA lag korrekt auf
Mi 16:30 Berlin).
- **⚠⚠ WINKEL-BESCHRIFTUNG WAR ÜBERALL INVERTIERT — Text korrigiert 2026-08-04, ZAHLEN
unverändert.** Beim Spiegeltest (User: „prüfe die Logik im umgekehrten Fall, fallender
Kurs") kam heraus: `calc_trend_angle` liefert **0°=AUFWÄRTS … 180°=ABWÄRTS**, also ist
`ad = angle90` **negativ bei STEIGENDEM** Kurs. Damit sagten die Texte das Gegenteil
dessen, was passierte — ein LONG in einem steigenden Markt bekam „⚠ Winkel gegen EMA"
(10), ein LONG in einem fallenden „Winkel bestätigt" (+5).
⚠ **Und `backtest_angle.py` benutzt DIESELBE verdrehte Beschriftung**
(`a5_dis = a5 < 90-dead` für LONG heißt „dagegen", ist aber STEIGEND). Der
dokumentierte Befund „Winkel dafür/neutral +0,055 vs dagegen +0,025" bedeutet richtig
gelesen: **LONG bei FALLENDEM Kurzfrist-Winkel (= Pullback) trägt doppelt so gut wie
bei bereits steigendem.** Das ist der im Projekt mehrfach belegte **Pullback-Effekt**
(vgl. „⭐ tiefer Pullback" und den RSI-Befund: *bestätigende* Oszillatoren sind
schlechter).
✅ **Konsequenz: die Gewichtung war von Anfang an NUMERISCH RICHTIG** — nur hieß der
Bonus „bestätigt", obwohl er den Gegen-Winkel belohnt. **Nicht umdrehen.** Geändert
wurden ausschließlich die Reason-Texte („⭐ Pullback (Kurzfrist-Winkel gegen den
Trend)" / „kein Pullback — Kurs läuft dem Signal schon davon") und die Kommentare.
Verifiziert: Konfidenzwerte vorher/nachher identisch (60/70/75 je Richtung).
⚠ **Lehre:** Der erste Verdacht lautete „systematisch invertierte Logik" — er war
falsch. Die Zahlen stimmten, die Sprache log. Bei einer verdrehten Konvention muss man
prüfen, ob die MESSUNG dieselbe Verdrehung teilt, bevor man den Code „repariert".
(Beim Reversal war es anders: dort hat die eigenständige Messung das Setup unabhängig
von der Beschriftung als negativ ausgewiesen.)
✅ **Spiegeltest bestanden:** Mit `reversal_enabled=false` ist das Verhalten über die
ganze Überdehnungs-Spanne **exakt symmetrisch** (steigend/LONG ↔ fallend/SHORT):
gleiche Signale, gleiche Konfidenz, gleicher Anti-Überdehnungs-Schnitt bei 3,5.
- **Winkel-vs-EMA-Warnung (nur Konfidenz):** Regressions-Winkel der Basis-TF gegen
die EMA-Richtung → 10 „⚠ Winkel gegen EMA (mögliche Wende)" (nachlaufende EMA!);
dafür → +5. `backtest_angle.py`: dafür/neutral +0,055 vs dagegen +0,025; als Gate
aber Gesamtertrag-negativ → bewusst nur Konfidenz/Warnung (Hilfe fürs manuelle
Überspringen der Lagging-Shorts).
- **Höher-TF-Winkel-Filter (M30+H1-Winkel) = VERWORFEN (gemessen,
`backtest_htf_angle.py`):** Signal verwerfen, wenn M30/H1-Regressionswinkel gegen
die Richtung steht → in JEDER Schwelle/Variante **schlechter**. Die entfernten
Signale (Short bei aufwärts-Winkel) haben **höheren** Edge (+0,040,05) als die
behaltenen (+0,02) — es ist der profitable Pullback-Setup. Filter kostet ~⅔ des
Gesamtertrags (+287→~+70). Gegen-Winkel-Shorts NICHT filtern; einzelne große
Verluste (z. B. 37 an einem H1-Auf-Tag) sind Varianz, Schutz über Notfall-Stop/
Sizing, nicht über Signal-Filter.
- **Auch als Konfidenz-STRAFE verworfen (`backtest_confluence_angle.py`):** statt
Hard-Filter den Konfluenz-Bonus streichen/Konf senken, wenn M30/H1-Winkel
dagegen → borderline-Signale fallen unters 55-Gate. Ergebnis: in JEDER Variante
**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 EMA12EMA50| > thr×ATR_H1)
läuft. **Fällt durch:** die weggefilterten Signale flippen das Vorzeichen zwischen
den Hälften — thr 0,5: ØR **H1 0,198 / H2 +0,001**; thr 1,0: **0,069 / +0,013**;
thr 1,5: **+0,029 / 0,063**. In KEINER Schwelle robust negativ in BEIDEN → Filter
hätte in H2 (positive/breakeven drops) **Geld gekostet**. **7× belegt: die 31 %-SHORT-
Schwäche ist REGIME (aktueller Uptrend), kein Signalfehler** — in H2 tragen dieselben
Gegen-H1-Shorts. Kein Filter; der Hebel bleibt Sizing + Notfall-Stop + Diskretion
(User-WARTEN-Übersteuerungen liefen 68 % WR / +259 €).
- **ER-Chop-Gate = VERWORFEN (gemessen, `backtest_chopgate.py`, 2 Halbjahre):**
Signal→WARTEN wenn Efficiency Ratio der letzten N Bars < X. Chop-Signale sind NICHT
robust schlechter — Effekt kippt: H1 Trend leicht besser (Ø-R +0,070 vs +0,053),
H2 **umgekehrt** (Chop +0,138 vs +0,129). Gate wirft zudem ~70 % (profitables)
Volumen weg (ΣR +2299→+836). Das Totband filtert Extrem-Chop schon (ER-0,02-Tag =
71 % WARTEN); Rest-Signale = Varianz. **5× belegt: Signal-Filter tragen nicht.**
- **Vola-Abzug (nur Konfidenz):** hohes ATR (oberes Terzil, `_ATR_HIGH≈0,27`) =
schwächster/negativer Edge → 8 „hohe Vola". Schwelle regime-abhängig → kein Gate.
- **Reversal-Lockout/-Persistenz = VERWORFEN (gemessen, `backtest_revlock.py`):**
Gegen-Signale **kurz nach** einem Reversal sind sogar **besser** als normale
(+0,072 vs +0,018 bei N≤6) → Reversal-Signale NICHT sperren/halten. Einzelne
Verluste (Bottom-Short nach REV) sind Varianz, kein Muster.
- **Rec-Logging:** `engine._run_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 = angle90`: **ad < 0 = STEIGEND**, ad > 0 = fallend.
Der Code triggert aber `stretch >= +3.0 and ad <= -2` → SHORT, kommentiert als
„überkauft + Winkel ab". Tatsächlich feuert er bei überkauft **UND WEITER STEIGENDEM**
Winkel — also GEGEN den Winkel, während CLAUDE.md „Einstieg IN Winkel-Richtung"
beschreibt. Dasselbe Vorzeichen steckt im Konfidenz-Block (Zeile ~790): ein SHORT
bekommt „**Winkel bestätigt**" (+5), während der Kurs steigt. Mit synthetischen
Reihen direkt nachgestellt: überkauft + steigend → `WAVE_REV_SHORT`, Grund „Winkel
gedreht" + „Winkel bestätigt". **Erklärt zwei dokumentierte Befunde:** die ~50 %
Richtungstreffer der Bounce-Anzeige (`backtest_bounce_reliability.py`) und „blutet in
starken Trends".
⚠⚠ **GEMESSEN (`backtest_reversal_angle.py`, 80k M5, kanonischer Exit, Echtkosten,
2 Halbjahre) — ALLE drei Varianten fallen durch:**
| Variante | H1 ØR | H2 ØR | PF | n |
|---|---|---|---|---|
| **IST** (Live) überkauft + Winkel steigt → SHORT | 0,122 | 0,031 | 0,79/0,94 | 1100/1615 |
| **KORRIGIERT** (Doku-Absicht) Winkel fällt → SHORT | **0,173** | **0,061** | 0,68/0,89 | 106/163 |
| **KONTROLLE** ohne Winkelbedingung | **0,099** | **0,023** | 0,82/0,96 | 1153/1693 |
Drei Schlüsse: (a) **Die Vorzeichenkorrektur ist SCHLECHTER** — gut, dass sie nicht
blind eingebaut wurde; sie feuert zudem kaum (106 statt 1100 Trades, weil bei 3×ATR
Überdehnung die 14-Bar-Regression fast immer noch steigt). (b) **Die Kontrolle ohne
Winkel ist die BESTE der drei** — die Winkelbedingung entfernt Trades, die im Schnitt
besser waren als die behaltenen; sie trägt mit KEINEM Vorzeichen etwas bei.
(c) **Der Trigger ist in jeder Variante beidhälftig negativ.**
⚠ Gemessen wurde der ROHE Trigger (ohne Konfidenz-/Raum-Gate); der Legacy-Recheck der
gegateten Variante kam auf ~Breakeven (PF 0,93/1,06) — dieselbe Richtung. **Keine
Messung stützt die dokumentierten „+0,185 / PF 1,35" mehr.**
✅ **ABGESCHALTET (User-Entscheidung 2026-08-04, `[trading] reversal_enabled=false`).**
Ausschlag gab nicht nur die negative Zahl, sondern dass das Setup **zwei gemessen
POSITIVE Schutzmechanismen aushebelt** (Anti-Überdehnung UND den M30-Gegen-Trend-
Filter, Edge ×2) — ein Setup ohne eigenen Edge, das validierte Filter umgeht, ist der
ungünstigste Fall.
⚠ **Wichtig für Backtests: der Modul-Default in `wave_rec` bleibt `True`.** Sämtliche
Backtests rufen dasselbe `_build`; ein False-Default hätte alle bestehenden Messungen
still verändert. Abgeschaltet wird ausschließlich im Live-Pfad
(`engine` → `wave.set_reversal_enabled(...)`, Config-Default dort `false`).
Verhaltensänderung nur im Band **|stretch| 3,03,5**: dort kam vorher ein
antizyklisches REV-Signal, jetzt das normale Trendsignal (und ab 3,5 greift wie
gehabt die Anti-Überdehnung). Mit 3 Szenarien getestet — überkauft+steigend:
`SHORT/WAVE_REV_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 (~7580 %) ist also kein Gate-Fehler; wer will, liest die
Reason-Liste („Reversal" + „hohe Vola") als Risiko-Kontext. **Gemessen (`backtest_bounce_reliability.py`):
Richtungs-Trefferquote der „aktiv"-Anzeige ~50 % (Münzwurf), Ø-Vorlauf ~0×ATR über
alle TF & H1-Regime → reiner Überdehnungs-Kontext, KEIN Signal.** (Die früheren
„70 %" aus `backtest_bounce.py` waren exit-getrieben, nicht Richtungs-Vorhersage.)
- **⚠⚠ TF-CHURN-FIX (2026-07-31, zwei Eingriffe) — Ursache für „93 % WARTEN statt 43 %":**
Auslöser war die User-Frage „warum hat der Bot die Treppe nicht erkannt und long
eröffnet?" zur Rally am 31.07. (82,5 → 84,4 in 2 h). Befund: in dem Fenster gab es
**99× WARTEN und 1× SHORT** — und dieser SHORT wurde am Tief autonom eröffnet
(AUTOSIG, 26,31 €, per `adverse15` geschlossen); den LONG machte der User um 11:56
von Hand gegen die Empfehlung. **Der WARTEN-Anteil live ist strukturell zu hoch:**
| | WARTEN |
|---|---|
| Backtest-Erwartung (`backtest_dist.py`) | ~43 % |
| Live letzte 24 h | **93,2 %** |
| Live letzte 7 Tage | 88,0 % |
| Live gesamt (93.599 Zeilen) | 78,6 % |
**MECHANISMUS (im Log belegt):** `_choose_tf` setzt den Score einer TF **hart auf
0,0**, sobald sie überdehnt ist (`raw = sep if (sep>=deadband and stretch<=_STRETCH_MAX)
else 0.0`) — genau das passiert M5 IM Trend. Real am 31.07.: M5 sprang zwischen
**0,00 und 1,93**, die TF wechselte **33× am Tag, Median-Abstand 5 min**. Die
vorhandene 1,2×-Hysterese ist dagegen wirkungslos (gegen 0,0 kommt jeder Wert durch).
Und `set_timeframe()` verwarf bei JEDEM Wechsel die laufende Breakout-Bestätigung
(`_pend = None`) und verankerte sie beim AKTUELLEN Kurs neu. Bei `breakout_k=0,3` und
ATR_M30≈0,53 braucht sie ~0,16 $, der Kurs lief ~0,08 $ je 5 min → **die Bestätigung
konnte rechnerisch nie fertig werden.** Dazu: landet die Heuristik im Trend auf M30,
braucht das EMA-Kreuz dort Stunden — eine 2-h-Bewegung wird nie zum Signal.
⚠ **Gleicher Fehlertyp wie beim P(break)-Modell am selben Tag:** `backtest_breakout.py`
hat k=0,3 auf einer **FESTEN** Zeitebene validiert; live wandert sie — die Bedingung,
unter der die Messung gilt, existiert im Betrieb nicht.
**FIX 1 (`wave_rec.set_timeframe`): `_pend` wird NICHT mehr zurückgesetzt.** Der Anker
gehört zum Signal, nicht zur Zeitebene; dreht die Richtung, verankert
`_confirm_breakout` ohnehin neu (`p.get("dir") != d`). Das Level behält den ATR der
alten TF — bewusst, eine Neuberechnung wäre wieder ein Reset.
**FIX 2 (`engine._tf_loop`): Mindest-Verweildauer `[trading] tf_min_dwell_s=900`**
(0 = aus). Gibt einer TF Zeit, ihr Signal überhaupt fertig zu bestätigen.
**Verifiziert mit synthetischen Szenarien:** Fix 1 — Bestätigung überlebt den Wechsel
M5→M30 und feuert LONG, Richtungswechsel verankert korrekt neu; Fix 2 — an der ECHTEN
Score-Folge vom 31.07. nachgespielt: 4 Wechsel → 2.
⚠ **Ehrlich zur Beweislage:** das ist **NICHT backtestbar** — Backtests laufen
grundsätzlich auf einer festen TF, das Churning existiert dort per Konstruktion nicht.
Begründung ist also nicht „gemessen besser", sondern „stellt die Bedingung her, unter
der die vorhandene Messung gilt". **Erfolgskontrolle = der WARTEN-Anteil**: er muss
sich Richtung der gemessenen ~43 % bewegen (Abfrage s. Tabelle oben).
⚠ **Nicht behoben (bewusst):** der 0,0-Einbruch des Scores bei Überdehnung selbst —
das wäre eine Änderung der TF-Bewertung und damit messpflichtig. Fix 2 halbiert das
Churning, verhindert aber nicht den ersten Absturz auf M30.
- **Timeframe:** `[trading] tf_select=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, 12 % = 0 %). Risiko-Modus bleibt gebaut & abgesichert (Guards s. u.),
Umschalten = `risk_pct=1.5`. Mechanik: `trader._send_locked`
bestimmt **erst den SL**; bei `risk_pct>0` → risiko-basiert (`calc_lots_risk`,
Verlust ≈ risk_pct % Equity, Margin als Deckel) = der gemessene Fix gegen die
Verlust-Asymmetrie. **Umschalten via `[trading] risk_pct` (z. B. 1.5 = überlebbar).**
⚠ **Risiko-Modus-Guards (Fix 2026-07-19):** `calc_lots_risk` rundet NICHT mehr auf
`volume_min` auf (unter Mindestlot → 0.0 statt still mehr Risiko), und
`_send_locked` fällt bei `risk_pct>0` NICHT mehr still auf Margin-Sizing zurück
(75 % wäre ein Vielfaches des gewollten Risikos) — der Trade wird stattdessen mit
Fehlermeldung abgelehnt. Bei `risk_pct=0` (aktuell) unverändert margin-basiert.
Harter Broker-SL immer mitgeschickt; adoptierte Fremd-Trades bekommen Schutz-SL.
- **Sizing-Simulation (`backtest_sizing.py`):** 90 % Margin = Wachstums-optimal,
ABER **36 % P(DD>80 %)** = Ruin-Risiko. Risiko 12 %/Trade = überlebbar (0 %
Quasi-Ruin). Exit-Asymmetrie gemessen `backtest_exit2.py` (Live-Exit Trail 1,5
ist OK; Problem war Sizing + manuelles Gewinner-Kappen, NICHT die Exit-Logik).
- **SL:** Band **1,82,2×ATR** (Ziel 2,0; `INIT_SL_MIN/MAX_ATR`). **Breakeven ab
+1,3×ATR** (`_BREAKEVEN_ATR`; war 0,6 → 21 % Breakeven-Scratches, gemessen
`backtest_exit.py be`: 1,3 = Plateau-Optimum → Scratch 5 %, Ø-R +15 %, kein
Tail-Risiko). **Früh-Ausstieg = AUS** (`_ADVERSE_EXIT_ATR=0`). Trailing
Init/Trail/Lock. **Teil-Exit AUS** (`_PARTIAL_TP_FRAC=0`, User-Vorgabe) → am TP
wird der **komplette** Trade geschlossen (kein 50 %-Runner mehr).
- **Früh-Ausstieg NUR bei Trades OHNE Signal-Deckung = VERWORFEN (gemessen
`backtest_adverse_nosignal.py`, 2026-07-23, Auslöser 57,34-€-Trade: manueller
LONG gegen WARTEN, KEIN Squeeze/Signal-Flip beteiligt):** Hypothese war, dass ein
Adverse-Exit bei signal-ungedeckten Trades (Totband/WARTEN-Zone, B0 zeigt live
39 % WR dort) helfen könnte, auch wenn er pauschal auf ALLE Trades schadet. **Fällt
durch — bei JEDER Schwelle (0,5/0,75/1,0/1,5×ATR), in BEIDEN Populationen (SIG/
NOSIG), fast durchgehend in BEIDEN Hälften negativ** (einzige Ausnahme: SIG H1
X=1,5 knapp +11 R, aber H2 dort 46). WR bricht von ~3843 % auf 1826 % ein — der
Früh-Ausstieg kappt zwar einige Trades vor dem vollen SL, schneidet aber weit öfter
Trades ab, die sich erholt hätten; NOSIG-H2 war ohne Eingriff sogar leicht profitabel
(ΣR +80, PF 1,15) und wird durch jede Adverse-Schwelle verschlechtert. Hypothese
„schwächere Population profitiert eher" widerlegt. Keine Änderung.
- **⚠⚠ TRAIL × BREAKEVEN GEMEINSAM GEPRÜFT = KEINE ÄNDERUNG — und ein
METHODEN-BEFUND, der einen alten Schluss entwertet (2026-08-05,
`backtest_trail_be.py` + `analyze_trail_resolution.py`).** Anlass: User-Frage zur
Kalibrierung „jetzt bei 3 Lot".
⚠ **Zur Prämisse: die Positionsgröße ändert das Trail-Optimum NICHT.** R ist
ATR-normiert; Lots skalieren nur das EUR-Ergebnis. Gemessen wurde deshalb die
echte offene Frage: **`be=1,3` stammt aus `backtest_exit.py`, als der Trail noch
1,5 war; `mult` wurde am 31.07. auf 1,0 gesenkt, OHNE das Breakeven neu zu
prüfen.** 2D-Sweep über beide Populationen (2.365 Squeeze- / 1.566 Wellen-Entries),
feste Entry-Listen, kanonischer Exit, Echtkosten, 2 Halbjahre.
**(1) Das Breakeven ist praktisch wirkungslos.** Bei mult ≤ 0,6 sind ALLE
be-Zeilen **identisch** — der enge Trail erreicht den Einstand ohnehin früher als
der Breakeven-Boden. Erst ab mult 0,8 wirkt er überhaupt, und dort ist be=0,6
marginal besser (+0,019 bzw. +0,018 ØR). ⚠ Das liegt **unter einem Standardfehler**
(≈0,026 bei n≈1.500) → **kein Handlungsbedarf**, be bleibt 1,3.
**(2) Der Trail ist monoton besser, je enger — bis 0,05×ATR.** Und genau das ist
das Problem: ein Trail von 0,05×ATR (≈3 Cent) wäre vom Spread allein ausgelöst.
Ein Optimum, das bis an die physikalische Unmöglichkeit monoton bleibt, misst
nicht den Markt, sondern die Methode.
⚠⚠ **AUFLÖSUNGS-ARTEFAKT NACHGEWIESEN.** `simulate()` prüft den Stop gegen
Bar-Hoch/-Tief. Ein Trail, der ENGER ist als die typische Intrabar-Spanne, ist
nicht auflösbar — das Modell nimmt den Bar-Hochpunkt als Gewinn mit und steigt
erst im NÄCHSTEN Bar aus. Test mit **in DOLLAR fixiertem** Trail auf M5 gegen M1
(597 gemeinsame Einstiege):
| Trail | M5 ØR | M1 ØR | Differenz |
|---|---|---|---|
| 0,10×ATR | +0,093 | +0,066 | **0,027** |
| 0,20×ATR | +0,049 | +0,039 | 0,010 |
| 0,30×ATR | +0,013 | +0,016 | +0,003 |
| 1,00×ATR | 0,046 | 0,010 | **+0,036** |
| 1,50×ATR | 0,038 | +0,028 | **+0,066** |
**Das Vorzeichen kippt bei ~0,3×ATR:** die gröbere Auflösung **überschätzt enge
Trails und unterschätzt weite**. Eine Tick-Simulation würde denselben Weg noch
weiter gehen. ⚠ **Damit ist der Befund vom 31.07. („enger ist monoton besser",
`backtest_trailmult.py`) teilweise entwertet** — er ruht auf derselben
M5-Auflösung. Die damalige Entscheidung, NICHT auf 0,5 zu gehen, war trotzdem
richtig, nur aus dem falschen Grund (genannt wurde „Randwert + Slippage";
der eigentliche Grund ist die Auflösungsgrenze).
⚠ Nebenbefund gegen meine eigene Slippage-Vermutung: die Trade-ZAHL ist über alle
Multiplikatoren fast konstant (2.108 bei 0,3 gegen 2.083 bei 1,0), nur die
Haltedauer sinkt (12 → 18 min) und der Worst-Case ist überall identisch (2,44,
der SL deckelt). Die Slippage-Exposition steigt also NICHT dramatisch — das
Auflösungs-Argument trägt, das Slippage-Argument kaum.
✅ **Ergebnis: Trail 1,0 / Breakeven 1,3 bleiben.** 1,0×ATR entspricht rund einer
M5-Bar-Spanne (die ATR IST die mittlere Bar-Spanne) und ist damit ungefähr die
feinste Einstellung, die diese Simulation ehrlich auflösen kann. Wer enger will,
braucht Tick-Daten — nicht mehr Bars.
- **Trailing-Optimierung geprüft = KEINE Änderung (gemessen, `backtest_trailing.py`,
2 Halbjahre, echte Phasen-Sim):** Die Live-Config (`_TRAIL_START_ATR=0,3` ·
mult 1,5 M5 · Breakeven 1,3) hat die **beste Verlustseite** (bestes H1, Worst nur
1,5×ATR). Trailing trägt klar: ohne Trailing ΣR 1313/Worst 2,0 vs. Live +1777/
Worst 1,5 (~+3000 R **und** kleinerer Einzelverlust). **Breiter ziehen** (mult
2,02,5) oder **Trail später starten** (0,3→1,0) macht H1 schlechter/Worst größer;
der frühe Trail-Start ist **netto schützend** (holt „läuft-schief"-Trades billiger
raus). Einzige mithaltende Variante (start0,6) kippte zwischen den Läufen =
Rauschen. → Ausschütteln ist Varianz, nicht Logikfehler.
- **Time-Stop (AKTIV, gemessen `backtest_timestop.py`):** Position wird geschlossen,
wenn sie nach **120 min noch in Phase „Init"** hängt (HW-Profit nie ≥0,3×ATR =
Whipsaw-Opfer; `trailing._TIMESTOP_MIN=120`, 0=aus, `closed_by=timestop`).
Verbessert BEIDE Hälften (H1 +98→+162, H2 +2191→+2298 R), Worst unverändert;
kürzere Fenster (30/60 min) kippten zwischen den Hälften = Rauschen. Dank
Phasen-Ratsche ist „Init nach 2 h" exakt „nie Trail-Start erreicht".
- **ATR-Floor 0,12→0,06 gesenkt (gemessen, `backtest_atrfloor.py`, 2 Halbjahre):**
Die reale Vola fiel unter den alten Floor (H1: **81 % der Signale ATR<0,12** →
Floor band dauernd, Exit-Distanzen künstlich ~1,31,6× breit, dazu ~35k
Log-Warnungen je 5-MB-Rotation). Floor 0,06 in **beiden** Hälften besser
(H1 153→−27 Pts, H2 +512→+554); ganz ohne Floor nur marginal besser → 0,06 als
Schutz behalten. Floor-Warnung jetzt **gedrosselt** (max. 1×/5 min).
- **Befund B — umgesetzt (Exit-Simulation `backtest_exit.py`, Doku
`docs/exit-simulation.md`):** altes Band 1,21,5 war zu eng (MAE ~1,7×ATR,
3137 % Frühstopps). 2,0×ATR hebt Ø-R +32 %, PF 1,33→1,39, Frühstopps →25 %,
Worst-Case auf 2,0×ATR gedeckelt. Breiter (34×ATR) wäre im Schnitt noch
besser, aber Tail-/Drawdown-Risiko → verworfen. ⚠ Da Sizing margin-basiert
(90 %, nicht risiko-basiert), schlägt die SL-Breite voll auf den EUR-Verlust
durch. Qualität-abhängiger SL NICHT belegt (MAE über alle Buckets gleich).
- **S/R-Close (am Level schließen) = pauschal 2× VERWORFEN, als GEMESSENES
Segment aber AKTIV:** Pauschal (`backtest_srclose.py`, Echtkosten, 79k Signale)
verdoppelt Close@Level die WR (35→67 %), verschlechtert aber PF/ΣR in BEIDEN
Hälften (0,78→0,67 · 0,88→0,76) = Gewinner-Kappen. **ABER** (`backtest_srbreak.py`,
47k Touches, User-Idee „P(Durchbruch)>60 % → laufen lassen"): Basisrate
P(break)=3637 %; das **Anlauf-Momentum trennt robust in beiden Hälften** —
Kurs KRIECHT ans Level (letzte 6 Bars <0,3×ATR) → P(break) nur 26 %, **Close
schlägt Laufenlassen Ø +0,12 R**; mit Schwung (≥0,3) → laufen lassen (4143 %).
Volumen/Touch-Zahl trennen NICHT robust.
- **Kalibriertes P(Durchbruch)-Modell (2026-07-13, `analyze_srwaves.py` +
`backtest_srclose_prob.py`):** Containment-Befund — **~68 % der Wellen erreichen
das gegenüberliegende Level, davon brechen nur ~37 % durch → ~63 % prallen ab**
(robust beide Hälften). P(break) ist **vorhersagbar & kalibriert**: Logit auf 4
Merkmalen (Anlauf-Momentum 6/3 Bars ·d, mit-Trend, Level-Distanz), auf H1
trainiert, auf H2 **AUC 0,71 + Kalibrierung trifft** (pred 17→21 %, 49→50 %,
69→76 %). Dominant = 3-Bar-Beschleunigung (`_PB_W` mom3 +0,78). **R-Ertrag
(Echtkosten):** Regel „close wenn P(break) < X" schlägt reines Trailing in BEIDEN
Hälften massiv (+6000/+7600 R); **User-Schwelle 0,60 im Optimum-Plateau (0,500,60
~gleich), validiert.** ⚠ Impliziert Strategiewechsel: ~90 % der Level-Touches
werden geschlossen (Trendfolger→Level-Scalper) — trägt, weil echte Kosten (0,265×
ATR) + 63 % Containment das Laufenlassen teuer machen.
- **S/R-Close SIGNAL-gegated („bei mit-Trend halten statt schließen") = VERWORFEN
(gemessen `backtest_srclose_signal.py`, 2026-07-22, User-Idee):** Vorschlag war,
den S/R-Close zu unterdrücken, solange die Empfehlung noch in Trade-Richtung zeigt
(halten bis WARTEN). **Fällt klar durch — in BEIDEN Hälften ~5300…−5700 R
schlechter** als der reine P(break)-Close. Grund/Datenbeleg: ~85 % ALLER Level-
Touches passieren, WÄHREND die (nachlaufende) EMA noch „in Richtung" zeigt
(23,8k/20,1k von 27,8k/26,2k Touches) — genau dort HÄLT das Level aber. Diese
Touches geschlossen = ΣR +11703/+13606, geHALTEN = nur +5978/+8347. Die
Trend-Empfehlung lagt → sie „bestätigt" ausgerechnet am Abpraller. Das kalibrierte
P(break)-Modell (hat „mit-Trend" als Merkmal) macht das schon richtig; es mit dem
groben EMA-Signal zu übersteuern kostet ~⅔ des S/R-Close-Edges. **12. verworfener
Signal-Eingriff.**
- **⚠⚠ AUTO-FLIP-CLOSE = TROTZ NEGATIVER MESSUNG GEBAUT (User-Vorgabe 2026-07-31
„schließe den Trade automatisch beim Close-Signal"), `[trading] auto_flip_close=true`,
`auto_flip_close_min_r=0.5`.** Dreht die Empfehlung GEGEN die offene Position und
steht sie ≥0,5×ATR im Plus → `trader.close(reason="flip_close")`.
**NEU GEMESSEN mit dem ECHTEN Exit (`backtest_flipclose2.py`, 80k M5, 2 Halbjahre,
Echtkosten, Exit aus `core/exit_model.py`) — bestätigt das alte Urteil, deutlicher:**
| Variante | Δ H1 | Δ H2 |
|---|---|---|
| Flip ab 0,0×ATR | 126 | 221 |
| Flip ab 0,3×ATR | 77 | 216 |
| **Flip ab 0,5×ATR** | **92** | **160** ← mildeste, deshalb Default |
| Flip auch im Minus | 205 | 364 |
Basis ohne Flip: H1 ΣR 537 · H2 140. **JEDE Variante ist in BEIDEN Hälften
schlechter.** Mechanik: Trefferquote steigt 39 → 45 %, Ertrag fällt =
**Gewinner-Kappen** (die nachlaufende EMA dreht oft mitten im Pullback).
⚠ **Warum überhaupt neu gemessen:** das alte `backtest_flipclose.py` nutzte ein
**VIERTES Exit-Modell** (Trail fest 1,5 · KEINE Lock-Phase · KEIN Time-Stop ·
KEIN Initial-TP · `_MAXH` 288 · Phasen über den Bar-Close statt über das
High-Water) — auf dieser Grundlage wäre das Urteil für den Live-Betrieb nicht
bindend gewesen. Für die Neumessung bekam `exit_model.simulate()` einen
**`stop_when`-Hook** (+ `ret_bar`), damit die Phasen-Mechanik NICHT wieder kopiert
werden musste; Rückwärtskompatibilität verifiziert (ohne Hook bitgenau identisch).
**NACHRANG gegenüber dem S/R-Close (gemessen `backtest_exit_combo.py`,
2026-07-31, `auto_flip_close_subordinate=true`):** Beide Exits konkurrieren um
dieselben Trades — wer zuerst feuert, gewinnt. Die S/R-Freigabe lief OHNE
Flip-Close, die Baseline existierte also nach dem Einbau nicht mehr. Gemessen auf
**IDENTISCHEN Entries** (5 Varianten, 2 Halbjahre, Echtkosten, Exit-Kern):
| Variante | Δ H1 | Δ H2 | 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 (511 %) —
genau die, für die dieser validiert ist. Mit Nachrang ist es in BEIDEN Hälften
besser als ohne. Umsetzung: liegt ein S/R-ZIEL ≤0,6×ATR entfernt (dieselbe
Schwelle wie der Live-Hinweis), hält der Flip sich zurück. Mit 4 Szenarien
getestet (in/außer Reichweite · kein Level · Nachrang aus).
⚠ **Ehrlich dazu:** auch MIT Nachrang kostet der Flip in H2 rund 35 R gegenüber
„nur S/R-Close" (49 vs 14). Der Nachrang begrenzt den Schaden, er dreht ihn
nicht um. Abschalten des Features bleibt `auto_flip_close=false`.
⚠ **Methodik-Fehler beim Bau, korrigiert:** der erste Lauf ließ die sequentielle
Sim nach dem Exit bei `xb+1` weiterlaufen — dadurch hatte JEDE Variante eine
ANDERE Trade-Folge (sobald eine früher aussteigt, verschiebt sich alles
Nachfolgende) und die Zahlen waren nicht vergleichbar. Jetzt feste, geteilte
Entry-Liste wie in `backtest_pbreak_rvalue.py`.
Umsetzung `engine._check_flip_close` im `_pos_loop`: nur im PLUS, 1× je Ticket,
Startup-Schonfrist beachtet, Log 🔄 + Telegram, Snapshot `flip_close`/
`flip_close_min_r`/`flip_close_count`. Mit **10 synthetischen Szenarien** getestet
(beide Richtungen · aus · unter Schwelle · gleichgerichtet · WARTEN · im Minus ·
flat · Schonfrist · Ticket-Dedup). Der Alarm (Blinken/Ton/Telegram) bleibt
unabhängig davon. **B4/B5-Pflicht: Live-Ertrag gegen die Erwartung halten —
driftet es weg, `auto_flip_close=false`.**
- **Auto-Flip-Close = VERWORFEN (gemessen `backtest_flipclose.py`, 2 Halbjahre,
sequentielle 1-Positions-Sim, 2026-07-16):** User-Taktik-Kern „Position im Plus
automatisch schließen, wenn die Empfehlung dreht" (heute nur Alarm). KEINE Variante
(minR 0/0,3/0,5×ATR, auch-im-Minus) hält in beiden Hälften ØR & ΣR ≥ Basis:
H1 ΣR 278→−280…−417, H2 **+174→+119…+144** (kostet 1732 % des Ertrags).
Mechanik: WR steigt 43→50 %, Ø-Gewinn schrumpft = **Gewinner-Kappen** — die
nachlaufende EMA dreht oft mitten im Pullback, der Trade wäre weitergelaufen.
Flip bleibt **Alarm + menschliche Entscheidung** (User-Übersteuerungen 68 % WR).
Der Rest der User-Taktik (Ziel=nächstes S/R, weiterlaufen wenn stark) IST der
P(break)-Auto-Close; der „3 %-Margin-Anker" = EUR-Schwelle, Klasse „verliert".
- **⚠ NACHGEMESSEN 2026-08-04 mit dem NACHTRAINIERTEN Modell — Ablehnung BESTÄTIGT,
deutlicher als zuvor** (User-Frage „es werden immer noch Mini-Trades mit geringen
Gewinnen geschlossen, wie verhindern wir das?"). Δ ΣR gegen minR=0:
| minR | H1 | H2 |
|---|---|---|
| 0,1×ATR | 6 | +10 |
| 0,2×ATR | **720** | 42 |
| 0,3×ATR | 1136 | 157 |
| 0,5×ATR | 1998 | 683 |
| 0,8×ATR | 2887 | 1577 |
**Monoton schlechter in BEIDEN Hälften.**
⚠⚠ **Und die Prämisse der Frage stimmt so nicht.** Auswertung der 295 Trades seit
20.07.: die 28 Gewinne ≤2 € bringen zusammen **+21,11 €** = **0,8 %** der
Brutto-Gewinne (2.770 €) — sie sind kein Ertragsproblem. Entscheidend ist, WOHER
sie kommen:
| Schließgrund | Gewinner | Median | 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 4653 %, PF<1). **Asymmetrie:
P(break) taugt zum EXIT** (Trade schon mit-Trend im Plus am Gegenlevel → kassieren),
**nicht zum ENTRY** (antizyklisch am Extrem = Münzwurf-Richtung, 6× belegt). Einstieg
bleibt mit-Trend (Pullback). **Trailing bleibt** — nicht mehr Standard-Exit (das ist
der S/R-Close), aber Manager für die ~10 % Durchbruch-Runner (P≥Schwelle) + Fallback
für Trades ohne erreichbares Gegenlevel.
- **Umsetzung:** `engine._p_break(mom6,mom3,wt,dist)` (eingebettete `_PB_MU/SD/W`);
`wave_rec.snapshot` liefert **`pb_feats` {mom6, mom3, ema_diff, atr} — FEST aus M5**
(aus dem ~15-s-Turn-Fetch gecacht, ATR-Floor 0,12 wie im Training). ⚠ **Bugfix
2026-07-13:** vorher kamen die Features aus der Wave-TF (Heuristik bis M30 → mom6
war 3-h- statt 30-min-Momentum, ATR 23× → P falsch skaliert) und das Level aus dem
Misch-Set `_sr_levels` (Zonen/Gaps/Tageslevel). **2. Bugfix gleicher Tag:** die
M15-Cluster-Level (`market.sr`, SR_MIN_TOUCHES) sind in frischen Trends oft LEER →
Hint verschwand komplett. Jetzt **trainingsgleiche Level**: `wave.pb_levels` = rohe
**M5-Pivots (k=3, ~300 Bars)** aus dem Turn-Fetch (M5 dafür 320 Bars tief), Highs
in `_m5_hl` gecacht. Am Rally-Hoch gibt es für LONGs korrekt KEIN Gegenlevel →
kein Hint (nichts zu closen). ⚠ **3. Bugfix 2026-07-15:** das Close-Level kommt
jetzt aus **`_draw_levels`** (dieselbe geclusterte+Hysterese-Linie wie Chart &
Dashboard) statt aus den rohen `pb_levels` — sonst feuerte die Meldung an einem
anderen Level als die gezeichnete Linie (User: „passt nicht zusammen"). P(break)-
Features (M5) unverändert, nur `level`/`dist` verschieben sich um Cents.
**`_sr_close_hint`** (Anzeige ≠ Auslösung, Fix
2026-07-14 — das 0,15×ATR_M5-Fenster ≈ 0,03 $ war als reine Anzeige unsichtbar
klein): Hinweis erscheint ab **≤0,6×ATR Annäherung** (blaue „nähert sich"-
Vorwarnung mit Live-P(break) + `gap_atr`); **Auslösung `close=true` nur AM Level**
(≤0,15×ATR = gemessener Touch) und `p_break < [trading] sr_close_pbreak`
(Code-Default 0,60, Band 0,300,90; **ini seit 2026-07-24 = 0,55** auf User-Wunsch —
liegt im validierten Optimum-Plateau 0,500,60, Hysterese-Zone damit 50/60).
Snapshot `sr_close_hint {level, p_break,
at_level, gap_atr, close, confirm}`. Hinweis existiert NUR mit offener Position.
**`confirm` = Bruch-BESTÄTIGUNGS-Preis** (Fix 2026-07-22, User-Frage nach einem
Trade, der am Level geschlossen wurde, obwohl der Kurs „durchgebrochen" war):
`level ± 0,5×ATR` in Trade-Richtung — **dieselbe Definition wie beim Modell-
TRAINING** (`_BRK_ATR=0,5`/`_BRK_W=12` in `backtest_srclose_prob.py`/
`analyze_srwaves.py`). Ein Docht, der die nackte Levelzahl nur um wenige Cent
überschreitet, zählt NICHT als Durchbruch — reales Fallbeispiel 2026-07-22:
Close bei Level 85,943 (P(break) 31 %), Kurs wischte auf 85,994 (< Bestätigung
86,017 = Level+0,5×ATR) und fiel zurück → nach Modell-eigener Definition eine
ABLEHNUNG, keine Bestätigung; die Vorhersage war korrekt. `confirm` jetzt in der
Log-Zeile („— Bestätigung erst ab X") UND im Frontend-Hinweistext (`#pos-srhint`,
alle 3 Zweige: Vorwarnung/Close/Laufen) sichtbar — macht den Unterschied
„Kurs > Levelzahl" vs. „strukturell bestätigter Bruch" auf einen Blick klar.
**Live-Prognose-Tracking (2026-07-23, User-Wunsch „mitloggen wie oft richtig/
falsch"):** jeder frische Level-Touch (`at_level` False→True) loggt EINMAL die
aktuelle Vorhersage (`bounce` wenn P(break)<Schwelle, sonst `break`) in die neue
Tabelle **`pbreak_predictions`** (`history.log_pbreak_prediction`, Felder u. a.
`predicted`, `confirm_price`, `reject_price`, `atr`). Auswertung läuft separat,
self-throttled ~5 min im `_trend_loop` (`engine._evaluate_pbreak_predictions`):
prüft gegen `candles_m1`, ob der Kurs binnen 60 min zuerst `confirm_price`
(→ outcome `break`) oder `reject_price` (→ `bounce`) erreicht — **exakt dieselbe
Definition wie beim Modell-TRAINING** (`_BRK_ATR=0,5`/`_BRK_W=12` M5-Bars=60 min).
Kein Treffer binnen 60 min → Timeout, zählt wie im Training als `bounce`. `correct`
= `predicted==outcome`. Broker-Zeit-Offset (`trader._broker_offset_s()`) korrekt
berücksichtigt (`pbreak_predictions.ts` = lokale Epoch, `candles_m1.time` = rohe
Broker-Zeit — Ausnahme, s. „Auch beachten"). Abfrage: `history.pbreak_accuracy
(period)` bzw. `GET /api/pbreak_accuracy?period=today|week|all` (read-only, kein
Token) → `{n, accuracy, break_accuracy, bounce_accuracy, pending, …}`. Getestet
mit synthetischen Szenarien (break/bounce/Gegenteil-falsch/Timeout) — alle 4 exakt
wie erwartet. **Reine Messung, kein Signal-/Exit-Eingriff** — noch keine UI-Karte
(Datensammlung; B4-artige Auswertung später möglich, wenn genug Zeilen vorliegen).
**Gegen-/Stop-Seiten-Anzeige `stop_approach` (2026-07-24, User-Frage „Kurs nähert
sich Support, aber keine Durchbruchwahrscheinlichkeit angezeigt"):** Der reguläre
`sr_close_hint` beobachtet nur das **Ziel-Level** (LONG→Resistance, SHORT→Support)
und wirkt nur im Plus — nähert sich der Kurs dem **Gegen-/Stop-Level** (LONG→Support
darunter, SHORT→Resistance darüber, typ. Verlust-Trade), war die App bewusst still.
Neu: `engine._stop_approach_hint` liefert für GENAU dieses Level eine **reine
Anzeige** von P(Durchbruch) — Break-Richtung `-d` (gegen den Trade), Feature-/
60-s-Glättung (`_stop_pb_state`) identisch zu `_sr_close_hint`. **KEIN Auto-Close.**
Snapshot `stop_approach {level, side, p_break, at_level, gap_atr, confirm, reject}`,
Frontend `#pos-stophint` (grün „Level hält wahrscheinlich" bei P<50, rot „bricht
wahrscheinlich durch" bei ≥50; nur wenn KEIN Ziel-`sr_close_hint` läuft; v=109).
**Vorher gemessen (`backtest_pbreak_calibration.py`, 2026-07-24, Gate „erst messen,
dann bauen"):** Das kalibrierte P(break)-Modell (auf Ziel-Seite trainiert) hält
auch auf der **Gegen-/Stop-Seite** — AUC **0,68/0,72** out-of-sample in BEIDEN
Hälften (≈ Ziel-Seite 0,67/0,71), im niedrigen P-Bereich sauber kalibriert (der
praktisch wichtige „hält der Support?"-Fall). ⚠ Einzige Schwäche: im HOHEN Bereich
(P≥0,5) GEGEN den Trend unterschätzt es die Bruch-Rate ~1013 Pp (predicted 58 % →
echt 71 %) — also **konservativ** (warnt eher zu wenig vor einem Bruch), kein
Kalibrierungs-Bruch. Deshalb reine Anzeige, kein Trade-Trigger.
**Anti-Flattern (2026-07-14, gemessen `analyze_pbreak_flicker.py`:** mom3 der
laufenden M5-Kerze ließ P 715 Pp/Bar springen, Max 77 Pp; 8 % der Annäherungen
hatten ≥2 Schwellen-Flips): (1) **P zeit-geglättet** (~60-s-EMA in `_pb_state`,
Reset bei Level-Wechsel/flat; angezeigtes `p_break` = geglättet) · (2)
**Hysterese ±5 Pp** um die Schwelle — close↔laufen wechselt erst bei klarem
Durchbruch (55/65 bei Default 0,60, liegt im Optimum-Plateau 0,500,60).
**Dashboard-Schalter** in der
Position-Kachel (`#srclose-btn`, grün=AN) → `POST /api/srclose` (Toggle,
`engine.set_sr_autoclose`); Zustand `snapshot.auto_sr_close`. **AUTO-Close (`_check_sr_close` im `_pos_loop`,
`auto_sr_close=true`):** schließt **nur im PLUS** (`pnl>0`) wenn `close=True`, 1×/
Ticket, wirkt bei gesperrtem Handy. **Optionaler Mindestgewinn** (`[trading]
sr_close_min_gain` Default 0=aus · UI `#srmin-input` · `POST /api/srclose_min` ·
`set_sr_close_min_gain` · Snapshot `sr_close_min_gain`): unter der EUR-Schwelle
wird am Level NICHT geschlossen. ⚠ Gemessen ist OHNE besser (Verworfen-Eintrag) —
Opt-in auf User-Wunsch, UI-Status warnt. **Automatisch auf X % des Einsatzes
(`[trading] sr_close_min_gain_pct`) — Stand 2026-07-29 wieder AUS (`=0`, User-Wunsch
„gewinn close und notfall stop wieder auf leer beim Eröffnen")** (Verlauf: 1,0→3,0
am 07-23 · 0 am 07-24 früh „nichts beim Öffnen setzen" · 3,0 am 07-24 abends zurück ·
**0 am 07-29** — gilt NUR für den Gewinn-Close, der Auto-Notfall-Stop bleibt AUS).
Bei `pct>0` wird bei JEDER echt neuen Position
(nicht beim Wiedererkennen nach Neustart) `sr_close_min_gain` frisch auf
`pct% × Margin dieser Position` gesetzt (`_check_auto_close`, retry-sicher via
`_sr_close_min_gain_armed_ticket` — Margin aus `trader.refresh()` im selben
`_pos_loop`-Tick, jeden Tick erneut versucht bis Margin>0; persistiert via
`_save_runtime_state`, Log „Gewinn-Close armiert … +X (N% Margin Y)"). Bei `pct=0`
wird beim Öffnen NICHTS gesetzt — dann bleibt der manuell im UI gesetzte/gemerkte
`sr_close_min_gain` maßgeblich. ⚠ **Umstellen auf 0 braucht Neustart:** `_sr_close_
min_gain_pct` lebt im Speicher; solange der alte Wert (3,0) läuft, re-armt JEDER neue
Trade erneut — die ini-Änderung greift erst nach `restart_server.bat`. Beim 0-Stellen
zusätzlich den persistierten Alt-Wert in `runtime_state.json` nullen (`sr_close_min_gain`)
UND `emergency_state.json` `loss` (Erb-Falle, sonst restauriert der Neustart den Alt-Wert).
**Gate verifiziert (2026-07-23, User-Nachfrage „darf nicht schließen bevor der
Wert erreicht wurde"):** `_check_sr_close` prüft `pnl < sr_close_min_gain → return`
**bevor** `_sr_close_hint`/`trader.close` überhaupt aufgerufen werden — isoliert
mit 6 synthetischen Szenarien getestet (unter/exakt-auf/über Schwelle, P(break)
hoch trotz Profit, Schwelle aus, Verlust-Trade) → alle korrekt. **Dabei echten
Bug gefunden & gefixt:** die 1%-Berechnung lief nur EINMALIG beim Ticket-Wechsel
(`if ticket != self._last_pos_ticket`); war `order_calc_margin` genau in diesem
einen Tick 0/nicht verfügbar (Broker-Race direkt nach Order-Fill), blieb
`sr_close_min_gain` für die GESAMTE Laufzeit der Position auf dem STALEN Wert der
vorherigen Position hängen (der Ticket-Wechsel-Zweig feuert nur 1×, keine zweite
Chance) — im ungünstigen Fall ein zu NIEDRIGER Alt-Wert, der den Trade dann exakt
zu früh schließt. **Fix:** neuer State `_sr_close_min_gain_armed_ticket`
(getrennt vom Notfall-Stop/TP-Restore-Zweig) — die %-Berechnung läuft jetzt JEDEN
`_pos_loop`-Tick erneut, bis sie für das aktuelle Ticket einmal erfolgreich war
(Margin>0), statt nur im Ticket-Wechsel-Moment. Wiederhergestellte Positionen
nach Neustart markieren sich sofort als „armiert" (kein Überschreiben des
persistierten Werts) — mit Test verifiziert. Log `🎯 S/R-AUTO-CLOSE` + Telegram + Toast
(`sr_close_count`), `closed_by=sr_close`. UI Position-Kachel: bernstein „Durchbruch
nur X% → Bot schließt" bzw. grün „Durchbruch X% wahrscheinlich → läuft weiter".
Abschalten `auto_sr_close=false` / `set_sr_autoclose(False)`; Schwelle via
`sr_close_pbreak`. Kein pauschaler S/R-Close (2× verworfen) — nur mit P(break)-Gate.
- **S/R-verankertes TRAILING = GEMESSEN, NICHT gebaut (`backtest_trailing_sr.py`,
2026-07-31, User-Idee „Trailing TP und SL auf die S/R-Linien anpassen"):** Die Idee
zerfällt in drei Teile — Initial-SL an S/R (schon verworfen, s. u.), fester TP am
Gegenlevel (schon 2× verworfen, `backtest_srclose.py`) und **Trailing-SL ans Level
nachziehen** = der einzig offene Teil (bisher wurden nur ATR-Abstände variiert).
Aufbau identisch zu `backtest_trailing.py` (echte Phasen-Mechanik, Breakeven 1,3),
Level = **M30-Pivots k=3, kausal** (Pivot erst 3 M30-Bars später bekannt), Deckel
**[0,5…2,0]×ATR** gegen den 11,4×ATR-Fall; 80k Bars, ~19,7k Signale je Hälfte.
**Ergebnis: schadet nicht, trägt aber zu wenig.** Basis (ATR-Trail 1,5) H1 0,020 /
H2 +0,064; **3 von 5 Varianten schlagen sie in BEIDEN Hälften** (Puffer 0,10:
+0,009/+0,004 · Puffer 0,50: +0,003/+0,014 · **Hybrid „engerer von beiden":
+0,006/+0,006**, WR 37→43 % bzw. 40→43 %). **Trotzdem nicht gebaut**, zwei Gründe:
(a) **Effektgröße 0,0060,014 R/Trade** = eine Größenordnung unter der B3-Hürde
(~0,1×ATR netto); (b) **nicht parameter-robust** — Puffer 0,10 besteht, **0,25 fällt
durch** (H1 0,023), 0,50 besteht wieder; ein Parameter, dessen Nachbar das Vorzeichen
kippt, ist Rauschen (dasselbe Kriterium, an dem ORB und die SMC-Sweeps starben —
beim Squeeze waren ALLE 8 Kombis positiv). ✅ Positiv-Befund: **der Worst-Case bleibt
in ALLEN Varianten 2,00×ATR** — der Deckel funktioniert, S/R-Verankerung fügt kein
Tail-Risiko hinzu (das war die Sorge aus `backtest_sl_method.py`). ⚠ **Die
KONTROLLVARIANTE „TP am Gegenlevel" reproduziert den alten Befund exakt** (WR 40→54 %,
ΣR H2 +1261→+740 = Gewinner-Kappen) — damit ist die Simulation gegen
`backtest_srclose.py` validiert und der TP-Teil endgültig erledigt.
**NACHTEST auf NUR Squeeze-Entries (`--squeeze`, 2026-07-31, Echtkosten): ERLEDIGT —
dort ist der S/R-Trail nicht nur zu klein, sondern klar SCHLECHTER.** Basis
H1 +0,102/H2 +0,108; **keine einzige Variante besteht** (S/R 0,10: +0,096/+0,093 ·
0,25: +0,083/+0,098 · 0,50: +0,052/+0,092 · Hybrid: +0,093/+0,122 = kippt ·
Hybrid weit: +0,091/+0,084). Anders als im Wave-Lauf ist das **kein Rauschen**: der
reine S/R-Trail wird **monoton schlechter, je breiter der Puffer** (in BEIDEN
Hälften) — sauberer monotoner Verlauf, nur gegen die Idee. **Mechanismus:** der
Squeeze lebt von wenigen Läufern (43 % Treffer, Ertrag im Tail); ein an das
nächstgelegene Level gebundener Stop holt genau dort raus, wo der Lauf beginnt. Bei
Wave-Signalen fällt das kaum auf (selten große Runner) — daher das umgekehrte
Vorzeichen zwischen beiden Läufen. ⚠ **Stärkster Gewinner-Kappen-Beleg des
Projekts:** die Kontrollvariante (fester TP am Gegenlevel) dreht den Squeeze von
**+0,102/+0,108 auf 0,066/0,004** = Edge vollständig zerstört. ⚠ **Abgrenzung
gegen den laufenden S/R-Auto-Close:** der ist **P(break)-gegated** (schließt nur,
wenn das Level wahrscheinlich HÄLT) und war auf Squeeze-Trades separat gemessen ein
**Wash** (`backtest_squeeze_srclose.py`, +0,190→+0,224 / +0,394→+0,377). **Das Gate
ist der Unterschied zwischen „Wash" und „Edge zerstört"** — ein ungegateter Level-TP
ist etwas grundlegend anderes. Damit ist die User-Idee in allen DREI Teilen
durchgemessen; der Live-Exit-Stack bleibt unverändert.
- **⚠⚠ SL-BASIS M5 vs. M15 auf der NEUEN Level-Entry-Population (gemessen
`backtest_sl_basis.py`, 2026-08-05, User-Frage „wäre M5-ATR vertretbar?") —
DIFFERENZIERT, und dabei eine Mess-Betriebs-Lücke gefunden.**
⚠ **Zuerst der strukturelle Fund:** `exit_model.simulate()` nimmt **EINEN** `atr`
für SL, TP, Trail und Breakeven — und **alle Backtests übergeben ATR_M5**. Live
kommt der SL dagegen aus **ATR_M15** (`mt5_utils.atr_value` mit `SL_TF`), im
Median **1,80×** so groß. Praktisch jede Projekt-Messung modelliert damit einen
SL von 2,0×ATR_M5, während live **~4,0×ATR_M5** gilt. Einzige bisherige Ausnahme:
`backtest_sl_width.py` (04.08.), das genau das explizit verglichen hat.
**Ergebnis auf Level-Entries (80k, 2 Halbjahre, nur die SL-Basis variiert):**
| Population | Variante | H1 ØR / Worst | H2 ØR / Worst |
|---|---|---|---|
| **SQUEEZE** | LIVE (≈4,1×ATR_M5) | +0,455 / **5,87** | +0,614 / **8,83** |
| | 2,5×ATR_M5 | +0,459 / 2,82 | +0,620 / 2,94 |
| | 2,0×ATR_M5 | +0,452 / 2,32 | +0,610 / 2,44 |
| **WELLE** | LIVE (≈3,9×ATR_M5) | **+0,371** / 4,97 | **+0,356** / 6,00 |
| | 2,5×ATR_M5 | +0,362 / 2,79 | +0,343 / 2,72 |
| | 3,0×ATR_M5 | +0,358 / 3,29 | +0,352 / 3,22 |
✅ **Ertragsseitig ist es ein WASH auf der Squeeze-Population** — alle Varianten
liegen innerhalb eines Standardfehlers (≈0,035 bei n≈800), und die einzige Zelle,
die den Live-Stand in ØR beidhälftig schlägt (2,5), hat **schlechtere Nachbarn**
→ nach der Nachbar-Regel Rauschen. **Auf der Wellen-Population ist der weite
Live-SL dagegen in BEIDEN Hälften der beste** — jede Verengung kostet dort.
⚠⚠ **Der EINZIGE robuste Unterschied ist das TAIL-RISIKO — und der ist groß und
monoton:** Worst-Case **8,83×ATR** (live, Squeeze H2) gegen **2,94** bei
2,5×ATR_M5, also **Faktor 3**. Bei 3,5 Lots und ATR_M5 0,33 sind das ~**890 €**
gegen ~300 € im einzelnen schlechtesten Trade.
✅ **Antwort: „vertretbar" ja — aber es ist keine Ertrags-, sondern eine
Risiko-Entscheidung.** Für den Squeeze kostet die Verengung gemessen nichts und
drittelt den Tail; für Wellen-/Signal-Trades kostet sie Ertrag in beiden Hälften.
Eine saubere Umsetzung wäre **quellen-abhängig** (`_calc_sl_tp` nutzt M5-ATR nur
bei `source="auto_squeeze"`) — strukturell begründet (Breakout- gegen
Pullback-Einstieg haben verschiedene Nachlauf-Dynamik), nicht data-mined.
**Nicht gebaut** — die Entscheidung über Tail-Risiko gehört dem User.
⚠ Das reproduziert NICHT `backtest_sl_tf_mismatch.py` (23.07., dort war M5 in H2
schlechter): dort wurde am Bar-Close eingestiegen, hier am Level. Der
Einstiegspreis hat den Befund gedreht.
- **S/R-verankerter SL = KEIN Edge (gemessen, `backtest_sl_method.py`):**
Pivot+Band vs. „S/R ∓ ATR/0,10-Puffer" vs. fest 2,0×ATR — **gedeckelt alle
identisch** (Ø-R 0,1960,201, PF 1,381,39, Worst 2,2×ATR): der **Band-Cap
[1,82,2]×ATR dominiert**, die Verankerung ist Rauschen. Nur **ungedeckelt**
sieht S/R besser aus (Ø-R 0,249) — aber Worst-Case **11,4×ATR** (Kontokiller
bei 90 %-Sizing) → verworfen. Aktuelle SL-Logik bleibt.
- **SL-ATR-Timeframe bei Squeeze-Trades (M5-Signal, aber SL_TF fix M15) = GEPRÜFT,
KEINE Änderung (gemessen `backtest_sl_tf_mismatch.py`, 2026-07-23, Auslöser
89,36-€-Nacht-Trade):** `_calc_sl_tp` sizt den Initial-SL IMMER aus M15-ATR,
unabhängig vom Signal-TF — bei Squeeze (M5-Signal) kann das stark divergieren
(real: M5-ATR fiel 0,29→0,10 — genau die Kompression, die den Squeeze auslöste —
während M15-ATR noch bei ~0,36 lag → SL 0,787 statt ~0,22 bei M5-Sizing). Der
validierte Squeeze-Backtest (`backtest_breakout_squeeze.py`) nutzt durchgehend
M5-ATR für den SL — Live weicht hier vom Backtest ab. **2-Stichproben-Test der
Umstellung auf M5-ATR-SL bei Squeeze-Trades: GEMISCHT** — H1 leicht besser
(ØR +0,167 vs +0,064, ΣR +35 vs +13), H2 (größere, aktuellere Stichprobe) klar
SCHLECHTER (WR 51 % vs 59 %, ΣR +281 vs +323) → fällt nach der beidhälftig-
robust-Regel durch. Der breitere M15-SL kassiert gelegentlich einen vollen
Verlust wie diese Nacht, verhindert aber mehr vorzeitige Ausstoppungen bei
normalen Rücksetzern — netto vorteilhaft. **Keine Änderung.**
- **Inter-Market (Brent-Bestätigung / DXY-Gegenwind) = VERWORFEN (gemessen
`backtest_intermarket.py`, 2026-07-19, letzte ungetestete Indikator-Klasse):**
1366 Trades, seq. Sim, Live-Exit, Echtkosten, 2 Halbjahre; Buckets nach Zustand
am Entry (Symbole: `SpotBrent`, `USDX`). **Beide Hypothesen kippen zwischen den
Hälften:** (A) Brent-Trend dagegen H1 0,002 vs H2 0,187 (H1 sogar BESSER als
„dafür"!), Brent-Mom-1h dagegen H1 **+0,311**/H2 0,233 = Flip; (B) DXY-Gegenwind
H1 0,13…−0,19 (schlechter, wie vermutet) aber H2 **+0,13…+0,17 = BESSER als
Rückenwind** — komplett invertiert, klassische Regime-Abhängigkeit (wie H1-Stärke-
Filter/Momentum). Einzig Brent-Mom-3h ist richtungs-konsistent, aber winzig
(Δ~0,060,14) und die Schwester-Varianten kippen → nach der vorab festgelegten
Verdict-Regel (beide Hälften UND Varianten-robust) **kein Einbau**. Damit ist auch
die letzte orthogonale Indikator-Klasse durch: **am Signal ist nichts mehr zu
holen — Hebel bleiben Sizing/Exit/Verhalten** (nicht gegen das Signal handeln:
live gemessen 39 % WR / 166 € vs. mit 55 % / +65 €).
`backtest_confluence.py` — MACD/ADX-Richtung praktisch neutral, ADX-Stärke leicht
negativ, ein MACD+ADX-Gate senkt den Edge (78 % Trades). RSI *bestätigend* ist
kontraproduktiv (RSI *gegen* Richtung = doppelter Edge = der Pullback-Effekt, den
der „⭐ tiefer Pullback"-Bonus schon nutzt). Standard-Oszillatoren also nicht
draufsatteln. Mehrquellen-Redundanz läuft bereits über die Gesamtempfehlung.
- **TradersUnion (TU) aus der Empfehlung ENTFERNT (User-Vorgabe 2026-07-06):** TU ist
Standard-Indikator-Konsens (MA/Oszillator-Ratings einer Website) — **dieselbe Klasse
wie MACD/ADX/RSI = kein Edge**, plus **Latenz** (5m „Strong Buy" während 4h/1d „Strong
Sell") und **nicht backtestbar** (Live-Scrape, keine History). Floss vorher als
Konfidenz-Faktor (`_tu_check` ±6/8 je 5m/15m) UND als Verdict-Stimme ein — **beides
neutralisiert** (`wave_rec._build` zieht `_tu_check` nicht mehr; `_verdict` ohne
TU-Vote). ⚠ Nebenbefund: der Konfidenz-Pfad hatte **keinen** `_STALE_S`-Check (nur
`trade_signal` hatte ihn) — mit der Entfernung erledigt. TU bleibt **reine Anzeige**
(Snapshot `tu`); `_tu_check`/`_TU_*` als Code erhalten (reaktivierbar). Fetch war
auf 5 min gedrosselt (`_TU_S=300`); **Loop seit 2026-07-19 ganz stillgelegt**
(`_tu_loop` nicht mehr in `start()` — TU ist aus Empfehlung/Verdict/Copilot raus
UND die Kachel ausgeblendet, der Scrape lief für niemanden; reaktivieren = Loop
wieder in die Liste). Elliott loggt nur noch **bei Änderung** (vorher
~6k identische Zeilen/Logrotation).
- **Order ohne Empfehlung = bestätigbarer HINWEIS, kein Block:** Bei WARTEN/
Gegen-Signal zeigt das Frontend einen **Bestätigungs-Dialog** („… trotzdem
eröffnen?") — bestätigen = Trade läuft, Abbrechen = kein Trade. **Kein** hartes
Blockieren (überschreibbar). `engine._open` blockt nicht (loggt nur).
⚠ **Log-Quellen-Attribution (Fix 2026-07-29):** `_open(fn, want, source)` bekommt
jetzt eine `source` (Default `"manuell"`); der Auto-Squeeze ruft `open_long/short
(source="auto_squeeze")`. Vorher schrieb `_open` bei JEDEM Auftrag gegen die
Empfehlung „— vom User bestätigt" — auch bei autonomen Squeeze-Entries (der User
fragte, ob ein Trade auto/manuell war; die Zeile log fälschlich „User"). Jetzt:
autonome Orders → „autonom vom Auto-Squeeze eröffnet", manuelle → wie bisher. Die
`🚀 AUTO-SQUEEZE-ENTRY`-Zeile bleibt die eindeutige Quelle der Wahrheit.
- **Entry-Checkliste im Order-Dialog (`engine._entry_checklist`, User-Vorgabe
2026-07-30 „blende einen Hinweis ein wenn die Checkliste gegen meine Eröffnung ist",
v=120):** Vor JEDER manuellen Eröffnung prüft der Server 7 Punkte mit LIVE-Daten und
zeigt die verletzten im Bestätigungs-Dialog. **Blockt nicht**, macht aber sichtbar,
was dagegen spricht — und zwar **auch dann, wenn ein Signal vorliegt** (der alte
Dialog kam nur bei WARTEN/Gegen-Signal). Punkte: (1) **Signal-Deckung** (die
gemessene Kern-Leckage), (2) **HTF-Trend M30+H1** („nur mit dem HTF-Trend, niemals
dagegen"), (3) **Entry-Raum** zum Gegenlevel (<0,6×ATR = gemessen negativ),
(4) **News-Konflikt** (|Sentiment|≥0,5 gegen die Richtung), (5) **Kosten**
(Spread/ATR, ab 0,32 = Kostenfalle), (6) **Mental-Check: Verlust-Serie heute**
(„nach 2 Losses Session beenden" — zeigt Anzahl, Tages-P&L und %-Anteil am Konto),
(7) **Nacht-Stunde** 07 Uhr. `verdict`: `stop` (≥2 „fail") · `warn` · `ok`.
Snapshot `checklist {long, short}` — nutzt nur bereits berechnete Snapshot-Teile;
die DB-Abfrage für die Verlust-Serie ist ~120 s gecacht (`_loss_streak_today`).
⚠ Die Gewichtung ist **NICHT backtest-optimiert**, sondern aus den im Projekt
gemessenen Leckagen + der User-Checkliste abgeleitet (Entscheidungshilfe, kein
validiertes Signal). Real beim Einbau (30.07. abends): beide Richtungen `stop` —
„keine Empfehlung" · „HTF unklar/dagegen" · „11 Verluste heute (261 € = 80 % des
Kontos)".
- **Liquiditäts-Sweeps (SMC: Equal Highs/Lows, Swing Liquidity) = VERWORFEN
(gemessen `backtest_liquidity_sweep.py`, 2026-07-30, User-Vorgabe mit Chart-
Beispielen + WTI-Praxisregeln):** Getestet, weil der **Sweep** (Fehlausbruch: Level
kurz durchstochen, Close zurück in der Zone) eine ANDERE Mechanik ist als der schon
verworfene Nackenlinien-Bruch (`backtest_doubletop.py`). Achsen: 3 Muster × Level-TF
(M5/M30/H1, die „Liquidität auf höherem TF"-Regel) × 2 Exits × 2 Halbjahre, plus
Session-Filter (NY 1416 / Asia 24 Uhr Berlin). **Kernbefunde:** (a) Der
**SMC-eigene Exit** („Stop über dem Sweep-Docht, Ziel = Gegen-Liquidität") ist in
**ALLEN 9 Kombinationen negativ** — die Lehre steht und fällt mit ihm, er trägt
nicht; mit dem generischen Live-Trailing sieht dasselbe Setup besser aus (derselbe
Exit-Artefakt wie bei `backtest_patterns.py`). (b) Equal Highs/Lows fallen fast
überall durch (Vorzeichen kippt zwischen den Hälften). (c) Einziger Treffer:
„Swing-Liquidity, M30-Level, Live-Exit" (+0,241/+0,358) — aber die **Nachbar-TFs
kippen** (M5 0,152/+0,247 · H1 0,111/+0,692) = **nicht parameter-robust**, exakt
das ORB-Muster von 2026-07-17. (d) Session-Filter: zu wenige Trades (n=331) für
eine Aussage. **Kein Einbau in die Gesamtempfehlung. 16. verworfener Signal-
Eingriff.** ⚠ Die DXY-Korrelations-Regel wurde bewusst NICHT erneut getestet — die
Inter-Market-Klasse ist 2026-07-19 gemessen durchgefallen.
- **Fibonacci-Retracements = VERWORFEN (gemessen `backtest_fibonacci.py`, 2026-08-04,
User-Frage zu einem USOIL-Kommentar „ein Fibonacci-Retracement sollte den Kurs auf
79 $ drücken"):** Fib-Level waren im Projekt bis dahin **nie gemessen** — weder als
S/R noch als Einstiegszone. Aufbau wie beim P(break)-Training: Schwünge aus
**M15**-Pivots (die genannte Zeitebene), Berührung ≤0,15×ATR, Ausgang ±0,5×ATR in
12 M5-Bars, 80k Bars, 2 Halbjahre — mit ZWEI Kontrollgruppen.
| Haltequote | H1 | H2 |
|---|---|---|
| **Fibonacci** | 54,3 % | 54,5 % |
| (a) Zufalls-Level gleicher Distanz | **55,7 %** | 53,8 % |
| (b) M30-Pivots (Live-Quelle) | **56,6 %** | **55,4 %** |
**Handelbarkeit (Bounce am Level, kanonischer Exit, Echtkosten):** H1 ØR 0,168 /
PF 0,71 · H2 ØR 0,082 / PF 0,85 — beidhälftig negativ.
⚠⚠ **Der aufschlussreichste Wert ist die Zufalls-Kontrolle: ~55 % Haltequote.**
Ein BELIEBIGES Level hält in diesem Markt in gut der Hälfte der Fälle — das ist die
Basisrate kurzfristiger Mean-Reversion, keine Eigenschaft von Fibonacci. Die Frage
war nie „hält das Level?", sondern „hält es BESSER als eine willkürliche Linie?" —
und Fib tut das nicht. **Damit ist auch das Selbsterfüllungs-Argument entkräftet**
(„so viele schauen darauf, also wirkt es"): dann müsste Fib über der Zufallslinie
liegen; es liegt darunter bzw. gleichauf.
✅ **Bestätigt umgekehrt die Struktur-These:** M30-Pivots — Stellen, an denen real
gehandelt wurde — halten in BEIDEN Hälften besser als beide gerechneten Linien.
Dieselbe Trennlinie wie beim Regressionskanal (`backtest_structure_tf.py`, 24/24
negativ) und beim P(break)-Modell: **„Linie im Chart" ist nicht „Struktur im Markt".**
**21. verworfener Signal-Eingriff.** ⚠ Grenzen: eine Schwung-Definition (M15-Pivots
k=3, ≥2×ATR); die Pivot-Kontrolle ist dünner (n=173/224). Der Vergleich ist aber
design-intern, die relative Aussage damit robuster als die Absolutwerte.
- **Meta-Labeling (López de Prado) auf dem Wellensignal = VERWORFEN (gemessen
`backtest_metalabel.py`, 2026-08-05, aus der Web-Recherche „Best Practices"):**
Kein 23. Filter, sondern ein **Zweitmodell**, das für ein bereits erzeugtes Signal
P(Gewinn) schätzt — dieselbe Machinerie wie das erfolgreiche P(break)-Modell
(12 kausale Merkmale: Konfidenz · Überdehnung · HTF/H1-Übereinstimmung · Kosten ·
Vola-Regime · mom3/mom6 · Efficiency Ratio · Nacht · Entry-Raum · Winkel;
logistische Regression, Fit auf einer Hälfte, Test auf der anderen, **in BEIDEN
Richtungen**). Population = der volle Live-Gate-Stack (929 bzw. 1316 Signale).
**Fällt klar durch:**
| | Fit H1 → Test H2 | Fit H2 → Test H1 |
|---|---|---|
| **AUC oos** | **0,508** | **0,479** |
| Kalibrierung | 49,3 % vorhergesagt vs. **38,6 %** real | 37,2 vs. 38,0 % |
| Top-Bucket (p≥0,55) | **74,1 % vorhergesagt, 39,1 % real** | (existiert nicht) |
Münzwurf in beiden Richtungen (P(break) erreicht 0,65), und **8 von 12
Merkmalsgewichten kippen das Vorzeichen** zwischen den Fits — das Modell lernt
nichts Stabiles. Die im H1→H2-Lauf scheinbar „besseren" Schwellen (p≥0,45…0,55)
sind reine **Handelsvermeidung** (ΣR steigt nur, weil n fällt; ØR bleibt negativ)
und reproduzieren in der Gegenrichtung nicht. **23. verworfener Signal-Eingriff.**
⚠ Deckt sich exakt mit dem Caveat der Quelle (Hudson & Thames): *„if the algorithm
is bad then meta-labeling would likely only reduce the downside."* Das Wellensignal
ist in H1 gemessen negativ — ein Zweitmodell rettet ein schwaches Primärsignal nicht.
✅ **Was das positiv zeigt:** die Trennung „am Signal ist nichts zu holen, der Hebel
liegt in **Ausführung** und Verhalten" hält auch gegen die aktuelle Lehrmeinung.
Am selben Tag hat der Ausführungs-Hebel (Stop-Order statt Market) **+0,61 ØR**
gebracht — mehr als alle 23 Signal-Ideen zusammen.
- **„Erst messen, dann umbauen":** Strategie-Änderungen per Backtest belegen, nicht
raten. Gegen-Trend verliert; nicht bei WARTEN handeln. **Stichproben ~28 Tage** —
ein Trend-Regime; Befunde später auf mehr/andere History gegenprüfen.
- **Wave-Auto-Trader (Dry-Run) KOMPLETT ENTFERNT (2026-07-23, Code-Review-Fund):** war
seit Langem Verlierer (WR 29 % / PF 0,64) vs. manuelle Trades (WR 5676 %); UI-Toggle
bereits vorher entfernt (Dashboard-Button steuert seitdem Auto-Squeeze), lief aber
weiter dormant bei jedem Tick mit (toter Ballast). `core/auto_trader.py` gelöscht
(Sicherung: `.removed_backup/`), `engine.self.auto` + alle 4 Aufrufstellen
(`_price_loop`, `_run_analysis`, `toggle_auto`, Snapshot-Keys `auto`/`min_conf`)
sowie die tote `auto`-Referenz in `TradingAgent.__init__` entfernt, `/api/auto`
gelöscht. `min_conf` (Frontend-Warnschwelle „Order ohne Signal") lebt jetzt als
eigene Konstante `_MIN_CONF_DISPLAY=70` in `engine.py`. Die `intended_trades`-DB-
Tabelle + `history.intended_*`-Methoden bleiben (historische Daten, kein Schreiber
mehr aktiv). Manuelles Handeln bleibt der validierte Edge auf dem Wave-Signal.
- **RE-TEST 2026-07-30 (User-Wunsch „Trades bei Empfehlung automatisch eröffnen") =
ERNEUT VERWORFEN, gemessen `backtest_auto_signal.py`:** Auto-Entry auf dem Wave-
Signal mit den HEUTIGEN Gates, echte `_build`-Logik (kein Nachbau), M30-/H1-Trend
aus M5 aggregiert und in `_build` gefüttert, sequentielle 1-Positions-Sim, Live-Exit
(SL 2,0/Trail 1,5/BE 1,3/Time-Stop 120 min), Echtkosten (Bar-Spread/ATR), 80k M5-Bars
in 2 Halbjahren. **ALLE 8 Varianten fallen durch** (Konf-Schwelle 55/65/70/75 ×
Nacht-Sperre an/aus): **H1 durchgehend NEGATIV** (ØR 0,076…−0,182, PF 0,780,90),
**H2 durchgehend positiv** (+0,112…+0,167) → klassisches Regime-Kippen, n=7181167
je Hälfte (keine Stichproben-Frage). ⚠ **Kontroll-Beleg, dass es am SIGNAL liegt und
nicht an der Sim:** der Squeeze läuft durch DIESELBE Exit-/Kosten-Maschinerie und ist
in H1 **+0,187/PF 1,29** — die Simulation funktioniert also, nur das Wave-Signal trägt
in H1 nicht. Deckt sich exakt mit dem Live-Dry-Run (WR 29 %/PF 0,64). Nebenbefunde:
(a) höhere Konfidenz hilft NICHT monoton (75 schlechter als 65) — bestätigt
`analyze_verdict_calibration.py` (conf_pct ist NICHT kalibriert, in H2 invertiert),
ein „nur bei hoher Konfidenz"-Auto-Entry ist damit auch erledigt; (b) die Nacht-Sperre
verbessert konsistent (H1 0,172→−0,123), reicht aber nicht; (c) ⚠ **Methodik-Lehre:**
ein Vorlauf mit nur 6k Bars zeigte „Konf≥65+Nacht TRÄGT" — der 80k-Lauf drehte das
komplett (n war 6099/Hälfte = Rauschen). **Kleine Stichprobe hätte hier zum Fehl-
Einbau geführt.** Warum manuelle „MIT Signal"-Trades trotzdem profitabel sind
(+118 €/WR 56 %, `analyze_entries`-Auswertung 2026-07-30): **menschliche AUSWAHL**
unter den Signalen (Kontext/Tageszeit/Marktlage), nicht „alle nehmen" — die
Grundgesamtheit ALLER Signale ist der Verlierer. **14. verworfener Signal-Eingriff.**
- **Auto-Signal-Entry = TROTZDEM GEBAUT auf ausdrücklichen User-Wunsch (2026-07-30,
„probieren wir es trotzdem mal"), `[trading] auto_signal`, Default FALSE.** ⚠ **Der
Backtest direkt darüber sagt NEIN** (H1 durchgehend negativ) — der User hat das nach
vollständiger Information (inkl. Live-Dry-Run-Historie WR 29 %/PF 0,64) bewusst
entschieden. Gebaut mit den **gemessen BESTEN** Parametern, nicht mit Default-Raten:
`auto_signal_min_conf=75` (mit Nacht-Sperre H1 0,053 = mildester Verlust, H2 +0,141
= bestes Netto; ⚠ NICHT weil „mehr Konfidenz besser" — conf_pct ist unkalibriert, 75
ist empirisch) und `auto_signal_skip_night=true` (verbessert JEDE Variante).
**Mechanik** (`engine._check_auto_signal` im `_pos_loop`, 1 s, NACH den Close-Checks):
eröffnet nur **FLAT**, nur bei `wave.signal()` LONG/SHORT mit `conf_pct ≥ min_conf`,
**1× je Signal-Episode** (Dedup `_signal_last_sig`, re-armt bei WARTEN/Flip — nicht
über den Preis wie beim Squeeze, weil das Signal keine Level-Marke hat); erbt die
Squeeze-Guards: Circuit-Breaker, Nacht-Sperre, S/R-Close-Cooldown, allgemeine
Wiedereinstiegs-Sperre. **Kein Nachkauf** bei gleichgerichteter Position, **kein
Drehen** einer Gegen-Position (unbelegt, wie Squeeze-Reverse). Log `🎯 AUTO-SIGNAL-
ENTRY` + Telegram, Setup-Tag `AUTOSIG_<dir>` (macht die Trades in der DB separierbar
→ B4-Monitor). Snapshot `auto_signal`/`auto_signal_min_conf`/`signal_entry_count`,
Toggle `POST /api/autosignal` = Dashboard-Button **🎯 SIG** (bernstein statt blau,
weil gemessen nicht tragfähig; zeigt die Schwelle an), neustart-fest via
`runtime_state.json`.
⚠⚠ **ABGESCHALTET am 2026-08-01 (`auto_signal=false`, via Toggle → `runtime_state.json`).**
Zwei Gründe, KEINER davon die Live-Bilanz (n=4 = +21,79 € sagt nichts): (1) gemessen in
H1 durchgehend negativ, gebaut gegen die Messung; (2) **beide Auto-Pfade konkurrieren um
den EINEN Positions-Slot** — ein AUTOSIG-Trade blockiert einen Squeeze-Einstieg und
umgekehrt. Solange beide laufen, ist die B5-Abbruchprüfung des Auto-Squeeze (s. u.)
nicht sauber messbar. ⚠ Er lief bis dahin **unbemerkt**, obwohl die ini `false` sagte:
ein Dashboard-Toggle hatte `true` in `runtime_state.json` persistiert, und das
überschreibt die ini beim Start. Der Config-Wächter prüft das jetzt mit.
Reaktivieren = Button 🎯 SIG; dann gilt wieder: ≥20 AUTOSIG-Trades, Live-ØR/PF gegen
die Erwartung halten (H2-Regime +0,14 vs H1-Regime 0,05).
⚠⚠ **NEU GEMESSEN 2026-08-04 nach dem Reversal-Abschalten
(`backtest_auto_signal_norev.py`, 80k M5, 2 Halbjahre, Nacht-Sperre an, keine
15-Min-Regel):** Frage des Users — trägt der Auto-Signal-Entry mit den heutigen
Änderungen? **Antwort: nein, aber deutlich näher dran.**
| conf ≥ 75 (LIVE-Schwelle) | ØR | PF | n |
|---|---|---|---|
| H1 | **0,047** | 0,94 | 678 |
| H2 | **+0,120** | 1,17 | 710 |
H1 bleibt negativ → **2-Stichproben-Regel weiterhin NICHT erfüllt**, und kein
Konfidenz-Schwellwert rettet das (55: 0,103 · 65: 0,111 · 75: 0,047).
✅ **Das Reversal-Abschalten wirkt aber konsistent** — Δ ØR durch die Abschaltung:
conf 55 **+0,015/+0,099** · conf 65 +0,001/+0,047 · **conf 75 +0,099/+0,087**.
An der Live-Schwelle verbessern sich BEIDE Hälften um ~+0,09; H1 geht von 0,145
auf 0,047, PF von 0,82 auf 0,94. Erste Bestätigung der Abschaltung auf dem
GESAMTEN Signal (bisher nur der isolierte Trigger).
⚠⚠ **Dabei gefunden — die Messung vom 30.07. war strukturell unvollständig:**
`backtest_auto_signal.py` ruft `_build` **ohne `angle=`** auf → Default 90,0 →
`ad = 0`. Damit konnte dort **weder der Reversal-Zweig feuern noch der
Winkel-Konfidenzterm (±15 Punkte) greifen** — obwohl die Docstring „echte
`_build`-Logik inkl. Reversal-Zweig" behauptet. Wirkung: der Winkelterm allein hebt
H1 bei conf 75 von 0,123 auf 0,047. **Deployment-Drift Fall 5** (s. Tabelle).
Die Originaldatei bleibt unverändert (ihre Zahlen sind dokumentiert); die live-treue
Variante steht als `backtest_auto_signal_norev.py` daneben und sollte künftig die
Referenz sein.
- **⚠⚠ AUTO-SIGNAL NEU GEMESSEN 2026-08-05 (`backtest_auto_signal_v3.py`) — ZUM
ERSTEN MAL BESTEHT ER, ABER NUR MIT EINEM ANDEREN EINSTIEGSPREIS.**
Anlass: User-Wunsch nach einer ausführlichen Neumessung „mit den neuen Änderungen".
Geändert seit dem 04.08. waren Nacht-Fenster 07→36 und — der eigentliche Punkt —
der **Ausführungs-Befund vom selben Tag**: `_confirm_breakout` hat ein LEVEL
(`_pend["level"]` = Anker ± k×ATR), an dem eine ruhende Stop-Order liegen könnte.
ALLE bisherigen SIG-Messungen sind am Bar-Close eingestiegen.
Voller Live-Gate-Stack (echte `_build`/`_confirm_breakout`/`_room_gate`),
sequentiell EIN Slot, Live-Dedup (1× je Episode), kanonischer Exit, Echtkosten,
80k M5, 2 Halbjahre:
| Konf | **A) Market (Bar-Close)** H1 / H2 | **B) am Bestätigungs-Level** H1 / H2 |
|---|---|---|
| ≥55 | 0,098 / 0,093 | **+0,349 / +0,331** |
| ≥65 | 0,092 / 0,112 | **+0,357 / +0,317** |
| **≥75 (live)** | 0,050 / 0,189 | **+0,380 / +0,272** |
| ≥85 | 0,118 / 0,234 | **+0,351 / +0,279** |
**A ist in ALLEN acht Feldern negativ** (bestätigt die drei Vormessungen).
**B besteht in ALLEN acht** — Trefferquote 39,6 → 53,2 %, PF 0,83 → 2,20, und
**alle vier Nachbar-Schwellen halten mit** (Parameter-Robustheit, an der ORB und
die SMC-Sweeps gescheitert sind).
⚠ **HÄRTETEST Fill-Slippage** (Konf≥65) — eine Stop-Order füllt zum Markt, der
Idealpreis ist die Obergrenze: +0,05 → **+0,315/+0,249** · +0,10 → +0,273/+0,197 ·
**+0,20 → +0,148/+0,090** (noch beidseitig positiv) · **+0,31 → +0,026/0,031**.
✅ **Die letzte Zeile ist die interne Kontrolle:** bei Slippage = dem gemessenen
Median-Abstand (0,311×ATR) konvergiert B gegen A — das Modell prüft sich selbst.
Der Kipp-Punkt liegt bei ~0,250,30×ATR Fill-Slippage.
**Abstand Bar-Close ↔ Bestätigungs-Level: Median +0,311×ATR** (Ø +0,422, Max
+5,29) — praktisch derselbe Wert wie beim Squeeze live (+0,275). Gleicher
Mechanismus, gleiche Größenordnung.
⚠ **Nacht-Fenster ist hier nahezu irrelevant:** 36 (+0,380/+0,272) · 07
(+0,371/+0,226) · keine Sperre (+0,359/**+0,302**) — alle drei bestehen, ohne
Sperre ist H2 sogar am besten. Deckt sich mit `backtest_cost_gate.py`.
⚠⚠ **GRENZEN — bewusst NICHTS umgeschaltet:**
(1) **Wo LIVE zwischen A und B liegt, ist für SIG NICHT verifizierbar** — beim
Squeeze gab es 34 echte Entries zum Nachrechnen, hier sind es 4. Die Empfehlung
macht das zwar gegenstandslos (eine ruhende Order IST B per Konstruktion), aber
behauptet ist es damit nicht.
(2) **Variante A ist STRENGER als live:** für die Vergleichbarkeit steigen beide
Varianten nur im Bar der frischen Bestätigung ein (`frisch`); live darf
`_check_auto_signal` auch später feuern. A ist ohnehin negativ, die Folgerung
bleibt — aber A ist nicht exakt das Live-Verhalten.
(3) **Operativ deutlich schwieriger als beim Squeeze:** die Squeeze-Box steht über
viele Bars still, `_pend` dagegen **verankert sich neu**, sobald der Kurs k×ATR
dagegen läuft, nach 12 Bars Timeout ODER bei Richtungswechsel. Eine ruhende Order
müsste ständig nachgezogen werden (Broker-Last, mehr Ablehnungen am
Mindestabstand) — und **wenn sie nicht platzierbar ist, landet man wieder bei A**,
also im gemessen negativen Fall. Wie oft das passiert, ist ungemessen.
(4) `_pend` wird aktuell **nicht im Snapshot veröffentlicht** — das wäre die erste
Bauarbeit.
✅✅ **PENDING-PFAD FÜR SIG GEBAUT & LIVE (2026-08-05, User-Vorgabe),
`[trading] signal_pending_entry=true`.** SIG läuft damit auf Variante B statt A.
⚠⚠ **Der heikelste Punkt war NICHT die Order, sondern die Kollision der beiden
Pfade.** `trader.pending_orders()` liefert ALLE magic-gleichen Orders — zwei
getrennte Manager (Squeeze/Signal) hätten sich **gegenseitig die Orders
storniert**, sporadisch und im Log kaum erkennbar. Deshalb **EIN gemeinsamer
Ziel-Zustand**: `engine._pending_ziel()` liefert `{order_type: preis}` + Quelle,
`_manage_squeeze_pending` gleicht nur noch ab. **VORRANG: Squeeze > Signal** — der
Squeeze ist unabhängig validiert und selten (~23×/Woche), das Signal feuert
dauernd; bei EINEM Positions-Slot ist die Reihenfolge damit eindeutig.
⚠ **Zwei additive Ergänzungen in `wave_rec._confirm_breakout` waren nötig:**
(1) Das Bestätigungs-Level wird jetzt **auch nach der Bestätigung** veröffentlicht
(`snap["breakout"]["level"]`, `confirmed=True`) — die Nachjagd-Bremse im
Market-Fallback braucht es genau dann. `pending` bleibt False, das Frontend rendert
nur bei `pending` und ist unberührt. (2) **`conf`** = die Konfidenz VOR dem
Stummschalten: der Rückgabewert setzt `conf_pct` auf 0 (das Signal gilt ja noch
nicht), die ruhende Order müsste sonst **blind** platziert werden. Damit wird die
Mindest-Konfidenz jeden Tick geprüft und die Order storniert, sobald sie fällt —
strenger als der Live-Check von vorher (der prüfte nur im Moment des Entries).
⚠ **Nachjagd-Bremse auch für SIG** (`squeeze_max_chase_atr`, 0,20): greift die
Pending nicht, wird nur noch bis 0,20×ATR über dem Bestätigungs-Level nachgekauft —
der Härtetest zeigt dort +0,148/+0,090, ab ~0,250,30 kippt es.
⚠ **Der wandernde Anker ist die operative Hauptlast:** `_pend` verankert sich bei
jeder k×ATR-Gegenbewegung, nach Timeout und bei Richtungswechsel neu — die Order
wird dann nachgezogen (Toleranz 0,02×ATR gegen Order-Churn). **Mit 12 Szenarien
getestet** (Vorrang · reiner Signal-Fall · SHORT · Konfidenz zu niedrig · Wechsel
Signal→Squeeze mit Storno · wanderndes Level · Toleranz · Gate · Fill · Feature aus
· Bestätigung eingetreten · Schonfrist). Live verifiziert am Snapshot:
`signal_pending=true`, `breakout` liefert `level`/`conf`/`confirmed`;
`pending_levels` leer, weil eine Position offen war = korrektes Verhalten.
Snapshot-Felder umbenannt: `squeeze_pending_levels` → **`pending_levels`** +
**`pending_quelle`**. Zurück: `signal_pending_entry=false`.
⚠ **B4-Pflicht:** dieselbe Erfolgskontrolle wie beim Squeeze — der Abstand
Einstieg↔Level muss gegen 0 gehen. AUTOSIG-Trades sind über `setup=AUTOSIG_*`
separierbar.
⚠⚠ **ZWEITER Methodik-Fehler, am selben Tag gefunden und die Messung WIEDERHOLT:
`set_entry_room(0.6)` fehlte.** `WaveRecommender.__init__` setzt
`_entry_room_atr = 0.0`, und `_room_gate` gibt dann `rec` **unverändert** zurück —
der Aufruf sieht im Code aus wie ein Gate, ist aber ein No-op. Betroffen waren
DREI am 05.08. neu gebaute Skripte (`backtest_auto_signal_v3`, `_metalabel`,
`_dist_gated`); die Referenz `backtest_breakout_gated.py` (04.08.) macht es richtig.
Aufgefallen ist es erst, weil `entry_room` in der Verteilung mit **0 %** auftauchte,
live aber 35 % blockt. **Neu gerechnet ändert sich die Population stark (n 967→295
bzw. 1327→524), die Schlussfolgerung aber NICHT:** Market bleibt in 7 von 8 Feldern
negativ, am Level bestehen weiterhin **alle vier** Konfidenz-Schwellen
(ØR +0,392/+0,356 · PF 2,37/2,39), und der Slippage-Härtetest hält bis 0,20×ATR
(+0,177/+0,118). **Die heutige Bau-Entscheidung ist damit gedeckt.**
⚠ Lehre: ein aufgerufenes Gate ist nicht dasselbe wie ein aktives Gate. Beim
Kopieren einer Backtest-Struktur gehört die **Konfiguration** mitkopiert — sonst
misst man einen Stack, den es nicht gibt.
⚠ **Methodik-Fehler beim Bau, gefunden und behoben:** der erste Entwurf liess
`pend["level"]` nach der Bestätigung über weitere Bars stehen — bei verzögertem
Einstieg (Slot belegt, Konfidenz zu niedrig) wäre er zu einem Preis eingestiegen,
den der Markt längst verlassen hatte. Kein Look-ahead, aber **unerreichbar**, und
sichtbar an einem unplausiblen Bruch zwischen Konf 65 und 75 (+0,33 → 0,12).
Behoben über das `frisch`-Flag. **Lehre: ein Preis, der einmal galt, ist keine
Einstiegsmöglichkeit — nur der Moment der Auslösung zählt.**
- **⚠⚠ 15-MINUTEN-REGEL GANZ ABGESCHALTET (`adverse_15min_atr = 0`, 2026-07-31,
User-Entscheidung nach der Messung).** Auf die User-Frage „vielleicht die Auto-Close
auf 30 min setzen?" wurde die **ZEIT-Achse** mitgemessen
(`backtest_adverse15_squeeze.py`, 15/30/45/60 min × 0,5/0,8×ATR, BEIDE Populationen,
kanonischer Exit, Echtkosten, 2 Halbjahre):
| Zeitpunkt | Schwelle | Squeeze Δ H1/H2 | Wave Δ H1/H2 |
|---|---|---|---|
| 15 min | 0,5 | 18 / 34 ← war live | 25 / 41 |
| 30 min | 0,5 | 20 / 35 | 22 / 13 |
| 45 min | 0,8 | 12 / 18 | 3 / 7 |
| 60 min | 0,8 | 8 / 10 | 3 / 3 |
**ALLE 16 Kombinationen sind in BEIDEN Hälften negativ** — die Regel wird nur
*monoton weniger schädlich*, je später und lockerer sie prüft. Die „beste" Variante
ist praktisch die, die nie feuert. **Es gibt keine Einstellung, bei der sie hilft**
→ 30 min hätte den Schaden halbiert, nicht beendet. Deshalb ganz aus.
⚠ **Damit ist auch die frühere Einschätzung „auf dem Auto-Signal-Pfad ~neutral"
korrigiert**: sie stützte sich auf `backtest_auto_signal.py` (eigenes Exit-Modell,
+0,001 vs +0,026); mit dem kanonischen Exit sind es 25/41. Sechster Fall desselben
Musters an einem Tag (gemessen unter X, betrieben unter Y).
Der Schutz-Stack bleibt vollständig: **Broker-SL 2×ATR + Trailing + Time-Stop 120 min**.
Die Squeeze-Ausnahme (unten) bleibt im Code — falls die Regel je reaktiviert wird,
gilt sie weiterhin nicht für den Squeeze.
- **⚠ 15-MINUTEN-REGEL: SQUEEZE-TRADES AUSGENOMMEN (gemessen
`backtest_adverse15_squeeze.py`, 2026-07-31).** Die Schwelle 0,5×ATR wurde in
`backtest_auto_signal.py` auf **WAVE-SIGNAL-Entries** kalibriert und danach auf ALLE
Bot-Trades angewandt — wieder ein Kalibrierungs-Bruch (gemessen unter X, betrieben
unter Y). Auf SQUEEZE-Entries schadet die Regel bei **JEDER** Schwelle in **BEIDEN**
Hälften: Δ gegen „ohne Regel" 0,3 → 25/38 · **0,5 → 18/34** · 0,8 → 12/39 ·
1,2 → 5/18. **Monoton** — also ist die Regel selbst das Problem, nicht ihre
Einstellung. Grund: der Squeeze ist runner-abhängig (43 % Treffer, Ertrag im Tail),
ein Schnitt nach 15 min trifft genau den Tail. Ohne Regel H1 54 / H2 **+220**, mit
0,5 H1 72 / H2 +186.
**Anlass (real, 2026-07-31):** drei Bot-Trades wurden per `adverse15` geschlossen —
der **Auto-Signal**-Trade um 11:44 (26,31 €) und die beiden **Squeeze**-Trades um
20:15/20:36 (13,77 / 28,09 €).
⚠⚠ **DIE URSPRÜNGLICHE ANEKDOTE DAZU WAR FALSCH — korrigiert 2026-08-01.** Hier stand,
der 11:44-Trade sei „gerettet" worden (Kurs lief 32 € weiter dagegen) und die beiden
Squeeze-Trades seien „zu früh gekappt" worden (+19,28 / +17,60 € möglich). Beide Zahlen
waren **maximale Auslenkung** (bester bzw. schlechtester danach berührter Kurs) — also
das, was der Markt hergab, NICHT das, was der Exit gefangen hätte. Mit dem kanonischen
Exit nachgerechnet (`core/exit_model.py`, M5 aus `candles_m1`):
| Trade | IST (mit Regel) | ohne Regel, Trail 1,5 |
|---|---|---|
| 11:29 AUTOSIG | 26,31 | **21,43** (also NICHT gerettet) |
| 20:00 SQUEEZE | 13,77 | **23,27** (also NICHT zu früh) |
| 20:21 SQUEEZE | 28,09 | 25,02 |
Über alle 7 Bot-Trades des Tages hätte das Abschalten der Regel **16,42 € gekostet**;
die Regel hat an DIESEM Tag also geholfen. ⚠ **Die Entscheidung, sie abzuschalten,
bleibt trotzdem richtig** — sie ruht auf `backtest_adverse15_squeeze.py` (alle 16
Kombinationen in BEIDEN Hälften negativ), nicht auf einem Tag mit n=3. Lehre: eine
Einzeltag-Anekdote taugt nicht als Beleg, auch nicht als *bestätigende* — und
„maximale Auslenkung" ist keine erzielbare Zahl. Der Gewinn des Tages kam woanders her:
die Umstellung **Trail 1,5 → 1,0** brachte auf denselben 7 Trades **+24,86 €**.
**Umsetzung:** `_bot_open_source` merkt sich beim Öffnen, WOMIT der Bot eröffnet hat
(`auto_squeeze` | `auto_signal`); `_check_adverse15` steigt bei `auto_squeeze` aus.
Mit 7 Szenarien getestet (Signal/Squeeze/manuell/altes Ticket · zu früh · unter
Schwelle · im Plus). **Für den Auto-Signal-Pfad bleibt sie aktiv** — dort ist sie
gemessen ~neutral (ØR +0,001 mit vs +0,026 ohne), also eine **Versicherung mit
kleiner Prämie**: hilft im schlechten Regime, kostet im guten. `=0` schaltet sie ganz aus.
- **15-Minuten-Regel (`[trading] adverse_15min_atr=0.5`, AKTIV, User-Wunsch 2026-07-30
„wenn der Trade nach 15 Minuten gegen die Richtung läuft schließe ihn"):** steht ein
**vom BOT eröffneter** Trade 15 min nach Entry ≥ X×ATR_M5 gegen die Richtung →
schließen (`engine._check_adverse15`, `closed_by=adverse15`, Log ⏱ + Telegram).
⚠ **Schwelle GEMESSEN kalibriert, NICHT die ursprüngliche Vorgabe** (`backtest_auto_
signal.py`, 15-Min-Achse): „sobald im Minus" (0,0) ist die **SCHLECHTESTE** Variante
(WR bricht 39→31 %, Netto-ØR 0,067 vs +0,026 ohne Regel) — sie kappt Trades, die
sich erholt hätten. **0,5×ATR ist die beste Regel-Variante** (H1 0,111→−0,079).
Netto über beide Hälften bringt die Regel ~nichts (+0,001 vs +0,026 **ohne**) → sie
ist ein **REGIME-Schutz** (hilft im schlechten Regime, kostet im guten), bewusst als
Sicherheit akzeptiert. **2. unabhängige Bestätigung** von `backtest_adverse_nosignal.py`
(dort fiel der Adverse-Exit bei JEDER Schwelle durch). Greift **NICHT bei manuellen
Trades** (`_bot_open_ticket`-Marker — dort bleibt die Diskretion beim User, gemessen
sein Vorteil) und nicht in der Startup-Schonfrist; **1× je Ticket** geprüft
(`_adverse15_done_ticket`) genau beim 15-min-Zeitpunkt. Snapshot `adverse15_atr`/
`adverse15_count`. 0 = aus. Mit **18 synthetischen Szenarien getestet** (9 Entry:
Konf-Gate, Dedup, WARTEN-Re-Arm, Nachkauf, Gegen-Position, Cooldown, Toggle,
Nacht-Sperre · 9 Adverse: über/unter Schwelle, zu früh, im Plus, SHORT-Richtung,
manueller Trade, aus, 1×-je-Ticket, Startup-Grace) — alle korrekt.
- **⚠⚠ AUTO-SQUEEZE B4-BEFUND 2026-08-01: der EINSTIEG ist intakt, der EXIT hat den
Edge gefressen — deshalb NICHT zurückgebaut, aber mit vorab fixierter Abbruchregel.**
Anlass war die User-Frage zu den negativen Auto-Trades vom 31.07. Der B4-Monitor
trennt beides sauber:
| | Trefferquote | ØR | PF |
|---|---|---|---|
| **Mechanik** (Regel neu gerechnet, Backtest-Exit = isolierter Einstieg) | 46 % | **+0,102** | 1,15 |
| **Live** (n=31, 17.31.07., Gesamtsystem mit echten Exits) | 45 % | **0,242** | 0,49 |
Die **Trefferquote stimmt praktisch überein (46 vs 45 %)** — das Setup findet also
weiterhin, was es finden soll. Die Lücke von **0,34 R/Trade** entsteht komplett im
Exit: Ø-Gewinn **+11,02 €** gegen Ø-Verlust **19,83 €** (Verhältnis 0,56, ΣEUR
**162,99**). Der Squeeze ist runner-abhängig (43 % Treffer tragen nur, wenn die
Gewinner deutlich größer sind) — genau die Läufer wurden gekappt.
**Alle drei Ursachen sind am 31.07. behoben worden**, der Live-Zeitraum liegt also
fast vollständig VOR den Reparaturen: (a) das **P(break)-Modell** schloss 91 % aller
Level-Berührungen = faktisch pauschaler S/R-Close, und der ist 2× als Gewinner-Kappen
gemessen → nachtrainiert; (b) die **15-Min-Regel** schnitt Squeeze-Trades, auf dieser
Population in beiden Hälften schädlich → aus; (c) **Trailing 1,53,0 je nach TF**
statt der gemessenen 1,5 (Deployment-Drift Fall 3) → einheitlich 1,0.
⚠ **Ehrlich:** die Mechanik liegt mit ØR +0,102 **unter** dem validierten Band
+0,14…+0,23 — der Einstieg ist also auch selbst schwächer geworden (Regime). Das ist
kein Freibrief, sondern der Grund für eine harte Latte statt unbegrenztem Zuwarten.
✅ **B5-ABBRUCHREGEL, VORAB FESTGELEGT (User-Zustimmung 2026-08-01):** nach **≥20
Squeeze-Trades ab dem 01.08.** — **Verhältnis (ØGewinn/ØVerlust) < 1,0 ODER PF < 1
→ `auto_squeeze=false`.** Bewusst VOR der Beobachtung fixiert, damit die Latte
hinterher nicht verschoben wird; überwacht als Messung `squeeze_b5` im
`measurement_reminder.py`. Nacht-Anteil der Altdaten: 12 der 31 Trades / 78,92 € —
die stammen aus der Zeit vor dem Nacht-Guard (seit 24.07. wieder an).
- **⚠⚠ DEPLOYMENT-DRIFT FALL 7 — DER SQUEEZE WIRD ZU EINEM PREIS GEHANDELT, DEN DIE
VALIDIERUNG NIE UNTERSTELLT HAT (gemessen `backtest_squeeze_entry.py`, 2026-08-05).**
Anlass: User-Frage zu einem Auto-Trade, der „am höchsten Punkt einer aufsteigenden
Kerze" eröffnet wurde (05.08. 08:33:20 — Level 75,757, Einstieg **75,822**, also
über dem Hoch 75,808 der laufenden M5-Kerze; die schloss dann bei 75,693).
**`backtest_breakout_squeeze.py` steigt AM LEVEL ein** (`sim(lvl, …)`). Live schickt
`_check_auto_squeeze` eine **MARKET**-Order, sobald `state=='active'` — der Kurs ist
dann schon ein Stück weiter. **Aus 34 echten Live-Entries (Logs 16.07.05.08.,
Level aus der Log-Zeile × Einstiegspreis aus der DB): Median +0,275×ATR über dem
Level**, Mittel +0,686 · 75 % +0,744 · Max +4,339; nur **18 %** liegen innerhalb
0,10×ATR, **53 % über 0,30×ATR**.
**Was dieser Preisunterschied kostet** (80k M5, 2 Halbjahre, sequentiell EIN Slot,
kanonischer Exit, Echtkosten):
| Variante | H1 ØR / ΣR | H2 ØR / ΣR | WR |
|---|---|---|---|
| **A Einstieg AM LEVEL** (Backtest-Annahme) | **+0,456 / +369,8** | **+0,611 / +777,8** | 56/62 % |
| **B Einstieg am Bar-Close** (≈ live) | **0,158 / 125,9** | **0,006 / 7,6** | 37/42 % |
**Allein der Einstiegspreis dreht das Setup — 0,61 ØR in BEIDEN Hälften, WR bricht
von 56 auf 37 %.** Modell B ist dabei **nicht pessimistisch**: sein Median-Abstand
(0,38×ATR) liegt nah am LIVE gemessenen (0,28×ATR); der Live-Betrieb liegt also
~80 % des Weges von A nach B, also grob bei **breakeven bis leicht negativ** —
nicht bei den dokumentierten +0,14…+0,23.
⚠⚠ **Das ordnet den B4-Befund vom 01.08. NEU ein.** Dort stand: „Mechanik +0,102 vs.
Live 0,242 → **die Lücke entsteht im EXIT**". Die Mechanik-Zahl unterstellt aber
denselben Level-Einstieg. Der Einstiegspreis erklärt den Großteil der 0,34-R-Lücke
— **es war überwiegend der ENTRY, nicht der Exit.**
⚠ **BEIDE naheliegenden Reparaturen fallen durch** (Regel vorab fixiert: ΣR und ØR
in beiden Hälften > B, Nachbarn ebenso):
· **Abstands-Bremse** („überspringen, wenn der Kurs schon > X×ATR über dem Level
steht", X = 0,10…0,40) — **alle 5 Schwellen durch.** Kontraintuitiv, aber konsistent:
die *nahen* Ausbrüche sind pro Trade die SCHLECHTEREN (0,10×ATR → ØR 0,254), weil ein
gerade eben überschrittenes Level oft ein Fehlausbruch ist (54 % der Box-Ausbrüche
kehren binnen 3 Bars zurück, `backtest_consolidation.py`). Die Bremse senkt den
Gesamtverlust nur, indem sie weniger handelt.
· **LIMIT-Order am Level** (Retest, 3/6/12 Bars Gültigkeit, ±0,1×ATR Puffer) —
**alle 5 Varianten SCHLECHTER als Market** (ØR 0,14…−0,27 gegen 0,006…−0,158);
151430 Ausbrüche werden dabei verpasst. Deckt sich mit dem Retest-Befund aus
`backtest_consolidation.py`.
✅ **Schlussfolgerung: Variante A war nie erreichbar** — sie unterstellt einen Fill zu
einem Preis, den der Markt bereits verlassen hat. Die Frage ist damit nicht „wie
steigt man klüger ein", sondern **ob der Auto-Squeeze überhaupt bleibt**. Das ist die
bereits fixierte **B5-Abbruchregel** (≥20 Squeeze-Trades ab 01.08.; Verhältnis < 1,0
ODER PF < 1 → `auto_squeeze=false`) — dieser Befund stärkt sie erheblich.
**Nichts abgeschaltet** — Autonomie-Schalter sind User-Entscheidungen.
⚠ Grenzen: die Live-Stichprobe ist n=34 mit genäherter ATR-Zuordnung; A und B sind
zwei Modell-Enden, der Live-Betrieb liegt dazwischen (näher an B).
✅✅ **LÖSUNG GEFUNDEN — RUHENDE STOP-ORDER STATT MARKET (Variante E, gemessen
2026-08-05; Anstoß aus der Web-Recherche: „don't chase breakouts with market orders;
place orders just beyond the breakout level").** Eine PENDING `BUY_STOP`/`SELL_STOP`
liegt bereits am Ausbruchs-Level, WÄHREND der Squeeze `armed` ist (das Level ist da
schon bekannt = Box-Grenze), und wird vom Ausbruch selbst ausgelöst — anders als die
Limit-Order (D) wartet sie NICHT auf einen Rücklauf, verpasst also nichts.
| Fill-Annahme | H1 ØR / PF / ΣR | H2 ØR / PF / ΣR |
|---|---|---|
| Level exakt (= A) | +0,456 / 2,65 / +370 | +0,611 / 3,56 / +778 |
| **Level +0,05×ATR** | **+0,397 / 2,28 / +322** | **+0,553 / 3,06 / +697** |
| Level +0,10×ATR | +0,355 / 2,07 / +288 | +0,509 / 2,76 / +639 |
| **Level +0,20×ATR (pessimistisch)** | **+0,244 / 1,62 / +196** | **+0,426 / 2,25 / +531** |
| Market/Live (= B) | 0,158 / 0,74 / 126 | 0,006 / 0,99 / 8 |
**Selbst mit 0,20×ATR unterstellter Fill-Slippage — dem ~9-fachen des Median-Spreads
— bleibt es in BEIDEN Hälften klar positiv.**
⚠⚠ **Der Schlüssel ist nicht der Median, sondern der SCHWANZ.** Live ist der
Median-Abstand +0,275×ATR, der **Mittelwert aber +0,686** (Max +4,339). Eine
Stop-Order **deckelt den Einstiegspreis konstruktiv**; die Market-Order auf dem
5-s-Poll tut das nicht. Die Extremfälle (+3,5 / +4,3×ATR) stammen erkennbar aus
**blockiert-dann-freigegebenen** Ausbrüchen: feuert der Squeeze, während eine
Position offen ist (oder Nacht-/News-Fenster), wird KEIN Dedup-Marker gesetzt — der
Entry kommt später zum dann gültigen Marktpreis, u. U. weit hinter dem Level.
✅✅ **GEBAUT & LIVE (2026-08-05, User-Vorgabe „setze alle Vorschläge um"),
`[trading] squeeze_pending_entry=true`.** `engine._manage_squeeze_pending` (im
`_pos_loop`, VOR dem Market-Pfad) hält **beide Box-Grenzen** mit ruhenden
`BUY_STOP`/`SELL_STOP` bestückt, solange der Squeeze `armed` ist:
`trader.place_stop/cancel_pending/pending_orders` (Größe und SL aus **denselben**
Funktionen wie die Market-Order, nur bezogen auf den Trigger-Preis — sonst wäre der
SL-Abstand ein anderer als validiert). **OCO-Ersatz:** füllt eine Seite, existiert
eine Position → die Bedingung „soll liegen" wird falsch und die Gegenseite wird im
selben Tick storniert (MT5 kennt kein natives OCO).
⚠ **Der kritische Punkt bei einer RUHENDEN Order: die Gates.** Nacht-Fenster,
News-Blackout, Circuit-Breaker und der An/Aus-Schalter waren bisher Laufzeit-Checks
im Market-Pfad; eine liegende Order würde sie schlicht ignorieren. Deshalb neu
**`engine._squeeze_guard()` als EINE Quelle für beide Wege** — jeden Tick neu
bewertet, und sobald ein Gate zuschlägt, werden die Orders **storniert**. Zwei
Kopien derselben Gates wären genau die Divergenz, die hier siebenmal als
Deployment-Drift dokumentiert ist.
⚠ **Market bleibt nur noch FALLBACK — mit Nachjagd-Bremse** (`squeeze_max_chase_atr`
= **0,20**): greift keine Pending (Broker-Mindestabstand, Squeeze sprang direkt auf
`active`, Order-Fehler), wird nur noch eingestiegen, solange der Kurs **höchstens
0,20×ATR** über dem Level steht — genau der Bereich, der gemessen in beiden Hälften
positiv ist (+0,244/+0,426). Darüber: Ausbruch auslassen, Log „⏭ Auto-Squeeze
übersprungen". `_squeeze_last_sig` wird dabei NICHT gesetzt → läuft der Kurs zum
Level zurück, darf der Entry noch kommen.
⚠ Weitere Fallstricke, alle behandelt: **Broker-Mindestabstand**
(`trade_stops_level`) wird VOR dem Senden geprüft und der Fehler nur bei ÄNDERUNG
geloggt (sonst 1 Zeile/Sekunde, wenn der Kurs an der Box-Grenze klebt) · Order auf
der falschen Marktseite wird abgelehnt · **Toleranz 0,02×ATR**, damit nicht jede
ATR-Zuckung die Orders löscht und neu setzt · Startup-Schonfrist gilt auch hier.
**Mit 10 Szenarien getestet** (setzen · Idempotenz beim zweiten Tick · Fill →
Gegenseite weg · Squeeze klärt sich · Gate schlägt zu · Schonfrist · Feature aus ·
Box verschiebt sich · Broker lehnt ab · Nachjagd-Bremse in beide Richtungen).
Live verifiziert am Snapshot: `squeeze_pending=true`, `squeeze_max_chase_atr=0.2`,
`squeeze_pending_levels` (leer, weil eine Position offen war = korrektes Verhalten).
**Zurück zum alten Verhalten: `squeeze_pending_entry=false`.**
⚠ **B4-Pflicht + Werkzeug:** der Live-Abstand Einstieg↔Level muss jetzt gegen ~0
gehen (vorher Median +0,275×ATR). Dafür gibt es jetzt
**`analyze_squeeze_entry_gap.py`** (führt die zwei nötigen Quellen zusammen: das
Level steht NUR im Log, der Fill NUR in `trades`) — mit Vorher/Nachher-Split am
Umbau-Stichtag. ⚠ **Die Erfolgsmeldung ist paradox:** eine per Pending gefüllte
Order erzeugt **keine** `AUTO-SQUEEZE-ENTRY`-Logzeile (die entsteht nur im
Market-Fallback). Squeeze-Trades **ohne** Log-Treffer sind also der Erfolg, nicht
ein Fehler — das Skript weist sie deshalb getrennt aus. Überwacht als Messung
`squeeze_entry_gap` im `measurement_reminder.py` (≥12 Squeeze-Trades ab dem
Umbau; Stichtag exakt 05.08. 08:55, nicht Mitternacht — der 08:33-Trade lief noch
über Market). Bleibt der Abstand bei ~0,3×ATR, füllt die Pending nicht → Broker-
Mindestabstand und die Logzeile „Squeeze-Pending nicht platzierbar" prüfen.
⚠ **Zahlen-Korrektur (05.08.):** zuerst standen hier Median +0,306 / Ø +0,741 —
das war eine Scratchpad-Auswertung mit FESTEM Stundenversatz. `analyze_squeeze_
entry_gap.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.
- **Auto-Squeeze-Entry = LIVE AUTONOM (User-Vorgabe 2026-07-16, `[trading]
auto_squeeze=true`, Default FALSE):** der Bot eröffnet **selbständig eine echte
Order**, sobald der **gemessen-validierte** Squeeze-Ausbruch feuert (`wave.squeeze.
state=='active'`) UND **flat** ist — NICHT auf dem Wave-Signal (das ist der
Dry-Run-Verlierer), sondern nur auf dem 2-Stichproben-positiven Squeeze.
`engine._check_auto_squeeze` im `_pos_loop` (1 s): **1× je Ausbruch** (Dedup über
(dir, level); re-armt, wenn der Squeeze klärt), öffnet via `open_long/short` (→
Trailing an, Emergency-Stop armiert 2 % beim nächsten Tick), Log `🚀 AUTO-SQUEEZE-
ENTRY` + Telegram + Fehlschlag-Push. Snapshot `auto_squeeze`/`squeeze_entry_count`;
Toggle `POST /api/autosqueeze` (`set_auto_squeeze`) = Dashboard-Button **🚀 BRK**
(blau=AN, ersetzt den alten Dry-Run-AUTO-Button). **Dead-Hour-Guard = AN
(User-Vorgabe 2026-07-24, `[trading] auto_squeeze_skip_night=true`) — **Fenster
seit 2026-08-05 auf `3,4,5,6` VERKÜRZT** (`[trading] auto_squeeze_night_hours`,
User-Vorgabe „verkürze auf die wirklich toten Stunden"; war fest 07):** der Bot
eröffnet in diesen Stunden **KEINE** Squeeze-Trades (`_squeeze_night_hours` →
`_check_auto_squeeze` return; kein Entry UND kein Reverse; leere Liste = aus).
⚠ **`_SQUEEZE_NIGHT=(0..7)` bleibt bestehen, ist aber NICHT mehr das Gate** — es
ist jetzt nur noch die STATISTISCHE Nacht-Definition für die Entry-Checkliste
(dort gilt die Kostenfalle weiterhin, s. u.) und den Tag/Nacht-Split im
B4-Monitor (damit der Vergleich mit den Altdaten stabil bleibt). Der
Auto-Signal-Pfad nutzt dasselbe neue Fenster (ist ohnehin aus).
⚠⚠ **Die Verkürzung ist NICHT ertrags-begründet, sondern preis-begründet** —
gemessen ist **keine einzige Nachtstunde in beiden Halbjahren negativ**, jede
geblockte Stunde kostet Ertrag (Details beim Fallbeispiel unten). 36 ist
schlicht das **günstigste Fenster mit noch ≥4 h Schutz**: Preis ΣR
**12,5/62,9 R** gegen **42,4/242,6** bei 07 = **~27 %** der bisherigen
Kosten. Freigegeben sind **0, 1, 2 und 7 Uhr**. ⚠ Std 0 ist dabei die **beste
Stunde des ganzen Tages** für den Squeeze (ØR +1,48/+1,51) — sie zu blocken war
der teuerste Einzelposten. Live verifiziert am neuen Snapshot-Feld
`squeeze_night_hours` + Startup-Log „Auto-Squeeze-Nachtsperre: 3:00, 4:00, 5:00,
6:00". Zurück auf den alten Stand: `auto_squeeze_night_hours = 0,1,2,3,4,5,6,7`.
Doppelt begründet: (1) **gemessen** unrentabel (`backtest_realcosts.py`, Spread÷Nacht-
ATR 0,320,50×ATR, beide Halbjahre negativ = Kostenfalle) und (2) **kein Trade
soll unbeaufsichtigt nachts feuern, während der User schläft** — Auslöser: „müsste
automatisch schließen wenn ich nicht da bin, z. B. nachts". Der Guard LÖST das,
indem er den Trade gar nicht erst öffnet; tagsüber (User eher erreichbar) läuft der
bestehende Auto-Close-Stack (Broker-SL 2×ATR broker-seitig + Trailing + Time-Stop +
S/R-Close). ⚠ Historie: bis 2026-07-17 war der Guard AUS (User wollte rund um die
Uhr), am 2026-07-24 auf User-Wunsch **zurück auf AN**. `=false` schaltet ihn wieder
aus (dann auch nachts, unbeaufsichtigt). Einen engen Schutz-%-Stop NUR für Squeeze
wurde bewusst NICHT gebaut (enger %-Stop unter Margin-Sizing wird vom Squeeze-
Rauschen ausgelöst, 48,94-€-Tag — der Squeeze braucht Raum).
⚠⚠ **FALLBEISPIEL 05.08.2026 — der Guard hat 5 Ausbrüche geblockt, darunter den
Einstieg direkt vor einem 1,8-$-Rutsch (User-Frage „warum ist der Squeeze nicht
eingestiegen?").** Nachgerechnet mit der ECHTEN `_squeeze_one` auf den M5-Bars
(nicht aus dem Log geschlossen — der Squeeze-ZUSTAND wird nirgends geloggt, nur der
Entry): zwischen 02:00 und 07:30 Berlin feuerte 5× `active`, **alle in 07 Uhr**.
Der Bot war ab 01:15 flat, die Nachtsperre war also der ALLEINIGE Blocker (kein
Cooldown, keine offene Position). Kursverlauf: Hoch **76,373** um 03:30 → Tief
**74,548** um 04:40 = **1,825 $ in 70 min**; der Ausbruch **04:00 SHORT @ 76,11**
lag praktisch auf dem Hoch.
**Mit dem kanonischen Exit** (`exit_model.LIVE`, Echtkosten aus dem Bar-Spread —
bewusst NICHT „maximale Auslenkung", s. die Korrektur bei der 15-Minuten-Regel):
02:00 SHORT **+3,28 R** · 03:00 LONG 0,24 · 04:00 SHORT **+1,36 R** · 06:15 LONG
0,60 · 07:30 SHORT 0,24 → **Σ +3,56 R ≈ +95 € bei 2,66 Lots**.
⚠ **Trotzdem kein Argument gegen den Guard:** n=5 an EINEM Tag, und **92 % davon
stecken in zwei Trades** — ohne den 02:00er bleiben +0,29 R ≈ +12 €. Ein Einzeltag
taugt nicht als Beleg, auch nicht als widerlegender (dieselbe Lehre wie am 01.08.).
Die Guard-Begründung ruht auf 2 Halbjahren; dazu kommt der nicht-ökonomische Grund
(keine unbeaufsichtigten Nacht-Trades), der von Zahlen gar nicht berührt wird.
✅ **Ein messbarer Ansatz ergibt sich aber:** die Nacht-Begründung ist eine
**Kosten**-Aussage (Spread÷ATR 0,320,50). In dieser Nacht lagen die realen Kosten
bei **0,110,22×ATR**, also klar UNTER der Falle — weil der ATR hoch war. Ein
**kosten-basiertes statt stunden-basiertes Gate** („blocke, wenn Spread/ATR > X")
würde genau solche Nächte durchlassen und ruhige weiter sperren.
⚠⚠ **GEMESSEN am selben Tag (`backtest_cost_gate.py`, 80k M5, 2 Halbjahre,
kanonischer Exit, Echtkosten, ECHTE `_squeeze_one`) — und das Ergebnis fällt
anders aus als die Frage lautete.**
**(a) Kein Kosten-Gate ersetzt das Stunden-Gate** nach der vorab fixierten Regel
(ΣR **und** ØR in beiden Hälften ≥ Stunden-Gate, plus Nachbar-Schwellen): alle 7
Schwellen 0,150,50 fallen durch.
**(b) Der eigentliche Befund: das STUNDEN-Gate selbst kostet Geld — in BEIDEN
Hälften.** Sequentiell mit EINEM Positions-Slot (die konto-relevante Rechnung;
ein geblockter Nacht-Trade gibt den Slot frei):
| Variante | H1 ΣR | H2 ΣR |
|---|---|---|
| **ohne Gate** | **+369,5** | **+778,1** |
| Stunden-Gate 07 (live) | +327,1 | +535,5 |
| Kosten-Gate 0,30 | +356,3 | +760,1 |
| Kosten-Gate 0,35 | +369,3 | +781,9 |
→ das Live-Gate kostet **42 R (H1) und 243 R (H2)**. Die von ihm GEBLOCKTEN
Ausbrüche sind in beiden Hälften **positiv** (ØR +0,496/+0,589) — in H1 sogar
besser als die behaltenen (+0,382). Es wirft gute Trades weg.
⚠⚠ **MECHANISMUS BELEGT — die Nachtsperre ist auf der FALSCHEN Population
begründet (Deployment-Drift, Fall 6).** Die Nacht-Kostenfalle ist echt und
reproduziert (`backtest_realcosts.py`): über ALLE Nacht-Bars Median **0,333** /
Ø **0,387** ×ATR gegen Tag 0,163/0,185. **Aber der Squeeze feuert dort gar nicht** —
er verlangt einen Ausbruch, also anziehenden ATR. Seine Nacht-Ausbrüche kosten nur
**Median 0,183 / Ø 0,191** (Tag 0,156/0,162), liegen also praktisch auf Tag-Niveau
und weit unter der Falle. **Der Squeeze selektiert sich aus der Kostenfalle
heraus.** Gemessen wurde die Falle am Wellensignal, angewandt wird das Gate auf den
Squeeze — dieselbe Fehlerklasse wie bei der 15-Minuten-Regel (auf Wave-Entries
kalibriert, auf alle Bot-Trades angewandt, auf Squeeze gemessen schädlich).
⚠ Nebenbefund: **ein Kosten-Gate TRENNT tatsächlich** in die richtige Richtung
(bei 0,30 hat die geblockte Gruppe H1 WR 44,9 % / ØR +0,175 gegen +0,485 behalten)
— nur ist auch sie noch positiv, deshalb kostet jedes Blocken ΣR. Bester ΣR-Wert
ist 0,35, und der blockt praktisch nichts mehr (1 bzw. 10 Trades) = faktisch „kein
Gate".
⚠ **Methodik-Lehre zur eigenen Regel:** „ΣR UND ØR ≥ Referenz" ist als
Entscheidungsregel schlecht spezifiziert, wenn die Varianten unterschiedlich viele
Trades handeln — ØR und ΣR laufen dann mechanisch gegeneinander (0,30 schlägt das
Stunden-Gate im ΣR um +29/+225, verfehlt das ØR in H2 um 0,004). Die Regel wurde
**nicht nachträglich gelockert**; stattdessen ist der Vergleich „Gate gegen KEIN
Gate" separat ausgewiesen — er stand ohnehin in derselben Tabelle.
⚠⚠ **DREI Gründe, warum daraus NICHT automatisch „Nachtsperre aus" folgt:**
(1) **Slippage ist nicht modelliert** — nachts ist das Buch dünner, der gemessene
Tail liegt bei 0,75×ATR über den Stop hinaus (`analyze_execution.py`). Das trifft
Nacht-Trades selektiv und ist die größte Bedrohung des Befunds. (2) **Live sagt das
Gegenteil**, wenn auch dünn: von den 31 Squeeze-Trades des B4-Fensters waren 12
Nacht-Trades mit **78,92 €** — allerdings alle VOR den Exit-Reparaturen vom 31.07.
(P(break) nachtrainiert, 15-Min-Regel aus, Trail 1,5→1,0). (3) Der
**Beaufsichtigungs-Grund** („kein Trade soll feuern, während ich schlafe",
User-Vorgabe 2026-07-24) ist eine Präferenz und wird von keiner Zahl berührt.
✅ **Konsequenz (User-Vorgabe 2026-08-05 „verkürze auf die wirklich toten
Stunden"): Fenster von 07 auf 36 verkürzt** — Details oben beim Guard.
⚠⚠ **Die Prämisse der Vorgabe hält der Messung nicht stand: es gibt KEINE toten
Stunden.** Je Stunde einzeln, sequentiell, beide Halbjahre — **alle 24 Stunden
sind in beiden Hälften positiv**, keine einzige ist beidhälftig negativ. Die
ruhigsten Nachtstunden (n über beide Hälften): 5 (41 Ausbrüche) · 4 (56) · 3 (61)
· 6 (62); die aktivsten 7 (75) · 0/2 (72) · 1 (71). ⚠ **Std 0 ist die stärkste
Stunde des gesamten Tages** (ØR +1,483/+1,510, ΣR +22,2/+86,1) — ausgerechnet sie
lag im alten Gate.
Damit ist die Auswahl **keine Ertrags-Optimierung, sondern eine Preis-Minimierung
für eine Präferenz** — und deshalb auch nicht overfitting-gefährdet: es wird keine
prädiktive Behauptung aufgestellt. Preisliste (ΣR gegen „kein Gate"):
| Fenster | Std | Preis H1 | Preis H2 |
|---|---|---|---|
| 45 | 2 | 3,9 | 18,1 |
| 46 | 3 | 6,8 | 39,9 |
| **36 (gewählt)** | **4** | **12,5** | **62,9** |
| 25 | 4 | 12,8 | 77,2 |
| 07 (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 45 (fast gratis); wer den
alten Stand will, trägt `0,1,2,3,4,5,6,7` ein.
⚠ **Was die Verkürzung NICHT ausräumt:** die drei Vorbehalte oben gelten
unverändert — vor allem die **nicht modellierte Slippage** (dünnes Nacht-Buch,
Tail 0,75×ATR über den Stop). Die freigegebenen Stunden 0/1/2/7 liegen mitten in
der dünnsten Zeit. **B4-Pflicht: die Squeeze-Trades aus 0/1/2/7 Uhr getrennt
mitzählen** — bleiben sie hinter der Erwartung (ØR ~+0,5 R), war die Slippage
der Grund und das Fenster gehört zurück auf 07.
**Stop-&-Reverse = AUS (Default, `[trading] auto_squeeze_reverse=false`,
`_squeeze_reverse`; User-Vorgabe 2026-07-17):** War kurz scharf, dann abgeschaltet —
nur der **flat-Entry ist gemessen-validiert**, der Reverse ist unbelegt, verdoppelt
Kosten und dreht am **Ausbruch-Extrem** (real 2026-07-17 18:55: WAVE_REV_SHORT gegen
einen News-Rally-LONG-Ausbruch bei 29,64 € geschlossen, dann LONG am Erschöpfungs-Top
82,32 — Elliott „impulse_up/5 exhausted" im selben Moment). Bei `false` bleibt ein
Gegen-Trade **unangetastet** (Drehen = manuelle Entscheidung); `_squeeze_last_sig`
wird dann NICHT gesetzt → schließt der Gegen-Trade (SL/manuell), kann der flat-Entry
auf denselben Ausbruch noch feuern. Bei `true` (historisch, reaktivierbar): läuft ein
Trade GEGEN den Ausbruch (auch manueller/Fremd-Trade!) → sofort `close(reason=
"squeeze_reverse")` (Log 🔄 + Telegram, Dedup `_squeeze_reverse_sig`), nächster flat-Tick
dreht. ⚠ NICHT gemessen (der Squeeze-Backtest nahm flat-Entry an) — deshalb Default aus.
**Exit-only-Variante („Gegen-Position beim Ausbruch schließen, OHNE Re-Entry") =
VERWORFEN (gemessen `backtest_squeeze_exit.py`, 2026-07-22, User-Idee „Breakout
schneller schließen"):** die sicherere Halb-Variante (nur schließen, nicht drehen)
fällt ebenfalls durch — in BEIDEN Hälften schlechter als der Live-Exit (SL 2×ATR+Trailing):
ΔΣR H1 15 (immer)/5 (nur im Minus), H2 27/29. Grund: der Gegen-Ausbruch ist oft ein
Fehlausbruch/Whipsaw → die Position prallt zurück, der frühe Close kappt Trades, die
sich erholt hätten. Der 07:38-Fall (LONG lief zum SL während SHORT-Ausbruch) war Varianz.
**Fazit: Squeeze taugt als ENTRY (validiert), NICHT als Gegen-EXIT.** Der einzige
Speed-Hebel bleibt die schnellere Erkennung (Variante A, ~5 s).
⚠⚠ **DRITTE MESSUNG 2026-08-05 (`backtest_squeeze_exit3.py`) — jetzt mit dem
KANONISCHEN Exit, und der Grund für das Nein ist ein anderer als gedacht.**
Anlass: Trade T=49423144 (manueller SELL 3,49 @ 75,012) lief in 14 min 1,135 $
ins Minus und endete am Initial-SL bei **343 €**; User-Wunsch: „der Breakout
hätte den Trade früher schließen sollen", nachgeschärft zu „bei einem Squeeze den
SL sofort eng setzen".
⚠ **Was an dem Tag wirklich passierte:** beim Einstieg war der Squeeze **active
SHORT** — er stand FÜR den Trade. Die Empfehlung war WARTEN (`block=entry_room`),
der Einstieg manuell. Der Gegen-Ausbruch (armed LONG @ 75,729) wurde erst ~35 min
später aktiv; ein Close dort hätte ≈**219 €** statt 343 € bedeutet (~124 €).
**Gemessen (beide Varianten, kanonischer Exit, Echtkosten, 2 Halbjahre, zwei
Populationen — 2.365 Squeeze- und 1.566 Wellen-Entries):**
| Variante | Auslösungen H1/H2 |
|---|---|
| sofort schließen (Squeeze-Entries) | **1 / 1** von 809 bzw. 1.274 |
| **enger SL 0,10,5×ATR** (Squeeze-Entries) | **0 / 0** |
| sofort schließen (Wellen-Entries) | 8 / 8 von 386 bzw. 835 |
| enger SL (Wellen-Entries) | 23 / 23 |
✅✅ **DAS IST DER EIGENTLICHE BEFUND: der Mechanismus feuert praktisch NIE.**
Nicht weil die Idee schlecht wäre, sondern weil sie **konstruktionsbedingt zu spät
kommt**: ein Gegen-Ausbruch verlangt eine NEUE Kompression (Box über 10 M5-Bars =
50 min), der Ausbruch selbst weitet die Box aber gerade erst. Die mittlere
Haltedauer liegt bei **1218 min**. Der Gegen-Ausbruch trifft also in rund **0,1 %
(Squeeze) bzw. 1 % (Welle)** der Trades ein — der Fall des Users war einer davon.
Die Kennzahl-Unterschiede in der Tabelle sind entsprechend Rauschen aus einer
Handvoll Fällen; keine Variante besteht auf beiden Populationen.
⚠ **Auch die abgeschwächte Form („enger SL statt Close") ändert nichts** — sie
adressiert zwar genau den dokumentierten Einwand (Gegen-Ausbrüche sind oft
Fehlausbrüche), kann ihn aber nicht ausspielen, wenn sie 0-mal auslöst.
⚠ Und sie wäre von dieser Simulation ohnehin **überschätzt**: ein enger Stop wird
von einer Bar-Simulation systematisch zu gut gerechnet (s. Auflösungs-Artefakt,
`analyze_trail_resolution.py`, 05.08.).
**Damit zum DRITTEN Mal verworfen — und diesmal mit einer Begründung, die nicht
vom Exit-Modell abhängt.**
**Re-Test unter Vola-Regimen = ERNEUT VERWORFEN (gemessen
`backtest_squeeze_exit_vol.py`, 2026-07-24, User-Frage „Markt gerade sehr volatil —
wirklich nicht profitabel?"):** Verschärfter Test mit echten SQUEEZE-Entries (statt
EMA-Crosses), 4 Varianten (immer/nur-im-Minus/nur-Verlust>0,5×ATR), ATR-Terzil-Split
je Hälfte + Extra-Fenster letzte ~30 Tage. Ergebnis: Effektgröße überall ~0
(±0,0020,005 R/Trade bei ~1150 Trades/Hälfte), **Vorzeichen kippt zwischen den
Regimen** (MODL: H1-Hochvola 1,6 / H2-Hochvola +3,0 / letzte 30 Tage 0,3) → nach
der vorab festgelegten Regel (beide Hälften UND Hoch-Vola-Terzil ≥ BASE) fällt JEDE
Variante durch. **Kernbefund: Worst-Case ist in ALLEN Varianten identisch**
(2,33/2,47 = SL-Deckel) — der Gegen-Squeeze-Exit reduziert das Tail-Risiko um
exakt 0, weil bei echten schnellen Brüchen immer der SL zuerst greift; er kappt nur
langsame Kriecher, von denen sich >50 % erholt hätten. (Methodik-Hinweis: absolute
ØR pessimistischer als im validierten Squeeze-Backtest — Entry am Bar-Close statt
am Level, keine Flat-Bedingung; für den Varianten-VERGLEICH neutral, da alle Exits
denselben Entries folgen.) 3. Messung derselben Idee (Reverse · Exit-only ·
Vola-Regime-Re-Test) — gleiche Antwort.
**S/R-Close kappt Squeeze-Runner? = GEPRÜFT, KEINE Änderung (gemessen
`backtest_squeeze_srclose.py`, 2026-07-24, User-Beobachtung „Squeeze wird direkt am
nächsten S/R geschlossen"):** Der Log bestätigt, dass Auto-Squeeze-Trades regelmäßig
vom P(break)-S/R-Auto-Close am nächsten Gegen-Level geschlossen werden (der Squeeze
wurde mit reinem Trailing validiert, ist runner-abhängig — Verdacht: Runner gekappt).
Direkter Vergleich NUR auf Squeeze-Entries (Box≤2,5×ATR, k=0,1): **Exit A = nur
Trailing (validiert)** vs. **Exit B = +S/R-Close (aktuell live)** = **Wash, kein
robuster Schaden** — H1 ØR +0,190→**+0,224** (ΣR +40→+47, B besser), H2 ØR
+0,394→+0,377 (ΣR +290→+278, B marginal schlechter) = Δ ±0,02 R/Trade (Rauschen).
**Warum unkritisch:** der S/R-Close ist P(break)-gegated (schließt nur wenn Level
wahrscheinlich HÄLT, P<60 %) → echte, starke Ausbrüche haben hohe P(break) und
laufen weiter; **PF/Runner-Tail bleiben erhalten** (H2 PF 1,58=1,58), nur die WR
steigt (50→55 %, Abpraller früher als Klein-Gewinn kassiert). Squeeze-Trades vom
S/R-Close auszunehmen ist also NICHT belegt. (Reaktivierbar per Toggle, falls der
User trotzdem die Runner nie kappen will — kostet gemessen ~nichts, ist aber
Präferenz, kein Edge.)
**S/R-Close-Cooldown (2026-07-17, Koordinations-Fix, kein Backtest):** nach einem
S/R-Auto-Close eröffnet der Auto-Squeeze **~3 min (`_SQUEEZE_SR_COOLDOWN_S=180`)
nicht in GLEICHER Richtung** wieder. Grund: der S/R-Close ging von „Level hält"
(P(break)<Schwelle) aus, ein sofortiger Gleichrichtungs-Squeeze („Level bricht") war
der widersprüchliche 1-s-Roundtrip (LONG-Close +15 → 1 s später neuer LONG, doppelter
Spread; real beobachtet 2026-07-17 14:17). `_check_sr_close` merkt Zeit+Richtung
(`_sr_close_ts`/`_sr_close_dir`), der Entry-Pfad prüft sie; `_squeeze_last_sig` wird
NICHT gesetzt → nach Ablauf feuert der Ausbruch, falls noch aktiv. **Der Reverse
(Gegenrichtung) bleibt unberührt** — nur der Gleichrichtungs-Wiedereinstieg wird
verzögert.
**Allgemeine Wiedereinstiegs-Sperre nach JEDEM Close (2026-07-28, Koordinations-Fix,
kein Backtest):** nach dem Schließen eines Trades durch **Notfall-Stop / Broker-SL /
Time-Stop / manuell** eröffnet der Auto-Squeeze **~3 min (`_SQUEEZE_REENTRY_COOLDOWN_S=
180`) nicht in GLEICHER Richtung** wieder. Grund: real am **2026-07-27 15:39** killte
ein zu enger manueller 3-€-Notfall-Stop (Sub-Spread bei Margin-Sizing) den Auto-
Squeeze-Entry #1 nach **16 s**; der Squeeze war noch `active` → sofortiger Re-Entry #2
(Open→Instant-Close-Kaskade, doppelter Spread). `_check_auto_close` erfasst den Flat-
Übergang (`_pos_close_ts`/`_pos_close_dir`, Richtung via `_open_pos_dir` solange offen),
der Squeeze-Entry-Pfad prüft ihn NACH dem S/R-Cooldown; `_squeeze_last_sig` wird NICHT
gesetzt → nach Ablauf feuert der Ausbruch, falls noch aktiv. Gegenrichtung/Reverse
unberührt. Deckt alle Close-Arten außer dem S/R-Close ab (der hat seinen eigenen
Marker). ⚠ Behebt das *Symptom*; der eigentliche Auslöser war der Sub-Spread-Stop
(`emergency_min_eur=3`-Guard fängt <3 € ab, 3 € genau auf der Grenze war zulässig).
- **TF-Wechsel nicht mehr persistiert:** `engine._apply_wave_tf` merkt den
Heuristik-TF nur im Speicher (kein `save_config` mehr) — sonst würde die
Secrets-`ini` bei jedem Switch neu geschrieben (Kommentar-Verlust/Churn). Beim
Neustart wählt die Heuristik den TF binnen 1 min neu.
- **Circuit Breaker (Tagesverlust-Stopp) = gebaut, aktuell AUS `[trading]
daily_loss_limit_pct=0` (User schaltete ihn am 2026-07-27 wenige Stunden nach dem
Einbau wieder ab). Kurz auf 8 aktiv (Überlebens-Basis, mit dem Autonomie-Ziel
reaktiviert nach jahrelanger Ablehnung), dann `=0`. Reaktivieren = ini auf z. B. 8.**
Mechanik bei `>0`: erreicht der **Tages-P&L
(realisiert heute + offene Position)** pct % der Balance, schließt der Bot die
Position (`closed_by=circuit_breaker`) und sperrt **neue Auto-Trades bis zum
nächsten Tag** (`_check_auto_squeeze` prüft `_circuit_halted()`). `_check_circuit_
breaker` im `_pos_loop` (1 s, unter Startup-Grace pausiert), realisierter Tages-P&L
~30 s gecacht (`_day_realized_cached`), Berlin-Datum als Trip-Marker (`_circuit_
tripped_date`) → automatischer Reset am neuen Tag. Log 🛑 + Telegram (eigener,
nicht vom Close-Gate betroffen). Snapshot `circuit_breaker {enabled, limit_pct,
limit_eur, day_total, tripped}`, Frontend `#circuit-banner` (rot bei ausgelöst,
bernstein ab 60 % des Limits, v=115). ⚠ **80 %-Margin-Sizing bleibt** (User-
Entscheidung, 3.+ Mal bestätigt; `risk_pct=0`) → der 8 %-Tagesstopp schützt v. a.
gegen die **Verlust-Serie/-Kaskade**, NICHT vollständig gegen den Einzeltrade
(dessen 2×ATR-SL unter 80 % Margin allein schon >8 % verlieren kann). 0 = aus.
Mit 5 synthetischen Szenarien getestet (nicht-zu-früh · Trip+Close · kein
Doppel-Close · aus · Auto-Squeeze-Block). ⚠ Der frühere „NICHT einbauen"-Grund
gilt nicht mehr — Ziel hat sich zu **autonomem Handel** verschoben, und ein
unbeaufsichtigter Agent braucht die Notbremse im Code.
- **Notfall-Stop = AUS beim Öffnen (Stand 2026-07-24, `auto_emergency_margin_pct=0`,
`auto_emergency_pct=0`, `auto_emergency_loss=0`).** User-Vorgabe 2026-07-24: „gewinn
close und notfall close … nicht setzen beim Eröffnen" → alle drei Auto-Arm-Modi 0,
beim Öffnen einer Position wird KEIN Notfall-Stop gesetzt. Nur ein **manuell** im
UI-Feld eingegebener Wert armiert (bleibt bis zum nächsten Positionswechsel).
**⚠ Vertipper-Schutz `[trading] emergency_min_eur=3` (Fix 2026-07-27):** ein manuell
gesetzter Notfall-Stop UNTER diesem Minimum wird als „aus" behandelt (nicht
scharfgestellt) + Log-Warnung. Grund: ein Sub-Spread-Wert schließt den Trade
**sofort** UND vererbt sich per `_emergency_remember` auf ALLE Folge-Trades — real
am 2026-07-27 setzte der User versehentlich 1 € → jeder neue Trade wurde in
Sekunden per Notfall-Close gekillt (Tickets 48504360/48504814 u. a., „sofort
geschlossen"). Guard in `set_emergency` (greift NUR auf die manuelle Eingabe;
die %-Auto-Arm-Pfade sind eh 0). 0 = kein Floor.
Kurz-Historie des margin-%-Modus: 2026-07-17 komplett raus (2 % ≈ 0,76×ATR zerschoss
die Squeeze-Trades, 48,94-€-Tag) · 2026-07-20 wenige Stunden als **3-%-GAP-NETZ
(Balance-Basis)** mit `risk_pct=1.5` · **2026-07-23 kurz margin-basiert `=3.0`**
(dritter Modus, ausgelöst durch zwei Notfall-Closes am selben Abend 11,20 €/20,47 €,
wobei die Schwelle beim ersten Trade noch auf einem händischen UI-Restwert von 5 €
hing) · **2026-07-24 auf User-Wunsch wieder ganz AUS.** Der margin-%-Modus bleibt im
Code (retry-sicheres Armieren via `_emergency_margin_armed_ticket`, hat Vorrang vor
Balance-%/Fixwert wenn >0), reaktivierbar per ini. ⚠ Kopplungs-Regel (falls
reaktiviert): unter Margin-Sizing kann ein enger %-Stop schneller greifen als der
2×ATR-SL. Schutz-Stack aktuell: **Broker-SL 2×ATR + Trailing + Time-Stop** (der
Auto-Notfall-Stop ist inaktiv).
**UI-Feld** (`#emg-input`, User-Vorgabe 2026-07-22, v=105) in der Trade-Leiste
(rot, neben Gewinn-Close), Enter blurrt nur (kein Doppel-Send/ungewolltes 0,
gleicher Fix wie Mindestgewinn 2026-07-22) — ein am Handy gesetzter Wert
überschreibt den Auto-Arm für den laufenden Trade und bleibt bis zum nächsten
echten Positionswechsel maßgeblich (mit Test verifiziert). ⚠ Server-seitig: wirkt
nur bei laufendem Server (Update-Reboot-Lücke → „Auch beachten"). Der Server
schließt die Position
**sofort**, sobald `P&L ≤ Wert` (`engine._check_emergency` im `_pos_loop`, 1 s;
wirkt auch bei gesperrtem Handy). **Auto-Arm — drei Modi (Priorität: margin-% >
Balance-% > Fixwert):**
(a) **NEU — % der Einsatz-Margin dieser Position (aktiv, User-Vorgabe 2026-07-23):**
`[trading] auto_emergency_margin_pct=3.0` → Schwelle = **pct% × Margin** (dieselbe
Margin wie beim Gewinn-Close, aus `trader.refresh()`). **Retry-sicher** (Fix
2026-07-23, gleiches Muster wie beim Gewinn-Close): läuft NICHT nur einmalig beim
Ticket-Wechsel, sondern jeden `_pos_loop`-Tick erneut, bis `order_calc_margin`
einmal >0 liefert (`_emergency_margin_armed_ticket`-Retry-Schutz) — verhindert,
dass bei einem Broker-Race im ersten Tick nach dem Öffnen dauerhaft ein stalen
Wert der vorherigen Position hängen bleibt. Mit 4 synthetischen Szenarien
verifiziert (sofort verfügbare Margin · verzögerte Margin/Retry · manuelles
Überschreiben bleibt erhalten · Neustart überschreibt NICHT). Snapshot
`emergency_margin_pct`.
(b) **% des Kontos (Fallback, wenn margin-%=0):** `[trading] auto_emergency_pct=2.0`
→ bei jeder neuen Position wird die Schwelle auf **pct% × Balance** gesetzt
(`_emergency_pct`, skaliert mit dem Konto; z. B. 2 % von 831 € = 16,62 € ≈
0,4×ATR; 1× auf 1 % getestet, war zu eng → 2 %). UI-Feld zeigt im Flat-Zustand
den dynamischen Wert (Frontend rechnet `pct%×balance`, Snapshot `emergency_pct`).
(c) **Fixwert (Fallback, wenn beide %-Modi aus):** Schwelle = **zuletzt
genutzter Wert** (`_emergency_remember`, Startwert `[trading] auto_emergency_loss`
Default **10**, 0 = aus).
Setzt der User im UI einen neuen Wert, wird der gemerkt und ab dem nächsten Trade
übernommen. Aktiver Stop verfällt nach Auslösen UND bei Positions-Close, der
gemerkte Wert bleibt. **Neustart-fest (Fix 2026-07-17, `emergency_state.json`):**
Der aktive Notfall-Stop/TP wird mit dem **Ticket** persistiert (`_save/_load_emergency_
state`, atomarer Write; bei Auto-Arm UND manuellem Set). Erkennt der Server dieselbe
offene Position nach einem `restart_server.bat` wieder, **stellt er den persistierten
Wert wieder her** (`_check_auto_close`: `ticket == _emergency_state_ticket` → restore,
Log „Auto-Close wiederhergestellt") statt auf die pct-Automatik zurückzuarmieren. Grund:
real am 2026-07-17 14:30 setzte ein Neustart bei offenem Trade die von Hand gesetzten
25 € auf 19,42 € (2 %) zurück → Notfall-Close bei 19,64 statt bei 25. Eine echt
neue Position (anderes Ticket) armiert normal aus pct/Merkwert und persistiert neu.
⚠ **Trailing-Zustand ebenfalls neustart-fest (Fix 2026-07-29):** `emergency_state.json`
persistiert jetzt zusätzlich `trail` (= `trail.enabled`) per-Ticket. Erkennt der Server
dieselbe Position wieder und war das Trailing vor dem Neustart AN, **reaktiviert er es
automatisch** (`_check_auto_close`-Restore-Zweig → `trail.toggle`, Log „Trailing
wiederhergestellt … nach Neustart"). Grund: real am 2026-07-29 lief eine Auto-Squeeze-
Position nach mehreren Server-Neustarts nur mit dem **Broker-SL** (Trailing/Breakeven/
Time-Stop weg), weil `trail.enabled` nur im Speicher lebte und die Wiedererkennung
(magic-match) es NIE reaktivierte — der User musste es von Hand einschalten. War das
Trailing AUS (z. B. nach manuellem SL/TP via `/api/sltp` → `trail.deactivate`), bleibt
es aus (der persistierte Zustand entscheidet — kein Überschreiben eines manuellen TP).
Der Zustand wird bei **jeder Änderung** persistiert (Change-Detection im `_pos_loop`,
fängt auch die Selbst-Deaktivierung des Trailings bei manueller SL/TP-Erkennung).
**„aus"-Button schaltet auch den Auto-Arm ab** (setzt
`_emergency_remember=0` — vorher blieb der gemerkte Wert und re-armte beim nächsten
Trade, „aus" wirkte nur bis dahin; Bug gefixt 2026-07-07). Gilt genauso fürs Gewinn-Ziel. (Auto-Arm 1× auf manuell-only gestellt, nach Fehl-Trade
wieder reaktiviert — echter Schutz gegen falsche Empfehlungen; Signal-Filter dagegen
4× gemessen wirkungslos/schädlich, s. `backtest_rev_htf.py`.) `engine.set_emergency()` · `POST /api/emergency {value}` ·
`snapshot.emergency_loss`. Das ist KEIN automatischer Circuit-Breaker, sondern
ein manueller Hard-Stop (vom User gewünscht). **Benachrichtigung IMMER** (Ausnahme
vom „Close-Push nur im Plus"): bei Erfolg „🚨 Notfall-Close", bei **Fehlschlag**
„⚠ Notfall-Close FEHLGESCHLAGEN — manuell schließen!" (Schwelle bleibt dann aktiv,
nächster Tick versucht erneut). Eigener `send_telegram`, **nicht** vom
`pnl>0`-Gate (`history`) noch vom 5-Min-Cooldown betroffen.
## Mobile-UI (web/) & Alarme
- Aufbau: **Tab-Navigation ganz oben** (sticky: Dashboard/**SIG Live**/**SIG Anzeige**/Logs/Statistik/**Charts**/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** (`#view-charts`): **M1/M5/M15/M30/H1 untereinander**, je ein
Candlestick-Chart mit **EMA12 (blau) + EMA50 (amber)**-Overlay und Trend-Header
(▲/▼/→ nach EMA12-vs-EMA50 mit Totband). Framework **TradingView Lightweight
Charts v4.2.3 — lokal vendored** (`web/lightweight-charts.standalone.production.js`,
KEIN CDN wegen WireGuard/Offline). Daten via `GET /api/bars?tf=&n=` →
`engine.get_bars` (OHLC+EMA+Trend unter `mt5_lock`). `app.js`: `loadCharts`/
`renderChart`, Charts nur bei sichtbarem Tab geladen + Auto-Refresh 20 s
(schont den Lock). Broker-Zeit (UTC+3) → UTC via `broker_utc_offset`. **Gitternetz
dezent** (2026-07-27, User „dunkler/unauffälliger": `grid`-Farbe `#161b22`→`#10151c`,
näher am Hintergrund `#0d1117`). **Chartmuster-Overlay (nur M30)** — s. Chartmuster-
Kachel: die 2 stärksten M30-Muster als Trigger-/Ziel-Preislinien.
**Regressionskanal-Overlay (nur M30, 2026-07-20):** `get_bars` liefert bei M30
`channel {dir, mid[], upper[], lower[]}` (Arrays deckungsgleich mit den Bars,
`structure.channel_series` — Regression über 60 abgeschl. Bars, linear über die
volle Breite extrapoliert). Frontend zeichnet 3 LineSeries (oben/unten gestrichelt,
Mitte gepunktet, grau). Dieselbe Kanal-Definition wie die Marktstruktur-Kachel —
REINE Anzeige (Signalvariante gemessen verworfen, s. `backtest_structure.py`).
**S/R-Overlay & Dashboard-Kachel = EINE Quelle** (`_draw_levels`; die Position-Kachel
`sr-res`/`sr-sup` speist jetzt via `_dash_sr` daraus statt aus dem Misch-Set
`_sr_levels` — vorher wichen Chart und Kachel ab, User-Feedback 2026-07-15). `_sr_levels`
bleibt intern für Strategie/Konfidenz. **„Klebrige" Hysterese** in `_draw_levels`
(`_draw_last`, verschärft 2026-07-21): die gezeichnete Linie **bleibt stehen, bis der
Kurs sie WIRKLICH durchbricht** (bid > R+0,1×ATR bzw. bid < S0,1×ATR) — ein neuer,
näherer M5-Pivot löst KEINEN Wechsel mehr aus (das war die Zitter-Quelle: auf M5
bilden sich laufend neue Pivots, die alte 0,25×ATR-Regel sprang darauf an). Ausnahme:
ist die alte Linie >1,5×ATR weit weg UND ein deutlich näheres Level existiert, wird
refresht (sonst bliebe sie irrelevant hängen). Gilt für PWA-Chart UND MQL5-CSV (eine
Quelle).
**Level-Auswahl nach Kursposition statt Entstehungstyp (Fix 2026-07-23, User-Frage
„Widerstand/Support-Retest von der falschen Seite"):** vorher kam Widerstand NUR aus
`ph` (Swing-Hochs) und Support NUR aus `pl` (Swing-Tiefs) — ein gebrochenes Level blieb
für IMMER im falschen Topf hängen. 2 von 4 Retest-Varianten hatten dadurch NIE eine
Durchbruchwahrscheinlichkeit: (1) Widerstand von unten ✓ funktionierte, (2) **Support-
Retest von unten** (Kurs unter einem gebrochenen Support, nähert sich von unten wieder)
✗ kein Hinweis, (3) Support von oben ✓ funktionierte, (4) **Widerstand-Retest von oben**
(Kurs über einem gebrochenen Widerstand, nähert sich von oben wieder) ✗ kein Hinweis.
Fix: `ph`/`pl` bleiben GETRENNT geclustert (unverändert granular — ein erster Versuch,
beide Rohpools VOR dem Clustern zusammenzulegen, erzeugte in dichten Zonen Ketten-Mega-
Cluster durch die verdoppelte Punktdichte, >1×ATR breit, verworfen), aber NACH dem
Clustern werden beide fertigen Linien-Listen zusammengeführt und die Wahl erfolgt rein
per `min/max` relativ zu `bid` — nicht mehr nach Topf-Zugehörigkeit. Verifiziert: Fall 1
bei Live-Daten (bid 90,951) und ein dichter Kontrollfall liefern exakt dieselben Level
wie vorher (keine Regression); Fälle 2+4 (synthetische Retest-Szenarien) finden jetzt
das nahe, vorher übersehene Level statt eines weit entfernten Ersatzlevels. Betrifft
`_draw_levels` direkt und damit ALLE Konsumenten (PWA-Chart, MQL5-CSV, Dashboard-
Kachel, `_sr_close_hint`/Auto-Close) — eine Quelle, ein Fix.
**⚠ LEVEL-TF M5→M30 umgestellt (2026-07-30, gemessen `backtest_level_tf.py`):**
Auslöser war die User-Beobachtung „die S/R-Linien liegen oft sehr nahe beieinander" —
quantifiziert stimmte das: **71 % der M5-Level lagen näher als 1×ATR** (Median-Abstand
nur 0,61×ATR). Gemessen über 80k M5-Bars in 2 Halbjahren (Trades bleiben M5, nur die
LEVEL-Quelle variiert; Live-Exit + Echtkosten). **M30 gewinnt in BEIDEN Hälften:**
Abstand **1,95×ATR** (nur 28 % eng) · AUC out-of-sample **0,721** (M15 0,725, M5 0,710)
· R-Ertrag der P(break)-Close-Regel **rund doppelt** (+12.424/+13.047 vs +6.698/+7.749
bei P<0,55). M15 lag dazwischen; M30 gewählt wegen des klar besten R-Ertrags und der
weitesten Level. **Das P(break)-Modell wurde dafür NACHTRAINIERT** (`_PB_MU/_SD/_W`
in `engine.py`): out-of-sample validiert (H1→H2), dann auf allen 80k Bars final
gefittet — 34.771 Touches, Basisrate 39,4 %, Kalibrierung trifft (pred/real 23/25 ·
38/36 · 58/59 · 86/87). Die **Merkmale bleiben M5** (mom6/mom3 auf M5-Bars), nur die
Level sind M30. Quelle live: `wave.pb_levels30` (M30-H/L aus dem 15-s-Turn-Fetch,
k=3, letzte 50 Bars = trainingsgleich zum 300-M5-Lookback), Fallback `pb_levels` (M5)
beim Kaltstart. ⚠ **NICHT mitverändert: das Entry-Raum-Gate** (`wave._room_gate`) nutzt
weiter die M5-`pb_levels` — `entry_room_atr=0.6` ist darauf kalibriert, eine Umstellung
dort wäre ungemessen. Alte M5-Modellwerte stehen als Kommentar über `_PB_MU` (Rückweg).
Real nach der Umstellung: R/S 84,617/83,722 = 2,9×ATR Abstand (vorher 0,28×ATR).
**S/R-Overlay:** `get_bars` liefert `levels {res, sup}` aus **`engine._draw_levels`**
= echte Kursstruktur (rohe M30-Pivots aus `wave.pb_levels30`, geclustert ~0,5×ATR,
**nur die NÄCHSTE je Seite = 2 Linien total, deckungsgleich mit Dashboard
sr-res/sr-sup** — mehr Linien tauchten beim Kurslauf laufend auf/ab, User-Feedback
2026-07-14; identisch zur Auto-Close-Logik). ⚠ Bugfix 2026-07-14: vorher
kamen die Linien aus `_sr_levels` (stale [zones]-Config + Tageslevel + Gaps) —
gezeichnet wurden Uralt-Level (81,24/82,64) statt aktueller Struktur. Farben
**blau** (User-Wunsch): Widerstand #1f6feb, Support #58a6ff. **Beschriftung**
(2026-07-14): PWA-Achsenlabel „Widerstand"/„Unterstützung" (+Nummer ab 2);
MQL5 zusätzlich sichtbares OBJ_TEXT-Label je Linie („Widerstand 78.65",
~8 Kerzen vor dem rechten Rand, in Linienfarbe).
⚠ **Kein MT5-Terminal-Zeichnen aus Python:** das `MetaTrader5`-Paket hat KEINE
`object_create`/`OBJ_HLINE`/`chart_*`-Funktionen (nur MQL5). S/R-Linien daher im
PWA-Chart. **Für das Desktop-Terminal: Datei-Bridge** — `engine._write_levels_file`
(im `_run_analysis`, ~5 s, `[trading] export_mql5_levels=true`) schreibt atomar
`<terminal>\MQL5\Files\sr_levels.csv` (`SYM;…` / `R;preis` / `S;preis` / **`CH;<U|M|L>;
t1;p1;t2;p2`** = Regressionskanal-Anker / **`LQ;<B|A>;preis;size;<U|D>;<1|0>`** = ECHTE
Liquiditäts-Wand + Pfeilrichtung + Dominanz (⚠ seit MQL5 v1.14 zeichnet der
Indikator davon NUR noch den PFEIL, rechtsbündig am aktuellen Rand — Linie und
Text-Label sind auf User-Wunsch raus, Details stehen im Tooltip) / **`LT;t1;p1;t2;p2;<U|D>`** =
Liquiditäts-Trendlinie, s. u.); Quelle **`_draw_levels`** (S/R) +
**`structure.channel_line`** (Kanal) + **`self.hl_walls`** (Liquidität),
Indikator-Farben blau DodgerBlue/DeepSkyBlue, Wände grün/orangerot.
⚠ **Chartmuster-Linien (`PT;…`) 2026-07-30 auf User-Wunsch ENTFERNT** („entferne die
chartmuster linien aus dem mt5" — Chart war zu voll); die Muster-Kachel + das
PWA-Chart-Overlay bleiben unverändert, nur der MT5-Export/Indikator-Handler ist raus.
**ECHTE Liquiditäts-Wände im MT5-Chart (User-Wunsch 2026-07-30 „zeichne mir die
liquiditätslinien in den mt5 ein"):** `core/hl_walls.py` holt vom **HL-Dashboard**
(`GET http://127.0.0.1:8001/api/walls`, `[trading] export_hl_walls=true`) das
**größte L2-Orderbuch-Level je Seite** — die einzigen Linien im Chart, die NICHT aus
Pivots geschätzt sind, sondern **tatsächlich ruhende Orders** zeigen (Pepperstone =
Market Maker, liefert kein Buch). ⚠ **Basis-Korrektur zwingend:** HL `xyz:CL` notiert
**~0,45 $ unter** dem CFD (anderer Kontrakt/Contango) — die Umrechnung passiert
serverseitig im HL-Projekt (Median über 60 Snapshots), hier kommt schon
`bid_mt5`/`ask_mt5` an; fehlt die Basis, wird NICHT gezeichnet. ⚠ **`127.0.0.1`, nicht
`localhost`** (IPv6-Auflösung → 2066 ms statt 1946 ms, lief in den 2-s-Timeout) und
der HL-Endpoint liest **nur seine DB** (live-`fetch_l2_book` dauerte ~2 s = Timeout).
**Pfeil + Liquiditäts-Trendlinie (User-Wunsch 2026-07-30, Indikator v1.13):**
(a) Jede Wand bekommt einen **Pfeil in ihrer Wirkrichtung** (Wingdings 233/234) —
Lesart **„die Wand HÄLT"**: Nachfrage-Wand stützt von unten (▲), Angebots-Wand
deckelt (▼); die **größere** der beiden wird dicker gezeichnet + „<<" im Label
(`dominant` aus dem Endpoint). ⚠ **Die Pfeilrichtung ist INTERPRETATION, nicht
gemessen** — die Gegenthese („Liquidity Grab": der Kurs läuft ZUR Wand, um sie
abzuräumen) ist genauso verbreitet. Welche stimmt, beantwortet `analyze_bookflow.py`
(Frage 3) nach genug Daten; bis dahin ist der Pfeil eine Lese-Hilfe, kein Signal.
(b) **Pinke Liquiditäts-Trendlinie** (`OBJ_TREND`, nach rechts verlängert,
`InpShowLiqTrend`/`InpLiqTrendColor`): lineare Regression über den **volumen-
gewichteten Wand-Schwerpunkt** `(bid_px·bid_sz + ask_px·ask_sz)/(bid_sz+ask_sz)`
der letzten **60 min** (≥20 Punkte nötig). ⚠ Sie zeigt etwas ANDERES als eine
Kurs-Trendlinie: **wohin das Gewicht der RUHENDEN Orders wandert**, nicht wohin der
gehandelte Preis läuft — steigt sie, ziehen die Käufer ihre großen Gebote hoch.
Zeiten werden im Bot in **Broker-Zeit** umgerechnet (`trader._broker_offset_s()`),
weil MT5-Charts die brauchen; Preise sind bereits basis-korrigiert.
Fail-safe: HL-Server aus → keine Linien, kein Fehler (Log 1×, dann still), Cache 10 s,
Daten älter als 60 s werden verworfen. **REINE ANZEIGE** — ob HL-Buch-Signale auf den
CFD übertragbar sind, wird gerade gemessen (`analyze_bookflow.py` im HL-Projekt,
Datensammlung seit 2026-07-30, Urteil in ~2 Wochen). Der Indikator
**`mql5/SR_Levels.mq5`** (**v1.29** — `LQ`-Handler zeichnet seit v1.14 **nur noch
den PFEIL**, rechtsbündig am aktuellen Rand (in `RepositionLabels()` nachgeführt,
bleibt beim Scrollen rechts; Details im **Tooltip**) — Linie und Text-Label sind
auf User-Wunsch raus („Chart zu voll"). **v1.15 = CHART-THEME** (User „Farben im
MT5 wie im Bild"): `ApplyTheme()` setzt beim Start Hintergrund/Gitter/Kerzenfarben
auf die Dashboard-Palette — Bull **#3B82F6**, Bear **#EF4444**, BG **#0D1117**,
Gitter **#10151C** (dezent), Text #8B949E; Liquiditäts-Pfeile **Gold #F59E0B**
(= „Liquiditätszone" der Legende). ⚠ Das ändert das Chart **dauerhaft** (bleibt
auch nach Entfernen des Indikators) → `InpApplyTheme=false` schaltet es ab, alle
Farben sind einzeln als Inputs überschreibbar. Schalter: `InpShowWalls`/
`InpWallBidColor`/`InpWallAskColor`/`InpShowLiqTrend`/`InpLiqTrendColor`;
PT-Handler entfernt; in `MQL5\Indicators\`, **braucht
`OnCalculate` als No-op** sonst Compile-Error, F7-kompilieren, auf den Chart ziehen)
liest die S/R-Linien per Timer (OBJ_HLINE) UND den **M30-Regressionskanal als 3
diagonale Trendlinien** (OBJ_TREND, nach rechts verlängert, oben/unten gestrichelt/
Mitte gepunktet grau; `InpShowChannel`/`InpChanColor`, Default an) — Anker in
Broker-Zeit (= Server-Zeit im Terminal), zeichnet **nur bei Datei-Änderung neu**
(Inhalts-Cache `g_last` gegen Flackern). Der Kanal ist dieselbe M30-Definition wie
PWA-Overlay + Marktstruktur-Kachel; ⚠ nach dem Update den Indikator **neu F7-
kompilieren** (v1.10). Die dynamische Handels-Range (`DrawRange`, Hoch/Tief
der letzten `InpRangeBars` Kerzen) ist **per Default AUS** (`InpShowRange=false`,
User-Wunsch 2026-07-14) — bei Bedarf über die Indikator-Eingaben aktivierbar. (Die
statischen `[zones]`-Config werden NICHT mehr in die CSV geschrieben — waren stale
WTI-Level, füllten den Chart.)
**Architektur bewusst: Python = Gehirn (Analyse/Strategie/PWA/News/LLM/Reports),
MQL5 = nur dünne Zeichen-Schicht.** Kein Full-Rewrite in MQL5 (PWA/Backtests/
Integrationen wären dort nicht/kaum machbar). **Sym-Zeile:** Symbol links · rechts `hdr-clock`
(**Srv = Broker-Wallclock** aus `market.server_time` via `getUTC*`, **Lok** =
Browser) + **Session** (`#session`; zeigt 2122 Uhr lokal „🕘 PRIME-TIME" —
gemessen die stärksten Stunden — und bei Headline-Burst „📰 NEWS-STURM"/„viele
News" aus `news.storm_state()` → Snapshot `news_storm` {level: normal/elevated/
storm, fresh, base_per_h}; Headline-Rate letzte Stunde vs Basisrate, Basis-Fenster
min. 6 h verankert. Beides reine Anzeige) + Verbindungspunkt. Darunter Balance ·
**Tag P/L** · Equity. **Kein** Bid/Ask/Spread (Kurs steht in der Position-Kachel,
färbt sich nahe S/R). **Tag P/L** (`day-pl`) = realisiert heute
(`stats_overview("today")`) + offene Position; grün/rot
(`day_pl{realized,open,total}` im Snapshot).
- **Gesamtempfehlung** (`#card-verdict`, ganz oben): grafische Aggregation aller
Module — Konfidenz-Ring (conic-gradient = `conf_pct`), Bias-Balken mit Nadel
(SHORT↔LONG) und Modul-Chips (Welle/M30/H1/KI/Elliott/**Squeeze**, je grün/rot/grau; **TU
entfernt**). ⚠ **Bias-Fix 2026-07-15:** stimmberechtigt nur Module MIT Aussage —
Squeeze (nur `active`), Elliott (nur mit Ziel), KI (nur mit Bias) haben sonst
**Gewicht 0** (vorher dämpfte das fast immer neutrale Squeeze-w=2,0 die Nadel
permanent ~×0,8); `agree/total` zählt nur noch die ANDEREN stimmberechtigten
Module (Welle zählte sich selbst mit).
**Bias-Fix ZWEITER TEIL 2026-07-30 (Review „Gesamtempfehlung komplett prüfen"):**
Die Regel „ohne Aussage = Gewicht 0" galt bis dahin für Squeeze/Elliott/KI, **NICHT
für die Welle selbst** — sie stand mit w=3,0 permanent im Nenner, auch bei WARTEN.
Das wog schwer: **~91 % der letzten 2000 Empfehlungen sind WARTEN** (Range-Regime),
die Nadel war also fast immer künstlich zur Mitte gezogen (realer Fall: bias
**+0,312 statt +0,625**, Faktor ~2). Jetzt `weight=0` bei WARTEN. **Zweiter Fix:**
`agree` nutzte als Referenz immer die Wellen-Richtung → bei WARTEN stand dort
zwangsläufig „0/N", obwohl sich die anderen Module einig waren (real „0/4" bei
2 einigen Modulen). Jetzt Referenz = Wellen-Richtung, ersatzweise die **Bias-
Richtung** (real: „2/3"). Beides REINE ANZEIGE — Order-Logik unberührt. Mit 6
synthetischen Szenarien verifiziert (alle einig · Rest dagegen · alles neutral ·
WARTEN+alle SHORT · Welle+Squeeze).
⚠ **Bias-Fix DRITTER TEIL 2026-07-31 (Anlass: User-Frage „H1 aus der Gesamt-
empfehlung entfernen, da wir jetzt mit M30 arbeiten?"):** Antwort **NEIN** — die
Prämisse vermischt zwei Dinge (auf M30 umgestellt wurde die **S/R-Level-Quelle**,
nicht die Zeitebenen-Logik; H1 war nie Basis-TF, `tf_max=M30`). Und die vermutete
**Doppelzählung ist gemessen schwach** (`verdict_votes`, n=7.074): bei gerichteter
Welle stimmt **M30 nur zu 14,9 %** mit ihr überein, H1 zu 47 %, M30 und H1
untereinander nur zu **33,8 %** — keine redundanten Stimmen (Reversals heben den
M30-Filter bewusst auf, und M30 ist meist gar nicht gerichtet). H1 fließt zwar auch
als Konfluenz-Bonus in die Konfidenz — aber in eine ANDERE Kennzahl (Konfidenz-Ring
vs. Bias-Nadel), nicht zweimal in dieselbe.
**DER ECHTE DEFEKT lag woanders und ist behoben:** `add("M30", …)`/`add("H1", …)`
hatten **Gewicht 1,5 UNBEDINGT** — auch bei Stimme 0. Derselbe Fehler wie beim
Wellen-Modul (Teil 2), hier übersehen. Eine 0-Stimme heißt „EMA-Abstand im Totband"
= **keine Aussage**, nicht „neutral". Gemessen: **M30 ist in 57,9 % der Fälle ohne
Aussage** (H1 nur 1,8 %) → Ø-Bias-Betrag **0,371 → 0,441**, Median-Verstärkung
**1,30×**, in **33,3 %** der Zeilen deutlich stärkere Nadel. Jetzt
`1.5 if vote != 0 else 0.0`; `others` filtert bereits auf `weight>0`, die
„x/y einig"-Zählung zieht mit. Live verifiziert: M30 „flach" → Gewicht 0,00,
bias 1,5/4,5 = **0,333** statt 1,5/6,0 = 0,250. REINE ANZEIGE.
**MODUL-AUDIT 2026-07-31 (User: „überprüfe alle Module auf Inkonsistenzen"):**
systematisch alle Verdict-Module gegen die Regel „keine Aussage → Gewicht 0"
geprüft, dazu Config-, Frontend- und Level-Quellen. **5 Befunde, alle behoben:**
**(1) KI-Copilot — der größte Rest des Musters.** Ein explizites „NEUTRAL" zählte
als echte Stimme mit Gewicht 1,0. Gemessen (`verdict_votes`, n=7.084): das ist
**in 93,1 % der Fälle** der Zustand (Elliott: 1,3 %) — also der Normalzustand des
Copiloten, kein abgewogenes Urteil. Damit hatte ausgerechnet das Modul mit der
schwächsten Beleglage (`analyze_verdict_calibration.py`: nicht robust prädiktiv)
den GRÖSSTEN dämpfenden Einfluss. Jetzt `1.0 if av != 0 else 0.0`.
**(2) Elliott** analog: Gewicht nur bei gerichtetem Ziel (1,3 % der Zeilen).
**Kumulierte Wirkung aller Gewichts-Fixes des Tages: Ø|bias| 0,370 → 0,441 →
**0,569** (= 1,54×), betrifft 79,5 % der Zeilen.** Live verifiziert: KI „NEUTRAL"
→ Gewicht 0,00, bias 2,5/4,5 = **0,556** statt 2,5/5,5 = 0,455.
**(3) Stale `[zones]` speisten die Wellen-Konfidenz — entfernt.** `_sr_levels` zog
beide Kanten jeder `[zones]`-Zone als S/R-Linien heran: **16 Kanten aus einem ~10 $
TIEFEREN Regime** (72,9077,00 bei Kurs 85,3). Aus dem CHART waren sie am 14.07.
schon entfernt worden, WEIL sie stale sind — in die Konfidenz liefen sie weiter.
Realer Audit-Fall: `market.sr` hatte GAR KEINE Widerstände (M15-Cluster sind in
frischen Trends oft leer) → der „nächste Widerstand" kam aus der `fvg`-Zone bei
**88,00** (2,66 $ weg), der echte lag bei **85,315** (0,03 $ weg). Der Abzug „dicht
unter Widerstand" (12) greift nur innerhalb 0,5×ATR und feuerte deshalb **nie**.
Entfernen ist keine Strategieänderung, sondern das Streichen von Fremdregime-Daten;
`parse_zones` bleibt in `config.py`, die ini-Sektion bleibt (reaktivierbar).
**(4) Tote Config entfernt** (Backup `oil_widget_config.ini.bak-2026-07-31`):
`[trading] trail_timeframe = M1` (nirgends gelesen, Name suggeriert fälschlich, er
steuere die Trailing-TF) und die komplette **`[setups]`-Sektion** (11 Schlüssel,
vollständig tot).
**(5) `raise StopIteration`** als Sprung innerhalb eines breiten `except Exception`
(Trade-Marker-Export) durch ein `if` ersetzt — funktionierte, wäre aber fragil,
sobald dort jemand Logging ergänzt (Phantom-Fehler im ~5-s-Takt).
**Sauber:** Frontend↔Backend (0 verwaiste Element-IDs), alle übrigen
Verdict-Gewichte, alle anderen Config-Schlüssel.
⚠ **KONSENS-ZEILE korrigiert (2026-07-31, User-Fund „4/4 Module — dabei haben wir
doch 9"):** In `vd-consensus` steckten ZWEI Fehler.
**(a) Falscher Bezug:** der Text lautete `… Module für ${sig}` mit der HEADLINE als
Bezug. Seit dem `ref`-Fix vom 30.07. zählt `agree` bei WARTEN aber die Übereinstimmung
mit der **Bias-Richtung** — deshalb stand real dort „stark LONG · 4/4 Module für
**WARTEN**". Die Richtung kommt jetzt als **`verdict.ref_dir`** vom Backend, statt im
Frontend aus `bias` nachgebaut zu werden (dort galt ein ±0,05-Totband, im Backend
`bias > 0` — die Nachbildung wäre am Rand auseinandergelaufen; dieselbe Sorte
Divergenz wie die fünf Deployment-Drift-Fälle desselben Tages).
**(b) Anderer Nenner als die sichtbaren Chips:** `total` zählt nur Module MIT Aussage
(Gewicht > 0, ohne die Welle). Nach den Gewichts-Fixes des Tages schweigen deutlich
mehr Module → „4/4" bei 9 sichtbaren Chips. Neu ist **`verdict.silent`** (Module mit
Gewicht 0) dabei, sodass die Summe aufgeht. Live: „**3 von 5 stimmberechtigten für
LONG (4 ohne Aussage)**" — 5 + 4 = 9 ✅. Bei gar keiner Aussage: „kein Modul mit
klarer Aussage". v=125.
**„WARUM WARTEN?" jetzt an der Headline (2026-07-31, v=126, User-Fund „WARTEN,
aber Konsens stark LONG"):** Der scheinbare Widerspruch entstand, weil die Welle in
aller Regel eine RICHTUNG hat und nur an einem Gate hängt — der Grund stand aber
ausschliesslich als Textzeile in der Meldungen-Karte, also weit weg von der Stelle,
an der die Frage aufkommt. `wave.breakout {pending, dir, level, need}` wurde berechnet
und gespeichert, vom Frontend jedoch **nirgends gerendert**. Jetzt hängt es direkt an
der Headline: „◌ WARTEN · M15 · **LONG, noch 0.02 bis zur Bestätigung**".
**Erste Auswertung der neuen `block_reason`-Telemetrie (3 h):** `breakout_pending`
**63 %** · `entry_room` 20 % · `min_conf` 18 % — und **kein einziges Mal** Totband
oder HTF-Gegen-Trend. Das beantwortet die offene Frage aus dem TF-Churn-Fix: die
93 % WARTEN kommen NICHT daher, dass die Welle richtungslos wäre, sondern von den
Gates — allen voran der Breakout-Bestätigung (die bis zum Fix desselben Tages
gar nicht fertig werden konnte).
**Offen (dokumentiert, nicht geändert):**
(0) **Wellen-Konfidenz aus `_draw_levels` speisen** statt aus dem Misch-Set — würde
den 12-Abzug tatsächlich auslösen, also Konfidenz senken und das 55%-Gate
verschieben. **Strategieänderung → messpflichtig.**
(a) **Drei divergierende Level-Quellen** seit der
M30-Umstellung: `_draw_levels` (M30 → Chart/S/R-Close), `pb_levels` (M5 → Entry-
Raum-Gate), `_sr_levels` (Misch-Set → Wellen-Konfidenz); real wichen die Supports
um 0,65 $ ab. Bewusst so (jedes Gate ist auf seine Quelle kalibriert), aber eine
Stolperfalle. (b) `tu` wird noch als Parameter durchgereicht, seit 2026-07-06 ungenutzt.
⚠⚠ **ERSTE AUSWERTUNG DER `verdict_votes` (2026-08-05, `analyze_verdict_modules.py`,
13.568 Zeilen 24.07.05.08.) — DIE GEWICHTUNG IST IN DER PRAXIS INVERTIERT.**
Nominal-Gewicht sagt fast nichts darüber, wie stark ein Modul die Nadel bewegt;
entscheidend ist, **wie oft es überhaupt mitredet**. Gemessener Anteil am
Gesamtgewicht, gemittelt über alle Zeilen:
| Modul | Nominal | stumm | **realer Einfluss** |
|---|---|---|---|
| **H1** | 1,5 | **2 %** | **38,5 %** |
| **Elliott** | 1,0 | **1 %** | **26,0 %** |
| M30 | 1,5 | 62 % | 12,2 % |
| Squeeze | 2,0 | 81 % | 7,0 % |
| Liq-Trend | 0,5 | 45 % | 6,1 % |
| Orderbuch | 0,5 | 62 % | 4,1 % |
| **Welle** | **3,0** | **91 %** | **3,8 %** |
| Muster | 0,25 | 77 % | 1,8 % |
| KI-Copilot | 1,0 | 97 % | 0,5 % |
⚠⚠ **DIESE TABELLE ALLEIN FÜHRT IN DIE IRRE — Korrektur am selben Tag.** Der
Anteil „über alle Zeilen" mischt zwei Dinge: die Gewichtung UND die Häufigkeit.
Entscheidend für die Frage „ist die Gewichtung kaputt?" ist der Anteil **wenn das
Modul spricht**:
| Modul | alle Zeilen | **wenn es spricht** |
|---|---|---|
| **Welle** | 3,8 % | **44,4 %** ← höchster Wert im Feld |
| H1 | 38,5 % | 39,3 % |
| Squeeze | 7,0 % | **37,3 %** |
| M30 | 12,1 % | 31,9 % |
| **Elliott** | 26,0 % | **26,2 %** |
| KI-Copilot | 0,5 % | 16,2 % |
| Liq-Trend / Orderbuch / Muster | 6,2 / 4,1 / 1,8 % | 11,1 / 10,8 / 8,1 % |
**Die Gewichtung ist NICHT invertiert.** Wenn die Welle spricht, dominiert sie
mit 44,4 % — genau wie entworfen; ihre 3,8 % im Alltag sind schlicht die 91 %
gewollte Stille der Gates. Dasselbe beim Squeeze (37,3 % wenn aktiv).
**Was BLEIBT, ist ein anderer Befund:** H1 und Elliott haben in beiden Spalten
fast denselben Wert (38,5/39,3 bzw. 26,0/26,2 %), weil sie **praktisch nie
schweigen**. Sie bilden einen **permanenten Hintergrund von ~65 %**, gegen den die
episodischen Module anschieben müssen. Der Ruhezustand der Nadel ist damit
faktisch „was H1 und Elliott sagen" — und Elliott ist über 1.163 Episoden flach.
⚠⚠ **DOKUMENTATIONS-FEHLER KORRIGIERT:** hier stand, Elliott habe „Gewicht nur bei
gerichtetem Ziel (1,3 % der Zeilen)". Die Daten sagen das Gegenteil —
**`elliott_w > 0` in 13.558 von 13.568 Zeilen (99,9 %)**. Der Fix vom 31.07.
(„keine Aussage → Gewicht 0") greift bei Elliott praktisch nie, weil praktisch
immer ein Ziel existiert. Elliott ist damit die **zweitlauteste Stimme im
gesamten Verdict**.
**Prädiktivität — Sichtung, KEIN Urteil** (Forward-Return 60 min, gegen die
Drift-Null, Minutentakt zu **Episoden** zusammengefasst, sonst zählt man dieselbe
Marktlage hundertfach): Elliott **0,031/0,020 bei 49 % Treffern über 1.163
Episoden** — die mit Abstand größte Stichprobe, und sie ist flach. Welle und M30
kippen zwischen den Hälften; KI-Copilot und Squeeze sind beidseitig positiv, aber
mit 53 bzw. 100 Episoden dünn. H1 hat nur **19** Episoden (es wechselt selten) —
prädiktiv nicht beurteilbar, sein Einfluss-Anteil ist aber reine Arithmetik und
damit belastbar.
**Grenzen:** 12 Tage = EIN Regime; die Reminder-Schwelle steht bewusst bei
25.000 Verdicts. **Die Einfluss-Tabelle ist der robuste Teil** (Auszählung über
13.568 Zeilen), die Return-Spalten sind der verrauschte.
**Nichts geändert** — die Bias-Nadel ist **reine Anzeige**; die Order steuert
die Welle über die Headline, nicht der Konsens. Eine Umgewichtung wäre eine
Anzeige-Änderung mit Wirkung auf die MANUELLE Entscheidung und damit
messpflichtig, sobald genug Verdicts vorliegen.
Formel/Details: `docs/gesamtempfehlung.md`.
Headline = **validierte Welle** (steuert Order) + **Zeitebene** (`verdict.tf`,
z. B. „▲ LONG · M5" — TF, für die die Empfehlung gilt); Rest = nur Konsens-Anzeige.
Backend `engine._verdict``verdict` im Snapshot; Render `renderVerdict`
(`app.js`). Details: `docs/gesamtempfehlung.md`.
**⚠ Konfidenz ist NICHT kalibriert (gemessen `analyze_verdict_calibration.py`,
2026-07-24, „Gesamtempfehlung verbessern"-Paket):** conf_pct gegen Forward-Return
(2 h, ×ATR, 1604 gesampelte Signale aus `recommendations`, 2 Hälften) — H1 (MaiJun)
perfekt monoton (conf 85100: WR 68 %/ØR +1,13; <55 klar negativ), **H2 (JunJul)
komplett INVERTIERT** (conf 85100: WR 40 %/ØR 0,90; mittlere Bins besser). Hohe
Konf = starker Trend + Konfluenz — im Chop-/Hochvola-Regime sind genau das die
Überdehnungen vor der Wende. **Konsequenzen:** conf-Anzeige NICHT als P(Erfolg)
aufwerten; **Konfidenz-Sizing-Idee VERWORFEN** (hätte in H2 die schlechtesten
Signale am größten gesized); Ring bleibt, was er ist (Score-Aggregat). Copilot-Bias
in derselben Messung nicht robust prädiktiv (n klein bzw. ØR ~0) — für die
datenbasierte Entscheidung (behalten/Gewicht/raus wie TU) loggt jetzt
**`verdict_votes`** (neue DB-Tabelle, `history.log_verdict_votes`, 1×/60 s aus
`_run_analysis`: headline/conf/bias/tf + Modul-Votes Welle/M30/H1/KI(+w)/
Elliott(+w)/Squeeze + news_score) — Auswertung nach ein paar Wochen analog
`analyze_verdict_calibration.py`.
**Live-Kosten-Chip (2026-07-24):** Snapshot `cost_ratio` = (askbid)/ATR_M5
(`pb_feats.atr`, Floor 0,06) → in der `vd-consensus`-Zeile „Kosten 0,14×ATR ✅
günstig / · normal / ⚠ teuer" (Schwellen 0,20/0,32 aus `backtest_realcosts.py`:
Ø 0,265, Nacht 0,320,50 = Falle, 1517 Uhr ~0,13). Reine Info — die ehrliche
Alternative zum abgeschalteten Dead-Hours-Gate.
**Ausrichtungs-Split im Order-Dialog (2026-07-24, Verhaltens-Hebel/B0):** Der
„trotzdem eröffnen?"-Dialog zeigt jetzt die EIGENEN Zahlen aus der DB
(`history.alignment_stats(40)` → Snapshot `alignment`, Engine-Cache ~120 s):
„📊 Deine Trades OHNE Signal: 42 % Trefferquote · 423 € (n36) / Mit Signal: …"
(Gruppen <5 Trades werden ausgeblendet). Real bei Einbau: 36 der letzten 40
Trades liefen OHNE Signal-Deckung mit 423 € — exakt die dokumentierte
Kern-Leckage, jetzt im Moment der Entscheidung sichtbar.
**News-Konflikt-Chip (2026-07-24, Auslöser 23.07.: zwei Shorts gegen einen
+0,98-Sentiment-Burst):** Snapshot `news_sentiment` (Score 1..+1 aus
`news.sentiment`); Frontend `#news-conflict` (Meldungen-Karte, bernstein) warnt
bei |Score|≥0,5 gegen offene Position bzw. aktives Signal („📰 News-Flow stark
bullisch (+0,98) — steht gegen deine SHORT-Position"). Reiner Kontext —
Prädiktivität unbelegt (Kalibrier-Messung: H2 nur n=4/6); dafür wird
`recommendations.news_score` seit 2026-07-24 wieder BEFÜLLT (war auf None
degradiert) → in ein paar Monaten messbar.
- ⚠⚠ **ZWEI NEUE TABS: „SIG Live" und „SIG Anzeige" (User-Vorgabe 2026-08-05,
v=141).** Sortiert wird nach **WIRKUNG, nicht nach Thema** — und die Zuordnung ist
**am Code prüfbar**, nicht Geschmackssache: `engine._verdict` ruft
`add(name, vote, gewicht, …)`; wer ein Gewicht > 0 bekommt, verschiebt die
Bias-Nadel und gehört nach „SIG Live".
| Tab | Karten | Begründung |
|---|---|---|
| **SIG Live** | KI-Copilot · Chartmuster | Verdict-Gewicht **1,0** bzw. **0,25** (`_PAT_W`) |
| **SIG Anzeige** | Marktstruktur · Kerzen-Anatomie | gemessen **ohne** Einfluss, als Signal verworfen |
**„Gewicht" heißt NICHT „belegt".** Der KI-Copilot hat Stimmrecht, ist aber
gemessen nicht robust prädiktiv — genau deshalb ist die Trennung nach Wirkung
sinnvoll: sie zeigt, was die Empfehlung TATSÄCHLICH bewegt, unabhängig davon, wie
gut es belegt ist. Chartmuster steht aus demselben Grund in „Live", obwohl die
Klasse als Signal verworfen wurde — seit dem 2026-08-01 hat sie 5 % Stimmanteil.
**Auf dem Dashboard BLEIBEN** Trade-Leiste, Gesamtempfehlung, Meldungen und
Statistik (live). Die „Meldungen"-Karte enthält zwar einflussreiche Teile
(Squeeze-Stimme, S/R-Close-Hinweis, Flip-Close), ist aber der **Live-Meldungsstrom
zum laufenden Trade** — sie gehört an die Stelle, an der gehandelt wird, nicht in
einen Modul-Tab. Wer das anders will, verschiebt `#card-pos`.
Umgesetzt als reiner Markup-Umzug (`showView` bildet `data-view="siglive"` auf
`#view-siglive` ab, keine JS-Änderung nötig); CSS-Regel `#view-siglive > .card`
analog zum Statistik-Tab, weil beide außerhalb des `<main>`-Grids liegen.
Verifiziert am ausgelieferten HTML: 7 Tabs, 7 Views, keine doppelten IDs, keine
verwaisten JS-Referenzen.
- **Karten** (Dashboard, von oben): Gesamtempfehlung · **Meldungen** · Marktstruktur ·
KI-Copilot (jetzt im Tab „SIG Live") ·
**Statistik (live) ganz unten**.
⚠ **Drei Karten am 2026-08-05 in den Statistik-Tab verschoben (User-Vorgabe,
v=138): „Manuelle Trades" (`#card-manual`), „Letzte Trades" (`#card-trades`) und
„Kurslücken" (`#card-gaps`)** — sie sind Rückschau, das Dashboard soll das
Handlungsrelevante zeigen. **Reiner Umzug im Markup**, keine Logik berührt: die
drei werden weiterhin von `render()` aus dem Snapshot gefüttert und hängen NICHT
an `loadStats()`; ein ausgeblendeter View ist nur `display:none`, die Elemente
bleiben im DOM (sonst wären ihre IDs beim Rendern weg — genau die Null-Falle aus
der Cache-Regel). ⚠ Dazu nötig: **`#view-stats > .card{margin:11px 10px}`** in
`style.css` — das Dashboard ist ein `<main>`-GRID mit eigenem Padding, `#view-stats`
eine schlichte `<section>`; ohne die Regel klebten die Karten randlos aneinander
und `.card.wide{grid-column:1/-1}` liefe ins Leere. Verifiziert am ausgelieferten
HTML (alle drei nach `id="view-stats"`, `#card-stats-live` bleibt im Dashboard),
keine doppelten IDs, Tag-Balance geprüft.
(Tagesziele-Kachel **entfernt** 2026-07-13. **Welle-Karte komplett entfernt
2026-07-15** — TF-Ampel wanderte in die Gesamtempfehlung, die Wellen-Meldungen
[Squeeze/Überdehnung/Setup/Lauf/Gründe] in die **„Meldungen"-Karte** (`#card-pos`,
ex-„Position"); das Wave-Signal ist eh die Verdict-Headline. **Trailing-Karte
ausgeblendet 2026-07-22** — `#card-trail.hidden` per CSS, User-Vorgabe; Backend/
Trailing-Logik + Snapshot-Felder `trail.*` unverändert aktiv, nur die Anzeige raus,
wieder einblendbar durch Entfernen der `hidden`-Klasse in `index.html`.)
- **Auto-Squeeze-Monitor (B4)** (`#sqm-box`, oben im **Statistik-Tab**, 2026-07-20):
zwei Blöcke — **Entry-Edge · Mechanik** (`engine.squeeze_monitor` rechnet die Squeeze-
Regel mit Live-Params auf ~30 Tagen M5 NEU, Backtest-Exit → **isoliert den Einstieg**,
ØR/PF gegen Erwartung **+0,14…+0,23 · PF>1**) und **Live-Trades · echt** (SQUEEZE_*-
Trades aus der DB, Gesamtsystem inkl. realer Exits: n/WR/Ø-Gew/-Verl/Verhältnis/ΣEUR/
Tag-Nacht; ØR-Näherung via SL-Distanz). Verdict-Badge (trägt/dünn/**driftet→B5**) aus
der Mechanik. **Kernaussage der Ansicht:** klaffen Mechanik (Edge lebt) und Live
(echtes Geld) auseinander → es liegt am **Exit/Verhalten, nicht am Signal** (real
2026-07-20: Mechanik ØR **+0,414**/PF 1,64, Live ØR 0,07/2,41 € — der alte Notfall-
Stop + Nacht-Trades fraßen den Edge, jetzt behoben → sollte konvergieren). Endpoint
`/api/squeeze_monitor` (Cache 60 s), lädt beim Öffnen des Statistik-Tabs (`loadStats`).
- **Statistik-live-Kachel** (`#card-stats-live`): kompakt Woche + Gesamt —
Trades, WR, PF, Ø-Gewinn/-Verlust, **Verhältnis** (Øgew/Øverl, <1 = Verlust-
Schiefe), PnL. Quelle `engine._compact_stats``snapshot.stats_compact`. Zum
objektiven Verfolgen der Stichprobe (statt Bauchgefühl pro Einzeltrade).
**TradersUnion- und Elliott/FVG-
Einzelkacheln ausgeblendet.** Elliott/FVG fließt weiter in die Gesamtempfehlung
(`_verdict`); **TU ist ganz raus aus Empfehlung, Verdict UND KI-Copilot-Kontext**
(`agent._tool_market` ohne `tradersunion`/`tu_consensus` — war die letzte Hintertür,
da der Copilot eine Verdict-Stimme ist; nur noch Snapshot-Anzeige).
- **TF-Wende-Ampel** (`#wave-turns`, jetzt IN der Gesamtempfehlung): Chips
**M1/M5/M15/M30/H1** (H1 neu 2026-07-15 — im `turn_data`-Fetch, aber NICHT im
Bounce-Scan, der bleibt M1M30), je grün(auf)/rot(ab) nach **EMA12/50-Stand**;
**frische Kreuzung (≤3 Bars)** blinkt + zeigt ⟳ + Richtungspfeil + Bars-seit-Wende.
Quelle: `wave.tf_turns`
(Backend `WaveRecommender._turn_state` mit **Totband** `_TREND_DEADBAND×ATR`
kein Geblinke bei seitwärts laufenden EMAs). 4-TF-Fetch ist **gedrosselt (~15 s)**
via `_turn_due` (spart Lock-Zeit; letzter Stand in `_last_tf_turns` gehalten).
REINE Anzeige — ändert die Empfehlung NICHT.
- **Marktstruktur-Kachel** (`#card-structure`, 2026-07-20, User-Idee „Pro-Chart-
Setup-Erkennung"): zeigt die Price-Action-Struktur auf **M30** — Swing-Folge
**HH/HL/LH/LL** (Chips grün=bullisch/rot=bärisch), letzter **BOS** (Break of
Structure, Richtung + gebrochenes Level + Bars her) und den **Regressionskanal**
(Richtung + Position des Kurses im Kanal 0100 % als Balken+Punkt). Quelle
`core/structure.py` (`MarketStructure`, Pivots k=3, Kanal = LS-Regression über 60
Bars ± max. Abweichung, ~30 s gedrosselt im Trend-Loop unter `mt5_lock`), Snapshot
`structure`. **REINE ANZEIGE — kein Trade-Trigger, kein Verdict-Gewicht, KEINE
Prognose/Pfeil.** **Signalvariante DIREKT gemessen & VERWORFEN (`backtest_structure.py`,
2026-07-20, 60k M30-Bars/5 J, 2 Halbjahre, dieselbe structure.py-Logik kausal):**
ALLE Varianten in BEIDEN Hälften negativ — (A) Kanal-Pullback „Kauf am unteren
Kanaldrittel/Demand-Zone" = der orange Pfeil: ØR 0,05…−0,11, PF<1, WR 4043 %
(der Kanalrand HÄLT nicht zuverlässig — antizyklisch am Extrem = Münzwurf, P(break)-
Klasse); (A2) +Swing-Filter (HH/HL) macht es nicht besser (0,06/0,07); (B) BOS-
Continuation = breakeven-negativ (0,011/0,005, Momentum-Klasse). Damit ist auch
die Struktur-Idee empirisch durch — die Kachel bleibt Ist-Zustand-Anzeige (wie
TF-Ampel/Squeeze), steuert nichts.
- **NACHTEST über ALLE Zeitebenen = ERNEUT VERWORFEN (`backtest_structure_tf.py`,
2026-08-03, User-Beobachtung im M5-Chart: „der Kurs folgt dem Kanal und bricht die
untere Linie nicht — können wir das nutzen?"):** Die alte Messung lief NUR auf M30,
M5 war streng genommen ungetestet. Jetzt mit dem **kanonischen Live-Exit**
(`exit_model.LIVE`: Trail 1,0 + Time-Stop + TP — die alte Datei nutzt eine Exit-KOPIE
mit Trail 1,5 OHNE Time-Stop) über M5/M15/M30, zwei Lesarten des M5-Kanals und drei
Regel-Varianten, 80k Bars je Zeitebene, 2 Hälften, Echtkosten:
| Zeitebene / Kanal | X=0,20 | X=0,30 | +Swing |
|---|---|---|---|
| **M5, 60 Bars** (=5 h, „was man im Chart sieht") | **0,201/0,017** | 0,170/0,054 | 0,161/0,082 |
| M5, 360 Bars (=30 h, zeitgleich zu M30) | 0,206/0,017 | 0,179/0,054 | 0,159/0,082 |
| M15, 120 Bars | 0,032/0,003 | 0,068/0,034 | 0,043/0,033 |
| M30, 60 Bars (die ALTE Messung) | 0,103/0,019 | 0,094/0,013 | 0,090/0,024 |
**24 Zellen, ALLE negativ**, PF 0,660,99. Drei Befunde:
(a) **Trefferquote 3743 %** über 1.2002.400 Berührungen je Hälfte — der Kanalrand
hält in WENIGER als der Hälfte der Fälle. Die im Chart sichtbaren „Halter" sind
Selektion: die Durchbrüche sehen hinterher nicht mehr nach Kanal aus.
(b) **M5 ist die SCHLECHTESTE Zeitebene** (0,201 vs. 0,103 auf M30) — feiner
auflösen erzeugt mehr Berührungen, und jede kostet Spread.
(c) **Keine Parameterfrage:** 60-Bar- und 360-Bar-Kanal liefern praktisch IDENTISCHE
Zahlen. Zwei so verschiedene Fenster mit gleichem Ergebnis → es liegt am Mechanismus.
**Legacy-Recheck-Bonus:** die M30-Zeile **reproduziert** den Befund vom 20.07.
(0,103/0,019 gegen damals 0,05…−0,11) — der alte Schluss war NICHT durch das
abweichende Exit-Modell verzerrt.
**Mechanismus:** Der Kanalrand ist eine Regressionslinie durch die letzten N Closes —
dahinter liegt **nichts**: keine Order, kein Pivot, keine Struktur. Er hält nur,
solange der Trend ohnehin läuft. Genau darin liegt der Unterschied zu **P(break) auf
einem echten S/R-Level** (Pivot = Stelle, an der real gehandelt wurde), das
out-of-sample kalibriert ist (AUC 0,65 bzw. 0,680,72). „Linie im Chart" ist nicht
„Struktur im Markt". **19. verworfener Signal-Eingriff.**
- **⚠⚠ P(break) LIVE-AUSWERTUNG (`analyze_pbreak_live.py`, 2026-07-31, 2275 ausgewertete
Vorhersagen seit 23.07.): DAS MODELL HÄLT LIVE NICHT, WAS DER BACKTEST VERSPRACH —
B5-Fall.** Fällige Messung aus dem Reminder, jetzt erledigt.
| | Gesamt | M5-Level (bis 29.07.) | M30-Level (ab 30.07.) | entkoppelt |
|---|---|---|---|---|
| n | 2275 | 1850 | 425 | 307 |
| **AUC** | **0,539** | 0,570 | **0,399** | **0,443** |
| Treffer Modell | 58,5 % | 58,7 % | 57,6 % | 56,7 % |
| „immer Abprall" | 59,0 % | 57,7 % | 64,7 % | 59,9 % |
| Überschuss | 0,5 Pp | +1,0 Pp | 7,1 Pp | 3,3 Pp |
Backtest-Erwartung war **AUC 0,71**. **Kalibrierung systematisch nach unten verzerrt:**
Bucket 019 % (n=1234, über die Hälfte!) sagt **8 % voraus, real 39,1 %**; Bucket
65100 % sagt 74,5 %, real 48,0 % (invertiert). Im Mittel prognostiziert das Modell
**23 % Durchbruch, real sind es 41 %**, und es sagt nur in **8 % der Fälle „break"**.
**DER KERNBEFUND — das Gate gatet nicht:** die **Bruchrate der GESCHLOSSENEN Gruppe
ist bei JEDER Schwelle 3840 %**, also identisch zur Basisrate (thr 20 → 39,1 % ·
thr 35 → 37,8 % · thr 55 → 40,5 %). Es findet live keinerlei Auswahl statt. Bei der
ini-Schwelle 0,55 werden **91,2 % aller Level-Berührungen geschlossen** → der
„P(break)-gegatete Close" ist faktisch zum **pauschalen S/R-Close** degeneriert, und
DER ist 2× gemessen und verworfen (`backtest_srclose.py`: WR verdoppelt, PF/ΣR in
beiden Hälften schlechter = Gewinner-Kappen). **Das erklärt die kontrafaktische
Live-Messung vom Vortag** (`analyze_srclose_live.py`: 8 €/Trade, 43 % klar zu früh
geschlossen, nur 26 % klar richtig) — Symptom und Ursache passen zusammen.
⚠⚠ **URSACHE GEFUNDEN (2026-07-31, `backtest_pbreak_touches.py` + Rekonstruktion aus
`candles_m1`): das Modell ist NICHT verfallen — es bekommt live eine ANDERE
Merkmalsverteilung, als es trainiert wurde.** Beweiskette in drei Schritten:
(1) **Der Backtest reproduziert die 0,71** (M30-Level, 4 Merkmale, Fit H1 → **AUC
out-of-sample H2 0,715**) → Modell und Pipeline sind in Ordnung.
(2) **Die Populations-Hypothese ist widerlegt:** live nach Berührungszahl
aufgeschlüsselt ist ausgerechnet die 1. Berührung (= die trainierte Situation) mit
**AUC 0,445 die SCHLECHTESTE** Gruppe, die Chop-Fälle (5.+) mit 0,577 die beste.
Der Versatz „Modell sagt 2325 %, real 4042 %" ist in JEDER Teilmenge gleich groß
(~17 Pp) — Signatur eines Merkmals-Problems, nicht eines Edge-Verfalls.
(3) **Merkmale aus `candles_m1` rekonstruiert (n=2275) — der Versatz ist messbar:**
| Merkmal | Live-Mittel | Training-µ | Differenz |
|---|---|---|---|
| mom3 | 0,149 | 1,4523 | **1,303** |
| mom6 | 0,181 | 1,6565 | **1,476** |
Daraus rechnet sich der z-Versatz exakt: mom3 → 1,433×(1,303)/2,418 = **0,772**,
mom6 → **0,275**, zusammen **1,05**. Aus der Trainings-Basisrate 39,4 % (z=0,429)
wird damit ~19 % — beobachtet werden 23 %. **Erklärt praktisch die gesamte
Fehlkalibrierung.**
**DER MECHANISMUS:** Im Training ist der Touch-Bar `jt` der Moment, in dem der Kurs
nach einem ECHTEN ANLAUF (Level bei Entry ≥0,3×ATR entfernt fixiert) zum ersten Mal
ans Level stößt → mom3 ≈ 1,45×ATR = ein Schub. **Live wählt `_draw_levels` das
nächstgelegene Level JEDE SEKUNDE NEU** (mit Hysterese) — der Kurs steht also oft
neben einem Level, das gerade erst zum nächstgelegenen WURDE, ohne je darauf
zugelaufen zu sein → mom3 ≈ 0,15. Gleicher Code, gleiche Formeln, aber **Training =
Level bei Entry fixiert, Live = Level wandert mit.** Konsequenz: das Modell antwortet
live konsistent „kein Schub → kein Durchbruch → 23 %", liegt damit IMMER unter der
Schwelle 0,55 → **das Gate war seit Inbetriebnahme faktisch nie aktiv.**
**Zwei Reparaturwege (beide ungemessen, erst prüfen):** (a) Ziel-Level beim Öffnen
der Position FIXIEREN statt laufend neu zu wählen (macht Live = Training), oder
(b) das Modell auf einer live-spiegelnden Stichprobe NEU trainieren (bei jedem Bar
im 0,15×ATR-Band um das dynamisch nächste Level sampeln, ohne Anlauf-Bedingung).
**TOUCH-ZAHL im Backtest (2 Halbjahre, `backtest_pbreak_touches.py`):** hebt die AUC
nur von **0,715 auf 0,725**, Gewicht +0,176 für die 2.4. Berührung (User-Vorzeichen
bestätigt), aber der **R-Ertrag ist nicht robust** (H1 +131, H2 43). ⚠ Im Trainings-
Datensatz sind **76 % Erst-Berührungen und es gibt KEINE 5+-Fälle** (live sind 63 %
fünfte oder später) — das live beobachtete umgekehrte U lässt sich dort nicht einmal
abbilden. Kleine Verbesserung, nicht der Hebel.
**Pipeline gegen das Training geprüft, KEIN Bug gefunden:** `dist` ist in beiden
entry-basiert (`abs(levelentry)/atr`, `backtest_srclose_prob.py:86`), `mom6`/`mom3`
in beiden am Touch-Bar `jt`, `confirm`/`reject` dieselbe ±0,5×ATR-Definition, Timeout
zählt in beiden als Abprall. Plausibelste Ursache = **andere Stichproben-Population**:
live werden 2275 Berührungen geloggt, entkoppelt (max. 1 je Level und Stunde) bleiben
nur **307** — dieselbe Position pendelt vielfach um dasselbe Level, live dominieren
also Chop-am-Level-Fälle. ⚠ Das entlastet das Modell NICHT: entkoppelt ist die AUC mit
0,443 sogar schlechter. ⚠ Der M30-Teil (AUC 0,399) ist mit n=425 über 2 Tage zu klein,
um die Umstellung als Ursache zu belegen — die Wiedervorlage (≥30 S/R-Closes auf
M30-Leveln) läuft weiter.
**TOUCH-ZAHL als fehlendes Merkmal (User-Idee 2026-07-31 „je öfter der Kurs die Linie
berührt, desto wahrscheinlicher der Durchbruch") — auf den Live-Daten BESTÄTIGT, aber
mit Umkehr:** Bruchrate nach n-ter Berührung desselben Levels im 30-min-Fenster, in
BEIDEN Live-Hälften dasselbe Muster — 1. Berührung 38,3/41,7 % · 2. 40,8/45,6 % ·
3. 43,2/45,1 % · **4. 45,1/47,4 %** (monoton, +6/+4 Pp über Basis) · **5.+ fällt
zurück auf 37,8/41,8 %** (UNTER Basis, und mit n≈1400 die größte Gruppe). Also ein
**umgekehrtes U**, kein monotoner Anstieg: 24 Berührungen = Level wird mürbe, ab 5 in
30 min = Range, das Level HÄLT. ⚠ Das Modell sieht davon **nichts** (sagt über alle
Berührungszahlen konstant ~23 % voraus) → echte orthogonale Information. ⚠ Belastbarkeit
begrenzt: die beiden „Hälften" liegen nur 4 Tage auseinander (kein Regimewechsel),
Buckets n=82139; und `backtest_srbreak.py` fand die Touch-Zahl früher „nicht robust"
(allerdings ohne Fenster-Definition und ohne die 5+-Umkehr). **Nächster Schritt: als
5. Merkmal in `backtest_srclose_prob.py` aufnehmen und über die 34.771 Touches / 2
echten Halbjahre neu fitten** — dort ist messbar, ob die AUC steigt.
**✅ BEHOBEN am selben Tag (Weg b, User-Entscheidung): MODELL NEU TRAINIERT.**
`backtest_pbreak_retrain.py` erzeugt eine **live-spiegelnde Stichprobe** — Level
dynamisch mit Hysterese wie `_draw_levels`, gesampelt an JEDEM Bar im 0,15×ATR-Band
(KEINE Anlauf-Bedingung), Ausgang/Fenster wie live. Kontrolle: die Merkmalsmittel
dieser Stichprobe (mom6 0,203 · mom3 0,202) treffen die live rekonstruierten Werte
(0,181 · 0,149) — sie bildet den Live-Pfad also wirklich ab.
| | ALT-Modell | NEU-Modell |
|---|---|---|
| AUC auf live-spiegelnder Stichprobe (H2, oos) | **0,368** | **0,654** |
| Ø-Vorhersage vs. real | 19,2 % vs 39,4 % | kalibriert |
**R-Ertrag (`backtest_pbreak_rvalue.py`, Echtkosten, Baseline = nur Trailing):** NEU
schlägt ALT in **BEIDEN Hälften bei JEDER Schwelle**; gegen die Baseline gewinnt es
bei 0,35/0,45/0,55 in beiden (bei 0,60 kippt H1). **Bestwert 0,35** — H1 149 vs
Baseline 506 (**+357**), H2 +1308 vs 110 (**+1418**). ⚠ Nebenbefund: das ALTE
Modell war in H1 **schlechter als gar kein Auto-Close** (653 vs 506), in H2 besser
(+576 vs 110) — genau die Regime-Abhängigkeit, die live als 8 €/Trade sichtbar war.
**Eingebaut:** neue `_PB_MU/_SD/_W` in `engine.py` (final auf allen 80k Bars gefittet,
n=76.542, Basisrate 37,8 %, nachdem H1→H2 validiert war; alte Werte als Kommentar für
den Rückweg) + **`sr_close_pbreak=0.35`** (Config-Wächter-Anker mitgezogen).
Verifiziert: Chart-Linien zeigen jetzt 43 %/36 % statt 815 %.
⚠⚠ **DAS NEUE MODELL INVERTIERT EINE ALTE PROJEKT-REGEL — bewusst so:** das Gewicht
von mom3 dreht das Vorzeichen (**+1,4330 → 0,5420**). Der alte Befund
(`backtest_srbreak.py`) lautete „Kurs KRIECHT ans Level → P(break) nur 26 %, mit
Schwung → laufen lassen (4143 %)". Das neue Modell sagt das Gegenteil: Anlauf-Schub
→ 26,5 % (das Level ABSORBIERT den Schub und hält), Schwung weg vom Level → 51 %.
**Beides gilt in seiner eigenen Population** — die alte Messung betraf den Anlauf zu
einem FIXIERTEN Level, die neue das Verweilen an einem DYNAMISCH gewählten. Für den
Live-Pfad gilt die neue Lesart; die alte Faustregel weiter oben im Text bezieht sich
auf die Anlauf-Situation und ist dort nicht widerlegt.
**Weiterhin offen:** auch das neue Modell schließt bei 0,35 noch ~9497 % der
Positionen (es wählt vor allem den BESSEREN Moment, ist also kein wirklich selektives
Gate). Und die **Touch-Zahl bringt im neuen Modell nichts mehr** (AUC 0,652 vs 0,654)
— die dynamische Level-Wahl erfasst denselben Effekt bereits; die User-Idee hat auf
den Fehler GEZEIGT, die Ursache lag eine Ebene tiefer. **B4/B5-Pflicht:** Live-AUC in
~2 Wochen gegen 0,65 halten (Reminder `pbreak_accuracy_v2`, 800 Vorhersagen ab 01.08.).
- **P(Trendumkehr) = GEMESSEN, NICHT BERECHENBAR (`analyze_reversal.py`, 2026-07-31,
User-Frage „können wir die Trendumkehr-Wahrscheinlichkeit berechnen?"):** Aufgebaut
exakt wie das erfolgreiche P(break)-Modell — mechanisch scharfes **Barriere-Rennen**
(erreicht der Kurs zuerst X×ATR GEGEN oder +X×ATR MIT dem Trend?), logistische
Regression auf 8 kausalen Merkmalen (Überdehnung · Regressionswinkel · mom3/mom6 ·
Trendstärke · Trendalter · Abstand zum M30-Gegenlevel · Vola-Regime), Fit auf H1,
**AUC + Kalibrierung gemessen auf H2**. 80k M5-Bars, ~18,3k/18,5k Stichproben.
**Ergebnis: Münzwurf in JEDER von 5 Ereignis-Definitionen.**
| Barriere / Fenster | Basisrate | AUC oos | AUC **in-sample** |
|---|---|---|---|
| 0,5×ATR / 60 min | 54,0 % | 0,509 | 0,521 |
| 1,0×ATR / 120 min | 50,8 % | 0,506 | 0,523 |
| 1,0×ATR / 360 min | 51,0 % | 0,507 | 0,523 |
| 1,5×ATR / 180 min | 49,7 % | 0,499 | 0,530 |
| 2,0×ATR / 240 min | 47,8 % | 0,501 | 0,524 |
**Die diagnostisch wichtigste Zahl ist die IN-SAMPLE-AUC (0,520,53):** das Modell
erklärt nicht einmal seine eigenen Trainingsdaten. Das ist NICHT Overfitting (da wäre
in-sample 0,70+ und oos bräche ein) — es ist schlicht **keine Information vorhanden**.
Einzelmerkmale allein: alle zwischen AUC 0,489 und 0,515. Basisrate ~50 % → der Trend
hat auf diesen Skalen **keine Persistenz**. Kleines kohärentes Muster: kurz (0,5×ATR/
60 min) leichte Mean-Reversion (54 %), lang (2,0×ATR/240 min) leichte Trendpersistenz
(47,8 %) — aber 24 Pp und **unbedingt** (das WANN ist nicht vorhersagbar).
**Warum P(break) funktioniert und das hier nicht — die Kern-Lehre:** P(break) fragt
nicht „wohin geht der Kurs", sondern **„hält diese konkrete Struktur?"** — bedingt auf
ein reales Objekt (Level mit Orders dahinter). „Trendumkehr" ist die UNBEDINGTE
Richtungsfrage, überall im Chart gestellt, ohne Struktur zum Festmachen. Genau die
Klasse, an der das Projekt 18× gescheitert ist. **Die kalibrierte umkehr-nahe Größe
existiert bereits: `stop_approach`** (P(break) auf dem Gegen-/Stop-Level, AUC 0,68/
0,72) — strukturgebunden statt frei schwebend. Kein Modul gebaut.
- **RICHTUNGS-PFEILE im MT5 statt des Kegels (`AR;<art>;<von>;<nach>;<text>`,
Indikator v1.32, 2026-07-31, User: „anstatt des Trichters hätte ich gerne einen
Pfeil, der die wahrscheinlichste Kursentwicklung anzeigt").**
**Randbedingung, die den Entwurf bestimmt:** die FREIE Richtungsfrage ist im
Projekt gemessen ein **Münzwurf** (`analyze_reversal.py`: AUC 0,4990,509
out-of-sample, **in-sample nur 0,52** — das Modell erklärt nicht mal seine eigenen
Trainingsdaten). Ein frei schwebender Prognose-Pfeil wäre unbelegt — und gefährlicher
als der Kegel, weil er überzeugender aussieht. Deshalb DREI Pfeile mit jeweils
EIGENER, benannter Beleglage statt einer Prognose:
| Art | Farbe | Aussage | Beleg |
|---|---|---|---|
| **L** | blau | am NÄCHSTEN Level: Durchbruch oder Abprall | P(break), AUC 0,65 oos, live kalibriert (27 % vs 28 %) |
| **S** | neongrün | Squeeze-Ausbruch | das EINZIGE 2-Stichproben-validierte Richtungssignal (ØR +0,14…+0,23) |
| **K** | grau | Konsens der Verdict-Module | ⚠ **NICHT kalibriert** (in H2 sogar invertiert) → heißt „Konsens", nicht „Prognose" |
**L im Detail:** genommen wird das NÄHERE der beiden Level. P(break) > 55 % → Pfeil
DURCH das Level (Ziel = Level ± 0,5×ATR = die Bestätigungsmarke); < 45 % → Pfeil WEG
vom Level (Abprall); **4555 % → WAAGERECHTER Pfeil „unentschieden"** — dort ist die
Zahl ein Münzwurf, und daraus eine Richtung zu zeichnen wäre genau der Fehler, den
dieser Entwurf vermeiden soll. **S** erscheint nur bei `state=='active'` und wird
dicker gezeichnet (Breite 3 statt 2) — der validierte Pfeil ist der auffälligste.
**K** erst ab |bias| ≥ 0,2, Länge proportional (max 2×ATR).
⚠⚠ **KORREKTUR NOCH AM SELBEN ABEND (v1.33, User: „Trichter anstatt Pfeil wird
angezeigt"):** L und K starten am SELBEN Punkt (aktueller Kurs) und zeigten real in
ENTGEGENGESETZTE Richtungen (`AR;L;…;85.110;63% Abprall` nach unten,
`AR;K;…;85.479;Konsens +1.00` nach oben). Zwei divergierende Linien aus einem Punkt
sehen aus wie **genau der Kegel, den sie ersetzen sollten** — der Nutzer sah keinen
Pfeil, sondern wieder einen Trichter. **Gefragt war EIN Pfeil, gebaut waren drei.**
Behoben: **`InpShowConsensus` per Default FALSE** (der Konsens steht ohnehin als
Bias-Nadel im Dashboard) und **`InpArrBars` 14 → 8** (kürzer, damit die Pfeilspitze
im sichtbaren Bereich liegt). Normalfall jetzt: **EIN Pfeil** (L, der kalibrierte);
S kommt nur beim Squeeze-Ausbruch dazu. `InpShowConsensus=true` holt K zurück.
⚠ Lehre: „sie messen Verschiedenes, also dürfen sie auseinanderzeigen" ist logisch
richtig und als BILD trotzdem falsch — zwei Linien aus einem Ursprung liest niemand
als zwei Aussagen.
Umsetzung MQL5: `OBJ_ARROWED_LINE` vom aktuellen Bar über `InpArrBars`=14 Bars nach
rechts, Label an der Spitze. ⚠ Das Label heißt `ALBL_*` und NICHT `T*` — sonst würde
`RepositionLabels()` es an den rechten Rand ziehen und von der Pfeilspitze lösen.
Schalter `InpShowArrows`, Farben einzeln. **Der Kegel bleibt im Code** und in der
Dashboard-Kachel; der Bot exportiert ihn nur nicht mehr ins Chart
(`[trading] export_cone=false`, `true` holt ihn zurück).
- ⚠ **KACHEL „Erwartete Spanne" ENTFERNT, Kernwerte in die Gesamtempfehlung
gezogen (User-Vorgabe 2026-08-05, v=140).** `#card-cone`/`#cone-list` sind raus;
neu ist die einzeilige `#vd-cone` **unter der Bias-Nadel** in `#card-verdict`:
„📐 erwartete Spanne · 30 75,9976,77 · 60 75,8276,94 · 120 75,5577,21 ·
trifft 77 %". Gezeigt wird nur noch das **80 %-Band** je Horizont; das enge
50 %-Band und der Erklär-Absatz sind entfallen und stecken im `title`.
**Platzierung und Farbe sind Absicht:** die Zeile steht UNTER der Nadel und ist
neutral/randlos — sie zeigt **Streuung, nicht Richtung**, und darf optisch nicht
mit dem Bias konkurrieren. Beschriftet bleibt sie mit der **real gemessenen**
Abdeckung (~77 %), nicht mit dem Nennwert 80 %.
**Backend unverändert aktiv** (`core/cone.py`, Snapshot `cone`, MT5-Export `CN;…`).
Verifiziert am ausgelieferten HTML (`card-cone` weg, `vd-cone` da, keine verwaisten
JS-Referenzen).
- **Wahrscheinlichkeits-Kegel „Erwartete Spanne" (`core/cone.py` + ehem. Kachel `#card-cone`
+ MT5 `CN;…`, 2026-07-31): STREUUNG, NICHT RICHTUNG — und out-of-sample kalibriert.**
Der User wollte ein „Kursvorhersagemodul, das mögliche Verläufe in MT5 einzeichnet".
Eingezeichnete PFADE wurden bewusst NICHT gebaut (suggerieren eine Präzision, die es
nicht gibt — die Lehre aus `analyze_verdict_calibration.py`, wo conf_pct in H2
invertiert war). Stattdessen: wie WEIT der Kurs in 30/60/120 min plausibel gelaufen
sein kann. **Gemessen (`analyze_cone.py`, 80k M5, r(h)=(C[i+h]C[i])/ATR[i], Fit auf
H1 → Abdeckung gemessen auf H2):**
| Band | 30 min | 60 min | 120 min |
|---|---|---|---|
| 80 % nominal | 76,7 % | 76,6 % | 75,3 % |
| 90 % nominal | 87,5 % | 87,2 % | 86,5 % |
⚠ Die Bänder sind konsistent **~35 Pp ZU ENG** (H2 war relativ zum ATR volatiler).
Das wird **NICHT weggefittet** — Kachel und Tooltip nennen die REAL gemessene
Abdeckung („trifft 77 %"), nicht den Nennwert. Damit ist der Kegel nach dem
P(break)-Modell die **zweite kalibriert geprüfte Komponente** im Projekt. Quantile
final auf allen 80k Bars gefittet (`_CONE` in `core/cone.py`; gleiches Vorgehen wie
beim P(break)-Modell: erst out-of-sample validieren, dann voll fitten), ATR-normiert
→ regime-übergreifend gültig. **Zentriert auf den AKTUELLEN Kurs, KEINE Drift
addiert** (Median lag bei ±0,0…0,16×ATR = klein gegen die Bandbreite; eine
Drift-Korrektur wäre eine Richtungsaussage und genau die ist nicht belegt).
Snapshot `cone {atr, price, levels[{minutes,bars,band,lo,hi,coverage}]}`; MT5-Export
`CN;<minuten>;<lo>;<hi>` (nur 80 %-Band) → Indikator **v1.31** zeichnet zwei sich
öffnende, gepunktete Pfade, verkettet über 30→60→120 (`g_coneT/g_coneLo/g_coneHi`,
je Redraw zurückgesetzt; Startpunkt aus der `PX`-Zeile, die deshalb `g_conePx` auch
dann setzt, wenn `InpShowPrice` aus ist). Farbe **neutral grau** (`InpConeColor`) —
bewusst keine Richtungsfarbe. `InpShowCone=false` schaltet ab. **Kein Signal, kein
Verdict-Gewicht.**
- **Kerzen-Anatomie (`core/candles.py` + Kachel `#card-candles`, 2026-07-31, User-Vorgabe
„Kerzengröße und Dochtlänge … im Kontext"): REINE ANZEIGE — und die Kachel widerspricht
bewusst der Lehrbuch-Deutung.** Erkennt mechanisch aus M5-OHLCV: Marubozu · Langer
Körper · Langer Docht oben/unten · Spinning Top · Doji · Engulfing (Schwellen identisch
zum Backtest, damit Anzeige und Messung dieselbe Sprache sprechen); Refresh ~20 s im
Trend-Loop unter `mt5_lock`, Snapshot `candle_anat {tf, items[], vol_hot_x}`.
**GEMESSEN (`backtest_candles.py`, 80k M5, 2 Halbjahre, Forward-Return 60 min): die
gängige Deutung ist in BEIDEN Hälften INVERTIERT.** ⚠ Methodik-Kern: gerichtete Klassen
werden gegen die **DRIFT-NULL** (bm×E[d]) gelesen, nicht gegen die Baseline — sonst
liest man reine Regime-Drift als Edge (H1 war Abwärts-, H2 Aufwärts-Regime).
Überschuss-Befunde: **„Langer Körper = Fortsetzung" → 0,059/0,076** (Kurs läuft
GEGEN die Kerze); **„im Trend = Fortsetzung" → 0,132/0,042** (es ist die KLIMAX-,
nicht die Fortsetzungskerze); **„2× Volumen = echt" → 0,117/0,204** vs 0,065/0,035
bei normalem Volumen (**monoton in die falsche Richtung** — mehr Volumen = stärkere
Gegenbewegung); **„Docht am Level zuverlässig" → in BEIDEN Hälften SCHLECHTER als in
der Range-Mitte**; „mit/gegen Trend" beim Docht kippt (0,102/+0,089) = Rauschen.
**Folgetest als Handelssignal = ERNEUT VERWORFEN (`backtest_candle_fade.py`, Live-Exit
SL 2,0/Trail 1,5/BE 1,3/Time-Stop 120', Echtkosten):** der Fade der Klimax-Kerze
fällt durch — Kontrolle (ohne Volumen-Filter) 0,080/0,046; Vol≥2,0 0,059/**+0,103**;
Vol≥2,5 **+0,144**/0,080; Vol≥3,0 +0,113/0,064 → **benachbarte Schwellen exakt
gegenläufig** = Rauschmuster. Einzige beidseitig positive Zelle (Vol≥2,0+Marubozu
+0,036/+0,043) hat n=91/118 bei 7 getesteten Varianten = Mehrfachvergleich-Artefakt.
⚠ **Methodisch die wichtigste Lehre des Laufs: Informations-Überschuss ≠ handelbarer
Edge.** Dieselbe 2,0-Schwelle liefert im Forward-Fenster 0,117/0,204 (also +0,12/+0,20
beim Fade), im echten Trade aber 0,059/+0,103 — die Gegenbewegung ist diffus, der
2×ATR-Stop wird unterwegs getroffen (WR nur 3540 %). **Der Weg zum Ziel zählt, nicht
nur das Ziel.** **18. verworfener Signal-Eingriff.****Konsequenz für die UI:** die
Kachel ist bewusst **NEUTRAL** gefärbt (keine grün/rot-Ampel) und der Hinweistext nennt
die **gemessene** Lesart („⚠ Klimax-Kerze — der Kurs läuft danach eher GEGEN die
Kerze"), nicht die Lehrbuch-Lesart — eine Richtungsampel würde hier systematisch
falsch stupsen. Gleiche Korrektur wie bei der Bounce-Anzeige (heißt deshalb
„Überdehnung", bernstein statt grün/rot). Kein Verdict-Gewicht, kein Trade-Trigger.
⚠⚠ **DRITTE MESSUNG 2026-08-05 (`backtest_candle_fade2.py`, User-Wunsch „messe
erneut") — ALLE 40 ZELLEN NEGATIV.** Bewusst wurde nur EINE neue Achse geprüft, die
am selben Tag Squeeze UND Signal von negativ auf positiv gedreht hat: **der
Einstiegspreis** (Market am Bar-Close gegen ruhende **Limit-Order am
Kerzen-Extrem**), dazu der **kanonische Exit** (Trail 1,0 statt der alten Kopie mit
1,5). Sequentiell EIN Slot, Echtkosten, 80k M5, 2 Halbjahre, Kontrollgruppe „ohne
Volumen", Regel vorab fixiert (ØR>0 UND PF>1 beidhälftig · schlägt die Kontrolle ·
Nachbar-Schwellen kippen nicht · n≥100).
| Volumen | Market H1/H2 | **Limit am Extrem** H1/H2 |
|---|---|---|
| Kontrolle | 0,142 / 0,095 | 0,191 / 0,103 |
| ≥2,0× | 0,134 / 0,077 | 0,177 / 0,058 |
| ≥2,5× | 0,051 / 0,111 | 0,096 / 0,072 |
**Keine einzige Variante ist irgendwo positiv**, auch die Gültigkeitsdauer der
Limit-Order (3/6/12 Bars) ändert nichts.
⚠⚠ **DER LEHRREICHE TEIL — der Ausführungs-Hebel ist NICHT universell.** Beim
Squeeze und beim Signal dreht die ruhende Order das Vorzeichen; hier macht sie es
**schlechter**. Der Grund ist strukturell: ein **Breakout**-Einstieg wird von einer
Stop-Order genau im Moment der Bestätigung gefüllt — ein **Fade**-Einstieg per Limit
am Extrem füllt nur, wenn die Klimax sich weiter ausdehnt, also **genau in den
Fällen, in denen man falsch liegt**. Sichtbar an der Kennzahl-Schere: die
Trefferquote STEIGT (38,0 → 40,1 %, bei Vol≥2,5 sogar 43,2 → 48,5 %), der ØR fällt
trotzdem. Der bessere Preis wird mit der schlechteren Auswahl bezahlt.
**Merksatz: ruhende Order hilft bei Momentum-Entries, schadet bei Reversion-Entries.**
⚠ Nebenbefund zur Stichprobengröße: im 12k-Vorlauf gab es noch positive Zellen
(Vol≥2,5 H1 **+0,207**, Vol≥3,0 Limit H1 +0,291) — über 80k verschwinden sie
restlos. Dasselbe Muster wie beim Auto-Signal-Vorlauf am 30.07. **Ein kleiner Lauf
hätte hier zum dritten Mal zum Fehl-Einbau geführt.**
**Kachel bleibt unverändert reine Anzeige.** Kein neuer Zähl-Eintrag in der
Verworfen-Liste — es ist dieselbe Klasse wie #18, jetzt zum dritten Mal bestätigt.
- **Chart-Muster (Doppeltop/-boden) als Signal = VERWORFEN (gemessen `backtest_doubletop.py`,
2026-07-20, User-Idee „Mustererkennung SKS/Doppeltop"):** das am saubersten mechanisch
definierbare Umkehrmuster (2 ~gleiche Swing-Pivots + Nackenlinien-Bruch, aus derselben
Pivot-Erkennung; Einstieg beim Bruch, Live-Exit, 60k M30-Bars, 2 Halbjahre). ALLE
Toleranzen (0,3/0,6/1,0×ATR) fallen durch: WR nur 4045 %, ØR 0,01…−0,15, PF<1;
die einzige leicht positive Zelle (H1 tol 0,3 +0,044) kippt in H2 auf 0,150 = Rauschen/
Regime, kein Edge. Deckt sich mit der schwachen Empirie (Lo/Mamaysky/Wang 2000: Effekt
klein & kaum handelbar; NY Fed: SKS nicht profitabel; out-of-sample schwach) UND der
Struktur-Klasse (Muster = Swings+Trendlinien = die gerade verworfene Klasse). **11.
verworfener Signal-Eingriff — am Signal ist nichts zu holen.**
- **Konsolidierungs-Setups (False Breakout · Distribution · Accumulation · Retest)
= VERWORFEN (gemessen `backtest_consolidation.py`, 2026-07-30, User-Vorgabe „lerne
für die Gesamtempfehlung die Konsolidierungen", 3 Chart-Beispiele):** Alle auf
DERSELBEN Box wie der validierte Squeeze (10 M5-Bars ≤2,5×ATR), 80k Bars, 2
Halbjahre, Live-Exit + Echtkosten.
· **A) False Breakout / Liquidity Sweep** (Ausbruch kehrt zurück → Gegenrichtung):
**fällt in JEDER Variante durch** (Rückkehr-Fenster 1/2/3 Bars × Live-/Setup-Exit ×
Session) — H1 durchgehend negativ (0,127…−0,252), H2 leicht positiv = Regime-Kippen.
Der **Setup-eigene Exit** („Stop hinter dem Docht, Ziel = Gegenseite der Range") ist
in ALLEN 6 Kombinationen negativ — dieselbe Beobachtung wie bei den SMC-Sweeps und
den Chartmustern: der Lehrbuch-Exit trägt nicht, der Ertrag kommt (wenn überhaupt)
vom generischen Trailing. ⚠ **Wertvoller Nebenbefund:** **54 % aller Box-Ausbrüche
kehren binnen 3 Bars in die Box zurück** — der Squeeze-Ausbruch scheitert also
mehrheitlich (deckt sich mit seiner 43 %-Trefferquote). Die Gegenrichtung zu handeln
rettet das aber NICHT.
· **B/C) Distribution/Accumulation** (Ausbruchsrichtung aus dem Trend VOR der Box
vorhersagbar?): **die Basisrate widerlegt es direkt** — nur **48 %** der Boxen nach
Aufwärtstrend brechen nach unten, 52 % bei Accumulation (Zufall = 50 %). „Distribution"
sah mit ØR +0,168/+0,270 zunächst tragfähig aus; der **KONTROLLTEST** (derselbe
Vor-Trend, aber Bruch in die GEGENrichtung = Trendfortsetzung) liefert in H2 mit
**+0,364 sogar MEHR** als die Distribution-Hypothese (H1: 0,021). Beide Varianten
sind Box-Ausbrüche mit Trailing; welche führt, wechselt mit dem Regime. **Kein
Richtungs-Edge aus dem Vor-Trend.**
· **Retest-Einstieg** (statt beim Bruch am zurücklaufenden Level einsteigen):
in BEIDEN Hälften schlechter als der direkte Bruch (Distribution 0,071/+0,010,
Accumulation 0,184/0,131) — der Retest kostet den besseren Einstiegspreis.
**Fazit: nichts davon in die Gesamtempfehlung.** Der Squeeze bleibt die einzige
validierte Box-Verwertung. **17. verworfener Signal-Eingriff.** ⚠ Methodisch wichtig:
Der Kontrolltest („trägt die Hypothese mehr als ein beliebiger Ausbruch?") ist genau
der, der bei `backtest_patterns.py` noch fehlt — hier hat er die Idee gekippt.
- **Volume Profile / „Liquiditätslinien" (POC/HVN/LVN, Value Area) = VERWORFEN
(gemessen `backtest_volume_profile.py`, 2026-07-30, User-Frage „System zur besseren
Vorhersage — Liquiditätsnachfragelinien / wahrsch. Verkaufslinie?"):** Getestet, WEIL
es die einzige Klasse mit einer **anderen Datenquelle** war (nicht „wo war ein Docht"
= Pivots/Swings, sondern **wo wurde tatsächlich gehandelt** = Volumen) — SMC/ICT-
„Order Blocks"/Liquidity-Linien selbst sind strukturell Pivot-Zonen und damit dieselbe
5× verworfene Klasse. Aufbau: feste Preis-Bins 0,05 $, rollierendes 24-h-Fenster
(288 M5-Bars), Bar-Volumen (`tick_volume` — CFD liefert `real_volume=0`) gleichmäßig
über [low, high], Value Area 70 %; 80k M5-Bars, 2 Halbjahre, Live-Exit + Echtkosten.
**Ergebnis: nichts trägt.** Am eindeutigsten fällt genau die *Kern-Idee* durch:
**Reversion am POC („der Kurs prallt an der Liquiditätslinie ab") ist in BEIDEN
Hälften negativ** (ØR 0,178/0,121, PF 0,79/0,84, n≈400 je Hälfte) — der POC hält
NICHT. HVN-Reversion kippt (0,067/+0,044), LVN-Momentum ist Breakeven-Rauschen
(0,031/+0,003). **Informations-Test (Teil 1, Forward-Return 2 h ohne Exit/Kosten,
relativ zur `mid`-Baseline gelesen — H1 war Abwärts-, H2 Aufwärts-Regime, absolute
Werte sind daher Drift-verzerrt):** KEIN Bucket weicht konsistent ab — LVN H1 0,11
/ H2 +0,09 relativ, HVN H1 +0,08 / H2 0,06 → beide kippen. **Tiefere Einsicht: die
Volume-Profile-Position ist ein TREND-PROXY, keine unabhängige Information**
(„außerhalb Value Area" = Ausbruch läuft = folgt dem Regime: H2 +0,304 vs +0,092 in
VA, H1 umgekehrt 0,122 vs 0,057) — sie erbt damit exakt die Regime-Anfälligkeit von
Momentum/Struktur. **15. verworfener Signal-Eingriff.** ⚠ Was BLEIBT: das bereits
validierte **P(break)-Modell** ist die einzige belegt-prädiktive „Liquiditäts"-Aussage
im Projekt (AUC 0,71 out-of-sample, kalibriert; ~63 % der Level-Touches prallen ab) —
und auch das trägt nur als **EXIT**, nicht als Entry. Einzige noch ungetestete Klasse
wäre **echter Order-Flow/L2-Orderbuch** (Pepperstone = Market Maker, liefert keins;
Hyperliquid hätte `l2Book` — bräuchte erst wochenlange Datensammlung UND einen Beleg,
dass HL-Orderbuch-Signale auf den CFD übertragbar sind).
- **Chartmuster-Kachel `#card-patterns` (`core/patterns.py`, 2026-07-27, User-Idee
„Visuelle Muster-Erkennung"): REINE ANZEIGE, KEIN Signal.** Erkennt mechanisch aus
den M30-Swing-Pivots (k=3, wie `structure.py`): **Doppeltop/-boden, Kopf-Schulter/
inverse SKS, Tasse+Henkel/invers (heuristisch), auf-/absteigendes + symmetrisches
Dreieck.** Je Muster: Richtung (bull/bear/neutral), Nackenlinie/Trigger, Measured-
Move-Ziel, Status (bildet sich / bestätigt / ungültig→raus), Güte (Extrem-Symmetrie),
Bars-her. Refresh ~30 s im Trend-Loop unter `mt5_lock`; Snapshot `patterns
{patterns[], atr, cur}`. **Nur AKTIVE, kursnahe Muster** (2026-07-27, `_detect`-Filter):
abgearbeitete Muster (Ziel bereits erreicht) UND zu weit entfernte (`_MAX_DIST_ATR=5,0`,
Trigger >5×ATR vom Kurs) werden ausgeblendet — sonst zeigte die Kachel alte, nicht mehr
handelbare Muster. **Grafische Darstellung (2026-07-27, User „grafisch darstellen"):**
(a) Muster-Karte zeigt je Item eine **kompakte SVG-Skizze** der Form (`_patternSvg`,
viewBox 56×32, farbcodiert grün/rot/grau) — Form auf einen Blick; (b) im **Charts-Tab
(M30)** werden die **2 stärksten Muster als Preislinien** gezeichnet: Trigger = kräftige
Linie (grün bull/rot bear, Label „▶ bestätigt"/„◌ bildet sich"), Ziel = gepunktete Linie
gleicher Farbe. Dazu liefert `get_bars` bei M30 jetzt `patterns` (aus `patterns.snapshot`).
Frontend zeigt Karte + Disclaimer „kein Signal". ⚠ **Signal-
Einfluss ERST nach Backtest** (`backtest_patterns.py`, noch zu bauen) — die
Muster-KLASSE ist bereits 2× als Signal verworfen (`backtest_doubletop.py` oben +
`backtest_structure.py`), also **hohe Beweislast**; die Kachel macht das Muster nur
sichtbar (was ein Kommentator meint), steuert nichts. v2-Kandidaten (Flags/Wimpel,
Rechteck, Rundung) dokumentiert, noch nicht gebaut. Web-Recherche (strike.money,
IG, Axi): Umkehr (Doppel-, SKS, Rundung) vs. Fortsetzung (Dreieck, Flag, Tasse);
Kursziel stets = Muster-Höhe ab Ausbruch projiziert — überall mit dem Caveat
„keine Garantie, nur Indikation".
**Backtest gebaut & gemessen (`backtest_patterns.py`, 2026-07-27, 80k M30, 2
Halbjahre, Einstieg beim Trigger-Bruch, Live-Exit+Echtkosten):** Mehrere Typen sehen
ØR-positiv in BEIDEN Hälften aus (Doppeltop +0,43/+0,23 · Tasse +0,35/+0,26 · inv.
Tasse +0,36/+0,58 · aufst. Dreieck +0,16/+0,10). ⚠ **ABER das ist ein Exit-Artefakt,
KEIN Muster-Edge:** die **Ziel-Trefferquote (Measured-Move vor SL) ist nur 1338 %**
— die eigene Muster-Prognose trifft also in ~⅔ der Fälle NICHT. Der positive ØR
kommt vom **Trailing-Exit auf einem Momentum-Bruch** (Trigger-Bruch = Breakout, und
Breakout+Trailing hat generisch Edge — s. Squeeze), nicht von der Muster-Vorhersage.
Deckt sich mit `backtest_doubletop.py` (dort mit Ziel-/Fix-Exit → negativ) — der
Unterschied ist NUR der Exit. **Fazit: NICHT reif für Signal-Einfluss** — es fehlt
die Kontrolle „generischer Breakout + gleicher Exit" (isoliert den Muster-Beitrag,
wie Squeeze vs. beliebige Box); ohne die ist „Muster wirkt" nicht belegt. Muster
bleiben **reine Anzeige**. Ziel%-Spalte = der ehrliche Muster-Check (1338 % = Muster
sagen den Kursverlauf kaum voraus).
- **MQL5-Chart-Erweiterungen 2026-07-30 (Indikator v1.16v1.18, alles REINE ANZEIGE):**
· **Trade-Marker** `TR;<O|C>;<broker_time>` — die Kerze der **Eröffnung wird
HELLGRÜN**, die des **Close DUNKELGRÜN** umrandet (OBJ_RECTANGLE über Bar-Spanne +
High/Low, `iBarShift` → sitzt auf jeder Chart-TF korrekt). Quelle: offene Position
(`trader.open_time`) + die letzten `_TRADE_MARKS`=6 geschlossenen Trades.
· **Liquiditätszonen** `EQ;<H|L>;lo;hi;<broker_time>`**Equal Highs / Equal Lows**
aus den M30-Pivots (zwei Pivots innerhalb `_EQ_TOL_ATR`=0,15×ATR, 240 Bars
auseinander) als **goldenes Band** + gestrichelte Kante, nach rechts verlängert
(Darstellung nach User-Referenzbild). Berechnung in `wave_rec` (`_liq_zones`,
Snapshot `liq_zones`), Mindesthöhe `_EQ_MIN_H_ATR`=0,10×ATR. ⚠ **Als SIGNAL
gemessen und verworfen** (`backtest_liquidity_sweep.py`) — nur Kontext.
· **Aktueller Kurs** `PX;bid` — fett oben rechts (OBJ_LABEL, CORNER_RIGHT_UPPER,
Arial Black, `InpShowPrice`/`InpPriceSize`/`InpPriceColor`).
· ⚠ **Zwei Fallen beim Pfeil-Bug** („Pfeile werden nicht angezeigt", v1.17):
(1) **`ANCHOR_LEFT` ist für `OBJ_ARROW` UNGÜLTIG** — erlaubt sind nur `ANCHOR_TOP`/
`ANCHOR_BOTTOM`; (2) die Pfeile sitzen rechts NEBEN der letzten Kerze → ohne
**`CHART_SHIFT=true`** gibt es dort keinen sichtbaren Bereich. Beides im Theme/
Handler behoben.
· ⚠ **Blink-Fix in `core/hl_walls.py`** („Pfeile blinken nur manchmal auf"): bei
einem Fehlschlag (HTTP-Timeout, HL-Server kurz busy) wurde der Cache auf `None`
gesetzt → `LQ`-Zeilen fehlten in der CSV → der Indikator löschte die Pfeile beim
Redraw. Jetzt wird der **letzte gute Stand `_STALE_GRACE_S`=120 s weitergereicht**;
erst danach verschwinden die Pfeile (echter Ausfall).
- **Kurslücken-Kachel** (`#card-gaps`): offene D1-Gaps des laufenden Jahres
(`gaps.GapAnalyzer`, Refresh ~10 min im Trend-Loop unter `mt5_lock`). Je Gap:
Richtung, Zone, Fill-Level, Abstand zum Kurs. Die **Fill-Level fließen als
S/R-Magnete in `_sr_levels`** → damit in die Wellen-Konfidenz (`set_sr_context`,
`_SR_BONUS`/`_SR_PENALTY`), Close-Alarme und Verdict-Kontext — und so indirekt in
den **Autotrader** (der auf die Welle hört). **Keine** eigene Handelsrichtung.
Snapshot `gaps`. Mess-/Einzeltool: `find_gaps.py`.
- **⚠⚠ ERKENNUNG WAR DEFEKT — zwei Fehler (Fix 2026-08-03, User-Meldung „die
Kurslückenerkennung funktioniert nicht richtig"):**
**(1) Die größte Lücke des Jahres wurde um 3 Cent verfehlt.** Die Definition
verlangte ein lückenloses **Vakuum zwischen den TAGESSPANNEN**. Am Wochenende
31.07.→03.08. fiel WTI von Schluss **86,33** auf Eröffnung **79,76** = **6,57 $** —
unsichtbar, weil Montags Hoch (81,67) das Freitags-Tief (81,64) um **0,03 $**
überlappte. In einem 24/5-Markt überlappen die Tagesspannen fast immer; die
strenge Regel fand im ganzen Jahr nur **5** Gaps.
**(2) Alle Gap-Datumsangaben waren einen Tag zu früh.** `int(b["time"])
_BROKER_OFFSET_S` zog 3 h von einer Bar-Zeit ab, die MT5 bereits als naive
Broker-Zeit liefert → jeder D1-Bar rutschte auf den Vortag (die gemeldete Lücke
„2026-07-26" war real der **27.07.**). Am Jahreswechsel hätte es zusätzlich den
`year`-Filter verschoben.
**Fix:** zwei getrennte Gap-Begriffe im Snapshot (`kind`):
· **`vacuum`** = die alte, strenge Definition — **NUR diese speisen `zone_lines()`
`_sr_levels` → Wellen-Konfidenz**, denn nur auf ihnen wurde gemessen. Der
Strategie-Eingang ist damit **bitgenau unverändert** (verifiziert: `zone_lines()`
liefert vorher wie nachher `[88.288]`).
· **`close_open`** = Schluss→Eröffnung, also das, was ein Mensch „Kurslücke" nennt.
**REINE ANZEIGE**, ab `min_close_open`=0,50 $, füllt auch am selben Tag.
Ergebnis live: 4 offene Lücken statt 1 (1 Vakuum + 3 Eröffnungslücken), darunter
endlich die **6,57-$-Wochenendlücke mit Fill bei 86,33**. Frontend kennzeichnet
beide Arten (`Vakuum` / `Eröffnung`) mit Tooltip, v=133.
**Bewusst NICHT getan:** die `close_open`-Lücken in die Konfidenz einzuspeisen.
Das wäre ein ungemessener Strategie-Eingriff — und der gemessene Befund unten
beruht auf der Vakuum-Definition. Wer sie einbeziehen will, misst vorher.
- **Gap-Magnet als Handelssignal = VERWORFEN (gemessen, `backtest_gaps.py`):**
Signale WEG vom nächsten offenen Gap haben **+0,094** Ø-Edge, ZUM Gap nur
**+0,014** — der Magnet zieht NICHT (das Gap blieb offen, weil der Trend von
ihm weglief). „Richtung Gap handeln" wäre schädlich; „weg" ist nur
Trendbestätigung (macht die Welle schon). Gaps bleiben reiner Kontext.
- **Tagesziele — Dashboard-Kachel ENTFERNT (2026-07-13):** Das Frontend zeigt die
Web-Prognose nicht mehr. Das Modul `daily_levels` läuft **unverändert weiter**: die
Level fließen als **S/R-Kandidaten** in `_sr_levels` (Anzeige/Close/Verdict-Kontext)
und in den 07:30-Tagesreport. `snapshot.daily_levels` bleibt befüllt (nur ohne UI).
- **Täglicher Report** (07:30 Berlin, `engine._send_daily_report`): Web-Prognose +
**Vortags-Bilanz** (`stats_overview("yesterday")`) → **Telegram-Push** + **E-Mail**
(via `mailer.send_graph_mail`, `[graph]`-Creds, Absender `[graph] sender`=
mailagent@hocks.eu). Empfänger: `[graph] report_to` (kommagetrennt, Default
`trading@hocks.eu, axel@hocks.eu`). Ersetzt `scripts/send_daily_report.ps1` (das
hier nie lief — Microsoft.Graph-PS-Modul fehlt; Python nutzt Graph-REST direkt).
⚠ Graph/Exchange-Zustellung kann **mehrere Minuten verzögert** sein (202 = nur
angenommen). Fail-safe: kein Provider/Graph → Report übersprungen. **Versand-Status
persistiert** (`report_state.json`, `_load/_save_report_state`) — vorher schickte
jeder Server-Neustart nach 07:30 den Report ERNEUT (real 3× am 07.07.); gilt für
Tages- UND Wochenreport.
- **B4-Wochenreport (automatisch, `engine._send_weekly_report`):** **montags ab 07:30
Berlin, 1×/Woche** (gleicher `_daily_loop`, `_weekly_last_run`=(ISO-Jahr,KW)) →
**E-Mail + Telegram**. Ruft `weekly_review.build_report(m5=…)` (Live-Stats vs.
Backtest-Erwartung, Leckage-Flags — Track B/B4) und schickt es als `<pre>` (Betreff
„Oil Wochenreport (B4) — KW…", Empfänger = `[graph] report_to`). **Squeeze-Breakout-
B4-Monitor** (`_squeeze_section`): rechnet die Squeeze-Regel mit den Live-Params auf
den letzten ~30 Tagen M5 NEU (mechanischer Setup → Live-Perf. = Neuberechnung, keine
Trade-Attribution nötig) und hält ØR/PF gegen +0,14…+0,23. M5-Bars holt die Engine
unter `mt5_lock` und reicht sie durch (`build_report(m5=…)`; standalone lädt via
`_fetch_m5`, schließt MT5 nur bei Eigen-Init). Fail-safe wie der Tagesreport.
Manuell jederzeit: `python weekly_review.py`.
- **Position-Kachel zeigt SL/TP + Einsatz** (`pos-sl`/`pos-margin`;
`trader.snapshot()` liefert `sl`/`tp`/`margin`). **Kopfzeile der Kachel**
(`.pos-top`, 2026-07-15, User-Vorgabe): **Kurs · G/V** in EINER Zeile (18-px).
Das G/V (`#pnl`) zeigt **NUR NETTO** (Brutto entfernt, kein „n."-Präfix): bei
Gewinn P&L × (1 `[trading] wht_pct`/100) = abzgl. WHT-Quellensteuer, bei Verlust
unverändert (keine Steuer). Snapshot `wht_pct`; Tooltip nennt den Satz. Swap ist
bereits im P&L (`trader.pnl = profit + swap`), Kommission beim Standard-Konto 0.
Einsatz/SL/TP/Richtung stehen in der Trade-Leiste, S/R im Chart (aus der Kachel
entfernt); `emg-lbl`-Beschriftungen ausgeblendet (Felder self-labeln via
Platzhalter/Button-Text). Fehlender SL → rot „⚠ kein SL
gesetzt!". „Einsatz" = gebundene Margin (`order_calc_margin`). **Notfall-Stop-
Feld** (`#emg-input` + setzen/aus): manueller Verlust-Wert → Server schließt
sofort (s. „Notfall-Stop"). Feld wird mit aktivem bzw. gemerktem Wert
(`emergency_loss`/`emergency_remember`) **vorbefüllt** (außer beim Tippen);
**Enter/„Go"** löst „setzen" wie der Button aus. Status `#emg-state`: „aktiv:
schließt bei −…" (rot) bzw. „vorgemerkt −… (aktiv beim Öffnen)". **Gewinn-Feld
(„Gewinn mitnehmen bei") aus dem Dashboard ENTFERNT (User-Vorgabe 2026-07-14)** —
das Backend bleibt vollständig erhalten (`engine.set_takeprofit`, kombinierter
`_check_auto_close`, `POST /api/takeprofit`, Telegram „💰 Gewinn mitgenommen",
Config `[trading] auto_takeprofit` Default 0=aus), ist ohne UI aber schlafend;
der P(break)-S/R-Auto-Close übernimmt die Gewinnmitnahme-Rolle.
- **Blinken:** LONG/SHORT-Button blinkt bei jeder Richtungs-Empfehlung (nicht mehr
an 70 % gekoppelt). **Ton bei Empfehlung** (`beepRecommendation`): **sofort beim
Signal-Wechsel** (WARTEN→LONG/SHORT oder Flip, 3-s-Flacker-Schutz) **+ Erinnerungs-
Ton alle 30 s** (`_REC_REBEEP_MS`), solange der Button blinkt — **nur wenn flat**
(keine Position). LONG = aufsteigend, SHORT = absteigend (unterscheidbar vom
Close-Alarm). Audio erst nach 1× Nutzer-Tap aktiv — ist es noch gesperrt, zeigt
ein einmaliger Toast „🔇 Ton gesperrt — einmal tippen". Kein Ton bei offener
Position. **Mute-Button** (`#mute-btn`, 🔊/🔇 rechts in der Tab-Leiste) schaltet
ALLE Töne (Gate in `_tone` + beiden beep-Funktionen), persistiert via
`localStorage("oil_muted")`. **CLOSE blinkt + Ton NUR wenn `pnl > sr_close_min_gain`**
(Feld leer/0 → `pnl>0` wie bisher; deckt sich mit dem Auto-Close-Gate — unter der
Schwelle schließt der Bot ohnehin nicht, also auch kein Alarm; Frontend `gainOK`,
gilt für Flip-Close UND S/R-Close-Blink/Ton). Der Telegram-Flip-Push (`_check_close_
alert`, Backend) bleibt bei `pnl>0`. **Flip-Close** = Signal-Flip gegen die Position.
**S/R-Ziel-Close-Hinweis
ENTFERNT** (stupste zum verfrühten Gewinner-Kappen → Verlust-Schiefe; Gewinner
führt das Trailing). Bei Verlust-Trade: kein Close-Alarm. **Jeder echte Auslöser
loggt** `🔔 Flip-Close-Alarm #N` + Zähler `snapshot.close_alert_count` (zum
objektiven Nachzählen, statt aus Trades/Recs zu rekonstruieren). Engine: `_check_close_alert` (Telegram max.
**1×/5 min**, `_CLOSE_ALERT_COOLDOWN_S` — kein Spam, wenn der Kurs ums Level
pendelt). ⚠ Cooldown ist **ticket-basiert** (`_close_alert_ticket`): Reset NUR
bei echter neuer Position; ein kurz flackerndes Ticket (transient leeres
`positions_get`) setzt den Cooldown NICHT mehr zurück (war die Spam-Ursache).
Frontend: `flipClose || srTarget`. Kein Auto-Close an S/R (Backtest).
- **Trade-Abschluss-Telegram ENTFERNT** (`history.log_trade_close` sendet kein
Telegram mehr; Import bereinigt). Close-bezogene Telegrams kommen jetzt **nur
noch** aus der Engine: **🔔 CLOSE-Signal** (echter Signal-Flip, `_check_close_
alert`) + **🚨 Notfall-Close / 💰 Gewinn-Close** (armierter Auto-Close,
`_check_auto_close`). Frontend-Blink/Ton weiterhin nur bei Flip im Plus.
- **Order-Bestätigungen:** Kein hartes Blockieren mehr. Eröffnen bei WARTEN/Gegen-
Signal → **Hinweis-Dialog „trotzdem eröffnen?"** (überschreibbar). CLOSE bei
**negativem** P&L → Sicherheitsabfrage; im Plus/Breakeven direkt. Sonst keine
Token-Dialoge (`require_token=false`). Audio braucht 1× Nutzer-Tap.
- Asset-Version aktuell **v=141** (in `web/index.html` hochzählen, siehe Workflows).
Schriftgrößen 2026-07-24 global **+4px** (2× je +2px auf User-Wunsch; Body-Basis
14→18px). ⚠ Betrifft in `style.css` sowohl `font-size:Npx` (71×) ALS AUCH die
`font:<weight> Npx/…`-**Shorthand** (3×: `.ms-chip`/`.ms-bos-lbl`/`.sqm-badge`
die wurden zuerst übersehen, weil das erste Bump-Skript nur `font-size` erfasste)
+ Chart-`fontSize` in `app.js`. Bei künftigen globalen Font-Änderungen die
Shorthand nicht vergessen.
## Hyperliquid-Kurs neben dem Broker-Kurs (2026-08-01)
**User-Wunsch:** „wenn Pepperstone geschlossen ist trotzdem den aktuellen Kurs und die
Liquidität von Hyperliquid einbinden" → nachgeschärft zu „blende den HL-Kurs **neben**
den Pepperstone-Kurs ein" (ehrlicher: man sieht die Abweichung, statt dass still
getauscht wird) → plus „bei geschlossenem Markt auch den **G/V** mit HL berechnen".
### ⚠⚠ Die Basis-Korrektur war am Wochenende ZIRKULÄR (Fix 2026-08-02)
**User-Fund:** „Auf app.hyperliquid.xyz steht WTIOIL-USDC bei 81,392, im Dashboard 86,424."
Beides war erklärbar — und dahinter steckten **zwei** Fehler.
**(1) Falsches Label (mein Fehler vom 01.08.).** Angezeigt wurde `mid_mt5` = HL + Basis,
also der auf Pepperstone-Niveau umgerechnete Wert — unter der Beschriftung „HYPERLIQUID
KURS". Vorher hieß das Feld nur „HL" (unklar, aber nicht falsch); die Ausschreibung machte
daraus eine unzutreffende Behauptung. **Jetzt steht dort der ROHE HL-Kurs** (exakt die
Zahl von der HL-Seite), das CFD-Äquivalent wanderte in den Tooltip.
**(2) Der eigentliche Bug: die Basis saugt bei stehendem Broker den HL-Move auf.**
Die Basis ist `Median(mt5_mid hl_mid)` über die letzten 60 Snapshots — **ohne Prüfung,
ob `mt5_mid` überhaupt noch lebt.** Steht Pepperstone (Wochenende/Feiertag), friert
`mt5_mid` ein, während HL 24/7 weiterläuft → die „Basis" wächst exakt um den HL-Move, und
`hl_mid + basis` rekonstruiert wieder den **EINGEFRORENEN Broker-Kurs**. Die Korrektur
wird zirkulär — also genau dann wertlos, wenn man sie braucht.
**Beleg aus `leadlag.db`** (beide Kurse nebeneinander, 46k Ticks):
| Zeitraum | Basis |
|---|---|
| 27.31.07., Broker **live** | **+0,42 … +0,58** (bemerkenswert stabil) |
| ab 31.07. 22:55 (Freeze), Broker **steht** | 1,07 → **+5,03** |
Real angezeigt: **86,414** statt der echten ~81,89 — Abstand zum eingefrorenen Broker-Bid
nur noch **0,081**. Der Wochenend-Move war dabei ECHT, kein dünnes Buch: So 03:00→05:00
von 85,51 auf 80,84, danach 13 h stabil um 81 bei ~600 Messungen/Stunde.
**Fix (HL-Projekt, `server.py` `/api/walls`):** Der Endpoint sucht jetzt die **letzte
echte Broker-Bewegung** (Rückwärtslauf über `_BASIS_LOOKBACK`=40 000 Zeilen ≈ 2,3 Tage —
muss ein ganzes Wochenende überspannen) und bildet die Basis aus dem Fenster **davor**.
Steht der Broker länger als `_MT5_STALE_S`=180 s (gleiche Schwelle wie
`engine._TICK_STALE_S`), ist die Basis **eingefroren** und der Endpoint liefert
`basis_stale: true` + `mt5_still_s`. Verifiziert an den Echtdaten: letzte Bewegung
31.07. 22:55:06 (= der eingefrorene Tick), Basis **+0,4765** statt +5,0285, Antwortzeit
109 ms (Timeout des Oil-Bots ist 2 s).
**Materielle Folge, nicht nur Kosmetik:** Die offene SHORT-Position (0,57 Lots ab
86,163, TP 84,123) wurde mit **15,02 €** angezeigt; mit eingefrorener Basis sind es
**+211 €**, und der TP lag 2,3 $ ÜBER dem echten Niveau. Die HL-G/V-Schätzung war vorher
zwangsläufig falsch, weil sie auf demselben zirkulären Wert rechnete.
**Vorbehalt bleibt:** HL ist ein Oracle-basierter HIP-3-Perp — dass er 81,3 zeigt, heißt
nicht, dass Pepperstone dort öffnet. Richtung belastbar, Höhe nicht. Deshalb weiter `≈`.
- **Snapshot:** `hl_live {mid_mt5, hl_mid, basis, basis_stale, age_s, bid, ask, bid_sz,
ask_sz, imb5, trend, slope_per_h, vs_mt5}` — IMMER befüllt, nicht nur am Wochenende.
`mid_mt5` = HL-Mid + gemessene Basis (HL `xyz:CL` notiert wegen anderem Kontrakt
~0,45 $ unter dem CFD; die Basis kommt serverseitig aus dem HL-Projekt als Median
über 60 Snapshots) → direkt mit dem Broker-Kurs vergleichbar.
Dazu `market_closed`, `tick_age_s`, `pnl_hl`.
- **Dashboard (v=129):** eigene Header-Box **HL** neben KURS; Tooltip nennt HL-Rohkurs,
Basis, Alter und Abweichung. Bei geschlossenem Markt heißt das Kurs-Label
„KURS · ZU", die Session-Zeile zeigt „🔒 MARKT ZU", und das **G/V wechselt auf die
HL-Schätzung** (mit `` markiert, Tooltip nennt den eingefrorenen Broker-Wert).
- **MT5 (Indikator v1.34):** `HL;<mt5_aequiv>;<alter_s>;<bid>;<ask>;<U|D|->` →
bernsteinfarbenes Label unter Kurs und G/V (`InpShowHL`/`InpHLColor`).
⚠ **DREI Sackgassen bei der „Markt zu"-Erkennung — dokumentiert, damit sie niemand
wiederholt:**
1. **`tick_local_ts`** ist die lokale **ABHOLZEIT**, nicht das Tick-Alter — sie
erneuert sich bei jedem Abruf, auch wenn der Broker denselben alten Tick liefert.
2. **`tick_server_ts` `trader._broker_offset_s()`** ist **ZIRKULÄR**: die
Offset-Funktion leitet den Zeitzonen-Versatz selbst aus dem letzten Tick ab
(`off = tick.time now`). Bei geschlossenem Markt misst sie die Tick-Veraltung
statt der Zeitzone (real **7,41 h** statt +3 h) — das Alter hebt sich rechnerisch
auf (Ergebnis: **347 s**).
3. **`pnl / Kursbewegung`** als EUR-je-Punkt-Faktor ergab **155 statt ~86 EUR** je
1,00 und Lot, weil `trader.pnl = profit + swap` den über das Wochenende
aufgelaufenen Swap enthält.
**Verwendet wird stattdessen:** Markt zu = **der Kurs hat sich seit `_TICK_STALE_S`
(180 s) nicht BEWEGT** (`_last_bid`/`_last_bid_move_ts`) — kein Zeitzonen-, Kalender-
oder Feiertagswissen nötig, deckt Wochenende, Feiertag und Broker-Ausfall gleich ab.
Und der G/V nutzt die vorhandene, korrekte Umrechnung **`trader.live_pnl(bid, ask)`**
(`tick_size`/`tick_value` inkl. Swap und Währung), gefüttert mit dem HL-Kurs ± halbem
Spread. ⚠ Bleibt eine **Schätzung** (anderer Kontrakt, Montags-Open-Spread unbekannt) —
deshalb das `` in der Anzeige.
### ⚠⚠ `_broker_offset_s()` lieferte bei geschlossenem Markt 9,5 h statt +3 h (Fix 2026-08-01)
Sackgasse (2) oben war **nur halb richtig** — die Zirkularität war nicht das eigentliche
Problem, sondern ein **echter Bug in `trader._broker_offset_s()`**, der weit über die
Anzeige hinausreichte. Gefunden bei der Frage „warum steht die Laufzeit auf 0:00?".
**Mechanismus:** `off = round((tick.time now)/1800)*1800` misst die Zeitzone nur,
solange der Tick FRISCH ist. Steht der Markt, friert `tick.time` ein und die Differenz
misst die **Veraltung**. Real gemessen am 01.08. um 11:25 (letzter Tick Fr 23:54:59):
```
tick.time now = 34210 s → gerundet 34200 (9,5 h) statt +10800 (+3 h)
```
Die alte Plausibilitätsprüfung `[12 h … +14 h]` **ließ das durch** — eine
Wochenend-Veraltung fällt genau in dieses Fenster. Ein noch längerer Stillstand (Mo
früh, ~49 h) wäre rausgefallen und hätte 0 geliefert, also *auch* falsch.
**Was still betroffen war** (die Laufzeit-Uhr war nur das Sichtbare):
`open_time` = `p.time off` landete **12,5 h in der ZUKUNFT** → Laufzeit 0:00 ·
Time-Stop-Alter in `trailing.py:504` wurde negativ → **der 120-min-Time-Stop hätte
nicht ausgelöst** · `deal.time`-Umrechnung beim externen Close · alle MQL5-Chart-Anker
(Kanal, Liquiditäts-Trendlinie, Trade-Marker).
**Fix:** Der Offset wird **nur noch von einem frischen Tick** (neu) bestimmt, sonst gilt
der zuletzt gültige Wert (`self._boff`, vorbelegt mit `_BROKER_OFF_DEFAULT_S` = UTC+3 —
dieselbe Konstante nutzt `engine.get_bars` schon als `broker_utc_offset`). Die Frische
wird am **bereits bekannten** Offset gemessen (`stale = now + _boff tick.time`), nicht
am Tick selbst — damit nicht zirkulär. Schwelle `_BROKER_TICK_FRESH_S = 900` liegt unter
dem halben Rundungsraster (1800/2), sodass die 30-min-Rundung immer auf den echten
Offset fällt. Ein echter Zeitzonen-Wechsel (Broker-DST) wird beim ersten frischen Tick
übernommen **und geloggt** (statt still zu passieren).
⚠ **Einordnung: Deployment-Drift** (Fall 4 in der Tabelle unten) — entworfen unter „Tick
ist frisch", betrieben unter „Tick kann beliebig alt sein". Kein Backtest hätte das
gefunden; Backtests rechnen nie mit Broker-Wallclock. Bestätigt die dortige Erwartung,
dass die Liste nicht vollständig war.
**Startwert der Stillstands-Uhr (gleicher Fix):** Weil der Offset jetzt verlässlich ist,
wird `_last_bid_move_ts` beim **ersten** Snapshot aus dem ECHTEN Tick-Alter vorbelegt
(`_now + _broker_offset_s() tick_server_ts`, Plausibilität 0…14 Tage) statt bei null zu
beginnen. Vorher brauchte der Bot nach **jedem** `restart_server.bat` volle 180 s, um ein
längst geschlossenes Wochenende zu bemerken — real lief der User genau in dieses
Blindfenster („KURS" statt „KURS · ZU", G/V auf dem eingefrorenen Broker-Wert).
Im **laufenden** Betrieb bleibt die Erkennung bewusst bewegungs-basiert: das fängt auch
einen eingefrorenen Feed, dessen Zeitstempel weiterlaufen.
Verifiziert nach dem Neustart: `market_closed=True` sofort, `tick_age_s` 56 844 s
(= Rohwert-Gegenrechnung auf **0 s** genau), `open_time` → 31.07. 22:54, Laufzeit +15,8 h.
### ⚠ Stiller `catch` im REST-Poll verschluckte JEDEN Render-Fehler (Fix 2026-08-01)
`pollSnapshot()` hatte `try { fetch → _applySnapshot(d) } catch {}` — der `catch` sollte
Netz-Aussetzer schlucken, umschloss aber auch `render()`. Ein Render-Fehler wurde damit
**lautlos** verschluckt: der Poll feuerte weiter alle 4 s, jeder Durchlauf starb still,
das Dashboard fror auf dem letzten guten Stand ein **und die Konsole blieb leer**. Genau
diese Kombination (eingefrorene Anzeige ohne jede Fehlermeldung) hat die Fehlersuche
unnötig lang gemacht. Jetzt ist nur noch der `fetch` im `try`; Netzfehler bleiben still,
**Render-Fehler werden laut**. Ergänzt die Null-Sicher-Regel von oben: die verhindert den
Absturz, das hier macht ihn sichtbar, falls er doch passiert.
**Beschriftung (User 2026-08-01):** Die Header-Labels heißen ausgeschrieben
**„PEPPERSTONE KURS"** und **„HYPERLIQUID KURS"** (vorher „KURS"/„HL" — ließ offen,
welcher Kurs woher kommt). Bei geschlossenem Markt: „PEPPERSTONE KURS · ZU".
## Web-API (server.py)
`GET /api/snapshot` (inkl. `verdict`-Aggregat) `/api/health` `/api/logs` `/api/stats` `/api/news` `/api/bars?tf=&n=` (Charts, read-only) `/api/squeeze_monitor` (B4-Monitor, read-only, Engine-Cache 60 s) · `WS /ws` ·
`POST /api/order|auto|trail|emergency|takeprofit|sltp|srclose|srclose_min|autosqueeze|autosignal|auth/check`.
`/api/sltp {sl,tp}` = manuelles SL/TP der offenen Position → `engine.set_sltp` →
`trader.modify_sltp` (TRADE_ACTION_SLTP, prüft Seite/Mindestabstand) **und deaktiviert
das Trailing** (`trail.deactivate` — sonst zöge es SL/TP beim nächsten Tick zurück,
User-Vorgabe). ⚠ **Extern-Erkennung (Fix 2026-07-17):** Setzt der User SL/TP **direkt
im MT5-Terminal** (nicht über die App), greift `deactivate()` nicht — das Trailing
überschrieb die Handeingabe (real: TP 90,000 → 81,484). `trailing._do_modify` vergleicht
jetzt den Broker-SL/TP mit dem **zuletzt selbst gesetzten** (`_last_sl/_last_tp`, tol =
Modify-Schwellwert): weicht er ab → **Trailing schaltet sich ab, Handeingabe bleibt
stehen** (Log „Manuelle SL/TP-Änderung erkannt"). `_last_sl/_last_tp` werden bei
Aktivierung genullt (kein Altwert-Fehlalarm); Erkennung erst ab dem 2. Modify. Token via `[web] api_token`; bei
`require_token=false` ist die Token-Pflicht aus (Zugang nur via WireGuard).
- **Snapshot-Cache:** `/api/snapshot` UND `/ws` liefern `engine.snapshot_cached()`
(TTL `_SNAP_TTL_S=0,8 s`) — EIN berechneter Snapshot wird über alle Geräte
geteilt, SQLite/Verdict-Last bleibt unabhängig von der Client-Anzahl.
- **WS-Wakeup nach Sperrbildschirm/Hintergrund (Fix 2026-07-22, User-Meldung „G/V
großer Lag"):** Backend-Pipeline ist schnell (`_pos_loop` 1 s · Snapshot-Cache
0,8 s · WS-Push 1 s; gemessen: MT5-Lock-Last selbst im Worst-Case nur ~25 ms,
0 Lock-Timeouts/`_pos_loop`-Fehler im Log) — die Ursache lag im **Frontend**:
mobile Browser pausieren WS+Timer beim Sperren/Backgrounding, ohne
`visibilitychange`-Handling zeigte die Seite bis zu einem zufälligen Reconnect
den STALEN Stand (Minuten alt). Fix: `visibilitychange`/`pageshow`/`focus`-Listener
(`_wakeRefresh` in `app.js`) holen beim Zurückkommen sofort einen frischen
REST-Snapshot UND bauen die WS-Verbindung hart neu auf (statt auf den evtl.
hängenden alten Socket zu warten).
**REST-Polling als Sicherheitsnetz (2026-07-22, User-Wunsch „JSON-Feed"):**
zusätzlich zum WS-Push pollt `app.js` alle `POLL_MS=4000` ms `/api/snapshot` —
fängt den Fall auf, dass der WS still hängt (kein `onclose`, keine Zustellung
mehr, z. B. schwaches WireGuard-Signal) OHNE auf einen Reconnect zu warten;
pausiert bei `document.hidden` (Akku/Daten). **`lastTs`-Dedup** (`_applySnapshot`,
vergleicht `engine.snapshot_cached().ts` — dieselbe Quelle für WS UND REST):
nur die jeweils NEUERE Quelle rendert → kein Flackern/Doppel-Render, wenn beide
fast gleichzeitig eintreffen. Alle Snapshot-Konsumenten (initialer Load,
Wake-Refresh, Poll, WS) laufen jetzt über `_applySnapshot` statt direkt `render`.
v=108. `GET /api/snapshot` ist ohnehin öffentlich/token-frei nutzbar — externe
Tools (Shortcuts, Home Assistant, …) können ihn direkt pollen.
## ⚠ Deployment-Drift — die strukturelle Schwachstelle (2026-07-31)
**Track B schützt gegen OVERFITTING** (ein Edge existiert gar nicht). Es schützt
**nicht** gegen **DEPLOYMENT-DRIFT**: ein real existierender Edge wird unter anderen
Bedingungen BETRIEBEN als VALIDIERT. An EINEM Tag wurden drei Fälle gefunden:
| # | Komponente | Gemessen unter | Betrieben unter | Schaden |
|---|---|---|---|---|
| 1 | P(break) | Anlauf zu **fixiertem** Level | **dynamisch** gewähltes Level | AUC 0,715 → **0,368** |
| 2 | `breakout_k=0,3` | **feste** Zeitebene | TF wechselt 33×/Tag, löscht den Anker | 43 % → **93 % WARTEN** |
| 3 | Trailing `mult` | **1,5** | **1,53,0** je nach TF | H1 ΣR 249 → **1367**, Worst 1,50 → 2,50 |
| 4 | `_broker_offset_s()` | **frischem** Tick | Tick beliebig alt (Wochenende) | Offset **9,5 h statt +3 h** → `open_time` 12,5 h in der Zukunft, **Time-Stop-Alter negativ** |
| 7 | Auto-Squeeze-**Einstiegspreis** | Fill **AM Ausbruchs-Level** (`backtest_breakout_squeeze.py`) | **MARKET**-Order, live median **+0,275×ATR** über dem Level (34 echte Entries) | ØR **+0,456 → 0,158** (H1) bzw. **+0,611 → 0,006** (H2); WR 56 → 37 % |
| 6 | `auto_squeeze_skip_night` | Nacht-Kostenfalle am **Wellensignal** (alle Nacht-Bars: 0,333×ATR) | **Squeeze**-Ausbrüche, die nur bei anziehendem ATR feuern (0,183×ATR = Tag-Niveau) | Gate kostet ΣR **42 (H1) / 243 (H2)**; geblockte Ausbrüche sind in beiden Hälften positiv |
| 5 | `backtest_auto_signal.py` | **ohne Winkel** (`_build` ohne `angle=` → Default 90 → `ad=0`) | Winkel steuert einen **Signalzweig** (Reversal) UND **±15 Konfidenzpunkte** | Urteil vom 30.07. beschrieb weder das alte noch das neue System; der Winkelterm allein hebt H1 bei conf 75 von **0,123 auf 0,047** |
Keiner wäre durch MEHR Backtesting gefunden worden — die Backtests waren korrekt.
**Ursachen (strukturell):** (a) Backtests sind NACHBAUTEN, keine Nutzer des Live-Codes
(`sim_run`/`_ema_series`/`_atr_series` liegen in ≥6 Skripten als Kopie); (b) es gibt
**Live-only-Gates**, die kein Backtest modelliert (Entry-Raum, EIA, Dead-Hours,
News-Blackout, Cooldowns, Startup-Grace); (c) Live-Parameter sind **kontextabhängig**
(Trail-mult je TF, SL-ATR fix M15, TF selbst per Heuristik), Backtest-Parameter
konstant; (d) es gab **keine Telemetrie über die Entscheidung selbst**.
**Behebung in 3 Stufen (User-Entscheidung 2026-07-31):**
- **Stufe 1 — ERLEDIGT:** `_MULT_BY_TF` auf durchgehend **1,5** und
`_MULT_BREAKOUT_ADD` auf **0,0** (`core/trailing.py`) — der einzige gemessene Wert.
⚠ **BEGRÜNDUNG NACHTRÄGLICH KORRIGIERT (beim Stufe-3-Refactor):** die zuerst
genannte „67 % größerer Einzelverlust" war ein **Artefakt** — `backtest_trailing.py`
setzt den Initial-SL auf `mult`×ATR statt fix 2,0, dort war der Worst-Case also
per Konstruktion gleich dem Multiplikator. Sauber nachgemessen mit fixem SL
(`backtest_trailmult.py`, `core/exit_model.py`, 80k Bars, 2 Halbjahre):
| Trail | H1 ΣR | H2 ΣR | Worst |
|---|---|---|---|
| 1,0 | **73** | **+1346** | 2,00 |
| **1,5 (live)** | 325 | +1294 | 2,00 |
| 2,0 | 446 | +1195 | 2,00 |
| 2,5 | 1174 | +1223 | 2,00 |
| 3,0 | 1088 | +1397 | 2,00 |
**Der Worst-Case ist bei JEDEM Multiplikator identisch 2,00** (der SL deckelt).
**Die Entscheidung bleibt richtig:** 1,5 schlägt 2,0 UND 2,5 in beiden Hälften.
**➜ TRAIL AUF 1,0 GESETZT (User-Vorgabe „setze alle Vorschläge um", 2026-07-31),
in `core/exit_model.py` (`LIVE.mult`), live verifiziert (`AKTIVIERT … mult=1.0x`).**
Voller Sweep (`backtest_trailmult.py`, fixer SL 2,0, 8 Werte 0,53,0): **enger ist
MONOTON besser** — Wave-Signal H1/H2 ΣR: 0,5 **+1258/+2418** · 0,8 +852/+1668 ·
1,0 +80/+1360 · 1,5 220/+1305 · 2,0 394/+1198 · 2,5 1072/+1327.
⚠ **Optimum am RAND des Suchbereichs = Warnsignal** („misst man besser oder nur
weniger?"). **Gegentest auf SQUEEZE-Entries mit Echtkosten** (`--squeeze`) — das
Setup lebt von LÄUFERN, müsste also dagegenhalten: **tut es nicht**, auch dort ist
enger monoton besser (1,0: H1 0,202/H2 +0,010 · 1,5: 0,219/0,032). Deshalb der
Schritt auf 1,0 — besser als 1,5 in beiden Hälften auf **beiden** Signalmengen —
aber **nicht** auf 0,5 (Randwert). ⚠ Zweiter Grund gegen 0,5: die Sim modelliert
**keine Exit-Slippage**; ein engerer Trail löst viel häufiger aus und ist stärker
betroffen (real gemessen bis 0,75×ATR über den Stop hinaus).
- **Stufe 2 — ERLEDIGT:** **Entscheidungs-Telemetrie.** (a) Jede Empfehlung loggt
jetzt einen maschinenlesbaren **`block_reason`** (`recommendations.block_reason`,
DB migriert mit Backup): `deadband · dead_hour · eia · htf_counter · stretch ·
min_conf · breakout_pending · entry_room · no_data · stale`; NULL = kein Block.
Ohne den war nicht feststellbar, WELCHES Gate die 93 % erzeugt. (b) **`analyze_
divergence.py`** hält vier Dinge gegen die Backtest-Erwartung: **A** Signal-Verteilung
(Soll ~43 % WARTEN), **B** Gate-Anteile inkl. Kennzeichnung „im Backtest modelliert?",
**C** TF-Wechsel-Rate (Soll: Median ≥15 min), **D0** Modell-Kalibrierung
(Ø-Vorhersage vs. echte Rate) und **D** Merkmals-Drift gegen `_PB_MU/_SD`.
⚠ **Lehre aus dem Bau selbst:** die Merkmals-Drift (D) hätte den realen Fehler
**NICHT** gefangen — mom3 lag nur **0,50σ** unter µ, bei Gewicht 1,43 wurden daraus
aber 0,71 im Logit (23 % statt 41 %). Deshalb ist **D0 der schärfere Test** und die
D-Schwelle auf 0,5σ gesetzt. Erster Lauf nach dem Fix: D0 zeigt **27,0 % vorhergesagt
vs. 28,0 % real** (vorher 23 vs 41) — die Reparatur ist damit live bestätigt.
- **Stufe 3 — ERLEDIGT (`core/exit_model.py`):** geteilter Exit-Kern. Enthält
**`LIVE: ExitParams`** (sl 2,0 · trail 1,5 · start 0,3 · be 1,3 · lock 3,5/0,6/1,2 ·
timestop 24 · tp 3,5 · max_hold 200) als **einzige Quelle** — `core/trailing.py`
importiert sie jetzt von dort (`_TRAIL_START_ATR`, `_BREAKEVEN_ATR`, `_PHASE4_*`,
`_MULT_BY_TF` sind abgeleitet, keine eigenen Zahlen mehr). Ändert jemand `LIVE.mult`,
ändern sich Live-Verhalten UND Messung gemeinsam → **Fall 3 ist konstruktiv
unmöglich geworden.** Dazu die kanonische `simulate()` für Backtests, mit Flags
`timestop`/`use_tp`, um Teilmodelle EXPLIZIT zu machen statt zu verstecken.
⚠⚠ **BEFUNDE BEIM REFACTOR — schlimmer als angenommen:**
(1) **Es gibt mindestens DREI materiell verschiedene Exit-Modelle** in den Backtests:
**(A)** Phasen-Modell ≈ live (`_trailing/_exit/_atrfloor/_candle_fade`) · **(B)
EINFACH** — `_TPTRAIL=0.5`, **kein** Breakeven, **kein** Lock, **kein** Time-Stop,
`_MAXH=240` (`_hourly`, `_hourly_split`, `_bounce`, `_events`, `_deadhour`,
`_chopgate`) · **(C)** Phasen mit **`be_on=1.0`** statt 1,3 (`_breakout`,
`_confluence_angle`). ⇒ **Mehrere dokumentierte Befunde (Dead-Hours, EIA-Blackout,
Bounce, Chop-Gate, Stunden-Analyse, breakout_k) ruhen auf einem Exit, der dem
Live-System NICHT entspricht.** Bewusst NICHT stillschweigend umgestellt — das
würde historische Schlüsse rückwirkend ändern; markiert als `LEGACY_SIMPLE` /
`LEGACY_BE10` in `exit_model.py`, Neubewertung = eigene Entscheidung.
(2) Selbst die „Phasen"-Skripte weichen voneinander ab: `_trailing` hat TP aber
**keinen** Time-Stop, `_candle_fade` Time-Stop aber **kein** TP, `_atrfloor` liefert
**Punkte statt R**. Live hat BEIDES → nur `timestop=True, use_tp=True` ist live-treu.
(3) **`backtest_trailing.py` koppelt den Initial-SL an `mult`** (`sl = entry
d*mult*atr`) — dadurch war die Stufe-1-Begründung teilweise falsch (s. o.).
✅ **Äquivalenz verifiziert** (500 synthetische Zufalls-Kursreihen je Fall):
`_candle_fade` (`timestop=True, use_tp=False`) und `_atrfloor` (`timestop=False,
use_tp=True`, ×ATR) sind **bitgenau identisch** zur neuen `simulate()`.
**Migrations-Regel (in `exit_model.py` festgehalten):** ein Backtest darf nur auf
`simulate()` umgestellt werden, wenn er danach DIESELBEN Zahlen liefert — sonst ist
es keine Refaktorierung, sondern eine unbemerkte Änderung der Messgrundlage.
**LEGACY-RECHECK (`backtest_legacy_recheck.py`, 2026-07-31): die beiden Befunde
nachgerechnet, die live ECHTE Gates steuern.**
· ✅ **EIA-Blackout BESTÄTIGT** — auch mit dem echten Exit + Echtkosten ist das
Fenster Mi 15:3016:30 in BEIDEN Hälften schlechter als der Rest (ØR-Differenz
0,097 / 0,119). Das aktive Gate ist gedeckt.
· ❌ **DEAD-HOURS reproduzieren NICHT als selektiver Befund** — mit dem echten Exit
sind **19 von 22 Stunden in BEIDEN Hälften negativ**, es gibt **keine** robust
positive Stunde. Die alte Analyse hatte 07 sowie 12 und 16 als *die* schlechten
Stunden herausgehoben; wenn fast alles negativ ist, ist diese Auswahl nicht mehr
gestützt. (Praktisch folgenlos, weil `dead_hours` ohnehin leer ist — aber eine
Reaktivierung auf der alten Begründung wäre nicht gedeckt.)
· ⚠ **BOUNCE/Reversal — relative Aussage hält, absolute NICHT.** Reversal schlägt
Trend in BEIDEN Hälften (H1 0,034 vs 0,174 · H2 **+0,029** vs 0,073, also
+0,140/+0,102) — robust. **Aber die dokumentierte Kennzahl „ØR +0,185 · PF 1,35 ·
70 % Treffer" REPRODUZIERT NICHT**: mit echtem Exit + Echtkosten liegt das Setup bei
~**Breakeven** (PF 0,93/1,06). Die alten Werte stammen aus dem vereinfachten Exit
OHNE Kosten. Das Setup bleibt sinnvoll als *nachrangiges, markiertes* Zweitsignal
(so ist es live auch eingebaut), aber es ist **kein +0,185-Ertragsbringer**.
· ✅ **ER-CHOP-GATE — Ablehnung BESTÄTIGT.** Alle vier ER-Bänder liegen gleichauf
(H1 0,137…−0,172 · H2 0,049…−0,067); die Efficiency Ratio trennt auch mit dem
echten Exit nicht. Gate bleibt zu Recht verworfen.
⚠ **Einschränkung des Rechecks (selbst benannt):** er handelt die **ungegatete**
EMA-Richtung — ohne Mindest-Konfidenz, Breakout-Bestätigung, HTF-Filter und
Raum-Gate. Für den VERGLEICH (Fenster gegen Rest, Stunde gegen Stunde) ist das
neutral, die absoluten Werte sind aber NICHT das Live-Signal.
⚠ **Konsequenz für die Erfolgskontrolle des TF-Churn-Fixes:** „WARTEN-Anteil
Richtung 43 % bewegen" ist als Zielgröße mit Vorsicht zu lesen — die 43 % stammen
aus `backtest_dist.py` (Signal-HÄUFIGKEIT), sie sind kein Ertragsversprechen. Mehr
Signale sind nur dann besser, wenn die freigegebenen Setups auch tragen.
⚠ **Erwartung: die drei Fälle sind nicht vollständig.** Sie wurden bei gezielter Suche
in ~20 min gefunden; die Trefferquote spricht für weitere. `analyze_divergence.py`
regelmäßig laufen lassen (Kandidat für den Wochenreport).
## ⚠⚠ News-Sentiment war GESÄTTIGT — Fix 2026-08-03
**Anlass:** User-Frage „können wir den X-Account anbinden, um den Copiloten mit mehr
Daten zu versorgen?" → erst prüfen, ob der vorhandene Textstrom überhaupt trägt.
**Befund: er trug nichts, weil der Score kaputt war.** `calc_news_sentiment` bildete
ein **reines Verhältnis** `(bullbear)/total` — **ohne jeden Bezug zur Beweismenge.**
Eine einzige bullische Schlagzeile bei null bearischen ergab damit **+1,00**.
| Diagnose | Wert |
|---|---|
| Log-Einträge auf ±1,00 gesättigt | **173 von 173 = 100 %** |
| davon mit einer Seite exakt 0 | **100 %** |
| Median-Beweismenge (bull+bear) | **2,8** gewichtete Headlines |
| DB gesamt (54.548 Zeilen) auf exakt +1,00 | **63,9 %** |
Real am 03.08.: Sentiment **+1,00** („bull=1.8, bear=0.0"), während WTI **5,9 %**
stand. Der Wert trug faktisch **ein Bit** (das Vorzeichen) — und löste im Entry-Check
dauerhaft falsche „News-Konflikt"-Warnungen aus (Schwelle |s|≥0,5).
⚠ Die Keyword-Listen sind NICHT die Ursache (56 bullisch / 48 bärisch, keine
Überschneidung) — es war allein die Formel.
**Fix:** Shrinkage `score = raw × total/(total+_EVIDENCE_K)`, `_EVIDENCE_K=4,0`
(gewählt, weil die reale Median-Beweismenge 2,8 beträgt → eine typische einseitige
3-Headline-Lage bleibt UNTER der 0,5-Warnschwelle; ~10 Headlines ergeben 0,71).
Zusätzlich liefert der Rückgabewert jetzt `raw_score` und `evidence`, damit im
UI/Copilot unterscheidbar ist, ob ein schwacher Score „ausgewogene Lage" oder „kaum
Daten" bedeutet. Der Leer-Fall gibt dieselben Felder zurück (sonst KeyError genau im
„keine News"-Fall). Mit 6 Szenarien getestet, live verifiziert: bei identischer
Beweislage (bull=1.8, bear=0.0) **+1,00 → +0,31**.
⚠⚠ **EHRLICH: Das repariert die KALIBRIERUNG, nicht die Aussagekraft.**
Gemessen wurde parallel die Prädiktivität (Forward-Return 120 min, 38.213 Paare über
84 Tage, 2 Hälften, **gegen die Regime-Drift gelesen** — H1 +0,005 $, H2 0,079 $):
| Bucket | H1 Überschuss | H2 Überschuss |
|---|---|---|
| bearisch ≤−0,5 | 0,029 ✓ | **+0,113 ✗ (Vorzeichen kippt)** |
| leicht bear | +0,118 ✗ | +0,031 ✗ |
| leicht bull | +0,106 ✓ | +0,044 ✓ |
| **bullisch ≥+0,5** (grösster Bucket) | 0,003 | **0,023 ✗** |
Der stark-bullische Bucket (16.241 / 11.818 Zeilen) liegt in BEIDEN Hälften bei null
bis negativ, der bearische kippt das Vorzeichen. **News-Sentiment ist nicht robust
prädiktiv** — auch mit korrigierter Skala. Es bleibt Kontext-Chip ohne Verdict-Stimme.
⚠ **Methodik-Falle beim Messen selbst:** der erste Lauf nutzte `candles_m1` (deckt nur
~14 Tage), wodurch `bisect` für alle älteren Zeitstempel denselben Randwert lieferte →
alle Forward-Returns exakt 0,0000. Erst der Lauf mit MT5-M5-Bars (JanAug) und einer
Deckungsprüfung (|Δt| ≤ 15 min) war gültig.
**Konsequenz für die X-Anbindung:** einen ZWEITEN Textstrom in eine Pipeline zu legen,
deren erster gemessen nichts vorhersagt, ist nicht begründbar — zumal X seit 2023
**keinen kostenlosen Lesezugang** mehr hat (Basic ≈ 200 $/Monat, Pro ≈ 5.000 $/Monat;
Scraping = ToS-Verstoß). Bei ~800 € Konto wären das ~25 % des Kontos pro Monat für
einen unvalidierten Input. Erst müsste der reparierte Score über ein paar Wochen
zeigen, dass Textdaten hier überhaupt etwas beitragen.
## KI-Copilot: warum er neutral ist, und was 2026-08-02 repariert wurde
**User-Frage:** „Der Bias ist fast immer neutral — können wir konkretere Empfehlungen
bekommen? Weitere Quellen (Kerzen, Muster)?"
**Diagnose — es ist NICHT der Prompt, es ist der Provider.** Der Bruch ist exakt
datierbar (`recommendations.ai_sentiment` je Tag): bis **19.07.** war der Copilot zu
**4290 %** gerichtet, ab **21.07.** nur noch zu **1,59,3 %**. Dazwischen liegt der
Wechsel auf **DeepSeek (2026-07-20)**. Der Prompt war die ganze Zeit unverändert.
⚠ Und er hedged **aus korrekter Schlussfolgerung**, nicht aus Schwäche: die
Begründungen sind konkret und zutreffend, und er bekommt zu ~90 % ein WARTEN-Signal,
einen negativen Track-Record UND die ausdrückliche Prompt-Anweisung, genau dann
NEUTRAL zu empfehlen. Er tut, was dort steht.
**Sind seine gerichteten Rufe etwas wert? Unentschieden — die Stichprobe trägt nicht.**
Forward-Return über `verdict_votes` × `candles_m1`: KI-LONG n=286 → **+0,29 $** in
Richtung / 76 % Treffer (60 min), KI-SHORT n=93 → 0,02 $ / 55 %. ⚠ **Das ist KEIN
Beleg:** die 286 Stimmen sind **20 Episoden an 5 Tagen** (Minutentakt-Logger), die 93
sind 16 Episoden — effektiv n≈20 gegen n≈16, alles in einem Regime. Dasselbe
Cluster-Muster, an dem ORB gescheitert ist. **Deshalb bewusst NICHT am Prompt gedreht,
um mehr Meinung zu erzwingen** — das würde eine korrekte Schlussfolgerung überschreiben
und einer unbelegten Stimme mehr Gewicht geben. Verdict-Gewicht bleibt 1,0 bei
gerichteter Aussage, 0 bei NEUTRAL.
**Zwei echte Prompt-FEHLER behoben (Korrektheit, keine Meinungsmache):**
1. `_SYS` beschrieb das Wellen-Signal als **„ATR-ZigZag"** — es ist seit Langem
**EMA12 vs. EMA50**; ZigZag wurde verworfen (~0 Edge). Das Modell missverstand also
die Natur des Hauptsignals.
2. `_SYS` nannte **„TradersUnion-Tachos"** als Input — TU ist seit 2026-07-19 gar nicht
mehr im Copilot-Kontext (`_tool_market`). Das Modell wurde auf Daten hingewiesen,
die es nie sieht.
**Die zwei kalibrierten Größen ergänzt.** Befund beim Nachsehen: `_tool_market` gab dem
Copiloten **nur Wellen-Signal + Session** — er war das schlechtest informierte Modul im
System und bekam ausgerechnet die einzigen beiden Komponenten nicht, deren Kalibrierung
out-of-sample nachgewiesen ist. Neu im Kontext: **`p_break_target` / `p_break_stop`**
(AUC 0,65 bzw. 0,680,72) und **`cone`** (80 %-Bänder, mit der REAL gemessenen Abdeckung
77 %, nicht dem Nennwert). Dazu eine Lese-Anleitung im Prompt — ohne sie liest ein LLM
„P(break) 30 %" als schwaches Signal statt als klare Aussage „Level hält mit 70 %";
4555 % ist ausdrücklich als Münzwurf markiert.
⚠ **Zwei Fallen beim Bau, beide an Live-Daten korrigiert:** (a) `p_break` steht im
Snapshot **bereits in Prozent** (3 = 3 %, deckungsgleich mit der Chart-Zeile „97 %
Abprall") — die zuerst gebaute ×100-Umrechnung hätte 300 erzeugt; (b) die Engine-Felder
`_last_sr_close_hint`/`_last_stop_approach`/`_last_cone` und die `engine`-Referenz im
Agent **existierten gar nicht** — der Fail-safe hätte still nichts geliefert. Beides
verdrahtet, der Snapshot rechnet die Werte ohnehin und weist sie jetzt nur zu (kein
zweites Rechnen, anders als beim Konsens-Pfeil). Mit Stub-Test verifiziert (Werte
unverändert durchgereicht, nur 80 %-Bänder, ohne Engine kein Absturz).
⚠ **Kerzen/Muster bewusst NICHT eingespeist.** Ein LLM wendet darauf die Lehrbuch-Lesart
an — und die ist hier gemessen **invertiert**: „langer Körper im Trend = Fortsetzung"
0,132/0,042 (Klimax, läuft dagegen); „2× Volumen = echt" → 0,117/0,204 (monoton
falsch herum); Muster-Kursziel trifft nur zu 1338 %. Roh eingespeist würden sie den
Copiloten aktiv verschlechtern. Offener Kandidat: sie MIT der gemessenen Lesart
mitgeben („langer Docht oben; gemessen läuft der Kurs danach eher GEGEN die Kerze") —
noch nicht gebaut.
## Vollprüfung aller Skripte + Nachrechnung der verworfenen Modelle (2026-08-04)
**Auftrag:** „alle Skripte ausgiebig testen und die verworfenen Modelle erneut
gegenrechnen." Werkzeug: `run_all_backtests.py` (probiert die Aufruf-Signaturen
adaptiv durch — die sind nicht einheitlich, s. Audit unten).
**Phase 1 — Lauffähigkeit: ✅ 87 von 87 Skripten laufen fehlerfrei** (8 000 Bars,
Timeout 900 s je Skript). Kein Defekt durch die heutigen `wave_rec`-Änderungen.
**Phase 2a — `breakout_k`: die dokumentierte Begründung REPRODUZIERT NICHT.**
Offener Punkt aus dem Stufe-3-Refactor: k=0,3 war mit dem `LEGACY_BE10`-Exit
(Breakeven 1,0) validiert. Erstmals mit dem **kanonischen** Exit nachgerechnet
(`backtest_breakout_canon.py`, echte `_confirm_breakout`-Mechanik, 80k M5,
2 Halbjahre, Echtkosten):
| k | H1 ØR / ΣR | H2 ØR / ΣR |
|---|---|---|
| 0,0 | 0,180 / 569 | 0,054 / 250 |
| **0,3 (LIVE)** | **0,170 / 356** | **0,069 / 217** |
| 0,5 | 0,142 / 262 | 0,074 / 208 |
| 0,8 | 0,112 / 166 | 0,032 / 73 |
| 1,0 | 0,101 / 130 | 0,004 / 8 |
**k=0,5 · 0,8 · 1,0 schlagen den Live-Wert 0,3 in BEIDEN Hälften** (ΣR-Differenz
bis +226/+209). Die Aussage „0,3 ist in beiden Hälften das Beste" gilt mit dem
Live-Exit **nicht mehr**.
⚠ **Trotzdem KEINE Änderung empfohlen**, aus drei Gründen: (a) **alle** Werte sind
negativ (PF 0,700,99) — es ist eine Wahl zwischen Verlusten, kein Edge;
(b) das Optimum lag am Rand des Suchbereichs → Gegenprobe bis k=3,0 zeigt **kein
sauberes Optimum** (H2: 8 → 17 → 20 → 19, H1 schwankt) — es ist also weder
reine Handelsvermeidung noch ein echter Bestwert; (c) gemessen wurde das
**UNGEGATETE** Signal (ohne min_conf, HTF-Filter, Entry-Raum), live laufen die
davor. **Nächster Schritt wäre eine GEGATETE Neumessung**, bevor ein Live-Gate
angefasst wird, das 63 % der WARTEN-Zustände erzeugt.
**Phase 2a-II — die GEGATETE Messung, nachgeholt (`backtest_breakout_gated.py`):**
Voller Live-Stack über die ECHTEN Methoden — `_build` (Totband · M30-Filter ·
Anti-Überdehnung · Winkel · min_conf 55 · Reversal AUS), Breakout-Bestätigung
1:1 aus `_confirm_breakout`, `_room_gate` mit echten kausalen M5-Pivots.
| k | H1 ØR / ΣR | H2 ØR / ΣR |
|---|---|---|
| 0,0 (Gate aus) | 0,173 / 193 | +0,003 / +7 |
| 0,2 | 0,185 / 149 | 0,012 / 16 |
| **0,3 (LIVE)** | **0,160 / 122** | **0,036 / 47** |
| 0,5 | 0,127 / 85 | 0,041 / 47 |
| 0,8 | 0,101 / 54 | 0,012 / 11 |
| 1,0 | 0,096 / 45 | 0,016 / 12 |
**Bestätigt: k=0,5/0,8/1,0 schlagen 0,3 in BEIDEN Hälften**, die Nachbarn halten
mit (0,8 und 1,0 nebeneinander) — die vorab fixierte Regel ist erfüllt. Auch die
ØR verbessert sich in H1 monoton (0,185 → 0,096), es ist also nicht nur
Handelsvermeidung.
⚠ **TROTZDEM keine Umstellung empfohlen — der Grund ist kein Messfehler, sondern
eine Zweckfrage:** (a) **alle** Werte bleiben negativ (PF 0,701,01); das Gate
begrenzt Schaden, es erzeugt keinen Edge. (b) Der Auto-Signal-Pfad ist ohnehin
AUS — das Signal ist ein **Vorschlag für den Menschen**, und dessen gemessener
Vorteil (+118 €/WR 56 % bei MIT-Signal-Trades) entstand auf der **k=0,3-Population**.
(c) k=0,8 kürzt die Empfehlungen um **~39 %** (H1 n=766 → 531) und treibt den
WARTEN-Anteil weiter hoch — der steht mit 93 % live ohnehin schon im Fokus, und
`breakout_pending` ist bereits die Ursache von 63 % aller Blocks.
**Vor einer Änderung ist zu klären, WOFÜR das Signal da ist:** als mechanischer
Auto-Entry wäre k=0,8 die belegte Wahl; als Auswahl-Angebot für den Menschen ist
weniger Auswahl nicht automatisch besser. Die Zahlen liegen vor, die Entscheidung
ist eine Nutzungs-, keine Messfrage.
⚠ **Zwei benannte Abweichungen der Simulation** (beide unvermeidbar):
(1) `_confirm_breakout` misst seinen Timeout mit `time.time()` gegen
`_breakout_timeout_s`=3600 s — in einem Bar-Loop vergeht keine Wall-Clock-Zeit,
der Timeout würde NIE feuern. 3600 s sind auf M5 exakt **12 Bars**, so wird hier
gezählt. **Merke: jede `time.time()`-Messung im Live-Code ist in einer
Bar-Simulation stumm.** (2) `hour=None`, weil die EIA-Prüfung in `_build` über
`datetime.now(_BERLIN)` läuft — mit Bar-Stunde würde ein Lauf am Mittwoch
15:3016:30 **alle** Bars blocken. `dead_hours` ist live leer, es fehlt also nur
das EIA-Fenster.
**Phase 2b — verworfene Modelle bei vollen Bars nachgefahren: ALLE bleiben verworfen.**
14 Klassen, keine einzige kippt ins Positive:
| Klasse | Ergebnis |
|---|---|
| ORB (EU/US, 15/30 min) | H1 0,018…−0,177 · H2 +0,013…+0,280 → Regime-Kippen |
| Doppeltop (3 Toleranzen) | 3/3 fallen durch |
| Volume Profile (POC/HVN/LVN) | 3/3 fallen durch |
| Liquidity Sweeps (SMC) | alle Varianten fallen durch |
| Konsolidierung (False Break) | alle Varianten fallen durch |
| Handbuch (PDH/PDL, Asian Range, Discount/Premium) | alle fallen durch |
| Marktstruktur | 5/5 fallen durch |
| Momentum · Inter-Market · Chop-Gate · Reversal-Lockout · S/R-Close-Signal · MACD/ADX/RSI · HTF-Winkel | unverändert verworfen |
⚠ **Bemerkenswert:** Bei ~14 Wiederholungstests wäre bei 5 % Fehlalarmquote rund
**ein** falsch-positives Ergebnis zu erwarten gewesen — es gab **keins**. Das
stützt die Ablehnungen zusätzlich. Die Regel wurde VOR dem Lauf fixiert (ØR>0 und
PF>1 in BEIDEN Hälften, Nachbarparameter müssen mithalten).
⚠ Das ist ausdrücklich **kein Freibrief für regelmäßiges Nachrechnen** — die
Begründung gegen wöchentliche Läufe (Mehrfachvergleich, ~47 Fehlalarme/Jahr)
steht unverändert. Anlass war hier die *einmalige* Signaländerung.
## Feste Einsatz-Margin + lokale Sprachausgabe (2026-08-04)
**Einsatz-Feld** (User-Wunsch „Feld, mit dem ich meine eingesetzte Margin selber
eingeben kann"): `[trading]`-unabhängiger Laufzeitwert `MANUAL_MARGIN` in
`core/config.py` (`set/get_manual_margin`, Muster wie `set_margin_buffer`).
**0 = automatisch** (bisheriges Verhalten: `margin_buffer_pct` % der freien Margin),
**> 0 = die Position wird auf genau diesen Margin-Einsatz gerechnet.**
· `calc_lots` setzt es um und **deckelt auf die freie Margin × Buffer** — ein zu
hoher Wunschwert wird begrenzt und geloggt, nicht an den Broker durchgereicht
(sonst Reject mit kryptischem retcode statt sauberer Meldung). Reicht der Betrag
nicht fürs Mindestlot → 0.0 + Warnung, der Trade wird sauber abgelehnt.
· **Vorrang vor dem Risiko-Modus** (`trader._send_locked`): eine ausdrückliche
Größenvorgabe schlägt die Rechenregel. Bei `risk_pct=0` (live) ohnehin der Fall.
· ⚠ **Wirkt auf ALLE neuen Positionen — auch die autonomen** (Squeeze/Signal).
· UI: Feld **„Einsatz"** in der **Order-Leiste** (`#margin-input`, v=136), bernstein
umrandet solange gesetzt. ⚠ **Bewusst NICHT in der Trade-Leiste** — die ist nur bei
OFFENER Position sichtbar, der Einsatz muss aber VOR dem Öffnen einstellbar sein.
Enter blurrt nur, `change` sendet einmal (gleicher Fix wie beim Mindestgewinn).
· `POST /api/manualmargin {value}` · Snapshot `manual_margin` · neustart-fest über
`runtime_state.json`. Ende-zu-Ende getestet (250 → Snapshot → 0 → persistiert).
**Einsatz in PROZENT (2026-08-05, v=137):** das Gegenstück zum festen Betrag —
`margin_buffer_pct` (live 95) hatte bisher **keine UI** und war nur in der ini
änderbar. Neu: Feld **„Einsatz %"** (`#marginpct-input`) direkt neben „Einsatz €" in
der Order-Leiste · `POST /api/marginpct {value}` → `engine.set_margin_pct` →
`config.set_margin_buffer` · Snapshot `margin_pct` · neustart-fest.
⚠ **VORRANG ist sichtbar gemacht:** steht links ein fester €-Betrag, wird das
Prozent-Feld **ausgegraut** (`.mg-field.muted`, gestrichelter Rand) und der Tooltip
sagt, dass der Prozentsatz dann nur noch die Obergrenze ist — zwei scheinbar
gleichrangige Felder ohne Hinweis wären die schlechtere Lösung.
⚠ Geklemmt auf **199 %** (`set_margin_buffer`); 100 % ließe keinen Puffer für
Spread/Swap. Ende-zu-Ende getestet: 50 → 50 · 150 → auf 99 geklemmt · 0 und 5
abgelehnt · 95 → 95, in `runtime_state.json` persistiert.
**Sprachausgabe: Piper, lokal** (`tools/speak.py`, `tools/piper/`). Anlass: die
Windows-Sprachausgabe funktioniert zwar, aber auf diesem Rechner ist **keine
deutsche Stimme installiert** — weder SAPI5 noch OneCore (nur David/Zira/Mark,
alle en-US). Deutscher Text käme mit englischer Aussprache. `Add-WindowsCapability`
scheitert ohne Adminrechte (elevated Fenster nötig).
Piper gewählt, weil es **lokal** läuft (passt zum Rest: WireGuard-only, Secrets in
der ini, keine Cloud-Abhängigkeit), **nichts kostet** (nach der 200-$/Monat-Absage
bei X relevant) und aus PowerShell/Python aufrufbar ist. Stimme *thorsten-medium*
(de_DE), Real-Time-Faktor **0,08** — 4,3 s Audio in 0,36 s.
⚠ **Das ZIP entpackt eine Ebene tiefer als erwartet** (`tools/piper/piper/piper.exe`)
— `speak.py` **sucht** die Binärdatei und das Modell deshalb, statt Pfade fest zu
verdrahten. Der erste Entwurf scheiterte genau daran; der Fail-safe hat die Pfade
sauber gemeldet statt stumm zu bleiben.
Aufruf: `python tools/speak.py "Text"` · Pipe · `--wav datei` (nur erzeugen).
Rückgabe 1 bei fehlendem Piper → ein Aufrufer im Bot läuft weiter, statt abzustürzen.
⚠ `tools/piper/` ist **gitignored** (Binaries + 60-MB-Modell gehören nicht ins Repo);
`speak.py` selbst ist versioniert.
## Modul „Manuelle Trades" + voller Entscheidungskontext (Stufe 1, 2026-08-04)
**User-Frage:** „Kann der Bot aus meinen manuellen Trades lernen — evtl. ein neues
Modul?" Auslöser: am 04.08. machten 18 manuelle Trades **+728 €**, der Bot 16,53 €,
bei 93 % WARTEN.
⚠ **Die exploratorische Vorprüfung ergab: noch NICHT lernbar** — und zwar aus drei
Gründen, die alle benannt gehören:
1. **Die entscheidenden Merkmale wurden nie gespeichert.** Beim Einstieg schrieb der
Bot 5 Felder; davon war `ai_sentiment` in **1,4 %** und `news_score` in **34 %** der
Trades befüllt. Die Marktlage eines manuellen Einstiegs war damit nicht
rekonstruierbar.
2. **Das naive Lernziel ist untauglich.** „Wann steigt er ein" bräuchte Gegenbeispiele
(Bars ohne Einstieg) — Millionen unbeschriftete Negative, und „nicht eingestiegen"
heißt nicht „hätte man nicht sollen". Die brauchbare Frage lautet: **welche seiner
Einstiege liefen gut** (saubere Labels über P&L).
3. **Die scheinbaren Muster sind Einzeltage.** Stunde 13:00 zeigt +478 € über n=69 —
davon stammen **+382 € (80 %) aus den fünf Trades des 04.08.** Genau die Falle, an
der ORB und die Dead-Hours gescheitert sind.
**Gebaut wurde deshalb Stufe 1 — Telemetrie, kein Verhalten:**
`trades` um **16 `ctx_*`-Spalten** erweitert (DB-Backup `…bak-2026-08-04`, 1.148
Zeilen unverändert): `p_break_target/-stop` · `dist_res_atr`/`dist_sup_atr` ·
`structure` · `squeeze` · `htf_trend`/`h1_trend` · `spread_atr` · `session` ·
`block_reason` · `conf_pct` · `atr_m5` · `bias` · `cone_pos` · `hour`. Alle Werte
werden im Snapshot ohnehin berechnet und nur eingesammelt
(`engine._run_analysis` → `trader.set_open_context(ctx=…)` → `history.log_trade_open`).
`log_trade_open` hängt die Spalten **dynamisch** an und ignoriert unbekannte Schlüssel
— eine ältere DB ohne Migration läuft weiter (fail-open).
⚠ **Fallstrick beim Bau:** `wave_snap` existiert in `_run_analysis` NICHT (nur in
`snapshot()`). Der erste Entwurf hätte einen `NameError` erzeugt, den das umgebende
`except` still geschluckt hätte — der Kontext wäre dauerhaft leer geblieben, ohne dass
es auffällt. Jetzt `self.wave.snapshot()`; `vd` zusätzlich per `locals()`-Guard
abgesichert (es entsteht in einem eigenen `try`).
Verifiziert auf einer DB-**Kopie**: alle 16 Spalten korrekt geschrieben, unbekannter
Schlüssel ignoriert, Basisfelder unberührt.
**Dashboard-Karte „Manuelle Trades"** (`#card-manual`, v=134): heute / 30 Tage /
gesamt / Bot-Vergleich, dazu der Split **mit vs. ohne Signal-Deckung**. Backend
`history.manual_stats(30)` → `engine._manual_stats_cached()` (~120 s Cache, wie
`_alignment_cached` — die Aggregation läuft über die ganze Tabelle und hat im
1-s-Snapshot nichts zu suchen). Bewusst **neutral gefärbt** (keine Ampel) und
ausdrücklich **deskriptiv**: die Karte zeigt eine Reifegrad-Zeile mit der
`ctx`-Abdeckung (heute 0 %), damit niemand sie für aussagekräftiger hält, als sie ist.
**Stufe 2 (später, NICHT gebaut):** wenn genug `ctx`-Daten vorliegen (~23 Monate),
messen was Gewinner von Verlierern unterscheidet — Fit auf H1, validiert auf H2, AUC
und Kalibrierung berichtet, wie beim P(break)-Modell. Ergebnis wäre ein **Hinweis im
Order-Dialog** („deine Trades in dieser Konstellation: X % WR, n=Y"), analog zum
bestehenden Ausrichtungs-Split. ⚠ **Ausdrücklich KEIN Auto-Entry daraus** — 22
Signal-Eingriffe sind gescheitert, und die gemessene Stärke des Users liegt in der
AUSWAHL, nicht in einer Regel. Ein Modul, das ihm seine eigene Historie zur aktuellen
Lage zeigt, spielt auf diese Stärke; eines das für ihn einsteigt, ersetzt sie.
## Backtest-Audit 2026-08-04 — rechnen die Skripte noch korrekt?
**Anlass:** User-Frage nach dem `angle=`-Fund in `backtest_auto_signal.py`. Prüfung
aller **87** Backtest-/Analyse-Skripte.
**Lauffähigkeit: ✅ in Ordnung.** Alle 87 kompilieren; 12 repräsentative (alle, die
`WaveRecommender` konstruieren) laufen fehlerfrei. Die heutigen `wave_rec`-Änderungen
(`_reversal_enabled`) brechen nichts — der Modul-Default `True` hält die Altmessungen
stabil. ⚠ Drei Skripte nehmen **`[TF] [N]`** statt einer Bar-Zahl als erstes Argument
(`backtest_signal.py`, `backtest_pullback.py`, `backtest_htf_filter.py`) — mit einer
Zahl als TF quittieren sie mit `KeyError`. Kein Defekt, nur die Signatur beachten.
**Der `angle=`-Befund: 11 Skripte, nicht 26.**
⚠ **Methodik-Fehler bei mir selbst, korrigiert:** Die erste Prüfung per Regex
(`_build\([^)]*angle\s*=`) meldete **26** Treffer — die Zeichenklasse `[^)]*` bricht an
der ersten Klammer ab, sodass Aufrufe wie
`w._build(..., htf_trend=m30s(T[i]), angle=ang)` fälschlich als „ohne Winkel" galten.
Die AST-Prüfung ergibt **11** echte Fälle (und erkennt zusätzlich Aufrufe, die `angle`
positional übergeben). **Regex ist für Aufruf-Analysen das falsche Werkzeug.**
Betroffen: `backtest_angle.py:78` · `backtest_auto_signal.py:177` ·
`backtest_confluence.py:144` · `backtest_dist.py:63` · `backtest_exit.py:150` ·
`backtest_htf_filter.py:106` · `backtest_improve.py:91` · `backtest_pullback.py:89` ·
`backtest_rev_exit.py:72` · `backtest_signal.py:91` · `backtest_stretch.py:73`.
**Wie schlimm ist es? Gemessen über 37.577 M5-Bars:**
| | mit Reversal (= bis 03.08.) | **ohne Reversal (= live seit 04.08.)** |
|---|---|---|
| Signal weicht ab | **19,1 %** | **1,1 %** |
| Ø-Konfidenz-Differenz | +7,7 | 4,4 |
| über dem 55 %-Gate | 79,4 → 90,6 % | 79,4 → 78,5 % |
| WARTEN-Anteil (ohne HTF) | — | 20,6 → 21,5 % (+1 Pp) |
⚠ **Das Reversal-Abschalten von heute hat die Lücke nebenbei fast geschlossen.** Solange
das Reversal aktiv war, sahen diese 11 Skripte eine Signal-Population, die sich in jedem
fünften Bar unterschied und systematisch 7,7 Konfidenzpunkte danebenlag — das betrifft
den Großteil der Projekt-Historie. **Ab jetzt sind sie näherungsweise gültig.**
⚠ Die WARTEN-Zeile ist NICHT mit der 43-%-Referenz vergleichbar: für die Messung wurde
`htf_trend` nicht übergeben, der M30-Filter (allein ~27 % der WARTEN) fehlt also. Nur
die **Differenz** (+1 Pp) ist aussagekräftig.
**Bewusst NICHT getan:** die 11 Skripte „repariert". Ein `angle=` zu ergänzen ändert
ihre Zahlen (~1 %) — das wäre laut Migrations-Regel keine Refaktorierung, sondern eine
Neumessung. Sie bleiben unverändert und reproduzierbar; wo ihre Schlussfolgerung wieder
relevant wird, gehört sie live-treu **neu gerechnet** (Muster:
`backtest_auto_signal_norev.py`).
## Gesamtprüfung 2026-08-02 (nach `docs/review-prompt.md`)
Erster vollständiger Durchlauf des Review-Prompts. **Keine Strategie-Änderung** — alle
Fixes betreffen Telemetrie, Doku und toten Code.
**Behoben:**
1. **`analyze_divergence.py` kannte keine Epochen** — und war damit selbst
driftanfällig. D0 mischte Vorhersagen des ALTEN und des am 31.07. nachtrainierten
P(break)-Modells und meldete dessen Fehlkalibrierung als aktuellen Alarm (das neue
Modell hatte **n=0**, weil der Markt seit Fr 22:55 zu ist); B las die
Prä-Migrations-`NULL`s von `block_reason` als blinden Fleck („None 66,8 %"); C
druckte bei 0 Zeilen ein „OK", obwohl es fehlende Daten waren. Neu: `_EPOCHS` +
`_clamp()` schneiden jede Auswertung auf den Beginn ihrer eigenen Datenlage.
⚠ **Beim Nachtrainieren eines Modells das Datum in `_EPOCHS` mitziehen** — sonst
schlägt der Wächter wochenlang Fehlalarm, und ein Wächter, der immer warnt, wird
ignoriert.
2. **Konsens-Pfeil: doppelte Arbeit für eine unsichtbare Linie.** Der MQL5-Export
rechnete im ~5-s-Takt ein **komplettes zweites `_verdict()`** für `AR;K` — der
Indikator verwirft die Zeile aber seit v1.33 per Default (`InpShowConsensus=false`).
Neu `[trading] export_consensus_arrow` (Default **false**, spiegelt den Indikator).
Nebeneffekt beseitigt: das zweite Verdict konnte vom Verdict im Snapshot abweichen.
3. **`/api/autosqueeze` loggt jetzt die Herkunft** (`[WEB] …`) wie `/api/autosignal`.
Vorher war bei einem Zustandswechsel nicht belegbar, ob Dashboard oder Code —
bei einem Schalter für autonome Echtgeld-Einstiege zu wenig.
4. **Reversal-Kennzahl an der Fundstelle korrigiert.** „ØR +0,185 · PF 1,35 · 70 %
Treffer" stand unverändert in der Reversal-Sektion, die Widerlegung 2 000 Zeilen
später im Legacy-Recheck. Wer die Sektion liest, sah die Korrektur nie.
5. Zwei tote `import datetime` in `core/notify.py` entfernt (beide Funktionen nutzen
`_time`), funktional nachgetestet.
**Geprüft und sauber:** 0 fehlende Frontend-IDs (von 86) · nur 2 Config-Schlüssel ohne
Leser (beide dokumentiert dormant) · Snapshot-Median **13 ms**, alle DB-Abfragen <13 ms,
Indizes vorhanden → **keine Performance-Maßnahme nötig** · 124 Datei- und 83
Funktionsreferenzen in CLAUDE.md stimmen · Asset- und Indikator-Version konsistent.
**Zwischenverdacht zurückgezogen:** „`block_reason` erklärt nur 33 % der WARTEN" war
ein reines Migrations-Artefakt (Spalte kam am 31.07.); seit 01.08. **100 % Abdeckung**.
**Offen (nicht behoben, bewusst):**
- ✅ **ERLEDIGT 2026-08-05: `auto_flip_close` ist jetzt SICHTBAR** (`#flip-note` in
der Meldungen-Karte, v=139). Zeigt Schwelle und Auslöse-Zähler und **nennt
ausdrücklich die Messung** („⚠ gemessen in beiden Halbjahren negativ", 92/160 R)
— ein blanker Zähler hätte wie ein Erfolg ausgesehen. Der Exit bleibt AN
(User-Wunsch), aber die Entscheidung ist jetzt informiert.
⚠ **Live-Gegenprobe 2026-08-05 (n=4):** die 4 Auslösungen brachten **+284,22 €**;
mit dem kanonischen Exit ab demselben Einstieg wären es **+183,51 €** gewesen —
der Flip war auf diesen Trades also **+100,71 € BESSER**. ⚠ Das ist KEIN Beleg:
n=4, und **+101 € stecken in EINEM Trade** (04.08. 16:15: +188,64 gegen +87,68).
Der 04.08. ist genau der Ausreißertag, der allein die 30-Tage-Bilanz trägt. Gegen
~2.000 Trades je Hälfte im Backtest (jede Variante, beide Hälften negativ) wiegt
das nichts — dieselbe Falle wie der 12k-Vorlauf beim Kerzen-Fade, der +0,291
zeigte und bei 80k verschwand. **Der Einwand ist geprüft, nicht bestätigt.**
✅✅ **ABGESCHALTET am 2026-08-05 (User-Entscheidung nach Vorlage der Messung),
`[trading] auto_flip_close = false`.** Erwarteter Gewinn 35250 R je nach
Messung, Nachteil praktisch keiner — **der Alarm bleibt unverändert**
(Blinken/Ton/Telegram bei Signal-Flip im Plus). Der Alarm-Pfad
`_check_close_alert` hängt an `_run_analysis`, NICHT am `_pos_loop`-Aufruf von
`_check_flip_close` — nur die automatische Ausführung entfällt. Damit ist wieder
der dokumentierte Ursprungszustand hergestellt: „Flip bleibt Alarm + menschliche
Entscheidung" (User-Übersteuerungen liefen gemessen 68 % WR).
Live verifiziert nach Neustart: `flip_close=False`, Alarm-Zähler unberührt.
⚠ **Kein `runtime_state.json`-Override vorhanden** (anders als bei `auto_signal`,
wo ein UI-Toggle die ini still überschrieb) — die ini-Änderung greift also direkt.
Neu im **Config-Wächter** (`measurement_reminder.py`, validiert = `false`): wird
der Schalter je wieder auf `true` gesetzt, läuft ein gemessen negativer Exit auf
echtem Geld, und das soll auffallen. Zurückholen: `auto_flip_close = true`.
- **`breakout_k=0,3` ruht weiter auf dem `LEGACY_BE10`-Exit** (Breakeven 1,0 statt
live 1,3). Der Legacy-Recheck hat EIA und Dead-Hours nachgerechnet, `breakout_k`
nicht. Kandidat für den nächsten Recheck.
- Die 5 Zähler-Snapshot-Felder (`close_alert_count` usw.) werden von nichts abgefragt.
## Ausstehende Messungen — Erinnerung per Timer
`measurement_reminder.py` + Windows-Task **`OilMeasurementReminder`** (täglich 18:00)
meldet per Popup, welche Messung **genug Daten** hat. Jeder Punkt hat eine PRÜFBARE
Fälligkeitsbedingung (Datenmenge/Datum), nicht nur ein Datum — gemeldet wird je Punkt
1× (Marker in `.reminders/`, gitignoriert; Marker löschen = Wiedervorlage).
Status jederzeit: `python measurement_reminder.py --status`.
⚠ **Warum es das gibt:** mehrere Module sammeln still Daten für „später" — und das
blieb liegen. Die P(break)-Prognose-Genauigkeit war seit 2026-07-23 auswertbar und
hatte am 2026-07-30 bereits **2213 ausgewertete Zeilen**, ohne dass jemand hinsah.
| Messung | Bedingung | Stand 2026-08-01 |
|---|---|---|
| P(break) erneut auswerten (nach Nachtraining) | ≥800 Vorhersagen ab 01.08. | 0 |
| **Modul-Audit: zweiter Durchgang** | ≥28 Tage seit dem 1. Audit | 2 (6 %) |
| Verdict-Module auf Prädiktivität (`verdict_votes`) | ≥25 000 Verdicts | 8142 (33 %) |
| **Manuelle Trades: Stufe 2** (was trennt Gewinner von Verlierern?) | ≥300 manuelle Trades **mit `ctx_*`** | 0 (Sammlung ab 04.08.) |
| **Auto-Squeeze: B5-Abbruchprüfung** (Latte vorab fixiert) | ≥20 Squeeze-Trades ab 01.08. | 2 (10 %) |
| ~~M30-Level-Umstellung live gegenprüfen~~ | **ERLEDIGT 04.08.** — s. u. | ✅ |
| M1-Squeeze Variante B | ≥180 Tage `candles_m1` | 14 (8 %) |
| News-Score-Prädiktivität | ≥90 Tage Sammlung | 9 (10 %) |
⚠ **Der Config-Wächter liest jetzt auch `runtime_state.json` (Fix 2026-08-01).** Er
verglich vorher nur die **ini** gegen die validierten Werte — die UI-Schalter leben aber
in `runtime_state.json` und werden beim Start **über** die ini gelegt. Dadurch meldete er
„alles auf dem gemessenen Stand", während live etwas anderes lief. Real: `auto_signal`
stand in der ini auf `false`, war aber durch einen Dashboard-Toggle dauerhaft `true` —
4 der 7 Bot-Trades vom 31.07. kamen aus diesem gemessen-negativen Pfad. `_cfg_now()`
bildet den Vorrang jetzt genauso ab wie der Engine-Start; mit einem Szenario verifiziert
(runtime `true` gegen validiert `false` → wird gemeldet). Neu überwacht: `auto_signal`
(soll `false`) und `auto_squeeze` (soll `true`).
### ✅ M30-Level-Umstellung live gegengeprüft (04.08.) — bestätigt, mit einem Vorbehalt
| Zeitraum | n | Med. Lots | **EUR/Lot** | Ø Move |
|---|---|---|---|---|
| **vorher** (M5-Level) | 89 | 0,81 | **+17,53** | 0,202 $ |
| **nachher** (M30-Level) | 28 | 0,53 | **+36,92** | 0,427 $ |
Ertrag **je Lot mehr als verdoppelt**, Ø-Kursbewegung je Trade ebenfalls — deckt sich
mit der Backtest-Erwartung („R-Ertrag rund doppelt") und mit dem Mechanismus (M30-Level
liegen 1,95×ATR auseinander statt 0,61×ATR, der Trade läuft also länger).
⚠⚠ **Die NORMIERUNG war entscheidend, sonst wäre der Schluss falsch gewesen.**
Unnormiert sah der 04.08. wie der Treiber aus (Ø **+55,41 €** je Trade gegen +19,30 €
im Rest). **Je Lot ist heute aber praktisch identisch zum Rest** (+38,81 gegen +35,87)
— der Unterschied war reine Positionsgröße (Median 1,36 statt 0,46 Lots, weil das
Konto an diesem Tag von ~800 auf ~1.400 € wuchs). Ø-EUR-Vergleiche über Zeiträume mit
unterschiedlicher Positionsgröße sind ungültig; das gehört bei jeder künftigen
Live-Auswertung mitgedacht.
⚠ **Nicht der Level-Quelle ALLEIN zuzuordnen:** am 31.07. kamen das
P(break)-Nachtraining UND die Schwellensenkung 0,55 → 0,35 dazu. Getrennt:
30.07. (nur M30-Level, altes Modell) n=**8**, +19,88 €/Lot · ab 31.07. (M30 + neues
Modell) n=20, +43,73 €/Lot. Das Fenster mit nur der Umstellung ist zu klein für eine
Zuordnung. **Der kombinierte Effekt ist bestätigt, die Ursachenaufteilung nicht.**
Beide Änderungen waren im Backtest validiert und zeigen live in dieselbe Richtung.
*(erledigt und ausgetragen: P(break)-Genauigkeit → `analyze_pbreak_live.py` 31.07.,
Modell daraufhin nachtrainiert · Chartmuster-Kontrolltest → 30.07., Muster schlagen
die Kontrolle in beiden Hälften, Gewicht 1,0)*
⚠ **Der Task `OilMeasurementReminder` wurde am 2026-07-31 testweise ausgelöst und
lief mit Ergebnis 0 durch** (Arbeitsverzeichnis korrekt gesetzt) — ein Erinnerungs-
system, das still scheitert, wäre schlimmer als keins.
Verwandte Timer: **`HyperliquidBookflow`** (Order-Flow, 2 Stufen ~3 Tage/~2 Wochen),
**`HyperliquidLeadLag`** (erledigt: Pepperstone führt), Wochenreport montags 07:30 (B4).
## WTI-Trading-Handbuch — Abgleich mit dem Gemessenen (2026-07-30)
Der User reichte ein 19-seitiges WTI-Handbuch ein („lerne daraus Kontext für die
Empfehlung"). Ergebnis des Abgleichs: **der belegbare Teil ist längst umgesetzt, der
Rest ist in diesem Projekt bereits gemessen — und fiel durch.** Kein Konzept aus dem
Handbuch wurde neu ins Signal aufgenommen.
**Bereits UMGESETZT (Handbuch bestätigt den Ist-Zustand):**
| Handbuch | im Bot |
|---|---|
| „Nur mit dem HTF-Trend" | M30-Gegen-Trend-Filter (gemessen: Edge ×2) + Entry-Checkliste |
| „Keine Trades vor EIA" | EIA-Blackout Mi 15:3016:30 (gemessen: beidhälftig negativ) |
| ATR-basierter Stop + Puffer | Initial-SL-Band 1,82,2×ATR (gemessen besser als 1,5) |
| „Nach TP1 trailen" | Trailing 1,5×ATR + Breakeven 1,3 (gemessen optimal) |
| „Nach 3 Losses Pause" | Mental-Check in der Entry-Checkliste (Verlust-Serie des Tages) |
| Journal + Kennzahlen | `trades`-DB, Statistik-Tab, Wochenreport (WR/PF/Verhältnis) |
| NY-Session am besten | gemessen: 2122 Uhr sind die einzig robust positiven Stunden |
**Bereits GEMESSEN und VERWORFEN (Handbuch empfiehlt es trotzdem):**
Liquidity Sweeps (Equal Highs/Lows) · Order-Block-Klasse = Pivot-Zonen (6×) ·
Double Top/Bottom · H&S/Triangle/Flag/Cup (Ziel-Trefferquote nur 1338 %) ·
Wyckoff Accumulation/Distribution · False Breakout · DXY-Korrelation
(`backtest_intermarket.py`) · Elliott (nur Verdict-Stimme, Gewicht 1,0).
**NEU gemessen (die echten Lücken, `backtest_handbook.py`) — alle durchgefallen:**
| Konzept | H1 | H2 | Urteil |
|---|---|---|---|
| **PDH/PDL** Bounce („WTI reagiert extrem stark") | 0,335 | 0,199 | ❌ beidseitig negativ |
| **PDH/PDL** Sweep | 0,462 | 0,184 | ❌ schlechtestes Ergebnis überhaupt |
| **Asian Range H/L** Bounce | 0,134 | 0,005 | ❌ |
| **Asian Range H/L** Sweep | 0,178 | 0,040 | ❌ |
| **Discount/Premium**-Filter | 0,097 (Basis 0,135) | +0,041 (Basis +0,053) | ❌ nicht robust, wirft 70 % Volumen weg |
| **Freitag ab 16:00 CET meiden** | 0,179 (Basis 0,135) | +0,020 (Basis +0,053) | ❌ **verschlechtert BEIDE** |
⚠ Zwei Befunde sind bemerkenswert: (a) **PDH/PDL ist der am klarsten schädliche
Level-Typ**, den das Projekt je gemessen hat — obwohl das Handbuch ihn als besonders
stark bewirbt. (b) Der **Freitagnachmittags-Filter verschlechtert beide Hälften**, die
dort geblockten Trades waren also überdurchschnittlich — dieselbe Lehre wie beim
Dead-Hours-Gate: pauschale Zeitfilter kosten mehr, als sie sparen.
**Nicht umgesetzt (bewusste User-Entscheidungen, nicht Messfrage):** 12 % Risk/Trade
und 3 % Tages-Limit (User fährt 95 % Margin, Circuit Breaker am 2026-07-30 erneut
abgelehnt) · Mindest-R:R 1:2 (der Bot nutzt Trailing statt festem Ziel — gemessen
besser) · Teil-Exit bei 1:1 (`_PARTIAL_TP_FRAC=0`, User-Vorgabe).
## Auch beachten
- **Startup-Schonfrist gegen Sofort-Close nach Neustart (User-Vorgabe 2026-07-20,
`[trading] startup_close_grace_s=60`):** In den ersten 60 s nach Bot-Start schließt
der Bot **keinen laufenden Trade selbst** — gilt für **S/R-Close** (`_check_sr_close`),
**Notfall-Stop/Gewinn-Ziel** (`_check_auto_close`, Armieren läuft weiter, nur der Close
ist gesperrt), **Time-Stop** (`trailing`, via `set_no_close_until`) und **Auto-Squeeze-
Reverse**. Grund: nach `restart_server.bat` sind Preis/ATR/P(break)/Level für einen
Moment instabil → ein adoptierter Trade, der gerade in S/R-Close- oder Notfall-Nähe
liegt, würde sonst in der ersten Sekunde auf halb-initialisierten Daten gekappt.
Engine setzt `_no_autoclose_until = start + grace`, `_in_startup_grace()` gated die
drei `_pos_loop`-Checks; die Trailing-Instanz bekommt denselben Stichtag. ⚠ Der
**Broker-SL (2×ATR) bleibt unberührt** — er läuft am Broker, kann NICHT verzögert
werden (dort ist auch keine Verzögerung erwünscht: ein Stop-Loss muss greifen). 0 = aus.
- **Update-Reboot-Lücke (Root-Cause der Nacht 2026-07-20, 95,48 €):** Windows
Update rebootete 01:30 (MoUsoCoreWorker/TrustedInstaller, 2×), der Server-Task
`OilTradingServer` hat nur einen **Logon-Trigger** → Server+MT5 down bis zum
Login 07:42; Time-Stop/S/R-Close/Notfall-Stop liefen nicht, nur der **Broker-SL**
(broker-seitig!) schützte. **Fix:** WU-Policy `AUOptions=4, Tag=Sa, 03:00`
(HKLM\...\WindowsUpdate\AU — Reboots nur noch Samstag früh, Markt zu); Standby/
Hibernate AC+DC=0. ⚠ Offen: nach einem Sa-Reboot startet der Server erst beim
nächsten Login — **Auto-Logon (netplwiz) wäre die letzte Lücke** (So-Nacht-Open),
braucht das Windows-Passwort des Users. Server-seitige Schutzschichten sind nur
so gut wie die PC-Laufzeit.
- **UI-Einstellungen neustart-fest (`runtime_state.json`, 2026-07-19):** App-gesetzte
Werte — 🚀-BRK-Toggle (`auto_squeeze`), Auto-Close-Schalter (`auto_sr_close`),
Gewinn-Close-Mindestgewinn (`sr_close_min_gain`) — werden bei jedem `set_*` atomar
persistiert (`_load/_save_runtime_state`) und überschreiben beim Start die
ini-Defaults (nur vorhandene Keys). ⚠ **`set_sr_close_min_gain` vergaß den `_save`
bis 2026-07-22** (Bug, jetzt gefixt) — der am Handy gesetzte Mindestgewinn überlebte
Änderungen nicht sauber; zusätzlich schickte Enter auf leerem Feld ungewollt 0
(`change`+`keydown` doppelt → Enter blurrt jetzt nur, `srclose_min`-Endpoint loggt `[WEB]`).
- **Log-Hygiene:** Trail-DEBUG-Zeile (`Trail: profit=…`) auf **1×/30 s gedrosselt**
(2026-07-19; vorher ~28k Zeilen je Rotation → 5×5 MB deckten nur ~2 Tage Forensik).
- **DB-Guard:** `history.log_trade_open` verwirft 0-Lot-/Preis-lose Einträge
(Reconcile-Artefakte; 6 Alt-Zeilen am 2026-07-19 bereinigt, Backup
`oil_widget_history.db.bak-2026-07-19`).
- Persönliches Auto-Memory unter `C:\Users\ah\.claude\projects\…\memory\`
(Beobachtungen/Feedback) ergänzt diese Datei.
- `oil_widget_config.ini` enthält **Live-Secrets** (Keys/Token) — nicht ins
Repo/Logs leaken.
- **DB** `oil_widget_history.db` (trades · intended_trades · recommendations · **candles_m1** · **pbreak_predictions** · **verdict_votes**).
Vor jedem Eingriff Backup (`*.bak-<datum>`); Zeitspalten sind lokale Epoch
(Broker-Offset bereits korrigiert) — **AUSNAHME `candles_m1.time` = ROHE Broker-Zeit
(UTC+3)**, deckungsgleich mit `copy_rates`.
- **M1-Candle-Logger (`core/candle_logger.py`, 2026-07-21, Daten-Sammlung, kein
Strategie-Eingriff):** schreibt abgeschlossene M1-Kerzen (OHLC + Spread-in-Preis +
tick_volume + symbol) nach `candles_m1` (PK `time`, INSERT OR IGNORE = dedupliziert,
self-healing). `engine.candles.log(sym)` im Trend-Loop, self-throttled ~55 s; erster
Lauf backfillt ~3000 Bars, danach die letzten 180. **Warum:** Broker-M1-History rollt
weg (kurz); M1 ist die Master-Auflösung → aus M1 jede höhere TF exakt aggregierbar.
⚠ Ticks/Echtzeit bewusst NICHT geloggt (~85k/Tag = 30M/Jahr, kein Bedarf — Strategien
sind M5+, Slippage aus echten Fills gemessen). ~370k Bars/Jahr = vernachlässigbar.