Files
claude-regelwerk/.vault-tools/trading-plattform-pruefung/pruef_beobachtung_echt.py
T
Pascal MichelandClaude Opus 5 371a1f5d2a Automatischer Push: 8 Datei(en) geändert
- .vault-tools/trading-plattform-pruefung/LIESMICH.md
- .vault-tools/trading-plattform-pruefung/angriffe.py
- .vault-tools/trading-plattform-pruefung/browserlauf_zugang.py
- .vault-tools/trading-plattform-pruefung/handabruf_kerzen.py
- .vault-tools/trading-plattform-pruefung/lies_paper_protokoll.py
- .vault-tools/trading-plattform-pruefung/pruef_beobachtung_echt.py
- .vault-tools/trading-plattform-pruefung/start_probe_backend.py
- .vault-tools/trading-plattform-pruefung/start_probe_zugang.py

Erzeugt vom PostToolUse-Hook .vault-tools/auto-push/auto_push.py.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-18 04:00:01 +02:00

168 lines
6.3 KiB
Python

"""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)