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:
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())
|
||||
Reference in new issue
Block a user