Open source · Canada and US · runs on your machine
taxjson computes capital gains the way the CRA and IRS actually define them: one average-cost pool per security across all of your accounts, the superficial-loss rule checked against your RRSP and TFSA too, options premiums folded where IT-479R says they go, splits, spin-offs and mergers handled with elections you choose. Nothing leaves your computer.
bash -c "$(curl -fsSL https://taxjson.com/install.sh)"
One line on Linux or macOS. Then taxjson init --country canada (or usa) in an empty folder, drop your exports into inputs/, and taxjson run.
REALIZED-GAINS SUMMARY — CAD, tax year 2026, basis: wash-adjusted TAXABLE ACCOUNTS ACCOUNT STOCK OPTION REALIZED DIVIDEND FEES margin 18,204.10 2,310.00 20,514.10 4,120.55 412.30 crypto 1,975.42 0.00 1,975.42 0.00 61.18 -------------------------------------------------------------- SUBTOTAL 20,179.52 2,310.00 22,489.52 4,120.55 473.48 NOTE: 2 superficial loss(es) denied — $1,236.40 deferred into the RRSP repurchase (see wash_radar_margin.rpt)
How it works
Download the activity CSV from each broker — IBKR Flex, Questrade, RBC Direct Investing, Webull, Kraken, Coinbase — and drop it into inputs/<account>/. Registered accounts are declared as sheltered in one config file.
taxjson run parses every file, converts at the Bank of Canada rate for each trade date, pools identical securities, applies splits and corporate actions, then runs the superficial-loss pass across taxable and registered accounts.
Per-account summaries, the wash radar ("safe to sell at a loss?"), harvest candidates, T1135, a tax estimate, and form-ready exports for Schedule 3 or Form 8949 land in reports/. Every number traces back to the rows that produced it.
Tax year, country, province and base currency live in taxjson.toml. Manual history goes in plain-text .tt files for anything a broker didn't export — an old position, a transfer in, a custody move.
Rules with receipts
Tax software usually asks you to trust it. taxjson ships REFERENCES.md: each computation names the section of the Act and the CRA or IRS document it follows — and each thing it deliberately does not do names the provision it skips.
s.54 / s.40(2)(g) / s.53(1)(f), with the RRSP and TFSA treated as affiliated persons (s.251.1). A loss denied because the plan repurchased is reported as permanently denied, not quietly deferred.
s.47 average cost, IT-387R2: a US listing and its TSX listing are the same pool; a CDR is not. Trades settle on the settlement date, as T4037 says.
IT-479R: premium on an assigned put reduces the shares' cost; on an exercised put it reduces the proceeds; expiries are losses of the premium.
Splits, spin-offs (s.86.1), mergers with the s.85.1 rollover election, return of capital, reinvested distributions — with the election recorded and the filing reminder printed at every run.
IRC §1091 with the Reg. 1.1091-1 matching, §1223(3) holding-period tacking, Rev. Rul. 2008-5 for IRA repurchases, Rev. Rul. 66-7 for the one-year test.
Property, per the CRA guide: every swap, spend and gift is a disposition at fair value; identical-property pooling and the superficial-loss rule apply per coin.
Verification
Internal reports can all agree and still be wrong. taxjson sanity compares the positions the books say you hold against the positions your broker says you hold, account by account. Any difference is either a trade that hasn't reached the CSV yet or a booking problem — there is no third kind. taxjson run finishes with that check.
Every fix lands with a regression test that failed before it. No mocks for tax math.
Conservation of gains under the superficial-loss rule, transfer-netting, and split-during-settlement books, thousands of random ledgers per run.
KNOWN_ISSUES.md lists what is out of scope and what to do by hand, with the citation.
Brokers
Questrade and IBKR Flex statements can be fetched directly with taxjson fetch. Every parser accounts for every row it reads: rows it recognises but doesn't translate are counted and named, never silently dropped.
Install
bash -c "$(curl -fsSL https://taxjson.com/install.sh)"
Installs the latest release into ~/.local/share/taxjson with its own Python environment and links taxjson into ~/.local/bin. Re-run to upgrade. Needs git and Python 3.9+; nothing else. TAXJSON_CHANNEL=dev tracks the development branch.
mkdir -p ~/taxes/2026 && cd ~/taxes/2026 taxjson init --country canada # or usa; writes taxjson.toml and inputs/ # drop broker CSVs into inputs/margin/, inputs/rrsp/, … taxjson run # books, reports, wash pass, sanity check taxjson sum # the realized-gains summary taxjson wash-radar # "can I sell this at a loss today?"
Prefer a clone? git clone https://github.com/taxjson/taxjson && cd taxjson && scripts/dev-setup.sh. The full pipeline reaches the Bank of Canada (FX rates) and, for unpriced crypto rows, Yahoo Finance; TAXJSON_OFFLINE=1 forbids both. No broker credential is ever stored or sent anywhere but that broker.
Frequently asked
No. Parsing, pooling and the wash pass all run locally. The only network calls are FX rates from the Bank of Canada and crypto prices from Yahoo Finance for rows the export didn't price, and both can be disabled. SECURITY.md lists every egress.
Both engines ship. Canada is average-cost ACB with the superficial-loss rule and settlement-date timing; the US engine is FIFO with §1091 wash sales, short/long-term classification and Form 8949 codes. One project, one country.
taxjson filed locks the numbers you filed; later runs flag any drift so an engine fix or a late row shows up as a candidate for an adjustment rather than a silent change.
Parsers are small, self-contained modules with a row-accounting contract; a new one is a contribution most people can make in an afternoon, and the plain-text .tt format covers you in the meantime.
A 2,100-test suite that every change must pass, three property fuzzers, an audit log in the changelog, and — the one that matters — taxjson sanity against your broker's own position export.