You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A CLI tool for analyzing Interactive Brokers (IBKR) holdings and trades. Downloads data via the IBKR Flex Query API, computes FIFO tax lots, and displays holdings with prices, positions, and USD conversions. Supports multiple IBKR accounts.
3
+
A CLI tool for analyzing Interactive Brokers (IBKR) holdings and trades. Downloads data via the IBKR Flex Query API, computes FIFO tax lots, and displays holdings with prices, positions, and USD/CAD conversions. Supports multiple IBKR accounts, non-IBKR broker additions, and tax reporting for both IRS and CRA.
4
+
5
+
For complete documentation, run `make book` to view the ibctl book locally, or browse the `book/` directory.
-**`data/`** contains `trades.json` per account, incrementally merged across downloads. This is the only directory that accumulates over time — IBKR limits each download to 365 days, so older trades can't be re-downloaded.
55
-
-**`cache/`** contains everything else: position snapshots, transfers, FX rates. Safe to delete entirely — the next `ibctl download` re-populates it.
56
-
-**`activity_statements/`** contains Activity Statement CSVs you download from the IBKR portal. ibctl reads them at command time and never modifies them.
57
-
-**`seed/`** (optional) contains permanent transaction history imported from previous brokers (e.g., UBS, RBC).
58
-
59
-
## IBKR Flex Query Setup
60
-
61
-
Follow these steps in the IBKR portal to create a Flex Query and generate an API token.
62
-
63
-
### Create a Flex Query
64
-
65
-
1. Log in to [IBKR Account Management](https://www.interactivebrokers.com/portal).
66
-
2. Navigate to **Performance & Reports** > **Flex Queries**.
67
-
3. In the **Activity Flex Query** section, click the **+** button.
68
-
4. Set the **Query Name** to something descriptive (e.g., "ibctl").
69
-
5. Under **Sections**, add the following sections, selecting all fields for each:
70
-
-**Trades**
71
-
-**Open Positions**
72
-
-**Cash Transactions** (used for FX rate extraction)
73
-
-**Cash Report** (provides cash balances by currency)
74
-
-**Transfers (ACATS, Internal)** (captures positions transferred from other brokers)
75
-
-**Incoming/Outgoing Trade Transfers** (preserves cost basis and holding period)
9. Note the **Query ID** displayed next to the query name.
90
-
91
-
**Note on trade history**: IBKR limits Flex Query periods to 365 calendar days. To capture older trades, change the Period in the IBKR portal to cover a different date range and run `ibctl download` again — new trades are merged into the existing cache, deduplicated by trade ID.
92
-
93
-
### Generate a Flex Web Service Token
94
-
95
-
1. On the same **Flex Queries** page, find the **Flex Web Service Configuration** section.
96
-
2. Click the **gear icon** to configure.
97
-
3. Generate a token and copy it securely.
98
-
4. Set it as the `IBKR_FLEX_WEB_SERVICE_TOKEN` environment variable.
99
-
100
31
## Environment Variables
101
32
102
33
| Variable | Required | Description |
103
34
|----------|----------|-------------|
104
35
|`IBKR_FLEX_WEB_SERVICE_TOKEN`| Yes (for `download`) | IBKR Flex Web Service token. Read-only — can only retrieve reports, not make trades. Never store in config files or version control. |
105
36
106
-
## Configuration
107
-
108
-
The `ibctl.yaml` file lives in the ibctl directory:
109
-
110
-
```yaml
111
-
version: v1
112
-
flex_query_id: "123456"
113
-
accounts:
114
-
rrsp: "U1234567"
115
-
holdco: "U2345678"
116
-
individual: "U3456789"
117
-
categorization:
118
-
- symbol: AAPL
119
-
category: EQUITY
120
-
type: STOCK
121
-
sector: TECH
122
-
geo: US
123
-
cash:
124
-
CAD: "-5000.00"
125
-
additions:
126
-
- account_alias: wealthsimple
127
-
symbol: VFV.TO
128
-
currency_code: CAD
129
-
trade_date: "20240315"
130
-
trade_price: "102.50"
131
-
trade_side: buy
132
-
quantity: "100"
133
-
last_price: "115.30"
134
-
tax_exempt: true
135
-
```
136
-
137
-
- `flex_query_id` — your IBKR Flex Query ID (required)
138
-
- `accounts`— maps user-chosen aliases to IBKR account IDs (required). Account numbers are confidential — only aliases appear in output and directory names.
- `cash`— optional manual cash adjustments by currency code (applied to cash positions in holdings display)
141
-
- `additions`— optional manually added trades from non-IBKR brokers. Addition accounts must not match IBKR account aliases. Trade dates use YYYYMMDD format. When `tax_exempt` is true, lots show P&L=0 and are excluded from tax calculations.
|`ibctl holding value`| Display portfolio value with estimated tax impact |
51
+
|`ibctl trade sale list`| List realized security sales with FIFO lot matching for tax reporting |
178
52
|`ibctl probe`| Probe the API and show per-account data counts |
179
53
180
-
All commands accept `--dir` to specify the ibctl directory (defaults to `.`).
181
-
182
-
## Seeding Historical Data
183
-
184
-
IBKR limits all data access to 365 days per request. To get your full trade history, download Activity Statement CSVs from the IBKR portal.
185
-
186
-
### Setup
187
-
188
-
1. Create subdirectories for each account using your aliases:
189
-
```bash
190
-
mkdir -p activity_statements/rrsp
191
-
mkdir -p activity_statements/holdco
192
-
mkdir -p activity_statements/individual
193
-
```
194
-
195
-
2. For each account, log in to [IBKR Account Management](https://www.interactivebrokers.com/portal).
196
-
197
-
3. Go to **Performance & Reports** > **Statements**.
198
-
199
-
4. Select **Activity Statement**, **Custom Date Range**, and click **Download CSV**.
200
-
201
-
5. Download yearly chunks (365-day maximum per file).
54
+
All commands accept `--dir` to specify the ibctl directory (defaults to `.`). Use `--help` on any command for detailed documentation.
202
55
203
-
6. Save each CSV in the account's subdirectory. Filenames don't matter — ibctl reads all `*.csv` files recursively.
56
+
## Tax Reporting
204
57
205
-
7. Run `ibctl holding list` — data from the CSVs is merged with Flex Query API data.
58
+
Generate CSV files for your accountant:
206
59
207
-
### How Merging Works
208
-
209
-
At command time, ibctl merges three data sources per account. CSV data takes precedence for overlapping date ranges:
210
-
211
-
1. **Activity Statement CSVs** (`activity_statements/<alias>/*.csv`) — primary source of truth for trades
212
-
2. **Seed data** (`seed/<alias>/transactions.json`) — imported transactions from previous brokers
213
-
3. **Flex Query cache** (`data/accounts/<alias>/trades.json`) — recent trades from the API, used only for dates not covered by CSVs
214
-
215
-
## Implementation
216
-
217
-
### Data Files
218
-
219
-
All data files use newline-separated protobuf JSON (one message per line). Monetary values use `standard.money.v1.Money` (units + micros for 6 decimal places). Dates use `standard.time.v1.Date` (year, month, day).
| `trades.json` | `ibctl.data.v1.Trade` | Deduplicated by trade ID | Persistent trade history. Incrementally merged across downloads so the cache grows over time. |
224
-
| `positions.json` | `ibctl.data.v1.Position` | Overwritten each download | IBKR-reported positions snapshot. Provides current market prices and verification data. **Not the source of truth** for quantities or cost basis — those are computed via FIFO from trades. |
225
-
| `transfers.json` | `ibctl.data.v1.Transfer` | Overwritten each download | Position transfers (ACATS, ATON, FOP, internal). Transfer-ins with a non-zero price become synthetic buy trades for FIFO. |
226
-
| `trade_transfers.json` | `ibctl.data.v1.TradeTransfer` | Overwritten each download | Preserves **original trade date** and **cost basis** for transferred positions (long-term vs short-term capital gains). |
227
-
| `corporate_actions.json` | `ibctl.data.v1.CorporateAction` | Overwritten each download | Stock splits, mergers, spinoffs for audit purposes. |
228
-
| `cash_positions.json` | `ibctl.data.v1.CashPosition` | Overwritten each download | Cash balances by currency from the IBKR Cash Report section. |
229
-
| `rates.json` | `ibctl.data.v1.ExchangeRate` | Deduplicated by date | Per-pair FX rates from [Bank of Canada](https://www.bankofcanada.ca) (X→CAD) and [frankfurter.dev](https://frankfurter.dev) (X→USD). Only missing dates are fetched. |
60
+
```bash
61
+
# For IRS (US tax reporting).
62
+
ibctl trade sale list --from 20250101 --to 20251231 --base-currency USD --format csv > trades-2025-usd.csv
230
63
231
-
### Seed Data
64
+
# For CRA (Canadian tax reporting).
65
+
ibctl trade sale list --from 20250101 --to 20251231 --base-currency CAD --format csv > trades-2025-cad.csv
66
+
```
232
67
233
-
The optional `seed/` directory contains permanent, manually curated transaction history from previous brokers. `transactions.json` uses the `ibctl.data.v1.ImportedTransaction` proto covering all transaction types (buys, sells, splits, dividends, interest, fees, etc.). Only security-affecting transactions are converted to Trade protos for FIFO processing.
68
+
See the [Tax Reporting Guide](book/src/tax-reporting.md) in the ibctl book for a detailed explanation of the CSV columns and methodology, suitable for sharing with your accountant.
234
69
235
-
### Data Pipeline
70
+
##Documentation
236
71
237
-
The `holding list` command runs:
72
+
The full ibctl book covers configuration, IBKR setup, FIFO methodology, FX rate handling, tax reporting, and detailed command references.
238
73
239
-
1. **Download**: Fetches all accounts' data from the IBKR Flex Query API. Trades are incrementally merged. FX rates are eagerly downloaded for all currency pairs from the earliest trade date to today.
240
-
2. **Merge**: Combines Activity Statement CSVs + seed data + Flex Query cache, with CSV data taking precedence for overlapping dates.
241
-
3. **FIFO**: Computes tax lots grouped by (account, symbol). Transfers and trade transfers are converted to synthetic trades. Buys before sells within the same date.
242
-
4. **Aggregation**: Tax lots are aggregated into positions with weighted average cost basis, then combined across accounts.
243
-
5. **Verification**: Computed positions are compared against IBKR-reported positions. Cost basis discrepancies > 0.1% are logged as warnings.
244
-
6. **Display**: Holdings are rendered with USD conversions (via FX rates), market value, unrealized P&L split into short-term and long-term capital gains, and optional symbol classifications.
0 commit comments