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,131 @@
|
||||
"""Rechnet Reaktionszeiten aus dem, was ein Lauf des Paper-Runners ohnehin hinterlaesst -- nur lesend.
|
||||
|
||||
Koordinator-Werkzeug fuer Tage, an denen der Runner seine Latenzen noch nicht selbst misst (vor Paket L).
|
||||
Quellen: das JSONL-Protokoll des Laufs (jede Zeile traegt echte Zeit, auch in der Generalprobe) und die
|
||||
Tabelle killswitch_ereignisse im Register (Zeitpunkt jedes Klicks). Das Register wird mit mode=ro geoeffnet.
|
||||
|
||||
Gerechnet wird:
|
||||
Klick -> Runner reagiert Registerereignis (Ausloeser pascal, erfolgreich) bis zur ersten Taktzeile
|
||||
danach, die den neuen Zustand traegt. Die Taktzeile entsteht am ENDE des
|
||||
Takts -- der Wert ist eine obere Schranke fuer "gelesen" und sagt, ab wann
|
||||
der neue Zustand im Protokoll wirkt.
|
||||
Kerze -> Taktende nur im Modus paper: Taktzeile mit frischer Kerze und Aktion, Zeit der Zeile
|
||||
minus (juengste Kerze + 60 s). Obere Schranke fuer Kerze -> Entscheidung,
|
||||
weil die Zeile erst nach den Orders geschrieben wird. In der Generalprobe
|
||||
sinnlos (Kerzen von einem anderen Tag) und deshalb ausgelassen.
|
||||
Taktabstand Abstand aufeinanderfolgender Taktzeilen -- der Rahmen fuer alles andere.
|
||||
|
||||
Nicht gerechnet: Fuellzeit (dafuer handelstag_auswertung.py --broker) und Meldungen (ntfy_lesen.py).
|
||||
Gibt nie einen Kontowert aus.
|
||||
|
||||
Aufruf (WSL, aus dem Projektordner):
|
||||
python latenz_auswertung.py <protokoll.jsonl> [<register.db>]
|
||||
"""
|
||||
|
||||
import json
|
||||
import sqlite3
|
||||
import statistics
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
from pathlib import Path
|
||||
|
||||
NEUER_ZUSTAND = {"anhalten": "angehalten", "glattstellen": "gesperrt", "fortsetzen": "aktiv", "freigeben": "aktiv"}
|
||||
KLICKER = "pascal"
|
||||
|
||||
|
||||
def zeit(text: str) -> datetime:
|
||||
return datetime.fromisoformat(text)
|
||||
|
||||
|
||||
def kennzahlen(name: str, werte: list[float], einheit: str = "s") -> None:
|
||||
if not werte:
|
||||
print(f" {name}: keine Messung")
|
||||
return
|
||||
sortiert = sorted(werte)
|
||||
p90 = sortiert[min(len(sortiert) - 1, int(round(0.9 * (len(sortiert) - 1))))]
|
||||
print(
|
||||
f" {name}: n={len(werte)} Median {statistics.median(werte):.1f} {einheit} "
|
||||
f"Mittel {statistics.fmean(werte):.1f} p90 {p90:.1f} Min {sortiert[0]:.1f} Max {sortiert[-1]:.1f}"
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
protokoll = Path(sys.argv[1])
|
||||
register = Path(sys.argv[2]) if len(sys.argv) > 2 else Path("data/registry.db")
|
||||
|
||||
zeilen = []
|
||||
with open(protokoll, encoding="utf-8") as strom: # Dateiiteration, nie splitlines (U+2028)
|
||||
for roh in strom:
|
||||
if roh.strip():
|
||||
zeilen.append(json.loads(roh))
|
||||
kopf = next((z for z in zeilen if z.get("art") == "kopf"), {})
|
||||
takte = [z for z in zeilen if z.get("art") == "takt"]
|
||||
modus = kopf.get("modus", "unbekannt")
|
||||
if not takte:
|
||||
print("Keine Taktzeile im Protokoll.")
|
||||
return 1
|
||||
start, ende = zeit(zeilen[0]["zeit"]), zeit(zeilen[-1]["zeit"])
|
||||
print(f"Protokoll {protokoll.name}: Modus {modus}, {len(takte)} Takte, {start:%H:%M:%S} bis {ende:%H:%M:%S} UTC")
|
||||
|
||||
abstaende = [
|
||||
(zeit(b["zeit"]) - zeit(a["zeit"])).total_seconds() for a, b in zip(takte, takte[1:])
|
||||
]
|
||||
print("Rahmen:")
|
||||
kennzahlen("Taktabstand", abstaende)
|
||||
|
||||
print("Klick -> Runner reagiert (Register gegen erste Taktzeile mit dem neuen Zustand):")
|
||||
if not register.exists():
|
||||
print(f" Register {register} fehlt -- ausgelassen")
|
||||
else:
|
||||
verbindung = sqlite3.connect(f"file:{register.as_posix()}?mode=ro", uri=True)
|
||||
try:
|
||||
ereignisse = verbindung.execute(
|
||||
"SELECT zeit, stufe, ausloeser, erfolgreich FROM killswitch_ereignisse ORDER BY id"
|
||||
).fetchall()
|
||||
finally:
|
||||
verbindung.close()
|
||||
reaktionen = []
|
||||
for ereignis_zeit, stufe, ausloeser, erfolgreich in ereignisse:
|
||||
wann = zeit(ereignis_zeit)
|
||||
if not (start <= wann <= ende):
|
||||
continue
|
||||
if ausloeser != KLICKER:
|
||||
print(f" {wann:%H:%M:%S} {stufe} von {ausloeser}: kein Klick, ausgelassen")
|
||||
continue
|
||||
if not erfolgreich:
|
||||
print(f" {wann:%H:%M:%S} {stufe}: nicht erfolgreich, ausgelassen")
|
||||
continue
|
||||
neu = NEUER_ZUSTAND.get(stufe)
|
||||
treffer = next(
|
||||
(t for t in takte if zeit(t["zeit"]) > wann and t.get("killswitch") == neu), None
|
||||
)
|
||||
if treffer is None:
|
||||
print(f" {wann:%H:%M:%S} {stufe}: keine Taktzeile mit Zustand {neu} danach")
|
||||
continue
|
||||
sekunden = (zeit(treffer["zeit"]) - wann).total_seconds()
|
||||
reaktionen.append(sekunden)
|
||||
print(f" {wann:%H:%M:%S} {stufe:<13} -> Takt {treffer.get('schritt')} ({treffer.get('phase')}) nach {sekunden:5.1f} s")
|
||||
kennzahlen("Klick -> Reaktion", reaktionen)
|
||||
|
||||
if modus == "paper":
|
||||
print("Kerze -> Taktende (obere Schranke fuer Kerze -> Entscheidung):")
|
||||
werte = []
|
||||
for takt in takte:
|
||||
if takt.get("frische_kerze") and takt.get("aktion") is not None and takt.get("letzte_kerze"):
|
||||
schluss = zeit(takt["letzte_kerze"]) + timedelta(seconds=60)
|
||||
werte.append((zeit(takt["zeit"]) - schluss).total_seconds())
|
||||
kennzahlen("Kerze -> Taktende", werte)
|
||||
ohne = sum(1 for takt in takte if takt.get("markt_offen") and not takt.get("frische_kerze"))
|
||||
print(f" Takte bei offenem Markt ohne frische Kerze: {ohne} von {len(takte)}")
|
||||
else:
|
||||
print("Kerze -> Entscheidung: im Modus", modus, "nicht gerechnet (Kerzen eines anderen Tages).")
|
||||
|
||||
orders = [z for z in zeilen if z.get("art") == "order"]
|
||||
meldefehler = [z for z in zeilen if z.get("art") == "ereignis" and z.get("was") == "benachrichtigung_fehlgeschlagen"]
|
||||
print(f"Dazu: {len(orders)} Orderzeile(n), {len(meldefehler)} fehlgeschlagene Meldung(en).")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user