Open source · Canada (US engine experimental) · runs on your machine
taxjson computes capital gains by the rules the CRA publishes, each one cited (a US engine for the IRS rules ships as experimental): one average-cost pool per security across all of your accounts, the superficial-loss rule checked against your RRSP and TFSA too, written-option premiums recognised in the year written as s.49 says, renames as dated events, 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; it installs taxjson and its short name tjs. Try it first on made-up exports: tjs init --demo ~/taxjson-demo. For your own books, tjs init --country canada (or usa) in the folder for your taxes, drop every year's exports into inputs/, and tjs run in this year's folder (2026/). Not sure what comes next? tjs checklist lists every step and, in a project, checks what is done and names the next one.
FOR THE RETURN — taxable accounts (crypto, ib, margin, questrade, webull), CAD (Schedule 3, tax year 2024) GAIN = PROCEEDS − COST(ACB) − OUTLAYS, the allowed gain; superficial losses denied: 467.49. shares & fund units (P1) (10689/10690) proceeds 71,112.60 cost(acb) 63,568.08 outlays 14.89 gain 7,529.63 denied 0.00 shares & fund units (P2) (13199/13200) proceeds 21,986.83 cost(acb) 21,432.29 outlays 11.00 gain 543.54 denied 467.49 options, crypto & other properties (P2) (15199/15300) proceeds 22,775.26 cost(acb) 15,675.27 outlays 89.20 gain 7,010.79 denied 0.00 RETURN proceeds 115,874.69 cost(acb) 100,675.64 outlays 115.09 gain 15,083.96 denied 467.49 ! 1 sale with no purchase NOT in these totals (SAMPG.US) — tjs find-missing-history ! FX on foreign cash (line 15300): not in the rows; its estimate is NOT RELIABLE — tjs fx-cash Not in the rows: T3/T5 slip capital gains (lines 17600, 17400). More: tjs sum --details
What you get
The whole of tjs sum on the demo project: eight made-up accounts, five taxable (an IB margin account, Questrade, Webull, crypto) and three sheltered (a TFSA, an RRSP and a locked-in LIRA). Gains, options, dividends and fees per account; the sheltered ones shown but kept out of the return; then the Schedule 3 lines, with the superficial losses denied (one of them for good, because the RRSP bought the stock back) and a ! line for anything the totals leave out. Run tjs init --demo ~/taxjson-demo, cd ~/taxjson-demo/2024, tjs run and tjs sum to see it yourself.
REALIZED-GAINS SUMMARY — CAD, tax year 2024, basis: wash-adjusted REALIZED = NON-OPT (shares, units, futures, crypto) + OPTION; TOTAL = REALIZED + DIVIDEND + PIL. DIVIDEND of crypto is staking rewards: ordinary income, not dividends. TAXABLE ACCOUNTS ACCOUNT NON-OPT OPTION REALIZED DIVIDEND PIL FEES TOTAL ---------------------------------------------------------------------------- crypto 5,439.28 0.00 5,439.28 23.85 0.00 150.09 5,463.13 ib 3,032.88 0.00 3,032.88 102.22 0.00 6.90 3,135.10 margin 64.67 206.36 271.03 681.75 0.00 8.28 952.78 questrade 2,718.25 0.00 2,718.25 24.69 0.00 31.99 2,742.94 webull 2,257.37 1,365.14 3,622.51 0.00 0.00 39.58 3,622.51 ---------------------------------------------------------------------------- SUBTOTAL 13,512.45 1,571.50 15,083.95 832.51 0.00 236.84 15,916.46 SHELTERED ACCOUNTS — not taxable ACCOUNT NON-OPT OPTION REALIZED DIVIDEND PIL FEES TOTAL ---------------------------------------------------------------------- lira 235.15 0.00 235.15 420.00 0.00 19.80 655.15 rrsp 471.51 -275.27 196.24 132.93 0.00 7.65 329.17 tfsa 3,143.45 0.00 3,143.45 528.00 0.00 39.80 3,671.45 ---------------------------------------------------------------------- SUBTOTAL 3,850.11 -275.27 3,574.84 1,080.93 0.00 67.25 4,655.77 ALL ACCOUNTS — sheltered included ACCOUNT NON-OPT OPTION REALIZED DIVIDEND PIL FEES TOTAL ---------------------------------------------------------------------------- crypto 5,439.28 0.00 5,439.28 23.85 0.00 150.09 5,463.13 ib 3,032.88 0.00 3,032.88 102.22 0.00 6.90 3,135.10 lira 235.15 0.00 235.15 420.00 0.00 19.80 655.15 margin 64.67 206.36 271.03 681.75 0.00 8.28 952.78 questrade 2,718.25 0.00 2,718.25 24.69 0.00 31.99 2,742.94 rrsp 471.51 -275.27 196.24 132.93 0.00 7.65 329.17 tfsa 3,143.45 0.00 3,143.45 528.00 0.00 39.80 3,671.45 webull 2,257.37 1,365.14 3,622.51 0.00 0.00 39.58 3,622.51 ---------------------------------------------------------------------------- TOTAL 17,362.56 1,296.23 18,658.79 1,913.44 0.00 304.09 20,572.23 FOR THE RETURN — taxable accounts (crypto, ib, margin, questrade, webull), CAD (Schedule 3, tax year 2024) GAIN = PROCEEDS − COST(ACB) − OUTLAYS, the allowed gain; superficial losses denied: 467.49. SCHEDULE 3 LINE PROCEEDS COST(ACB) OUTLAYS GAIN ------------------------------------------------------------------------------------------------- shares & fund units (P1) (10689/10690) 71,112.60 63,568.08 14.89 7,529.63 shares & fund units (P2) (13199/13200) 21,986.83 21,432.29 11.00 543.54 options, crypto & other properties (P2) (15199/15300) 22,775.26 15,675.27 89.20 7,010.79 ------------------------------------------------------------------------------------------------- RETURN 115,874.69 100,675.64 115.09 15,083.96 ! 1 sale with no purchase NOT in these totals (SAMPG.US) — tjs find-missing-history ! FX on foreign cash (line 15300): not in the rows; its estimate is NOT RELIABLE — tjs fx-cash Not in the rows: T3/T5 slip capital gains (lines 17600, 17400). More: tjs sum --details
REALIZED-GAINS SUMMARY — CAD, tax year 2024, basis: wash-adjusted REALIZED = NON-OPT (shares, units, futures, crypto) + OPTION; TOTAL = REALIZED + DIVIDEND + PIL. DIVIDEND of crypto is staking rewards: ordinary income, not dividends. TAXABLE ACCOUNTS crypto non-opt 5,439.28 option 0.00 realized 5,439.28 dividend 23.85 pil 0.00 fees 150.09 total 5,463.13 ib non-opt 3,032.88 option 0.00 realized 3,032.88 dividend 102.22 pil 0.00 fees 6.90 total 3,135.10 margin non-opt 64.67 option 206.36 realized 271.03 dividend 681.75 pil 0.00 fees 8.28 total 952.78 questrade non-opt 2,718.25 option 0.00 realized 2,718.25 dividend 24.69 pil 0.00 fees 31.99 total 2,742.94 webull non-opt 2,257.37 option 1,365.14 realized 3,622.51 dividend 0.00 pil 0.00 fees 39.58 total 3,622.51 SUBTOTAL non-opt 13,512.45 option 1,571.50 realized 15,083.95 dividend 832.51 pil 0.00 fees 236.84 total 15,916.46 SHELTERED ACCOUNTS — not taxable ACCOUNT NON-OPT OPTION REALIZED DIVIDEND TOTAL --------------------------------------------------------- lira 235.15 0.00 235.15 420.00 655.15 rrsp 471.51 -275.27 196.24 132.93 329.17 tfsa 3,143.45 0.00 3,143.45 528.00 3,671.45 --------------------------------------------------------- SUBTOTAL 3,850.11 -275.27 3,574.84 1,080.93 4,655.77 ALL ACCOUNTS — sheltered included crypto non-opt 5,439.28 option 0.00 realized 5,439.28 dividend 23.85 pil 0.00 fees 150.09 total 5,463.13 ib non-opt 3,032.88 option 0.00 realized 3,032.88 dividend 102.22 pil 0.00 fees 6.90 total 3,135.10 lira non-opt 235.15 option 0.00 realized 235.15 dividend 420.00 pil 0.00 fees 19.80 total 655.15 margin non-opt 64.67 option 206.36 realized 271.03 dividend 681.75 pil 0.00 fees 8.28 total 952.78 questrade non-opt 2,718.25 option 0.00 realized 2,718.25 dividend 24.69 pil 0.00 fees 31.99 total 2,742.94 rrsp non-opt 471.51 option -275.27 realized 196.24 dividend 132.93 pil 0.00 fees 7.65 total 329.17 tfsa non-opt 3,143.45 option 0.00 realized 3,143.45 dividend 528.00 pil 0.00 fees 39.80 total 3,671.45 webull non-opt 2,257.37 option 1,365.14 realized 3,622.51 dividend 0.00 pil 0.00 fees 39.58 total 3,622.51 TOTAL non-opt 17,362.56 option 1,296.23 realized 18,658.79 dividend 1,913.44 pil 0.00 fees 304.09 total 20,572.23 FOR THE RETURN — taxable accounts (crypto, ib, margin, questrade, webull), CAD (Schedule 3, tax year 2024) GAIN = PROCEEDS − COST(ACB) − OUTLAYS, the allowed gain; superficial losses denied: 467.49. shares & fund units (P1) (10689/10690) proceeds 71,112.60 cost(acb) 63,568.08 outlays 14.89 gain 7,529.63 denied 0.00 shares & fund units (P2) (13199/13200) proceeds 21,986.83 cost(acb) 21,432.29 outlays 11.00 gain 543.54 denied 467.49 options, crypto & other properties (P2) (15199/15300) proceeds 22,775.26 cost(acb) 15,675.27 outlays 89.20 gain 7,010.79 denied 0.00 RETURN proceeds 115,874.69 cost(acb) 100,675.64 outlays 115.09 gain 15,083.96 denied 467.49 ! 1 sale with no purchase NOT in these totals (SAMPG.US) — tjs find-missing-history ! FX on foreign cash (line 15300): not in the rows; its estimate is NOT RELIABLE — tjs fx-cash Not in the rows: T3/T5 slip capital gains (lines 17600, 17400). More: tjs sum --details
On a narrow terminal tjs sum stacks each account's figures, as above on a phone; tjs sum <account> shows one account, tjs sum --details what the short view leaves out.
How it works
Download the activity CSV from each broker — Interactive Brokers, Questrade, RBC Direct Investing, Webull, Kraken, Coinbase — and drop every year's files into inputs/<account>/, one folder shared by all your tax years; or let tjs fetch download Questrade and IBKR for you (the taxjson-fetch plugin, installed by default; --without-fetch leaves it out). Registered accounts are declared as sheltered in taxjson.toml.
tjs run parses every file, converts at the Bank of Canada rate for each settlement date (Valet API), pools identical securities, applies renames, splits and corporate actions on their dates, then runs the superficial-loss pass across taxable and registered accounts.
Per-account summaries, Schedule 3 or Form 8949 rows, the wash radar ("safe to sell at a loss?"), harvest candidates, T1135, a tax and minimum-tax estimate land in reports/ and behind the commands. Every number traces back to the rows that produced it.
tjs init makes one folder for your taxes: inputs/ with every year's exports, and a folder per tax year (2025/, 2026/, …; tjs new-year starts the next one from this one). Each year's folder holds the two files you edit: taxjson.toml (the tax year, country, province, accounts and the figures you enter by hand — prior-year carryovers, fund distributions) and ticker.map (symbol mappings), plus that year's broker positions snapshots in holdings/ and the results. In Canada, tobase.map beside the year folders holds the TSX / US interlisted pairs that ship with taxjson, one file every year reads (tjs update-tobase-map brings it up to date). What a broker didn't export — an old purchase, a transfer in, a rename on its date — goes in plain-text .tt files in the account's folder. tjs years shows every year at a glance.
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), sized with CRA's formula for each sale: the least of the units sold, the units bought in the 61-day window and the units held at day 30. The RRSP and TFSA are 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 count on the settlement date, as T4037 says.
A ticker change happens on a date: the pool, its cost and the acquisition dates move to the new symbol then. A later trade in the old ticker is never silently pooled — tjs renames lists it, and the rename's .tt line (RENAME <date> OLD NEW late=…) says whether it is the same shares or another company.
s.49(1)–(4), IT-479R: a written option's premium is a gain in the year written, a buy-back a loss in its own year, an assignment folds the premium into the shares, and an exercised warrant's cost goes into the shares (s.49(3)). tjs option-boundary says whether a filed year needs a T1-ADJ.
A dividend belongs to the year it is paid (s.82(1)); a Canadian trust's distribution to the year it became payable (s.104(13)), dated by its record date. Payments in lieu, return of capital and reinvested distributions each have their own rule.
Splits, spin-offs (s.86.1), mergers with the s.85.1 rollover election, return of capital — with the election recorded and the filing reminder printed at every run.
tjs amt shows the 2024+ AMT line by line with its 7-year carryover (s.120.2); close-year records the year's carry-forwards and next year's estimate, carryover and handoff start from them.
IRC §1091 wash sales across your accounts, Rev. Rul. 2008-5 for IRA repurchases, lots carried between your own accounts, §355 spin-offs and §356 boot per lot (Reg. 1.358-2, 1.356-1(b)), Form 8949 codes.
Property, per the CRA guide: every swap, spend and gift is a disposition at fair value; a network fee paid in the coin is one too; identical-property pooling and the superficial-loss rule apply per coin.
tjs tax-logic prints every rule taxjson applies for your project's country, one line each, with your settings filled in. It is the spec: each rule has an id the tests cite, the build fails on a rule with no test, and Canadian and US rules never mix — a project has one country, and the other country's settings and commands are refused.
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. Save a positions snapshot per broker account in the year's holdings/ folder (tjs fetch --positions writes one): each is matched to its account by the account number inside it or its file name, with no setting, and every taxjson run then finishes with the same check.
Every fix lands with a regression test that failed before it, tagged with the tax-logic rule it pins. No mocks for tax math.
After eight review rounds and a pre-release security audit, two full-coverage audits of the whole codebase: about 1,300 findings, then 1,600 in the re-audit that also checked the first round's fixes. Every confirmed finding reproduced, fixed and pinned.
Three property fuzzers (gains conserved under the superficial-loss rule, transfer netting, split-during-settlement books) run thousands of random ledgers per run; mutation testing of both engines finds the boundaries no test pins.
tjs audit recomputes each disposition from its broker row through FX, the cost pool and the loss rule, and ties it to the reports; check-filed recomputes every year you locked and reports drift.
tjs check-dates holds every trade and settlement date to its market's calendar; edge-cases lists what lands on a year end or a 30-day boundary, and why.
KNOWN_ISSUES.md lists what is out of scope and what to do by hand, with the citation.
Commands
checklist lists every step from install to filing and, in a project, checks each and names the next; init makes the exports folder and the year's project (--demo: a made-up one to try), years and new-year handle the year folders, align, format, format-map and migrate keep the files tidy and current, fetch downloads Questrade and IBKR activity (plugin), elect records corporate-action elections, ticker-map adds the lines a run suggested, update-tobase-map updates the interlisted pairs (Canada).
run parses, converts, pools, applies corporate actions and the wash pass, and ends with the broker cross-check; crypto-sends, find-missing-history and opening settle what the exports leave open.
sum for the year by form line, estimate and amt for the tax and minimum tax, instalments, stats for wins and losses on closed trades, fx-cash for currency gains on foreign cash, div-estimate for the dividends your holdings should pay next, from their own history.
list per account (or as of a date), shares combined across accounts.
events, trades, divs, dil, roc, gains, fees, leaps and transfers: the rows behind the numbers, over any window.
winners and losers ranked, and per-ticker totals: divs-sum, dil-sum, roc-sum, trades-sum, fees-sum, ccd-sum, leaps-sum.
wash-radar, buy-check and sell-check answer "is this safe from the superficial-loss rule today?"; rebuy lists what you sold at a loss and the first day you can buy each back; harvest shows unrealized gains and losses at current prices; tips gives tax-efficiency advice for next year; watch reports changes from cron.
form-export for Schedule 3 / Form 8949 rows, t1135, reconcile-slips against the T5008 / 1099-B, slip-audit for T5 / T3 slips, carryover, option-boundary, close-year to lock what you filed, check-filed for drift, and handoff to start the next year from it.
audit traces every gain to its broker row, wash-sales lists each denied loss and why, tax-logic states every rule, edge-cases and check-dates look at the boundaries, sanity compares with your broker, journals, renames, spinoffs and splits show each event.
redact strips personal details from an export so you can share it; help shows the grouped list (--all: both countries' commands).
tjs with no command lists every command under these headings, one line each — inside a project only your country's (tjs help --all shows both). Every command takes -h; most take --json, and --details brings back the explanations the default view leaves out. Each command in full: docs/commands.md; a short overview is in the deck.
Brokers
Questrade and IBKR Flex activity can be downloaded directly with tjs fetch through the taxjson-fetch plugin, a separate package the installer installs by default (--without-fetch leaves it out). The core itself holds no broker client and reads no credential; a fetcher for another broker is a plugin of its own. Every parser accounts for every row it reads: rows it recognises but doesn't translate are counted and named, never silently dropped. Where each broker's download is, and its quirks: docs/brokers.md.
Install
bash -c "$(curl -fsSL https://taxjson.com/install.sh)"
Installs the stable release into ~/.local/share/taxjson with its own Python environment and links taxjson and its short name tjs into ~/.local/bin. Re-run to upgrade. Needs git and Python 3.9+; nothing else. Add _ --channel beta (or latest, dev for the development branch, or a version like v0.30.1) to pick another channel; the installer remembers it, so re-running upgrades along the same channel.
The Questrade / IBKR auto-fetch plugin comes with it; it contacts a broker only when you run tjs fetch for a configured account. To leave it out (remembered on upgrade; _ --with-fetch puts it back):
bash -c "$(curl -fsSL https://taxjson.com/install.sh)" _ --without-fetch
To try it first, a project of made-up exports (six accounts, tax year 2024) with the commands to run:
tjs init --demo ~/taxjson-demo
Your own books:
mkdir -p ~/taxes && cd ~/taxes tjs init --country canada # or usa; inputs/ for every year, 2026/ for this one cd 2026 # each year's folder: its taxjson.toml, ticker.map, results tjs checklist # every step: what is done, what is next # drop broker CSVs, all years together, into ../inputs/margin/, ../inputs/rrsp/, … tjs run # books, reports, wash pass, broker check tjs sum # the year, by Schedule 3 line tjs wash-radar # "can I sell this at a loss today?" tjs rebuy # when can I buy back what I sold at a loss? tjs checklist # again at year end: ready to file?
Prefer a clone? git clone https://github.com/taxjson/taxjson && cd taxjson && scripts/dev-setup.sh. Coming from an older version? docs/upgrading.md lists what each release asks of you. A step-by-step first project is in docs/getting-started.md.
Privacy and security
Parsing, pooling and the wash pass run on your computer — no account, no server, no web UI; just commands and files. A run goes online only for what its cache lacks: exchange rates from the Bank of Canada (Yahoo Finance before May 2007, for currencies the Bank does not publish, and for a US-dollar base), and from Yahoo Finance a price for a crypto row the export left unpriced or a close for an in-kind move to or from a registered account. It sends a currency pair or a symbol and a date, nothing about you. TAXJSON_OFFLINE=1 forbids every lookup.
The core holds no broker client and reads no broker credential. Auto-fetch is the separate taxjson-fetch package, which the installer adds by default (--without-fetch leaves it out): it does nothing until you run tjs fetch, talks only to your broker, keeps its token outside your project, and sends it nowhere else. harvest and tips --online look up current prices, so Yahoo sees those symbols.
A personal-data scan runs in every build and as the pre-push hook, fails closed, and reads a private denylist kept outside the repository. tjs redact strips the account numbers, names and contact details it recognises from an export while keeping every row shape — review the copy before you share it.
SECURITY.md lists every network call, what it sends and how to turn it off, and how to report a vulnerability. The core package needs nothing beyond Python's standard library (tomli before Python 3.11); the one-line installer also adds the fx extra (yfinance and pandas, for the Yahoo Finance rates and prices) and the taxjson-fetch plugin, which uses only the standard library.
Frequently asked
No. Parsing, pooling and the wash pass all run locally. A run looks up only what its cache lacks: FX rates from the Bank of Canada (Yahoo Finance before May 2007, for currencies the Bank does not publish, and for a US-dollar base), and from Yahoo Finance the price of a crypto row the export didn't price or the close for an in-kind move to or from a registered account; TAXJSON_OFFLINE=1 disables every lookup. harvest and tips --online look up current prices, so Yahoo sees those ticker symbols; the optional fetch plugin talks only to your broker. SECURITY.md lists every egress.
Canada is the supported product: average-cost ACB with the superficial-loss rule and settlement-date timing, validated against real multi-account books and the brokers' own positions. A US engine ships too — FIFO with §1091 wash sales across your accounts, lots carried between your own accounts, short/long-term classification and Form 8949 codes — but it is experimental: unit-tested, never validated on a real account. Use it as a draft; if it gets something wrong on your US books, open an issue with a made-up file that shows it (see "How do I get help?"). A project is one country; the two rule sets never mix.
tjs checklist. It is the filing checklist as a command: each step is proved by running the command behind it — a clean run, the broker cross-check, no missing cost basis, every disposition audited, the T5008 reconciled, no year-end option needing an amendment — and the steps only you can confirm are marked with --done. --walk takes you through the open ones.
tjs close-year locks the numbers you filed; check-filed and every later run flag any drift so an engine fix or a late row shows up as a candidate for an adjustment rather than a silent change. The lock also records what next year needs — the positions and cost at Dec 31, the trades that settle in January, the net capital loss and minimum-tax carry-forwards — and next year's tjs handoff checks the new project starts from exactly that.
Parsers are small, self-contained modules with a row-accounting contract; a new one is an afternoon's work given the export's layout. Open an issue with its header line and a few made-up rows in the same shape (never a raw export; see "How do I get help?"). Meanwhile the generic importer reads any CSV through a column mapping you write, and the plain-text .tt format covers the rest (docs/brokers.md).
An 8,500+-test suite that every change must pass, each tax test tagged with the rule it pins; three property fuzzers and mutation testing; eight review rounds and two full-coverage audits (about 1,300 and 1,600 findings) logged in the changelog; and — the one that matters — tjs sanity against your broker's own position export. Every computation names its ITA / CRA / IRC source in REFERENCES.md, and tjs tax-logic states each rule as the code applies it.
Upgrade by re-running the installer, then read docs/upgrading.md: it lists, release by release, every change that asks something of you. Most come down to tjs migrate (--dry-run first): since v0.27.0 it turns a missing_history.json into .tt lines, and since v0.27.1 it makes the per-year copies of tobase.map one shared file beside the year folders. A command that needs it stops and names the file until you do. The same page lists the problems an upgrade alone fixes.
First tjs checklist in your project: it names the next step and its command. For a message you don't understand, search docs/troubleshooting.md for its text (on an older release, also the fixes by release). For a bug, open an issue with the bug-report template. Never attach a raw export: reproduce the problem on a made-up file first (copy your broker's demo CSV from examples/ and edit its rows). Only if that is not possible, tjs redact a copy — it strips account numbers, names and addresses but keeps amounts and dates — read all of it, and share it only after that review.