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

@@ -397,10 +397,368 @@ def windows_faelle() -> None:
abweichungen.append("Nachholfenster")
# --- IBKR-Instanz (Paket IB2), WSL-Teil ------------------------------------------------------------
IBKR_JOURNAL_GUT = (
"Ende: boersenschluss, Position 0, Orders 2, Killswitch aktiv, Exit 0\n"
"trading-paper-ibkr.service: Deactivated successfully.\n"
)
# Erfundene Ausgabe von scripts/ibkr_pruefen.py lesen: ohne Kontonummer, ohne Betrag.
IBKR_KONTO_GUT = (
"verbunden: Gateway 127.0.0.1:4004, Client-ID 12, Handelsmodus paper, Konto DU... (9 Zeichen), "
"vom Gateway bestaetigt\n"
"Konto: Kontowert, Bargeld und Kaufkraft gelesen (Betraege werden nicht ausgegeben)\n"
"Waehrung des Kontowerts: EUR\n"
"Positionen: 0\n"
"Offene Orders: 0\n"
"ERGEBNIS: gelesen\n"
)
# Eine erfundene Kontonummer in der Form der Paper-Konten, nur fuer die Maske.
KONTO_ERFUNDEN = "DU0000000"
KONTO_ERFUNDEN_LANG = "DUQ000000"
# 22:45 Berlin ist 16:45 New York (nach Boersenschluss), 16:00 Berlin ist 10:00 New York.
NACH_SCHLUSS = f"{TAG}T22:45:00+02:00"
IM_HANDEL = f"{TAG}T16:00:00+02:00"
def baue_ibkr(wurzel: Path) -> Path:
ordner = wurzel / "betrieb"
(ordner / "artifacts" / "paper").mkdir(parents=True)
(ordner / "data" / "paper").mkdir(parents=True)
(ordner / "data" / "paper" / "broker.txt").write_text("ibkr\n", encoding="utf-8")
shutil.copyfile(ECHTES_PROTOKOLL, ordner / "artifacts" / "paper" / f"{TAG}T131023-paper.jsonl")
with sqlite3.connect(ordner / "data" / "registry.db") as db:
db.execute("CREATE TABLE killswitch_zustand (id INTEGER PRIMARY KEY, zustand TEXT, seit TEXT, ausloeser TEXT)")
db.execute("CREATE TABLE killswitch_ereignisse (id INTEGER PRIMARY KEY AUTOINCREMENT, zeit TEXT, stufe TEXT, "
"ausloeser TEXT, grund TEXT, ergebnis TEXT, erfolgreich INTEGER)")
db.execute("INSERT INTO killswitch_zustand VALUES (1, 'aktiv', '2026-09-23T13:31:00+00:00', 'automatik')")
db.execute("INSERT INTO killswitch_ereignisse (zeit, stufe, ausloeser, grund, ergebnis, erfolgreich) "
"VALUES ('2026-09-23T13:31:00+00:00', 'fortsetzen', 'automatik', 'Tagesstart', '{}', 1)")
(wurzel / "kalender.json").write_text(json.dumps(KALENDER_TAG), encoding="utf-8")
(wurzel / "journal.txt").write_text(IBKR_JOURNAL_GUT, encoding="utf-8")
(wurzel / "dienste.json").write_text(json.dumps({"trading-paper-ibkr.timer": "active"}), encoding="utf-8")
(wurzel / "konto.json").write_text(json.dumps({"exit": 0, "text": IBKR_KONTO_GUT}), encoding="utf-8")
return ordner
def fahre_ibkr(wurzel: Path, ordner: Path, jetzt: str = NACH_SCHLUSS, tag: str = TAG, *,
projekt: Path | None = None, meldung: bool = False, env: dict | None = None) -> dict:
befehl = [
sys.executable, str(HIER / "abendkontrolle_wsl.py"), "--teil", "ibkr", "--tag", tag,
"--ibkr-ordner", str(ordner), "--projekt", str(wurzel if projekt is None else projekt),
"--kalender", str(wurzel / "kalender.json"), "--journal", str(wurzel / "journal.txt"),
"--dienste", str(wurzel / "dienste.json"), "--ibkr-konto", str(wurzel / "konto.json"), "--jetzt", jetzt,
]
if not meldung:
befehl.append("--ohne-meldung")
umgebung = {k: v for k, v in os.environ.items() if k != "ABENDKONTROLLE_AUSWERTUNG"}
umgebung.update(env or {})
lauf = subprocess.run(befehl, capture_output=True, text=True, env=umgebung, timeout=300)
try:
ergebnis = json.loads(lauf.stdout.strip().splitlines()[-1])
except (IndexError, json.JSONDecodeError):
return {"befunde": [f"KEINE JSON-AUSGABE exit {lauf.returncode}: {lauf.stderr[-300:]}"], "exit": lauf.returncode}
ergebnis["_exit"] = lauf.returncode
return ergebnis
def ibkr_wsl_faelle() -> None:
def fall(name, vorbereiten, *, befund, anzahl=None, exit_code=0, jetzt=NACH_SCHLUSS, tag=TAG,
auch=(), nicht=(), fakt=None):
with tempfile.TemporaryDirectory() as tmp:
wurzel = Path(tmp)
ordner = baue_ibkr(wurzel)
vorbereiten(wurzel, ordner)
erwarte("IBKR " + name, fahre_ibkr(wurzel, ordner, jetzt, tag), befund=befund, anzahl=anzahl,
exit_code=exit_code, auch=auch, nicht=nicht, fakt=fakt)
nichts = lambda w, o: None # noqa: E731
prot = lambda o: o / "artifacts" / "paper" / f"{TAG}T131023-paper.jsonl" # noqa: E731
def schreibe(pfad: Path, text: str) -> None:
pfad.write_text(text, encoding="utf-8")
def konto(text: str, code: int = 0):
return lambda w, o: schreibe(w / "konto.json", json.dumps({"exit": code, "text": text}))
def register(*befehle: str):
def vorbereiten(w, o):
with sqlite3.connect(o / "data" / "registry.db") as db:
for befehl in befehle:
db.execute(befehl)
return vorbereiten
def zeilen_aendern(o: Path, aendern) -> None:
zeilen = prot(o).read_text(encoding="utf-8").splitlines()
schreibe(prot(o), "\n".join(aendern(zeilen)) + "\n")
fall("Grundlinie", nichts, befund=None, anzahl=0,
fakt=lambda f, e: f.get("konto") == {"positionen": 0, "orders": 0, "exit": 0} and e.get("teil") == "ibkr")
# I2 No-Go-Tag: Zeitgeber aus und kein Protokoll ist leise; Zeitgeber an und kein Protokoll ist ein Befund.
def zeitgeber_aus(w, o):
prot(o).unlink()
schreibe(w / "dienste.json", json.dumps({"trading-paper-ibkr.timer": "inactive"}))
fall("Zeitgeber aus, kein Protokoll", zeitgeber_aus, befund=None, anzahl=0,
fakt=lambda f, e: f.get("nicht_gestartet") is True
and any("IBKR-Instanz heute nicht gestartet" in h for h in e.get("hinweise", [])))
fall("Zeitgeber an, kein Protokoll", lambda w, o: prot(o).unlink(), befund="die Instanz lief nicht")
fall("Zeitgeber aus, aber von Hand gelaufen",
lambda w, o: schreibe(w / "dienste.json", json.dumps({"trading-paper-ibkr.timer": "inactive"})),
befund=None, anzahl=0, fakt=lambda f, e: len(f.get("laeufe") or []) == 1 and not f.get("nicht_gestartet"))
fall("Kalender leer", lambda w, o: (schreibe(w / "kalender.json", "[]"), prot(o).unlink()), befund=None, anzahl=0)
# I3 Instanzmarke
fall("Marke fehlt", lambda w, o: (o / "data" / "paper" / "broker.txt").unlink(), befund="Instanzmarke")
fall("Marke alpaca", lambda w, o: schreibe(o / "data" / "paper" / "broker.txt", "alpaca\n"),
befund="nicht der Betriebsordner der IBKR-Instanz")
# I4 Register
fall("gesperrt nach Red Button",
register("UPDATE killswitch_zustand SET zustand = 'gesperrt', ausloeser = 'pascal'",
"INSERT INTO killswitch_ereignisse (zeit, stufe, ausloeser, grund, ergebnis, erfolgreich) "
"VALUES ('2026-09-23T15:00:00+00:00', 'glattstellen', 'pascal', 'Probe', '{}', 1)"),
befund="IBKR-Killswitch steht auf gesperrt", auch=("letztes Ereignis glattstellen von pascal",))
fall("gesperrt ohne Ereignis",
register("UPDATE killswitch_zustand SET zustand = 'gesperrt'", "DELETE FROM killswitch_ereignisse"),
befund="IBKR-Killswitch steht auf gesperrt", auch=("ohne Ereignis im Register",))
fall("Killswitch unbekannter Zustand", register("UPDATE killswitch_zustand SET zustand = 'kaputt'"),
befund="IBKR-Killswitch in unbekanntem Zustand")
fall("Register fehlt", lambda w, o: (o / "data" / "registry.db").unlink(), befund="IBKR-Register data/registry.db fehlt")
fall("Register ohne Ereignistabelle", register("DROP TABLE killswitch_ereignisse"),
befund="IBKR-Killswitch nicht geprueft")
# I5 Ende je Lauf, Handel, Schlussposition
fall("Lauf endete mit abbruch",
lambda w, o: zeilen_aendern(o, lambda z: [s.replace('"grund": "boersenschluss"', '"grund": "abbruch"')
if '"art": "ende"' in s else s for s in z]),
befund="Lauf endete mit abbruch")
fall("nie aktiv",
lambda w, o: zeilen_aendern(o, lambda z: [s.replace('"killswitch": "aktiv"', '"killswitch": "angehalten"') for s in z]),
befund="Tag ohne Handel")
# Das Journal meldet Position 0, das Protokoll eine Restposition: Die Schlussposition kommt aus dem
# Protokoll, unabhaengig vom Journal (Anforderung Nr. 5, "Position 0").
fall("Restposition im Protokoll bei sauberem Journal",
lambda w, o: zeilen_aendern(o, lambda z: [s.replace('"position": 0', '"position": 10')
if '"art": "ende"' in s else s for s in z]),
befund="endete laut Protokoll mit Position 10", anzahl=1)
# Ein Lauf ohne Zeile ende: pruefe_laeufe ueberspringt ihn, im Teil ibkr meldet ihn
# pruefe_schlusspositionen (Nachkritik IB2).
fall("Lauf ohne Zeile ende",
lambda w, o: zeilen_aendern(o, lambda z: [s for s in z if '"art": "ende"' not in s]),
befund="Lauf ohne Zeile ende im Protokoll")
# I6 Journal
fall("Journal Exit 4", lambda w, o: schreibe(w / "journal.txt", IBKR_JOURNAL_GUT.replace("Exit 0", "Exit 4")),
befund="Exit 4")
fall("Journal ohne Ende", lambda w, o: schreibe(w / "journal.txt", "gestartet\n"),
befund="von trading-paper-ibkr.service an diesem Tag")
fall("Journal Failed with result",
lambda w, o: schreibe(w / "journal.txt", IBKR_JOURNAL_GUT + "trading-paper-ibkr.service: Failed with result 'exit-code'.\n"),
befund="Journal: trading-paper-ibkr.service endete mit 'Failed with result'")
fall("Journal Traceback",
lambda w, o: schreibe(w / "journal.txt", IBKR_JOURNAL_GUT + "Traceback (most recent call last):\n File x\nValueError: y\n"),
befund="zeigt 'Traceback")
fall("Journal Marke passt nicht zur Konfiguration",
lambda w, o: schreibe(w / "journal.txt", IBKR_JOURNAL_GUT + "Dieser Ordner ist die IBKR-Instanz, die Konfiguration "
"waehlt aber [broker] wahl = 'alpaca'. Nichts wird gebaut, das Alpaca-Konto bleibt unberuehrt.\n"),
befund="zeigt 'Nichts wird gebaut'")
# I7 Konto am Gateway
fall("Konto 2 Positionen",
konto(IBKR_KONTO_GUT.replace("Positionen: 0\n", "Positionen: 2\n AAPL.XNAS 10\n KO.XNYS 10\n")),
befund="Beim IBKR-Gateway stehen 2 Position(en)", nicht=("AAPL.XNAS 10", "KO.XNYS 10"))
fall("Konto 1 offene Order", konto(IBKR_KONTO_GUT.replace("Offene Orders: 0", "Offene Orders: 1")),
befund="Beim IBKR-Gateway stehen 1 offene Order(s)")
fall("Konto ohne Zeilen", konto("verbunden: Gateway 127.0.0.1:4004\nERGEBNIS: gelesen\n"),
befund="Konto am Gateway nicht gelesen")
fall("Konto Voraussetzung fehlt", konto("Voraussetzung fehlt: config/paper-ibkr.toml fehlt\n", 4),
befund="endete mit Exit 4", auch=("Voraussetzung fehlt",))
fall("Konto BEFUND mit Kontonummer wird maskiert",
konto(IBKR_KONTO_GUT + f"BEFUND: Gateway meldet das Konto {KONTO_ERFUNDEN} als gesperrt\n", 1),
befund="[konto]", nicht=(KONTO_ERFUNDEN,))
# Die echte Form hat drei Buchstaben vorn und sechs Ziffern (Pruefung IB2); erfunden, wie oben.
fall("Konto BEFUND mit Kontonummer in der echten Form wird maskiert",
konto(IBKR_KONTO_GUT + f"BEFUND: Gateway meldet das Konto {KONTO_ERFUNDEN_LANG} als gesperrt\n", 1),
befund="[konto]", nicht=(KONTO_ERFUNDEN_LANG,))
fall("im Handelsfenster wird das Konto nicht gelesen", konto("kaputt", 9), befund=None, anzahl=0, jetzt=IM_HANDEL,
fakt=lambda f, e: "konto" not in f and any("nicht gelesen" in h for h in e.get("hinweise", [])))
# Bedienung
fall("Betriebsordner fehlt", lambda w, o: shutil.rmtree(o), befund="Betriebsordner der IBKR-Instanz fehlt", anzahl=1)
fall("Tag falsch", nichts, befund="abgebrochen", exit_code=2, tag="2026-13-40")
# Meldeweg des Teils ibkr ohne --ohne-meldung, gegen den lokalen Probeserver: Titel beginnt mit
# "IBKR Abendkontrolle", Prioritaet hoch (Anforderung Nr. 5). Eine Zeile kennung in der kopierten
# config/paper.toml faellt heraus, sonst stuende sie vor dem Titel.
with tempfile.TemporaryDirectory() as tmp_server:
mitschrift = Path(tmp_server) / "mitschrift.jsonl"
port = 18766
server = subprocess.Popen([sys.executable, str(HIER / "ntfy_probe_server.py"), str(port), str(mitschrift)],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
try:
import socket
import time
for _ in range(50):
try:
socket.create_connection(("127.0.0.1", port), timeout=0.2).close()
break
except OSError:
time.sleep(0.1)
with tempfile.TemporaryDirectory() as tmp:
wurzel = Path(tmp)
projekt = baue_projekt(wurzel)
ordner = baue_ibkr(wurzel)
(ordner / "data" / "paper" / "broker.txt").unlink() # ein Befund, damit gemeldet wird
text = (ECHTES_PROJEKT / "config" / "paper.toml").read_text(encoding="utf-8")
anker = 'ntfy_url = "https://ntfy.sh"'
if text.count(anker) != 1:
raise RuntimeError("Anker ntfy_url fehlt in config/paper.toml")
text = text.replace(anker, f'ntfy_url = "http://127.0.0.1:{port}"\nklartext_lokal = true')
text = "\n".join(z for z in text.split("\n") if not z.strip().startswith("kennung"))
schreibe(projekt / "config" / "paper.toml", text)
schreibe(wurzel / "secrets.toml", '[ntfy]\naktiv = true\nthema = "abendkontrolle-angriff"\n')
ergebnis = fahre_ibkr(wurzel, ordner, projekt=projekt, meldung=True,
env={"TRADING_GEHEIMNISSE": str(wurzel / "secrets.toml")})
zeilen = ([json.loads(z) for z in mitschrift.read_text(encoding="utf-8").split("\n") if z.strip()]
if mitschrift.exists() else [])
titel = [str(z.get("title") or "") for z in zeilen]
stufen = [z.get("priority") for z in zeilen]
gesendet = (ergebnis.get("fakten") or {}).get("meldung")
ok = (gesendet == "gesendet" and len(zeilen) == 1 and titel[0].startswith("IBKR Abendkontrolle")
and stufen == ["high"])
zeile = (f"{'OK ' if ok else 'FEHL'} IBKR Meldung mit Titel IBKR und Prioritaet hoch: "
f"titel={titel} prioritaet={stufen} meldung={gesendet}")
print(zeile)
if not ok:
abweichungen.append(zeile)
finally:
server.terminate()
server.wait(timeout=10)
# --- IBKR-Instanz (Paket IB2), Windows-Teil ----------------------------------------------------------
def ibkr_windows_faelle() -> None:
sys.path.insert(0, str(HIER))
import abendkontrolle as ak
from datetime import datetime
gut = {"tag": TAG, "teil": "ibkr", "befunde": [], "hinweise": [], "exit": 0,
"fakten": {"handelstag": True,
"ende": {"grund": "boersenschluss", "exit": "0", "orders": "40", "position": "0"},
"laeufe": [{"takte_aktiv": 375}], "konto": {"positionen": 0, "orders": 0, "exit": 0},
"killswitch": {"zustand": "aktiv", "ausloeser": "automatik"}}}
alpaca = {"tag": TAG, "befunde": [], "hinweise": [], "exit": 0,
"fakten": {"handelstag": True,
"ende": {"grund": "boersenschluss", "exit": "0", "orders": "79", "position": "0"},
"laeufe": [{"takte_aktiv": 375}], "broker_offen": 0, "bericht": True,
"killswitch": {"zustand": "aktiv", "ausloeser": "automatik"}}}
def pruefe_fall(name, ok, grund):
zeile = f"{'OK ' if ok else 'FEHL'} W IBKR {name}: {grund}"
print(zeile)
if not ok:
abweichungen.append(zeile)
def lauf(name, ergebnis, *, teil="ibkr", pruefe):
with tempfile.TemporaryDirectory() as tmp:
vault = Path(tmp)
(vault / "Inbox").mkdir()
eingabe = vault / "ergebnis.json"
eingabe.write_text(ergebnis if isinstance(ergebnis, str) else json.dumps(ergebnis), encoding="utf-8")
argumente = ["--vault", str(vault), "--tag", TAG, "--ohne-wsl", str(eingabe), "--jetzt", "2026-09-23T22:45:00"]
if teil is not None:
argumente += ["--teil", teil]
code = ak.main(argumente)
notiz = vault / "Inbox" / f"{TAG}.md"
text = notiz.read_text(encoding="utf-8") if notiz.exists() else ""
eigene = (vault / ak.ABLAGE_TEIL / f"{TAG}-ibkr.json").exists()
fremde = (vault / ak.ABLAGE_TEIL / f"{TAG}.json").exists()
try:
ok, grund = pruefe(code, text, eigene, fremde)
except Exception as ausnahme: # der Treiber selbst bricht nicht ab
ok, grund = False, f"Pruefung warf {ausnahme!r}"
pruefe_fall(name, ok, grund)
lauf("ohne Befund: eigene Zeile, eigene Datei", gut, pruefe=lambda c, t, e, f: (
c == 0 and e and not f and "— Abendkontrolle IBKR (automatisch): ohne Befund" in t
and "Konto beim Gateway: 0 Position(en), 0 offene Order(s)" in t
and "Abendkontrolle Trading Plattform" not in t, f"code {c} eigene {e} fremde {f} {t[-160:]!r}"))
lauf("mit Befund", {**gut, "befunde": ["Beim IBKR-Gateway stehen 2 Position(en)"]}, pruefe=lambda c, t, e, f: (
"Abendkontrolle IBKR (automatisch): **1 Befund(e)** #needs-review" in t
and " - Beim IBKR-Gateway stehen 2 Position(en)" in t and e, repr(t[-200:])))
lauf("nicht gestartet ist leise",
{"tag": TAG, "teil": "ibkr", "befunde": [], "exit": 0,
"hinweise": ["IBKR-Instanz heute nicht gestartet (trading-paper-ibkr.timer: inactive)"],
"fakten": {"handelstag": True, "nicht_gestartet": True, "zeitgeber": "inactive"}},
pruefe=lambda c, t, e, f: ("Abendkontrolle IBKR (automatisch): IBKR-Instanz heute nicht gestartet (Zeitgeber aus)." in t
and "#needs-review" not in t and e and c == 0, repr(t[-160:])))
lauf("kein Handelstag", {"tag": TAG, "teil": "ibkr", "befunde": [], "hinweise": [], "exit": 0,
"fakten": {"handelstag": False}},
pruefe=lambda c, t, e, f: ("Abendkontrolle IBKR (automatisch): kein Handelstag" in t and e, repr(t[-120:])))
lauf("Abbruch wird wiederholt", {"tag": TAG, "teil": "ibkr", "befunde": ["Abendkontrolle abgebrochen: X"],
"hinweise": [], "fakten": {}, "exit": 2},
pruefe=lambda c, t, e, f: (c == 1 and not e and "**1 Befund(e)**" in t, f"code {c} eigene {e}"))
lauf("gescheitert", "nicht json", pruefe=lambda c, t, e, f: (
c == 1 and not e and "Abendkontrolle IBKR (automatisch): **gescheitert**" in t, f"code {c} {t[-120:]!r}"))
# Ein Ergebnis des falschen Teils (etwa von einem WSL-Teil ohne IB2) landet nicht in der Datei der Instanz.
lauf("Ergebnis eines anderen Teils", {**gut, "teil": "alpaca"}, pruefe=lambda c, t, e, f: (
c == 1 and not e and not f and "Abendkontrolle IBKR (automatisch): **gescheitert**" in t,
f"code {c} eigene {e} fremde {f}"))
lauf("ohne --teil mit --ohne-wsl nur Alpaca", alpaca, teil=None, pruefe=lambda c, t, e, f: (
f and not e and "Abendkontrolle IBKR" not in t and "Abendkontrolle Trading Plattform" in t,
f"eigene {e} fremde {f}"))
# Beide Teile in derselben Notiz, Alpaca zuerst, je mit eigener Ergebnisdatei.
with tempfile.TemporaryDirectory() as tmp:
vault = Path(tmp)
(vault / "Inbox").mkdir()
(vault / "a.json").write_text(json.dumps(alpaca), encoding="utf-8")
(vault / "i.json").write_text(json.dumps(gut), encoding="utf-8")
gemeinsam = ["--vault", str(vault), "--tag", TAG, "--jetzt", "2026-09-23T22:45:00"]
ak.main(gemeinsam + ["--teil", "alpaca", "--ohne-wsl", str(vault / "a.json")])
ak.main(gemeinsam + ["--teil", "ibkr", "--ohne-wsl", str(vault / "i.json")])
text = (vault / "Inbox" / f"{TAG}.md").read_text(encoding="utf-8")
stelle_a = text.find("Abendkontrolle Trading Plattform")
stelle_i = text.find("Abendkontrolle IBKR")
ok = (0 <= stelle_a < stelle_i and text.count("## Agentenprotokoll") == 1
and (vault / ak.ABLAGE_TEIL / f"{TAG}.json").exists()
and (vault / ak.ABLAGE_TEIL / f"{TAG}-ibkr.json").exists())
pruefe_fall("beide Teile in einer Notiz", ok, f"alpaca {stelle_a} ibkr {stelle_i}")
# Relativ zu IBKR_ERSTER_TAG: Den Wert setzt der Koordinator vor Welle 1 (Planuebersicht IB2).
erster = ak.IBKR_ERSTER_TAG.isoformat()
pruefe_fall("IBKR_ERSTER_TAG ist ein Werktag nicht vor ERSTER_TAG",
ak.IBKR_ERSTER_TAG.weekday() < 5 and ak.IBKR_ERSTER_TAG >= ak.ERSTER_TAG, erster)
# Ohne --tag: am ersten Tag vor 22:30 ist fuer den Teil ibkr nichts faellig, auch kein Tag davor.
with tempfile.TemporaryDirectory() as tmp:
vault = Path(tmp)
(vault / "Inbox").mkdir()
(vault / "i.json").write_text(json.dumps(gut), encoding="utf-8")
ak.main(["--vault", str(vault), "--teil", "ibkr", "--ohne-wsl", str(vault / "i.json"), "--jetzt", f"{erster}T08:00:00"])
dateien = sorted(p.name for p in (vault / ak.ABLAGE_TEIL).glob("*-ibkr.json"))
pruefe_fall("ohne --tag nichts vor IBKR_ERSTER_TAG", dateien == [], str(dateien))
# faellige Tage je Teil, direkt
with tempfile.TemporaryDirectory() as tmp:
ablage = Path(tmp)
def ibkr_tage(jetzt):
return ak.faellige_tage(datetime.fromisoformat(jetzt), ablage, erster_tag=ak.IBKR_ERSTER_TAG,
endung=ak.IBKR_ENDUNG)
ergebnis = ibkr_tage(f"{erster}T08:00")
pruefe_fall("faellig: nichts vor IBKR_ERSTER_TAG", ergebnis == [], str(ergebnis))
ergebnis = ibkr_tage(f"{erster}T22:45")
pruefe_fall("faellig: der erste Tag nach 22:30", erster in ergebnis, str(ergebnis))
(ablage / "2026-09-29-ibkr.json").write_text("{}", encoding="utf-8")
ibkr = ibkr_tage("2026-09-30T08:00")
alpaca_tage = ak.faellige_tage(datetime.fromisoformat("2026-09-30T08:00"), ablage)
pruefe_fall("faellig: eigene Ergebnisdatei je Teil", "2026-09-29" not in ibkr and "2026-09-29" in alpaca_tage,
f"ibkr {ibkr} alpaca {alpaca_tage}")
if __name__ == "__main__":
if sys.platform == "win32":
windows_faelle()
ibkr_windows_faelle()
else:
wsl_faelle()
ibkr_wsl_faelle()
print(f"ERGEBNIS: {len(abweichungen)} Abweichung(en)")
sys.exit(1 if abweichungen else 0)