ibkr-httpapi: Sechs Assetklassen, Keine Windows-VM, Verdammt Echtes Geld

Ich habe schon einen Monat meines Lebens dafür verbrannt, mt5-httpapi zu bauen, eine komplette Windows-VM, die unter QEMU/KVM innerhalb von Docker hochfährt, nur damit ich mit einem Broker reden kann, der ausschließlich ein Windows-Python-Wheel veröffentlicht. Es funktioniert. Es ist außerdem komplett irre: eine hardwarevirtualisierte Windows-11-Installation, bis auf die Knochen entrümpelt, die MetaTrader 5 im Portable-Modus fährt, damit ich einen REST-Endpoint anfassen kann statt MQL5 zu schreiben. Ich würde es wieder tun. Aber als ich anfing, Interactive Brokers anzubinden, saß ich zehn lange Minuten angespannt da und wartete auf denselben Albtraum: irgendein IBKR-Binary mit Windows-Beigeschmack, noch eine VM, noch eine 4GB-ISO zum Runterladen, noch eine Runde «warum bleibt mein Mauszeiger im noVNC-Fenster hängen».
Kam nicht. IB Gateway, das headless Trading-Terminal, das IBKR für den API-Zugang veröffentlicht, läuft nativ auf Linux. Keine .exe. Kein wine. Kein Wheel, das nur für win_amd64 baut. Nur ein JVM-Prozess, den man direkt in einen Container kippen kann. Also ist ibkr-httpapi das Schwesterprojekt, das mt5-httpapi immer verdient hätte: dieselbe Form, curl rein, JSON raus, ein Bearer-Token, ein URL-Schema pro Assetklasse, nur dass der ganze Stack aus Linux-Containern besteht. Kein KVM. Keine Windows-ISO. Kein noVNC auf einen kompletten Desktop, nur um sich einmal pro Jahrzehnt durch einen Windows-Installer zu klicken.

Mit Einem Broker Zu Reden Ist Elend

Die Geschichte der Interactive-Brokers-API ist eines dieser Dinge, an denen man versteht, warum jeder Retail-Trading-Bot auf GitHub entweder verlassen oder drei Jahre hinterher ist:

  • Die TWS-API existiert nur, wenn etwas mit grafischer Oberfläche läuft, entweder TWS selbst oder sein headless Bruder IB Gateway. Es gibt kein «ruf einen REST-Endpoint auf und fertig», es gibt ein Socket-Protokoll, das nur mit einer lebenden Desktop-Session spricht.
  • Diese Session einzuloggen heißt: durch einen Login-Screen klicken, Bedingungen akzeptieren, manchmal einen 2FA-Push auf dem Handy bestätigen, jeden Tag, weil IBKR das Gateway alle 24 Stunden zwangsweise trennt, ob es dir passt oder nicht.
  • ib_async (der gepflegte Fork des aufgegebenen ib_insync) gibt dir einen anständigen asyncio-Wrapper über diesen Socket, reicht dir aber rohe Contract-Objekte, du baust also weiterhin von Hand Stock gegen Option gegen Future gegen Bag für jede Assetklasse, jede mit anderen Pflichtfeldern.
  • IBKR sperrt dir den API-Zugang bei wiederholten Pacing-Verstößen. Es gibt kein sanftes Warnsystem, du haust zu hart auf den Endpoint für historische Daten und kassierst eine Sperre. Die meisten selbstgebauten Skripte haben null Rate-Limit-Bewusstsein, bis es sie einmal erwischt.
  • Jeder Marktdaten-Aufruf, den du machst, wird entweder weggeworfen, sobald du ihn gelesen hast, oder du schreibst dir zum vierten Mal in diesem Jahr eine eigene CSV/SQLite-Persistenzschicht von Grund auf.
  • Du willst RSI oder MACD auf den Kerzen, die du gerade geholt hast? Das ist noch eine Abhängigkeit, noch eine Indikator-Bibliothek, noch ein Satz Sonderfälle rund um NaN-Aufwärmphasen.

Nichts davon ist wirklich IBKRs Schuld, das ist eine Broker-API auf Profi-Niveau, kein Spielzeug, aber es bedeutet, dass jedes Projekt, das «gib mir AAPLs OHLC über HTTP» will, am Ende dieselben sechs Dinge schlecht neu erfindet. Ich hatte es satt, sie neu zu erfinden, also habe ich das Ding einmal richtig gebaut und eine Spec davorgesetzt.

