"""Beobachtung des Paper-Runners gegen die der NautilusEnv — auf ECHTEN IEX-Kerzen eines Tages. Koordinator-Pruefskript zu Teil 10, nicht fuers Repo; ein Fangnetz, kein Nachweis. Frage (Herkunft: Projektvorgabe, Spec Teil 10, "Abweichungen vom Training"): Liefert der Weg des Runners (REST-Antwort -> AlpacaKerzenquelle -> Kerze -> baue_beobachtung) Wert fuer Wert dieselbe Beobachtung wie der Weg des Trainings (dieselbe REST-Antwort -> alpaca_json_zu_bars -> Bar -> NautilusEnv._beobachtung)? Der Test im Repo baut seine Kerzen aus den Bars selbst und sieht deshalb keine Abweichung zwischen den zwei Umwandlungen; dieser Lauf nimmt zwei getrennte Wege von derselben Antwort. Was es nicht sieht: Beobachtungen mit offener Position (Position und Kontowert kommen im Betrieb vom Broker), SIP gegen IEX (gemessen in der Vorabmessung), andere Instrumente und Tage. Regressionsnachweis: --sabotage verschiebt einen Schlusskurs um einen Cent und muss mit Exit 2 enden. Aufruf in WSL aus dem Projektordner (dort liegt config/secrets.toml; der Schluessel wird nie ausgegeben): python pruef_beobachtung_echt.py 2026-09-17 [--sabotage] """ import dataclasses import sys import tomllib from datetime import datetime from pathlib import Path import numpy as np import requests from nautilus_trader.model.data import BarType from nautilus_trader.test_kit.providers import TestInstrumentProvider from trading.data.loaders import alpaca_json_zu_bars from trading.data.loaders import nur_regulaere_handelszeit from trading.paper.beobachtung import baue_beobachtung from trading.paper.kerzen import HANDELSZONE from trading.paper.kerzen import AlpacaKerzenquelle from trading.paper.kerzen import handelsbeginn from trading.paper.kerzen import handelsende from trading.rl.env import EnvConfig from trading.rl.env import NautilusEnv from trading.sim.venues import lade_venue_config FENSTER = 20 EPISODE = 120 PERIODEN = (5, 20) BASIS = 100 MAX_STUFE = 2 BAR_TYPE = "AAPL.XNAS-1-MINUTE-LAST-EXTERNAL" def main() -> int: if len(sys.argv) < 2: print(__doc__) return 3 tag = datetime.strptime(sys.argv[1], "%Y-%m-%d").replace(tzinfo=HANDELSZONE) sabotage = "--sabotage" in sys.argv[2:] ende = handelsende(tag) # Weg des Runners. kerzen = AlpacaKerzenquelle.from_secrets("config/secrets.toml", feed="iex").kerzen_des_tages( "AAPL.XNAS", ende ) print("Kerzen ueber die Kerzenquelle:", len(kerzen)) if len(kerzen) < FENSTER + EPISODE: print("VORAUSSETZUNG FEHLT: zu wenige Kerzen fuer eine Episode") return 3 # Weg des Trainings: dieselbe Anfrage noch einmal roh, dann die Umwandlung des Katalogs. geheim = tomllib.loads(Path("config/secrets.toml").read_text(encoding="utf-8"))["alpaca"] kopf = {"APCA-API-KEY-ID": geheim["key_id"], "APCA-API-SECRET-KEY": geheim["secret_key"]} parameter = { "symbols": "AAPL", "timeframe": "1Min", "start": handelsbeginn(ende).isoformat().replace("+00:00", "Z"), "end": ende.isoformat().replace("+00:00", "Z"), "feed": "iex", "adjustment": "raw", "limit": 10000, } antwort = requests.get( "https://data.alpaca.markets/v2/stocks/bars", headers=kopf, params=parameter, timeout=30 ) antwort.raise_for_status() nutzlast = antwort.json() if nutzlast.get("next_page_token"): print("VORAUSSETZUNG FEHLT: die Antwort hat eine Folgeseite; der Vergleich waere unvollstaendig") return 3 instrument = TestInstrumentProvider.equity(symbol="AAPL", venue="XNAS") bars = nur_regulaere_handelszeit( alpaca_json_zu_bars( nutzlast, symbol="AAPL", bar_type=BarType.from_str(BAR_TYPE), price_precision=instrument.price_precision, size_precision=instrument.size_precision, ) ) print("Kerzen ueber den Katalogweg: ", len(bars)) if len(bars) != len(kerzen): print("BEFUND: die zwei Wege liefern verschieden viele Kerzen") return 2 halbe_cent = sum( 1 for eintrag in nutzlast["bars"]["AAPL"] for feld in ("o", "h", "l", "c") if round(float(eintrag[feld]) * 100) != float(eintrag[feld]) * 100 ) print("Rohkurse mit mehr als zwei Nachkommastellen:", halbe_cent) if sabotage: kerzen = list(kerzen) kerzen[57] = dataclasses.replace(kerzen[57], close=round(kerzen[57].close + 0.01, 2)) venue = lade_venue_config("config/venues/us_equity.toml") env = NautilusEnv( venue_config=venue, instrument=instrument, bars=bars, config=EnvConfig( bar_type=BAR_TYPE, fenster=FENSTER, basis_menge=BASIS, max_stufe=MAX_STUFE, episoden_laenge=EPISODE, indikator_perioden=PERIODEN, ), ) startkapital = venue.starting_balance.as_double() verglichen = 0 ungleich = 0 groesste = 0.0 for start in range(0, len(bars) - FENSTER - EPISODE + 1, 5): soll, _ = env.reset(options={"start_index": start}) ist = baue_beobachtung( kerzen[start : start + FENSTER], position=0, hoechste_position=MAX_STUFE * BASIS, kontowert=startkapital, startkapital=startkapital, schritte_seit_trade=0, episoden_laenge=EPISODE, indikator_perioden=PERIODEN, ) verglichen += 1 if soll.shape != ist.shape or not np.array_equal(soll, ist): ungleich += 1 abstand = float(np.max(np.abs(soll.astype(np.float64) - ist.astype(np.float64)))) groesste = max(groesste, abstand) if ungleich <= 3: stellen = np.flatnonzero(soll != ist)[:5] print(f" ungleich ab Kerze {start}: Stellen {stellen.tolist()}, groesster Abstand {abstand:.3e}") env.close() print(f"{verglichen} Beobachtungen verglichen, {ungleich} ungleich, groesster Abstand {groesste:.3e}") if ungleich: print("BEFUND: die Beobachtung des Runners weicht von der der NautilusEnv ab") return 2 return 0 if __name__ == "__main__": try: sys.exit(main()) except Exception as ausnahme: # noqa: BLE001 - ein Absturz des Pruefskripts ist selbst ein Befund print(f"BEFUND: das Pruefskript ist abgebrochen: {ausnahme!r}") sys.exit(2)