- .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]>
237 lines
13 KiB
Python
237 lines
13 KiB
Python
"""Rueckweg des Paper-Runners von zehn Aktien auf AAPL, einmal, nach Pascals Vorgabe.
|
|
|
|
Koordinator-Werkzeug, nicht fuers Repo. Pascal am 2026-09-24: zehn Aktien "bis Mittwoch" -- gelesen als
|
|
bis einschliesslich Mittwoch 2026-09-30; ab Donnerstag 2026-10-01 handelt der Runner wieder AAPL mit der
|
|
Registerversion. Der Rueckweg ist der aus Paket M: config/paper-aapl-register.toml wird config/paper.toml.
|
|
Geplante Aufgabe "Trading Plattform - Rueckweg AAPL" (rueckweg_aapl_einrichten.ps1): 2026-09-30 22:50,
|
|
zweiter Versuch 2026-10-01 22:10, verpasste Termine beim naechsten Start (StartWhenAvailable).
|
|
|
|
Schritte, jeder mit seinem Grund:
|
|
1. Nicht vor FRUEHESTENS (nach Boersenschluss des letzten Tages mit zehn Aktien).
|
|
2. Ergebnisdatei ABLAGE/rueckweg.json da -> schon erledigt, nichts tun.
|
|
3. Laeuft der Runner (systemctl is-active trading-paper.service in WSL), wird nichts getauscht: Er hat
|
|
seine Konfiguration beim Start gelesen, und ein Tausch mitten am Tag wirkte erst beim naechsten Start.
|
|
Befund, der zweite Termin versucht es erneut.
|
|
Stand er, wird nach WARTEN_S noch einmal gefragt: Hat erst dieser Aufruf WSL geweckt, startet der
|
|
Zeitgeber den Runner 60 s danach (OnStartupSec=60) und liest dabei die Konfiguration.
|
|
4. Im Projektordner muss ZWEIG ausgecheckt sein, sonst Befund.
|
|
5. config/paper.toml gleich der Rueckweg-Datei: sauberer Ordner -> schon getauscht (Ergebnisdatei,
|
|
Ende); nur config/paper.toml geaendert -> halb erledigt (ein gescheiterter Commit), weiter mit 8;
|
|
sonst Befund.
|
|
6. Sonst muss der Ordner sauber sein (git status --porcelain leer), sonst Befund: Ein Commit ueber
|
|
fremde Aenderungen waere schlimmer als ein Tag mehr mit zehn Aktien. Und config/paper.toml muss die
|
|
Konfiguration mit mehreren Aktien sein (Zeile 'quelle = "folge"'), sonst Befund.
|
|
7. Jeder Schluessel aus config/paper.toml ausser den erwarteten Unterschieden (ERWARTET_ANDERS) muss
|
|
in der Rueckweg-Datei mit demselben Wert stehen, sonst Befund: Der Tausch verloere ihn still oder
|
|
schaltete ihn still um (etwa [auto_fortsetzen] aktiv = false, von Pascal gesetzt).
|
|
8. Bytes kopieren, git add, git commit (lokal; gepusht wird von der naechsten Sitzung -- ein Push aus
|
|
einer Aufgabe ohne Fenster koennte an einer Anmeldeabfrage haengen).
|
|
9. Eine Zeile in Inbox/<heute>.md unter "## Agentenprotokoll"; bei einem Befund zusaetzlich eine Meldung
|
|
mit hoher Prioritaet ueber abendkontrolle_wsl.py --meldung.
|
|
Aufruf von Hand: python rueckweg_aapl.py [--jetzt ISO] [--projekt PFAD] [--vault PFAD] [--ohne-wsl]
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
import tomllib
|
|
import traceback
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
from abendkontrolle import KEIN_FENSTER # noqa: E402
|
|
from abendkontrolle import WSL # noqa: E402
|
|
from abendkontrolle import WSL_PY # noqa: E402
|
|
from abendkontrolle import WSL_SKRIPT # noqa: E402
|
|
from abendkontrolle import schreibe_notiz # noqa: E402
|
|
|
|
VAULT = Path(r"D:\Claude")
|
|
PROJEKT = VAULT / "Code Projekte" / "active - Trading Plattform"
|
|
GIT = r"C:\Program Files\Git\cmd\git.exe"
|
|
FRUEHESTENS = datetime(2026, 9, 30, 22, 5)
|
|
ZIEL = Path("config") / "paper.toml"
|
|
RUECKWEG = Path("config") / "paper-aapl-register.toml"
|
|
MEHRERE = 'quelle = "folge"'
|
|
ZWEIG = "master"
|
|
ERWARTET_ANDERS = ("agent.", "umgebung.instrumente", "umgebung.instrument", "grenzen.max_tagesverlust_anteil")
|
|
WARTEN_S = 90
|
|
NOCHMAL = "Steht noch ein Termin der Aufgabe aus, versucht er es erneut; sonst entscheidet die naechste Sitzung."
|
|
NACHRICHT = (
|
|
"Rueckweg auf AAPL: config/paper.toml aus config/paper-aapl-register.toml\n\n"
|
|
"Pascal am 2026-09-24: zehn Aktien bis Mittwoch 2026-09-30. Ab 2026-10-01 handelt der Runner wieder\n"
|
|
"AAPL mit der Registerversion (Rueckweg aus Paket M). Automatisch durch die geplante Aufgabe\n"
|
|
"'Trading Plattform - Rueckweg AAPL' (.vault-tools/trading-plattform-pruefung/rueckweg_aapl.py).\n\n"
|
|
"Co-Authored-By: Claude Opus 5.5 <[email protected]>\n"
|
|
)
|
|
|
|
|
|
def git(projekt: Path, *argumente: str) -> subprocess.CompletedProcess:
|
|
return subprocess.run(
|
|
[GIT, "-C", str(projekt), *argumente], capture_output=True, text=True, encoding="utf-8",
|
|
errors="replace", timeout=120, creationflags=KEIN_FENSTER,
|
|
env={**__import__("os").environ, "GIT_TERMINAL_PROMPT": "0", "GCM_INTERACTIVE": "never"},
|
|
)
|
|
|
|
|
|
def runner_aktiv(vorgabe: str | None) -> bool:
|
|
if vorgabe is not None:
|
|
zustand = vorgabe # Angriffsfaelle: die Ausgabe von systemctl is-active, ohne WSL
|
|
else:
|
|
lauf = subprocess.run(
|
|
[WSL, "-d", "Debian", "-u", "claude", "--", "systemctl", "is-active", "trading-paper.service"],
|
|
capture_output=True, timeout=60, creationflags=KEIN_FENSTER,
|
|
)
|
|
zustand = lauf.stdout.decode("utf-8", errors="replace").strip()
|
|
if zustand in ("active", "activating", "deactivating", "reloading"):
|
|
return True
|
|
if zustand in ("inactive", "failed"):
|
|
return False
|
|
raise RuntimeError(f"Zustand des Runners nicht lesbar: {zustand!r}")
|
|
|
|
|
|
def melde(titel: str, text: str, ohne_wsl: bool) -> str:
|
|
if ohne_wsl:
|
|
return "ohne WSL, nicht gesendet"
|
|
lauf = subprocess.run(
|
|
[WSL, "-d", "Debian", "-u", "claude", "--", WSL_PY, WSL_SKRIPT, "--meldung", titel, text],
|
|
capture_output=True, timeout=120, creationflags=KEIN_FENSTER,
|
|
)
|
|
return lauf.stdout.decode("utf-8", errors="replace").strip()[-200:]
|
|
|
|
|
|
def blaetter(datei: Path) -> dict:
|
|
"""Alle Blattschluessel einer TOML-Datei als Punktpfad mit ihrem Wert."""
|
|
def ablaufen(tabelle: dict, pfad: str):
|
|
for name, wert in tabelle.items():
|
|
voll = f"{pfad}.{name}" if pfad else name
|
|
if isinstance(wert, dict):
|
|
yield from ablaufen(wert, voll)
|
|
else:
|
|
yield voll, wert
|
|
with datei.open("rb") as f:
|
|
return dict(ablaufen(tomllib.load(f), ""))
|
|
|
|
|
|
def fehlende_schluessel(ziel: Path, quelle: Path) -> list[str]:
|
|
"""Was der Tausch ausser den erwarteten Unterschieden aendern wuerde.
|
|
|
|
Projektvorgabe (Paket M, Rueckweg): Die beiden Dateien unterscheiden sich nur in [agent],
|
|
umgebung.instrumente (zehn Aktien) gegen umgebung.instrument (AAPL) und im Wert von
|
|
grenzen.max_tagesverlust_anteil (1 % gegen 0,2 %); verglichen am 2026-09-24 im Arbeitsbaum M.
|
|
Jeder andere Schluessel, der nur in config/paper.toml steht, ginge beim Tausch still verloren
|
|
(etwa [auto_fortsetzen] aus Paket AF oder etwas aus Z2), und jeder andere Wert, der sich
|
|
unterscheidet, sprang still um -- etwa auto_fortsetzen.aktiv, das Pascal in config/paper.toml
|
|
abgeschaltet hat (Befund der Plankritik AF, 2026-09-24).
|
|
"""
|
|
alt, neu = blaetter(ziel), blaetter(quelle)
|
|
befunde = []
|
|
for name in sorted(alt.keys() | neu.keys()):
|
|
if name.startswith(ERWARTET_ANDERS):
|
|
continue
|
|
if name not in neu:
|
|
befunde.append(f"{name} fehlt in der Rueckweg-Datei")
|
|
elif name not in alt:
|
|
# Die Gegenrichtung (Nachkritik AF, 2026-09-24): Hat Pascal [auto_fortsetzen] in
|
|
# config/paper.toml geloescht -- laut Task 421 ein gueltiges Abschalten --, brachte der
|
|
# Tausch aktiv = true aus der Rueckweg-Datei still zurueck.
|
|
befunde.append(f"{name} steht nur in der Rueckweg-Datei")
|
|
elif neu[name] != alt[name]:
|
|
befunde.append(f"{name} anderer Wert")
|
|
return befunde
|
|
|
|
|
|
def rueckweg(jetzt: datetime, projekt: Path, ablage: Path, runner_vorgabe: str | None) -> tuple[str, str | None]:
|
|
"""(Ergebnis fuer die Notiz, Befund oder None)."""
|
|
if jetzt < FRUEHESTENS:
|
|
return f"zu frueh ({jetzt:%Y-%m-%d %H:%M}), nichts getan", None
|
|
if (ablage / "rueckweg.json").exists():
|
|
return "schon erledigt", None
|
|
if runner_aktiv(runner_vorgabe):
|
|
return "", f"Der Runner laeuft; config/paper.toml nicht getauscht. {NOCHMAL}"
|
|
if runner_vorgabe is None:
|
|
# Hat erst dieser Aufruf WSL geweckt, startet der Zeitgeber den Runner 60 s danach
|
|
# (OnStartupSec=60) mit der Konfiguration, die er dann liest. Also warten und noch einmal fragen.
|
|
time.sleep(WARTEN_S)
|
|
if runner_aktiv(None):
|
|
return "", f"Der Runner ist eben gestartet; config/paper.toml nicht getauscht. {NOCHMAL}"
|
|
zweig = git(projekt, "rev-parse", "--abbrev-ref", "HEAD").stdout.strip()
|
|
if zweig != ZWEIG:
|
|
return "", f"Im Projektordner ist {zweig!r} ausgecheckt, nicht {ZWEIG}; nicht getauscht"
|
|
ziel, quelle = projekt / ZIEL, projekt / RUECKWEG
|
|
if not quelle.is_file():
|
|
return "", f"{RUECKWEG.as_posix()} fehlt; nicht getauscht"
|
|
status = git(projekt, "status", "--porcelain")
|
|
if status.returncode != 0:
|
|
return "", f"git status scheiterte ({status.returncode}); nicht getauscht"
|
|
geaendert = [z for z in status.stdout.splitlines() if z.strip()]
|
|
if ziel.is_file() and ziel.read_bytes() == quelle.read_bytes():
|
|
if not geaendert:
|
|
return "config/paper.toml ist schon die AAPL-Konfiguration", None
|
|
if geaendert != [f" M {ZIEL.as_posix()}"] and geaendert != [f"M {ZIEL.as_posix()}"]:
|
|
return "", f"Projektordner nicht sauber (git status: {' | '.join(geaendert)[:200]}); nicht getauscht"
|
|
# Halb erledigt: getauscht, aber nicht committet (etwa ein gescheiterter Commit beim ersten Termin).
|
|
else:
|
|
if geaendert:
|
|
return "", f"Projektordner nicht sauber (git status: {' | '.join(geaendert)[:200]}); nicht getauscht"
|
|
if MEHRERE not in ziel.read_text(encoding="utf-8"):
|
|
return "", "config/paper.toml ist nicht die Konfiguration mit mehreren Aktien; nicht getauscht, Pascal entscheidet"
|
|
fehlend = fehlende_schluessel(ziel, quelle)
|
|
if fehlend:
|
|
return "", (
|
|
f"config/paper-aapl-register.toml weicht ausserhalb der erwarteten Stellen von config/paper.toml ab "
|
|
f"({', '.join(fehlend)[:200]}); nicht getauscht, Pascal entscheidet"
|
|
)
|
|
ziel.write_bytes(quelle.read_bytes())
|
|
for schritt in (("add", ZIEL.as_posix()), ("commit", "-m", NACHRICHT)):
|
|
lauf = git(projekt, *schritt)
|
|
if lauf.returncode != 0:
|
|
return "", (
|
|
f"git {schritt[0]} scheiterte ({lauf.returncode}: {(lauf.stderr or lauf.stdout).strip()[:200]}); "
|
|
f"Datei ist getauscht, aber nicht committet. {NOCHMAL}"
|
|
)
|
|
kopf = git(projekt, "rev-parse", "--short", "HEAD").stdout.strip()
|
|
return f"config/paper.toml auf AAPL (Register) zurueckgestellt, Commit {kopf} (nicht gepusht)", None
|
|
|
|
|
|
def main(argv=None) -> int:
|
|
zerleger = argparse.ArgumentParser(allow_abbrev=False)
|
|
zerleger.add_argument("--jetzt")
|
|
zerleger.add_argument("--projekt", default=str(PROJEKT))
|
|
zerleger.add_argument("--vault", default=str(VAULT))
|
|
zerleger.add_argument("--ohne-wsl", action="store_true", help="Angriffsfaelle: keine Meldung, Runner laut --runner-zustand")
|
|
zerleger.add_argument("--runner-zustand", help="Angriffsfaelle: Ausgabe von systemctl is-active statt WSL")
|
|
argumente = zerleger.parse_args(argv)
|
|
if argumente.ohne_wsl and argumente.runner_zustand is None:
|
|
argumente.runner_zustand = "inactive"
|
|
jetzt = datetime.fromisoformat(argumente.jetzt) if argumente.jetzt else datetime.now()
|
|
projekt, vault = Path(argumente.projekt), Path(argumente.vault)
|
|
ablage = projekt / "data" / "paper" / "abendkontrollen"
|
|
ablage.mkdir(parents=True, exist_ok=True)
|
|
try:
|
|
ergebnis, befund = rueckweg(jetzt, projekt, ablage, argumente.runner_zustand)
|
|
except Exception as ausnahme:
|
|
ergebnis, befund = "", f"abgebrochen: {type(ausnahme).__name__}: {str(ausnahme)[:200]}"
|
|
with (ablage / "lauf.log").open("a", encoding="utf-8", newline="\n") as log:
|
|
log.write(traceback.format_exc())
|
|
with (ablage / "lauf.log").open("a", encoding="utf-8", newline="\n") as log:
|
|
log.write(f"{datetime.now().isoformat(timespec='seconds')} rueckweg: {ergebnis or befund}\n")
|
|
if ergebnis in ("schon erledigt",) or ergebnis.startswith("zu frueh"):
|
|
return 0
|
|
kopf = f"- {jetzt:%H:%M} Uhr — Rückweg auf AAPL (automatisch, Pascals Vorgabe: zehn Aktien bis 30.09.)"
|
|
if befund is None:
|
|
(ablage / "rueckweg.json").write_text(
|
|
json.dumps({"zeit": jetzt.isoformat(), "ergebnis": ergebnis}, ensure_ascii=False), encoding="utf-8"
|
|
)
|
|
schreibe_notiz(vault, jetzt.date().isoformat(), f"{kopf}: {ergebnis}.")
|
|
return 0
|
|
gemeldet = melde("Rueckweg AAPL nicht ausgefuehrt", befund, argumente.ohne_wsl)
|
|
schreibe_notiz(vault, jetzt.date().isoformat(), f"{kopf}: **nicht ausgeführt** — {befund} #needs-review (Meldung: {gemeldet})")
|
|
return 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|