- .claude/agents/executer.md - .claude/agents/planer.md - .claude/agents/pruefer.md - .claude/agents/recherche.md - .claude/skills/plan-executer-pruefung/SKILL.md - .claude/workflows/executer-welle.js - .claude/workflows/plankritik.js - .claude/workflows/pruefung.js - .claude/workflows/welle-mit-pruefung.js - .gitignore - .vault-tools/regelwerk-check/ENTWURF.md - .vault-tools/regelwerk-check/regelwerk_check.py - … und 20 weitere Erzeugt vom PostToolUse-Hook .vault-tools/auto-push/auto_push.py. Co-Authored-By: Claude Opus 5 <[email protected]>
183 lines
8.1 KiB
Python
183 lines
8.1 KiB
Python
"""Lesender Probeaufruf fuer Paket M: GET /v2/assets, trades/latest und bars mit mehreren Symbolen.
|
|
|
|
Koordinator-Werkzeug, nicht Teil des Repos. Anlass: Paket M (zehn Aktien, 2026-09-25) haengt an drei
|
|
Antwortformen, die im Projekt nie gemessen wurden -- GET /v2/assets/{symbol}, GET /v2/stocks/trades/latest
|
|
mit mehreren Symbolen und das Seitenverhalten von GET /v2/stocks/bars mit mehreren Symbolen. Das Werkzeug
|
|
belegt sie an genau dem Code, der sie liest (AlpacaBroker.anlage, AlpacaBroker.letzte_kurse,
|
|
AlpacaKerzenquelle.kerzen_mehrerer), und zusaetzlich roh: Schluessel und Typen, nie Werte ausser den
|
|
Wahrheitswerten der Startpruefung, Zaehlungen und dem Alter des letzten Trades.
|
|
Nur GET, kein schreibender Aufruf. Liest die Geheimnisdatei ueber den Aufloeser des Projekts
|
|
(TRADING_GEHEIMNISSE, sonst config/secrets.toml) und gibt die Schluessel nie aus.
|
|
Aufruf (WSL, aus einem Arbeitsbaum mit Paket M, Venv):
|
|
python m_probeaufruf.py <handelstag JJJJ-MM-TT> <kennung> [<kennung> ...]
|
|
Exit 0: alle Formen wie angenommen. 2: eine Abweichung oder ein Starthindernis. 1: Ausnahme.
|
|
"""
|
|
|
|
import sys
|
|
import tomllib
|
|
from datetime import UTC
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
import requests
|
|
|
|
sys.path.insert(0, str(Path.cwd()))
|
|
|
|
from trading import geheimnisse # noqa: E402
|
|
from trading.broker.alpaca import DATEN_URL # noqa: E402
|
|
from trading.broker.alpaca import TRADING_URL # noqa: E402
|
|
from trading.broker.alpaca import AlpacaBroker # noqa: E402
|
|
from trading.broker.schnittstelle import zu_symbol # noqa: E402
|
|
from trading.paper.kerzen import HANDELSZONE # noqa: E402
|
|
from trading.paper.kerzen import AlpacaKerzenquelle # noqa: E402
|
|
from trading.paper.kerzen import handelsbeginn # noqa: E402
|
|
from trading.paper.kerzen import handelsende # noqa: E402
|
|
|
|
FEED = "iex"
|
|
# Klein genug, dass zehn Symbole mal 390 Minuten mehrere Seiten brauchen.
|
|
KLEINE_SEITE = 1000
|
|
MAX_SEITEN = 50
|
|
|
|
|
|
def typ(wert) -> str:
|
|
return type(wert).__name__
|
|
|
|
|
|
def iso_z(zeitpunkt: datetime) -> str:
|
|
return zeitpunkt.astimezone(UTC).isoformat().replace("+00:00", "Z")
|
|
|
|
|
|
def zaehle_seiten(sitzung, kopf: dict, parameter: dict) -> tuple[dict[str, int], int, int]:
|
|
"""Kerzen je Symbol ueber alle Seiten, Zahl der Seiten, Seiten mit mehr als einem Symbol."""
|
|
parameter = dict(parameter)
|
|
je_symbol: dict[str, int] = {}
|
|
seiten = 0
|
|
gemischt = 0
|
|
while seiten < MAX_SEITEN:
|
|
antwort = sitzung.get(f"{DATEN_URL}/v2/stocks/bars", headers=kopf, params=parameter, timeout=30)
|
|
seiten += 1
|
|
if antwort.status_code != 200:
|
|
raise RuntimeError(f"GET /v2/stocks/bars antwortete {antwort.status_code}")
|
|
nutzlast = antwort.json()
|
|
bars = nutzlast.get("bars") or {}
|
|
if len(bars) > 1:
|
|
gemischt += 1
|
|
for symbol, liste in bars.items():
|
|
je_symbol[symbol] = je_symbol.get(symbol, 0) + len(liste or [])
|
|
token = nutzlast.get("next_page_token")
|
|
if not token:
|
|
break
|
|
parameter["page_token"] = token
|
|
return je_symbol, seiten, gemischt
|
|
|
|
|
|
def main() -> int:
|
|
if len(sys.argv) < 3:
|
|
print(__doc__)
|
|
return 1
|
|
datum = sys.argv[1]
|
|
kennungen = sys.argv[2:]
|
|
symbole = [zu_symbol(kennung) for kennung in kennungen]
|
|
werte = tomllib.loads(geheimnisse.pfad().read_text(encoding="utf-8"))["alpaca"]
|
|
kopf = {"APCA-API-KEY-ID": werte["key_id"], "APCA-API-SECRET-KEY": werte["secret_key"]}
|
|
sitzung = requests.Session()
|
|
sitzung.trust_env = False
|
|
abweichungen = 0
|
|
|
|
# 1. GET /v2/assets roh fuer das erste Symbol: nur Schluessel und Typen.
|
|
antwort = sitzung.get(f"{TRADING_URL}/v2/assets/{symbole[0]}", headers=kopf, timeout=20)
|
|
print(f"GET /v2/assets/{symbole[0]}: HTTP {antwort.status_code}")
|
|
roh = antwort.json() if antwort.status_code == 200 else {}
|
|
if isinstance(roh, dict):
|
|
print(f" schluessel und typen: {dict((k, typ(v)) for k, v in sorted(roh.items()))}")
|
|
for feld, erwartet in (("symbol", "str"), ("tradable", "bool"), ("shortable", "bool"), ("easy_to_borrow", "bool")):
|
|
gefunden = typ(roh.get(feld)) if isinstance(roh, dict) and feld in roh else "fehlt"
|
|
if gefunden != erwartet:
|
|
print(f"ABWEICHUNG: Feld {feld} ist {gefunden}, erwartet {erwartet}")
|
|
abweichungen += 1
|
|
|
|
# 2. anlage() je Instrument ueber den Code des Projekts.
|
|
broker = AlpacaBroker(
|
|
werte["key_id"], werte["secret_key"], universum=tuple(kennungen), feed=FEED, timeout=20.0
|
|
)
|
|
for kennung in kennungen:
|
|
anlage = broker.anlage(kennung)
|
|
print(
|
|
f"anlage {kennung}: tradable {anlage.handelbar}, shortable {anlage.leerverkaufbar}, "
|
|
f"easy_to_borrow {anlage.leicht_leihbar}"
|
|
)
|
|
if not (anlage.handelbar and anlage.leerverkaufbar and anlage.leicht_leihbar):
|
|
print(f"STARTHINDERNIS: {kennung} haelt die Startpruefung einer Folge mit Short nicht")
|
|
abweichungen += 1
|
|
|
|
# 3. trades/latest roh und ueber den Code.
|
|
antwort = sitzung.get(
|
|
f"{DATEN_URL}/v2/stocks/trades/latest",
|
|
headers=kopf,
|
|
params={"symbols": ",".join(symbole), "feed": FEED},
|
|
timeout=20,
|
|
)
|
|
print(f"GET /v2/stocks/trades/latest mit {len(symbole)} Symbolen: HTTP {antwort.status_code}")
|
|
roh = antwort.json() if antwort.status_code == 200 else {}
|
|
trades = roh.get("trades") if isinstance(roh, dict) else None
|
|
print(f" schluessel oben: {sorted(roh) if isinstance(roh, dict) else typ(roh)}, trades ist {typ(trades)}")
|
|
if not isinstance(trades, dict) or sorted(trades) != sorted(symbole):
|
|
gesehen = sorted(trades) if isinstance(trades, dict) else typ(trades)
|
|
print(f"ABWEICHUNG: trades traegt {gesehen} statt {sorted(symbole)}")
|
|
abweichungen += 1
|
|
kurse = broker.letzte_kurse(kennungen)
|
|
jetzt = datetime.now(UTC)
|
|
for kennung in kennungen:
|
|
kurs = kurse.get(kennung)
|
|
alter = "keiner" if kurs is None else f"vor {(jetzt - kurs.zeit).total_seconds():.0f} s"
|
|
print(f"letzter trade {kennung}: {alter}")
|
|
|
|
# 4. Kerzen des Handelstags: gebuendelt ueber den Code gegen einzeln ueber den Code.
|
|
mitternacht = datetime.strptime(datum, "%Y-%m-%d").replace(tzinfo=HANDELSZONE)
|
|
bis = handelsende(mitternacht)
|
|
quelle = AlpacaKerzenquelle(werte["key_id"], werte["secret_key"], feed=FEED, timeout=30.0)
|
|
gebuendelt = quelle.kerzen_mehrerer(kennungen, bis)
|
|
for kennung in kennungen:
|
|
einzeln = quelle.kerzen_des_tages(kennung, bis)
|
|
gleich = [k.zeit for k in einzeln] == [k.zeit for k in gebuendelt[kennung]]
|
|
print(
|
|
f"kerzen {kennung}: gebuendelt {len(gebuendelt[kennung])}, einzeln {len(einzeln)}"
|
|
+ ("" if gleich else " ABWEICHUNG")
|
|
)
|
|
if not gleich:
|
|
abweichungen += 1
|
|
|
|
# 5. Seitenverhalten roh: eine grosse Seite gegen viele kleine, je Symbol dieselbe Summe.
|
|
grundparameter = {
|
|
"symbols": ",".join(symbole),
|
|
"timeframe": "1Min",
|
|
"start": iso_z(handelsbeginn(bis)),
|
|
"end": iso_z(bis),
|
|
"feed": FEED,
|
|
"adjustment": "raw",
|
|
}
|
|
gross, seiten_gross, _ = zaehle_seiten(sitzung, kopf, grundparameter | {"limit": 10_000})
|
|
klein, seiten_klein, gemischt = zaehle_seiten(sitzung, kopf, grundparameter | {"limit": KLEINE_SEITE})
|
|
summe = sum(gross.values())
|
|
print(f"seiten mit limit 10000: {seiten_gross}, kerzen zusammen {summe}")
|
|
print(f"seiten mit limit {KLEINE_SEITE}: {seiten_klein}, davon mit mehr als einem symbol: {gemischt}")
|
|
if gross != klein:
|
|
print(f"ABWEICHUNG: je Symbol ueber die kleinen Seiten {klein}, ueber die grosse {gross}")
|
|
abweichungen += 1
|
|
if summe > KLEINE_SEITE and seiten_klein == 1:
|
|
print("HINWEIS: limit gilt offenbar je Symbol und nicht ueber alle; der Code sammelt in beiden Faellen richtig")
|
|
|
|
if abweichungen:
|
|
print(f"ERGEBNIS: {abweichungen} Abweichung(en) oder Starthindernis(se)")
|
|
return 2
|
|
print("ERGEBNIS: alle Formen wie angenommen")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
sys.exit(main())
|
|
except Exception as ausnahme: # noqa: BLE001 - ein Absturz ist selbst ein Befund
|
|
print(f"BEFUND: das Werkzeug ist abgebrochen: {ausnahme!r}")
|
|
sys.exit(1)
|