The same direct-indexing engine is run in four jurisdictions on this platform. The optimizer is identical across them — same objective, same constraint set, same risk model. What changes, underneath, is the tax engine: lot identification, wash-sale equivalent, holding-period boundary, and any jurisdiction-specific exemptions or surcharges. This post is about what bites where, and what the after-tax dollar looks like at the end of a five-year backtest when the same construction is swapped between US, Canadian, Australian, and Indian rules.
What changes when the jurisdiction selector flips
| US | CA | AU | IN | |
|---|---|---|---|---|
| Lot ID | HIFO (configurable) | ACB averaging (statutory) | FIFO (with discount window) | FIFO (with grandfathering) |
| Wash-sale equivalent | §1091 · 30 days both sides | Superficial loss · 30 days both sides | ATO 'mischief' guidance · case law | None statutory |
| Long-term boundary | 12 months | n/a (single rate) | 12 months (50% CGT discount) | 12 months (LTCG @ 10%) |
| Headline rate (top band) | 37% st / 23.8% lt (incl. NIIT) | 33% federal + provincial | Marginal × 0.5 for LT | 10% LTCG > ₹1L; 15% STCG |
| Transaction tax | — | — | — | STT 0.1% buy + sell |
| Grandfathering | — | — | — | Pre-31-Jan-2018 basis = max(cost, FMV) |
One optimizer, four pluggable τ functions
The objective is the same one the long-only deep-dive describes — a TE penalty against the local benchmark plus a tax-cost penalty. The variable that changes is τ, the tax-cost function:
τ_𝒥(w) = Σ_t rate_𝒥(holding_period_t) · realised_gain_𝒥(t, w)
+ surcharge_𝒥(realised_𝒥)
+ transaction_tax_𝒥(turnover)
where 𝒥 ∈ {US, CA, AU, IN}
rate_𝒥, realised_𝒥, surcharge_𝒥, transaction_tax_𝒥 are the
jurisdiction's lot-identification rule + applicable rate scheduleThe lot-identification piece is the consequential one. Under HIFO the optimizer can target the highest-cost lot to maximise the realised loss on a sale; under ACB it can't — every sale realises a slice of the average basis, so the harvest signal has to be larger in magnitude before the trade clears. Under FIFO with a discount window (AU) the optimizer trades off early realisation against waiting for the long-term gate to open, so the timing dimension becomes load-bearing.
Same vintage, same universe, four engines
The cleanest comparison: same start date (Jan 2 2019), same universe (US large-cap, 100 names — used as a stand-in for "large-cap liquid index" in each jurisdiction; production runs use the local large-cap index in CA, AU, and IN), same TE budget (5%), same starting capital ($1M), and only the tax engine swapped underneath.
| Jurisdiction | Pre-tax CAGR | After-tax CAGR | α | Harvest / NAV / yr | Turnover |
|---|---|---|---|---|---|
| United States | 12.6% | 12.0% | +130 bp | 1.4% | 26% |
| Canada | 12.4% | 11.4% | +50 bp | 0.6% | 18% |
| Australia | 12.5% | 11.6% | +88 bp | 0.8% | 22% |
| India | 12.5% | 11.7% | +62 bp | 1.0% | 32% |
Tax alpha is not portable. The same optimizer, on the same names, on the same dates, produces 50 bp in Canada and 130 bp in the US — because lot mechanics, not portfolio construction, are what HIFO turns on.
The interesting edge cases
- CA · ACB averaging. When the optimizer would otherwise sell into a small embedded loss, the ACB rule means the realised loss is fractional of the average — you have to dispose of the entire position to fully realise it. The optimizer respects this and will sometimes hold a partial loss rather than realise a smaller fraction.
- AU · holding-period gate. A name 11 months 28 days into a long-term hold has a dramatically different marginal tax cost than the same name 12 months 2 days in. The optimizer's tax penalty is a step function across that gate, and the solver makes timing trades (delay a sell by a few days) when the discount kicks in.
- IN · grandfathering crossover. For lots acquired before 31 Jan 2018, basis is max(historical cost, FMV on 31 Jan 2018). The optimizer's lot record carries both numbers and uses the right one when computing realised gain. Without this, harvest signals on long-tenured pre-2018 lots are systematically wrong.
- US · NIIT and AMT interactions. The 3.8% NIIT layer sits on top of long-term capital gains for high earners, so the effective long-term rate is 23.8% rather than 20%. The optimizer reads marginal-rate inputs on a per-account basis to handle this.
What's not in the engine
- State / provincial tax. The engines compute federal-equivalent tax only. State (US), provincial (CA), and other sub-national layers are passed through as a marginal-rate adjustment but not modelled with their own statutes.
- Cross-border treaties. The engine assumes a single jurisdiction per account. Treaties are not applied; if you need a treaty position computed, that's a separate workflow.
- Inheritance / step-up. Step-up at death, gifting basis-carryover, and pension-vehicle wrappers are not modelled here. They sit downstream of the optimizer in the household-planning layer.
- United States — IRC §1091 (wash sales), §1259 (constructive sales), §1411 (NIIT), §1(h) (capital gain rate schedule).
- Canada — Income Tax Act §40(2)(g)(i) (superficial loss), §47 (identical properties / ACB rule).
- Australia — Income Tax Assessment Act 1997, Part 3-1 (CGT). The 50% discount is in §115-25; the ATO's 'wash sale' guidance is in TR 2008/1.
- India — Income Tax Act §10(38), §112A (LTCG on listed equity), STT under the Finance (No. 2) Act, 2004. Grandfathering provision in §112A(4).
Educational illustration · numbers illustrative. Nothing here is investment, tax, or legal advice; jurisdictional rules change.