Automatischer Push: 14 Datei(en) geändert

- .vault-tools/trading-plattform-pruefung/LIESMICH.md
- .vault-tools/trading-plattform-pruefung/bremse_echtlauf.py
- .vault-tools/trading-plattform-pruefung/browser_w.py
- .vault-tools/trading-plattform-pruefung/generalprobe_echtlauf.py
- .vault-tools/trading-plattform-pruefung/ha_aktiv_setzen.py
- .vault-tools/trading-plattform-pruefung/handelstag_auswertung.py
- .vault-tools/trading-plattform-pruefung/hausmelder_echtlauf.py
- .vault-tools/trading-plattform-pruefung/killswitch_simulation.py
- .vault-tools/trading-plattform-pruefung/killswitch_simulation_mutanten.py
- .vault-tools/trading-plattform-pruefung/latenz_auswertung.py
- .vault-tools/trading-plattform-pruefung/s1_alpaca_lesen.py
- .vault-tools/trading-plattform-pruefung/s5_echtlauf.py
- … und 2 weitere

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-22 04:00:01 +02:00
1 parent 246af19683
commit 593acdd122
14 files changed
+2090 -5

No files matched your search

@@ -0,0 +1,224 @@
"""Eigener Lauf des Hausmelders gegen einen mitschreibenden Server auf 127.0.0.1 -- und wahlweise ein POST ans echte HA.
Koordinator-Pruefcode, nicht Teil des Repos. Teil 1 (immer): echter Hausmelder-Faden, echter requests-POST,
lokaler HTTP-Server, gestellte Statusdatei, Wegwerf-Register. Teil 2 (nur mit --echt <secrets.toml>): ein
einziger pruefe()-Durchlauf mit unterscheidbaren Werten an die Adresse aus der Geheimnisdatei; die Adresse
wird nie ausgegeben. Rueckgelesen wird danach unter Windows mit ha_einrichten.py.
Aufruf (WSL): python hausmelder_echtlauf.py <code-baum> <wegwerf-ordner> [--echt <secrets.toml>]
"""
import json
import os
import sys
import threading
import time
from datetime import UTC
from datetime import datetime
from http.server import BaseHTTPRequestHandler
from http.server import ThreadingHTTPServer
from pathlib import Path
CODE = Path(sys.argv[1]).resolve()
WEG = Path(sys.argv[2]).resolve()
ECHT_SECRETS = Path(sys.argv[4]).resolve() if len(sys.argv) > 4 and sys.argv[3] == "--echt" else None
os.chdir(CODE)
sys.path.insert(0, str(CODE))
from trading.dashboard import hausmelder as modul # noqa: E402
from trading.dashboard.hausmelder import Hausmelder # noqa: E402
from trading.dashboard.hausmelder import killswitch_leser # noqa: E402
from trading.dashboard.hausmelder import lies_hausmelder_config # noqa: E402
from trading.dashboard.hausmelder import starte_hausmelder # noqa: E402
from trading.paper.status import schreibe_status # noqa: E402
from trading.registry.registry import Registry # noqa: E402
EMPFANGEN = []
ANTWORT = {"code": 200, "location": None}
class Mitschreiber(BaseHTTPRequestHandler):
def do_POST(self):
laenge = int(self.headers.get("Content-Length", "0"))
rumpf = self.rfile.read(laenge)
EMPFANGEN.append({"pfad": self.path, "typ": self.headers.get("Content-Type"),
"daten": json.loads(rumpf.decode("utf-8")), "antwort": ANTWORT["code"]})
self.send_response(ANTWORT["code"])
if ANTWORT["location"]:
self.send_header("Location", ANTWORT["location"])
self.send_header("Content-Length", "0")
self.end_headers()
def do_GET(self):
EMPFANGEN.append({"pfad": self.path, "typ": "GET", "daten": None, "antwort": 200})
self.send_response(200)
self.send_header("Content-Length", "0")
self.end_headers()
def log_message(self, *a):
pass
def status(pfad, **felder):
grund = {"zeit": datetime.now(UTC).isoformat(), "modus": "paper", "phase": "handelt", "position": 0,
"kontowert": 100000.0, "startkapital": 100000.0}
grund.update(felder)
schreibe_status(pfad, grund)
def warte_bis(bedingung, sekunden=5.0):
ende = time.monotonic() + sekunden
while time.monotonic() < ende:
if bedingung():
return True
time.sleep(0.05)
return False
def main():
WEG.mkdir(parents=True, exist_ok=False)
befunde = []
server = ThreadingHTTPServer(("127.0.0.1", 0), Mitschreiber)
port = server.server_address[1]
threading.Thread(target=server.serve_forever, daemon=True).start()
kennung = "nachtlauf-" + "a" * 40
url = f"http://127.0.0.1:{port}/api/webhook/{kennung}"
registry = Registry(db_pfad=WEG / "registry.db", artefakt_wurzel=WEG / "artifacts")
registry.setze_killswitch_zustand("angehalten", ausloeser="neustart")
statusdatei = WEG / "paper" / "status.json"
statusdatei.parent.mkdir(parents=True)
# Teil 1a: der Faden selbst, mit kurzem Takt.
melder = Hausmelder(webhook_url=url, status_datei=statusdatei, killswitch=killswitch_leser(registry),
takt_s=0.1, lebenszeichen_s=1.5)
melder.starte()
if not warte_bis(lambda: len(EMPFANGEN) == 1):
befunde.append("kein erster POST ohne Statusdatei")
erster = EMPFANGEN[0]["daten"] if EMPFANGEN else {}
print("1. ohne Statusdatei:", json.dumps(erster, ensure_ascii=False))
if erster.get("phase") != "kein_status" or erster.get("killswitch") != "angehalten" or erster.get("herzschlag_alter_s") != -1:
befunde.append("Rueckfall ohne Statusdatei stimmt nicht")
if EMPFANGEN and (EMPFANGEN[0]["pfad"] != f"/api/webhook/{kennung}" or EMPFANGEN[0]["typ"] != "application/json"):
befunde.append("Pfad oder Content-Type falsch")
status(statusdatei, phase="handelt", position=20, kontowert=100012.345)
warte_bis(lambda: len(EMPFANGEN) == 2)
zweiter = EMPFANGEN[-1]["daten"]
print("2. nach Statusaenderung:", json.dumps(zweiter, ensure_ascii=False))
if zweiter.get("position") != 20 or zweiter.get("tagesergebnis_usd") != 12.35 or zweiter.get("phase") != "handelt":
befunde.append("Statuswerte kommen nicht an")
# Ohne Aenderung: In 0,8 s (acht Takte) darf nichts kommen; der Herzschlag wird dabei aelter.
stand = len(EMPFANGEN)
time.sleep(0.8)
if len(EMPFANGEN) != stand:
befunde.append(f"ohne Aenderung kamen {len(EMPFANGEN) - stand} POST(s)")
# Lebenszeichen nach 1,5 s.
if not warte_bis(lambda: len(EMPFANGEN) == stand + 1, 3.0):
befunde.append("kein Lebenszeichen")
print(f"3. ohne Aenderung still, Lebenszeichen nach der Frist: {len(EMPFANGEN)} POSTs bisher")
# Killswitch aus dem Register, ohne dass die Statusdatei sich aendert.
registry.setze_killswitch_zustand("gesperrt", ausloeser="pascal")
stand = len(EMPFANGEN)
warte_bis(lambda: len(EMPFANGEN) == stand + 1)
print("4. Killswitch:", EMPFANGEN[-1]["daten"].get("killswitch"))
if EMPFANGEN[-1]["daten"].get("killswitch") != "gesperrt":
befunde.append("Killswitch-Wechsel kommt nicht an")
# Fehlschlag: 500 kippt den Merker nicht, es wird wiederholt; danach 200.
ANTWORT["code"] = 500
status(statusdatei, phase="pausiert", position=0, kontowert=99990.0)
stand = len(EMPFANGEN)
warte_bis(lambda: len(EMPFANGEN) >= stand + 3)
wiederholt = len(EMPFANGEN) - stand
ANTWORT["code"] = 200
warte_bis(lambda: EMPFANGEN[-1]["antwort"] == 200)
stand = len(EMPFANGEN)
time.sleep(0.6)
print(f"5. bei HTTP 500 wiederholt: {wiederholt} Versuche; nach 200 still: {len(EMPFANGEN) - stand} weitere; "
f"Fehlschlaege: {sorted(set(melder.fehlschlaege))}")
if wiederholt < 3 or len(EMPFANGEN) != stand:
befunde.append("Wiederholung nach Fehlschlag stimmt nicht")
if EMPFANGEN[-1]["daten"].get("tagesergebnis_usd") != -10.0:
befunde.append("negatives Tagesergebnis stimmt nicht")
# Weiterleitung: wird nicht gefolgt (kein GET am Server), gilt als Fehlschlag.
ANTWORT["code"] = 302
ANTWORT["location"] = f"http://127.0.0.1:{port}/woanders"
status(statusdatei, phase="handelt", position=10)
stand = len(EMPFANGEN)
warte_bis(lambda: len(EMPFANGEN) >= stand + 2)
gets = [e for e in EMPFANGEN if e["typ"] == "GET" or e["pfad"] == "/woanders"]
ANTWORT["code"] = 200
ANTWORT["location"] = None
warte_bis(lambda: EMPFANGEN[-1]["antwort"] == 200)
print(f"6. Weiterleitung: gefolgt {len(gets)}-mal; Fehlschlaege: {sorted(set(melder.fehlschlaege))}")
if gets:
befunde.append("einer Weiterleitung wurde gefolgt")
# Veraltet: Herzschlag 200 s alt, Phase nicht beendet.
status(statusdatei, phase="handelt", position=10,
zeit=datetime.fromtimestamp(time.time() - 200, UTC).isoformat())
stand = len(EMPFANGEN)
warte_bis(lambda: len(EMPFANGEN) >= stand + 1)
print("7. veraltet:", EMPFANGEN[-1]["daten"].get("veraltet"), EMPFANGEN[-1]["daten"].get("herzschlag_alter_s"))
if EMPFANGEN[-1]["daten"].get("veraltet") is not True:
befunde.append("veraltet wird nicht gemeldet")
melder.stoppe()
if melder.laeuft:
befunde.append("Faden laeuft nach stoppe() weiter")
geheim = [text for text in melder.fehlschlaege if kennung in text or "127.0.0.1" in text]
if geheim or kennung in repr(melder):
befunde.append("Adresse in Fehlschlag oder repr")
schluessel = sorted(EMPFANGEN[-1]["daten"])
print("8. Schluessel der Nutzlast:", schluessel)
if schluessel != sorted(["phase", "modus", "killswitch", "position", "tagesergebnis_usd",
"herzschlag_alter_s", "veraltet", "gesendet"]):
befunde.append("Nutzlast traegt andere Schluessel")
# Teil 1b: Verdrahtung ueber die Geheimnisdatei.
secrets = WEG / "secrets.toml"
secrets.write_text(f'[homeassistant]\nwebhook_url = "{url}"\naktiv = true\n', encoding="utf-8")
stand = len(EMPFANGEN)
zweiter_melder = starte_hausmelder(status_datei=statusdatei, killswitch=killswitch_leser(registry),
secrets_pfad=secrets)
ok = zweiter_melder is not None and warte_bis(lambda: len(EMPFANGEN) > stand)
if zweiter_melder is not None:
zweiter_melder.stoppe()
print(f"9. starte_hausmelder mit aktiv = true: Faden {'lief und hat gesendet' if ok else 'FEHLT'}")
if not ok:
befunde.append("starte_hausmelder sendet nicht")
secrets.write_text(f'[homeassistant]\nwebhook_url = "{url}"\naktiv = false\n', encoding="utf-8")
if starte_hausmelder(status_datei=statusdatei, killswitch=killswitch_leser(registry), secrets_pfad=secrets) is not None:
befunde.append("aktiv = false startet einen Faden")
secrets.write_text('[homeassistant]\nwebhook_url = "ftp://falsch"\naktiv = true\n', encoding="utf-8")
if starte_hausmelder(status_datei=statusdatei, killswitch=killswitch_leser(registry), secrets_pfad=secrets) is not None:
befunde.append("eine falsche Adresse startet einen Faden")
server.shutdown()
# Teil 2: ein POST ans echte HA, mit Werten, die sich von der neutralen Lage unterscheiden.
if ECHT_SECRETS is not None:
config = lies_hausmelder_config(ECHT_SECRETS)
if config is None or not config.webhook_url:
befunde.append("echte Geheimnisdatei ohne [homeassistant]")
else:
status(statusdatei, modus="generalprobe", phase="handelt", position=7, kontowert=100004.56)
registry.setze_killswitch_zustand("aktiv", ausloeser="pascal")
echter = Hausmelder(webhook_url=config.webhook_url, status_datei=statusdatei,
killswitch=killswitch_leser(registry))
echter.pruefe()
print(f"10. echtes HA: zugestellt={echter._zuletzt is not None}, Fehlschlaege={echter.fehlschlaege}, "
f"Laenge der Adresse {len(config.webhook_url)}")
if echter._zuletzt is None:
befunde.append("echtes HA hat nicht mit 200 geantwortet")
print(f"{len(befunde)} Befund(e): {befunde}")
return 2 if befunde else 0
if __name__ == "__main__":
sys.exit(main())