{"id":49747344,"url":"https://github.com/osauer/ibkr","last_synced_at":"2026-05-24T06:01:24.585Z","repository":{"id":356799333,"uuid":"1234071553","full_name":"osauer/ibkr","owner":"osauer","description":"Read-only Interactive Brokers (IBKR) client - Go CLI, stdio MCP server, and Go library. The data flows out; orders don't go in.","archived":false,"fork":false,"pushed_at":"2026-05-21T20:21:15.000Z","size":2143,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-21T22:46:08.040Z","etag":null,"topics":["claude-code","claude-desktop","finance","golang","ibkr","interactive-brokers","mcp","mcp-server","model-context-protocol","options","trading","tws-api"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/osauer.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-09T17:59:39.000Z","updated_at":"2026-05-21T20:17:44.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/osauer/ibkr","commit_stats":null,"previous_names":["osauer/ibkr"],"tags_count":107,"template":false,"template_full_name":null,"purl":"pkg:github/osauer/ibkr","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osauer%2Fibkr","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osauer%2Fibkr/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osauer%2Fibkr/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osauer%2Fibkr/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/osauer","download_url":"https://codeload.github.com/osauer/ibkr/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osauer%2Fibkr/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33423284,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-23T22:14:44.296Z","status":"online","status_checked_at":"2026-05-24T02:00:06.296Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["claude-code","claude-desktop","finance","golang","ibkr","interactive-brokers","mcp","mcp-server","model-context-protocol","options","trading","tws-api"],"created_at":"2026-05-10T06:06:09.673Z","updated_at":"2026-05-24T06:01:24.559Z","avatar_url":"https://github.com/osauer.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ibkr\n\n[![ci](https://github.com/osauer/ibkr/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/osauer/ibkr/actions/workflows/ci.yml)\n[![release](https://img.shields.io/github/v/release/osauer/ibkr?display_name=tag\u0026sort=semver)](https://github.com/osauer/ibkr/releases/latest)\n[![go.mod](https://img.shields.io/github/go-mod/go-version/osauer/ibkr)](go.mod)\n[![Go reference](https://pkg.go.dev/badge/github.com/osauer/ibkr.svg)](https://pkg.go.dev/github.com/osauer/ibkr)\n[![license](https://img.shields.io/github/license/osauer/ibkr)](LICENSE)\n\n**A read-only client for your Interactive Brokers account.** One Go binary, three surfaces — CLI, stdio MCP server, Go library — all returning the same JSON. No Python or Java runtime to install.\n\n![ibkr positions — stocks and options grouped by underlying, with per-leg Greeks (Δ Γ Θ ν) and a portfolio rollup of effective delta, dollar delta, daily theta, and FX sensitivity](docs/social/positions.png)\n\n```sh\n$ ibkr account --watch        # in-place refresh, ^C to stop\n\n$ ibkr quote AAPL --json | jq '{last, prev_close, change, change_pct}'\n{\n  \"last\": 207.42,\n  \"prev_close\": 206.10,\n  \"change\": 1.32,\n  \"change_pct\": 0.64\n}\n\n$ ibkr quote SPY --watch        # streaming, ^C to stop\n  14:32:01    583.18  1.2k    583.21  800     583.20\n  14:32:02    583.19    900   583.22  650     583.21\n  14:32:03    583.21  1.1k    583.24  720     583.23\n```\n\nFrom a Claude Desktop or Claude Code session with `ibkr mcp` wired up:\n\n\u003e *\"What's in my IBKR account and how am I doing this week?\"*\n\u003e *\"Show me my AAPL position with today's P\u0026L and the option-leg deltas.\"*\n\u003e *\"What expiries are available for NVDA, with ATM IV?\"*\n\u003e *\"If I buy 100 MSFT at 418 with a stop at 408, what's the EUR risk?\"*\n\nRead-only is structural — four independent layers refuse `order`, `trade`, `cancel`. [Details](#safety).\n\n**Contents** — [Install](#install-in-two-commands) · [Features](#what-you-get) · [Pick your path](#pick-your-path) · [Architecture](#architecture) · [Protocol coverage](#protocol-coverage) · [Configure](#configure) · [Safety](#safety) · [Other install paths](#other-install-paths) · [Testing](#testing) · [Troubleshooting](#troubleshooting)\n\n## Install in two commands\n\n```sh\ncurl -fsSL https://raw.githubusercontent.com/osauer/ibkr/main/install.sh | sh\nibkr setup claude-desktop\n```\n\nThe installer detects your OS/arch, fetches the matching tarball from the latest release, verifies the SHA-256, drops `ibkr` in `~/.local/bin`, clears macOS Gatekeeper quarantine, and adds `~/.local/bin` to your shell rc if it isn't on PATH. The second command writes the MCP server entry into Claude Desktop's config — quit Claude (⌘Q) and relaunch. Skip it if you only want the shell tool. [Other install paths.](#other-install-paths)\n\n**Prerequisites.** A running [IB Gateway](https://www.interactivebrokers.com/en/trading/ibgateway-stable.php) 10.37+ or TWS (paper or live) on the same machine. Auto-discovered on the four standard ports. An **IBKR Pro** account (IBKR Lite cannot use the TWS API).\n\n## What you get\n\n- **Account snapshot.** NLV, buying power, cash, margin, available funds, gross position value, session unrealized/realized P\u0026L, the margin cushion, and IBKR's start-of-trading-day Daily P\u0026L (account-level) — rendered in the account's base currency with the right symbol (`€`, `$`, `£`, `¥`, or the ISO code). Margin accounts also get the look-ahead block (post-overnight-cycle projections of init/maint/available/excess), which catches \"fine intraday, blown by 5pm\" cases. Multi-currency accounts get a `currency_exposure` block: one row per non-base holding with the gateway-reported FX rate and the base-currency conversion.\n- **Positions with live Greeks.** Each option leg carries delta, gamma, theta, vega, plus its own bid/ask, IV, and prior settle — so wide spreads on illiquid contracts are visible and option-level daily P\u0026L is unambiguous. A `DAY P\u0026L` column carries IBKR's per-conId start-of-trading-day delta (from `reqPnLSingle`) for both stocks and options — one consistent metric across the book. The column always renders; nil rows show em-dash so the field is discoverable on the first call. A `portfolio` block aggregates effective delta in share-equivalents, dollar delta, daily theta, gamma, and vega, with an FX-sensitivity rollup for multi-currency books. `--by underlying` consolidates stock and option legs per name. `ibkr positions --watch` re-polls in place.\n- **Quotes — snapshot and streaming.** `ibkr quote AAPL` for a snapshot; `ibkr quote AAPL --watch` for coalesced live ticks. Prev-close, daily change, and change-% on every row (pre-market: yesterday's close arrives even when regular-session ticks haven't started). Options addressed as `SYM YYMMDD C|P STRIKE`. Streaming is also exposed as an MCP resource subscription — multiple watchers share one IBKR market-data line per symbol.\n- **Option chains.** `ibkr chain SPY` lists expiries with ATM implied vol, days-to-expiry, and the 1-σ **implied move** (`spot × IV × √(DTE/365)` — the desk-standard \"expected move by expiry\" used for earnings sizing and strike selection) by default; `--expiry 2025-12-19` switches to the strike grid. Per-strike call/put delta surfaces on the wire. Chain IV is cached daemon-side with phase-aware TTL (60 s during RTH, 4 h otherwise) so repeated lookups within a decision pause cost zero gateway round trips.\n- **Daily OHLCV history.** `ibkr history AAPL --days 30`.\n- **Scanners — preset or ad-hoc.** Seven built-in presets (`top-movers`, `top-losers`, `most-active`, `unusual-vol`, `gappers`, `high-iv-rank`, `unusual-opt-vol`) covering direction, volume, opening gaps, and option-flow signals. `ibkr scan \u003cpreset\u003e` for the shorthand; `ibkr scan --type SCANCODE --exchange LOCATIONCODE` for ad-hoc queries; `ibkr scan params [--instrument STK]` to dump the gateway's valid `scanCode` / `locationCode` catalog. Add your own presets in `config.toml`.\n- **Position sizing.** `ibkr size --symbol AAPL --entry 207.50 --stop 202.50 --risk-pct 1` does fixed-fractional math against live NLV. Add `--target` to also get the **R-multiple** (reward:risk) and the breakeven win rate — the standard \"is this trade worth taking\" filter (≥ 2R typical). Pure arithmetic; never proposes or attempts an order.\n- **S\u0026P 500 market breadth.** `ibkr breadth` returns three readings every call: the percentage of S\u0026P 500 constituents above their 50-day SMA (the tactical signal), the percentage above the 200-day SMA (the slower companion that catches cyclical tops cleanly), and the count of names making fresh 52-week highs vs lows today. The narrow-rally pattern — SPX near highs with `net_new_highs_pct` near zero or negative — fires when a few mega-caps carry the index while the median name is rolling over. IBKR doesn't redistribute the underlying S\u0026P DJI / NYSE breadth indices on retail subscriptions (verified via `reqContractDetails`), so the daemon computes all three locally from the 500 constituent daily closes. A once-daily refresh post-close (16:35 ET) slides each name's 200-bar window forward and updates the 252-bar rolling max/min. **Cold start is the only long path: ~60 min on a fresh cache** — IBKR's historical-data pacing limit (60 requests per 10-min sliding window) caps the fan-out at ~6 names/min, and the cap is per-request not per-bar so pulling 262 days isn't slower than pulling 60. The autospawned daemon's default 15-minute idle window would still kill the daemon mid-bootstrap (the cold-start budget exceeds it ~4×), so spin it up explicitly with `ibkr daemon --foreground` the first time and leave it running until the indicator turns from `computing` to `ok`. After cold-start the cache persists across daemon restarts and every subsequent refresh is fast. Useful as an input to a risk-regime check; see `docs/specs/risk-regime-dashboard.md` for one such dashboard the daemon's two new endpoints (this one and the next) feed directly.\n- **SPY dealer zero-gamma — best effort.** `ibkr gamma` estimates the price level where aggregate dealer gamma flips sign, using the [Perfiliev recipe](https://perfiliev.com/blog/how-to-calculate-gamma-exposure-and-zero-gamma-level/) against IBKR's option chain (6 nearest expirations, ATM ±10 %, BS-recomputed gamma across a spot sweep). Methodology v2 fits a per-expiry quadratic skew curve in log-moneyness at snapshot time and reprices each leg's IV at the scenario-spot's moneyness during the sweep — sticky-moneyness rather than sticky-IV — so the headline shifts ~30-80 SPX points relative to v1 and tracks SpotGamma's posted numbers materially better. Method token: `perfiliev-bs-sweep-v2-stickymoneyness`. The compute runs against **SPY** (the S\u0026P 500 ETF) rather than SPX (the index) because SPY has continuous extended-hours quoting on SMART/ARCA and a single trading class. Regime signal tracks SPX dealer gamma closely; absolute level is SPY-scale (~SPX/10). The result now also carries separate γ-zero readings for the near bucket (DTE ≤ 7 days, where ~59% of 2025 SPX volume now lives) and the term bucket (DTE \u003e 7); a regime row's `horizon_agreement` field flags `diverge` when near and term γ-zero straddle spot. The compute is heavy — multi-minute fan-out across hundreds of legs; the first caller of an NY trading session kicks a background job and gets `status: \"computing\"` with an ETA. The result carries two signals on purpose: the signed Perfiliev `zero_gamma` (a regime hint that can invert near covered-call-ETF flow or autocall barriers) and a sign-agnostic `gamma_total_abs` / `top_strikes` view that's robust to the dealer-positioning assumption. **Treat the number as a regime hint, not a precise level.** Full methodology, limitations, and a manual calibration ritual against SpotGamma's public posts are documented in `docs/specs/risk-regime-dashboard.md`.\n- **Risk-regime snapshot — state of all five indicators in one call.** `ibkr regime` fetches the full risk-regime dashboard (VIX/VIX3M term structure, HYG vs SPY divergence, USD/JPY weekly move, SPY dealer zero-gamma, S\u0026P 500 breadth) in a single call, so an LLM consumer or dashboard doesn't have to know individual tickers, IBKR data paths, or the methodology spec. Each row carries a `streak: {band, sessions, since}` field counting how many consecutive trading sessions the indicator has been in its current band — useful for distinguishing day 1 of a stress event from day 5 (the spec's \"sustained 2-3 days, not single spikes\" language). The gamma row also surfaces near vs term γ-zero alongside the combined headline and flags `horizon_agreement` when the two readings disagree. The default text layout is compact — a header row keys five columns (state · indicator · value · band · note); pass `--explain` to expand into the full provenance view with day-N streak markers, ETA clocks on `computing` rows, methodology tokens, and the spec's threshold prose under each row. JSON (`--json`) is unaffected and always carries every field. The value isn't \"all five always populated\" — expect indicator 4 (gamma) to surface `status: \"computing\"` on the first call of an NY trading day, and indicator 5 (breadth) to do the same on the first call against a fresh daemon while the local 50-DMA engine bootstraps (~60 min — see the breadth bullet above for the pacing-limit reason and the `ibkr daemon --foreground` workaround). Each row carries raw measurements plus a `notes` field embedding the spec's threshold bands verbatim, and rows where an advisory sub-field didn't land within the fetch budget surface a `fields_missing` array. The MCP-equivalent tool `ibkr_regime` makes this work in natural language from a Claude Desktop conversation. The daemon does not derive green/yellow/red status from the raw measurement (the spec calls those bands user-tunable, so threshold derivation stays in the renderer or in the LLM's reasoning); the daemon-side band classification used for streak persistence is documented in the spec and exposed via the streak field's `band` only. `ibkr regime --log \u003cpath\u003e` appends today's snapshot to a JSONL file at the supplied path — useful for the 4-week SpotGamma cross-check the spec recommends; analyse with `jq` or pandas afterward.\n\nEverything supports `--json`. Tables are color-coded by sign on a terminal (P\u0026L green/red, non-live data badges yellow); pipes, redirects, and `--json` are always plain. `NO_COLOR=1` disables; `IBKR_COLOR=always|never` overrides.\n\n## Pick your path\n\n### Claude Desktop, Cursor, Continue, Zed\n\n`ibkr mcp` is a stdio MCP server. Every CLI verb an LLM should ever call has a matching MCP tool (local-config verbs like `setup` and `version` are intentionally excluded), and `make check` fails if the two surfaces drift. The server also exposes streaming quotes for stocks and ETFs as an MCP resource:\n\n- `ibkr://quote/{symbol}`\n\n`resources/subscribe` delivers coalesced ticks via `notifications/resources/updated` until you `resources/unsubscribe` or close the stdio. `ibkr setup claude-desktop` handles Claude Desktop end-to-end. For other clients, paste this into the client's MCP config (path varies):\n\n```json\n{\n  \"mcpServers\": {\n    \"ibkr\": {\n      \"command\": \"/ABSOLUTE/PATH/TO/ibkr\",\n      \"args\": [\"mcp\"]\n    }\n  }\n}\n```\n\nThe `command` must be the absolute path. `~` is not expanded by `exec` and `$PATH` is not consulted. `which ibkr` gives you the right value. After upgrading the binary, fully quit and relaunch the client — it caches the spawned server process.\n\n`claude.ai` (web) accepts only remote MCP servers and cannot reach a local IB Gateway. Use Desktop.\n\nLogs (macOS, Claude Desktop): `~/Library/Logs/Claude/mcp-server-ibkr.log`.\n\n### Claude Code\n\nInside a standalone Claude Code session:\n\n```\n/plugin marketplace add osauer/ibkr\n/plugin install ibkr@ibkr\n```\n\nOr — for **Claude for Mac**'s embedded Claude Code pane, which doesn't expose `/plugin` slash commands — from a regular terminal:\n\n```sh\nclaude plugin marketplace add osauer/ibkr\nclaude plugin install ibkr@ibkr\n```\n\nThe plugin carries a skill, a `PreToolUse` hook that hard-blocks trading verbs (failing closed if `jq` is missing from PATH), and a `SessionStart` hint when the binary isn't installed. The skill's `allowed-tools` pre-allows the read-only patterns once the skill activates. For a global allowlist that fires *before* the skill activates, copy `settings/ibkr.settings.json` into `~/.claude/settings.json` by hand.\n\n**The plugin doesn't ship the binary.** It only carries the skill, hooks, and manifest — you still need the `ibkr` binary on PATH from [Install in two commands](#install-in-two-commands). The two have independent release cadences and independent update paths:\n\n```sh\n# Binary release (new MCP tool descriptions are baked into the binary):\ncurl -fsSL https://raw.githubusercontent.com/osauer/ibkr/main/install.sh | sh\n\n# Plugin release (new skill commands, settings, hooks):\nclaude plugin update ibkr@ibkr\n```\n\nRestart the host (Claude for Mac, standalone Claude Code session, Cursor, …) after either update so it respawns the MCP server subprocess with the new descriptions and reloads the skill at the next session start.\n\n### The shell\n\n```sh\n$ ibkr account --json | jq '.net_liquidation, .base_currency'\n$ ibkr quote AAPL,MSFT --json | jq '.[] | {sym: .symbol, last: .last, chg: .change_pct}'\n$ ibkr positions --by underlying --json | jq '.portfolio.effective_delta'\n$ ibkr chain NVDA --json | jq '.expiries[] | select(.iv \u003e 0.6)'\n$ ibkr size --symbol AAPL --entry 207.50 --stop 202.50 --risk-pct 1\n```\n\n`ibkr --help` lists subcommands; `ibkr \u003ccmd\u003e --help` lists flags. `ibkr status` first if anything looks off.\n\n### Go and other agent SDKs\n\n`pkg/ibkr` speaks the TWS API protocol directly:\n\n```go\nimport \"github.com/osauer/ibkr/pkg/ibkr\"\n\ncfg := ibkr.DefaultConfig()    // 127.0.0.1:4001\ncfg.Port = 4002                // paper\n\nc := ibkr.NewConnector(\u0026ibkr.ConnectorConfig{\n    ServiceName: \"myapp\",\n    PoolConfig:  \u0026ibkr.PoolConfig{ClientIDs: []int{15}, BaseConfig: cfg},\n})\nif err := c.Start(ctx); err != nil { return err }\n\nsnap, _ := c.RequestAccountSummary(ctx, 5*time.Second)\nfmt.Printf(\"NLV: %.2f %s\\n\", *snap.NetLiquidation, snap.Currency)\n```\n\nFrom Python, TypeScript, or Rust, shell out to the CLI: subprocess in, JSON out. Wrap each `ibkr \u003ccmd\u003e --json` invocation as a function and register it with your model's tool-call API.\n\n## Architecture\n\n```mermaid\nflowchart LR\n    CLI[\"ibkr (CLI)\u003cbr/\u003estateless\"] --\u003e|JSON-RPC over Unix socket| D[\"ibkr daemon\u003cbr/\u003esame binary, stateful\"]\n    MCP[\"ibkr mcp\u003cbr/\u003estdio MCP server\"] --\u003e|same socket| D\n    D --\u003e|TWS API protocol, TCP| GW[\"IB Gateway or TWS\"]\n    D --- LIB[\"pkg/ibkr\u003cbr/\u003eshared library\"]\n```\n\nOne binary, two halves. CLI and MCP server are stateless and short-lived. The daemon (same binary, `ibkr daemon`) holds the gateway connection, contract cache (stocks + options, persisted to `~/.cache/ibkr/contracts.json` across restarts), subscription state, and a per-symbol prev-close cache. Clients reach it over a Unix socket. The daemon autospawns on the first call and idle-exits after fifteen minutes (override via `[daemon] idle_timeout`). One client ID held for its lifetime, so a single TWS handshake amortises across every subsequent call.\n\nA `flock` instance lock on `\u003csocket-dir\u003e/ibkrd.lock` makes the daemon singleton — any second `ibkr daemon` exits with `another ibkrd holds the instance lock`. So the CLI, the MCP server, and as many MCP-enabled sessions as you have open all share **one daemon, one socket, one IBKR client-ID slot at the gateway**. The MCP server (`ibkr mcp`) keeps its socket connection open for the lifetime of the client (Claude Desktop, etc.) — this is by design, so tool calls return instantly — and that pinned connection keeps the daemon's active-connections count above zero, so the daemon stays alive as long as any MCP client is live. Quit the MCP host and the daemon idles out on its usual fifteen-minute timer.\n\nThe wire between CLI and daemon has no version field today; the CLI prints a stderr warning on version skew and points at the fix (`pkill -x ibkr` and let the next call respawn).\n\n## Protocol coverage\n\n`pkg/ibkr` is a clean-room Go implementation of the TWS wire protocol — no Python bridge, no third-party dependencies on InteractiveBrokers source. The table below shows what's plumbed today. Wire ops with both a \"read-side method\" *and* a write-side counterpart (e.g. `placeOrder`) are exposed in the library but **refused at the daemon layer in v0.x** by a `//go:build !trading` stub; the binary cannot send them. The full per-method godoc lives in [pkg/ibkr/doc.go](pkg/ibkr/doc.go).\n\n| Capability                       | Wire opcodes                                                              | Library entry point                                                | Status                |\n|----------------------------------|---------------------------------------------------------------------------|--------------------------------------------------------------------|-----------------------|\n| Account summary                  | `reqAccountSummary` (62), `accountSummary` (63), `acctValue` (6)          | `Connector.RequestAccountSummary`, `GetAccountSummary`             | ready                 |\n| Positions + portfolio            | `reqPositions` (61), `position` (61), `portfolioValue` (7), $LEDGER:ALL   | `Connector.GetCachedPositions`                                     | ready                 |\n| Snapshot quote                   | `reqMktData` (1) snapshot=true, `tickPrice` (1), `tickSnapshotEnd` (57)   | `Connector.FetchMarketSnapshot`                                    | ready                 |\n| Streaming quote                  | `reqMktData` (1) snapshot=false, `tickPrice`/`tickSize`/`tickGeneric`     | `Connector.SubscribeMarketData`, `GetMarketData`                   | ready                 |\n| Generic-tick set                 | gen-ticks 100, 101, 104, 106, 165 (option vol, OI, HV, IV, Misc Stats)    | populated into `MarketData` automatically                          | ready                 |\n| Contract resolution              | `reqContractData` (9), `contractData` (10)                                | `Connector.FetchContractDetails`                                   | ready                 |\n| Option chains                    | `reqSecDefOptParams` (78), `tickOptionComputation` (21)                   | `Connector.FetchOptionExpiries`, `FetchOptionExpiryStrikes`, `GetOptionGreeks`, `GetOptionIV` | ready                 |\n| Daily historical bars            | `reqHistoricalData` (20), `historicalData` (17)                           | `Connector.FetchHistoricalDailyBars`                               | ready                 |\n| Market scanner                   | `reqScannerSubscription` (22), `reqScannerParameters` (24)                | `Connector.RunScannerSubscription`, `RunScannerParameters`         | ready (v0.12)         |\n| Market-data type switch          | `reqMarketDataType` (59), `marketDataType` (58)                           | `Connector.SetMarketDataType`                                      | ready                 |\n| Order placement / cancel         | `placeOrder` (3), `cancelOrder` (4)                                       | `Connector.SubmitOrder`, `CancelOrder`                             | wire-ready, refused by daemon in v0.x |\n| Real-time bars                   | `reqRealTimeBars` (50)                                                    | —                                                                  | not implemented       |\n| Market depth (L2)                | `reqMktDepth` (10), `reqMktDepthL2` (13)                                  | —                                                                  | not implemented       |\n| Fundamental data                 | `reqFundamentalData` (52)                                                 | —                                                                  | not implemented       |\n| News bulletins                   | `reqNewsBulletins` (12)                                                   | —                                                                  | not implemented       |\n| Financial Advisor (FA)           | `reqFA` (18)                                                              | —                                                                  | not implemented       |\n| IV / option-price calculators    | `reqCalcImpliedVolatility` (54), `reqCalcOptionPrice` (55)                | —                                                                  | not implemented       |\n\nTested against IB Gateway server-versions 100 through 203. Handshake auto-negotiates the highest protocol version the gateway and library agree on. The library has no claim to be a full TWS API replacement — it covers the read-side surface the `ibkr` binary needs, with a small forward-compatibility buffer (the order ops) for a v2 line that hasn't shipped. Open an issue if you want a \"not implemented\" row plumbed; the wire format is documented and additions are usually self-contained.\n\n## Configure\n\nNo config file is required. The daemon TCP-probes `4001` (Gateway live), `4002` (Gateway paper), `7496` (TWS live), `7497` (TWS paper), picks the first responder, and falls over to alternates if the first one accepts TCP but never completes the handshake. The account is auto-detected via `managedAccounts`. Default client ID is `15`.\n\nWrite a config to **pin** a dimension. Anything you write is binding; anything you omit stays auto. Default path: `$XDG_CONFIG_HOME/ibkr/config.toml`, falling back to `~/.config/ibkr/config.toml`.\n\n```toml\n[gateway]\nhost       = \"127.0.0.1\"\nport       = 4001          # binding: skip the probe\nclient_id  = 15\naccount    = \"\"            # empty = auto-detect via managedAccounts\ntls        = false         # binding: no TLS fallback\n\n[daemon]\nidle_timeout = \"15m\"\nlog_level    = \"info\"\n\n[scans.top-movers]\ntype     = \"TOP_PERC_GAIN\"\nexchange = \"STK.US.MAJOR\"\nlimit    = 20\n```\n\n`ibkr status` shows what the daemon ended up using and where each value came from (`pinned` or `discovered`).\n\n**TLS semantics.** A pinned `tls` value (true or false) is strict. An omitted `tls` means \"auto\": plain first, TLS on no-handshake-data.\n\n**Strict keys.** Unknown top-level keys or sections fail at startup with a message that names them — your config can't silently drop fields. Supported sections: `[gateway]`, `[daemon]`, `[scans.\u003cname\u003e]`.\n\n### Adding scanners\n\nTwo paths, depending on who's calling:\n\n**Humans — add a preset to `config.toml`.** Use this when you want a stable shorthand you'll call by name:\n\n```toml\n[scans.tech-gainers]\ntype     = \"TOP_PERC_GAIN\"\nexchange = \"STK.NASDAQ\"\nlimit    = 25\n```\n\nThen `ibkr scan tech-gainers`. **Caveat:** writing **any** `[scans.*]` block makes the seven built-in defaults disappear — the `[scans]` table is replace-not-merge. Copy the defaults from [internal/config/config.go](internal/config/config.go) into your file if you want to keep them. Daemon restart (`pkill -x ibkr`; next call respawns) is required for new presets to be visible.\n\n**Agents — use the ad-hoc form, no config write needed:**\n\n```\nibkr scan --type TOP_PERC_GAIN --exchange STK.NASDAQ --limit 25 --json\n```\n\nAd-hoc rows are capped at 50 (vs. preset's user-set limit) to keep an agent from accidentally pulling thousands.\n\n**Finding the right `scanCode` and `locationCode`.** The TWS Market Scanner UI hides these strings behind human labels. Dump your gateway's actual catalog with:\n\n```\nibkr scan params --instrument STK [--json]\n```\n\nThe catalog varies by gateway version and by your market-data subscriptions — `scanCode`s like `HIGH_OPT_IMP_VOLAT_OVER_HIST` require US options data. `--instrument STK` narrows to stock scans; omit for everything. Add `--raw` to get the full XML (~200 KB–2 MB) if you need a less-common field. There's no need to memorize the values — the catalog is the source of truth.\n\n## Safety\n\n`ibkr` is read-only in 0.x. Four independent layers refuse `order`, `trade`, `cancel`:\n\n1. The daemon's order-handler dispatch returns `ErrTradingDisabled` for both `MethodOrderPlace` and `MethodOrderCancel` ([internal/daemon/trading_disabled.go](internal/daemon/trading_disabled.go)). `pkg/ibkr` exposes order types and a wire-side `Connector.SubmitOrder` for library consumers, but no CLI subcommand reaches them.\n2. The bundled [settings/ibkr.settings.json](settings/ibkr.settings.json) denies the verbs in `permissions.deny`.\n3. The plugin's `PreToolUse` hook hard-blocks the verb patterns and fails closed if `jq` is missing from PATH.\n4. A unit test in `internal/mcp` refuses to ship the MCP server with any tool whose name contains `order`, `trade`, `cancel`, `submit`, or `place`.\n\nPer [semver](https://semver.org/#spec-item-4), 0.x releases may break compatibility between minor versions. 1.0 is reserved for the first stable read-only line.\n\n## Other install paths\n\n- **`go install`**: `go install github.com/osauer/ibkr/cmd/ibkr@latest`. Requires Go 1.26+.\n- **Different install dir**: `IBKR_INSTALL_DIR=/usr/local/bin sh install.sh`. The installer won't touch your shell rc when you override; manage PATH yourself.\n- **Inspect the installer first**: `curl -fsSL https://raw.githubusercontent.com/osauer/ibkr/main/install.sh -o install.sh \u0026\u0026 less install.sh \u0026\u0026 sh install.sh`.\n- **Manual download**: pick a tarball from the latest [release](https://github.com/osauer/ibkr/releases/latest). Each contains `ibkr` plus `LICENSE` and `README.md`. Verify against the bundled `SHA256SUMS`.\n- **Local build**: `git clone … \u0026\u0026 make install`.\n- **Reproducible builds**: release tarballs are built with `-trimpath -buildvcs=false` and stamp the version/commit/date via `-ldflags`. Rebuilding the same tag (`make release-binaries RELEASE_VERSION=vX.Y.Z`) produces byte-identical binaries — bring your own checksum and verify against the published `SHA256SUMS`.\n\nWindows is not supported — the daemon uses Unix-only primitives (setsid, flock, AF_UNIX sockets). WSL works.\n\n## Testing\n\n```sh\nmake check      # gofmt + go vet + staticcheck + govulncheck + plugin/parity checks\nmake test       # check + unit tests + integration tests against a live gateway\n```\n\n`make check` is the binding gate. It fails on stdlib vulnerabilities, so an outdated Go toolchain is a build failure. One-time tool installs: `go install honnef.co/go/tools/cmd/staticcheck@latest` and `go install golang.org/x/vuln/cmd/govulncheck@latest`.\n\nIntegration tests under `test/integration/` connect to the live IB Gateway on `127.0.0.1:4001` and skip cleanly when it isn't reachable, so `go test ./...` doesn't hang on a laptop with no gateway. Override the port with `IBKR_TEST_PORT=4002 make test`.\n\nNo mock daemons. `pkg/ibkr/protocoltest/` is a wire-level encoder/decoder spec used by unit tests. Behavioural verification runs against a real IB Gateway.\n\n## Troubleshooting\n\n**\"gateway not responding to TWS handshake within 12s\".** The gateway accepts your TCP connection but never replies to the v100 handshake. Almost always the API socket is disabled. Launch TWS once, accept \"Enable ActiveX and Socket Clients\", quit TWS, restart Gateway. The flag carries over via shared `~/Jts/\u003cuserdir\u003e/ibg.xml`. It also silently un-ticks itself when more than one of TWS / IB Gateway / IBKR Desktop is launched against the same login — if it keeps coming back, run only one of them.\n\n**\"no IBKR listener found on 127.0.0.1 ports ...\".** Auto-discovery probed all four standard ports and got nothing. The error message tells you which case you're in: if TWS / IB Gateway / IBKR Desktop is running, the API socket is closed (checkbox unchecked, login pending, or non-default socket port — pin it in `[gateway]`); if nothing is running, just start one and the daemon reconnects automatically. On a non-loopback host, set `host = \"192.168.x.y\"` explicitly — auto-discovery only probes loopback.\n\n**\"none of N discovered endpoint(s) completed TWS handshake\".** Both Gateway and TWS are running, both accept TCP, but neither completes the API handshake. Usually a stale Gateway window from earlier in the day plus a freshly logged-in TWS. The status output names every endpoint that was tried. Quit the one you don't need.\n\n**`daemon socket did not appear`.** The daemon crashed during startup. Check `~/.local/state/ibkr/ibkr-daemon.log`. Common causes: gateway not running, configured `client_id` already in use, wrong port. Orphaned sockets from crashed daemons are handled automatically.\n\n**Quotes time out.** Strict live entitlements, market closed. The daemon defaults to `SetMarketDataType(2)` (frozen), which returns the last-known price; with `live` only, snapshots stay empty out of trading hours. Loosen the gateway's market-data permissions.\n\n**`use of closed network connection` during handshake.** IB Gateway rate-limits fast handshake retries. Wait ~30 seconds before restarting.\n\n**CLI vs daemon version skew warning.** `pkill -x ibkr`, then re-invoke any subcommand. The daemon autospawns from the new binary.\n\n**Capturing the wire protocol for diagnostics.** Set `IBKR_WIRE_INTERCEPTOR=1` to enable the in-process recorder; pair with `IBKR_WIRE_LOG_PATH=/path/to/wire.jsonl` to also persist every frame as JSON-lines. `IBKR_WIRE_RING_SIZE=N` sizes the in-memory ring (default 256). For raw bytes, `IBKR_PACKET_LOG_TEMPLATE=/path/to/packets.bin` enables the lower-level packet logger. All four are off by default. Captured frames carry account-sensitive data — see [SECURITY.md §Diagnostic data sensitivity](SECURITY.md#diagnostic-data-sensitivity) before sharing logs.\n\n## Disclaimer \u0026 trademarks\n\nThis project is an **independent, third-party client** for Interactive Brokers' [publicly documented TWS API](https://interactivebrokers.github.io/). It is not built, endorsed, sponsored, or supported by Interactive Brokers Group, Inc., or any of its affiliates.\n\n- \"Interactive Brokers\", \"IBKR\", \"TWS\", and \"IB Gateway\" are trademarks or registered trademarks of Interactive Brokers Group, Inc. or its affiliates. They are used here nominatively, solely to identify the brokerage and the API this project connects to.\n- `pkg/ibkr` is a clean-room Go re-implementation of the TWS wire protocol. **No code, libraries, or jars distributed by Interactive Brokers are included or redistributed in this project.**\n- This project does not store, cache, or redistribute IBKR market data. All data is read live from a gateway you run locally, against your own account, and never leaves your machine via this code.\n- Connecting to IBKR via the TWS API requires an **IBKR Pro** account; IBKR Lite does not include API access.\n- Nothing here is investment advice. Use at your own risk; the MIT license's AS IS clause applies in full.\n\nIf you are Interactive Brokers and have a concern with anything in this repository, please open a GitHub issue and we will respond promptly.\n\n## License\n\nMIT. See [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fosauer%2Fibkr","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fosauer%2Fibkr","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fosauer%2Fibkr/lists"}