Files
AH-Oil-Trader/CLAUDE.md
T
Axel HocksandClaude Opus 4.8 21d855b163 Chartmuster im MT5-Terminal zeichnen (Datei-Bridge + SR_Levels.mq5 v1.11)
Engine schreibt die top-2 erkannten Muster als PT;name;dir;trigger;target;status
in sr_levels.csv; SR_Levels.mq5 zeichnet Trigger-/Nackenlinie (rot=bear/gruen=bull,
kraeftig) + gepunktete Ziel-Linie + Label. Reine Anzeige. Indikator nach Update
mit F7 neu kompilieren.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 18:59:25 +02:00

1351 lines
107 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 (Stand aktuell):** **DeepSeek (`[deepseek]`) ist der aktive
Copilot-Provider** (`[agent] provider=deepseek`, 2026-07-20). `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). Auto-Start:
Aufgabe `OilTradingServer` (Login, versteckt via VBS).
- **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).
- **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).
- **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`.
- **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.
- **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 — profitabel, aber **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.)
- **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 80 % (User-Vorgabe 2026-07-20, `risk_pct=0`,
`margin_buffer_pct=80`):** `calc_lots` setzt die Lots auf ~80 % der freien Margin.
Verlauf 90→95→75→(1,5 % risiko, wenige Stunden am 2026-07-20)→**80 % margin** —
der User hat die Margin-Wahl damit 3× getroffen (bewusst; nicht ungefragt umstellen).
⚠ 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.
- **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 = 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".
- **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=3.0`) — Stand 2026-07-24 abends wieder AN
(Verlauf: 1,0→3,0 am 07-23 · 0 am 07-24 früh „nichts beim Öffnen setzen" ·
3,0 am 07-24 abends auf User-Wunsch zurück — 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 (beim 0-Stellen am 07-24 auch den persistierten
Alt-Wert 17,17 in `runtime_state.json` genullt — Erb-Falle).
**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-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).
- **„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.
- **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`):** der Bot
eröffnet **nachts 07 Uhr KEINE** Squeeze-Trades (`_SQUEEZE_NIGHT=(0..7)`,
`_squeeze_skip_night` → `_check_auto_squeeze` return; kein Entry UND kein Reverse).
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).
**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).
**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.
- **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.
**„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/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): eine 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`.
**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.
**S/R-Overlay:** `get_bars` liefert `levels {res, sup}` aus **`engine._draw_levels`**
= echte Kursstruktur (rohe M5-Pivots aus `wave.pb_levels`, 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 / **`PT;name;dir;trigger;target;status`** =
erkanntes Chartmuster (2026-07-27, top 2 aus `patterns.snapshot`); Quelle
**`_draw_levels`** (S/R) + **`structure.channel_line`** (Kanal) + **`self.patterns`**
(Muster), Indikator-Farben blau DodgerBlue/DeepSkyBlue, Muster rot/grün);
der Indikator **`mql5/SR_Levels.mq5`** (**v1.11** — zeichnet jetzt auch die Muster:
Trigger-/Nackenlinie kräftig + gepunktete Ziel-Linie + Label; 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). 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.
- **Karten** (Dashboard, von oben): Gesamtempfehlung · **Meldungen** · Marktstruktur ·
KI-Copilot · Letzte Trades · Kurslücken · **Statistik (live) ganz unten**.
(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.
- **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.**
- **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}`; 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).
- **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`.
- **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=116** (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.
## 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|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.
## 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.