"""WSL-Teil von ha_totmann.py: Thema, Token und Handelskalender, nur ueber die Pipe (Paket Z2). Laeuft in WSL mit dem Python der Trading Plattform (~/venvs/trading; gebraucht wird nur die Standardbibliothek). Die Geheimnisdatei der Plattform liegt nur hier. Dieses Skript liest daraus [ntfy] thema und token und [alpaca] key_id und secret_key, fragt den Kalender von Alpaca (GET /v2/calendar, lesend, ohne Proxy aus der Umgebung) und schreibt genau eine JSON-Zeile auf stdout: thema, token, kalender. Gestartet wird es nur von ha_totmann.py unter Windows, das stdout als Pipe liest und davon nie etwas ausgibt. Von Hand gestartet stuende das Thema auf der Konsole -- deshalb gibt es keinen Aufruf dafuer in LIESMICH.md. stderr traegt nur Befunde ohne Werte: Feldnamen und Laengen. ha_totmann.py importiert diese Datei auch unter Windows, fuer die Regeln (zugang, pruefe_kalender, ausgabe) und fuer seine Aktion trocken; beim Import wird nichts gelesen. Aufruf (nur durch ha_totmann.py): python ha_totmann_wsl.py [--geheimnisse PFAD] [--tage 400] Exit: 0 fertig, 2 Ausnahme, 3 Voraussetzung fehlt. """ import argparse import json import re import sys import tomllib import urllib.parse import urllib.request from datetime import date from datetime import timedelta from pathlib import Path GEHEIMNISSE = "/home/claude/.config/trading-plattform/secrets.toml" ALPACA_KALENDER = "https://paper-api.alpaca.markets/v2/calendar" ZEITLIMIT_S = 30 THEMA_MUSTER = re.compile(r"^[A-Za-z0-9_-]{1,64}$") TOKEN_MUSTER = re.compile(r"^[A-Za-z0-9_-]{1,128}$") KALENDER_FELDER = ( ("date", re.compile(r"^\d{4}-\d{2}-\d{2}$")), ("open", re.compile(r"^\d{2}:\d{2}$")), ("close", re.compile(r"^\d{2}:\d{2}$")), ) class Befund(Exception): """Eine Voraussetzung fehlt. Der Text nennt Feldnamen und Laengen, nie einen Wert.""" def zugang(geheim: dict) -> tuple[str, str]: """Thema und Token aus dem Abschnitt [ntfy]; der Token darf fehlen, das Thema nicht.""" ntfy = geheim.get("ntfy") or {} if not isinstance(ntfy, dict): raise Befund("[ntfy] ist kein Abschnitt") thema = str(ntfy.get("thema") or "").strip() token = str(ntfy.get("token") or "").strip() if not THEMA_MUSTER.match(thema): raise Befund(f"[ntfy] thema fehlt oder passt nicht zur Regel (Laenge {len(thema)})") if token and not TOKEN_MUSTER.match(token): raise Befund(f"[ntfy] token passt nicht zur Regel (Laenge {len(token)})") return thema, token def pruefe_kalender(daten: object) -> list[dict]: """Je Handelstag date, open und close; jede andere Form ist ein Befund.""" if not isinstance(daten, list): raise Befund("Kalender: Antwort ist keine Liste") tage = [] for eintrag in daten: if not isinstance(eintrag, dict): raise Befund("Kalender: ein Eintrag ist keine Tabelle") for feld, muster in KALENDER_FELDER: wert = eintrag.get(feld) if not isinstance(wert, str) or not muster.match(wert): raise Befund(f"Kalender: Feld {feld} nicht lesbar") tage.append({feld: eintrag[feld] for feld, _muster in KALENDER_FELDER}) return tage def ausgabe(thema: str, token: str, kalender: list[dict]) -> str: """Die eine Zeile fuer die Pipe. ASCII, damit kein Zeilentrenner darin stehen kann.""" return json.dumps( {"thema": thema, "token": token, "kalender": kalender}, ensure_ascii=True, separators=(",", ":"), ) def kalender_holen(alpaca: dict, tage: int) -> list[dict]: """GET /v2/calendar von heute bis heute plus tage, lesend, ohne Proxy aus der Umgebung.""" key_id = str(alpaca.get("key_id") or "") secret_key = str(alpaca.get("secret_key") or "") if not key_id or not secret_key: raise Befund("[alpaca] key_id oder secret_key fehlt") heute = date.today() parameter = urllib.parse.urlencode( {"start": heute.isoformat(), "end": (heute + timedelta(days=tage)).isoformat()} ) anfrage = urllib.request.Request( f"{ALPACA_KALENDER}?{parameter}", headers={"APCA-API-KEY-ID": key_id, "APCA-API-SECRET-KEY": secret_key}, ) oeffner = urllib.request.build_opener(urllib.request.ProxyHandler({})) with oeffner.open(anfrage, timeout=ZEITLIMIT_S) as antwort: return pruefe_kalender(json.loads(antwort.read().decode("utf-8"))) def main(argv=None) -> int: zerleger = argparse.ArgumentParser( description="WSL-Teil von ha_totmann.py; nur ueber die Pipe benutzen.", allow_abbrev=False, ) zerleger.add_argument("--geheimnisse", default=GEHEIMNISSE) zerleger.add_argument("--tage", type=int, default=400) argumente = zerleger.parse_args(argv) try: pfad = Path(argumente.geheimnisse) if not pfad.is_file(): raise Befund("die Geheimnisdatei liegt nicht am erwarteten Ort") geheim = tomllib.loads(pfad.read_text(encoding="utf-8")) thema, token = zugang(geheim) alpaca = geheim.get("alpaca") or {} if not isinstance(alpaca, dict): raise Befund("[alpaca] ist kein Abschnitt") kalender = kalender_holen(alpaca, argumente.tage) except Befund as befund: print(f"BEFUND {befund}", file=sys.stderr) return 3 except Exception as ausnahme: # Nur der Typ: Eine Ausnahme kann eine Adresse oder einen Kopf mit Schluessel tragen. print(f"ABBRUCH: {type(ausnahme).__name__}", file=sys.stderr) return 2 print(ausgabe(thema, token, kalender)) return 0 if __name__ == "__main__": sys.exit(main())