Der Stack

Der Stack ist ein FastAPI-Service (ibkrapi/) vor einem IB-Gateway-Container, der über den TWS-API-Socket via ib_async mit ihm redet. Nichts Exotisches, eine einzelne geteilte IB-Instanz, von einem asyncio.Lock bewacht, damit gleichzeitige Requests nicht im Connect-Aufruf übereinander stolpern, mit exponentiellem Backoff (startet beim konfigurierten reconnect_backoff, verdoppelt bis reconnect_max_backoff), falls der Socket je wegbricht. Jeder Router zieht die Verbindung über eine einzige Funktion, get_ib(), der erste Aufrufer wartet auf den Handshake, alle anderen benutzen ihn weiter. Wenn IBKR das Gateway für seinen täglichen Neustart zwangsweise trennt, verbindet der nächste eingehende Request bei Bedarf neu, statt die gesamte API zu killen.
Der Gateway-Container baut auf gnzsnz/ib-gateway-docker auf, mit eingebackenem IBC (IB Controller), der den headless Login-Ablauf übernimmt, Benutzername und Passwort kommen aus einer gitignorierten .env.ibkr, und IBC steuert die Login-Oberfläche stellvertretend für IB Gateway, damit niemand die Maus bewegen muss. Ein rechtliches Detail, das man kennen sollte: IBKRs Installer-Lizenz verbietet die Weiterverteilung vorgebauter Images, die ihr Binary enthalten, also baust du das Gateway-Image lokal, statt dir den Docker-Hub-Tag irgendeines Fremden mit eingebackenem IBKR-Installer zu ziehen. docker-compose.yml.example schreibt das aus und liefert standardmäßig ein veränderliches :stable-Tag, das du auf einen Digest festnageln sollst, sobald du dein eigenes gebaut hast.

Sechs Assetklassen, Ein URL-Schema

Jeder Markttyp bekommt sein eigenes Präfix, statt eines überladenen «symbol»-Endpoints, der stillschweigend rät, was du gemeint hast:

/stocks/<symbol>              Equities (STK)
/options/<symbol>             Options (OPT) — ?expiry=YYYYMMDD&strike=N&right=C|P
/options/<symbol>/chain       Full option chain — all strikes × expirations
/futures/<symbol>             Futures (FUT) — ?expiry=YYYYMM&exchange=CME
/futures/<symbol>/continuous Continuous future — no expiry needed
/cfd/<symbol>                 CFDs
/forex/<pair>                 Currencies (CASH) — IDEALPRO default
/crypto/<symbol>              Crypto (CRYPTO) — PAXOS default

Aktien, Optionen, Futures, CFDs, Forex, Krypto, sechs Assetklassen, eine Contract-Factory für jede in ibkrapi/contracts.py, jede verlangt genau die Disambiguierungsparameter, die diese Klasse tatsächlich braucht (Optionen wollen Expiry/Strike/Right, Futures wollen Expiry/Exchange, Aktien wollen so gut wie nichts) und füllt den Rest aus config.yaml:contract_defaults.<class>, damit du nicht bei jedem Aufruf «SMART/USD» wiederholst. Wo es für die Assetklasse Sinn ergibt, bekommst du dieselben vier Verben: Kontraktdetails, einen Live-Tick-Snapshot (mit angehängten Griechen bei Optionen), historische Kerzen und rohe historische Ticks. Über den sechs Marktdatenklassen liegen assetklassenübergreifende Ordererfassung, eine Positionsliste, die Kontoübersicht und eine Historie aus Ausführungen und abgeschlossenen Orders, alles unter demselben /v1-Präfix serviert.

curl -H "Authorization: Bearer $API_TOKEN" 
    "http://localhost:8889/v1/stocks/AAPL/rates?duration=30+D&barSize=1d"
curl -H "Authorization: Bearer $API_TOKEN" 
    "http://localhost:8889/v1/options/AAPL/tick?expiry=20260619&strike=200&right=C"
curl -H "Authorization: Bearer $API_TOKEN" 
    "http://localhost:8889/v1/futures/ES/continuous?exchange=CME"

Spec Zuerst, Nicht Nach Gefühl

