diff --git a/.gitignore b/.gitignore index 1937dcb..b36d44d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,10 @@ # ── SECRETS (NIEMALS committen) ───────────────────────────────────────────── oil_widget_config.ini +# ⚠ AUCH die Backups! `oil_widget_config.ini` allein deckt `…ini.bak-` NICHT +# ab — beim Modul-Audit 2026-07-31 lag ein solches Backup ungeschützt als untracked +# im Repo. Die Datei enthält dieselben Live-Keys/Token wie das Original. +oil_widget_config.ini.* +oil_widget_config.ini.bak* API Key*.txt *.key .env diff --git a/CLAUDE.md b/CLAUDE.md index 87c70ca..ee3cc7e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1385,7 +1385,42 @@ dort bereits nachvalidiert, ØR +0,305.) **Eine** Oberfläche: `1.5 if vote != 0 else 0.0`; `others` filtert bereits auf `weight>0`, die „x/y einig"-Zählung zieht mit. Live verifiziert: M30 „flach" → Gewicht 0,00, bias −1,5/4,5 = **−0,333** statt −1,5/6,0 = −0,250. REINE ANZEIGE. + ⚠ **MODUL-AUDIT 2026-07-31 (User: „überprüfe alle Module auf Inkonsistenzen"):** + systematisch alle Verdict-Module gegen die Regel „keine Aussage → Gewicht 0" + geprüft, dazu Config-, Frontend- und Level-Quellen. **5 Befunde, alle behoben:** + **(1) KI-Copilot — der größte Rest des Musters.** Ein explizites „NEUTRAL" zählte + als echte Stimme mit Gewicht 1,0. Gemessen (`verdict_votes`, n=7.084): das ist + **in 93,1 % der Fälle** der Zustand (Elliott: 1,3 %) — also der Normalzustand des + Copiloten, kein abgewogenes Urteil. Damit hatte ausgerechnet das Modul mit der + schwächsten Beleglage (`analyze_verdict_calibration.py`: nicht robust prädiktiv) + den GRÖSSTEN dämpfenden Einfluss. Jetzt `1.0 if av != 0 else 0.0`. + **(2) Elliott** analog: Gewicht nur bei gerichtetem Ziel (1,3 % der Zeilen). + **Kumulierte Wirkung aller Gewichts-Fixes des Tages: Ø|bias| 0,370 → 0,441 → + **0,569** (= 1,54×), betrifft 79,5 % der Zeilen.** Live verifiziert: KI „NEUTRAL" + → Gewicht 0,00, bias 2,5/4,5 = **0,556** statt 2,5/5,5 = 0,455. + **(3) Stale `[zones]` speisten die Wellen-Konfidenz — entfernt.** `_sr_levels` zog + beide Kanten jeder `[zones]`-Zone als S/R-Linien heran: **16 Kanten aus einem ~10 $ + TIEFEREN Regime** (72,90–77,00 bei Kurs 85,3). Aus dem CHART waren sie am 14.07. + schon entfernt worden, WEIL sie stale sind — in die Konfidenz liefen sie weiter. + Realer Audit-Fall: `market.sr` hatte GAR KEINE Widerstände (M15-Cluster sind in + frischen Trends oft leer) → der „nächste Widerstand" kam aus der `fvg`-Zone bei + **88,00** (2,66 $ weg), der echte lag bei **85,315** (0,03 $ weg). Der Abzug „dicht + unter Widerstand" (−12) greift nur innerhalb 0,5×ATR und feuerte deshalb **nie**. + Entfernen ist keine Strategieänderung, sondern das Streichen von Fremdregime-Daten; + `parse_zones` bleibt in `config.py`, die ini-Sektion bleibt (reaktivierbar). + **(4) Tote Config entfernt** (Backup `oil_widget_config.ini.bak-2026-07-31`): + `[trading] trail_timeframe = M1` (nirgends gelesen, Name suggeriert fälschlich, er + steuere die Trailing-TF) und die komplette **`[setups]`-Sektion** (11 Schlüssel, + vollständig tot). + **(5) `raise StopIteration`** als Sprung innerhalb eines breiten `except Exception` + (Trade-Marker-Export) durch ein `if` ersetzt — funktionierte, wäre aber fragil, + sobald dort jemand Logging ergänzt (Phantom-Fehler im ~5-s-Takt). + ✅ **Sauber:** Frontend↔Backend (0 verwaiste Element-IDs), alle übrigen + Verdict-Gewichte, alle anderen Config-Schlüssel. ⚠ **Offen (dokumentiert, nicht geändert):** + (0) **Wellen-Konfidenz aus `_draw_levels` speisen** statt aus dem Misch-Set — würde + den −12-Abzug tatsächlich auslösen, also Konfidenz senken und das 55%-Gate + verschieben. **Strategieänderung → messpflichtig.** (a) **Drei divergierende Level-Quellen** seit der M30-Umstellung: `_draw_levels` (M30 → Chart/S/R-Close), `pb_levels` (M5 → Entry- Raum-Gate), `_sr_levels` (Misch-Set → Wellen-Konfidenz); real wichen die Supports diff --git a/core/engine.py b/core/engine.py index e157d61..b9e22db 100644 --- a/core/engine.py +++ b/core/engine.py @@ -22,8 +22,11 @@ import MetaTrader5 as mt5 from core.config import ( HISTORY_DB_FILE, MAGIC, load_config, set_margin_buffer, set_risk_per_trade, - parse_zones, ) +# ⚠ `parse_zones` wurde am 2026-07-31 hier entfernt (letzter Nutzer war `_sr_levels`, +# s. dort): die `[zones]`-Config stammte aus einem ~10 $ tieferen Kursregime und +# lieferte der Wellen-Konfidenz Phantom-Level. Die Funktion bleibt in `core/config.py` +# und die `[zones]`-Sektion in der ini erhalten — reaktivierbar, falls je gewollt. from core.logger import get_logger from core.history import HistoryLogger from core.mt5data import MT5Data @@ -1898,13 +1901,24 @@ class TradingEngine: for s in (sr.get("supports") or []): p = s.get("price") if p: (above if p > bid else below).append(p) - # Stehende Zonen aus der Config (beide Kanten als Linien) - try: - for z in parse_zones(self.cfg): - for edge in (z["lo"], z["hi"]): - (above if edge > bid else below).append(edge) - except Exception: - pass + # ⚠ [zones]-CONFIG ENTFERNT (2026-07-31, Modul-Audit). Vorher flossen hier + # beide Kanten jeder `[zones]`-Zone als S/R-Linien ein — 16 Kanten aus einem + # ~10 $ TIEFEREN Kursregime (Zonen bei 72,90–77,00 bei einem Kurs von 85,3). + # Aus dem CHART wurden sie schon am 2026-07-14 entfernt, WEIL sie stale sind; + # in die Wellen-Konfidenz liefen sie aber weiter. + # Realer Fall beim Audit (Kurs 85,342): `market.sr` hatte GAR KEINE + # Widerstände (M15-Cluster sind in frischen Trends oft leer) → der nächste + # „Widerstand" kam aus der stale `fvg`-Zone bei **88,00** (2,66 $ weg), + # während der echte bei **85,315** lag (0,03 $ weg). Der Konfidenz-Abzug + # „dicht unter Widerstand" (−12) greift nur innerhalb 0,5×ATR und feuerte + # deshalb NIE, obwohl er hätte feuern müssen. + # Entfernen ist KEINE Strategieänderung, sondern das Streichen von Daten aus + # einem anderen Preisregime: fehlt ein Level, gibt es weder Bonus noch Abzug — + # das ist ehrlicher als ein falsches Level. + # ⚠ OFFEN (messpflichtig, NICHT hier gemacht): die Wellen-Konfidenz stattdessen + # aus `_draw_levels` (M30, dieselbe Quelle wie Chart + S/R-Close) speisen. Das + # würde den −12-Abzug tatsächlich auslösen → Konfidenz sinkt → das 55%-Gate + # verschiebt sich. Strategieänderung, gehört gemessen. # Tages-Level aus der Morgen-Recherche (Kontext) try: for lv in self.daily.zone_lines(): @@ -2181,23 +2195,26 @@ class TradingEngine: # für Trade open und close" — kurz nach dem Einbau wieder verworfen). # Code bleibt reaktivierbar via `[trading] export_trade_marks=true`; # spart im Aus-Zustand die DB-Abfrage im ~5-s-Export-Takt. - try: - if not self._export_trade_marks: - raise StopIteration - boff = self.trader._broker_offset_s() - marks: list[tuple[str, int]] = [] - ps = self.trader.snapshot() - if ps.get("ticket") and ps.get("open_time"): - marks.append(("O", int(float(ps["open_time"]) + boff))) - for t in (self.history.last_closed_trades(_TRADE_MARKS) or []): - if t.get("entry_time"): - marks.append(("O", int(float(t["entry_time"]) + boff))) - if t.get("exit_time"): - marks.append(("C", int(float(t["exit_time"]) + boff))) - for kind, ts in marks[:2 * _TRADE_MARKS + 1]: - lines.append(f"TR;{kind};{ts}") - except Exception: - pass + # ⚠ Kein `raise StopIteration` mehr als Sprung (2026-07-31, Audit): + # StopIteration IST eine Exception und wurde vom breiten `except` unten + # gefangen — funktionierte, wäre aber sofort fragil, sobald dort jemand + # Logging ergänzt (Phantom-Fehler bei JEDEM Export-Takt). + if self._export_trade_marks: + try: + boff = self.trader._broker_offset_s() + marks: list[tuple[str, int]] = [] + ps = self.trader.snapshot() + if ps.get("ticket") and ps.get("open_time"): + marks.append(("O", int(float(ps["open_time"]) + boff))) + for t in (self.history.last_closed_trades(_TRADE_MARKS) or []): + if t.get("entry_time"): + marks.append(("O", int(float(t["entry_time"]) + boff))) + if t.get("exit_time"): + marks.append(("C", int(float(t["exit_time"]) + boff))) + for kind, ts in marks[:2 * _TRADE_MARKS + 1]: + lines.append(f"TR;{kind};{ts}") + except Exception: + pass # Zonen NICHT mehr aus der (statischen) [zones]-Config — die dynamische # Handels-Range zeichnet der Indikator selbst aus den Chart-Kerzen (TF-genau). path = os.path.join(self._mql5_files_dir, "sr_levels.csv") @@ -2260,13 +2277,21 @@ class TradingEngine: # lagt + nicht backtestbar + Standard-Indikator-Konfluenz = kein Edge. Bleibt # nur noch reine Anzeige (Snapshot `tu`), zählt NICHT mehr in Bias/Agree. - # 5) KI-Copilot — OHNE Aussage (kein advisory/bias, z. B. Agent aus) → Gewicht 0 - # (stimmt nicht mit ab, Chip bleibt sichtbar). Explizites "NEUTRAL" = echte - # Neutral-Stimme mit Gewicht. + # 5) KI-Copilot — Gewicht NUR bei gerichteter Aussage (LONG/SHORT). + # ⚠ GEÄNDERT 2026-07-31 (Modul-Audit): vorher zählte ein explizites "NEUTRAL" + # als echte Stimme mit Gewicht 1,0. Gemessen an 7.084 Verdicts ist das aber + # **in 93,1 % der Fälle** der Zustand — also der Normalzustand des Copiloten, + # kein abgewogenes Urteil (Elliott zeigt dasselbe Muster nur in 1,3 %). + # Damit hatte ausgerechnet das Modul mit der SCHWÄCHSTEN Beleglage + # (`analyze_verdict_calibration.py`: nicht robust prädiktiv) den GRÖSSTEN + # dämpfenden Einfluss auf die Bias-Nadel. Wirkung der Angleichung: + # Ø|bias| 0,441 → 0,569 (Median-Verstärkung 1,25×, betrifft 79,5 % der Zeilen); + # kumuliert mit den Fixes von Welle/Squeeze/M30/H1: 0,370 → 0,569 = 1,54×. + # Der Chip bleibt sichtbar und zeigt „NEUTRAL", er stimmt nur nicht mehr mit ab. adv = (agent or {}).get("advisory") or {} ab = adv.get("bias") av = 1 if ab == "LONG" else -1 if ab == "SHORT" else 0 - add("KI-Copilot", av, 1.0 if ab else 0.0, + add("KI-Copilot", av, 1.0 if av != 0 else 0.0, f"{ab or '—'} · {adv.get('confidence', 0)}%") # 6) Elliott/FVG — nur stimmberechtigt, wenn ein Ziel existiert (sonst w=0) @@ -2275,7 +2300,11 @@ class TradingEngine: if tgt is not None and bid: ev = -1 if tgt < bid else 1 if tgt > bid else 0 edet = f"{(elliott or {}).get('wave', '—')} → {tgt:.3f}" - ew = 1.0 + # ⚠ Gewicht nur bei GERICHTETEM Ziel (2026-07-31, Modul-Audit): liegt das + # Ziel exakt auf dem Kurs, ist ev=0 — das ist keine Aussage und darf die + # Nadel nicht dämpfen. Betraf 1,3 % der Zeilen (klein, aber dieselbe Regel + # wie bei Welle/Squeeze/M30/H1/KI — Konsistenz statt Sonderfall). + ew = 1.0 if ev != 0 else 0.0 add("Elliott", ev, ew, edet) # 7) Squeeze-Breakout — Event-Signal: stimmt NUR bei `active` mit (dann 2,0,