Skip to content

Commit 7fa45bf

Browse files
committed
commit
1 parent 1dd05d9 commit 7fa45bf

32 files changed

+1828
-212
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
/.tmp/
2+
/book/book/

Makefile

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,19 @@ GOLANGCI_LINT_ARCH := arm64
3333
else
3434
GOLANGCI_LINT_ARCH := $(UNAME_ARCH)
3535
endif
36+
MDBOOK_VERSION := 0.5.2
37+
ifeq ($(UNAME_OS),Darwin)
38+
MDBOOK_OS := apple-darwin
39+
ifeq ($(UNAME_ARCH),arm64)
40+
MDBOOK_ARCH := aarch64
41+
else
42+
MDBOOK_ARCH := x86_64
43+
endif
44+
else ifeq ($(UNAME_OS),Linux)
45+
MDBOOK_OS := unknown-linux-gnu
46+
MDBOOK_ARCH := $(UNAME_ARCH)
47+
endif
48+
3649

3750
GO_GET_PKGS ?=
3851

@@ -94,6 +107,14 @@ upgrade: ## Upgrade dependencies
94107
go get -u -t ./... $(GO_GET_PKGS)
95108
go mod tidy -v
96109

110+
.PHONY: book
111+
book: $(BIN)/mdbook ## Serve the book locally
112+
cd ./book && $(abspath $(BIN)/mdbook) serve --open --port 3000
113+
114+
.PHONY: bookbuild
115+
bookbuild: $(BIN)/mdbook ## Build the book for static hosting
116+
cd ./book && $(abspath $(BIN)/mdbook) build
117+
97118
$(BIN)/buf: Makefile
98119
@mkdir -p $(BIN)
99120
go install github.com/bufbuild/buf/cmd/buf@$(BUF_VERSION)
@@ -118,6 +139,17 @@ $(BIN)/protoc-gen-go:
118139
@mkdir -p $(BIN)
119140
go install google.golang.org/protobuf/cmd/protoc-gen-go
120141

142+
# Download the mdbook binary from GitHub releases.
143+
$(BIN)/mdbook: Makefile
144+
$(eval DIR=$(abspath $(BIN)))
145+
@mkdir -p $(DIR)
146+
$(eval MDBOOK_TMP := $(shell mktemp -d))
147+
cd $(MDBOOK_TMP); \
148+
curl -fsSL "https://github.com/rust-lang/mdBook/releases/download/v$(MDBOOK_VERSION)/mdbook-v$(MDBOOK_VERSION)-$(MDBOOK_ARCH)-$(MDBOOK_OS).tar.gz" -o mdbook.tar.gz && \
149+
tar -xzf mdbook.tar.gz && \
150+
mv mdbook $(DIR)/mdbook
151+
@rm -rf "$(MDBOOK_TMP)"
152+
121153
.PHONY: __checknodiffgeneratedinternal
122154
__checknodiffgeneratedinternal:
123155
@echo bash etc/script/checknodiffgenerated.bash make generate

README.md

Lines changed: 24 additions & 191 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# ibctl
22

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 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.
46

57
## Prerequisites
68

