diff --git a/CLAUDE.md b/CLAUDE.md index ebeaf61..371ea29 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -402,6 +402,46 @@ dort bereits nachvalidiert, ØR +0,305.) **Eine** Oberfläche: ⚠ **Lehre:** die naheliegende Antwort („numpy") war die falsche. Ein Profil kostet zwei Minuten und hätte diese Bremse jederzeit gezeigt — sie lag seit Monaten in jedem gegateten Backtest. +- **⚠⚠ PIPELINE-STUFE 1 IST JETZT AUTOMATISCH — `pre-commit`-Hook (2026-08-07).** + Nach einer Web-Recherche zum 2026er-Python-Standard umgesetzt, aber **gefiltert**: + von uv/Ruff/pyproject/CI ist hier nur das sinnvoll, was zu einer Einzelmaschine + mit GUI-gebundenem MT5 und Live-Geld passt. + **(1) Abhängigkeiten festgehalten** — `requirements.txt` (Laufzeit) und + `requirements-dev.txt` (Werkzeuge/Messung), **Versionen gepinnt**. ⚠ Bis dahin + gab es **gar keine** Liste; nach einem Maschinenausfall wäre die Rekonstruktion + Raten gewesen. Ermittelt per AST über `core/`, `server.py`, `tools/` — nicht + geraten. + **(2) `tools/pre_commit.py`** fährt vor jedem Commit `check_nfalle` + `pytest` + (~5 s). Einrichten: `python tools/pre_commit.py --install`. Notausgang + `git commit --no-verify` ist Absicht — eine Prüfung, die einen dringenden Fix + blockiert, wird sonst deinstalliert. + ⚠ **Bewusst OHNE das `pre-commit`-Framework**: dessen Nutzen ist Werkzeug- + Versionierung über mehrere Maschinen. Hier gibt es eine. Es würde stattdessen + eine ZWEITE ruff-Version in einer isolierten Umgebung installieren — genau die + Divergenz „Prüfumgebung ≠ Laufumgebung", die heute schon einmal zugeschlagen hat. + **(3) Ruff statt pyflakes** (`ruff check --select F821`) — gleiche Befunde, + ganzer Scan in 2,1 s. ⚠ **Kein `ruff format`**: das würde 161 Dateien + umformatieren, darunter alle Backtests → Migrations-Regel verletzt. + ⚠⚠ **VIERTER Fall der Fehlerklasse „Erfolg melden, wo nichts geprüft wurde" — + und diesmal fiel er nur durch einen Test auf.** `--select F821,E999` lässt ruff + 0.16 **komplett abbrechen** („Rule E999 was removed", Exit 2); die Meldung geht + nach stderr, und weil ich den **Rückgabecode nicht prüfte**, meldete die Stufe + „keine undefinierten Namen" — obwohl sie **gar nichts** geprüft hatte. Ein + absichtlich eingebauter `F821`-Fehler kam **glatt durch den Hook**. Behoben: + nur `F821`, und **Exit 2 = Werkzeugfehler** löst jetzt den pyflakes-Rückfall aus + statt als „sauber" zu gelten. + ✅ **Beide Richtungen verifiziert** (wie beim Deploy-Skript): mit eingebautem + Fehler → Hook **stoppt** den Commit; ohne → grün. Der Test-Commit wurde + zurückgenommen (war nur lokal). + ⚠ **Nicht umgesetzt und warum:** *Gitea Actions* — der Runner liefe auf + derselben Maschine wie der Live-Bot und konkurrierte um CPU, bei 5 s Prüfzeit + ohne Mehrwert gegenüber dem Hook. *Container/Blue-Green* — unmöglich, MT5 + braucht eine Desktop-Sitzung, und es gibt EINE Broker-Verbindung. + *mypy/ty* — kaum Annotationen vorhanden, grosser Aufwand für wenig Ertrag. + ⚠ **Struktureller Fund dabei:** `core/engine.py:3527` importiert + **`backtest_breakout_squeeze`** (ebenso `weekly_review.py`). Ein Backtest ist + damit Teil des **Live**-Abhängigkeitsgraphen — „Backtests sind nur Doku" stimmt + nicht, ein Fehler dort kann den laufenden Bot treffen. - **⚠⚠ TEST-SUITE (`pytest`, `tests/`, seit 2026-08-07) — 17 Tests, ~1 s.** Im Projekt sind über Monate **dutzende Szenario-Tests** entstanden („mit 10 Szenarien getestet", „mit 18 synthetischen Szenarien"), alle als diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..6882941 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,21 @@ +# WERKZEUGE fuer Entwicklung, Messung und Pruefung — NICHT fuer den Live-Betrieb. +# +# ⚠ Bewusst getrennt von `requirements.txt`: der Bot soll sich auf einer frischen +# Maschine mit einer moeglichst kleinen Oberflaeche wiederherstellen lassen. scipy +# und scikit-learn haben nichts im Handelspfad zu suchen, auch wenn Backtests sie +# nutzen. +# +# Installation: python -m pip install -r requirements-dev.txt + +-r requirements.txt + +# ── Pruefung (laeuft im pre-commit-Hook) ───────────────────────────────── +pytest==9.1.1 +ruff==0.16.1 # ersetzt pyflakes: gleiche Befunde, deutlich schneller +pyflakes==3.4.0 # Rueckfall in `tools/check_nfalle.py`, falls ruff fehlt + +# ── Messung / Backtests ────────────────────────────────────────────────── +numpy==2.2.6 +scipy==1.18.0 # `backtest_cost_gate.ci95` (Bootstrap-Konfidenzintervalle) +scikit-learn==1.9.0 # bislang ungenutzt; Kandidat fuer P(break)-Nachbau +pandas==3.0.2 # bislang ungenutzt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..2a0f0d2 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,40 @@ +# LAUFZEIT-Abhaengigkeiten des Bots (server.py + core/). +# +# ⚠ WOZU: bis 2026-08-07 gab es GAR KEINE Abhaengigkeitsliste. Stirbt die +# Maschine, waere die Rekonstruktion Raten gewesen — bei einem System, das +# echtes Geld bewegt, die groesste Luecke der Pipeline. +# +# ⚠ Versionen sind GEPINNT, nicht offen. Ein stiller Minor-Sprung (z. B. bei +# `MetaTrader5` oder `fastapi`) kann den laufenden Bot brechen; das faellt dann +# nachts auf, nicht beim Update. Bewusst aktualisieren statt zufaellig erben. +# +# Installation auf einer frischen Maschine: +# python -m pip install -r requirements.txt +# +# ⚠ Erzeugt aus dem LAUFENDEN Stand (pip freeze, gefiltert auf die real +# importierten Pakete — ermittelt per AST ueber core/, server.py, tools/). +# Nicht handgepflegt raten: bei neuen Importen hier nachtragen. + +# ── Broker-Anbindung (Windows-only, kein Ersatz moeglich) ──────────────── +MetaTrader5==5.0.5735 + +# ── Web-Backend fuer die Mobile-PWA (Port 8000) ────────────────────────── +fastapi==0.137.1 +uvicorn==0.49.0 +websockets==16.0 # WS-Push /ws +# starlette/pydantic kommen als fastapi-Abhaengigkeiten, hier zur Nachvollzieh- +# barkeit gepinnt (sonst waehlt pip beim Neuaufbau evtl. andere Versionen) +starlette==1.3.1 +pydantic==2.13.3 + +# ── Nachrichten, Kalender, Uebersetzung ────────────────────────────────── +requests==2.33.1 +beautifulsoup4==4.14.3 # importiert als `bs4` +feedparser==6.0.12 +deep-translator==1.11.4 + +# ── KI-Copilot (LLM-Provider) ──────────────────────────────────────────── +# ⚠ Beide Clients sind eingebunden; aktiv ist laut `[agent] provider` nur einer. +# Die z.ai-/DeepSeek-/Kimi-Endpunkte laufen ueber den openai-kompatiblen Client. +anthropic==0.99.0 +openai==2.34.0 diff --git a/tools/check_nfalle.py b/tools/check_nfalle.py index df0f175..8c91fce 100644 --- a/tools/check_nfalle.py +++ b/tools/check_nfalle.py @@ -19,6 +19,7 @@ Geprueft wird: import pathlib import py_compile import re +import subprocess import sys import tempfile @@ -80,31 +81,73 @@ print(f" {npy} Dateien geprueft.") SCHWER = ("undefined name", "undefined local", "syntax error") print() print("=" * 88) -print(" A2) PYFLAKES — undefinierte Namen (was py_compile nicht sieht)") +print(" A2) LINTER — undefinierte Namen (was py_compile nicht sieht)") print("=" * 88) +# ⚠ RUFF statt pyflakes (2026-08-07, Web-Recherche zum 2026er-Standard): gleiche +# Befunde, deutlich schneller, ein Werkzeug statt vieler. Bewusst NUR +# `ruff check` mit ENGER Regelauswahl — **kein `ruff format`**: das wuerde 161 +# Dateien umformatieren, darunter alle Backtests, und damit die Migrations-Regel +# verletzen (deren Zahlen sind dokumentiert). +# F821 = undefined name ← die Klasse, die hier real weh tut +# E999 = syntax error +# F811 (redefinition) bleibt DRAUSSEN, s. Begruendung bei SCHWER oben. +# pyflakes bleibt als Rueckfall, damit die Pruefung auch ohne ruff laeuft. +# ⚠⚠ NUR F821 — `E999` gibt es in ruff 0.16 NICHT MEHR. Wird es trotzdem +# selektiert, bricht ruff KOMPLETT ab („Rule E999 was removed") mit Exit 2. +# Genau das ist beim ersten Bau passiert, und schlimmer: der Rueckgabecode +# wurde nicht geprueft, die Fehlermeldung ging nach stderr — die Stufe meldete +# also „keine undefinierten Namen", waehrend sie GAR NICHTS geprueft hat. Ein +# absichtlich eingebauter `F821`-Fehler kam glatt durch den Hook. +# Syntaxfehler deckt ohnehin Stufe A (`py_compile`) ab. +# ⚠ RUFF-EXIT-CODES: 0 = sauber · 1 = Befunde · 2 = Werkzeugfehler. +# 2 darf NIEMALS als „sauber" durchgehen. +_lint_ok = False try: - import io - - from pyflakes.api import checkPath - from pyflakes.reporter import Reporter - - aus, err = io.StringIO(), io.StringIO() - rep = Reporter(aus, err) - for p in dateien(".py"): - checkPath(str(p), rep) - zeilen = [z for z in (aus.getvalue() + err.getvalue()).splitlines() if z.strip()] - schwer = [z for z in zeilen if any(s in z for s in SCHWER)] - for z in schwer: + r = subprocess.run([sys.executable, "-m", "ruff", "check", + "--select", "F821", "--no-cache", + "--output-format", "concise", str(WURZEL)], + capture_output=True, text=True, timeout=180) + if r.returncode not in (0, 1): + print(f" ⚠ ruff meldet einen WERKZEUGFEHLER (Exit {r.returncode}) — " + f"Rueckfall auf pyflakes:") + for z in (r.stderr or r.stdout).strip().splitlines()[:3]: + print(f" {z}") + raise RuntimeError("ruff nicht nutzbar") + _lint_ok = True + treffer = [z for z in r.stdout.splitlines() + if z.strip() and not z.startswith(("Found", "All checks", "warning:"))] + for z in treffer: kurz = z.replace(str(WURZEL) + "\\", "").replace(str(WURZEL) + "/", "") - befunde.append(f"PF {kurz}") + befunde.append(f"RUFF {kurz}") print(f" FEHLER {kurz}") - if not schwer: - print(" keine undefinierten Namen.") - print(f" ({len(zeilen) - len(schwer)} kosmetische Hinweise unterdrueckt — " - f"ungenutzte Importe, f-Strings ohne Platzhalter)") -except ImportError: - print(" ⚠ pyflakes nicht installiert — Stufe uebersprungen.") - print(" Installation: python -m pip install pyflakes") + if not treffer: + print(" keine undefinierten Namen (ruff, F821/E999).") +except Exception: + pass + +if not _lint_ok: + try: + import io + + from pyflakes.api import checkPath + from pyflakes.reporter import Reporter + + aus, err = io.StringIO(), io.StringIO() + rep = Reporter(aus, err) + for p in dateien(".py"): + checkPath(str(p), rep) + zeilen = [z for z in (aus.getvalue() + err.getvalue()).splitlines() + if z.strip()] + schwer = [z for z in zeilen if any(s in z for s in SCHWER)] + for z in schwer: + kurz = z.replace(str(WURZEL) + "\\", "").replace(str(WURZEL) + "/", "") + befunde.append(f"PF {kurz}") + print(f" FEHLER {kurz}") + if not schwer: + print(" keine undefinierten Namen (pyflakes-Rueckfall).") + except ImportError: + print(" ⚠ weder ruff noch pyflakes installiert — Stufe uebersprungen.") + print(" Installation: python -m pip install -r requirements-dev.txt") # ── B) JavaScript ──────────────────────────────────────────────────────── diff --git a/tools/pre_commit.py b/tools/pre_commit.py new file mode 100644 index 0000000..447d903 --- /dev/null +++ b/tools/pre_commit.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 +"""Pre-Commit-Pruefung: faehrt die Stufe-1-Checks automatisch vor jedem Commit. + +⚠ WOZU. Am 2026-08-07 habe ich `py_compile`, `pytest` und `check_nfalle` rund +fuenfzehnmal von Hand gestartet — und trotzdem gingen zwei Fehler durch, weil +ich sie im falschen Moment aufrief: die `\\n`-Falle fiel erst beim naechsten +Aufruf auf, die beiden `log.`-Fehler in `core/trader.py` lagen MONATE drin +(einer seit dem Initial-Commit). Ein Hook vergisst das nicht. + +WAS GEPRUEFT WIRD (~3–4 s): + 1. `tools/check_nfalle.py` — kompiliert alle .py, ruff (F821/E999), + JS-String-Literale, die \\n-Falle in beide Richtungen, .ps1-BOM + 2. `pytest` — die Szenario-Suite + +⚠ NOTAUSGANG: `git commit --no-verify` ueberspringt den Hook. Das ist Absicht — +eine Pruefung, die einen dringenden Fix blockiert, wird sonst dauerhaft +deinstalliert. Wer ihn nutzt, sollte die Pruefung danach nachholen. + +⚠ BEWUSST OHNE das `pre-commit`-Framework: dessen Nutzen ist die Verwaltung von +Werkzeug-Versionen ueber mehrere Entwickler und Maschinen hinweg. Hier gibt es +eine Maschine und einen Entwickler; ein eigenes, versioniertes Skript ist +transparenter und braucht keine isolierten Zusatz-Umgebungen (die dann eine +ANDERE ruff-Version haetten als die Kommandozeile — genau die Art Divergenz, +die dieses Projekt schon oft genug hatte). + +Aufruf: + python tools/pre_commit.py # Pruefung von Hand + python tools/pre_commit.py --install # als Git-Hook einrichten +""" +import subprocess +import sys +import time +from pathlib import Path + +WURZEL = Path(__file__).resolve().parents[1] +PY = sys.executable + + +def lauf(titel: str, argv: list[str]) -> bool: + t0 = time.time() + r = subprocess.run(argv, cwd=WURZEL, capture_output=True, text=True) + dauer = time.time() - t0 + if r.returncode == 0: + print(f" ✅ {titel:<28} {dauer:.1f}s") + return True + print(f" ❌ {titel:<28} {dauer:.1f}s") + # Nur die letzten Zeilen — der Hook soll die Ursache zeigen, nicht fluten. + for z in (r.stdout + r.stderr).strip().splitlines()[-25:]: + print(f" {z}") + return False + + +def installieren() -> int: + hooks = WURZEL / ".git" / "hooks" + if not hooks.is_dir(): + print(" Kein .git/hooks-Verzeichnis gefunden."); return 1 + ziel = hooks / "pre-commit" + # ⚠ Git fuer Windows fuehrt Hooks ueber `sh` aus, nicht ueber cmd — deshalb + # ein sh-Shim mit Vorwaertsschraegstrichen. + ziel.write_text( + "#!/bin/sh\n" + "# Erzeugt von tools/pre_commit.py --install\n" + f'exec "{Path(PY).as_posix()}" -X utf8 ' + f'"{(WURZEL / "tools" / "pre_commit.py").as_posix()}"\n', + encoding="utf-8", newline="\n") + print(f" Hook installiert: {ziel}") + print(" Ueberspringen im Notfall: git commit --no-verify") + return 0 + + +def main() -> int: + if "--install" in sys.argv: + return installieren() + + print("── Pre-Commit ─────────────────────────────────────") + ok = True + ok &= lauf("Syntax · Linter · Scanner", + [PY, "-X", "utf8", str(WURZEL / "tools" / "check_nfalle.py")]) + ok &= lauf("Szenario-Tests (pytest)", [PY, "-X", "utf8", "-m", "pytest", "-q"]) + if ok: + print("── alles gruen ────────────────────────────────────") + return 0 + print("── COMMIT GESTOPPT ────────────────────────────────") + print(" Beheben — oder bewusst uebergehen: git commit --no-verify") + return 1 + + +if __name__ == "__main__": + sys.exit(main())