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,7 +1,7 @@
|
|||||||
export const meta = {
|
export const meta = {
|
||||||
name: 'plankritik',
|
name: 'plankritik',
|
||||||
description: 'Liest einen Planordner mit getrennten pruefer-Linsen, gleichzeitig nach Verbrauch bis vier, fasst doppelte Befunde zusammen und lässt jeden wichtigen oder blockierenden Befund von einem zweiten pruefer zu widerlegen versuchen, höchstens zwei gleichzeitig',
|
description: 'Plankritik: pruefer-Linsen nach Verbrauch, doppelte Befunde zusammengefasst, wichtige und blockierende von einem zweiten pruefer widerlegt',
|
||||||
whenToUse: 'Schritt 2 der Standardschleife vor dem ersten Executer, Pflicht ab etwa drei Tasks (AI/Regelwerk/codeworkflow.md, "Plankritik vor dem ersten Executer"). Args: {projekt, planordner, spec?, kopie?, materialisieren?: {ohne: [relpfade], tasks?: [taskdateien]}, suite?, linsen?, plaetze?: 1..4, verifizieren?: true, verifizieren_ab?: "wichtig"|"blockierend", effort?, werkzeuge?: Ordner der Planwerkzeuge (Pfad, keine Ausgabe; Vorgabe D:/Claude/.vault-tools/plan-kopie), zusatz?}. Ohne kopie baut materialisieren sie selbst (kopie_bauen.py plus plan_materialisiere.py); ohne() muss Geheimnisdateien und große Datenordner nennen. plaetze begrenzt die gleichzeitigen Linsen; verbrauch.js senkt sie nach dem 5-Stunden-Fenster und der Woche. Rückgabe mit status fertig oder gebremst; ein gebremster Lauf wird neu gestartet, nicht fortgesetzt.',
|
whenToUse: 'Schritt 2 der Standardschleife vor dem ersten Executer, Pflicht ab etwa drei Tasks (AI/Regelwerk/codeworkflow.md). Args: {projekt, planordner, spec?, kopie?, materialisieren?: {ohne: [relpfade], tasks?: [taskdateien]}, suite?, linsen?, plaetze?: 1..4, verifizieren?: true, verifizieren_ab?: "wichtig"|"blockierend", effort?, werkzeuge?: Ordner der Planwerkzeuge, zusatz?}. Einzelheiten im Kommentar unter meta.',
|
||||||
phases: [
|
phases: [
|
||||||
{ title: 'Vorbereitung', detail: 'Hilfsagent: Kopie bauen, Plan anwenden, Planwerkzeuge fahren' },
|
{ title: 'Vorbereitung', detail: 'Hilfsagent: Kopie bauen, Plan anwenden, Planwerkzeuge fahren' },
|
||||||
{ title: 'Plankritik', detail: 'je Linse ein pruefer, gleichzeitig nach Verbrauch' },
|
{ title: 'Plankritik', detail: 'je Linse ein pruefer, gleichzeitig nach Verbrauch' },
|
||||||
@@ -9,6 +9,19 @@ export const meta = {
|
|||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Diese Datei traegt LF. Mit CRLF im Arbeitsbaum (seit einer Nachbesserung am 2026-09-24) lehnte
|
||||||
|
// die Rechtepruefung sie per Name wie per scriptPath mit "script contains control characters" ab:
|
||||||
|
// Das CR ist das Steuerzeichen. Belegt am 2026-09-26 (Erinnerung
|
||||||
|
// gespeicherter-workflow-per-name-scheitert). Die Einzelheiten zu den Args stehen hier, damit meta
|
||||||
|
// kurz bleibt:
|
||||||
|
// - werkzeuge ist ein Pfad, keine Ausgabe; Vorgabe D:/Claude/.vault-tools/plan-kopie.
|
||||||
|
// - Ohne kopie baut materialisieren sie selbst (kopie_bauen.py plus plan_materialisiere.py);
|
||||||
|
// ohne() muss Geheimnisdateien und große Datenordner nennen.
|
||||||
|
// - plaetze begrenzt die gleichzeitigen Linsen; verbrauch.js senkt sie nach dem 5-Stunden-Fenster
|
||||||
|
// und der Woche.
|
||||||
|
// - Rückgabe mit status fertig oder gebremst; ein gebremster Lauf wird neu gestartet, nicht
|
||||||
|
// fortgesetzt.
|
||||||
|
//
|
||||||
// Rollenparameter: model wird immer gesetzt (Stamm). Der Effort der pruefer kommt aus dem
|
// Rollenparameter: model wird immer gesetzt (Stamm). Der Effort der pruefer kommt aus dem
|
||||||
// Frontmatter von .claude/agents/pruefer.md; args.effort überschreibt ihn nur bewusst.
|
// Frontmatter von .claude/agents/pruefer.md; args.effort überschreibt ihn nur bewusst.
|
||||||
// Der Hilfsagent hat keinen agentType und bekommt deshalb seinen Effort mit (low: gemessen
|
// Der Hilfsagent hat keinen agentType und bekommt deshalb seinen Effort mit (low: gemessen
|
||||||
|
|||||||
@@ -166,3 +166,16 @@ Seit Paket IB2 prüft die Abendkontrolle zwei Teile je Tag, nacheinander: den Al
|
|||||||
| `abendkontrolle_mutanten.py` | dazu Mutanten der IBKR-Teile beider Dateien, auch für Schlussposition, Titelpräfix, Meldeweiche und die Prüfung des Teils; die Anker „keine Ergebnisdatei“ und „Wochenende mit“ folgen dem Code aus IB2 | wie oben |
|
| `abendkontrolle_mutanten.py` | dazu Mutanten der IBKR-Teile beider Dateien, auch für Schlussposition, Titelpräfix, Meldeweiche und die Prüfung des Teils; die Anker „keine Ergebnisdatei“ und „Wochenende mit“ folgen dem Code aus IB2 | wie oben |
|
||||||
|
|
||||||
Die Zahlen der Fälle und Mutanten im Abschnitt vom 2026-09-24 gelten für den Stand vor IB2; wie viele es jetzt sind, sagt die Zeile `ERGEBNIS` des Laufs. Was der IBKR-Teil nicht sieht: einen Tagesbericht (die Instanz hat keine Tagesauswertung), `handelstag_auswertung.py` und den Dienst `ibkr-gateway.service`. Holt die Kontrolle einen Tag während der New Yorker Handelszeit nach (Anmeldung am Nachmittag), liest sie das Konto nicht; der nächste Abendlauf liest es für seinen eigenen Tag. Den Zeitgeber liest sie zum Zeitpunkt der Kontrolle, nicht für den geprüften Tag: Beim Nachholen nach einem Umschalten kann sie einen Ausfall als „nicht gestartet“ werten oder einen No-Go-Tag als Befund.
|
Die Zahlen der Fälle und Mutanten im Abschnitt vom 2026-09-24 gelten für den Stand vor IB2; wie viele es jetzt sind, sagt die Zeile `ERGEBNIS` des Laufs. Was der IBKR-Teil nicht sieht: einen Tagesbericht (die Instanz hat keine Tagesauswertung), `handelstag_auswertung.py` und den Dienst `ibkr-gateway.service`. Holt die Kontrolle einen Tag während der New Yorker Handelszeit nach (Anmeldung am Nachmittag), liest sie das Konto nicht; der nächste Abendlauf liest es für seinen eigenen Tag. Den Zeitgeber liest sie zum Zeitpunkt der Kontrolle, nicht für den geprüften Tag: Beim Nachholen nach einem Umschalten kann sie einen Ausfall als „nicht gestartet“ werten oder einen No-Go-Tag als Befund.
|
||||||
|
|
||||||
|
## Paket DH (Trading Plattform 0.34.0): Abendkontrolle vom Docker-Host
|
||||||
|
|
||||||
|
Nach dem Umzug auf den Docker-Host (Hyper-V-VM) prüft der Host selbst: `deploy/docker-host/abendkontrolle-host.sh` aus dem Repo startet werktags 16:30 New Yorker Zeit `abendkontrolle_wsl.py` im Abbild der Plattform, mit `--journal` und `--dienste` statt `journalctl` und `systemctl` im Container — erst den Teil alpaca im Projekt `trading-alpaca`, dann den Teil ibkr im Projekt `trading-ibkr`, dort im Netz des Gateways und mit `--ibkr-python python`, weil es das Venv aus WSL im Abbild nicht gibt. Dafür liegen `abendkontrolle_wsl.py` und `handelstag_auswertung.py` als Kopie unter `/opt/trading-plattform/werkzeuge/` auf dem Host (Umzugs-Drehbuch, 4.4); wer eines davon hier ändert, kopiert es dort neu. Die Ergebnisse liegen je Tag unter `/var/lib/trading-abendkontrolle/<tag>.json` und `<tag>-ibkr.json`; scharf ist jeder Teil erst ab dem Datum in `erster-tag` beziehungsweise `erster-tag-ibkr` dort.
|
||||||
|
|
||||||
|
| Datei | Was sie tut | Aufruf |
|
||||||
|
|---|---|---|
|
||||||
|
| `abendkontrolle.py` | seit Paket DH mit `--quelle wsl` (Vorgabe, wie bisher) oder `--quelle dockerhost` für den Teil alpaca und `--quelle-ibkr` für den Teil ibkr (ohne Angabe wie `--quelle`). `dockerhost` liest das Ergebnis des Hosts über `ssh.exe` als Benutzer `kontrolle` mit dem Schlüssel `~/.ssh/trading-dh-kontrolle`, `BatchMode` und `StrictHostKeyChecking=yes` gegen `~/.ssh/known_hosts_trading_dh`, angefragt mit `JJJJ-MM-TT` oder `JJJJ-MM-TT-ibkr`. Auf dem Host darf der Schlüssel nur `abendkontrolle-lesen.sh`. Exit 3 dort heißt „noch kein Ergebnis“: Notiz mit `#needs-review`, der nächste Lauf versucht es erneut | `python abendkontrolle.py --quelle dockerhost [--quelle-ibkr wsl] [--tag JJJJ-MM-TT]` |
|
||||||
|
| `abendkontrolle_einrichten.ps1` | seit Paket DH mit `-Quelle` und `-QuelleIbkr` (je `wsl` oder `dockerhost`); kommt ein Teil vom Host, liegt der Abendlauf auf 23:15. Die letzte Zeile nennt beide Quellen | `powershell -NoProfile -File abendkontrolle_einrichten.ps1 -Quelle dockerhost -QuelleIbkr wsl` |
|
||||||
|
| `abendkontrolle_dockerhost_angriffe.py` | zwölf Fälle ohne Netz, abgedichtet: `wsl.exe` zeigt ins Leere, und `fahre_wsl` lehnt ohne eigene Attrappe des Falls ab, ein Aufruf des echten Wegs ist eine Abweichung (so auch der Windows-Teil von `abendkontrolle_angriffe.py`). Die Fälle: beide Ergebnisse landen in Ablage und Notiz, Argumente von ssh je Teil, Exit 3 als Befund, Müll als Befund, fällige Tage einzeln je Teil, ohne Schalter bleibt es WSL, `--quelle-ibkr wsl` hält die Instanz in WSL, ein Ergebnis des falschen Teils vom Host, unbekannte Quelle, Frist und Aufgabe passen zueinander. Fängt je Fall auch `SystemExit` | `python abendkontrolle_dockerhost_angriffe.py` unter Windows |
|
||||||
|
| `abendkontrolle_ibkr_python_angriffe.py` | seit der Prüfung vom 2026-09-26 mit zweiter Sperre: `IBKR_ORDNER` zeigt ins Leere; dazu dichtet `rueckweg_aapl_angriffe.py` seither `WSL` und `WSL_SKRIPT` im Modul `rueckweg_aapl` ab, und `abendkontrolle_angriffe.py` räumt seinen leeren Ordner im Temp wieder weg. Sonst: vier Fälle ohne Netz für `--ibkr-python` von `abendkontrolle_wsl.py`: Schalter, Vorgabe `IBKR_PY`, `ibkr_pruefen.py lesen` läuft mit dem genannten Interpreter (Ersatzskript im Wegwerf-Ordner), der Ablauf reicht den Schalter weiter | `python abendkontrolle_ibkr_python_angriffe.py` in WSL im Venv |
|
||||||
|
|
||||||
|
Was es nicht tut: Es prüft nicht, ob der Host die Kontrolle überhaupt fährt — bleibt sie dort aus, steht jeden Abend „noch kein Ergebnis“ in der Notiz, und das ist der Hinweis. Steht `-QuelleIbkr` falsch, fällt das nicht immer auf: Prüft die Aufgabe den Teil ibkr in WSL, obwohl die Instanz schon auf dem Host läuft, meldet sie ihn dort nur leise als „nicht gestartet“, weil der Zeitgeber in WSL aus ist. Ein Anmelde-Lauf zwischen 22:30 und dem Ende der Prüfung auf dem Host bekommt einmal „noch kein Ergebnis“, weil die Frist `FRUEHESTENS` für beide Quellen gilt (die Mutanten suchen genau diese Zeile).
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
"""Startet die Abendkontrolle des Paper-Betriebs und schreibt ihr Ergebnis in die Tagesnotiz.
|
"""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
|
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
|
"Trading Plattform - Abendkontrolle" (abendkontrolle_einrichten.ps1): werktags 22:45 (kommt ein Teil
|
||||||
nach jeder Anmeldung. Auftrag Pascal 2026-09-24: dauerhaft, uebersteht Neustarts, blockiert keine
|
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
|
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).
|
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,
|
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.
|
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>]
|
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
|
import argparse
|
||||||
@@ -55,6 +57,20 @@ FRUEHESTENS = time(22, 30)
|
|||||||
WSL_TIMEOUT_S = 1200
|
WSL_TIMEOUT_S = 1200
|
||||||
KEIN_FENSTER = 0x08000000 # CREATE_NO_WINDOW: pythonw hat keine Konsole, wsl.exe soll keine oeffnen
|
KEIN_FENSTER = 0x08000000 # CREATE_NO_WINDOW: pythonw hat keine Konsole, wsl.exe soll keine oeffnen
|
||||||
UEBERSCHRIFT = "## Agentenprotokoll"
|
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.
|
# Seit Paket IB2: die IBKR-Instanz als zweiter Teil mit eigener Ergebnisdatei und eigener Zeile.
|
||||||
TEILE = ("alpaca", "ibkr")
|
TEILE = ("alpaca", "ibkr")
|
||||||
# Der erste Tag, den der Teil ibkr prueft; gesetzt beim Einspielen von IB2 (Planuebersicht IB2).
|
# 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]})")
|
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:
|
def zeile_fuer(ergebnis: dict, uhrzeit: str) -> str:
|
||||||
kopf = f"- {uhrzeit} Uhr — Abendkontrolle Trading Plattform (automatisch)"
|
kopf = f"- {uhrzeit} Uhr — Abendkontrolle Trading Plattform (automatisch)"
|
||||||
befunde = ergebnis.get("befunde") or []
|
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("--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("--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("--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)
|
argumente = zerleger.parse_args(argv)
|
||||||
vault = Path(argumente.vault)
|
vault = Path(argumente.vault)
|
||||||
ablage = vault / ABLAGE_TEIL
|
ablage = vault / ABLAGE_TEIL
|
||||||
@@ -238,7 +304,9 @@ def main(argv=None) -> int:
|
|||||||
# schreibbar, lauf.log gesperrt), laeuft der naechste trotzdem (Pruefung IB2).
|
# schreibbar, lauf.log gesperrt), laeuft der naechste trotzdem (Pruefung IB2).
|
||||||
try:
|
try:
|
||||||
logge(ablage, f"{vorsatz}start, faellig: {tage}")
|
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:
|
except Exception as ausnahme:
|
||||||
code = 1
|
code = 1
|
||||||
try:
|
try:
|
||||||
@@ -248,8 +316,12 @@ def main(argv=None) -> int:
|
|||||||
return code
|
return code
|
||||||
|
|
||||||
|
|
||||||
def fahre_teil(teil: str, tage: list[str], jetzt: datetime, vault: Path, ablage: Path, ohne_wsl: str | None) -> int:
|
def fahre_teil(teil: str, tage: list[str], jetzt: datetime, vault: Path, ablage: Path, ohne_wsl: str | None,
|
||||||
"""Ein Teil fuer alle faelligen Tage: Ergebnisdatei, Zeile in der Notiz, lauf.log."""
|
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 ""
|
endung = IBKR_ENDUNG if teil == "ibkr" else ""
|
||||||
zeile_von = zeile_fuer_ibkr if teil == "ibkr" else zeile_fuer
|
zeile_von = zeile_fuer_ibkr if teil == "ibkr" else zeile_fuer
|
||||||
vorsatz = "" if teil == "alpaca" else f"{teil} "
|
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:
|
try:
|
||||||
if ohne_wsl:
|
if ohne_wsl:
|
||||||
ergebnis = json.loads(Path(ohne_wsl).read_text(encoding="utf-8"))
|
ergebnis = json.loads(Path(ohne_wsl).read_text(encoding="utf-8"))
|
||||||
|
elif quelle == "dockerhost":
|
||||||
|
ergebnis = fahre_dockerhost(tag, teil)
|
||||||
else:
|
else:
|
||||||
ergebnis = fahre_wsl(tag, teil)
|
ergebnis = fahre_wsl(tag, teil)
|
||||||
if not isinstance(ergebnis, dict) or not isinstance(ergebnis.get("befunde"), list):
|
if not isinstance(ergebnis, dict) or not isinstance(ergebnis.get("befunde"), list):
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ Aufruf in WSL (Venv) und unter Windows: python abendkontrolle_angriffe.py
|
|||||||
Exit 0, wenn jeder Fall so ausgeht wie erwartet; sonst 1 mit der Liste der Abweichungen.
|
Exit 0, wenn jeder Fall so ausgeht wie erwartet; sonst 1 mit der Liste der Abweichungen.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import atexit
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
@@ -291,11 +292,66 @@ def wsl_faelle() -> None:
|
|||||||
|
|
||||||
# --- Windows-Teil --------------------------------------------------------------------------------
|
# --- Windows-Teil --------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def abdichten(ak) -> list:
|
||||||
|
"""Sperrt die echten Wege von abendkontrolle.py fuer die Windows-Faelle (Plankritik DH, 2026-09-26).
|
||||||
|
|
||||||
|
Die Faelle laufen mit --ohne-wsl; bricht ein Mutant diesen Schalter, liefe sonst der echte
|
||||||
|
WSL-Teil gegen den echten Projektordner, und seit Paket DH ssh zum Docker-Host. wsl.exe und
|
||||||
|
ssh.exe zeigen ins Leere, fahre_wsl und fahre_dockerhost schreiben jeden Aufruf mit und
|
||||||
|
lehnen ab; jeder Aufruf ist eine Abweichung.
|
||||||
|
"""
|
||||||
|
gerufen = []
|
||||||
|
abdichten.erwartet = False
|
||||||
|
|
||||||
|
def verboten(name):
|
||||||
|
def weg(*argumente, **benannte):
|
||||||
|
gerufen.append(name)
|
||||||
|
zeile = f"FEHL echter Weg {name} gerufen: {argumente}"
|
||||||
|
abweichungen.append(zeile)
|
||||||
|
if not abdichten.erwartet:
|
||||||
|
print(zeile)
|
||||||
|
raise RuntimeError(f"echter Weg {name} in den Angriffsfaellen")
|
||||||
|
|
||||||
|
weg.sperre = True
|
||||||
|
return weg
|
||||||
|
|
||||||
|
leer = Path(tempfile.mkdtemp(prefix="abendkontrolle-angriffe-kein-weg-"))
|
||||||
|
atexit.register(shutil.rmtree, leer, True) # der leere Ordner bleibt nicht im Temp liegen
|
||||||
|
ak.WSL = str(leer / "wsl.exe")
|
||||||
|
ak.SSH = str(leer / "ssh.exe")
|
||||||
|
ak.WSL_SKRIPT = "/nicht-vorhanden/abendkontrolle_wsl.py"
|
||||||
|
ak.fahre_wsl = verboten("fahre_wsl")
|
||||||
|
ak.fahre_dockerhost = verboten("fahre_dockerhost")
|
||||||
|
return gerufen
|
||||||
|
|
||||||
|
|
||||||
def windows_faelle() -> None:
|
def windows_faelle() -> None:
|
||||||
sys.path.insert(0, str(HIER))
|
sys.path.insert(0, str(HIER))
|
||||||
import abendkontrolle as ak
|
import abendkontrolle as ak
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
gerufen = abdichten(ak)
|
||||||
|
# Die Abdichtung greift: Ohne --ohne-wsl nimmt main den WSL-Weg, und der muss als Abweichung
|
||||||
|
# ankommen, nicht als Lauf. Die erwartete Abweichung wird danach wieder entfernt.
|
||||||
|
vorher = len(abweichungen)
|
||||||
|
abdichten.erwartet = True
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
(Path(tmp) / "Inbox").mkdir()
|
||||||
|
ak.main(["--vault", tmp, "--tag", TAG, "--teil", "alpaca", "--jetzt", "2026-09-23T22:45:00"])
|
||||||
|
abdichten.erwartet = False
|
||||||
|
# Beleg fuer die uebrigen Zeilen von abdichten: beide Programme und das Skript ins Leere,
|
||||||
|
# fahre_dockerhost gesperrt.
|
||||||
|
gesperrt = (not Path(ak.WSL).exists() and not Path(ak.SSH).exists()
|
||||||
|
and ak.WSL_SKRIPT.startswith("/nicht-vorhanden/")
|
||||||
|
and getattr(ak.fahre_dockerhost, "sperre", False))
|
||||||
|
ok = gesperrt and gerufen == ["fahre_wsl"] and len(abweichungen) == vorher + 1
|
||||||
|
del abweichungen[vorher:]
|
||||||
|
del gerufen[:]
|
||||||
|
zeile = f"{'OK ' if ok else 'FEHL'} W Abdichtung: der echte WSL-Weg kommt als Abweichung an"
|
||||||
|
print(zeile)
|
||||||
|
if not ok:
|
||||||
|
abweichungen.append(zeile)
|
||||||
|
|
||||||
gut = {"tag": TAG, "befunde": [], "hinweise": ["auto_fortsetzen: fortgesetzt"], "exit": 0,
|
gut = {"tag": TAG, "befunde": [], "hinweise": ["auto_fortsetzen: fortgesetzt"], "exit": 0,
|
||||||
"fakten": {"handelstag": True, "ende": {"grund": "boersenschluss", "exit": "0", "orders": "79", "position": "0"},
|
"fakten": {"handelstag": True, "ende": {"grund": "boersenschluss", "exit": "0", "orders": "79", "position": "0"},
|
||||||
"laeufe": [{"takte_aktiv": 375}], "broker_offen": 0, "bericht": True,
|
"laeufe": [{"takte_aktiv": 375}], "broker_offen": 0, "bericht": True,
|
||||||
@@ -637,6 +693,8 @@ def ibkr_windows_faelle() -> None:
|
|||||||
import abendkontrolle as ak
|
import abendkontrolle as ak
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
abdichten(ak)
|
||||||
|
|
||||||
gut = {"tag": TAG, "teil": "ibkr", "befunde": [], "hinweise": [], "exit": 0,
|
gut = {"tag": TAG, "teil": "ibkr", "befunde": [], "hinweise": [], "exit": 0,
|
||||||
"fakten": {"handelstag": True,
|
"fakten": {"handelstag": True,
|
||||||
"ende": {"grund": "boersenschluss", "exit": "0", "orders": "40", "position": "0"},
|
"ende": {"grund": "boersenschluss", "exit": "0", "orders": "40", "position": "0"},
|
||||||
|
|||||||
@@ -0,0 +1,291 @@
|
|||||||
|
"""Angriffsfaelle fuer die Quellen wsl und dockerhost von abendkontrolle.py (Paket DH), unter Windows, ohne Netz.
|
||||||
|
|
||||||
|
Koordinator-Werkzeug, nicht fuers Repo. Ersetzt ssh.exe durch eine .cmd-Attrappe, die ein kleines
|
||||||
|
Python-Skript startet; der Vault ist ein Wegwerf-Ordner. Die Attrappe schreibt jeden Aufruf mit
|
||||||
|
und antwortet je nach ANGRIFF_ART mit einem Ergebnis des angefragten Teils (JJJJ-MM-TT oder
|
||||||
|
JJJJ-MM-TT-ibkr), mit Exit 3 (noch nichts da), mit Muell oder mit einem Ergebnis des falschen
|
||||||
|
Teils. Der Treiber faengt je Fall auch SystemExit: Gegen ein abendkontrolle.py ohne --quelle
|
||||||
|
bricht argparse damit ab, und das soll ein Fall sein, nicht das Ende des Laufs.
|
||||||
|
Abgedichtet (Plankritik DH, 2026-09-26): WSL zeigt in jedem Lauf ins Leere, und fahre_wsl ist
|
||||||
|
ohne eigene Attrappe des Falls eine Attrappe, die ablehnt; ein Aufruf des echten Wegs ist eine
|
||||||
|
Abweichung, kein Lauf. Zwei Faelle belegen beide Sperren.
|
||||||
|
Aufruf: python abendkontrolle_dockerhost_angriffe.py
|
||||||
|
Exit 0 ohne Abweichung, 2 mit Abweichung.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import contextlib
|
||||||
|
import importlib.util
|
||||||
|
import io
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
HIER = Path(__file__).resolve().parent
|
||||||
|
|
||||||
|
ATTRAPPE_PY = r'''
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
with open(os.environ["ANGRIFF_PROTOKOLL"], "a", encoding="utf-8") as datei:
|
||||||
|
datei.write(json.dumps(sys.argv[1:]) + "\n")
|
||||||
|
art = os.environ.get("ANGRIFF_ART", "gut")
|
||||||
|
if art == "fehlt":
|
||||||
|
sys.stderr.write("abendkontrolle-lesen: fuer diesen Tag liegt noch kein Ergebnis\n")
|
||||||
|
sys.exit(3)
|
||||||
|
if art == "muell":
|
||||||
|
print("kein json")
|
||||||
|
sys.exit(0)
|
||||||
|
ziel = sys.argv[-1]
|
||||||
|
ibkr = ziel.endswith("-ibkr")
|
||||||
|
tag = ziel[:-5] if ibkr else ziel
|
||||||
|
teil = "ibkr" if ibkr and art != "falscher_teil" else "alpaca"
|
||||||
|
print(json.dumps({"tag": tag, "teil": teil, "befunde": [], "hinweise": [], "fakten": {"handelstag": True}, "exit": 0}))
|
||||||
|
'''
|
||||||
|
|
||||||
|
FAELLE = []
|
||||||
|
|
||||||
|
|
||||||
|
def fall(funktion):
|
||||||
|
FAELLE.append(funktion)
|
||||||
|
return funktion
|
||||||
|
|
||||||
|
|
||||||
|
def lade():
|
||||||
|
spec = importlib.util.spec_from_file_location("abendkontrolle_unter_angriff", HIER / "abendkontrolle.py")
|
||||||
|
modul = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(modul)
|
||||||
|
return modul
|
||||||
|
|
||||||
|
|
||||||
|
def lauf(art, *argumente, modul=None):
|
||||||
|
"""Ein Lauf von main() in einem Wegwerf-Vault; liefert Exit, Notiz, Ergebnisdateien und ssh-Aufrufe."""
|
||||||
|
with tempfile.TemporaryDirectory() as ordner:
|
||||||
|
wurzel = Path(ordner)
|
||||||
|
vault = wurzel / "vault"
|
||||||
|
(vault / "Inbox").mkdir(parents=True)
|
||||||
|
protokoll = wurzel / "ssh.jsonl"
|
||||||
|
attrappe = wurzel / "ssh_attrappe.py"
|
||||||
|
attrappe.write_text(ATTRAPPE_PY, encoding="utf-8")
|
||||||
|
cmd = wurzel / "ssh_attrappe.cmd"
|
||||||
|
cmd.write_text(f'@"{sys.executable}" "{attrappe}" %*\r\n', encoding="utf-8")
|
||||||
|
modul = modul or lade()
|
||||||
|
modul.SSH = str(cmd)
|
||||||
|
# Abdichtung (Plankritik DH, 2026-09-26): Der echte WSL-Weg lief sonst gegen den echten
|
||||||
|
# Projektordner, fragte das Gateway und schickte echte ntfy-Meldungen. wsl.exe und das
|
||||||
|
# Skript zeigen ins Leere, und fahre_wsl ist eine Attrappe, die jeden Aufruf samt beiden
|
||||||
|
# Pfaden mitschreibt und ablehnt -- ausser ein Fall hat seine eigene gesetzt (wsl_attrappe).
|
||||||
|
modul.WSL = str(wurzel / "kein-wsl" / "wsl.exe")
|
||||||
|
modul.WSL_SKRIPT = "/nicht-vorhanden/abendkontrolle_wsl.py"
|
||||||
|
unerwartet = []
|
||||||
|
if not getattr(modul.fahre_wsl, "attrappe", False):
|
||||||
|
|
||||||
|
def wsl_verboten(tag, teil="alpaca"):
|
||||||
|
unerwartet.append((tag, teil, modul.WSL, modul.WSL_SKRIPT))
|
||||||
|
raise RuntimeError("echter WSL-Weg ohne Attrappe")
|
||||||
|
|
||||||
|
modul.fahre_wsl = wsl_verboten
|
||||||
|
os.environ["ANGRIFF_PROTOKOLL"] = str(protokoll)
|
||||||
|
os.environ["ANGRIFF_ART"] = art
|
||||||
|
code = modul.main(["--vault", str(vault), *argumente])
|
||||||
|
if unerwartet:
|
||||||
|
raise AssertionError(f"WSL-Weg ohne Attrappe gerufen: {unerwartet}")
|
||||||
|
notizen = sorted((vault / "Inbox").glob("*.md"))
|
||||||
|
notiz = "\n".join(n.read_text(encoding="utf-8") for n in notizen)
|
||||||
|
ablage = vault / modul.ABLAGE_TEIL
|
||||||
|
ergebnisse = sorted(p.name for p in ablage.glob("*.json")) if ablage.exists() else []
|
||||||
|
aufrufe = []
|
||||||
|
if protokoll.exists():
|
||||||
|
aufrufe = [json.loads(z) for z in protokoll.read_text(encoding="utf-8").splitlines() if z.strip()]
|
||||||
|
return code, notiz, ergebnisse, aufrufe
|
||||||
|
|
||||||
|
|
||||||
|
def wsl_attrappe(modul, gerufen):
|
||||||
|
"""Ersetzt fahre_wsl im Modul; schreibt (tag, teil) mit und liefert ein Ergebnis des Teils."""
|
||||||
|
|
||||||
|
def fahre_wsl(tag, teil="alpaca"):
|
||||||
|
gerufen.append((tag, teil))
|
||||||
|
return {"tag": tag, "teil": teil, "befunde": [], "hinweise": [], "fakten": {}, "exit": 0}
|
||||||
|
|
||||||
|
fahre_wsl.attrappe = True
|
||||||
|
modul.fahre_wsl = fahre_wsl
|
||||||
|
|
||||||
|
|
||||||
|
@fall
|
||||||
|
def ergebnis_vom_host_landet_in_ablage_und_notiz():
|
||||||
|
code, notiz, ergebnisse, aufrufe = lauf(
|
||||||
|
"gut", "--quelle", "dockerhost", "--tag", "2026-10-05", "--jetzt", "2026-10-05T23:30"
|
||||||
|
)
|
||||||
|
assert code == 0, code
|
||||||
|
assert ergebnisse == ["2026-10-05-ibkr.json", "2026-10-05.json"], ergebnisse
|
||||||
|
assert "Abendkontrolle Trading Plattform (automatisch): ohne Befund" in notiz, notiz
|
||||||
|
assert "Abendkontrolle IBKR (automatisch): ohne Befund" in notiz, notiz
|
||||||
|
assert len(aufrufe) == 2, aufrufe
|
||||||
|
|
||||||
|
|
||||||
|
@fall
|
||||||
|
def ssh_mit_festem_schluessel_ohne_rueckfrage_nur_zum_bekannten_host():
|
||||||
|
_, _, _, aufrufe = lauf("gut", "--quelle", "dockerhost", "--tag", "2026-10-05", "--jetzt", "2026-10-05T23:30")
|
||||||
|
for argumente, ziel in zip(aufrufe, ("2026-10-05", "2026-10-05-ibkr"), strict=True):
|
||||||
|
assert argumente[:3] == ["-p", "2498", "-i"], argumente
|
||||||
|
assert argumente[3].endswith("trading-dh-kontrolle"), argumente
|
||||||
|
assert argumente[4:14] == [
|
||||||
|
"-o", "BatchMode=yes",
|
||||||
|
"-o", "IdentitiesOnly=yes",
|
||||||
|
"-o", "StrictHostKeyChecking=yes",
|
||||||
|
"-o", "UserKnownHostsFile=~/.ssh/known_hosts_trading_dh",
|
||||||
|
"-o", "ConnectTimeout=20",
|
||||||
|
], argumente
|
||||||
|
assert argumente[14:] == ["[email protected]", ziel], argumente
|
||||||
|
|
||||||
|
|
||||||
|
@fall
|
||||||
|
def noch_kein_ergebnis_ist_ein_befund_in_der_notiz_und_wird_wiederholt():
|
||||||
|
code, notiz, ergebnisse, _ = lauf(
|
||||||
|
"fehlt", "--quelle", "dockerhost", "--tag", "2026-10-05", "--jetzt", "2026-10-05T23:30"
|
||||||
|
)
|
||||||
|
assert code == 1, code
|
||||||
|
assert ergebnisse == [], ergebnisse
|
||||||
|
assert "**gescheitert**" in notiz and "noch kein Ergebnis" in notiz, notiz
|
||||||
|
assert "#needs-review" in notiz, notiz
|
||||||
|
assert "fuer 2026-10-05-ibkr noch kein Ergebnis" in notiz, notiz
|
||||||
|
|
||||||
|
|
||||||
|
@fall
|
||||||
|
def muell_vom_host_ist_ein_befund():
|
||||||
|
code, notiz, ergebnisse, _ = lauf(
|
||||||
|
"muell", "--quelle", "dockerhost", "--tag", "2026-10-05", "--jetzt", "2026-10-05T23:30"
|
||||||
|
)
|
||||||
|
assert code == 1, code
|
||||||
|
assert ergebnisse == [], ergebnisse
|
||||||
|
assert "keine JSON-Zeile vom Docker-Host" in notiz, notiz
|
||||||
|
|
||||||
|
|
||||||
|
@fall
|
||||||
|
def faellige_tage_kommen_einzeln_ueber_ssh():
|
||||||
|
modul = lade()
|
||||||
|
_, _, ergebnisse, aufrufe = lauf("gut", "--quelle", "dockerhost", "--jetzt", "2026-10-05T22:10", modul=modul)
|
||||||
|
tage = ["2026-09-28", "2026-09-29", "2026-09-30", "2026-10-01", "2026-10-02"]
|
||||||
|
ibkr = [f"{t}-ibkr" for t in tage if t >= modul.IBKR_ERSTER_TAG.isoformat()]
|
||||||
|
ziele = [a[-1] for a in aufrufe]
|
||||||
|
assert ziele == tage + ibkr, ziele
|
||||||
|
assert ergebnisse == sorted(f"{z}.json" for z in ziele), ergebnisse
|
||||||
|
|
||||||
|
|
||||||
|
@fall
|
||||||
|
def ohne_schalter_bleibt_es_bei_wsl():
|
||||||
|
modul = lade()
|
||||||
|
gerufen = []
|
||||||
|
wsl_attrappe(modul, gerufen)
|
||||||
|
|
||||||
|
def verboten(*_argumente):
|
||||||
|
raise AssertionError("ohne --quelle darf nichts ueber SSH laufen")
|
||||||
|
|
||||||
|
modul.fahre_dockerhost = verboten
|
||||||
|
code, _, ergebnisse, aufrufe = lauf("gut", "--tag", "2026-10-05", "--jetzt", "2026-10-05T23:30", modul=modul)
|
||||||
|
assert code == 0, code
|
||||||
|
assert gerufen == [("2026-10-05", "alpaca"), ("2026-10-05", "ibkr")], gerufen
|
||||||
|
assert aufrufe == [], aufrufe
|
||||||
|
assert ergebnisse == ["2026-10-05-ibkr.json", "2026-10-05.json"], ergebnisse
|
||||||
|
|
||||||
|
|
||||||
|
@fall
|
||||||
|
def die_ibkr_instanz_bleibt_mit_quelle_ibkr_wsl_in_wsl():
|
||||||
|
modul = lade()
|
||||||
|
gerufen = []
|
||||||
|
wsl_attrappe(modul, gerufen)
|
||||||
|
code, _, ergebnisse, aufrufe = lauf(
|
||||||
|
"gut", "--quelle", "dockerhost", "--quelle-ibkr", "wsl", "--tag", "2026-10-05", "--jetzt", "2026-10-05T23:30",
|
||||||
|
modul=modul,
|
||||||
|
)
|
||||||
|
assert code == 0, code
|
||||||
|
assert [a[-1] for a in aufrufe] == ["2026-10-05"], aufrufe
|
||||||
|
assert gerufen == [("2026-10-05", "ibkr")], gerufen
|
||||||
|
assert ergebnisse == ["2026-10-05-ibkr.json", "2026-10-05.json"], ergebnisse
|
||||||
|
|
||||||
|
|
||||||
|
@fall
|
||||||
|
def ein_ergebnis_des_falschen_teils_vom_host_gilt_als_gescheitert():
|
||||||
|
code, notiz, ergebnisse, aufrufe = lauf(
|
||||||
|
"falscher_teil", "--quelle", "dockerhost", "--teil", "ibkr", "--tag", "2026-10-05", "--jetzt", "2026-10-05T23:30"
|
||||||
|
)
|
||||||
|
assert code == 1, code
|
||||||
|
assert [a[-1] for a in aufrufe] == ["2026-10-05-ibkr"], aufrufe
|
||||||
|
assert ergebnisse == [], ergebnisse
|
||||||
|
assert "gehoert zu 'alpaca'" in notiz and "**gescheitert**" in notiz, notiz
|
||||||
|
|
||||||
|
|
||||||
|
@fall
|
||||||
|
def eine_unbekannte_quelle_ist_ein_bedienfehler():
|
||||||
|
fehler = io.StringIO()
|
||||||
|
try:
|
||||||
|
with contextlib.redirect_stderr(fehler):
|
||||||
|
lauf("gut", "--quelle", "irgendwo", "--tag", "2026-10-05")
|
||||||
|
except SystemExit as ende:
|
||||||
|
assert ende.code == 2, ende.code
|
||||||
|
assert "--quelle: invalid choice" in fehler.getvalue(), fehler.getvalue()
|
||||||
|
return
|
||||||
|
raise AssertionError("argparse hat die Quelle irgendwo angenommen")
|
||||||
|
|
||||||
|
|
||||||
|
@fall
|
||||||
|
def die_heutige_frist_und_die_aufgabe_passen_zueinander():
|
||||||
|
quelle = (HIER / "abendkontrolle.py").read_text(encoding="utf-8")
|
||||||
|
aufgabe = (HIER / "abendkontrolle_einrichten.ps1").read_text(encoding="utf-8")
|
||||||
|
assert "StrictHostKeyChecking=no" not in quelle
|
||||||
|
assert "if tag == jetzt.date() and jetzt.time() < FRUEHESTENS:" in quelle
|
||||||
|
assert aufgabe.count('[ValidateSet("wsl", "dockerhost")]') == 2
|
||||||
|
assert "'\" --quelle ' + $Quelle + ' --quelle-ibkr ' + $QuelleIbkr" in aufgabe
|
||||||
|
assert 'if ($Quelle -eq "dockerhost" -or $QuelleIbkr -eq "dockerhost") { $Uhrzeit = "23:15" }' in aufgabe
|
||||||
|
assert "if (-not $QuelleIbkr) { $QuelleIbkr = $Quelle }" in aufgabe
|
||||||
|
|
||||||
|
|
||||||
|
@fall
|
||||||
|
def der_echte_wsl_weg_ist_versperrt():
|
||||||
|
# Ohne --quelle und ohne eigene Attrappe nimmt main den WSL-Weg. Er muss als Abweichung
|
||||||
|
# enden, nicht als Lauf.
|
||||||
|
try:
|
||||||
|
lauf("gut", "--tag", "2026-10-05", "--jetzt", "2026-10-05T23:30")
|
||||||
|
except AssertionError as ausnahme:
|
||||||
|
text = str(ausnahme)
|
||||||
|
assert "WSL-Weg ohne Attrappe" in text, text
|
||||||
|
# Beleg fuer die Zeilen in lauf(): Beim Aufruf zeigten wsl.exe und das Skript ins Leere.
|
||||||
|
assert "kein-wsl" in text and "/nicht-vorhanden/" in text, text
|
||||||
|
return
|
||||||
|
raise AssertionError("ein Lauf ohne WSL-Attrappe kam durch")
|
||||||
|
|
||||||
|
|
||||||
|
@fall
|
||||||
|
def der_echte_wsl_aufruf_findet_kein_wsl():
|
||||||
|
# Die zweite Sperre: Selbst die echte fahre_wsl findet in der Abdichtung kein wsl.exe, und das
|
||||||
|
# Skript, das sie dort starten wuerde, gibt es nicht.
|
||||||
|
modul = lade()
|
||||||
|
with tempfile.TemporaryDirectory() as ordner:
|
||||||
|
modul.WSL = str(Path(ordner) / "kein-wsl" / "wsl.exe")
|
||||||
|
modul.WSL_SKRIPT = "/nicht-vorhanden/abendkontrolle_wsl.py"
|
||||||
|
try:
|
||||||
|
modul.fahre_wsl("2026-10-05", "alpaca")
|
||||||
|
except FileNotFoundError:
|
||||||
|
return
|
||||||
|
raise AssertionError("fahre_wsl lief ohne wsl.exe weiter")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
abweichungen = 0
|
||||||
|
for funktion in FAELLE:
|
||||||
|
try:
|
||||||
|
funktion()
|
||||||
|
except (Exception, SystemExit) as ausnahme:
|
||||||
|
abweichungen += 1
|
||||||
|
print(f"ABWEICHUNG {funktion.__name__}: {type(ausnahme).__name__}: {ausnahme}")
|
||||||
|
else:
|
||||||
|
print(f"OK {funktion.__name__}")
|
||||||
|
print(f"{len(FAELLE)} Faelle, {abweichungen} Abweichung(en) ({datetime.now():%Y-%m-%d %H:%M})")
|
||||||
|
return 2 if abweichungen else 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -2,16 +2,27 @@
|
|||||||
# Koordinator-Werkzeug, nicht fuers Repo. Auftrag Pascal 2026-09-24: taegliche Abendkontrolle,
|
# Koordinator-Werkzeug, nicht fuers Repo. Auftrag Pascal 2026-09-24: taegliche Abendkontrolle,
|
||||||
# dauerhaft, uebersteht Neustarts, blockiert keine Updates von Claude Code oder Desktop.
|
# dauerhaft, uebersteht Neustarts, blockiert keine Updates von Claude Code oder Desktop.
|
||||||
#
|
#
|
||||||
# Zwei Ausloeser: werktags 22:45 (nach der Tagesauswertung um 22:20) und fuenf Minuten nach jeder
|
# Zwei Ausloeser: werktags 22:45 (nach der Tagesauswertung um 22:20) oder statt dessen 23:15, sobald ein Teil vom
|
||||||
# Anmeldung (holt einen verpassten Abend nach; abendkontrolle.py prueft nur Tage ohne Ergebnisdatei).
|
# Docker-Host kommt (-Quelle oder -QuelleIbkr dockerhost, seit Paket DH), und fuenf Minuten nach
|
||||||
|
# jeder Anmeldung (holt einen verpassten Abend nach; abendkontrolle.py prueft nur Tage ohne
|
||||||
|
# Ergebnisdatei).
|
||||||
# Nur bei angemeldetem Benutzer (Interactive): WSL und der ganze Paper-Betrieb brauchen die Anmeldung
|
# Nur bei angemeldetem Benutzer (Interactive): WSL und der ganze Paper-Betrieb brauchen die Anmeldung
|
||||||
# ohnehin (Aufgabe "Trading Plattform - WSL wachhalten"). pythonw.exe startet ohne Fenster.
|
# ohnehin (Aufgabe "Trading Plattform - WSL wachhalten"). pythonw.exe startet ohne Fenster.
|
||||||
# Voller Interpreterpfad: -Execute "python" endet in der Aufgabenplanung mit 0x80070002.
|
# Voller Interpreterpfad: -Execute "python" endet in der Aufgabenplanung mit 0x80070002.
|
||||||
#
|
#
|
||||||
# Aufruf: powershell -NoProfile -File abendkontrolle_einrichten.ps1 [-Aktion einrichten|entfernen]
|
# Aufruf: powershell -NoProfile -File abendkontrolle_einrichten.ps1 [-Aktion einrichten|entfernen]
|
||||||
|
# [-Quelle wsl|dockerhost] [-QuelleIbkr wsl|dockerhost]
|
||||||
param(
|
param(
|
||||||
[ValidateSet("einrichten", "entfernen")]
|
[ValidateSet("einrichten", "entfernen")]
|
||||||
[string]$Aktion = "einrichten"
|
[string]$Aktion = "einrichten",
|
||||||
|
# Seit Paket DH: wsl (Vorgabe) prueft in WSL wie bisher; dockerhost liest das Ergebnis des
|
||||||
|
# Docker-Hosts ueber SSH, werktags erst um 23:15 (der Host prueft um 16:30 New Yorker Zeit).
|
||||||
|
[ValidateSet("wsl", "dockerhost")]
|
||||||
|
[string]$Quelle = "wsl",
|
||||||
|
# Die Quelle fuer den Teil ibkr; ohne Angabe dieselbe wie -Quelle. Die IBKR-Instanz zieht an
|
||||||
|
# einem eigenen Abend um (Umzugs-Drehbuch DH, Phase 7).
|
||||||
|
[ValidateSet("wsl", "dockerhost")]
|
||||||
|
[string]$QuelleIbkr
|
||||||
)
|
)
|
||||||
|
|
||||||
$Name = "Trading Plattform - Abendkontrolle"
|
$Name = "Trading Plattform - Abendkontrolle"
|
||||||
@@ -26,8 +37,10 @@ if (-not (Test-Path $Py)) { throw "Interpreter fehlt: $Py" }
|
|||||||
if (-not (Test-Path $Skript)) { throw "Skript fehlt: $Skript" }
|
if (-not (Test-Path $Skript)) { throw "Skript fehlt: $Skript" }
|
||||||
|
|
||||||
$Benutzer = "$env:USERDOMAIN\$env:USERNAME"
|
$Benutzer = "$env:USERDOMAIN\$env:USERNAME"
|
||||||
$Aufruf = New-ScheduledTaskAction -Execute $Py -Argument ('"' + $Skript + '"') -WorkingDirectory "D:\Claude"
|
if (-not $QuelleIbkr) { $QuelleIbkr = $Quelle }
|
||||||
$Abends = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Monday, Tuesday, Wednesday, Thursday, Friday -At "22:45"
|
$Aufruf = New-ScheduledTaskAction -Execute $Py -Argument ('"' + $Skript + '" --quelle ' + $Quelle + ' --quelle-ibkr ' + $QuelleIbkr) -WorkingDirectory "D:\Claude"
|
||||||
|
if ($Quelle -eq "dockerhost" -or $QuelleIbkr -eq "dockerhost") { $Uhrzeit = "23:15" } else { $Uhrzeit = "22:45" }
|
||||||
|
$Abends = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Monday, Tuesday, Wednesday, Thursday, Friday -At $Uhrzeit
|
||||||
$Anmeldung = New-ScheduledTaskTrigger -AtLogOn -User $Benutzer
|
$Anmeldung = New-ScheduledTaskTrigger -AtLogOn -User $Benutzer
|
||||||
$Anmeldung.Delay = "PT5M"
|
$Anmeldung.Delay = "PT5M"
|
||||||
$Wer = New-ScheduledTaskPrincipal -UserId $Benutzer -LogonType Interactive -RunLevel Limited
|
$Wer = New-ScheduledTaskPrincipal -UserId $Benutzer -LogonType Interactive -RunLevel Limited
|
||||||
@@ -41,3 +54,4 @@ $Aufgabe = Get-ScheduledTask -TaskName $Name
|
|||||||
"{0}: {1}" -f $Aufgabe.TaskName, $Aufgabe.State
|
"{0}: {1}" -f $Aufgabe.TaskName, $Aufgabe.State
|
||||||
$Aufgabe.Triggers | ForEach-Object { "Ausloeser: {0} {1} {2}" -f $_.CimClass.CimClassName, $_.StartBoundary, $_.Delay }
|
$Aufgabe.Triggers | ForEach-Object { "Ausloeser: {0} {1} {2}" -f $_.CimClass.CimClassName, $_.StartBoundary, $_.Delay }
|
||||||
"Naechster Lauf: {0}" -f (Get-ScheduledTaskInfo -TaskName $Name).NextRunTime
|
"Naechster Lauf: {0}" -f (Get-ScheduledTaskInfo -TaskName $Name).NextRunTime
|
||||||
|
"Quellen: {0} (alpaca), {1} (ibkr)" -f $Quelle, $QuelleIbkr
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
"""Angriffsfaelle fuer --ibkr-python von abendkontrolle_wsl.py (Nachtrag zu Paket DH, IB2), in WSL, ohne Netz.
|
||||||
|
|
||||||
|
Koordinator-Werkzeug, nicht fuers Repo. Auf dem Docker-Host laeuft der Teil ibkr im Abbild; dort
|
||||||
|
gibt es das Venv aus WSL nicht, und ibkr_pruefen.py lesen laeuft mit dem python des Containers
|
||||||
|
(abendkontrolle-host.sh: --ibkr-python python). Die Faelle pruefen den Schalter, die Vorgabe, den
|
||||||
|
Aufruf mit dem genannten Interpreter gegen ein Ersatzskript in einem Wegwerf-Ordner und die
|
||||||
|
Weitergabe im Ablauf. Das Ersatzskript druckt den Interpreter, der es startet; so faellt ein
|
||||||
|
Aufruf mit dem festen Venv-Pfad auf, auch wenn es den Pfad gibt.
|
||||||
|
Aufruf (WSL, Venv): python abendkontrolle_ibkr_python_angriffe.py
|
||||||
|
Exit 0 ohne Abweichung, 2 mit Abweichung.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import importlib.util
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
HIER = Path(__file__).resolve().parent
|
||||||
|
ERSATZ = (
|
||||||
|
"import sys\n"
|
||||||
|
"print('Interpreter: ' + sys.executable)\n"
|
||||||
|
"print('Positionen: 0')\n"
|
||||||
|
"print('Offene Orders: 0')\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
FAELLE = []
|
||||||
|
|
||||||
|
|
||||||
|
def fall(funktion):
|
||||||
|
FAELLE.append(funktion)
|
||||||
|
return funktion
|
||||||
|
|
||||||
|
|
||||||
|
def lade():
|
||||||
|
spec = importlib.util.spec_from_file_location("abendkontrolle_wsl_unter_angriff", HIER / "abendkontrolle_wsl.py")
|
||||||
|
modul = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(modul)
|
||||||
|
# Zweite Sperre (Pruefung DH-Nachtrag, 2026-09-26): Die Faelle geben den Ordner mit dem
|
||||||
|
# Ersatzskript ausdruecklich mit. Nimmt ein Mutant stattdessen die Vorgabe, zeigt sie ins Leere
|
||||||
|
# und nicht auf den Betriebsordner mit dem echten ibkr_pruefen.py.
|
||||||
|
modul.IBKR_ORDNER = "/nicht-vorhanden/trading-ibkr-betrieb"
|
||||||
|
return modul
|
||||||
|
|
||||||
|
|
||||||
|
@fall
|
||||||
|
def der_schalter_nennt_den_interpreter():
|
||||||
|
argumente = lade().parse_args(["--tag", "2026-10-05", "--teil", "ibkr", "--ibkr-python", "/usr/local/bin/python"])
|
||||||
|
assert argumente.ibkr_python == "/usr/local/bin/python", argumente
|
||||||
|
|
||||||
|
|
||||||
|
@fall
|
||||||
|
def ohne_schalter_bleibt_das_venv_aus_wsl():
|
||||||
|
modul = lade()
|
||||||
|
argumente = modul.parse_args(["--tag", "2026-10-05", "--teil", "ibkr"])
|
||||||
|
assert argumente.ibkr_python == modul.IBKR_PY, argumente
|
||||||
|
assert modul.IBKR_PY == "/home/claude/venvs/trading-ibkr/bin/python", modul.IBKR_PY
|
||||||
|
|
||||||
|
|
||||||
|
@fall
|
||||||
|
def lesen_laeuft_mit_dem_genannten_interpreter():
|
||||||
|
modul = lade()
|
||||||
|
with tempfile.TemporaryDirectory() as ordner:
|
||||||
|
wurzel = Path(ordner)
|
||||||
|
(wurzel / "scripts").mkdir()
|
||||||
|
(wurzel / "scripts" / "ibkr_pruefen.py").write_text(ERSATZ, encoding="utf-8")
|
||||||
|
code, text = modul.lies_konto_ibkr(wurzel, None, sys.executable)
|
||||||
|
assert code == 0, (code, text)
|
||||||
|
assert f"Interpreter: {sys.executable}" in text, text
|
||||||
|
assert "Positionen: 0" in text and "Offene Orders: 0" in text, text
|
||||||
|
|
||||||
|
|
||||||
|
@fall
|
||||||
|
def der_ablauf_gibt_den_schalter_weiter():
|
||||||
|
quelle = (HIER / "abendkontrolle_wsl.py").read_text(encoding="utf-8")
|
||||||
|
assert "lies_konto_ibkr, ordner, argumente.ibkr_konto, argumente.ibkr_python)" in quelle
|
||||||
|
assert '[python, "scripts/ibkr_pruefen.py", "lesen"], cwd=ordner,' in quelle
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
abweichungen = 0
|
||||||
|
for funktion in FAELLE:
|
||||||
|
try:
|
||||||
|
funktion()
|
||||||
|
except (Exception, SystemExit) as ausnahme:
|
||||||
|
abweichungen += 1
|
||||||
|
print(f"ABWEICHUNG {funktion.__name__}: {type(ausnahme).__name__}: {ausnahme}")
|
||||||
|
else:
|
||||||
|
print(f"OK {funktion.__name__}")
|
||||||
|
print(f"{len(FAELLE)} Faelle, {abweichungen} Abweichung(en) ({datetime.now():%Y-%m-%d %H:%M})")
|
||||||
|
return 2 if abweichungen else 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -87,6 +87,8 @@ Nur eine Meldung schicken: python abendkontrolle_wsl.py --meldung "<titel>" "<te
|
|||||||
Fuer Angriffsfaelle ohne Netz und ohne systemd: --projekt, --kalender, --journal, --dienste,
|
Fuer Angriffsfaelle ohne Netz und ohne systemd: --projekt, --kalender, --journal, --dienste,
|
||||||
--ohne-broker, --ohne-kerzen; im Teil ibkr dazu --ibkr-ordner, --ibkr-konto (JSON mit exit und
|
--ohne-broker, --ohne-kerzen; im Teil ibkr dazu --ibkr-ordner, --ibkr-konto (JSON mit exit und
|
||||||
text statt des Aufrufs von ibkr_pruefen.py) und --jetzt (ISO-Zeitpunkt mit Versatz).
|
text statt des Aufrufs von ibkr_pruefen.py) und --jetzt (ISO-Zeitpunkt mit Versatz).
|
||||||
|
Seit dem Nachtrag zu Paket DH (IB2): --ibkr-python nennt den Interpreter fuer ibkr_pruefen.py lesen
|
||||||
|
(Vorgabe IBKR_PY, das Venv in WSL; auf dem Docker-Host python im Abbild, abendkontrolle-host.sh).
|
||||||
Ausgabe: genau eine JSON-Zeile auf stdout. Exit 0 gelaufen (Befunde stehen in der Ausgabe),
|
Ausgabe: genau eine JSON-Zeile auf stdout. Exit 0 gelaufen (Befunde stehen in der Ausgabe),
|
||||||
2 abgebrochen (die bis dahin gesammelten Befunde stehen trotzdem in der Ausgabe).
|
2 abgebrochen (die bis dahin gesammelten Befunde stehen trotzdem in der Ausgabe).
|
||||||
"""
|
"""
|
||||||
@@ -486,13 +488,17 @@ def lesen_erlaubt(jetzt: datetime) -> bool:
|
|||||||
return not IBKR_SPERRE_VON <= (ny.hour, ny.minute) < IBKR_SPERRE_BIS
|
return not IBKR_SPERRE_VON <= (ny.hour, ny.minute) < IBKR_SPERRE_BIS
|
||||||
|
|
||||||
|
|
||||||
def lies_konto_ibkr(ordner: Path, konto_datei: str | None) -> tuple[int, str]:
|
def lies_konto_ibkr(ordner: Path, konto_datei: str | None, python: str = IBKR_PY) -> tuple[int, str]:
|
||||||
"""Exit und Ausgabe (stdout und stderr) von scripts/ibkr_pruefen.py lesen, oder aus einer Datei."""
|
"""Exit und Ausgabe (stdout und stderr) von scripts/ibkr_pruefen.py lesen, oder aus einer Datei.
|
||||||
|
|
||||||
|
python ist der Interpreter dafuer: in WSL das Venv der Instanz (IBKR_PY), auf dem Docker-Host
|
||||||
|
das python des Abbilds (--ibkr-python, seit dem Nachtrag zu Paket DH).
|
||||||
|
"""
|
||||||
if konto_datei is not None:
|
if konto_datei is not None:
|
||||||
daten = json.loads(Path(konto_datei).read_text(encoding="utf-8"))
|
daten = json.loads(Path(konto_datei).read_text(encoding="utf-8"))
|
||||||
return int(daten["exit"]), str(daten["text"])
|
return int(daten["exit"]), str(daten["text"])
|
||||||
lauf = subprocess.run(
|
lauf = subprocess.run(
|
||||||
[IBKR_PY, "scripts/ibkr_pruefen.py", "lesen"], cwd=ordner, capture_output=True, text=True,
|
[python, "scripts/ibkr_pruefen.py", "lesen"], cwd=ordner, capture_output=True, text=True,
|
||||||
encoding="utf-8", errors="replace", timeout=IBKR_LESEN_TIMEOUT_S,
|
encoding="utf-8", errors="replace", timeout=IBKR_LESEN_TIMEOUT_S,
|
||||||
env={**os.environ, "PYTHONIOENCODING": "utf-8"},
|
env={**os.environ, "PYTHONIOENCODING": "utf-8"},
|
||||||
)
|
)
|
||||||
@@ -574,7 +580,7 @@ def kontrolliere_ibkr(argumente: argparse.Namespace, k: Kontrolle) -> None:
|
|||||||
k.hinweis(f"Konto am IBKR-Gateway nicht gelesen: {jetzt.astimezone(NEW_YORK):%H:%M} New Yorker Zeit "
|
k.hinweis(f"Konto am IBKR-Gateway nicht gelesen: {jetzt.astimezone(NEW_YORK):%H:%M} New Yorker Zeit "
|
||||||
"liegt im Handelsfenster")
|
"liegt im Handelsfenster")
|
||||||
return
|
return
|
||||||
ergebnis = k.schritt("Konto am IBKR-Gateway", lies_konto_ibkr, ordner, argumente.ibkr_konto)
|
ergebnis = k.schritt("Konto am IBKR-Gateway", lies_konto_ibkr, ordner, argumente.ibkr_konto, argumente.ibkr_python)
|
||||||
if ergebnis is not None:
|
if ergebnis is not None:
|
||||||
pruefe_konto_ibkr(ergebnis[0], ergebnis[1], k)
|
pruefe_konto_ibkr(ergebnis[0], ergebnis[1], k)
|
||||||
|
|
||||||
@@ -667,6 +673,8 @@ def parse_args(argv=None) -> argparse.Namespace:
|
|||||||
# Seit Paket IB2: der Teil ibkr prueft die IBKR-Instanz statt des Alpaca-Runners.
|
# Seit Paket IB2: der Teil ibkr prueft die IBKR-Instanz statt des Alpaca-Runners.
|
||||||
zerleger.add_argument("--teil", choices=("alpaca", "ibkr"), default="alpaca")
|
zerleger.add_argument("--teil", choices=("alpaca", "ibkr"), default="alpaca")
|
||||||
zerleger.add_argument("--ibkr-ordner", default=IBKR_ORDNER)
|
zerleger.add_argument("--ibkr-ordner", default=IBKR_ORDNER)
|
||||||
|
# Seit dem Nachtrag zu Paket DH (IB2): der Interpreter fuer ibkr_pruefen.py lesen.
|
||||||
|
zerleger.add_argument("--ibkr-python", default=IBKR_PY)
|
||||||
# Nur fuer die Angriffsfaelle: Exit und Ausgabe von ibkr_pruefen.py als JSON-Datei, und die Uhr.
|
# Nur fuer die Angriffsfaelle: Exit und Ausgabe von ibkr_pruefen.py als JSON-Datei, und die Uhr.
|
||||||
zerleger.add_argument("--ibkr-konto")
|
zerleger.add_argument("--ibkr-konto")
|
||||||
zerleger.add_argument("--jetzt", help="ISO-Zeitpunkt mit Versatz statt der Uhr")
|
zerleger.add_argument("--jetzt", help="ISO-Zeitpunkt mit Versatz statt der Uhr")
|
||||||
|
|||||||
@@ -13,6 +13,12 @@ HIER = Path(__file__).resolve().parent
|
|||||||
sys.path.insert(0, str(HIER))
|
sys.path.insert(0, str(HIER))
|
||||||
import rueckweg_aapl as rw # noqa: E402
|
import rueckweg_aapl as rw # noqa: E402
|
||||||
|
|
||||||
|
# Abdichtung (Pruefung DH-Nachtrag, 2026-09-26): Die Faelle laufen mit --ohne-wsl. Bricht ein Mutant
|
||||||
|
# diesen Schalter, riefe melde() sonst den echten WSL-Teil mit --meldung, also ntfy. rueckweg_aapl
|
||||||
|
# holt WSL und WSL_SKRIPT als Wert aus abendkontrolle; deshalb werden sie hier im Modul ersetzt.
|
||||||
|
rw.WSL = str(Path(tempfile.gettempdir()) / "rueckweg-angriffe-kein-wsl" / "wsl.exe")
|
||||||
|
rw.WSL_SKRIPT = "/nicht-vorhanden/abendkontrolle_wsl.py"
|
||||||
|
|
||||||
ZEHN = '[agent]\nquelle = "folge"\ninstrumente = ["AAPL.XNAS", "MSFT.XNAS"]\n'
|
ZEHN = '[agent]\nquelle = "folge"\ninstrumente = ["AAPL.XNAS", "MSFT.XNAS"]\n'
|
||||||
AAPL = '[agent]\nquelle = "register"\ninstrument = "AAPL.XNAS"\n'
|
AAPL = '[agent]\nquelle = "register"\ninstrument = "AAPL.XNAS"\n'
|
||||||
abweichungen: list[str] = []
|
abweichungen: list[str] = []
|
||||||
|
|||||||
Reference in new issue
Block a user