The Bot Ledger
DE

Start / Anleitungen

Kalshi-API-Anleitung: Trading-Bot in 5 Schritten anbinden

Kalshi-API für einen Bot, in Python: signierte Anfragen, die Form des Orderbuchs, Orders platzieren und stornieren, Fills und kostenlose Candlestick-Historie.

Server racks in a data center lit blue
Auth
API-Key-ID plus eine RSA-Signatur über Zeitstempel, Methode und Pfad, bei jeder Anfrage.
Lesen
Eine REST-API für Märkte, Orderbücher, Candlesticks; ein Websocket für Fills und Orderbuch-Deltas.
Handeln
Limit-Orders in Cent oder Dollar pro Kontrakt; das Orderbuch ist in Kontrakten.
Taker-Gebühr
0.07 x contracts x p x (1-p), pro Order auf den Cent aufgerundet. Maker zahlen auf fast allen Serien nichts.
Abrechnung
Von der Börse veröffentlicht; Krypto-Märkte werden über einen Durchschnitt abgerechnet, den die Börse während des Aufbaus streamt.
Geografie
US-Konten. Prämienprogramme sind für Nutzer außerhalb der USA geschlossen.
Doku
docs.kalshi.com

Fünf Schritte, bewusst vereinfacht. Die Endpunkt-Pfade folgen der öffentlichen Referenz zum Zeitpunkt des Schreibens; prüfe die Doku, bevor du dich darauf verlässt.

  1. Jede Anfrage signieren

    Erzeuge in den Kontoeinstellungen einen API-Key; du bekommst eine Key-ID und eine Datei mit dem privaten Schlüssel. Jede Anfrage trägt die ID, einen Millisekunden-Zeitstempel und eine RSA-PSS-Signatur über Zeitstempel, HTTP-Methode und Pfad.

    import time, base64, requests
    from cryptography.hazmat.primitives import hashes, serialization
    from cryptography.hazmat.primitives.asymmetric import padding
    
    BASE = "https://api.elections.kalshi.com/trade-api/v2"
    key = serialization.load_pem_private_key(open("kalshi.pem", "rb").read(), password=None)
    
    def headers(method, path):
        ts = str(int(time.time() * 1000))
        msg = (ts + method + "/trade-api/v2" + path).encode()
        sig = key.sign(msg, padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.DIGEST_LENGTH), hashes.SHA256())
        return {"KALSHI-ACCESS-KEY": KEY_ID, "KALSHI-ACCESS-SIGNATURE": base64.b64encode(sig).decode(), "KALSHI-ACCESS-TIMESTAMP": ts}
  2. Die Märkte einer Serie auflisten

    r = requests.get(f"{BASE}/markets", params={"series_ticker": "KXBTC15M", "status": "open"}, headers=headers("GET", "/markets"))
    markets = r.json()["markets"]
    ticker = markets[0]["ticker"]
  3. Das Orderbuch lesen

    Der Orderbuch-Endpunkt hat seine Form geändert: die neuere Form listet pro Stufe den Preis in Dollar und die Größe. Ein Parser, der für die alten Cent-Arrays geschrieben wurde, liefert ohne Fehler eine Tiefe von null.

    path = f"/markets/{ticker}/orderbook"
    ob = requests.get(BASE + path, headers=headers("GET", path)).json()["orderbook_fp"]
    yes_bids = ob["yes_dollars"]      # [[price_dollars, size], ...]
    no_bids  = ob["no_dollars"]
    best_yes_ask = 1 - float(no_bids[-1][0])   # ein YES-Ask ist das Spiegelbild des besten NO-Bids

    Die Preisleiter kann bei einem spürbaren Anteil der Snapshots gekreuzt sein (bestes YES-Bid plus bestes NO-Bid über 1.00). Sichere das ab, bevor du quotest.

  4. Eine Order platzieren und stornieren

    order = {"ticker": ticker, "action": "buy", "side": "yes", "type": "limit",
             "count": 20, "yes_price": 85, "client_order_id": "win-1757500800"}
    r = requests.post(f"{BASE}/portfolio/orders", json=order, headers=headers("POST", "/portfolio/orders"))
    order_id = r.json()["order"]["order_id"]
    
    requests.delete(f"{BASE}/portfolio/orders/{order_id}", headers=headers("DELETE", f"/portfolio/orders/{order_id}"))
  5. Fills und Settlements lesen

    fills = requests.get(f"{BASE}/portfolio/fills", headers=headers("GET", "/portfolio/fills")).json()["fills"]
    settled = requests.get(f"{BASE}/portfolio/settlements", headers=headers("GET", "/portfolio/settlements")).json()

    Nutze in einem Live-Bot den Websocket für Fills; das Abfragen des Order-Endpunkts ist ratenbegrenzt.

Kostenlose Historie, die niemand erwähnt

Der Candlesticks-Endpunkt liefert die Bid-, Ask- und Volumenhistorie jedes Markts im gewählten Intervall, zurück bis zur Listung. Für die Recherche ersetzt er einen Logger, den du noch nicht geschrieben hast.

path = f"/series/KXBTC15M/markets/{ticker}/candlesticks"
c = requests.get(BASE + path, params={"start_ts": t0, "end_ts": t1, "period_interval": 1}, headers=headers("GET", path)).json()