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
Copy file name to clipboardExpand all lines: README.md
+20-7Lines changed: 20 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,6 @@
1
1
# ibctl
2
2
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.
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 multi-currency conversions. Supports multiple IBKR accounts, non-IBKR broker additions, and tax reporting for both IRS and CRA.
4
4
5
5
For complete documentation, run `make book` to view the ibctl book locally, or browse the `book/` directory.
|`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 |
57
+
|`ibctl transaction list`| List all transactions (buys, sells, dividends, interest, WHT, etc.) chronologically |
58
+
|`ibctl transaction sale list`| List realized security sales with FIFO lot matching for tax reporting |
52
59
|`ibctl probe`| Probe the API and show per-account data counts |
53
60
54
-
All commands accept `--dir` to specify the ibctl directory (defaults to `.`). Use `--help` on any command for detailed documentation.
61
+
All commands accept `--dir` to specify the ibctl directory (defaults to `.`). All holding and transaction commands accept `--base-currency` (default `USD`) to convert values to a different currency. Use `--help` on any command for detailed documentation.
55
62
56
63
## Tax Reporting
57
64
58
65
Generate CSV files for your accountant:
59
66
60
67
```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
68
+
# Realized capital gains for IRS (US tax reporting).
69
+
ibctl transaction sale list --from 20250101 --to 20251231 --base-currency USD --format csv > transaction-sale-list-2025-usd.csv
70
+
71
+
# Realized capital gains for CRA (Canadian tax reporting).
72
+
ibctl transaction sale list --from 20250101 --to 20251231 --base-currency CAD --format csv > transaction-sale-list-2025-cad.csv
73
+
74
+
# Complete transaction history for IRS (dividends, interest, WHT, buys, sells, etc.).
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.
Copy file name to clipboardExpand all lines: book/src/commands/download.md
+17-3Lines changed: 17 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -10,7 +10,7 @@ Fetches IBKR data via the Flex Query API and stores it locally. Also eagerly dow
10
10
11
11
The download command makes a single Flex Query API call using the `flex_query_id` from `ibctl.yaml` and the `IBKR_FLEX_WEB_SERVICE_TOKEN` environment variable. The response is split per account (matched by account ID to your configured aliases) and written to two locations:
12
12
13
-
-**`data/`** -- Trades are written here. This directory is persistent and append-only. New trades are merged with existing trades, deduplicated by trade ID. This ensures trade history accumulates over time even if your Flex Query period is limited.
13
+
-**`data/`** -- Trades and income are written here. This directory is persistent and append-only. New trades are merged with existing trades, deduplicated by trade ID. Income records (dividends, interest, withholding tax) from Flex Query CashTransactions are converted to Income protos and persisted alongside trades. This ensures trade and income history accumulates over time even if your Flex Query period is limited.
14
14
-**`cache/`** -- Positions, cash positions, transfers, trade transfers, and corporate actions are written here. These are point-in-time snapshots that are overwritten on each download.
15
15
16
16
After writing account data, the downloader collects all non-USD currency codes from trades and eagerly downloads FX rates for each currency pair. Rates are stored in `cache/fx/{BASE}.{QUOTE}/rates.json`. Sources:
@@ -20,9 +20,24 @@ After writing account data, the downloader collects all non-USD currency codes f
20
20
21
21
Weekend and holiday dates produce no-data markers in the rate files. When a rate is needed for such a date, ibctl falls back to the most recent business day rate.
22
22
23
+
## Option exercises, assignments, and expirations
24
+
25
+
Option Exercises, Assignments and Expirations from the Flex Query are converted to synthetic trades during download:
26
+
27
+
-**Expirations** become sell trades at $0 (creating a loss for option buyers, a gain for writers).
28
+
-**Exercises and assignments** create synthetic stock trades at the strike price.
29
+
30
+
These synthetic trades are merged into the trade data alongside regular trades and flow through FIFO like any other trade.
31
+
32
+
## Income data persistence
33
+
34
+
Flex Query CashTransactions are parsed for dividends, interest, and withholding tax. These are converted to Income protos (with an `IncomeType` enum: DIVIDEND, INTEREST, WITHHOLDING_TAX) and persisted in `data/accounts/<alias>/income.json`. This ensures income data accumulates over time, just like trade data.
35
+
36
+
Activity Statement CSV dividends, interest, and withholding tax are read at command time (not during download) and merged with persisted income data for display in the `transaction list` output.
37
+
23
38
## Idempotency
24
39
25
-
The download command is idempotent. Running it multiple times safely merges new trades into the existing data directory without creating duplicates. Cache data is simply overwritten with the latest snapshot.
40
+
The download command is idempotent. Running it multiple times safely merges new trades and income into the existing data directory without creating duplicates. Cache data is simply overwritten with the latest snapshot.
26
41
27
42
## Flags
28
43
@@ -34,4 +49,3 @@ The download command is idempotent. Running it multiple times safely merges new
34
49
35
50
- The `--download` flag on other commands (such as `ibctl holding list --download`) calls this same download logic before displaying results.
36
51
- Unknown account IDs in the Flex Query response are logged as warnings and skipped. Add them to the `accounts` section of `ibctl.yaml` to process them.
37
-
- Option exercises, assignments, and expirations from the Flex Query are converted to synthetic trades and merged alongside regular trades.
Aggregates holdings by category and displays each category's market value, percentage of net liquidation value, and capital gains breakdown.
8
8
9
9
Categories are defined in the `categorization` section of `ibctl.yaml`. Cash positions default to category `CASH`. Symbols without a configured category appear as `UNCATEGORIZED`.
10
10
11
+
Use `--base-currency` (default `USD`) to convert all values to a different currency.
Aggregates holdings by geographic classification and displays each geo's market value, percentage of net liquidation value, and capital gains breakdown.
8
8
9
9
Geographic classifications are defined in the `categorization` section of `ibctl.yaml`. Symbols without a configured geo appear as `UNCATEGORIZED`.
10
10
11
+
Use `--base-currency` (default `USD`) to convert all values to a different currency.
Copy file name to clipboardExpand all lines: book/src/commands/holding-list.md
+16-9Lines changed: 16 additions & 9 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,11 +1,15 @@
1
1
# holding list
2
2
3
3
```
4
-
ibctl holding list [--dir DIR] [--format FORMAT] [--download]
4
+
ibctl holding list [--dir DIR] [--format FORMAT] [--download] [--base-currency CURRENCY]
5
5
```
6
6
7
7
Shows combined positions across all accounts. Positions are computed via FIFO tax lot matching with weighted average cost basis, then verified against IBKR-reported positions.
8
8
9
+
Use `--base-currency` (default `USD`) to convert all values to a different currency. Cost basis is converted using the FX rate on the lot's open date; current market value uses today's rate. This correctly shows FX-adjusted unrealized P&L -- the same methodology used by `transaction sale list` for realized gains.
10
+
11
+
Positions worth less than 0.01 in the base currency are filtered out. This removes dust positions (e.g., a JPY cash balance that rounds to -0.00 in USD).
12
+
9
13
## Columns
10
14
11
15
| Column | Description |
@@ -14,21 +18,23 @@ Shows combined positions across all accounts. Positions are computed via FIFO ta
14
18
| CURRENCY | Native currency of the last price and average price |
15
19
| LAST PRICE | Most recent market price per share in native currency |
16
20
| AVG PRICE | Weighted average cost basis per share in native currency |
17
-
| LAST USD| Last price converted to USD using the most recent FX rate |
18
-
| AVG USD| Average cost basis converted to USD|
19
-
| MKT VAL USD| Market value in USD (last price USD * position) |
20
-
| P&L USD| Unrealized profit/loss in USD (computed per lot, excluding tax-exempt lots) |
21
-
| STCG USD| Short-term unrealized capital gain in USD (lots held < 365 days) |
22
-
| LTCG USD| Long-term unrealized capital gain in USD (lots held >= 365 days) |
21
+
| LAST {BASE}| Last price converted to base currency using today's FX rate |
22
+
| AVG {BASE}| Average cost basis converted to base currency using date-specific FX rates from each lot's open date|
23
+
| MKT VAL {BASE}| Market value in base currency (last price base * position) |
24
+
| P&L {BASE}| Unrealized profit/loss in base currency (computed per lot, excluding tax-exempt lots, using date-specific FX rates) |
25
+
| STCG {BASE}| Short-term unrealized capital gain in base currency (lots held < 365 days) |
26
+
| LTCG {BASE}| Long-term unrealized capital gain in base currency (lots held >= 365 days) |
23
27
| POSITION | Total quantity held across all accounts |
`{BASE}` is replaced with the `--base-currency` value (e.g., `LAST USD`, `MKT VAL CAD`).
34
+
29
35
## Sort order
30
36
31
-
Securities are sorted by market value in USD descending (highest value first). Cash positions appear at the bottom, also sorted by market value descending.
37
+
Securities are sorted by market value in base currency descending (highest value first). Cash positions appear at the bottom, also sorted by market value descending.
32
38
33
39
## Cash positions
34
40
@@ -44,7 +50,7 @@ Holdings from tax-exempt accounts (RRSP, TFSA, or additions with `tax_exempt: tr
44
50
45
51
## Totals row
46
52
47
-
The table output includes a totals row summing MKT VAL USD, P&L USD, STCG USD, and LTCG USD.
53
+
The table output includes a totals row summing MKT VAL {BASE}, P&L {BASE}, STCG {BASE}, and LTCG {BASE}.
48
54
49
55
## Discrepancy warnings
50
56
@@ -63,3 +69,4 @@ Unmatched sells (where the buy occurred before the data window) are also logged
63
69
|`--dir`|`.`| The ibctl directory containing `ibctl.yaml`|
64
70
|`--format`|`table`| Output format: `table`, `csv`, or `json`|
65
71
|`--download`|`false`| Download fresh data before displaying |
72
+
|`--base-currency`|`USD`| Base currency for value conversion (case-insensitive, e.g., `USD`, `CAD`) |
ibctl holding lot list [--dir DIR] [--format FORMAT] [--download] [--symbol SYMBOL]
4
+
ibctl holding lot list [--dir DIR] [--format FORMAT] [--download] [--symbol SYMBOL] [--base-currency CURRENCY]
5
5
```
6
6
7
7
Lists individual FIFO tax lots. Unlike `holding list`, which aggregates positions by symbol, this command shows each tax lot separately with its own open date, quantity, and cost basis.
8
8
9
+
Use `--base-currency` (default `USD`) to convert all values to a different currency. Cost basis is converted using the FX rate on the lot's open date; current market value uses today's rate. This means each lot's base-currency cost basis reflects the FX rate at the time of purchase, producing accurate FX-adjusted unrealized P&L.
10
+
9
11
## Columns
10
12
11
13
| Column | Description |
@@ -18,27 +20,29 @@ Lists individual FIFO tax lots. Unlike `holding list`, which aggregates position
18
20
| AVG PRICE | Cost basis price per share in native currency |
19
21
| P&L | Unrealized P&L in native currency |
20
22
| MKT VAL | Current market value in native currency |
21
-
| AVG USD| Cost basis price converted to USD|
22
-
| P&L USD| Unrealized P&L in USD|
23
-
| STCG USD| Short-term unrealized P&L in USD (lot held < 365 days), equals P&L USD or 0 |
24
-
| LTCG USD| Long-term unrealized P&L in USD (lot held >= 365 days), equals P&L USD or 0 |
25
-
| MKT VAL USD| Current market value in USD|
23
+
| AVG {BASE}| Cost basis price converted to base currency using the FX rate on the lot's open date|
24
+
| P&L {BASE}| Unrealized P&L in base currency|
25
+
| STCG {BASE}| Short-term unrealized P&L in base currency (lot held < 365 days), equals P&L {BASE} or 0 |
26
+
| LTCG {BASE}| Long-term unrealized P&L in base currency (lot held >= 365 days), equals P&L {BASE} or 0 |
27
+
| MKT VAL {BASE}| Current market value in base currency|
26
28
| CATEGORY | User-defined asset category from configuration |
27
29
| TYPE | User-defined asset type |
28
30
| SECTOR | User-defined sector classification |
29
31
| GEO | User-defined geographic classification |
30
32
33
+
`{BASE}` is replaced with the `--base-currency` value (e.g., `AVG USD`, `P&L CAD`).
34
+
31
35
## Sort order
32
36
33
37
Lots are sorted by date (oldest first), then by symbol, then by account.
34
38
35
39
## Tax-exempt lots
36
40
37
-
Lots from tax-exempt accounts show zero P&L, P&L USD, STCG USD, and LTCG USD.
41
+
Lots from tax-exempt accounts show zero P&L, P&L {BASE}, STCG {BASE}, and LTCG {BASE}.
38
42
39
43
## Totals row
40
44
41
-
The table output includes a totals row summing P&L USD, STCG USD, LTCG USD, and MKT VAL USD.
45
+
The table output includes a totals row summing P&L {BASE}, STCG {BASE}, LTCG {BASE}, and MKT VAL {BASE}.
42
46
43
47
## Flags
44
48
@@ -48,3 +52,4 @@ The table output includes a totals row summing P&L USD, STCG USD, LTCG USD, and
48
52
|`--format`|`table`| Output format: `table`, `csv`, or `json`|
49
53
|`--download`|`false`| Download fresh data before displaying |
50
54
|`--symbol`| (all) | Filter to a specific symbol. Omit to show all symbols. |
55
+
|`--base-currency`|`USD`| Base currency for value conversion (case-insensitive, e.g., `USD`, `CAD`) |
0 commit comments