Pruefwerkzeuge Trading Plattform: Abendkontrolle vom Docker-Host (Paket DH), abgedichtete Angriffstreiber; plankritik.js auf LF
- abendkontrolle.py: --quelle und --quelle-ibkr (Vorgabe wsl wie bisher, dockerhost liest das Ergebnis des Hosts ueber SSH), fahre_dockerhost - abendkontrolle_wsl.py: --ibkr-python fuer ibkr_pruefen.py im Container - abendkontrolle_einrichten.ps1: -Quelle und -QuelleIbkr, 23:15 sobald ein Teil vom Host kommt - neue Angriffstreiber abendkontrolle_dockerhost_angriffe.py und abendkontrolle_ibkr_python_angriffe.py - Abdichtung gegen echte WSL- und SSH-Wege in den Treibern, auch in abendkontrolle_angriffe.py und rueckweg_aapl_angriffe.py (nach dem ntfy-Vorfall eines Mutanten am 2026-09-26) - LIESMICH: Abschnitt Paket DH - .claude/workflows/plankritik.js: LF statt CRLF (das CR war die Ursache von "script contains control characters"), meta gekuerzt, Einzelheiten im Kommentar Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
c115a13190
commit
59087fb235
9 files changed
+588
-16
No files matched your search
@@ -1,8 +1,9 @@
|
||||
"""Startet die Abendkontrolle des Paper-Betriebs und schreibt ihr Ergebnis in die Tagesnotiz.
|
||||
|
||||
Koordinator-Werkzeug, nicht fuers Repo. Laeuft unter Windows mit pythonw.exe als geplante Aufgabe
|
||||
"Trading Plattform - Abendkontrolle" (abendkontrolle_einrichten.ps1): werktags 22:45 und fuenf Minuten
|
||||
nach jeder Anmeldung. Auftrag Pascal 2026-09-24: dauerhaft, uebersteht Neustarts, blockiert keine
|
||||
"Trading Plattform - Abendkontrolle" (abendkontrolle_einrichten.ps1): werktags 22:45 (kommt ein Teil
|
||||
vom Docker-Host, 23:15; seit Paket DH) und fuenf Minuten nach jeder Anmeldung. Auftrag Pascal
|
||||
2026-09-24: dauerhaft, uebersteht Neustarts, blockiert keine
|
||||
Updates -- deshalb eine Windows-Aufgabe (bleibt ueber Neustarts, holt verpasste Laeufe nach) und kein
|
||||
Claude-Prozess (haelt keine Datei von Claude Code oder Desktop offen).
|
||||
|
||||
@@ -29,6 +30,7 @@ Augenblick schreibt, schuetzt das nicht (Zeitfenster Lesen bis Ersetzen, Millise
|
||||
Annahme, benannt: Die Uhr dieses Rechners geht nach Berliner Zeit (tzutil: W. Europe Standard Time,
|
||||
gelesen am 2026-09-24); ohne das Paket tzdata kennt Python unter Windows keine Zeitzonen-Datenbank.
|
||||
Aufruf von Hand: python abendkontrolle.py [--tag JJJJ-MM-TT] [--vault D:\\Claude] [--ohne-wsl <json-datei>]
|
||||
[--quelle wsl|dockerhost] [--quelle-ibkr wsl|dockerhost]
|
||||
"""
|
||||
|
||||
import argparse
|
||||
@@ -55,6 +57,20 @@ 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 DH (Trading Plattform 0.34.0): Nach dem Umzug auf den Docker-Host prueft der Host
|
||||
# selbst (abendkontrolle-host.sh, werktags 16:30 New Yorker Zeit, beide Teile); diese Aufgabe
|
||||
# liest nur noch sein Ergebnis ueber SSH. Der Schluessel darf dort genau einen Befehl
|
||||
# (abendkontrolle-lesen.sh mit JJJJ-MM-TT oder JJJJ-MM-TT-ibkr), der Host ist ueber seinen
|
||||
# Fingerabdruck festgelegt. Umgeschaltet wird je Teil: --quelle fuer alpaca, --quelle-ibkr fuer
|
||||
# ibkr (ohne Angabe wie --quelle), weil die IBKR-Instanz an einem eigenen Abend umzieht.
|
||||
QUELLEN = ("wsl", "dockerhost")
|
||||
SSH = r"C:\Windows\System32\OpenSSH\ssh.exe"
|
||||
SSH_ZIEL = "[email protected]"
|
||||
SSH_PORT = "2498"
|
||||
SSH_SCHLUESSEL = Path.home() / ".ssh" / "trading-dh-kontrolle"
|
||||
# Ohne Leerzeichen im Wert: ssh trennt UserKnownHostsFile an Leerraum; ~ setzt ssh selbst ein.
|
||||
SSH_BEKANNTE = "~/.ssh/known_hosts_trading_dh"
|
||||
SSH_TIMEOUT_S = 120
|
||||
# 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).
|
||||
@@ -97,6 +113,48 @@ def fahre_wsl(tag: str, teil: str = "alpaca") -> dict:
|
||||
raise RuntimeError(f"keine JSON-Zeile vom WSL-Teil (Exit {lauf.returncode}; {' | '.join(fehler)[:300]})")
|
||||
|
||||
|
||||
def fahre_dockerhost(tag: str, teil: str = "alpaca") -> dict:
|
||||
"""Liest das Ergebnis, das die Abendkontrolle auf dem Docker-Host fuer <tag> und <teil> abgelegt hat.
|
||||
|
||||
Der Host legt je Tag <tag>.json (alpaca) und <tag>-ibkr.json (ibkr) ab; angefragt wird mit
|
||||
genau dem Namen ohne .json. Exit 3 von abendkontrolle-lesen.sh heisst: Dafuer liegt dort noch
|
||||
nichts. Das ist ein Fehlschlag wie jeder andere -- die Notiz bekommt #needs-review, und der
|
||||
naechste Lauf versucht es erneut. BatchMode: nie eine Rueckfrage. StrictHostKeyChecking=yes:
|
||||
nur der bekannte Host.
|
||||
"""
|
||||
ziel = tag + (IBKR_ENDUNG if teil == "ibkr" else "")
|
||||
lauf = subprocess.run(
|
||||
[
|
||||
SSH,
|
||||
"-p", SSH_PORT,
|
||||
"-i", str(SSH_SCHLUESSEL),
|
||||
"-o", "BatchMode=yes",
|
||||
"-o", "IdentitiesOnly=yes",
|
||||
"-o", "StrictHostKeyChecking=yes",
|
||||
"-o", f"UserKnownHostsFile={SSH_BEKANNTE}",
|
||||
"-o", "ConnectTimeout=20",
|
||||
SSH_ZIEL,
|
||||
ziel,
|
||||
],
|
||||
capture_output=True, timeout=SSH_TIMEOUT_S, creationflags=KEIN_FENSTER,
|
||||
)
|
||||
if lauf.returncode == 3:
|
||||
raise RuntimeError(
|
||||
f"auf dem Docker-Host liegt fuer {ziel} noch kein Ergebnis "
|
||||
"(dort laeuft die Abendkontrolle werktags 16:30 New Yorker Zeit)"
|
||||
)
|
||||
ausgabe = lauf.stdout.decode("utf-8", errors="replace")
|
||||
for zeile in reversed(ausgabe.splitlines()):
|
||||
zeile = zeile.strip()
|
||||
if zeile.startswith("{"):
|
||||
try:
|
||||
return json.loads(zeile)
|
||||
except json.JSONDecodeError:
|
||||
break
|
||||
fehler = lauf.stderr.decode("utf-8", errors="replace").strip().splitlines()[-3:]
|
||||
raise RuntimeError(f"keine JSON-Zeile vom Docker-Host (Exit {lauf.returncode}; {' | '.join(fehler)[:300]})")
|
||||
|
||||
|
||||
def zeile_fuer(ergebnis: dict, uhrzeit: str) -> str:
|
||||
kopf = f"- {uhrzeit} Uhr — Abendkontrolle Trading Plattform (automatisch)"
|
||||
befunde = ergebnis.get("befunde") or []
|
||||
@@ -218,6 +276,14 @@ def main(argv=None) -> int:
|
||||
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")
|
||||
zerleger.add_argument(
|
||||
"--quelle", choices=QUELLEN, default="wsl",
|
||||
help="wsl (Vorgabe): pruefen in WSL; dockerhost: das Ergebnis des Docker-Hosts ueber SSH lesen",
|
||||
)
|
||||
zerleger.add_argument(
|
||||
"--quelle-ibkr", choices=QUELLEN,
|
||||
help="die Quelle fuer den Teil ibkr; ohne Angabe dieselbe wie --quelle",
|
||||
)
|
||||
argumente = zerleger.parse_args(argv)
|
||||
vault = Path(argumente.vault)
|
||||
ablage = vault / ABLAGE_TEIL
|
||||
@@ -238,7 +304,9 @@ def main(argv=None) -> int:
|
||||
# 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))
|
||||
# Seit Paket DH: die Quelle je Teil; die IBKR-Instanz zieht an einem eigenen Abend um.
|
||||
quelle = (argumente.quelle_ibkr or argumente.quelle) if teil == "ibkr" else argumente.quelle
|
||||
code = max(code, fahre_teil(teil, tage, jetzt, vault, ablage, argumente.ohne_wsl, quelle))
|
||||
except Exception as ausnahme:
|
||||
code = 1
|
||||
try:
|
||||
@@ -248,8 +316,12 @@ def main(argv=None) -> int:
|
||||
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."""
|
||||
def fahre_teil(teil: str, tage: list[str], jetzt: datetime, vault: Path, ablage: Path, ohne_wsl: str | None,
|
||||
quelle: str = "wsl") -> int:
|
||||
"""Ein Teil fuer alle faelligen Tage: Ergebnisdatei, Zeile in der Notiz, lauf.log.
|
||||
|
||||
quelle (seit Paket DH): wsl prueft in WSL, dockerhost liest das Ergebnis des Docker-Hosts.
|
||||
"""
|
||||
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} "
|
||||
@@ -259,6 +331,8 @@ def fahre_teil(teil: str, tage: list[str], jetzt: datetime, vault: Path, ablage:
|
||||
try:
|
||||
if ohne_wsl:
|
||||
ergebnis = json.loads(Path(ohne_wsl).read_text(encoding="utf-8"))
|
||||
elif quelle == "dockerhost":
|
||||
ergebnis = fahre_dockerhost(tag, teil)
|
||||
else:
|
||||
ergebnis = fahre_wsl(tag, teil)
|
||||
if not isinstance(ergebnis, dict) or not isinstance(ergebnis.get("befunde"), list):
|
||||
|
||||
Reference in new issue
Block a user