Files
AH-Oil-Trader/tools/check_nfalle.py
T
Axel HocksandClaude Opus 5 e69907edbe pyflakes eingebunden — zwei echte Fehler in core/trader.py gefunden
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 <noreply@anthropic.com>
2026-08-07 00:18:24 +02:00

208 lines
8.8 KiB
Python

"""Sucht die \\n-Falle in allen Projektdateien — in BEIDE Richtungen.
Die Falle: beim Schreiben ueber ein Shell-Heredoc wird aus einem gemeinten `\\n`
ein ECHTER Zeilenumbruch mitten im String-Literal. In Python bricht das den
Compile (zweimal heute passiert), in JS ebenso — aber nicht ueberall faellt es
sofort auf.
Geprueft wird:
A) Python: kompiliert jede Datei. Ein zerrissenes Literal faellt hier auf.
B) JS: Zeilen mit ungerader Zahl UNMASKIERTER Anfuehrungszeichen ausserhalb
von Kommentaren = angefangenes, nicht geschlossenes String-Literal.
C) Umgekehrter Fall: literales `\\n` als TEXT dort, wo es niemand lesen will
(HTML-Attribute, Markdown-Fliesstext) — das entsteht, wenn ein `\\\\n`
zu viel escaped wurde.
D) Verdaechtige Zeilenpaare: eine Zeile endet mit oeffnendem Quote, die
naechste besteht nur aus einem Quote — die Signatur der beiden heutigen
Faelle.
"""
import pathlib
import py_compile
import re
import sys
import tempfile
WURZEL = pathlib.Path(r"c:\Users\ah\Downloads\tradingrobot")
SKIP = {".git", "__pycache__", ".reminders", "node_modules", ".removed_backup"}
befunde = []
def dateien(*endungen):
for p in WURZEL.rglob("*"):
if any(s in p.parts for s in SKIP):
continue
if p.is_file() and p.suffix in endungen:
yield p
# ── A) Python ────────────────────────────────────────────────────────────
print("=" * 88)
print(" A) PYTHON — kompiliert jede Datei (ein zerrissenes Literal faellt hier auf)")
print("=" * 88)
npy = 0
tmp = tempfile.mkdtemp()
for p in dateien(".py"):
npy += 1
try:
py_compile.compile(str(p), cfile=f"{tmp}/x.pyc", doraise=True)
except py_compile.PyCompileError as e:
msg = str(e).strip().splitlines()[-1][:110]
befunde.append(f"PY {p.relative_to(WURZEL)}: {msg}")
print(f" FEHLER {p.relative_to(WURZEL)}")
print(f" {msg}")
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 <datei>` 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
duerfen mehrzeilig sein und werden uebersprungen)."""
treffer = []
in_tmpl = False
for nr, z in enumerate(text.splitlines(), 1):
# Backticks zaehlen -> Template-Literal-Zustand fortschreiben
bt = len(re.findall(r"(?<!\\)`", z))
if in_tmpl:
in_tmpl ^= bool(bt % 2)
continue
if bt % 2:
in_tmpl = True
continue
s = re.sub(r"//.*$", "", z) # Zeilenkommentar weg
# ⚠ Quotes NICHT einfach zaehlen — das erzeugt Fehlalarme: '"' ist ein
# gueltiges Literal, das EIN doppeltes Anfuehrungszeichen enthaelt (real
# in app.js aufgetreten). Stattdessen zeichenweise laufen und den jeweils
# AKTIVEN Quote-Typ verfolgen; Quotes der anderen Art zaehlen dann nicht.
aktiv, esc = None, False
for ch in s:
if esc:
esc = False
continue
if ch == "\\":
esc = True
continue
if aktiv:
if ch == aktiv:
aktiv = None
elif ch in ('"', "'"):
aktiv = ch
if aktiv:
treffer.append((nr, z.strip()[:100]))
return treffer
print()
print("=" * 88)
print(" B) JAVASCRIPT — unabgeschlossene String-Literale")
print("=" * 88)
for p in dateien(".js"):
if "lightweight-charts" in p.name:
continue
tr = js_offene_strings(p.read_text(encoding="utf-8", errors="replace"))
if tr:
for nr, z in tr:
befunde.append(f"JS {p.relative_to(WURZEL)}:{nr}")
print(f" FEHLER {p.relative_to(WURZEL)}:{nr} {z}")
else:
print(f" OK {p.relative_to(WURZEL)}")
# ── C) literales \n als TEXT ─────────────────────────────────────────────
print()
print("=" * 88)
print(" C) UMGEKEHRT — literales \\n im sichtbaren Text (HTML-Attribute, Markdown)")
print("=" * 88)
for p in list(dateien(".html")) + list(dateien(".md")):
t = p.read_text(encoding="utf-8", errors="replace")
roh = []
for m in re.finditer(r'title="([^"]*)"', t):
if "\\n" in m.group(1):
roh.append(f"title=...{m.group(1)[:60]}")
if p.suffix == ".md":
for nr, z in enumerate(t.splitlines(), 1):
if "\\n" in z and "`" not in z and "\\\\n" not in z:
roh.append(f"Zeile {nr}: {z.strip()[:80]}")
if roh:
for r in roh[:5]:
befunde.append(f"TXT {p.relative_to(WURZEL)}: {r}")
print(f" FEHLER {p.relative_to(WURZEL)} {r}")
else:
print(f" OK {p.relative_to(WURZEL)}")
# ── D) Signatur der heutigen Faelle ──────────────────────────────────────
print()
print("=" * 88)
print(' D) SIGNATUR — Zeile endet offen, naechste besteht nur aus einem Quote')
print("=" * 88)
sig = 0
for p in list(dateien(".py")) + list(dateien(".js")):
zeilen = p.read_text(encoding="utf-8", errors="replace").splitlines()
for i in range(len(zeilen) - 1):
if zeilen[i + 1].strip() in ('"', "'", '",', "',"):
sig += 1
befunde.append(f"SIG {p.relative_to(WURZEL)}:{i+2}")
print(f" VERDAECHTIG {p.relative_to(WURZEL)}:{i+2}")
print(" keine gefunden." if not sig else f" {sig} Stellen.")
print()
print("=" * 88)
print(" ERGEBNIS: " + ("keine \\n-Falle gefunden" if not befunde
else f"{len(befunde)} Befund(e)"))
print("=" * 88)
sys.exit(1 if befunde else 0)