From 0fe51dea487c5cb5eb4bb9788835e2ab4ad65dc0 Mon Sep 17 00:00:00 2001 From: Axel Hocks Date: Wed, 19 Aug 2026 23:14:43 +0200 Subject: [PATCH] Pipeline: zwei neue Pruefstufen vor dem Commit (Typaritaet + JS-Render) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User: "baue alle und integriere in die pipeline vor dem commit". Beide Stufen schliessen Luecken, die HEUTE real zugeschlagen haben. A3) TYPPRUEFUNG (ty 0.0.73, Konfiguration in ty.toml) Anlass: `paarweise(d)` wurde mit 1 von 2 Pflichtargumenten gerufen. py_compile sah nichts (Syntax ok), ruff F821 auch nicht (der Name IST definiert), kein Test deckte den Pfad ab - der Fehler schlug erst zur Laufzeit zu. ⚠ BEWUSST ENGES REGELSET: der volle ty-Lauf meldet 172 Befunde, davon 97 `unresolved-attribute` aus fehlenden MetaTrader5-Stubs. Eine Stufe mit Dauer-Treffern wird ignoriert, und mit ihr die eine echte Meldung (die Lehre vom 07.08.). Aktiv sind nur die Aritaets-Regeln. `call-non-callable` und `not-iterable` sind AUS - beide melden hier ausschliesslich Falsch-Positive aus heterogenen dicts (`c["have"]()`, `"up" in c`); geprueft, nicht angenommen. ⚠ Der dokumentierte Einwand "Pruefumgebung != Laufumgebung" greift NICHT: ein Typchecker fuehrt nichts aus, er kann also nicht divergieren. Er braucht auch kein MT5. ⚠ Nebenbefund: die CLAUDE.md-Begruendung gegen mypy ("kaum Annotationen vorhanden") ist veraltet - gemessen sind 305 von 443 Funktionen annotiert (69 %). B2) RENDER-PROBE (tools/render_check.mjs gegen tests/fixtures/snapshot.json) Anlass: `node --check` prueft nur SYNTAX. Der ReferenceError von heute Abend (RSI-Zeile griff in renderM15(s) auf d.market zu) war syntaktisch einwandfrei und brach im Browser den GESAMTEN Render ab - sechs leere Zeilen und eine leere Breakout-Karte. Fuer Python gibt es ruff F821, fuer JS gab es nichts. ⚠ Laeuft gegen einen GESPEICHERTEN Snapshot, nicht gegen den laufenden Server - der Hook darf nicht davon abhaengen, ob gerade ein Server laeuft. ⚠ KEIN ESLint: das braeuchte npm und node_modules, gegen die dokumentierte Entscheidung ("kein npm, keine package.json"). BEIDE RICHTUNGEN VERIFIZIERT, je Stufe: A3 Aritaetsfehler eingebaut -> "FEHLER too-many-positional-arguments", 1 Befund; zurueck -> gruen B2 d.market zurueckgebaut -> "❌ EXCEPTION: d is not defined", 1 Befund; zurueck -> gruen Beide haengen an tools/check_nfalle.py und laufen damit im pre-commit-Hook mit. Fehlt das Werkzeug (ty nicht installiert, node nicht im PATH, Fixture fehlt), wird die Stufe uebersprungen statt zu blockieren - eine frische Maschine darf am Hook nicht haengenbleiben. Ein WERKZEUGFEHLER (Exit != 0/1) gilt dagegen ausdruecklich NICHT als sauber. ty in requirements-dev.txt gepinnt. Co-Authored-By: Claude Opus 5 --- requirements-dev.txt | 2 + tests/fixtures/snapshot.json | 1 + tools/check_nfalle.py | 90 ++++++++++++++++++++++++++++++++++++ ty.toml | 34 ++++++++++++++ 4 files changed, 127 insertions(+) create mode 100644 tests/fixtures/snapshot.json create mode 100644 ty.toml diff --git a/requirements-dev.txt b/requirements-dev.txt index 6882941..234854d 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -19,3 +19,5 @@ 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 +# Typpruefung (Aritaet) — Stufe A3 in tools/check_nfalle.py, seit 2026-08-19 +ty==0.0.73 diff --git a/tests/fixtures/snapshot.json b/tests/fixtures/snapshot.json new file mode 100644 index 0000000..a7d45da --- /dev/null +++ b/tests/fixtures/snapshot.json @@ -0,0 +1 @@ +{"ts":1787174006.9368982,"connected":true,"running":true,"error":null,"symbol":"SpotCrude","wave_tf":"M5","min_conf":70,"auth_required":false,"market":{"symbol":"SpotCrude","bid":85.227,"ask":85.259,"spread":0.032,"change":-0.04200000000000159,"pct":-0.04924664360673224,"day_high":86.678,"day_low":84.292,"balance":668.3,"equity":668.3,"currency":"EUR","error":null,"trend_angle":137.13918407284268,"angles":{"M5":79.10681441974654,"M15":137.13918407284262,"M30":151.34457584184128,"H1":110.58013642771405},"rsi_m15":44.41470027202243,"atr_m15":0.2942462693384998,"server_time":"2026-08-19T23:58:59+00:00","tick_local_ts":1787173139.6395023,"tick_server_ts":1787183939,"sessions":{"0":[[79200,86400]],"1":[[0,79200]],"2":[[0,79200]],"3":[[0,79200]],"4":[[0,79200]],"5":[[0,79200]],"6":[]},"reversal":null,"reasons":[],"sr":{"supports":[{"price":84.73625,"touches":4}],"resistances":[{"price":85.40066666666667,"touches":3},{"price":85.9305,"touches":6}],"trendline_up":null,"trendline_dn":null}},"sr_levels":{"resistance":85.412,"support":85.212,"resistances":[85.412],"supports":[85.212]},"session":{"active":[],"since_open_min":{"DE":853,"US":493},"just_opened":null,"next_open":null,"phase":"außerhalb DE/US","weekend":false},"position":{"ticket":null,"order_type":null,"entry_price":0.0,"lots":0.0,"pnl":0.0,"cur_price":0.0,"sl":0.0,"tp":0.0,"margin":0.0,"open_time":0.0},"day_pl":{"realized":-207.48,"open":0.0,"total":-207.48},"circuit_breaker":{"enabled":true,"limit_pct":8.0,"limit_eur":53.46,"day_total":-207.48,"tripped":true},"emergency_loss":null,"emergency_remember":0.0,"emergency_pct":0.0,"emergency_margin_pct":0.0,"wht_pct":26.375,"takeprofit":null,"takeprofit_remember":0.0,"trail":{"enabled":false,"atr":0.10099999999999706,"atr_raw":0.10099999999999706,"atr_tf":"M5","atr_tf_override":"M5","high_water":null,"last_sl":85.227,"last_tp":85.786,"trail_mult":1.0,"phase":"—"},"wave_signal":{"signal":"WARTEN","conf_pct":0,"score":0.0,"setup":"WAVE","regime":null,"rsi":null,"block":"entry_room","reasons":["kein Raum: Support 85.21 nur 0.13×ATR entfernt (Gate 0.6) — Ertrag gedeckelt, Kosten fressen den Edge"]},"wave":{"tf":"M5","atr":0.11042857142856885,"direction":"down","wave_start":85.339,"move_atr":-1.02,"n_pivots":0,"trend_sep":-1.86,"bounce":{"state":null,"dir":null,"tf":null},"htf_trend":0,"h1_trend":1,"tf_turns":{"M1":{"dir":1,"bars_ago":62,"fresh":false},"M5":{"dir":-1,"bars_ago":33,"fresh":false},"M15":{"dir":-1,"bars_ago":8,"fresh":false},"M30":{"dir":0,"bars_ago":0,"fresh":false},"H1":{"dir":1,"bars_ago":58,"fresh":false}},"tf_atr":{"M1":0.056642857142859625,"M5":0.11042857142856885,"M15":0.2934285714285707,"M30":0.4577142857142838,"H1":0.695214285714283},"pb_feats":{"mom6":0.583,"mom3":0.292,"ema_diff":-1.707,"atr":0.12},"pb_levels":{"ph":[85.08,85.025,85.08,85.396,85.412,85.382,85.367,85.402,85.392,85.797,85.797,85.892,85.992,85.802,85.762,85.717,85.877,85.587,85.832,85.587,86.263,85.927,86.103,85.837,85.332,85.433,85.663,86.337,86.423,86.678,86.132,85.247,85.247,85.152,85.227],"pl":[84.785,84.859,85.061,85.262,85.262,85.266,85.322,85.322,85.212,85.602,85.51,85.457,85.502,85.502,85.377,85.365,85.262,85.147,85.117,85.517,85.517,85.476,84.292,85.247,85.247,85.412,85.872,86.097,85.737,84.992,84.781,85.022]},"pb_levels30":{"ph":[85.412,85.992,85.877,86.263,86.678],"pl":[85.212,85.117,84.292]},"liq_zones":{"eqh":null,"eql":null},"squeeze":{"state":"armed","dir":"LONG","level":85.239,"box_atr":1.71,"box_hi":85.227,"box_lo":85.022},"breakout":{"pending":false,"dir":null,"need":null},"error":null,"last_update":1787174006.5307655},"elliott":{"tf":"M5","pattern":"impulse_up","wave":"5 (vollendet)","dir":"up","target":85.188,"target_label":"Reversal ~0.382–0.618","exhaustion":true,"invalidation":85.328,"valid":false,"fvg":{"type":"bullish","low":85.082,"high":85.087,"mid":85.084},"n_pivots":231,"note":"Impuls aufwärts vollendet bei 85.328 → Reversal abwärts wahrscheinlich","error":null,"last_update":1787174006.5317712,"stale":false},"tu":{"symbol":"WTI/USD","intervals":{},"error":null,"last_update":null,"busy":false},"agent":{"advisory":{"bias":"NEUTRAL","confidence":30,"timeframe":"M5","headline":"Kein Einstieg: Support zu nah, Setup-Historie negativ, außerhalb der Handelssession","reasoning":"Das WAVE-Signal wartet, da der Support bei 85.21 nur 0.13×ATR entfernt ist (zu geringe Ertragspotenzial). Das Setup hat eine negative durchschnittliche PnL (-0.43) und liegt außerhalb der aktiven Handelssessions, was die Volatilität erhöht.","risks":["Geringe Liquidität außerhalb der Handelssessions","Niedriges Ertragspotenzial durch nahen Support","Negatives Track-Record des aktuellen Setups"],"position_note":""},"error":null,"last_update":1787173743.7303193,"busy":false,"provider":"zai","configured":true,"provider_dead":{}},"daily_levels":{"support":null,"res_lo":null,"res_hi":null,"bias":"neutral","summary":"","ts":0.0,"error":null},"gaps":{"gaps":[{"date":"2026-06-04","dir":"down","kind":"close_open","lo":97.866,"hi":98.491,"fill":98.491,"size":0.625},{"date":"2026-07-13","dir":"up","kind":"close_open","lo":71.775,"hi":73.594,"fill":71.775,"size":1.819},{"date":"2026-07-27","dir":"down","kind":"vacuum","lo":86.222,"hi":88.288,"fill":88.288,"size":2.066},{"date":"2026-08-10","dir":"up","kind":"close_open","lo":77.383,"hi":78.769,"fill":77.383,"size":1.386}],"n_open":4,"n_vacuum":1,"n_close_open":3,"n_total":34,"last_update":1787173799.4602318},"structure":{"trend":"range","swings":[{"type":"HL","price":85.212},{"type":"HH","price":85.992},{"type":"LL","price":85.117},{"type":"HH","price":86.263},{"type":"LL","price":84.292},{"type":"HH","price":86.678}],"last_swing":"HH","bos":{"dir":"up","level":86.263,"bars_ago":6},"channel":{"dir":"up","pos":0.17,"upper":86.494,"lower":84.882,"mid":85.693,"slope_atr":0.016},"channel_line":{"t1":1787070600,"t2":1787180400,"dir":"up","upper":[86.039,86.494],"mid":[85.238,85.693],"lower":[84.428,84.882]},"tf":"M30","error":null},"patterns":{"patterns":[{"type":"double_bottom","name":"Doppelboden","dir":"bull","quality":0.7,"trigger":85.992,"target":86.867,"height_atr":1.9,"status":"forming","bars_ago":7,"note":"zwei Tiefs 85.21/85.12, Nackenlinie 85.99","measured":true},{"type":"inv_cup","name":"Inverse Tasse+Henkel","dir":"bear","quality":0.63,"trigger":85.117,"target":84.242,"height_atr":1.9,"status":"forming","bars_ago":7,"note":"abgerundetes Top 85.99, Rand ~85.12 (Henkel-Unterkante)","measured":true}],"atr":0.4577,"cur":85.227,"ts":1787173981.271978},"candle_anat":{"tf":"M5","items":[{"cls":"Engulfing","label":"Engulfing","dir":-1,"bars_ago":2,"vol_x":1.3,"hot":false,"reading":"⚠ Klimax-Kerze — gemessen läuft der Kurs danach eher GEGEN die Kerze (nicht Fortsetzung)","body_pct":62,"wick_up_pct":7,"wick_lo_pct":30},{"cls":"Doji","label":"Doji","dir":0,"bars_ago":4,"vol_x":0.7,"hot":false,"reading":"Gleichstand — keine Seite dominiert","body_pct":0,"wick_up_pct":13,"wick_lo_pct":87},{"cls":"Doji","label":"Doji","dir":0,"bars_ago":5,"vol_x":0.8,"hot":false,"reading":"Gleichstand — keine Seite dominiert","body_pct":4,"wick_up_pct":59,"wick_lo_pct":37},{"cls":"Doji","label":"Doji","dir":0,"bars_ago":6,"vol_x":0.8,"hot":false,"reading":"Gleichstand — keine Seite dominiert","body_pct":9,"wick_up_pct":9,"wick_lo_pct":81}],"vol_hot_x":2.0,"ts":1787173991.363231},"cone":{"atr":0.12,"price":85.227,"levels":[{"minutes":30,"bars":6,"band":50,"lo":85.12,"hi":85.337,"coverage":44.4,"skala":0.972},{"minutes":30,"bars":6,"band":80,"lo":84.994,"hi":85.456,"coverage":76.7,"skala":0.972},{"minutes":60,"bars":12,"band":50,"lo":85.064,"hi":85.391,"coverage":45.1,"skala":1.016},{"minutes":60,"bars":12,"band":80,"lo":84.879,"hi":85.572,"coverage":76.6,"skala":1.016},{"minutes":120,"bars":24,"band":50,"lo":84.999,"hi":85.461,"coverage":45.5,"skala":0.976},{"minutes":120,"bars":24,"band":80,"lo":84.737,"hi":85.716,"coverage":75.3,"skala":0.976}]},"stats_compact":{"week":{"n":75,"wr":49,"pf":0.49,"avg_win":14.89,"avg_loss":-29.7,"ratio":0.5,"pnl":-577.82},"all":{"n":1345,"wr":56,"pf":0.96,"avg_win":14.03,"avg_loss":-19.55,"ratio":0.72,"pnl":-462.05}},"close_alert_count":0,"news_storm":{"level":"elevated","fresh":4,"base_per_h":1.7},"news_sentiment":0.32,"alignment":{"with":{"n":359,"wr":56,"pnl":331.53,"pnl_lot":1.84},"against":{"n":91,"wr":41,"pnl":-262.58,"pnl_lot":-7.9},"none":{"n":895,"wr":57,"pnl":-531.0,"pnl_lot":-0.78}},"hl_ctx":{"funding":8.04049e-05,"open_interest":1973714.6100000003,"mark":84.612,"oracle":84.471,"premium":0.14099999999999113,"day_volume":128046328.89346,"network":"mainnet"},"rec_track":{"n":72,"wr30":48.6,"avg30":0.025,"n30":72,"wr60":46.4,"avg60":-0.202,"n60":69},"squeeze_b5":{"n":11,"ziel":20,"wins":8,"losses":3,"avg_win":13.62,"avg_loss":-94.15,"ratio":0.14,"pf":0.39,"sum_eur":-173.46,"latte":1.0},"auto_m15":false,"m15_entry_count":1,"manual_stats":{"tage":30,"fenster":{"n":453,"wr":58.3,"sum":-496.27,"avg":-1.1},"gesamt":{"n":1296,"wr":55.8,"sum":-422.84,"avg":-0.33},"heute":{"n":32,"wr":50.0,"sum":-206.17,"avg":-6.44},"mit_signal":{"n":63,"wr":58.7,"sum":-113.8,"avg":-1.81},"ohne_signal":{"n":382,"wr":57.9,"sum":-468.72,"avg":-1.23},"bot":{"n":35,"wr":54.3,"sum":-36.8,"avg":-1.05},"ctx_abdeckung":{"n":1296,"mit_ctx":181,"pct":14.0}},"manual_margin":0.0,"margin_pct":95.0,"position_brk":null,"margin_brk":10.0,"margin_m15":85.0,"cost_ratio":0.27,"m15_setup":{"atr_m15":0.2934,"bias":{"m30":0,"h1":1,"verboten":null,"einig":false,"richtung":null},"kosten":{"ratio":0.109,"stufe":"guenstig"},"zonen":[{"seite":"res","level":85.412,"gap_atr15":0.63,"p_break":36},{"seite":"sup","level":85.212,"gap_atr15":0.05,"p_break":42}],"motor":{"signal":"WARTEN","block":"entry_room","grund":"kein Raum: Support 85.21 nur 0.13×ATR entfernt (Gate 0.6) — Ertrag gedeckelt, Kosten fressen den Edge","conf_pct":0,"raum":{"gate":0.6,"atr_m5":0.12,"long":{"level":85.247,"dist_atr":0.17,"frei":false},"short":{"level":85.212,"dist_atr":0.13,"frei":false}}},"m5":{"squeeze":"armed","squeeze_dir":"LONG","box_atr":1.71,"atr":0.12,"kosten":0.267},"reichweite":[{"minutes":30,"lo":84.994,"hi":85.456},{"minutes":60,"lo":84.879,"hi":85.572},{"minutes":120,"lo":84.737,"hi":85.716}],"ziel":null,"ziel_grund":"kein Veto — Richtung offen, kein Ziel bestimmbar","status":"gruen","lesart":"auf","nah":{"seite":"sup","level":85.212,"gap_atr15":0.05,"p_break":42},"vola":{"datr":1.0,"stufe":"normal"},"veto":{"with":{"n":359,"wr":56,"pnl":331.53,"pnl_lot":1.84},"against":{"n":91,"wr":41,"pnl":-262.58,"pnl_lot":-7.9},"none":{"n":895,"wr":57,"pnl":-531.0,"pnl_lot":-0.78}}},"m15_track":{"n":200,"offen":47,"roh":8000,"wr30":59,"wr60":56,"avg30":0.253},"entry_gap":{"n_seit_umbau":3,"soll":12},"sr_close_hint":null,"stop_approach":null,"auto_sr_close":false,"sr_close_min_gain":0.0,"sr_close_pbreak":0.35,"sr_close_count":0,"close_notify_min":50.0,"auto_squeeze":true,"squeeze_night_hours":[3,4,5,6],"squeeze_pending":true,"pending_levels":[],"pending_quelle":"","squeeze_max_chase_atr":0.2,"squeeze_entry_count":0,"adverse15_atr":0.0,"adverse15_count":0,"flip_close":false,"flip_close_min_r":0.5,"flip_close_count":0,"last_trades":[{"direction":"BUY","lots":0.87,"entry_time":1787167100,"exit_time":1787173139,"entry_price":85.236,"exit_price":85.227,"pnl":-0.67,"commission":0.0,"closed_by":"sl","setup":"WAVE"},{"direction":"SELL","lots":0.87,"entry_time":1787165969,"exit_time":1787166966,"entry_price":85.212,"exit_price":85.272,"pnl":-4.47,"commission":0.0,"closed_by":"sl","setup":"WAVE"},{"direction":"BUY","lots":0.87,"entry_time":1787164282,"exit_time":1787165939,"entry_price":85.187,"exit_price":85.171,"pnl":-1.19,"commission":0.0,"closed_by":"sl","setup":"WAVE"},{"direction":"BUY","lots":0.09,"entry_time":1787164195,"exit_time":1787164226,"entry_price":85.116,"exit_price":85.161,"pnl":0.27,"commission":0.0,"closed_by":"manual","setup":"AUTOM15_LONG"},{"direction":"BUY","lots":0.37,"entry_time":1787163083,"exit_time":1787164193,"entry_price":85.836,"exit_price":85.102,"pnl":-23.29,"commission":0.0,"closed_by":"sl","setup":"WAVE"},{"direction":"BUY","lots":0.37,"entry_time":1787162863,"exit_time":1787163039,"entry_price":85.861,"exit_price":85.831,"pnl":-0.95,"commission":0.0,"closed_by":"circuit_breaker","setup":"WAVE"},{"direction":"BUY","lots":0.38,"entry_time":1787162741,"exit_time":1787162832,"entry_price":85.966,"exit_price":85.837,"pnl":-4.2,"commission":0.0,"closed_by":"circuit_breaker","setup":"WAVE"},{"direction":"BUY","lots":0.38,"entry_time":1787162411,"exit_time":1787162635,"entry_price":86.109,"exit_price":85.942,"pnl":-5.44,"commission":0.0,"closed_by":"circuit_breaker","setup":"WAVE"},{"direction":"BUY","lots":0.38,"entry_time":1787161336,"exit_time":1787162231,"entry_price":85.836,"exit_price":85.832,"pnl":-0.13,"commission":0.0,"closed_by":"sl","setup":"AUTOM15_LONG"},{"direction":"BUY","lots":0.39,"entry_time":1787159908,"exit_time":1787161335,"entry_price":86.506,"exit_price":85.812,"pnl":-23.21,"commission":0.0,"closed_by":"sl","setup":"WAVE"}],"calendar":{"enabled":true,"n":2,"blackout":null,"next":null,"age_min":4},"market_closed":true,"tick_age_s":866,"hl_live":{"mid_mt5":85.478,"hl_mid":84.596,"basis":0.8815,"age_s":1.6,"basis_stale":true,"bid":85.458,"ask":85.496,"bid_sz":668.48,"ask_sz":359.99,"imb5":0.9397,"trend":"up","slope_per_h":0.3867,"vs_mt5":0.251},"pnl_hl":null,"checklist":{"long":{"side":"LONG","items":[{"key":"signal","state":"fail","label":"Keine Empfehlung (WARTEN)","detail":"gemessen: Trades ohne Signal sind die größte Verlustquelle"},{"key":"htf","state":"warn","label":"HTF-Trend unklar/seitwärts","detail":"kein klarer M30/H1-Trend"},{"key":"room","state":"ok","label":"Raum bis zum Level: 1.5×ATR","detail":""},{"key":"cost","state":"warn","label":"Kosten erhöht (0.27×ATR)","detail":""},{"key":"streak","state":"fail","label":"Heute schon 17 Verluste (33 Trades, -207 € = 31% des Kontos)","detail":"deine Regel: nach 2 Losses Session beenden"}],"fails":2,"warns":2,"verdict":"stop"},"short":{"side":"SHORT","items":[{"key":"signal","state":"fail","label":"Keine Empfehlung (WARTEN)","detail":"gemessen: Trades ohne Signal sind die größte Verlustquelle"},{"key":"htf","state":"fail","label":"HTF-Trend steht dagegen (M30/H1)","detail":"deine Regel: nur MIT dem HTF-Trend, niemals dagegen"},{"key":"room","state":"fail","label":"Kein Raum: Level nur 0.1×ATR entfernt","detail":"gemessen: <0,6×ATR Raum = negativer Erwartungswert"},{"key":"cost","state":"warn","label":"Kosten erhöht (0.27×ATR)","detail":""},{"key":"streak","state":"fail","label":"Heute schon 17 Verluste (33 Trades, -207 € = 31% des Kontos)","detail":"deine Regel: nach 2 Losses Session beenden"}],"fails":4,"warns":1,"verdict":"stop"}}} \ No newline at end of file diff --git a/tools/check_nfalle.py b/tools/check_nfalle.py index 67b0941..7007fcd 100644 --- a/tools/check_nfalle.py +++ b/tools/check_nfalle.py @@ -81,6 +81,91 @@ print(f" {npy} Dateien geprueft.") SCHWER = ("undefined name", "undefined local", "syntax error") print() print("=" * 88) +# ── A3) TYPPRUEFUNG: falsche Aufrufe ───────────────────────────────────── +# ⚠⚠ WARUM ES DAS BRAUCHT (2026-08-19): `paarweise(d)` wurde mit 1 von 2 +# Pflichtargumenten gerufen. py_compile sah nichts (Syntax ok), ruff F821 auch +# nicht (der Name IST definiert), und kein Test deckte den Pfad ab. Der Fehler +# schlug erst zur Laufzeit zu. Genau diese Luecke schliesst `ty`. +# ⚠ BEWUSST ENGES REGELSET (`ty.toml`): der volle Lauf meldet 172 Befunde, davon +# 97 `unresolved-attribute` aus fehlenden MetaTrader5-Stubs. Eine Stufe mit +# Dauer-Treffern wird ignoriert, und mit ihr die eine echte Meldung. Aktiv +# sind nur die Aritaets-Regeln; `call-non-callable`/`not-iterable` sind AUS, +# weil sie hier nur Falsch-Positive aus heterogenen dicts liefern (geprueft). +# ⚠ EXIT-CODES wie bei ruff: 0 sauber · 1 Befunde · alles andere = Werkzeugfehler +# und darf NIEMALS als "sauber" durchgehen (die Lehre vom 07.08.). +def _stufe_typen(): + print("") + print("=" * 88) + print(" A3) TYPPRUEFUNG — falsche Aufrufe (Aritaet)") + print("=" * 88) + cfg = WURZEL / "ty.toml" + if not cfg.exists(): + print(" ty.toml fehlt — Stufe uebersprungen.") + return + try: + r = subprocess.run([sys.executable, "-m", "ty", "check", + "--config-file", str(cfg), + str(WURZEL / "core"), str(WURZEL / "server.py")], + capture_output=True, text=True, timeout=300) + except FileNotFoundError: + print(" `ty` nicht installiert — Stufe uebersprungen " + "(pip install ty). Eine frische Maschine darf hier nicht haengen.") + return + except Exception as e: + print(f" ⚠ ty nicht ausfuehrbar ({e}) — Stufe uebersprungen.") + return + if r.returncode not in (0, 1): + print(f" ⚠ ty meldet einen WERKZEUGFEHLER (Exit {r.returncode}) — " + f"NICHT als sauber gewertet:") + for z in (r.stderr or r.stdout).strip().splitlines()[:3]: + print(f" {z}") + befunde.append("TY Werkzeugfehler") + return + treffer = [z for z in (r.stdout or "").splitlines() if z.startswith("error[")] + for z in treffer: + befunde.append(f"TY {z.strip()}") + print(f" FEHLER {z.strip()}") + if not treffer: + print(" keine falschen Aufrufe.") + + +# ── B2) RENDER-PROBE: JS-Laufzeitfehler im Dashboard ───────────────────── +# ⚠⚠ `node --check` prueft nur SYNTAX. Ein ReferenceError (falsche Variable im +# falschen Scope) ist syntaktisch einwandfrei und bricht im Browser den +# GESAMTEN Render ab — alles ab der Fehlerstelle bleibt auf "—" stehen. +# Real am 19.08.: die RSI-Zeile griff in `renderM15(s)` auf `d.market` zu; +# sechs leere Zeilen und eine komplett leere Breakout-Karte. +# ⚠ Laeuft gegen einen GESPEICHERTEN Snapshot (`tests/fixtures/snapshot.json`), +# NICHT gegen den laufenden Server — der Hook darf nicht davon abhaengen, ob +# gerade ein Server laeuft. +def _stufe_render(): + print("") + print("=" * 88) + print(" B2) RENDER-PROBE — JS-Laufzeitfehler (was `node --check` nicht sieht)") + print("=" * 88) + werkzeug = WURZEL / "tools" / "render_check.mjs" + fixture = WURZEL / "tests" / "fixtures" / "snapshot.json" + if not werkzeug.exists() or not fixture.exists(): + print(" Werkzeug oder Fixture fehlt — Stufe uebersprungen.") + return + try: + r = subprocess.run(["node", str(werkzeug), str(fixture)], + capture_output=True, text=True, timeout=120) + except FileNotFoundError: + print(" node nicht im PATH — Stufe uebersprungen.") + return + except Exception as e: + print(f" ⚠ Render-Probe nicht ausfuehrbar ({e}) — uebersprungen.") + return + aus = (r.stdout or "") + (r.stderr or "") + if r.returncode != 0 or "EXCEPTION" in aus: + for z in aus.strip().splitlines()[:6]: + print(f" {z}") + befunde.append("RENDER " + aus.strip().splitlines()[0][:80]) + else: + print(" alle Render-Funktionen laufen ohne Exception durch.") + + print(" A2) LINTER — undefinierte Namen (was py_compile nicht sieht)") print("=" * 88) # ⚠ RUFF statt pyflakes (2026-08-07, Web-Recherche zum 2026er-Standard): gleiche @@ -335,6 +420,11 @@ print(" keine gefunden." if not sig else f" {sig} Stellen.") print() print("=" * 88) +_stufe_typen() +_stufe_render() + +print("") +print("=" * 88) print(" ERGEBNIS: " + ("keine \\n-Falle gefunden" if not befunde else f"{len(befunde)} Befund(e)")) print("=" * 88) diff --git a/ty.toml b/ty.toml new file mode 100644 index 0000000..67237c6 --- /dev/null +++ b/ty.toml @@ -0,0 +1,34 @@ +# Typpruefung — BEWUSST ENGES Regelset (2026-08-19). +# +# ⚠⚠ Der volle Lauf meldet 172 Befunde, davon 97 `unresolved-attribute` aus +# fehlenden MetaTrader5-Stubs. Eine Stufe mit Dauer-Treffern wird ignoriert, +# und mit ihr die eine echte Meldung — das ist im Projekt dokumentiert +# (ruff-Schweregrad-Trennung, 07.08.). Deshalb blockieren nur die Regeln, +# die ECHTE Aufruffehler finden und hier nachweislich rauschfrei sind. +# +# Anlass ist ein realer Fehler vom 19.08.: `paarweise(d)` mit 1 von 2 +# Pflichtargumenten. py_compile, ruff F821 und pytest liessen ihn durch; er +# schlug erst zur Laufzeit zu. +[src] +exclude = ["backtest_*.py", "analyze_*.py", "tests/**"] + +[rules] +# ── AKTIV: echte Aufruffehler ──────────────────────────────────────────── +missing-argument = "error" +too-many-positional-arguments = "error" +unknown-argument = "error" +# ⚠ call-non-callable / not-iterable BEWUSST AUS: beide melden hier nur +# Falsch-Positive aus heterogenen dicts (`c["have"]()` in den CHECKS, +# `"up" in c` in news.py). Geprueft, nicht angenommen. +call-non-callable = "ignore" +not-iterable = "ignore" +unresolved-import = "ignore" +# ── AUS: braucht vollstaendige Stubs/Annotationen, sonst Dauer-Rauschen ── +unresolved-attribute = "ignore" +unsupported-operator = "ignore" +invalid-argument-type = "ignore" +no-matching-overload = "ignore" +not-subscriptable = "ignore" +possibly-missing-attribute = "ignore" +invalid-assignment = "ignore" +invalid-return-type = "ignore"