- .claude/agents/planer.md - .claude/skills/plan-executer-pruefung/SKILL.md - .claude/workflows/plankritik.js - .claude/workflows/planung.js - .claude/workflows/pruefung.js - .claude/workflows/verbrauch.js - .claude/workflows/welle-mit-pruefung.js - .vault-tools/plan-kopie/LIESMICH.md - .vault-tools/plan-kopie/plan_zeichen.py - .vault-tools/plan-kopie/rot_nachweis.py - .vault-tools/trading-plattform-pruefung/LIESMICH.md - .vault-tools/trading-plattform-pruefung/abendkontrolle.py - … und 34 weitere Erzeugt vom PostToolUse-Hook .vault-tools/auto-push/auto_push.py. Co-Authored-By: Claude Opus 5 <[email protected]>
205 lines
8.5 KiB
Python
205 lines
8.5 KiB
Python
"""Paket AF ohne Netz: Gegenfall und Gegenprobe der Fortsetzung zum Tagesstart.
|
|
|
|
Koordinator-Werkzeug, nicht Teil des Repos (Paket AF, 2026-09-24). Faehrt die echte
|
|
PaperSitzung des Arbeitsbaums mit der ausgelieferten config/paper.toml und ihrem Schalter
|
|
[auto_fortsetzen] gegen einen ProbeBroker, mit echten IEX-Kerzen aus einer JSON-Datei von
|
|
hole_kerzen.py (fuer jedes Instrument dieselben), einer ProbeUhr, einem Wegwerf-Register
|
|
mit der Neustartregel und einem Melder, der nur sammelt. Kein Netz, keine Order an Alpaca,
|
|
das echte Register bleibt unberuehrt.
|
|
|
|
Drei Faelle:
|
|
restposition vor der Oeffnung gestartet, Restposition beim Start -> nicht fortgesetzt
|
|
mitten 60 Marktminuten nach der Oeffnung gestartet, flach -> nicht fortgesetzt
|
|
flach vor der Oeffnung gestartet, flach -> fortgesetzt (die Gegenprobe)
|
|
Exit 0 heisst: Der Fall lief wie erwartet. Jede Abweichung ist eine Zeile BEFUND und Exit 2,
|
|
eine fehlende Voraussetzung Exit 3.
|
|
Aufruf (WSL, aus dem Arbeitsbaum): python af_gegenfall.py <kerzen.json> <fall>
|
|
"""
|
|
|
|
import json
|
|
import sys
|
|
import tempfile
|
|
from datetime import datetime
|
|
from datetime import timedelta
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path.cwd()))
|
|
|
|
from trading.killswitch.killswitch import Killswitch # noqa: E402
|
|
from trading.killswitch.killswitch import lade_killswitch_config # noqa: E402
|
|
from trading.paper.agenten import agenten_je_instrument # noqa: E402
|
|
from trading.paper.agenten import beschreibung_je_instrument # noqa: E402
|
|
from trading.paper.agenten import lade_agent # noqa: E402
|
|
from trading.paper.config import enge_limits # noqa: E402
|
|
from trading.paper.config import lade_paper_config # noqa: E402
|
|
from trading.paper.kerzen import FesteKerzenquelle # noqa: E402
|
|
from trading.paper.kerzen import Kerze # noqa: E402
|
|
from trading.paper.probe import ProbeBroker # noqa: E402
|
|
from trading.paper.sitzung import PaperSitzung # noqa: E402
|
|
from trading.paper.uhr import ProbeUhr # noqa: E402
|
|
from trading.registry.registry import Registry # noqa: E402
|
|
from trading.risk.limits import lade_limits # noqa: E402
|
|
|
|
FAELLE = ("restposition", "mitten", "flach")
|
|
RESTPOSITION = 10
|
|
|
|
|
|
class SammelMelder:
|
|
"""Nimmt jede Meldung an und schickt nichts."""
|
|
|
|
aktiv = True
|
|
|
|
def __init__(self) -> None:
|
|
self.gesehen = []
|
|
|
|
def melde(self, meldung):
|
|
self.gesehen.append(meldung)
|
|
return None
|
|
|
|
|
|
class Protokoll:
|
|
"""Haelt die Zeilen im Speicher, durch json.dumps wie das echte Protokoll."""
|
|
|
|
def __init__(self) -> None:
|
|
self.zeilen = []
|
|
|
|
def schreibe(self, eintrag) -> None:
|
|
self.zeilen.append(json.loads(json.dumps(eintrag, ensure_ascii=False, default=str)))
|
|
|
|
|
|
def lies_kerzen(pfad: Path) -> list:
|
|
roh = json.loads(pfad.read_text(encoding="utf-8"))
|
|
return [
|
|
Kerze(
|
|
zeit=datetime.fromisoformat(k["zeit"]),
|
|
open=float(k["open"]),
|
|
high=float(k["high"]),
|
|
low=float(k["low"]),
|
|
close=float(k["close"]),
|
|
volume=float(k["volume"]),
|
|
)
|
|
for k in roh
|
|
]
|
|
|
|
|
|
def main() -> int:
|
|
if len(sys.argv) != 3 or sys.argv[2] not in FAELLE:
|
|
print(f"Aufruf: python af_gegenfall.py <kerzen.json> <{'|'.join(FAELLE)}>")
|
|
return 3
|
|
pfad, fall = Path(sys.argv[1]), sys.argv[2]
|
|
if not pfad.is_file():
|
|
print(f"VORAUSSETZUNG FEHLT: {pfad}")
|
|
return 3
|
|
kerzen = sorted(lies_kerzen(pfad), key=lambda kerze: kerze.zeit)
|
|
if not kerzen:
|
|
print(f"VORAUSSETZUNG FEHLT: keine Kerze in {pfad}")
|
|
return 3
|
|
config = lade_paper_config("config/paper.toml")
|
|
if not getattr(config, "auto_fortsetzen", False):
|
|
print("VORAUSSETZUNG FEHLT: config/paper.toml traegt [auto_fortsetzen] aktiv = true nicht")
|
|
return 3
|
|
limits = enge_limits(lade_limits("config/risk.toml"), config)
|
|
ks_config = lade_killswitch_config("config/killswitch.toml")
|
|
quelle = FesteKerzenquelle(kerzen)
|
|
if fall == "mitten":
|
|
beginn = kerzen[0].zeit + timedelta(minutes=60)
|
|
else:
|
|
beginn = kerzen[0].zeit - timedelta(minutes=30)
|
|
uhr = ProbeUhr(beginn)
|
|
rest = config.alle_instrumente[-1]
|
|
broker = ProbeBroker(
|
|
kerzenquelle=quelle,
|
|
uhr=uhr,
|
|
instrument_id=config.instrument_id,
|
|
instrumente=config.alle_instrumente,
|
|
positionen={rest: RESTPOSITION} if fall == "restposition" else None,
|
|
)
|
|
protokoll = Protokoll()
|
|
melder = SammelMelder()
|
|
with tempfile.TemporaryDirectory(prefix="af-gegenfall-") as wurzel:
|
|
ordner = Path(wurzel)
|
|
registry = Registry(db_pfad=ordner / "registry.db", artefakt_wurzel=ordner / "artifacts")
|
|
Killswitch.laden(broker=broker, registry=registry, config=ks_config)
|
|
agent, beschreibung = lade_agent(config, registry)
|
|
sitzung = PaperSitzung(
|
|
broker=broker,
|
|
kerzenquelle=quelle,
|
|
agent=agent,
|
|
agent_beschreibung=beschreibung_je_instrument(config, beschreibung),
|
|
agenten=agenten_je_instrument(config, agent),
|
|
registry=registry,
|
|
limits=limits,
|
|
killswitch_config=ks_config,
|
|
config=config,
|
|
uhr=uhr,
|
|
protokoll=protokoll,
|
|
protokoll_pfad="af-gegenfall.jsonl",
|
|
status_pfad=ordner / "status.json",
|
|
modus="probe",
|
|
soll_abbrechen=lambda: False,
|
|
melder=melder,
|
|
)
|
|
code = sitzung.laufe()
|
|
gespeichert = registry.killswitch_zustand()
|
|
ereignisse = registry.killswitch_ereignisse()
|
|
|
|
auto = [z for z in protokoll.zeilen if z.get("was") == "auto_fortsetzen"]
|
|
orders = [z for z in protokoll.zeilen if z.get("art") == "order"]
|
|
takte = [z for z in protokoll.zeilen if z.get("art") == "takt"]
|
|
print(f"Fall {fall}, {len(kerzen)} Kerzen ab {kerzen[0].zeit.isoformat()}, Beginn {beginn.isoformat()}")
|
|
print(f"Instrumente: {', '.join(config.alle_instrumente)}")
|
|
for zeile in auto:
|
|
print(f"auto_fortsetzen {zeile.get('ergebnis')} | {zeile.get('text')} | phase {zeile.get('phase')}")
|
|
for meldung in melder.gesehen:
|
|
print(f"Meldung [{meldung.prioritaet}] {meldung.titel}")
|
|
for zeile in meldung.text.split("\n"):
|
|
if zeile.startswith(("Killswitch", "Nicht selbst fortgesetzt", "Ausloeser", "Position")):
|
|
print(f" {zeile}")
|
|
print(f"Killswitch am Ende: {gespeichert.zustand}, Ausloeser {gespeichert.ausloeser}")
|
|
print(f"Killswitch-Ereignisse: {[(e.stufe, e.ausloeser) for e in ereignisse]}")
|
|
print(f"Takte {len(takte)}, Orders {len(orders)}, Exit {code}")
|
|
|
|
befunde = []
|
|
titel = [m.titel for m in melder.gesehen]
|
|
automatik = [e for e in ereignisse if e.ausloeser == "automatik"]
|
|
if fall == "flach":
|
|
if [z.get("ergebnis") for z in auto] != ["fortgesetzt"]:
|
|
befunde.append(f"auto_fortsetzen {[z.get('ergebnis') for z in auto]} statt ['fortgesetzt']")
|
|
if "Paper-Runner laeuft" not in titel or "Paper-Runner wartet" in titel:
|
|
befunde.append(f"Meldungen {titel}")
|
|
if len(automatik) != 1 or automatik[0].stufe != "fortsetzen":
|
|
befunde.append(f"Ereignisse mit Ausloeser automatik: {[(e.stufe, e.grund[:60]) for e in automatik]}")
|
|
if not orders:
|
|
befunde.append("keine Order -- fortgesetzt, aber nicht gehandelt")
|
|
if code != 0:
|
|
befunde.append(f"Exit {code} statt 0")
|
|
else:
|
|
if fall == "restposition":
|
|
erwartet = f"Konto nicht flach: {rest} {RESTPOSITION}"
|
|
else:
|
|
erwartet = "dieser Prozess ist nicht vor der Oeffnung gestartet"
|
|
if [z.get("text") for z in auto] != [f"nicht_fortgesetzt: {erwartet}"]:
|
|
befunde.append(f"auto_fortsetzen {[z.get('text') for z in auto]}")
|
|
wartet = [m for m in melder.gesehen if m.titel == "Paper-Runner wartet"]
|
|
if len(wartet) != 1 or f"Nicht selbst fortgesetzt: {erwartet}" not in wartet[0].text.split("\n"):
|
|
befunde.append(f"Meldungen {titel}")
|
|
if automatik:
|
|
befunde.append(f"{len(automatik)} Ereignis(se) mit Ausloeser automatik")
|
|
if orders:
|
|
befunde.append(f"{len(orders)} Order(s) ohne Fortsetzung")
|
|
if any(t.get("killswitch") != "angehalten" for t in takte):
|
|
befunde.append("ein Takt nicht angehalten")
|
|
erwarteter_code = 4 if fall == "restposition" else 0
|
|
if code != erwarteter_code:
|
|
befunde.append(f"Exit {code} statt {erwarteter_code}")
|
|
for befund in befunde:
|
|
print(f"BEFUND: {befund}")
|
|
if befunde:
|
|
return 2
|
|
print("ERGEBNIS: Fall wie erwartet")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|