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:
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"]}
|
||||
|
||||
Reference in new issue
Block a user