Automatischer Push: 11 Datei(en) geändert

- .vault-tools/trading-plattform-pruefung/LIESMICH.md
- .vault-tools/trading-plattform-pruefung/abendkontrolle.py
- .vault-tools/trading-plattform-pruefung/abendkontrolle_angriffe.py
- .vault-tools/trading-plattform-pruefung/abendkontrolle_mutanten.py
- .vault-tools/trading-plattform-pruefung/abendkontrolle_wsl.py
- .vault-tools/trading-plattform-pruefung/browserlauf.py
- .vault-tools/trading-plattform-pruefung/browserlauf_zugang.py
- .vault-tools/trading-plattform-pruefung/ha_totmann.py
- .vault-tools/trading-plattform-pruefung/ha_totmann_wsl.py
- .vault-tools/trading-plattform-pruefung/scan_push.sh
- AI/Regelwerk/codeworkflow.md

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

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
Pascal MichelandClaude Opus 5 committed 2026-09-26 04:00:02 +02:00
1 parent ea278dc24f
commit c115a13190
11 files changed
+1848 -37

No files matched your search

@@ -17,6 +17,11 @@ die mit "## Agentenprotokoll" beginnt.
einer Zeile in lauf.log: Die Kontrolle ist gelaufen und hat ihre Meldung schon geschickt; ein zweiter
Lauf schickte sie nur noch einmal.
- Ein Tag, der laenger als NACHHOLEN_TAGE scheitert, faellt aus dem Fenster (lauf.log behaelt ihn).
Seit Paket IB2 zwei Teile je Tag, nacheinander: alpaca wie oben, dann ibkr (abendkontrolle_wsl.py
--teil ibkr) mit eigener Ergebnisdatei <tag>-ibkr.json, eigener Zeile "Abendkontrolle IBKR" in
derselben Notiz und eigenem Nachholen ab IBKR_ERSTER_TAG. --teil nimmt nur einen Teil; mit
--ohne-wsl gilt ohne --teil nur alpaca (die Angriffsfaelle von vor IB2 bleiben gleich). Ein
Ergebnis, das zu einem anderen Teil gehoert, gilt als gescheitert.
Die Notiz wird ueber eine Nachbardatei und os.replace geschrieben: Ein Abbruch mitten im Schreiben
hinterlaesst die alte Datei, nicht eine halbe. Gegen eine Claude-Sitzung, die dieselbe Notiz im selben
Augenblick schreibt, schuetzt das nicht (Zeitfenster Lesen bis Ersetzen, Millisekunden) -- benannt.
@@ -50,25 +55,34 @@ FRUEHESTENS = time(22, 30)
WSL_TIMEOUT_S = 1200
KEIN_FENSTER = 0x08000000 # CREATE_NO_WINDOW: pythonw hat keine Konsole, wsl.exe soll keine oeffnen
UEBERSCHRIFT = "## Agentenprotokoll"
# Seit Paket IB2: die IBKR-Instanz als zweiter Teil mit eigener Ergebnisdatei und eigener Zeile.
TEILE = ("alpaca", "ibkr")
# Der erste Tag, den der Teil ibkr prueft; gesetzt beim Einspielen von IB2 (Planuebersicht IB2).
IBKR_ERSTER_TAG = date(2026, 9, 25)
IBKR_ENDUNG = "-ibkr"
KOEPFE = {
"alpaca": "Abendkontrolle Trading Plattform (automatisch)",
"ibkr": "Abendkontrolle IBKR (automatisch)",
}
def faellige_tage(jetzt: datetime, ablage: Path) -> list[str]:
def faellige_tage(jetzt: datetime, ablage: Path, erster_tag: date = ERSTER_TAG, endung: str = "") -> list[str]:
tage = []
for zurueck in range(NACHHOLEN_TAGE, -1, -1):
tag = jetzt.date() - timedelta(days=zurueck)
if tag < ERSTER_TAG or tag.weekday() >= 5:
if tag < erster_tag or tag.weekday() >= 5:
continue
if tag == jetzt.date() and jetzt.time() < FRUEHESTENS:
continue
if (ablage / f"{tag.isoformat()}.json").exists():
if (ablage / f"{tag.isoformat()}{endung}.json").exists():
continue
tage.append(tag.isoformat())
return tage
def fahre_wsl(tag: str) -> dict:
def fahre_wsl(tag: str, teil: str = "alpaca") -> dict:
lauf = subprocess.run(
[WSL, "-d", "Debian", "-u", "claude", "--", WSL_PY, WSL_SKRIPT, "--tag", tag],
[WSL, "-d", "Debian", "-u", "claude", "--", WSL_PY, WSL_SKRIPT, "--tag", tag, "--teil", teil],
capture_output=True, timeout=WSL_TIMEOUT_S, creationflags=KEIN_FENSTER,
)
ausgabe = lauf.stdout.decode("utf-8", errors="replace")
@@ -107,7 +121,11 @@ def zeile_fuer(ergebnis: dict, uhrzeit: str) -> str:
killswitch = fakten.get("killswitch") or {}
if killswitch:
teile.append(f"Killswitch {killswitch.get('zustand')} ({killswitch.get('ausloeser')})")
zusammen = "; ".join(teile)
return _zeile(kopf, befunde, "; ".join(teile), fakten, hinweise)
def _zeile(kopf: str, befunde: list, zusammen: str, fakten: dict, hinweise: list) -> str:
"""Kopf, Urteil, Zusammenfassung, Befunde und Hinweise -- fuer beide Teile dieselbe Form."""
if not befunde:
text = f"{kopf}: ohne Befund. {zusammen}."
else:
@@ -119,6 +137,38 @@ def zeile_fuer(ergebnis: dict, uhrzeit: str) -> str:
return text
def zeile_fuer_ibkr(ergebnis: dict, uhrzeit: str) -> str:
"""Die eigene Zeile der IBKR-Instanz (seit Paket IB2). Kein Betrag, keine Kontonummer."""
kopf = f"- {uhrzeit} Uhr — {KOEPFE['ibkr']}"
befunde = ergebnis.get("befunde") or []
fakten = ergebnis.get("fakten") or {}
hinweise = ergebnis.get("hinweise") or []
if fakten.get("handelstag") is False and not befunde:
return f"{kopf}: kein Handelstag laut Alpaca-Kalender."
if fakten.get("nicht_gestartet") and not befunde:
return f"{kopf}: IBKR-Instanz heute nicht gestartet (Zeitgeber aus)."
teile = []
ende = fakten.get("ende") or {}
if ende:
teile.append(
f"Ende {ende.get('grund')}, Exit {ende.get('exit')}, Orders {ende.get('orders')}, "
f"Position {ende.get('position')}"
)
laeufe = fakten.get("laeufe") or []
if laeufe:
teile.append(f"{len(laeufe)} Lauf/Läufe, {sum(l.get('takte_aktiv', 0) for l in laeufe)} Takte aktiv")
konto = fakten.get("konto") or {}
if konto:
teile.append(
f"Konto beim Gateway: {konto.get('positionen')} Position(en), "
f"{konto.get('orders')} offene Order(s)"
)
killswitch = fakten.get("killswitch") or {}
if killswitch:
teile.append(f"Killswitch {killswitch.get('zustand')} ({killswitch.get('ausloeser')})")
return _zeile(kopf, befunde, "; ".join(teile), fakten, hinweise)
def schreibe_notiz(vault: Path, tag: str, zeile: str) -> None:
notiz = vault / "Inbox" / f"{tag}.md"
if notiz.exists():
@@ -167,47 +217,81 @@ def main(argv=None) -> int:
zerleger.add_argument("--vault", default=str(VAULT))
zerleger.add_argument("--ohne-wsl", help="Ergebnis aus einer JSON-Datei statt aus WSL (Angriffsfaelle)")
zerleger.add_argument("--jetzt", help="ISO-Zeitpunkt statt der Uhr (Angriffsfaelle)")
zerleger.add_argument("--teil", choices=TEILE, help="nur diesen Teil; ohne Angabe beide, mit --ohne-wsl nur alpaca")
argumente = zerleger.parse_args(argv)
vault = Path(argumente.vault)
ablage = vault / ABLAGE_TEIL
jetzt = datetime.fromisoformat(argumente.jetzt) if argumente.jetzt else datetime.now()
tage = [argumente.tag] if argumente.tag else faellige_tage(jetzt, ablage)
logge(ablage, f"start, faellig: {tage}")
# Ohne --teil beide Teile, erst alpaca, dann ibkr. Mit --ohne-wsl ohne --teil nur alpaca: Eine
# Ergebnisdatei ist immer die eines Teils, und die Angriffsfaelle von vor IB2 bleiben gleich.
teile = [argumente.teil] if argumente.teil else (["alpaca"] if argumente.ohne_wsl else list(TEILE))
code = 0
for teil in teile:
if argumente.tag:
tage = [argumente.tag]
elif teil == "ibkr":
tage = faellige_tage(jetzt, ablage, erster_tag=IBKR_ERSTER_TAG, endung=IBKR_ENDUNG)
else:
tage = faellige_tage(jetzt, ablage)
vorsatz = "" if teil == "alpaca" else f"{teil} "
# Scheitert ein Teil ausserhalb seiner eigenen Fehlerbehandlung (Ergebnisdatei nicht
# schreibbar, lauf.log gesperrt), laeuft der naechste trotzdem (Pruefung IB2).
try:
logge(ablage, f"{vorsatz}start, faellig: {tage}")
code = max(code, fahre_teil(teil, tage, jetzt, vault, ablage, argumente.ohne_wsl))
except Exception as ausnahme:
code = 1
try:
logge(ablage, f"{vorsatz}abgebrochen: {type(ausnahme).__name__}")
except Exception:
pass
return code
def fahre_teil(teil: str, tage: list[str], jetzt: datetime, vault: Path, ablage: Path, ohne_wsl: str | None) -> int:
"""Ein Teil fuer alle faelligen Tage: Ergebnisdatei, Zeile in der Notiz, lauf.log."""
endung = IBKR_ENDUNG if teil == "ibkr" else ""
zeile_von = zeile_fuer_ibkr if teil == "ibkr" else zeile_fuer
vorsatz = "" if teil == "alpaca" else f"{teil} "
code = 0
for tag in tage:
uhrzeit = jetzt.strftime("%H:%M") if tag == jetzt.date().isoformat() else f"{jetzt:%Y-%m-%d %H:%M}"
try:
if argumente.ohne_wsl:
ergebnis = json.loads(Path(argumente.ohne_wsl).read_text(encoding="utf-8"))
if ohne_wsl:
ergebnis = json.loads(Path(ohne_wsl).read_text(encoding="utf-8"))
else:
ergebnis = fahre_wsl(tag)
ergebnis = fahre_wsl(tag, teil)
if not isinstance(ergebnis, dict) or not isinstance(ergebnis.get("befunde"), list):
raise RuntimeError("Ergebnis des WSL-Teils in unerwarteter Form")
# Ein Ergebnis ohne teil stammt von einem WSL-Teil vor IB2 und gilt als alpaca. Passt
# der Teil nicht, landete es sonst still in der falschen Ergebnisdatei und Zeile.
if ergebnis.get("teil", "alpaca") != teil:
raise RuntimeError(f"Ergebnis des WSL-Teils gehoert zu {ergebnis.get('teil')!r}, nicht zu {teil!r}")
except Exception as ausnahme:
code = 1
logge(ablage, f"{tag}: gescheitert {ausnahme!r}\n{traceback.format_exc()}")
logge(ablage, f"{vorsatz}{tag}: gescheitert {ausnahme!r}\n{traceback.format_exc()}")
try:
schreibe_notiz(
vault, tag,
f"- {uhrzeit} Uhr — Abendkontrolle Trading Plattform (automatisch): **gescheitert** "
f"- {uhrzeit} Uhr — {KOEPFE[teil]}: **gescheitert** "
f"({type(ausnahme).__name__}: {str(ausnahme)[:200]}) #needs-review. Der nächste Lauf versucht es erneut.",
)
except Exception as zweite:
logge(ablage, f"{tag}: auch die Notiz scheiterte {zweite!r}")
logge(ablage, f"{vorsatz}{tag}: auch die Notiz scheiterte {zweite!r}")
continue
# Ein Abbruch im WSL-Teil (Exit 2) wird wiederholt; alles andere gilt als gelaufen.
if ergebnis.get("exit") != 2:
(ablage / f"{tag}.json").write_text(
(ablage / f"{tag}{endung}.json").write_text(
json.dumps(ergebnis, ensure_ascii=False, indent=1), encoding="utf-8", newline="\n"
)
else:
code = 1
logge(ablage, f"{tag}: {len(ergebnis['befunde'])} Befund(e), exit {ergebnis.get('exit')}")
logge(ablage, f"{vorsatz}{tag}: {len(ergebnis['befunde'])} Befund(e), exit {ergebnis.get('exit')}")
try:
schreibe_notiz(vault, tag, zeile_fuer(ergebnis, uhrzeit))
schreibe_notiz(vault, tag, zeile_von(ergebnis, uhrzeit))
except Exception as ausnahme:
code = 1
logge(ablage, f"{tag}: Notiz nicht geschrieben {ausnahme!r}")
logge(ablage, f"{vorsatz}{tag}: Notiz nicht geschrieben {ausnahme!r}")
return code