- .vault-tools/plan-kopie/LIESMICH.md - .vault-tools/plan-kopie/rot_nachweis.py - .vault-tools/plan-kopie/welle_vergleich.py - .vault-tools/plan-kopie/welle_vergleich_treiber.py - .vault-tools/trading-plattform-pruefung/LIESMICH.md - .vault-tools/trading-plattform-pruefung/angriffe.py - .vault-tools/trading-plattform-pruefung/browserlauf.py - .vault-tools/trading-plattform-pruefung/browserlauf_zugang.py - .vault-tools/trading-plattform-pruefung/c_eigenlauf.py - .vault-tools/trading-plattform-pruefung/d_eigenlauf.py - .vault-tools/trading-plattform-pruefung/handelstag_auswertung.py - .vault-tools/trading-plattform-pruefung/ntfy_probe_server.py - … und 7 weitere Erzeugt vom PostToolUse-Hook .vault-tools/auto-push/auto_push.py. Co-Authored-By: Claude Opus 5 <[email protected]>
116 lines
5.6 KiB
Python
116 lines
5.6 KiB
Python
"""Vorpruefung vor dem Start des Paper-Runners: liest nur, startet nichts, aendert nichts.
|
|
|
|
Koordinator-Werkzeug zu Teil 10, nicht fuers Repo. Fragen und Herkunft der Erwartung:
|
|
1. Laeuft schon ein Runner? Projektvorgabe (Doppelstart endet mit Exit 2; hier vorher sichtbar).
|
|
2. Wie steht der Killswitch? Nur Auskunft: Der Runner-Start stellt nach der Neustartregel immer
|
|
auf angehalten. Ein Befund ist allein 'gesperrt'. Gelesen aus
|
|
data/registry.db, nur lesend.
|
|
3. Ist das Paper-Konto glatt? Projektvorgabe (der Tag beginnt ohne Position und ohne offene Order).
|
|
4. Was sagt die Marktuhr des Brokers? Quelle: GET /v2/clock (is_open, next_open, next_close).
|
|
5. Wie weit liegt die eigene Uhr daneben? Quelle: Date-Kopf der Broker-Antwort gegen die eigene Uhr;
|
|
mehr als 30 Sekunden waeren fuer TOTP und Minutentakt ein Problem.
|
|
Was es nicht sieht: ob das Dashboard laeuft (das fragt der Koordinator unter Windows ab), ob der
|
|
IEX-Feed frisch liefert (dafuer handabruf_kerzen.py), und den Code-Stand (dafuer git im Projektordner).
|
|
Eine fehlende Voraussetzung ist ein Befund (Exit 3), jede Abweichung ein Befund (Exit 2).
|
|
|
|
Liest config/secrets.toml aus dem Projektordner und gibt die Schluessel nie aus.
|
|
Aufruf aus dem Projektordner (WSL, Venv): python vorstart_pruefung.py
|
|
"""
|
|
|
|
import sqlite3
|
|
import subprocess
|
|
import sys
|
|
import tomllib
|
|
from datetime import UTC, datetime
|
|
from email.utils import parsedate_to_datetime
|
|
from pathlib import Path
|
|
|
|
import requests
|
|
|
|
HANDEL_URL = "https://paper-api.alpaca.markets"
|
|
|
|
|
|
def main() -> int:
|
|
befunde: list[str] = []
|
|
|
|
laeufer = subprocess.run(["pgrep", "-af", "scripts/paper.py"], capture_output=True, text=True).stdout.strip()
|
|
eigene = [z for z in laeufer.splitlines() if "pgrep" not in z]
|
|
print(f"1. laufende runner: {len(eigene)}")
|
|
for zeile in eigene:
|
|
print(" ", zeile[:140])
|
|
if eigene:
|
|
befunde.append("es laeuft schon ein Runner")
|
|
|
|
db = Path("data/registry.db")
|
|
if not db.is_file():
|
|
print("VORAUSSETZUNG FEHLT: data/registry.db (aus dem Projektordner starten)")
|
|
return 3
|
|
verbindung = sqlite3.connect(f"file:{db.as_posix()}?mode=ro", uri=True)
|
|
try:
|
|
zeile = verbindung.execute("select zustand, seit, ausloeser from killswitch_zustand").fetchone()
|
|
finally:
|
|
verbindung.close()
|
|
# Kein Befund, gleich wie er steht: Der Runner-Start stellt nach der Neustartregel immer auf
|
|
# angehalten (Registerereignis 'neustart'). Ein 'aktiv' VOR dem Start heisst nur, dass ein
|
|
# frueheres Fortsetzen ueberschrieben wird -- der Klick muss NACH dem Start kommen.
|
|
print(f"2. killswitch im register: {zeile}")
|
|
if zeile and zeile[0] == "aktiv":
|
|
print(" HINWEIS: steht auf aktiv; der Runner-Start ueberschreibt das mit angehalten, Fortsetzen erst danach")
|
|
if zeile and zeile[0] == "gesperrt":
|
|
befunde.append("Killswitch ist gesperrt; Fortsetzen allein genuegt dann nicht (Freigeben im Dashboard)")
|
|
|
|
secrets = Path("config/secrets.toml")
|
|
if not secrets.is_file():
|
|
print("VORAUSSETZUNG FEHLT: config/secrets.toml")
|
|
return 3
|
|
werte = tomllib.loads(secrets.read_text(encoding="utf-8"))["alpaca"]
|
|
kopf = {"APCA-API-KEY-ID": werte["key_id"], "APCA-API-SECRET-KEY": werte["secret_key"]}
|
|
|
|
positionen = requests.get(f"{HANDEL_URL}/v2/positions", headers=kopf, timeout=20)
|
|
offene = requests.get(f"{HANDEL_URL}/v2/orders", headers=kopf, params={"status": "open", "limit": 100}, timeout=20)
|
|
if positionen.status_code != 200 or offene.status_code != 200:
|
|
print(f"VORAUSSETZUNG FEHLT: Broker antwortet {positionen.status_code}/{offene.status_code}")
|
|
return 3
|
|
print(f"3. paper-konto: {len(positionen.json())} position(en), {len(offene.json())} offene order(s)")
|
|
for p in positionen.json():
|
|
print(f" position {p.get('symbol')} qty {p.get('qty')} side {p.get('side')}")
|
|
for o in offene.json():
|
|
print(f" order {o.get('side')} {o.get('qty')} {o.get('symbol')} status {o.get('status')}")
|
|
if positionen.json() or offene.json():
|
|
befunde.append("das Paper-Konto ist nicht glatt")
|
|
|
|
vorher = datetime.now(UTC)
|
|
uhr = requests.get(f"{HANDEL_URL}/v2/clock", headers=kopf, timeout=20)
|
|
nachher = datetime.now(UTC)
|
|
if uhr.status_code != 200:
|
|
print(f"VORAUSSETZUNG FEHLT: /v2/clock antwortet {uhr.status_code}")
|
|
return 3
|
|
daten = uhr.json()
|
|
print(f"4. marktuhr: is_open {daten.get('is_open')}, next_open {daten.get('next_open')}, next_close {daten.get('next_close')}")
|
|
|
|
datum = uhr.headers.get("Date")
|
|
if datum:
|
|
server = parsedate_to_datetime(datum)
|
|
mitte = vorher + (nachher - vorher) / 2
|
|
abstand = (mitte - server).total_seconds()
|
|
# Der Date-Kopf hat Sekundenaufloesung; unter zwei Sekunden ist deshalb nichts zu sagen.
|
|
print(f"5. eigene uhr gegen den Date-Kopf des Brokers: {abstand:+.1f} s (Aufloesung eine Sekunde)")
|
|
if abs(abstand) > 30:
|
|
befunde.append(f"die eigene Uhr liegt {abstand:+.0f} s neben der des Brokers")
|
|
else:
|
|
print("5. kein Date-Kopf in der Antwort; Uhrvergleich entfaellt")
|
|
|
|
print()
|
|
for befund in befunde:
|
|
print("BEFUND:", befund)
|
|
print(f"{len(befunde)} Befund(e).")
|
|
return 2 if befunde else 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
sys.exit(main())
|
|
except Exception as ausnahme: # noqa: BLE001 - ein Absturz des Werkzeugs ist selbst ein Befund
|
|
print(f"BEFUND: das Werkzeug ist abgebrochen: {ausnahme!r}")
|
|
sys.exit(2)
|