Files
AH-Oil-Trader/CLAUDE.md
T

95 KiB
Raw Blame History

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_contentmax_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 contentmax_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 (WR68 % · PF1,3 · Verh0,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, 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). 1,0 wäre gemessen noch besser (H1 +415/H2 +266 R), filtert aber ~¾ der Signale → Start 0,6, per B4-Wochenreport prüfen und ggf. via ini erhöhen.
  • 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 schlechter2500…−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_analysishistory.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=heuristicengine._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 (Default 0,60, Band 0,300,90). Snapshot sr_close_hint {level, p_break, at_level, gap_atr, close, confirm}. Hinweis existiert NUR mit offener Position. confirm = Bruch-BESTÄTIGUNGS-Preis (Fix 2026-07-22, User-Frage nach einem Trade, der am Level geschlossen wurde, obwohl der Kurs „durchgebrochen" war): level ± 0,5×ATR in Trade-Richtung — dieselbe Definition wie beim Modell- TRAINING (_BRK_ATR=0,5/_BRK_W=12 in backtest_srclose_prob.py/ analyze_srwaves.py). Ein Docht, der die nackte Levelzahl nur um wenige Cent überschreitet, zählt NICHT als Durchbruch — reales Fallbeispiel 2026-07-22: Close bei Level 85,943 (P(break) 31 %), Kurs wischte auf 85,994 (< Bestätigung 86,017 = Level+0,5×ATR) und fiel zurück → nach Modell-eigener Definition eine ABLEHNUNG, keine Bestätigung; die Vorhersage war korrekt. confirm jetzt in der Log-Zeile („— Bestätigung erst ab X") UND im Frontend-Hinweistext (#pos-srhint, alle 3 Zweige: Vorwarnung/Close/Laufen) sichtbar — macht den Unterschied „Kurs > Levelzahl" vs. „strukturell bestätigter Bruch" auf einen Blick klar. Live-Prognose-Tracking (2026-07-23, User-Wunsch „mitloggen wie oft richtig/ falsch"): jeder frische Level-Touch (at_level False→True) loggt EINMAL die aktuelle Vorhersage (bounce wenn P(break)<Schwelle, sonst break) in die neue Tabelle pbreak_predictions (history.log_pbreak_prediction, Felder u. a. predicted, confirm_price, reject_price, atr). Auswertung läuft separat, self-throttled ~5 min im _trend_loop (engine._evaluate_pbreak_predictions): prüft gegen candles_m1, ob der Kurs binnen 60 min zuerst confirm_price (→ outcome break) oder reject_price (→ bounce) erreicht — exakt dieselbe Definition wie beim Modell-TRAINING (_BRK_ATR=0,5/_BRK_W=12 M5-Bars=60 min). Kein Treffer binnen 60 min → Timeout, zählt wie im Training als bounce. correct = predicted==outcome. Broker-Zeit-Offset (trader._broker_offset_s()) korrekt berücksichtigt (pbreak_predictions.ts = lokale Epoch, candles_m1.time = rohe Broker-Zeit — Ausnahme, s. „Auch beachten"). Abfrage: history.pbreak_accuracy (period) bzw. GET /api/pbreak_accuracy?period=today|week|all (read-only, kein Token) → {n, accuracy, break_accuracy, bounce_accuracy, pending, …}. Getestet mit synthetischen Szenarien (break/bounce/Gegenteil-falsch/Timeout) — alle 4 exakt wie erwartet. Reine Messung, kein Signal-/Exit-Eingriff — noch keine UI-Karte (Datensammlung; B4-artige Auswertung später möglich, wenn genug Zeilen vorliegen). Gegen-/Stop-Seiten-Anzeige stop_approach (2026-07-24, User-Frage „Kurs nähert sich Support, aber keine Durchbruchwahrscheinlichkeit angezeigt"): Der reguläre sr_close_hint beobachtet nur das Ziel-Level (LONG→Resistance, SHORT→Support) und wirkt nur im Plus — nähert sich der Kurs dem Gegen-/Stop-Level (LONG→Support darunter, SHORT→Resistance darüber, typ. Verlust-Trade), war die App bewusst still. Neu: engine._stop_approach_hint liefert für GENAU dieses Level eine reine Anzeige von P(Durchbruch) — Break-Richtung -d (gegen den Trade), Feature-/ 60-s-Glättung (_stop_pb_state) identisch zu _sr_close_hint. KEIN Auto-Close. Snapshot stop_approach {level, side, p_break, at_level, gap_atr, confirm, reject}, Frontend #pos-stophint (grün „Level hält wahrscheinlich" bei P<50, rot „bricht wahrscheinlich durch" bei ≥50; nur wenn KEIN Ziel-sr_close_hint läuft; v=109). Vorher gemessen (backtest_pbreak_calibration.py, 2026-07-24, Gate „erst messen, dann bauen"): Das kalibrierte P(break)-Modell (auf Ziel-Seite trainiert) hält auch auf der Gegen-/Stop-Seite — AUC 0,68/0,72 out-of-sample in BEIDEN Hälften (≈ Ziel-Seite 0,67/0,71), im niedrigen P-Bereich sauber kalibriert (der praktisch wichtige „hält der Support?"-Fall). ⚠ Einzige Schwäche: im HOHEN Bereich (P≥0,5) GEGEN den Trend unterschätzt es die Bruch-Rate ~1013 Pp (predicted 58 % → echt 71 %) — also konservativ (warnt eher zu wenig vor einem Bruch), kein Kalibrierungs-Bruch. Deshalb reine Anzeige, kein Trade-Trigger. Anti-Flattern (2026-07-14, gemessen analyze_pbreak_flicker.py: mom3 der laufenden M5-Kerze ließ P 715 Pp/Bar springen, Max 77 Pp; 8 % der Annäherungen hatten ≥2 Schwellen-Flips): (1) P zeit-geglättet (~60-s-EMA in _pb_state, Reset bei Level-Wechsel/flat; angezeigtes p_break = geglättet) · (2) Hysterese ±5 Pp um die Schwelle — close↔laufen wechselt erst bei klarem Durchbruch (55/65 bei Default 0,60, liegt im Optimum-Plateau 0,500,60). Dashboard-Schalter in der Position-Kachel (#srclose-btn, grün=AN) → POST /api/srclose (Toggle, engine.set_sr_autoclose); Zustand snapshot.auto_sr_close. AUTO-Close (_check_sr_close im _pos_loop, auto_sr_close=true): schließt nur im PLUS (pnl>0) wenn close=True, 1×/ Ticket, wirkt bei gesperrtem Handy. Optionaler Mindestgewinn ([trading] sr_close_min_gain Default 0=aus · UI #srmin-input · POST /api/srclose_min · set_sr_close_min_gain · Snapshot sr_close_min_gain): unter der EUR-Schwelle wird am Level NICHT geschlossen. ⚠ Gemessen ist OHNE besser (Verworfen-Eintrag) — Opt-in auf User-Wunsch, UI-Status warnt. Automatisch auf X % des Einsatzes ([trading] sr_close_min_gain_pct) — Stand 2026-07-24 AUF 0 = AUS (User-Vorgabe „gewinn close … nicht setzen beim Eröffnen"; war kurz 1,0→3,0 am 2026-07-23). 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, Margin aus trader.refresh() im selben _pos_loop-Tick, persistiert via _save_runtime_state, Log „Gewinn-Close armiert … +X (N% Margin Y)"). Bei pct=0 (aktuell) wird beim Öffnen NICHTS gesetzt — es bleibt der manuell im UI gesetzte/gemerkte sr_close_min_gain (Default 0). ⚠ Beim Abschalten 2026-07-24 auch den persistierten Alt-Wert in runtime_state.json auf 0 gesetzt (stand auf 17,17 vom letzten 3%-Auto-Set), sonst hätte ihn jeder neue Trade als festen Mindestgewinn geerbt. Mechanik im Code erhalten (reaktivierbar via ini). 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 = AUS (User-Vorgabe 2026-07-17, [trading] auto_squeeze_skip_night=false): der Bot eröffnet jetzt auch nachts 07 Uhr auf Squeeze-Ausbrüche. Der ursprüngliche Guard (_SQUEEZE_NIGHT=(0..7), _squeeze_skip_night) bleibt dormant/reaktivierbar (true = wieder an). Warum raus: das live-Verlust-Argument (3 Nacht-Squeezes 46 €) war emergency-getrieben und mit dem Notfall-Stop-Ausbau moot; der gemessene Nacht-Kosten-Befund (backtest_realcosts.py, Spread÷Nacht-ATR 0,320,50×ATR, beide Halbjahre negativ) bleibt aber gültig → Nacht-Squeezes sind unbelegt, per B4- Wochenmonitor beobachten; tragen sie nicht, Guard wieder =true. ⚠ Erste echte Autonomie, jetzt rund um die Uhr — B4-Monitor gegen +0,14…+0,23; driftet es → Button aus / auto_squeeze=false / Guard zurück. 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). 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.
  • Kein automatischer Tagesverlust-Schalter (Circuit Breaker): vom User abgelehnt — NICHT einbauen. Bei 90 %-Margin-Sizing bleibt Drawdown-Risiko hoch.
  • 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). 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-Bridgeengine._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; Quelle _draw_levels (S/R) + structure.channel_line (Kanal), Indikator-Farben blau DodgerBlue/DeepSkyBlue); der Indikator mql5/SR_Levels.mq5 (v1.10, 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._verdictverdict im Snapshot; Render renderVerdict (app.js). Details: docs/gesamtempfehlung.md.
  • 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_statssnapshot.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.
  • 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=113 (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_sltptrader.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). 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.