StrategiesTax-Aware Direct Indexing

Tax-Aware Direct Indexing

Track a benchmark. Harvest losses lot by lot. Respect wash sales.

Net exposure
100%
Tracking error
≤ 5%
Role
Standalone book
Backtest · 20152024Illustrative
Ann. return (after-tax)
10.1%
Tracking error
12.6%
After-tax boost
+0.7%
US large-cap index

The dark track is the strategy's after-tax NAV. The grey track is the benchmark NAV. Both start at $1M on the start date, rebalanced daily under the CLARABEL solver.

Numbers are plausible placeholders, not a real backtest. Replace by running taxview-runner over the same window. Daily rebalance, CLARABEL solver. Marginal-rate assumptions: short-term 37%, long-term 20%, NIIT 3.8%.

Illustrative
2024-12-31(end of window)
Strategy (after-tax)$10.11M
Benchmark$10.46M
$2.00M$4.00M$6.00M$8.00M$10.00M20152017201920212023
Static view. Resize wider for hover details.
Ann. return
10.1%
Benchmark return
8.7%
Alpha (after-tax)
71 bps
Tracking error
1260 bps
Avg turnover
14.8%
Lifetime harvested
38.6% NAV
The idea

An index fund holds the index opaquely. Direct indexing holds the same names transparently — so every name that drops can be sold for a tax loss against your other gains, then replaced inside the tracking-error budget.

The objective
Subject to
Σ wᵢ = 1Fully invested — weights sum to 1
0 ≤ wᵢ ≤ c_maxLong-only with per-name cap
TE(w) ≤ TE_maxTracking error stays inside the budget

Minimize the weighted sum of two things: (i) the portfolio's factor distance from the benchmark, measured under the risk model Σ, and (ii) the tax cost on realized gains. The two weights λ_te and λ_tax are the conductor's batons — push λ_tax up and the optimizer harvests harder at the cost of wider tracking error; push λ_te up and the portfolio hugs the index more tightly.

We solve this as a portfolio-optimization problem each day, using CVXPY with the CLARABEL conic solver. The solver searches the feasible set defined by the constraints and returns the weight vector that minimises (or maximises) the objective — typically in tens of milliseconds for a 500-name universe.

What goes in, what comes out
Inputs
  • Holdings + lots

    Per-account lot-level positions with acquisition date, cost basis, and quantity.

  • Benchmark weights

    Target weights from the chosen index (S&P 100, Russell 1000, MSCI World, etc.).

  • Prices + risk model

    Daily prices and the factor risk model that drives the tracking-error metric.

  • Account constraints

    TE budget, single-name cap, exclusions, and any other per-account knobs.

Outputs
  • Trade list

    Lot-level buys and sells for the day, each identified by the lot it touches.

  • New weight vector

    Target weights w* after the solve — what the portfolio should hold tomorrow.

  • Realized P/L

    Per-lot realized gain/loss for the day, split into short-term and long-term buckets.

Customization · Coming soon

Factor tilt

A factor tilt lets the optimizer hold more of the names that score well on a chosen factor — quality, value, momentum, or low-volatility — and less of the names that score poorly. The portfolio still tracks the benchmark, but with a measurable lean toward the chosen factor.

How the optimizer applies it

B_f is the column of factor loadings for the chosen factor from the risk model. The constraint forces the portfolio's active exposure to that factor to be at least t_f standard deviations above the benchmark. The optimizer redistributes weight within the tracking-error budget to satisfy it — buying high-scoring names, underweighting low-scoring ones.

The trade-off

You consume part of your tracking-error budget on the tilt. Less budget remains for tax-loss harvesting, so factor tilts typically reduce expected harvest activity slightly. The factor's own active return is the offset.

Where you see it

The console's risk panel shows the current active factor exposure next to its target. Trade tickets annotate names whose factor score drove the buy or sell.

 Long/Short Tax-AwareMarket-Neutral Pair SleeveTax-Aware Direct Indexing
Net exposure100%0%100%
Factor exposureTrackedPinned to zeroTracked
Source of returnIndex + tax + activeCross-sectional alphaIndex + tax alpha
RoleStandalone bookCompanion sleeveStandalone book