IBKR Options Trading
All commands run as ibkr <command>. If ibkr is not on PATH, install it
(requires Python 3.11+ and uv):
git clone https://github.com/baileywickham/ibkr
cd ibkr && uv tool install --editable .
Output is JSON. Exit codes: 0 ok, 2 gateway unreachable,
3 validation error, 4 token rejected, 5 account error, 6 delayed-data blocked.
Mode: paper account by default (Gateway port 4002). Add --live (port 4001) ONLY
when the user explicitly says to trade the live account.
Commands
ibkr status # connection + account summary
ibkr positions | orders | trades
ibkr chain AAPL # expirations
ibkr chain AAPL --expiry 2026-07-17 --strikes 8 # strikes around spot, quotes + Greeks
ibkr quote AAPL # stock quote
ibkr quote AAPL --expiry 2026-07-17 --strike 200 --right C # option quote
ibkr place --symbol AAPL --expiry 2026-07-17 --strike 200 --right C \
--side BUY --qty 1 --limit 3.50 # PREVIEW (never trades)
ibkr place ... --execute TOKEN # place previewed order
ibkr place-vertical --symbol AAPL --expiry 2026-07-17 --right C --side BUY \
--long-strike 200 --short-strike 205 --qty 1 --limit 1.80
ibkr stock --symbol AAPL --side BUY --qty 10 --limit 250 # shares (preview/execute)
ibkr cancel ORDER_ID
ibkr close 355C # PREVIEW closing one position (match by symbol)
ibkr close --all # PREVIEW closing every position
ibkr close 355C --execute TOKEN # place the closing order(s)
ibkr close --all --limit 0.02 # override the closing limit price
Limit orders only; market orders are intentionally not implemented.
For verticals, --limit is the net debit (BUY) or net credit (SELL), always positive.
close builds an offsetting order priced marketably at the current bid/ask
(SELL to close a long, BUY to close a short). A long/short option pair on one
underlying is recognized as a spread and closed as a SINGLE net-priced combo —
so --limit is the net price for the whole spread, not a per-leg price (a
per-leg limit would make the buy-to-close leg marketable and leg you into a
naked option). Other multi-leg matches refuse --limit; narrow the query to one
leg or use place-vertical --side SELL for a net-priced combo close. There is no
native close-position call in the IBKR API — this replicates the TWS "Close" button.
Same preview→confirm→execute flow as place. If a position has no bid/ask quote
(e.g. a deep-OTM contract whose closing bid is negative), pass --limit. The
preview prints each position, its closing action, quantity, and limit, plus a
token; nothing is placed until you re-run with --execute TOKEN.
Confirmation protocol (NON-NEGOTIABLE for live mode)
- Run the
place/place-vertical command WITHOUT --execute. This is a preview:
it prints the resolved contract, current quotes, max loss/gain, and a token.
- Show the user the preview (contract, side, qty, limit, max loss, premium).
- LIVE MODE: wait for the user to explicitly approve THIS order in chat. Never
infer approval from an earlier message, never batch approvals. Paper mode:
self-confirmation is acceptable for testing.
- Re-run the identical command with
--execute TOKEN. The CLI rejects the token
if any parameter changed, the preview is older than 5 minutes, or it was
already used — in that case re-preview, re-confirm.
Real-money safety (read before live trading)
- Account pinning: every order pins an account. With a single-account login
it's automatic; if the login has multiple accounts, set
account = "U..." in
~/.ibkr-options/config.toml or orders fail with an account error (exit 5).
- Delayed-data guard: by default the tool uses delayed (~15 min) data
(
market_data_type = 3). A live order priced off delayed data is refused
(exit 6) unless you pass --allow-delayed or set allow_delayed_live = true
in config. Only suggest that opt-in when the user understands their limit
prices are based on stale quotes; previews still print a DELAYED warning.
For realtime, subscribe and set market_data_type = 1. Paper is never blocked.
- Rejection surfacing: a rejected order returns
"rejected": true with the
reason in messages (e.g. [202] Limit price too far outside of NBBO). Always
check for this — a Cancelled/Inactive status means the order did NOT work.
- Limit orders only, and prices must respect IBKR tick rules ($0.05 ≥ $3.00,
else $0.01) or IBKR rejects them.
Reads vs writes (all-account vs this-CLI)
One consistent rule: reads are account-wide, writes act only on orders this CLI
placed.
positions and orders show everything the account holds, regardless of where
it was placed. Each open order carries client_id (0 = placed via web Portal /
TWS / mobile) and perm_id (stable id to cross-reference with the Portal).
cancel only works on orders this CLI placed (nonzero order_id). An order with
client_id: 0 is read-only here — cancel it where it was placed. So before a
close, check orders: if a resting exit already exists there, closing again
would double up.
trades is the exception the TWS API forces: fills are session-scoped, not
account-wide. For full account trade history use the Portal or Flex Queries.
Operational notes
- If you get
gateway_unreachable: ask the user to launch IB Gateway (on macOS
something like open -a "IB Gateway 10.45" — the app name carries its version)
and log in (paper or live to match the mode).
Do not attempt to enter credentials yourself — login is the user's job.
- First-time Gateway setup: in Configure → Settings → API → Settings, "Enable
ActiveX and Socket Clients" must be on and "Read-Only API" must be OFF for
order placement. Socket port: 4002 paper / 4001 live.
"data": "delayed" in output means no realtime subscription for that
instrument — quotes are 15-20 min old. Say so when showing the user numbers.
- Never provide personalized investment advice; research, data, and executing
the user's decisions only.
1---2name: ibkr3description: Trade and research options on Interactive Brokers via a local CLI (chains, quotes, Greeks, single-leg and vertical orders). Use when the user asks about option chains, option quotes, or wants to place/cancel option orders on IBKR. Requires IB Gateway running locally. Paper account is the default; live needs --live.4---5
6# IBKR Options Trading
7
8All commands run as `ibkr <command>`. If `ibkr` is not on PATH, install it
9(requires Python 3.11+ and [uv](https://github.com/astral-sh/uv)):
10
11 git clone https://github.com/baileywickham/ibkr
12 cd ibkr && uv tool install --editable .
13
14Output is JSON. Exit codes: 0 ok, 2 gateway unreachable,
153 validation error, 4 token rejected, 5 account error, 6 delayed-data blocked.
16
17**Mode**: paper account by default (Gateway port 4002). Add `--live` (port 4001) ONLY
18when the user explicitly says to trade the live account.
19
20## Commands
21
22```
23ibkr status # connection + account summary
24ibkr positions | orders | trades
25ibkr chain AAPL # expirations
26ibkr chain AAPL --expiry 2026-07-17 --strikes 8 # strikes around spot, quotes + Greeks
27ibkr quote AAPL # stock quote
28ibkr quote AAPL --expiry 2026-07-17 --strike 200 --right C # option quote
29ibkr place --symbol AAPL --expiry 2026-07-17 --strike 200 --right C \
30 --side BUY --qty 1 --limit 3.50 # PREVIEW (never trades)
31ibkr place ... --execute TOKEN # place previewed order
32ibkr place-vertical --symbol AAPL --expiry 2026-07-17 --right C --side BUY \
33 --long-strike 200 --short-strike 205 --qty 1 --limit 1.80
34ibkr stock --symbol AAPL --side BUY --qty 10 --limit 250 # shares (preview/execute)
35ibkr cancel ORDER_ID
36ibkr close 355C # PREVIEW closing one position (match by symbol)
37ibkr close --all # PREVIEW closing every position
38ibkr close 355C --execute TOKEN # place the closing order(s)
39ibkr close --all --limit 0.02 # override the closing limit price
40```
41
42Limit orders only; market orders are intentionally not implemented.
43For verticals, `--limit` is the net debit (BUY) or net credit (SELL), always positive.
44
45`close` builds an offsetting order priced marketably at the current bid/ask
46(SELL to close a long, BUY to close a short). A long/short option pair on one
47underlying is recognized as a spread and closed as a SINGLE net-priced combo —
48so `--limit` is the net price for the whole spread, not a per-leg price (a
49per-leg limit would make the buy-to-close leg marketable and leg you into a
50naked option). Other multi-leg matches refuse `--limit`; narrow the query to one
51leg or use `place-vertical --side SELL` for a net-priced combo close. There is no
52native close-position call in the IBKR API — this replicates the TWS "Close" button.
53Same preview→confirm→execute flow as place. If a position has no bid/ask quote
54(e.g. a deep-OTM contract whose closing bid is negative), pass `--limit`. The
55preview prints each position, its closing action, quantity, and limit, plus a
56token; nothing is placed until you re-run with `--execute TOKEN`.
57
58## Confirmation protocol (NON-NEGOTIABLE for live mode)
59
601. Run the `place`/`place-vertical` command WITHOUT `--execute`. This is a preview:
61 it prints the resolved contract, current quotes, max loss/gain, and a `token`.
622. Show the user the preview (contract, side, qty, limit, max loss, premium).
633. LIVE MODE: wait for the user to explicitly approve THIS order in chat. Never
64 infer approval from an earlier message, never batch approvals. Paper mode:
65 self-confirmation is acceptable for testing.
664. Re-run the identical command with `--execute TOKEN`. The CLI rejects the token
67 if any parameter changed, the preview is older than 5 minutes, or it was
68 already used — in that case re-preview, re-confirm.
69
70## Real-money safety (read before live trading)
71
72- **Account pinning**: every order pins an account. With a single-account login
73 it's automatic; if the login has multiple accounts, set `account = "U..."` in
74 `~/.ibkr-options/config.toml` or orders fail with an account error (exit 5).
75- **Delayed-data guard**: by default the tool uses delayed (~15 min) data
76 (`market_data_type = 3`). A **live** order priced off delayed data is refused
77 (exit 6) unless you pass `--allow-delayed` or set `allow_delayed_live = true`
78 in config. Only suggest that opt-in when the user understands their limit
79 prices are based on stale quotes; previews still print a DELAYED warning.
80 For realtime, subscribe and set `market_data_type = 1`. Paper is never blocked.
81- **Rejection surfacing**: a rejected order returns `"rejected": true` with the
82 reason in `messages` (e.g. `[202] Limit price too far outside of NBBO`). Always
83 check for this — a `Cancelled`/`Inactive` status means the order did NOT work.
84- **Limit orders only**, and prices must respect IBKR tick rules ($0.05 ≥ $3.00,
85 else $0.01) or IBKR rejects them.
86
87## Reads vs writes (all-account vs this-CLI)
88
89One consistent rule: **reads are account-wide, writes act only on orders this CLI
90placed.**
91- `positions` and `orders` show everything the account holds, regardless of where
92 it was placed. Each open order carries `client_id` (0 = placed via web Portal /
93 TWS / mobile) and `perm_id` (stable id to cross-reference with the Portal).
94- `cancel` only works on orders this CLI placed (nonzero `order_id`). An order with
95 `client_id: 0` is read-only here — cancel it where it was placed. So before a
96 `close`, check `orders`: if a resting exit already exists there, closing again
97 would double up.
98- `trades` is the exception the TWS API forces: fills are session-scoped, not
99 account-wide. For full account trade history use the Portal or Flex Queries.
100
101## Operational notes
102
103- If you get `gateway_unreachable`: ask the user to launch IB Gateway (on macOS
104 something like `open -a "IB Gateway 10.45"` — the app name carries its version)
105 and log in (paper or live to match the mode).
106 Do not attempt to enter credentials yourself — login is the user's job.
107- First-time Gateway setup: in Configure → Settings → API → Settings, "Enable
108 ActiveX and Socket Clients" must be on and "Read-Only API" must be OFF for
109 order placement. Socket port: 4002 paper / 4001 live.
110- `"data": "delayed"` in output means no realtime subscription for that
111 instrument — quotes are 15-20 min old. Say so when showing the user numbers.
112- Never provide personalized investment advice; research, data, and executing
113 the user's decisions only.