Keiner der obigen Router ist ein handgeschriebener FastAPI-Decorator, der über Dateien verstreut liegt und hofft, synchron zu bleiben. api/v1.yaml ist die tatsächliche Quelle der Wahrheit, ein OpenAPI-3.1.0-Dokument mit 42 Operationen und 34 Schemas, und make generate ist das Dach-Target, das daraus drei verschiedene Generatoren antreibt: fastapi-codegen (mit eigenen Jinja-Templates) wirft ibkrapi/api/_generated/{models.py, routers/*.py} als async Stub-Handler aus, die direkt an das handgeschriebene ibkrapi/api/impl.py delegieren; oapi-codegen gibt einen vollständig typisierten Go-Client in pkg/clients/go/client.gen.go aus; und openapi-python-client erzeugt ein eigenständiges, installierbares Python-Client-Paket. Neun Router werden in server.py in die App gehängt, system, stocks, options, futures, cfd, forex, crypto, orders, history, alle unter einem festen /v1-Präfix. Niemand editiert etwas unter _generated/ oder pkg/clients/* von Hand; du änderst die Spec, lässt den Generator neu laufen, und die Drift-Prüfung in der CI killt den Build, falls du es vergisst.

MCP, Damit Man Einem Agenten Kein curl-Handbuch Geben Muss

Dieselbe FastAPI-App hängt außerdem einen MCP-Server unter /mcp ein, streamable-HTTP, in der App-Wurzel, nicht unter dem /v1-Präfix. Er startete sein Leben mit drei generischen Tools (ping, endpoints, request), die einen Agenten jede REST-Route über Methode und Pfad aufrufen ließen. Funktioniert technisch, bedeutet aber, dass das Modell zur Laufzeit einen OpenAPI-Katalog runterlädt und sich dann durch ein Pfad-Template rät, was genau die «lies erst die Doku»-Steuer ist, die die Spec eigentlich abschaffen sollte. Das flog also raus: /mcp stellt jetzt 24 dedizierte, typisierte Tools bereit, jedes mit typisierten Parametern und einer Beschreibung, die der Agent liest. Das Schema ist die Doku.
Der interessante Teil ist, was mit den sechs Assetklassen passierte. Über REST sind das sechs URL-Präfixe. Über MCP klappen sie hinter ein einziges asset_class-Enum zusammen, stock, option, future, cfd, forex, crypto, sodass get_contract, get_quote, get_rates und get_rates_ta die Arbeit von sonst 24 fast identischen Tools pro Klasse machen, die den Kontext des Agenten für null Bedeutungsgewinn aufblähen. Drumherum liegen die Spezialfälle, die nur für eine Klasse Sinn ergeben (get_option_chain, place_option_combo, exercise_option, get_future_continuous, list_future_contracts, get_stock_ticks), die Order-Familie (list_orders, get_order, place_order, cancel_order, cancel_all_orders), Konto und Positionen (get_account, get_account_values, list_accounts, list_positions), Historie (get_executions, get_completed_orders), sowie ping plus das ursprüngliche endpoints/request-Paar, das als Notausgang für alles ohne dediziertes Tool bleibt.
Nichts davon ist eine Parallelimplementierung neben der REST-API, die darauf wartet, von ihr wegzudriften. Jedes Tool dispatcht in-process durch dieselben Router, dieselbe Validierung, dieselbe Bearer-Auth und denselben Rate-Limiter wie ein HTTP-Aufruf, ein Agent, der get_rates vollhämmert, verbrennt also dasselbe historische 10-Minuten-Pacing-Budget wie curl, und kassiert denselben 429 RATE_LIMIT_NEAR-Umschlag, wenn er die Linie überschreitet. Lass api_token leer und beide Oberflächen sind exakt gleich unauthentifiziert.
Tools, die etwas verändern, tragen ihre Warnung im Schema selbst, dem einzigen Ort, an den das Modell garantiert schaut. Die Beschreibung von place_order endet mit «Dies platziert eine echte, unumkehrbare Order auf einem Live-Brokerage-Konto, ruf es nur auf, wenn der Nutzer es ausdrücklich verlangt hat, und bestätige vorher die Parameter.» Das ist keine Fußnote in einer README, die der Agent nie öffnen wird; es steht in der Tool-Definition, bei jedem Aufruf mitgeliefert.
Es steckt auch ein Stück MCP-Client-Realität drin. Starlettes Mount("/mcp", ...) serviert unter /mcp/* und schickt einen 307 auf die nackte Form /mcp, und ein guter Teil der MCP-Clients folgt dieser Weiterleitung bei einem POST schlicht nicht. Statt das halbe Ökosystem an einem Spec-Detail zerbrechen zu lassen, gibt es eine Middleware, die nacktes /mcp vor dem Routing zu /mcp/ umschreibt und weitermacht.
Zum Anbinden an einen Client gibt es ein @psyb0t/ibkr-httpapi-Plugin, eine dünne stdio-zu-HTTP-Brücke, über IBKR_HTTPAPI_URL auf deine Instanz gerichtet (die Server-Wurzel; ein angehängtes /v1 wird abgeschnitten, weil das jeder genau einmal falsch macht) plus IBKR_HTTPAPI_TOKEN, wenn Auth an ist. In Claude Code sind das zwei Zeilen:

claude plugin marketplace add psyb0t/agents
claude plugin install ibkr-httpapi@psyb0t

Was die Warnung weiter unten auf dieser Seite deutlich schärfer macht: ein Agent mit place_order und cancel_all_orders in seiner Toolliste ist eine schlechte Inferenz davon entfernt, etwas Teures zu tun. Das Schema sagt ihm, er soll vorher bestätigen. Sorg dafür, dass ihm das noch etwas anderes sagt.

Auth, Weil Das Hier Kein Spielzeug Ist

Bearer-Token, geprüft mit hmac.compare_digest statt mit nacktem ==, weil ein naiver String-Vergleich Timing-Informationen leakt, mit genug Requests kannst du ein Token Zeichen für Zeichen bisektieren. Setz api_token in config.yaml (oder API_TOKEN per Env) und jeder Request braucht Authorization: Bearer <token> oder kassiert einen 401 mit dem Standard-Fehlerumschlag: {code, message, details}. Lass das Token leer und die API steht sperrangelweit offen für alles, was den Socket erreicht, was bei einem Loopback-only-Deployment hinter nginx in Ordnung ist und überall sonst eine miserable Idee, und die Projektdokumentation ist da unmissverständlich.

Pacing + Die Goldgrube

Jeder Aufruf Richtung IBKR läuft durch einen präventiven Rate-Limiter, bevor er den Socket überhaupt anfasst, weil IBKR bei wiederholten Pacing-Verstößen den API-Zugang abschaltet und «sorry, mein Bot wusste das nicht» keine funktionierende Verteidigung ist. Drei Stufen, jede mit eigenem Sliding-Window-Zähler plus einem asyncio.Lock pro Kontrakt plus einem globalen Nebenläufigkeits-Semaphor: historical-Datenaufrufe sind auf soft-50/hard-55 pro 10-Minuten-Fenster gedeckelt (IBKRs harte Grenze liegt bei 60), market_data-Aufrufe bleiben unter der ~50-Nachrichten-pro-Sekunde-Decke des TWS-Sockets, und orders werden am härtesten gedrosselt, 5/Sek., 3 gleichzeitig, weil eine Flut von Order-Aufrufen fast nie Absicht ist. Überschreite die weiche Grenze und du bekommst eine Warnung in den Logs; überschreite die harte und der Aufrufer kassiert einen 429 RATE_LIMIT_NEAR-Umschlag mit der genauen Regel, der Nutzung, dem Limit und dem Retry-After eingebacken in details.
Hinter derselben Tür wird alles Cachebare bei jedem Aufruf unter data/history/ auf die Platte geschrieben, Kerzen und Ticks gehen in CSV-Dateien pro (Assetklasse, Symbol, Timeframe) in genau der Form, die wickworks frisst, Kontraktdetails und Optionsketten-Metadaten bekommen einen JSON-Cache mit langer TTL, und jeder Tick- oder Ketten-Snapshot wird an eine historische Aufzeichnung angehängt. Nichts wird je gelöscht; es ist ausdrücklich als append-only «Goldgrube» gedacht, du mountest ./data, sicherst sie, und jeder Aufruf, den du machst, sammelt sich still zu einem Langzeitdatensatz an, statt weggeworfen zu werden, nachdem du die Antwort einmal gelesen hast. Du brauchst garantiert frische Daten statt des Caches? Jeder cachebare Endpoint akzeptiert ?refresh=true, was den Cache-Read überspringt, das frische Ergebnis aber trotzdem zurückschreibt, damit der nächste Aufrufer davon profitiert.

Technische Analyse, Ohne Eine Indikator-Bibliothek Zu Schreiben

Jeder /rates-Endpoint pro Assetklasse hat einen Bruder, POST /<class>/<symbol>/rates/ta, der dieselben Kerzen holt und sie an wickworks weiterreicht, denselben TA-Sidecar, den mt5-httpapi schon benutzt. RSI, MACD, Bollinger-Bänder, ADX, ATR, VWAP, Ichimoku, Order Blocks, Fair Value Gaps, BOS/CHoCH-Strukturbrüche, Swing-Struktur, Support- und Widerstandsniveaus, Liquiditätszonen, Session-Anker, serverseitig berechnet, in einem Aufruf, auf Kerzen, die du ohnehin schon hast. Seit dem letzten Update ist das schlauer geworden: der TA-Pfad setzt jetzt auf demselben Kerzen-Cache auf, den /rates benutzt, statt einen eigenen separaten Fetch zu machen, eine wiederholte TA-Anfrage gegen gecachte Kerzen kostet also null IBKR-Pacing-Budget und liefert trotzdem frische Indikatorberechnungen. wickworks bleibt bewusst strikt primitiv, rohe Serien und strukturelle Fakten, nie «kaufen» oder «verkaufen», wenn du also Meinungen willst, baust du sie in deinem eigenen Consumer, nicht im Sidecar.
Du richtest ihn mit wickworks.url in der Config auf deine eigene Instanz; lass es leer und /rates/ta gibt einfach sauber einen 503 zurück, statt so zu tun, als funktioniere es. Der ausgehende Aufruf selbst ist schemabeschränkt, nur http:// und https://, genau damit eine falsch konfigurierte URL nicht zu einem SSRF gegen etwas wie file:// verbogen werden kann.

Warum Zur Hölle Ein Headless Gateway Eine VNC-Oberfläche Braucht

Faire Frage, denn IB Gateway ist nicht wirklich headless im klassischen Sinn, es ist eine Java-Swing-GUI-Anwendung, die unter einem virtuellen Framebuffer (Xvfb) im Container läuft. IBC steuert diese GUI programmatisch, um sich einzuloggen und sich durch die Dialoge des täglichen Neustarts zu klicken, und kommt in der überwältigenden Mehrheit der Fälle ohne Menschen in der Nähe zurecht. Aber IBKR wirft gelegentlich einen Stock zwischen die Speichen: ein wöchentlicher 2FA-Push, der abläuft, ein unerwarteter Dialog, den IBCs Automatisierung nicht erkennt, eine «neues Gerät»-Bestätigung beim ersten Hochfahren eines frisch gebauten Gateways. Wenn das passiert, musst du den Desktop hinter Xvfb tatsächlich *sehen*, und dafür ist Dockerfile.novnc da, ein kleiner websockify-Proxy, der den VNC-Port des Gateways (:5900) über HTTP/WebSocket nach vorne bringt, damit du den IB-Gateway-Desktop aus einem normalen Browser-Tab anschauen (und anklicken) kannst, ohne nativen VNC-Client. Es ist keine vollständige virtuelle Maschine wie das dockurr/windows-Setup, das mt5-httpapi braucht, da ist kein Betriebssystem zu booten, es ist ein 57-zeiliges, per Digest festgenageltes python:3.12-slim-Image, das websockify fährt und dessen Entrypoint lediglich dein VNC-Passwort in index.html einsetzt, damit es sich von allein direkt zur Session verbindet. Du richtest einen Browser drauf, wenn etwas klemmt, reparierst den Dialog, schließt den Tab und vergisst, dass es existiert, bis zum nächsten wöchentlichen 2FA-Schluckauf.

Container-Härtung, Weil Das Hier Geld Anfasst

Die API- und wickworks-Container laufen mit cap_drop: [ALL], read_only: true-Root-Dateisystemen mit noexec,nosuid-tmpfs-Mounts für die Teile, die schreiben müssen, no-new-privileges:true, und Speicher-, CPU- und PID-Limits pro Service. Das Netzwerk zerfällt in drei isolierte Docker-Netze: front (nginx redet mit der API), backend (die API redet mit dem Gateway, das ausgehend zur IBKR-Cloud muss), und ein internal: true-Netz für den API-zu-wickworks-Verkehr, das überhaupt keinen Weg nach draußen hat, wickworks kann physisch nicht nach Hause telefonieren, selbst wenn du es wolltest. Der Gateway-Container ist die eine Ausnahme, die nicht vollständig zugesperrt laufen kann (Xvfb plus eine JVM plus IBC, das quer übers Dateisystem schreibt, verträgt kein read-only Root), er bekommt also no-new-privileges als Untergrenze. Alle öffentlichen Basis-Images sind per SHA-Digest festgenagelt, Python-Abhängigkeiten sind mit uv pip compile --generate-hashes hash-verriegelt und mit --require-hashes installiert, es gibt eine rollende 7-Tage-Altersschranke für neue Abhängigkeitsversionen, damit ein frisch veröffentlichtes, in der Lieferkette vergiftetes Paket nicht am selben Tag landen kann, und make audit / make audit-go / make audit-compose fahren pip-audit, govulncheck beziehungsweise einen grep-basierten Compose-Scanner (verbotene Einstellungen wie privileged, pid:host, Docker-Socket-Mounts, unfixierte Tags, öffentlich exponierte Ports).


Der Teil, In Dem Das Ding Ein Verdammtes Echtes Brokerage-Konto Bedient

Ich vergrabe das nicht in einer Fußnote. Das ist kein Marktdaten-Spielzeug, POST /orders platziert eine echte Order gegen ein echtes IBKR-Konto und bewegt echtes Geld in dem Moment, in dem sie angenommen wird. Es gibt bewusst keinen Endpoint zum Ändern von Orders: um eine ruhende Order zu ändern, stornierst du sie (DELETE /orders/{orderId}) und platzierst absichtlich eine neue, statt eine lebende Order im Flug zu mutieren. DELETE /orders ohne ID storniert *alle* offenen Orders des Kontos auf einen Schlag. POST /options/exercise übt echte Kontrakte aus oder lässt sie verfallen. Keines davon hat einen Rückgängig-Knopf. Wenn du einen Agenten oder ein Skript auf dieses Ding richtest, lass es einem Menschen das aufgelöste Symbol, die Richtung, die Menge und den Preis bestätigen, bevor irgendetwas Veränderndes feuert, und lass es niemals eine abgelehnte Order automatisch wiederholen, eine Ablehnung ist ein Stoppsignal, kein Bug, den man umschifft.
Zwei Dinge dämpfen den Explosionsradius, wenn du sie willst: TRADING_MODE=paper in .env.ibkr verbindet mit IBKRs Paper-Trading-Gateway (Port 4002) statt mit dem Live-Gateway (4001), Paper-Kontonummern beginnen mit DU, Live-Nummern mit U, und /accounts sagt dir, mit welchem du tatsächlich redest. Und IBC unterstützt READ_ONLY_API=yes, was die gesamte Trading-API auf Gateway-Ebene sperrt, falls du von dem Ding nur Marktdaten und Kontoeinblick willst. Lass api_token leer und diese gesamte Oberfläche, Marktdaten, Positionen und Orderplatzierung gleichermaßen, ist für alles unauthentifiziert, was den Port erreicht. Setz das Token. Lausch auf Loopback. Sei nicht der Grund, warum das Skript von jemand anderem eine Order auf deinem Konto platziert.

Unterm Strich

War mt5-httpapi «bring MetaTrader 5 dazu, HTTP zu sprechen, auch wenn Windows im Weg steht», dann ist ibkr-httpapi die Version, in der Windows nie im Weg stand, IB Gateway läuft einfach auf Linux wie ein normales Stück Serversoftware, also besteht der ganze Stack aus Containern, einer Spec-First-API mit generierten Clients in zwei Sprachen, 24 typisierten MCP-Tools, die durch dieselben Handler laufen, wenn ein Agent fährt, Pacing, das dich vor einer IBKR-Sperre bewahrt, einem Platten-Cache, der jeden Aufruf in permanente Daten statt in weggeworfenes JSON verwandelt, und demselben wickworks-TA-Sidecar, das die Indikator-Mathematik serverseitig erledigt. Sechs Assetklassen, ein Bearer-Token, null Windows-VMs. Hol es dir auf github.com/psyb0t/ibkr-httpapi, lies die Lizenzhinweise, bevor du das Gateway-Image baust, und setz ein API-Token, bevor du das irgendwo anders als auf localhost freigibst. Es steht unter WTFPL, mach damit, was zur Hölle du willst, gib mir nur nicht die Schuld, wenn dein Bot 500 Puts auf den falschen Ticker kauft.