From e69907edbe340a5a0bbe6f9aa02df1c6f7e8a97d Mon Sep 17 00:00:00 2001 From: Axel Hocks Date: Fri, 7 Aug 2026 00:18:24 +0200 Subject: [PATCH] =?UTF-8?q?pyflakes=20eingebunden=20=E2=80=94=20zwei=20ech?= =?UTF-8?q?te=20Fehler=20in=20core/trader.py=20gefunden?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Auf die Frage nach einem Lint-Modul: pyflakes installiert und als Stufe A2 in tools/check_nfalle.py eingebunden. py_compile prueft nur die SYNTAX; ein Tippfehler in einem NAMEN ist syntaktisch einwandfrei — und dieses Projekt hat sehr viele breite except-Exception-Bloecke, in denen ein NameError unsichtbar bleibt. Beim ERSTEN Lauf zwei echte Fehler, beide in core/trader.py, beide nutzten log. (den Namen gibt es dort nicht, nur log_trade/log_hist): · _broker_offset_s(): der NameError lief in das except-pass UND nahm die darunter stehende Zuweisung self._boff = int(off) mit -> ein echter Broker-Zeitzonen-Wechsel waere nie uebernommen worden, dauerhaft und lautlos. Ausgerechnet der Zweig, der als Deployment-Drift Fall 4 gebaut wurde (falscher Offset -> Time-Stop-Alter negativ). · modify_sltp(): steht NICHT in einem try -> der Fehler lief bis in /api/sltp. Der Broker hatte SL/TP bereits geaendert, der User bekam trotzdem eine Fehlermeldung, und set_sltp kam nie bis trail.deactivate() — das Trailing blieb an und haette die Handeingabe zurueckgezogen. Seit dem Initial-Commit drin. Beide behoben (log_trade). Zusaetzlich die Annotation 'HistoryLogger | None' ueber if TYPE_CHECKING sauber importiert (String-Annotation, kein Laufzeit-Import, kein Zirkel). Projektweit jetzt 0 undefinierte Namen. Schweregrad-Trennung ist Absicht: nur undefined name / syntax error werden gemeldet, die ~105 kosmetischen Hinweise unterdrueckt. Auch 'redefinition of unused' bleibt draussen — es trifft das legitime Muster hook = None + bedingtes def hook (in zwei Backtests geprueft, beide korrekt). Eine Pruefung mit Dauer-Treffern wird ignoriert, und mit ihr die eine echte. Backtest-Dateien nicht angefasst (Zahlen). Neustart verifiziert, Log sauber. Co-Authored-By: Claude Opus 5 --- CLAUDE.md | 33 ++++++++++++++++++++++++++ core/trader.py | 35 +++++++++++++++++++++++++--- tools/check_nfalle.py | 54 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 3 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 72b7912..bc3ce02 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -275,6 +275,39 @@ dort bereits nachvalidiert, ØR +0,305.) **Eine** Oberfläche: als offenes Literal (gültiges JS mit einem Anführungszeichen darin — real in `app.js`). Der Scan verfolgt jetzt zeichenweise den AKTIVEN Quote-Typ. Erster vollständiger Lauf: **161 Python-Dateien, `app.js`, alle HTML/MD sauber.** +- **⚠⚠ LINTER: `pyflakes` (seit 2026-08-07 installiert, Stufe A2 in + `tools/check_nfalle.py`) — und er hat beim ERSTEN Lauf zwei echte Fehler + gefunden.** `py_compile` prüft **nur die Syntax**; ein Tippfehler in einem + **Namen** ist syntaktisch einwandfrei. Dieses Projekt ist dafür besonders + anfällig, weil es sehr viele breite `except Exception`-Blöcke hat (bewusst, als + Fail-safe) — ein `NameError` darin ist **unsichtbar**: keine Meldung, kein + Logeintrag, nur ein Zweig, der nichts tut. Genau das war am 04.08. schon einmal + fast passiert (`wave_snap`), gefunden damals nur durch Lesen. + **Die beiden Funde in `core/trader.py`** — beide nutzten `log.`, den Namen gibt + es dort NICHT (nur `log_trade`/`log_hist`): + · **`_broker_offset_s()`** (seit dem Fix vom 01.08. drin): der `NameError` lief in + das `except Exception: pass` **und nahm die darunter stehende Zuweisung + `self._boff = int(off)` mit** → ein echter **Broker-Zeitzonen-Wechsel wäre nie + übernommen worden**, dauerhaft und lautlos. Ausgerechnet der Zweig, der als + Deployment-Drift **Fall 4** gebaut wurde (falscher Offset → `open_time` in der + Zukunft, **Time-Stop-Alter negativ**). + · **`modify_sltp()`** (seit dem **Initial-Commit**): steht **nicht** in einem + `try` → der Fehler lief bis in `/api/sltp` durch. Folge: der Broker hatte SL/TP + **bereits geändert**, der User bekam trotzdem eine Fehlermeldung, und + `set_sltp` kam nie bis `trail.deactivate()` — **das Trailing blieb an** und hätte + die Handeingabe beim nächsten Tick zurückgezogen, also genau das, was die + User-Vorgabe verhindern soll. Aufgefangen hat das nur die separate + Extern-Erkennung in `trailing._do_modify`, einen Tick später. + ✅ Beide behoben (`log_trade`), dazu die Annotation `'HistoryLogger | None'` über + `if TYPE_CHECKING` sauber importiert. **Projektweit jetzt 0 undefinierte Namen.** + ⚠ **Schweregrad-Trennung ist Absicht:** gemeldet werden nur `undefined name` / + `syntax error`; die ~105 kosmetischen Hinweise (ungenutzte Importe, f-Strings + ohne Platzhalter) werden unterdrückt. Auch `redefinition of unused` steht bewusst + NICHT in der Liste — es trifft das legitime Muster `hook = None` + bedingtes + `def hook` (geprüft in zwei Backtests, beide korrekt). **Eine Prüfung mit + Dauer-Treffern wird ignoriert, und mit ihr die eine echte Meldung.** + ⚠ Backtest-Dateien wurden **nicht** angefasst — eine Änderung dort könnte ihre + dokumentierten Zahlen verschieben. - **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. diff --git a/core/trader.py b/core/trader.py index 0418986..25e971a 100644 --- a/core/trader.py +++ b/core/trader.py @@ -8,6 +8,7 @@ loggt Trades in die HistoryLogger-DB. from __future__ import annotations import threading import time +from typing import TYPE_CHECKING import MetaTrader5 as mt5 @@ -22,6 +23,14 @@ from core.mt5_utils import ( ) from core.logger import get_logger +# ⚠ NUR für die Typ-Annotation `'HistoryLogger | None'` (String, wird zur Laufzeit +# nie ausgewertet). Unter TYPE_CHECKING importiert = kein Laufzeit-Import und damit +# auch kein Zirkel-Risiko. Vorher meldete pyflakes hier zu Recht einen undefinierten +# Namen — und eine Prüfung, die dauerhaft einen bekannten Treffer ausgibt, wird +# irgendwann ignoriert. +if TYPE_CHECKING: # pragma: no cover + from core.history import HistoryLogger + log_trade = get_logger("trade") log_hist = get_logger("hist") @@ -673,8 +682,18 @@ class TradeManager: stale = (now + self._boff) - tick.time if stale <= _BROKER_TICK_FRESH_S and -12 * 3600 <= off <= 14 * 3600: if off != self._boff and self._boff_logged != off: - log.info(f"Broker-Zeitzone neu erkannt: " - f"{self._boff / 3600:+.1f} h → {off / 3600:+.1f} h") + # ⚠ war `log.` — diesen Namen gibt es in dieser Datei NICHT + # (nur `log_trade`/`log_hist`). Gefunden 2026-08-07 mit + # pyflakes. Folge war schwerer als ein fehlender Logeintrag: + # der NameError lief in das `except Exception: pass` unten, + # und die Zuweisung `self._boff = int(off)` DARUNTER wurde + # dabei übersprungen → ein echter Broker-Zeitzonen-Wechsel + # wäre nie übernommen worden, dauerhaft und lautlos. + # Genau der Fall, für den dieser Zweig 2026-08-01 gebaut + # wurde (Deployment-Drift Fall 4). + log_trade.info(f"Broker-Zeitzone neu erkannt: " + f"{self._boff / 3600:+.1f} h → " + f"{off / 3600:+.1f} h") self._boff_logged = off self._boff = int(off) except Exception: @@ -873,7 +892,17 @@ class TradeManager: with self._lock: self.sl = round(new_sl, si.digits) self.tp = round(new_tp, si.digits) - log.info(f"Manuelles SLTP: SL={self.sl} · TP={self.tp} (Ticket {pos.ticket})") + # ⚠⚠ war `log.` — den Namen gibt es hier nicht (nur `log_trade`/ + # `log_hist`). Seit dem Initial-Commit drin, gefunden 2026-08-07 mit + # pyflakes. Die Stelle steht NICHT in einem `try`, der NameError lief + # also bis in `/api/sltp` durch — mit drei Folgen: (1) der Broker hatte + # SL/TP bereits geändert, (2) der User bekam trotzdem eine Fehlermeldung, + # (3) `set_sltp` kam nie bis `trail.deactivate()`, das Trailing blieb an + # und hätte die Handeingabe beim nächsten Tick zurückgezogen — genau das, + # was die User-Vorgabe verhindern soll. (Aufgefangen hat es nur die + # separate Extern-Erkennung in `trailing._do_modify`, einen Tick später.) + log_trade.info(f"Manuelles SLTP: SL={self.sl} · TP={self.tp} " + f"(Ticket {pos.ticket})") return None def snapshot(self): diff --git a/tools/check_nfalle.py b/tools/check_nfalle.py index ce08b66..8744554 100644 --- a/tools/check_nfalle.py +++ b/tools/check_nfalle.py @@ -53,6 +53,60 @@ for p in dateien(".py"): print(f" {npy} Dateien geprueft.") +# ── A2) pyflakes: was py_compile NICHT sieht ───────────────────────────── +# ⚠⚠ WARUM DIESE STUFE NOETIG IST. `py_compile` prueft nur die SYNTAX. Ein +# Tippfehler in einem Namen ist syntaktisch einwandfrei und fliegt erst zur +# Laufzeit — und dieses Projekt hat sehr viele breite `except Exception`-Bloecke +# (bewusst, als Fail-safe im Trading-Betrieb). Ein `NameError` darin ist damit +# UNSICHTBAR: keine Meldung, kein Logeintrag, nur ein Zweig, der nichts tut. +# Real gefunden beim ERSTEN Lauf ueberhaupt (2026-08-07): +# · `core/trader.py` nutzte zweimal `log.` — den Namen gibt es dort nicht +# (nur `log_trade`/`log_hist`). Einer davon seit dem Initial-Commit. +# · Folge 1 (`_broker_offset_s`): der NameError lief in ein `except: pass` +# und nahm die Zuweisung `self._boff = int(off)` mit → ein echter +# Broker-Zeitzonen-Wechsel waere nie uebernommen worden. +# · Folge 2 (`modify_sltp`): kein try drumherum → der Fehler lief bis in +# `/api/sltp`; der Broker hatte SL/TP schon geaendert, der User bekam +# trotzdem einen Fehler, und `trail.deactivate()` wurde uebersprungen. +# GETRENNT NACH SCHWEREGRAD: eine Pruefung, die dauerhaft 100 harmlose Zeilen +# ausgibt, wird ignoriert — und dann auch die eine wichtige. +# ⚠ `redefinition of unused` steht BEWUSST NICHT hier drin. pyflakes meldet damit +# auch das voellig legitime Muster `hook = None` gefolgt von einem bedingten +# `def hook(...)` — real in `backtest_flipclose2.py` und +# `backtest_adverse15_squeeze.py`, beide geprueft und korrekt. Zwei unvermeidbare +# Dauer-Treffer sind genau der Weg, auf dem eine Pruefung ignoriert wird, und mit +# ihr dann auch die eine echte Meldung. Wer die Klasse doch sehen will, ruft +# `python -m pyflakes ` direkt auf. +SCHWER = ("undefined name", "undefined local", "syntax error") +print() +print("=" * 88) +print(" A2) PYFLAKES — undefinierte Namen (was py_compile nicht sieht)") +print("=" * 88) +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.") + 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") + + # ── B) JavaScript ──────────────────────────────────────────────────────── def js_offene_strings(text: str): """Zeilen mit angefangenem, nicht geschlossenem ' oder " (Template-Literale