@@ -26,145 +28,12 @@ export IBKR_FLEX_WEB_SERVICE_TOKEN="your-flex-web-service-token"
2628
ibctl holding list
2729
```
2830

29-
## Directory Structure
30-
31-
All commands operate on an **ibctl directory** specified by `--dir` (defaults to the current directory). This directory has a well-known layout:
32-
33-
```
34-
<dir>/
35-
├── ibctl.yaml # Configuration file
36-
├── data/ # Persistent — do not delete
37-
│ └── accounts/<alias>/
38-
│ └── trades.json # Incrementally merged trade history
39-
├── cache/ # Safe to delete — re-populated on next download
40-
│ ├── accounts/<alias>/
41-
│ │ ├── positions.json # Latest IBKR-reported positions snapshot
42-
│ │ ├── transfers.json # Position transfers (ACATS, FOP, internal)
43-
│ │ ├── trade_transfers.json # Cost basis for transferred positions
44-
│ │ ├── corporate_actions.json # Stock splits, mergers, spinoffs
45-
│ │ └── cash_positions.json # Cash balances by currency
46-
│ └── fx/<BASE>.<QUOTE>/
47-
│ └── rates.json # Daily FX rates per currency pair
48-
├── activity_statements/ # User-managed IBKR Activity Statement CSVs
49-
│ └── <alias>/*.csv
50-
└── seed/ # Optional — pre-transfer tax lots from previous brokers
51-
└── <alias>/transactions.json
52-
```
53-
54-
- **`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)
76-
- **Corporate Actions** (captures stock splits, mergers, spinoffs)
77-
6. Under **Delivery Configuration**, set:
78-
- **Format**: `XML`
79-
- **Period**: `Last 365 Calendar Days`
80-
7. Under **General Configuration**, set:
81-
- **Date Format**: `yyyyMMdd`
82-
- **Time Format**: `HHmmss`
83-
- **Date/Time Separator**: `; (semi-colon)`
84-
- **Include Canceled Trades?**: `No`
85-
- **Include Currency Rates?**: `Yes`
86-
- **Include Audit Trail Fields?**: `No`
87-
- **Breakout by Day?**: `No`
88-
8. Click **Save**.
89-
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-
10031
## Environment Variables
10132

10233
| Variable | Required | Description |
10334
|----------|----------|-------------|
10435
| `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. |
10536

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.
139-
- `categorization` — optional classification metadata for holdings display (category, type, sector, geo)
140-
- `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.
142-
143-
## Usage
144-
145-
```bash
146-
# Set the IBKR token.
147-
export IBKR_FLEX_WEB_SERVICE_TOKEN="your-flex-web-service-token"
148-
149-
# View combined holding list (downloads data automatically).
150-
ibctl holding list
151-
ibctl holding list --format csv
152-
ibctl holding list --format json
153-
ibctl holding list --cached # Skip download, use cached data only
154-
155-
# Force re-download of IBKR data (all accounts).
156-
ibctl download
157-
158-
# Probe the API to see what data is available per account.
159-
ibctl probe
160-
161-
# Archive the ibctl directory to a zip file.
162-
ibctl data zip -o backup.zip
163-
164-
# Use a different ibctl directory (default is current directory).
165-
ibctl holding list --dir ~/Documents/ibkr
166-
```
167-
16837
## Commands
16938

17039
| Command | Description |
@@ -175,70 +44,34 @@ ibctl holding list --dir ~/Documents/ibkr
17544
| `ibctl data zip -o <file>` | Archive the ibctl directory to a zip file |
17645
| `ibctl download` | Download and cache IBKR data via Flex Query API |
17746
| `ibctl holding list` | Display holdings with prices, positions, and classifications |
47+
| `ibctl holding lot list` | Display individual FIFO tax lots |
48+
| `ibctl holding category list` | Display holdings aggregated by category |
49+
| `ibctl holding geo list` | Display holdings aggregated by geographic classification |
50+
| `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 |
17852
| `ibctl probe` | Probe the API and show per-account data counts |
17953

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.
20255

203-
6. Save each CSV in the account's subdirectory. Filenames don't matter — ibctl reads all `*.csv` files recursively.
56+
## Tax Reporting
20457

205-
7. Run `ibctl holding list` — data from the CSVs is merged with Flex Query API data.
58+
Generate CSV files for your accountant:
20659

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).
220-
221-
| File | Proto Message | Merge Strategy | Purpose |
222-
|------|--------------|----------------|---------|
223-
| `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
23063

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+
```
23267

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.
23469

235-
### Data Pipeline
70+
## Documentation
23671

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.
23873

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.
74+
```bash
75+
# Serve the book locally with live reload.
76+
make book
77+
```

book/book.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[book]
2+
title = "ibctl"
3+
authors = ["Peter Edge"]
4+
language = "en"
5+
6+
[output.html.print]
7+
enable = true

book/src/SUMMARY.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Summary
2+
3+
- [Introduction](index.md)
4+
- [Quick Start](quickstart.md)
5+
- [Installation](installation.md)
6+
- [Configuration](configuration.md)
7+
- [IBKR Flex Query Setup](ibkr-setup.md)
8+
- [Directory Structure](directory-structure.md)
9+
- [Data Pipeline](data-pipeline.md)
10+
- [FIFO Tax Lot Computation](fifo.md)
11+
- [FX Rate Handling](fx-rates.md)
12+
- [Non-IBKR Additions](additions.md)
13+
- [Commands](commands/overview.md)
14+
- [download](commands/download.md)
15+
- [holding list](commands/holding-list.md)
16+
- [holding lot list](commands/holding-lot-list.md)
17+
- [holding category list](commands/holding-category-list.md)
18+
- [holding geo list](commands/holding-geo-list.md)
19+
- [holding value](commands/holding-value.md)
20+
- [trade sale list](commands/trade-sale-list.md)
21+
- [Tax Reporting Guide](tax-reporting.md)
22+
- [Glossary](glossary.md)

0 commit comments

Comments
 (0)