Automatischer Push: 32 Datei(en) geändert

- .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]>
This commit is contained in:
Pascal MichelandClaude Opus 5 committed 2026-09-24 04:00:02 +02:00
1 parent d7ffc9a5b5
commit 67d674cd0b
32 files changed
+1618 -88

No files matched your search

@@ -92,3 +92,14 @@ Die Compose-Probe (Bauen aus dem echten Baum mit verankerten Ausschlüssen, Star
Vor dem Browser-Lauf `npm run build` im Projekt, sonst dient das Backend eine alte Oberfläche aus. Die Datensatz- und Dateinamen in `browserlauf.py` und `pruef_teil3b.py` sind die des Bestands vom 2026-09-17; nach einem neuen Katalog oder neuen Protokollen nachziehen. Die Statustexte in `wiederaufbau.py` sind aus `Kopfzeile.tsx` abgeschrieben.
Die generischen Planwerkzeuge liegen unter `.vault-tools/plan-kopie/`.
## Geheimnisdatei seit Paket E2 (2026-09-23)
Die Werkzeuge nennen `config/secrets.toml` nicht mehr fest. Wer eine Projektklasse ruft, gibt `None` weiter (`from_secrets(None, …)`, `secrets_pfad=None`, `lies_ntfy_geheimnisse()`); wer die Datei selbst liest (`handabruf_kerzen.py`, `handelstag_auswertung.py`, `vorstart_pruefung.py`, `pruef_beobachtung_echt.py`), holt den Pfad mit `trading.geheimnisse.pfad()` und nennt im Fehlerfall `geheimnisse.beschreibung()`; `ha_einrichten.py` löst gegen den übergebenen Projektordner auf. Der Auflöser nimmt das Argument, dann `TRADING_GEHEIMNISSE`, dann `$CREDENTIALS_DIRECTORY/secrets.toml`, sonst `config/secrets.toml` relativ zum Arbeitsverzeichnis — **nach dem Umzug der Datei ins Linux-Dateisystem steht die Variable nur in einer Login-Shell** (`~/.profile`). Ein Werkzeug aus einer Skriptdatei über `wsl --exec bash` sieht sie nicht und meldet „fehlt": dann `export TRADING_GEHEIMNISSE=/home/claude/.config/trading-plattform/secrets.toml` im Skript setzen. `start_probe_w.py` und `start_probe_zugang.py` nehmen beide Variablen absichtlich aus ihrer Umgebung, damit eine Probe nie die echte Datei liest; `ha_aktiv_setzen.py` bekommt den Pfad als Argument.
## Paket M (2026-09-24): zehn Aktien in einem Runner
| Datei | Was sie tut | Aufruf |
|---|---|---|
| `handelstag_auswertung.py` | seit Paket M mit mehreren Instrumenten: Instrumente aus dem Protokollkopf; je Instrument Entscheidungen, Orders und die aus der Folge erwartete Zahl (Stufen, Versatz, Entscheidungen, Schlussbein, ohne geteilte Beine), erreichte Positionen und Takte mit frischer Kerze; groesste Zahl von Orders in einem Takt, Ereignisse je Art, Positionen je Instrument am Ende, BEFUND fuer jede nicht gefuellte Orderzeile; das Taktfeld orders gilt als Orders dieses Takts; `--broker` und `--kerzen` fragen alle Symbole des Kopfs, jede Order gegen die Kerze ihres Symbols | wie bisher: `python handelstag_auswertung.py <protokoll> [--broker] [--kerzen]` aus dem Projektordner |
| `m_probeaufruf.py` | lesender Probeaufruf vor dem Einspielen: `GET /v2/assets` roh (Schluessel und Typen) und ueber `AlpacaBroker.anlage`, `trades/latest` mit allen Symbolen roh und ueber `letzte_kurse`, Kerzen gebuendelt gegen einzeln, Seitenverhalten mit kleiner Seite; Exit 0 nur, wenn alles wie angenommen ist und keine Aktie die Startpruefung einer Folge mit Short verfehlt | aus einem Arbeitsbaum mit Paket M in WSL: `python m_probeaufruf.py <handelstag> <kennung> ...` |
@@ -0,0 +1,87 @@
"""Liest die Antwortformen dreier Alpaca-Endpunkte fuer Paket M (zehn Aktien). Nur lesend, keine Order.
Koordinator-Werkzeug, nicht Teil des Repos (entstanden 2026-09-24 nachts). Ausgegeben werden Schluessel,
Typen, Seitenmarken und die Handelsfelder der Anlagen (tradable, shortable, easy_to_borrow, fractionable,
marginable) -- keine Kontodaten, kein Schluessel. Endpunkte:
GET /v2/assets/{symbol} (Handels-API, paper-api)
GET /v2/stocks/trades/latest?symbols=... (Daten-API, feed=iex)
GET /v2/stocks/bars?symbols=...&timeframe=1Min (Daten-API, feed=iex, Seitenverhalten ueber Symbole)
Aufruf (WSL, TRADING_GEHEIMNISSE gesetzt): python alpaca_formen_lesen.py JJJJ-MM-TT SYMBOL [SYMBOL ...]
"""
import sys
import tomllib
from pathlib import Path
import requests
sys.path.insert(0, str(Path.cwd()))
from trading import geheimnisse # noqa: E402
HANDEL = "https://paper-api.alpaca.markets"
DATEN = "https://data.alpaca.markets"
def form(wert, tiefe=0):
if isinstance(wert, dict):
if tiefe > 1:
return "{...}"
return "{" + ", ".join(f"{k}: {form(v, tiefe + 1)}" for k, v in list(wert.items())[:14]) + "}"
if isinstance(wert, list):
return f"[{len(wert)} x {form(wert[0], tiefe + 1) if wert else '-'}]"
return type(wert).__name__
def main() -> int:
tag, symbole = sys.argv[1], sys.argv[2:]
werte = tomllib.loads(geheimnisse.pfad().read_text(encoding="utf-8"))["alpaca"]
s = requests.Session()
s.trust_env = False
s.headers.update({"APCA-API-KEY-ID": werte["key_id"], "APCA-API-SECRET-KEY": werte["secret_key"]})
print("== /v2/assets/{symbol}")
for sym in symbole:
a = s.get(f"{HANDEL}/v2/assets/{sym}", timeout=20)
if a.status_code != 200:
print(sym, "HTTP", a.status_code)
continue
j = a.json()
felder = {k: j.get(k) for k in ("symbol", "exchange", "status", "tradable", "shortable", "easy_to_borrow", "fractionable", "marginable")}
print(felder)
print("Schluessel einer Anlage:", sorted(j.keys()) if symbole else "-")
print("== /v2/stocks/trades/latest (gebuendelt)")
t = s.get(f"{DATEN}/v2/stocks/trades/latest", params={"symbols": ",".join(symbole), "feed": "iex"}, timeout=20)
print("HTTP", t.status_code, "Form:", form(t.json()))
trades = t.json().get("trades", {})
print("Symbole mit Trade:", len(trades), "von", len(symbole), "; Felder eines Trades:", sorted(next(iter(trades.values())).keys()) if trades else "-")
print("== /v2/stocks/bars (gebuendelt, Seiten)")
start, ende = f"{tag}T13:30:00Z", f"{tag}T20:00:00Z"
seiten, gesamt, je_symbol, token = 0, 0, {}, None
while True:
p = {"symbols": ",".join(symbole), "timeframe": "1Min", "start": start, "end": ende, "feed": "iex", "limit": 10000, "adjustment": "raw"}
if token:
p["page_token"] = token
b = s.get(f"{DATEN}/v2/stocks/bars", params=p, timeout=30)
if b.status_code != 200:
print("HTTP", b.status_code, b.text[:200])
return 1
j = b.json()
seiten += 1
if seiten == 1:
print("Form erste Seite:", form(j))
for sym, liste in (j.get("bars") or {}).items():
je_symbol[sym] = je_symbol.get(sym, 0) + len(liste)
gesamt += len(liste)
token = j.get("next_page_token")
if not token or seiten > 20:
break
print("Seiten:", seiten, "Balken gesamt:", gesamt)
print("Balken je Symbol:", dict(sorted(je_symbol.items())))
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -118,7 +118,13 @@ def main() -> int:
sys.path.insert(0, str(ha_projekt / "src"))
import ha_verbindung as hv
geheimnisse = trading / "config" / "secrets.toml"
# Seit E2 (2026-09-23): der Aufloeser des Projekts entscheidet -- TRADING_GEHEIMNISSE, sonst
# config/secrets.toml relativ zum uebergebenen Projektordner.
sys.path.insert(0, str(trading))
from trading import geheimnisse as aufloeser # noqa: E402
aufgeloest = aufloeser.pfad()
geheimnisse = aufgeloest if aufgeloest.is_absolute() else trading / aufgeloest
token = hv.lies_token()
print(f"Token gelesen (Laenge {len(token)}), Instanz aus dem HA-Projekt, Aktion {aktion}")
@@ -7,7 +7,7 @@ ob die Kerzen verzoegert kommen. Sinnvoll, solange gehandelt wird -- Vorboerse a
Berliner Zeit (04:00 ET), regulaer ab 15:30 Uhr. Nachts zeigt es nur die letzte Kerze des
Vortags; dann belegt es nur, dass Schluessel und Abruf funktionieren.
Liest config/secrets.toml aus dem Projektordner, gibt die Schluessel nie aus.
Liest die Geheimnisdatei ueber den Aufloeser des Projekts (TRADING_GEHEIMNISSE, sonst config/secrets.toml), gibt die Schluessel nie aus.
Aufruf aus dem Projektordner (WSL, Venv):
~/venvs/trading/bin/python /mnt/d/Claude/.vault-tools/trading-plattform-pruefung/handabruf_kerzen.py [feed]
@@ -21,15 +21,18 @@ from pathlib import Path
import requests
sys.path.insert(0, str(Path.cwd()))
from trading import geheimnisse # noqa: E402 -- aus dem Projektordner, wie oben verlangt
DATEN_URL = "https://data.alpaca.markets"
FRISCH_S = 120 # eine abgeschlossene Minute plus die Wartezeit des Runners
def main() -> int:
feed = sys.argv[1] if len(sys.argv) > 1 else "iex"
secrets = Path("config/secrets.toml")
if not secrets.exists():
print(f"VORAUSSETZUNG FEHLT: {secrets} (aus dem Projektordner starten)")
secrets = geheimnisse.pfad()
if not secrets.is_file():
print(f"VORAUSSETZUNG FEHLT: {geheimnisse.beschreibung()}")
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"]}
@@ -1,30 +1,37 @@
"""Wertet einen Paper-Handelstag aus: Protokoll des Runners, Orders des Paper-Kontos, IEX-Kerzen des Tages.
Koordinator-Werkzeug zu Teil 10, nicht fuers Repo. Es misst und berichtet; ein Urteil faellt es nur an
drei Stellen, und dort gegen Werte aus dem Protokollkopf (Projektvorgabe, config/paper.toml):
vier Stellen, und dort gegen Werte aus dem Protokollkopf (Projektvorgabe, config/paper.toml):
- Annahme bis Fuellung einer Order (filled_at - created_at laut Alpaca) gegen order_timeout_s -- nicht
filled_at - submitted_at: Am 2026-09-21 lagen zwischen Annahme und Weitergabe 8,7 s, und der Runner wartet
ab der Annahme. Beide Spannen werden ausgegeben; mehr als die Haelfte der Grenze ist ein Hinweis,
- Schlussposition ungleich null,
- Schlussposition ungleich null (seit Paket M auch je Instrument),
- eine Orderzeile des Protokolls, die nicht filled ist,
- eine Position beim Broker mit negativer Stueckzahl (Short): wird nur gemeldet, nicht bewertet.
Was es misst:
1. Aus dem Protokoll: Zeilen je Art, Takte je Phase und Killswitch-Zustand, Anteil frischer Kerzen,
erste Entscheidung, Orders, Ende. Dazu je Takt mit frischer Kerze die Spanne
erste Entscheidung, Orders, Ereignisse, Ende. Dazu je Takt mit frischer Kerze die Spanne
takt.zeit - (letzte_kerze + 60 s). Das ist eine OBERGRENZE fuer die Zeit, bis die Kerze abrufbar war:
Der Runner wartet vor dem ersten Abruf kerze_verzoegerung_s und schreibt die Taktzeile erst am Ende
des Takts. Den Versuch, bei dem die Kerze kam, schreibt das Protokoll nicht mit; die Spanne laesst ihn
nur schaetzen. Takte mit Order sind getrennt ausgewiesen, weil dort die Orderabfrage in der Spanne steckt.
2. Mit --broker (lesend, Paper-Konto): Orders des Tages mit submitted_at, filled_at, filled_avg_price;
offene Positionen mit Vorzeichen der Stueckzahl. Kein schreibender Aufruf.
3. Mit --kerzen (lesend): IEX-Minutenkerzen des Tages; je gefuellter Order der Abstand des Fuellkurses
zum Schluss der letzten vor der Order abgeschlossenen Kerze, in Basispunkten, mit Vorzeichen so,
dass positiv Kosten bedeutet (Kauf ueber dem Schluss, Verkauf darunter).
Das Taktfeld orders ist die Zahl der Orders DIESES Takts (PaperSitzung._takt_ende), keine Summe.
Seit Paket M (2026-09-24) mit mehreren Instrumenten: die Instrumente aus dem Kopf; je Instrument
Entscheidungen, Orders, die aus der Folge erwartete Zahl, erreichte Positionen und Takte mit frischer
Kerze; die groesste Zahl von Orders in einem Takt, die Takte mit Orders in der Schlussphase und die
Positionen je Instrument am Ende.
2. Mit --broker (lesend, Paper-Konto): Orders des Tages in allen Symbolen des Kopfs mit submitted_at,
filled_at, filled_avg_price; offene Positionen mit Vorzeichen der Stueckzahl. Kein schreibender Aufruf.
3. Mit --kerzen (lesend): IEX-Minutenkerzen des Tages fuer alle Symbole des Kopfs, ueber alle Seiten; je
gefuellter Order der Abstand des Fuellkurses zum Schluss der letzten vor der Order abgeschlossenen Kerze
IHRES Symbols, in Basispunkten, mit Vorzeichen so, dass positiv Kosten bedeutet (Kauf ueber dem
Schluss, Verkauf darunter).
Was es nicht sieht: Teilausfuehrungen einzeln (nur den Durchschnittskurs der Order), den konsolidierten
Kurs (nur IEX), Gebuehren (das Paper-Konto rechnet keine), und ob ein ausgebliebener Takt am Runner oder
am Rechner lag. Ein Fangnetz fuer die Auswertung, kein Nachweis ueber die Handelsidee.
Liest config/secrets.toml aus dem Projektordner und gibt die Schluessel nie aus.
Liest die Geheimnisdatei ueber den Aufloeser des Projekts (TRADING_GEHEIMNISSE, sonst config/secrets.toml) und gibt die Schluessel nie aus.
Aufruf aus dem Projektordner (WSL, Venv):
python handelstag_auswertung.py <paper-protokoll.jsonl> [--broker] [--kerzen]
Exit 0, 3 wenn eine Voraussetzung fehlt (Protokoll, Kopfzeile, Schluessel, Netz).
@@ -38,14 +45,61 @@ from collections import Counter
from datetime import UTC, datetime, timedelta
from pathlib import Path
sys.path.insert(0, str(Path.cwd()))
from trading import geheimnisse # noqa: E402 -- aus dem Projektordner, wie oben verlangt
HANDEL_URL = "https://paper-api.alpaca.markets"
DATEN_URL = "https://data.alpaca.markets"
# Seiten des Kerzenabrufs, hoechstens. Zehn Symbole mal 390 Minuten sind rund 3.900 Kerzen, eine Seite.
MAX_SEITEN = 20
def zeitpunkt(text: str) -> datetime:
return datetime.fromisoformat(text.replace("Z", "+00:00"))
def instrumente_aus(kopf: dict) -> list[str]:
"""Die Instrumente des Laufs: instrumente seit Paket M, sonst instrument, sonst AAPL wie bisher."""
liste = kopf.get("instrumente")
if isinstance(liste, list) and liste:
return [str(kennung) for kennung in liste]
einzeln = kopf.get("instrument")
return [str(einzeln)] if einzeln else ["AAPL.XNAS"]
def symbole_aus(kopf: dict) -> list[str]:
return [kennung.split(".")[0] for kennung in instrumente_aus(kopf)]
def erwartete_orders(agent: object, index: int, entscheidungen: int) -> int | None:
"""Die Orders, die eine Folge bei so vielen Entscheidungen stellt, samt Schlussbein.
Nur fuer quelle folge, sonst None. Der Folgezaehler des Instruments beginnt bei index mal
versatz_takte und zaehlt je Entscheidung eins weiter -- in einer Minute ohne frische Kerze
entscheidet das Instrument nicht, und der Zaehler steht. Angenommen ist ein flacher Start;
ein Seitenwechsel sind zwei Beine; steht die Folge am Ende nicht auf null, kommt das
Schlussbein dazu. Geteilte Beine (ueber max_notional_je_order) zaehlt die Rechnung nicht mit.
"""
if not isinstance(agent, dict) or agent.get("quelle") != "folge":
return None
stufen = agent.get("stufen")
takte = agent.get("takte_je_stufe")
if not isinstance(stufen, list) or not stufen or not isinstance(takte, int) or takte < 1:
return None
versatz = index * int(agent.get("versatz_takte") or 0)
vorher = 0
orders = 0
for zaehler in range(versatz, versatz + entscheidungen):
stufe = stufen[(zaehler // takte) % len(stufen)]
if stufe != vorher:
orders += 2 if stufe * vorher < 0 else 1
vorher = stufe
if vorher != 0:
orders += 1
return orders
def lies_protokoll(pfad: Path) -> list[dict]:
zeilen = []
# Dateiiteration statt splitlines(): splitlines trennt auch an U+2028 (Lesson vom 2026-09-17).
@@ -84,6 +138,8 @@ def teil_protokoll(zeilen: list[dict]) -> dict | None:
f"kopf: modus {kopf.get('modus')}, agent {kopf.get('agent')}, feed {kopf.get('feed')}, "
f"order_timeout_s {kopf.get('order_timeout_s')}, start {kopf.get('zeit')}"
)
instrumente = instrumente_aus(kopf)
print(f"instrumente: {instrumente}")
takte = [z for z in zeilen if z.get("art") == "takt"]
print(f"takte: {len(takte)}, phasen {dict(Counter(t.get('phase') for t in takte))}")
print(f"killswitch je takt: {dict(Counter(t.get('killswitch') for t in takte))}")
@@ -94,12 +150,9 @@ def teil_protokoll(zeilen: list[dict]) -> dict | None:
if ohne:
print(f" takte bei offenem markt OHNE frische kerze: {len(ohne)}; erste fuenf: {[t.get('zeit') for t in ohne[:5]]}")
orderschritte = set()
letzter_orders = 0
for t in takte:
if isinstance(t.get("orders"), int) and t["orders"] > letzter_orders:
orderschritte.add(t.get("schritt"))
letzter_orders = t["orders"]
# orders in der Taktzeile sind die Orders DIESES Takts (self._orders - vorher), keine laufende
# Summe. Bis Paket M las das Werkzeug sie als Summe und ordnete nur Takte mit wachsender Zahl zu.
orderschritte = {t.get("schritt") for t in takte if isinstance(t.get("orders"), int) and t["orders"] > 0}
ruhig, mit_order = [], []
for t in takte:
if not t.get("frische_kerze") or not t.get("letzte_kerze") or not t.get("zeit"):
@@ -127,15 +180,62 @@ def teil_protokoll(zeilen: list[dict]) -> dict | None:
)
print(f"aktionen: {dict(Counter(t.get('aktion') for t in entscheidungen))}")
print(f"positionen je takt: {dict(Counter(t.get('position') for t in takte))}")
if len(instrumente) > 1:
erreicht = {
kennung: sorted(
{
t["positionen"][kennung]
for t in takte
if isinstance(t.get("positionen"), dict) and kennung in t["positionen"]
}
)
for kennung in instrumente
}
print(f"positionen erreicht je instrument: {erreicht}")
frisch_je = {
kennung: sum(
1 for t in takte if isinstance(t.get("frische_kerzen"), dict) and t["frische_kerzen"].get(kennung)
)
for kennung in instrumente
}
print(f"takte mit frischer kerze je instrument: {frisch_je}")
entscheidungen_je = {
kennung: sum(
1 for t in takte if isinstance(t.get("aktionen"), dict) and t["aktionen"].get(kennung) is not None
)
for kennung in instrumente
}
else:
entscheidungen_je = {instrumente[0]: len(entscheidungen)}
orders_je_takt = [t.get("orders") for t in takte if isinstance(t.get("orders"), int)]
print(f"groesste zahl von orders in einem takt: {max(orders_je_takt) if orders_je_takt else 0}")
schluss_mit_order = [t for t in takte if t.get("phase") == "schliesst" and (t.get("orders") or 0) > 0]
print(f"takte mit orders in der schlussphase: {len(schluss_mit_order)}")
hinweise = Counter(t.get("hinweis") for t in takte if t.get("hinweis"))
for text, zahl in hinweise.most_common(8):
print(f" hinweis x{zahl}: {str(text)[:140]}")
for z in zeilen:
if z.get("art") == "ereignis":
print(f"ereignis {z.get('zeit')}: {z.get('was')} | {str(z.get('text'))[:160]} | phase {z.get('phase')}")
for z in zeilen:
if z.get("art") == "order":
print(f"order {z.get('zeit')}: {z.get('seite')} {z.get('menge')} gefuellt {z.get('gefuellt')} status {z.get('status')} id {z.get('id')}")
ereignisse = [z for z in zeilen if z.get("art") == "ereignis"]
print(f"ereignisse je art: {dict(sorted(Counter(z.get('was') for z in ereignisse).items()))}")
for z in ereignisse:
print(f"ereignis {z.get('zeit')}: {z.get('was')} | {str(z.get('text'))[:160]} | phase {z.get('phase')}")
orderzeilen = [z for z in zeilen if z.get("art") == "order"]
for z in orderzeilen:
print(
f"order {z.get('zeit')}: {z.get('instrument') or ''} {z.get('seite')} {z.get('menge')} "
f"gefuellt {z.get('gefuellt')} status {z.get('status')} position danach {z.get('position_danach')} id {z.get('id')}"
)
if z.get("status") != "filled":
print(f"BEFUND: Orderzeile nicht gefuellt ({z.get('instrument') or ''} {z.get('seite')} {z.get('menge')}, status {z.get('status')})")
orders_je = Counter(z.get("instrument") for z in orderzeilen if z.get("instrument"))
print(f"orders je instrument: {dict(sorted(orders_je.items()))}")
if len(instrumente) == 1 and not orders_je:
# Protokolle von vor 0.28.0 tragen kein Instrument in der Orderzeile.
orders_je = Counter({instrumente[0]: len(orderzeilen)})
print("je instrument (erwartet: aus stufen, versatz und entscheidungen, samt schlussbein, ohne geteilte beine):")
for index, kennung in enumerate(instrumente):
erwartet = erwartete_orders(kopf.get("agent"), index, entscheidungen_je.get(kennung, 0))
text = "" if erwartet is None else f", erwartet {erwartet}"
print(f" {kennung}: entscheidungen {entscheidungen_je.get(kennung, 0)}, orders {orders_je.get(kennung, 0)}{text}")
enden = [z for z in zeilen if z.get("art") == "ende"]
if not enden:
print("BEFUND: keine Zeile 'ende' -- der Runner wurde nicht ordentlich beendet")
@@ -146,13 +246,19 @@ def teil_protokoll(zeilen: list[dict]) -> dict | None:
)
if ende.get("position") not in (0, None):
print("BEFUND: Schlussposition ungleich null")
positionen = ende.get("positionen")
if isinstance(positionen, dict):
print(f" positionen am ende: {positionen}")
offen = {kennung: menge for kennung, menge in positionen.items() if menge != 0}
if offen:
print(f"BEFUND: Schlussposition ungleich null in {offen}")
return kopf
def kopfzeilen() -> dict[str, str] | None:
secrets = Path("config/secrets.toml")
if not secrets.exists():
print(f"VORAUSSETZUNG FEHLT: {secrets} (aus dem Projektordner starten)")
secrets = geheimnisse.pfad()
if not secrets.is_file():
print(f"VORAUSSETZUNG FEHLT: {geheimnisse.beschreibung()}")
return None
werte = tomllib.loads(secrets.read_text(encoding="utf-8"))["alpaca"]
return {"APCA-API-KEY-ID": werte["key_id"], "APCA-API-SECRET-KEY": werte["secret_key"]}
@@ -163,7 +269,7 @@ def teil_broker(kopf: dict, tag_start: datetime, anmeldung: dict[str, str]) -> l
parameter = {
"status": "all",
"symbols": "AAPL",
"symbols": ",".join(symbole_aus(kopf)),
"after": tag_start.strftime("%Y-%m-%dT%H:%M:%SZ"),
"until": (tag_start + timedelta(days=1)).strftime("%Y-%m-%dT%H:%M:%SZ"),
"direction": "asc",
@@ -176,6 +282,7 @@ def teil_broker(kopf: dict, tag_start: datetime, anmeldung: dict[str, str]) -> l
orders = antwort.json()
grenze = float(kopf.get("order_timeout_s") or 0)
print(f"orders beim broker am {tag_start.date()}: {len(orders)} (order_timeout_s laut protokoll: {grenze})")
print(f"orders beim broker je symbol: {dict(sorted(Counter(o.get('symbol') for o in orders).items()))}")
for o in orders:
erstellt = zeitpunkt(o["created_at"]) if o.get("created_at") else None
eingereicht = zeitpunkt(o["submitted_at"]) if o.get("submitted_at") else None
@@ -214,11 +321,11 @@ def teil_broker(kopf: dict, tag_start: datetime, anmeldung: dict[str, str]) -> l
return orders
def teil_kerzen(orders: list[dict], tag_start: datetime, feed: str, anmeldung: dict[str, str]) -> None:
def teil_kerzen(orders: list[dict], tag_start: datetime, feed: str, anmeldung: dict[str, str], symbole: list[str]) -> None:
import requests
parameter = {
"symbols": "AAPL",
"symbols": ",".join(symbole),
"timeframe": "1Min",
"start": tag_start.strftime("%Y-%m-%dT%H:%M:%SZ"),
"end": (tag_start + timedelta(days=1)).strftime("%Y-%m-%dT%H:%M:%SZ"),
@@ -227,13 +334,21 @@ def teil_kerzen(orders: list[dict], tag_start: datetime, feed: str, anmeldung: d
"limit": 10_000,
"sort": "asc",
}
antwort = requests.get(f"{DATEN_URL}/v2/stocks/bars", headers=anmeldung, params=parameter, timeout=30)
if antwort.status_code != 200:
print(f"VORAUSSETZUNG FEHLT: /v2/stocks/bars antwortet {antwort.status_code}: {antwort.text[:200]}")
return
kerzen = (antwort.json().get("bars") or {}).get("AAPL") or []
print(f"kerzen ({feed}) am {tag_start.date()}: {len(kerzen)}")
nach_zeit = {zeitpunkt(k["t"]): k for k in kerzen}
kerzen: dict[str, list[dict]] = {symbol: [] for symbol in symbole}
for _seite in range(MAX_SEITEN):
antwort = requests.get(f"{DATEN_URL}/v2/stocks/bars", headers=anmeldung, params=parameter, timeout=30)
if antwort.status_code != 200:
print(f"VORAUSSETZUNG FEHLT: /v2/stocks/bars antwortet {antwort.status_code}: {antwort.text[:200]}")
return
nutzlast = antwort.json()
for symbol, liste in (nutzlast.get("bars") or {}).items():
kerzen.setdefault(symbol, []).extend(liste or [])
token = nutzlast.get("next_page_token")
if not token:
break
parameter["page_token"] = token
print(f"kerzen ({feed}) am {tag_start.date()}: {dict((symbol, len(liste)) for symbol, liste in kerzen.items())}")
nach_zeit = {symbol: {zeitpunkt(k["t"]): k for k in liste} for symbol, liste in kerzen.items()}
for o in orders:
if not o.get("filled_at") or not o.get("filled_avg_price") or not o.get("submitted_at"):
continue
@@ -241,16 +356,16 @@ def teil_kerzen(orders: list[dict], tag_start: datetime, feed: str, anmeldung: d
# Die letzte vor der Order ABGESCHLOSSENE Kerze beginnt zwei Minutenanfaenge vor der laufenden Minute
# nur dann nicht, wenn die Order in derselben Minute liegt wie ihr Schluss: Minute abrunden, eine zurueck.
entscheidungskerze = eingereicht.replace(second=0, microsecond=0) - timedelta(minutes=1)
kerze = nach_zeit.get(entscheidungskerze)
kerze = nach_zeit.get(o.get("symbol"), {}).get(entscheidungskerze)
if kerze is None:
print(f" {o.get('side')} {o.get('qty')}: keine Kerze um {entscheidungskerze.isoformat()} (IEX-Luecke)")
print(f" {o.get('symbol')} {o.get('side')} {o.get('qty')}: keine Kerze um {entscheidungskerze.isoformat()} (IEX-Luecke)")
continue
schluss = float(kerze["c"])
kurs = float(o["filled_avg_price"])
vorzeichen = 1.0 if o.get("side") == "buy" else -1.0
bp = vorzeichen * (kurs - schluss) / schluss * 10_000
print(
f" {o.get('side')} {o.get('qty')}: fuellkurs {kurs:.4f}, schluss der kerze {entscheidungskerze.strftime('%H:%M')} UTC {schluss:.4f}, "
f" {o.get('symbol')} {o.get('side')} {o.get('qty')}: fuellkurs {kurs:.4f}, schluss der kerze {entscheidungskerze.strftime('%H:%M')} UTC {schluss:.4f}, "
f"kosten {bp:+.2f} bp ({vorzeichen * (kurs - schluss) * float(o.get('filled_qty') or 0):+.2f} USD)"
)
@@ -285,7 +400,7 @@ def main() -> int:
orders = ergebnis
if "--kerzen" in schalter:
print("== ausfuehrungskosten gegen die IEX-kerze")
teil_kerzen(orders, tag_start, str(kopf.get("feed") or "iex"), anmeldung)
teil_kerzen(orders, tag_start, str(kopf.get("feed") or "iex"), anmeldung, symbole_aus(kopf))
return 0
@@ -23,7 +23,7 @@ def main() -> int:
datum, ziel = sys.argv[1], Path(sys.argv[2])
config = lade_paper_config("config/paper.toml")
mitternacht = datetime.strptime(datum, "%Y-%m-%d").replace(tzinfo=HANDELSZONE)
quelle = AlpacaKerzenquelle.from_secrets("config/secrets.toml", feed=config.feed, timeout=20.0)
quelle = AlpacaKerzenquelle.from_secrets(None, feed=config.feed, timeout=20.0)
kerzen = quelle.kerzen_des_tages(config.instrument_id, handelsende(mitternacht))
ziel.write_text(
json.dumps(
@@ -0,0 +1,45 @@
"""Misst Luecken im IEX-Minutenfeed je Symbol und Handelstag. Nur lesend.
Koordinator-Werkzeug (2026-09-24 nachts, Paket M). Anlass: Der Gratis-Feed IEX liefert fuer weniger bei IEX
gehandelte Werte nicht jede Minute eine Kerze (am 2026-09-23 AMGN 287 von 390). Der Watchdog haelt an, wenn
der letzte Kurs aelter als kursstrom_max_alter_s (300 s) ist; diese Messung zeigt, wie lang die laengste
Minute ohne Kerze je Symbol ist, und wie oft eine Luecke ueber 5 Minuten reicht. Ausgegeben werden nur Zaehler.
Aufruf (WSL, TRADING_GEHEIMNISSE gesetzt): python iex_luecken.py TAG[,TAG...] SYMBOL [SYMBOL ...]
"""
import sys
import tomllib
from datetime import datetime
from pathlib import Path
import requests
sys.path.insert(0, str(Path.cwd()))
from trading import geheimnisse # noqa: E402
DATEN = "https://data.alpaca.markets"
def main() -> int:
tage, symbole = sys.argv[1].split(","), sys.argv[2:]
werte = tomllib.loads(geheimnisse.pfad().read_text(encoding="utf-8"))["alpaca"]
s = requests.Session()
s.trust_env = False
s.headers.update({"APCA-API-KEY-ID": werte["key_id"], "APCA-API-SECRET-KEY": werte["secret_key"]})
for tag in tage:
p = {"symbols": ",".join(symbole), "timeframe": "1Min", "start": f"{tag}T13:30:00Z",
"end": f"{tag}T20:00:00Z", "feed": "iex", "limit": 10000, "adjustment": "raw"}
j = s.get(f"{DATEN}/v2/stocks/bars", params=p, timeout=30).json()
assert not j.get("next_page_token"), "mehr als eine Seite"
print(f"== {tag}")
for sym in symbole:
zeiten = [datetime.fromisoformat(b["t"].replace("Z", "+00:00")) for b in (j.get("bars") or {}).get(sym, [])]
luecken = [int((b - a).total_seconds() // 60) - 1 for a, b in zip(zeiten, zeiten[1:])]
ueber5 = sum(1 for x in luecken if x >= 5)
print(f"{sym:5} Kerzen {len(zeiten):3} laengste Luecke {max(luecken) if luecken else '-':>3} min Luecken >=5 min: {ueber5}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,57 @@
"""Prüft für eine Liste von Symbolen den letzten Tagesschluss gegen eine Kursgrenze. Nur lesend.
Koordinator-Werkzeug, nicht Teil des Repos. Anlass: Paket M (zehn Aktien, 2026-09-25) — eine Order
darf höchstens max_notional_je_order (10.000 USD) haben, ein Bein ist basis_menge (10) Stück, also
muss jeder Kurs unter 1.000 USD liegen. Einziger Netzzugriff: ein lesender Abruf der Tageskerzen bei
Alpaca (IEX). Ausgegeben werden Symbol, Datum der Kerze, Schlusskurs (öffentlicher Börsenkurs, kein
Depotwert) und ob er unter der Grenze liegt; nie ein Schlüssel.
Aufruf (WSL, aus dem Projektordner, TRADING_GEHEIMNISSE gesetzt):
python kurs_unter_grenze.py 1000 AAPL MSFT NVDA ...
"""
import sys
import tomllib
from pathlib import Path
import requests
sys.path.insert(0, str(Path.cwd()))
from trading import geheimnisse # noqa: E402
DATEN_URL = "https://data.alpaca.markets"
def main() -> int:
grenze = float(sys.argv[1])
symbole = sys.argv[2:]
werte = tomllib.loads(geheimnisse.pfad().read_text(encoding="utf-8"))["alpaca"]
sitzung = requests.Session()
sitzung.trust_env = False
antwort = sitzung.get(
f"{DATEN_URL}/v2/stocks/bars/latest",
params={"symbols": ",".join(symbole), "feed": "iex"},
headers={"APCA-API-KEY-ID": werte["key_id"], "APCA-API-SECRET-KEY": werte["secret_key"]},
timeout=20,
)
print("HTTP", antwort.status_code)
if antwort.status_code != 200:
print(antwort.text[:300])
return 1
bars = antwort.json().get("bars", {})
fehler = 0
for symbol in symbole:
bar = bars.get(symbol)
if not bar:
print(f"{symbol:6} keine Kerze")
fehler += 1
continue
schluss = float(bar["c"])
ok = schluss < grenze
fehler += 0 if ok else 1
print(f"{symbol:6} {bar['t'][:16]} {schluss:9.2f} {'unter' if ok else 'UEBER'} {grenze:.0f}")
return 0 if fehler == 0 else 2
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,182 @@
"""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)
@@ -18,8 +18,8 @@ from trading.paper.melder import lies_ntfy_geheimnisse
def main() -> int:
config = lade_melder_config("config/paper.toml", secrets_pfad="config/secrets.toml")
thema, token = lies_ntfy_geheimnisse("config/secrets.toml")
config = lade_melder_config("config/paper.toml", secrets_pfad=None)
thema, token = lies_ntfy_geheimnisse()
kopf = {"Authorization": f"Bearer {token}"} if token else {}
try:
antwort = requests.get(
@@ -105,7 +105,7 @@ def main():
mitternacht = datetime.strptime(datum, "%Y-%m-%d").replace(tzinfo=HANDELSZONE)
config = lade_paper_config("config/paper.toml")
killswitch_config = lade_killswitch_config("config/killswitch.toml")
quelle = AlpacaKerzenquelle.from_secrets("config/secrets.toml", feed=config.feed, timeout=10.0)
quelle = AlpacaKerzenquelle.from_secrets(None, feed=config.feed, timeout=10.0)
kerzen = list(quelle.kerzen_des_tages(config.instrument_id, handelsende(mitternacht)))
print(f"{datum}: {len(kerzen)} IEX-Kerzen in {config.instrument_id}")
schluesse = [k.close for k in kerzen]
@@ -39,6 +39,7 @@ import requests
from nautilus_trader.model.data import BarType
from nautilus_trader.test_kit.providers import TestInstrumentProvider
from trading import geheimnisse
from trading.data.loaders import alpaca_json_zu_bars
from trading.data.loaders import nur_regulaere_handelszeit
from trading.paper.beobachtung import baue_beobachtung
@@ -68,7 +69,7 @@ def main() -> int:
ende = handelsende(tag)
# Weg des Runners.
kerzen = AlpacaKerzenquelle.from_secrets("config/secrets.toml", feed="iex").kerzen_des_tages(
kerzen = AlpacaKerzenquelle.from_secrets(None, feed="iex").kerzen_des_tages(
"AAPL.XNAS", ende
)
print("Kerzen ueber die Kerzenquelle:", len(kerzen))
@@ -77,7 +78,7 @@ def main() -> int:
return 3
# Weg des Trainings: dieselbe Anfrage noch einmal roh, dann die Umwandlung des Katalogs.
geheim = tomllib.loads(Path("config/secrets.toml").read_text(encoding="utf-8"))["alpaca"]
geheim = tomllib.loads(geheimnisse.pfad().read_text(encoding="utf-8"))["alpaca"]
kopf = {"APCA-API-KEY-ID": geheim["key_id"], "APCA-API-SECRET-KEY": geheim["secret_key"]}
parameter = {
"symbols": "AAPL",
@@ -21,7 +21,7 @@ from trading.broker.alpaca import AlpacaBroker # noqa: E402
from trading.risk.limits import lade_limits # noqa: E402
limits = lade_limits("config/risk.toml")
broker = AlpacaBroker.from_secrets("config/secrets.toml", universum=limits.universum, feed="iex", timeout=10.0)
broker = AlpacaBroker.from_secrets(None, universum=limits.universum, feed="iex", timeout=10.0)
def form(objekt) -> str:
@@ -13,7 +13,7 @@ Was es nicht sieht: ob das Dashboard laeuft (das fragt der Koordinator unter Win
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.
Liest die Geheimnisdatei ueber den Aufloeser des Projekts (TRADING_GEHEIMNISSE, sonst config/secrets.toml) und gibt die Schluessel nie aus.
Aufruf aus dem Projektordner (WSL, Venv): python vorstart_pruefung.py
"""
@@ -27,6 +27,9 @@ from pathlib import Path
import requests
sys.path.insert(0, str(Path.cwd()))
from trading import geheimnisse # noqa: E402 -- aus dem Projektordner, wie oben verlangt
HANDEL_URL = "https://paper-api.alpaca.markets"
@@ -59,9 +62,9 @@ def main() -> int:
if zeile and zeile[0] == "gesperrt":
befunde.append("Killswitch ist gesperrt; Fortsetzen allein genuegt dann nicht (Freigeben im Dashboard)")
secrets = Path("config/secrets.toml")
secrets = geheimnisse.pfad()
if not secrets.is_file():
print("VORAUSSETZUNG FEHLT: config/secrets.toml")
print(f"VORAUSSETZUNG FEHLT: {geheimnisse.beschreibung()}")
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"]}