- .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]>
137 lines
5.5 KiB
Python
137 lines
5.5 KiB
Python
"""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())
|