How we decompose portfolio active return.
Performance attribution answers the question every advisor fields in a quarterly review: where did the return come from? ALAN runs a Brinson-Hood-Beebower 3-effect decomposition across 8 sleeves and pairs it with a 6-factor diagnostic strip. The math is published in full so a CFA charterholder can replicate every number on a notepad.
The Brinson identity, written down once.
For each sleeve s with portfolio weight wp(s), benchmark weight wb(s), portfolio return rp(s), and benchmark return rb(s):
allocation(s) = (wp(s) - wb(s)) × (rb(s) - rb_total) selection(s) = wb(s) × (rp(s) - rb(s)) interaction(s) = (wp(s) - wb(s)) × (rp(s) - rb(s))
The three effects sum to the sleeve's total active contribution. Summing across sleeves gives the portfolio's active return:
Σ_s [allocation(s) + selection(s) + interaction(s)] ≡ rp_total - rb_total
The kernel asserts this identity to floating-point tolerance on every result and surfaces the gap as identityGap so any input corruption is visible in the API response.
The 8 sleeves.
Allocation analysis is only as good as the buckets it runs against. ALAN starts with 8 advisor-grade sleeves:
- US Equity: common stock domiciled in or primarily traded in US markets.
- International Equity: common stock from developed markets outside the US.
- Emerging-Markets Equity: common stock from emerging-market economies.
- US Bonds: Treasury, agency, municipal, and corporate fixed income issued in the US.
- International Bonds: sovereign and corporate fixed income issued outside the US.
- Real Assets: REITs, commodities, infrastructure, inflation-linked.
- Cash: money-market funds, T-bills, demand deposits.
- Alternatives: hedge funds, private equity, venture, other non-traditional vehicles.
The classifier maps tickers to sleeves using a small static table for common ETFs (VEA, EEM, AGG, TLT, VNQ, GLD, …) plus a GICS-1 sector lookup for individual equities. Anything we cannot classify falls back to Alternatives so it surfaces in the UI for advisor review rather than silently polluting another sleeve.
Per-advisor sleeve overrides are on the roadmap; the math layer accepts arbitrary sleeve identifiers today, so a custom taxonomy slots in without code changes.
Where each sleeve's benchmark return comes from.
A Brinson decomposition needs a benchmark return for every sleeve it touches. Each one is measured from stored daily closes for a named index proxy, over the same two snapshot dates the portfolio return is measured between:
- US Equity: S&P 500 (SPY).
- International Equity: developed markets ex-US (EFA).
- Emerging-Markets Equity: emerging markets (EEM).
- US Bonds: US aggregate bond (AGG).
- Real Assets: equal-weight US REITs (VNQ) and broad commodities (PDBC).
- Cash: par, no accrual. Cash positions are carried at par in the position snapshot, so the sleeve nets to zero on both sides by construction.
- International Bonds and Alternatives: no series is wired. These sleeves are excluded from the decomposition and named in the report, rather than filled in.
These are price returns from daily closes; distributions are not reinvested, which understates the bond and real-asset sleeves by their income yield over the period.
When a sleeve is excluded, both the portfolio and benchmark weight vectors are renormalised over the sleeves that remain so the BHB identity still holds, and the report publishes the share of benchmark weight and portfolio value the decomposition covers. The portfolio's true return across every holding is reported separately, so an excluded sleeve never silently disappears from performance.
What is deliberately notdone: deriving one sleeve's return from another's. A fixed multiple of the US equity index is not a measurement of international equities, emerging markets, real assets, or alternatives, and a report that shows one as though it were is wrong in a way the reader cannot see. Fixed weights across several measured series — the real-assets basket above — are ordinary index construction and are disclosed with the figure.
The factor model.
On top of Brinson we run a regression of the portfolio's daily excess return on the Fama-French 5 factors plus Momentum:
(rp - rf) = α + β_mkt·MKT_RF + β_smb·SMB + β_hml·HML
+ β_rmw·RMW + β_cma·CMA + β_mom·MOM + εThe factor loadings (β) tell you which systematic exposures the portfolio is taking: how cheap, how big, how high- quality, how momentum-tilted, how interest-rate-sensitive. Multiplying each loading by the realized factor return over the analysis period gives the percentage points of active return attributable to that factor. The residual is selection alpha.
Factor returns come from the Kenneth French data library at Dartmouth Tuck, ingested directly with no third-party aggregator on the path. The training window is rolling-3y of daily observations (504-day floor); regressions with fewer joined observations are skipped.
Inputs and limits.
Two inputs determine output quality:
- Snapshots. The advisor performance pipeline writes one
AdvisorClientPositionSnapshotper client per UTC day at 02:00 via the lazy-cron. Attribution over a window needs at least two snapshots; the API returns statusmissing_snapshotswhen a window is too thin. - Sector tags. Sleeve classification falls back to
us_equityfor tickers without a GICS-1 sector tag. The admin Attribution Sleeve Coverage panel surfaces unmapped tickers ranked by position count.
Currency residual is currently rolled into selection at the sleeve level. A separate currency bar appears in the cumulative-active waterfall when multi-currency snapshots ship.
What the numbers mean, and what they don't.
Attribution is a backward-looking decomposition, not an outperformance forecast. Three things to keep in mind when reading a result:
- Allocation is path-independent. A sleeve that ended the period at the same weight it started will still register an allocation effect if its weight drifted meaningfully mid-period.
- Interaction is real, not noise. When the portfolio over-weights a sleeve and picks better securities within it, the joint contribution is captured in interaction rather than double-counted.
- Factor loadings have estimation error. A single quarter is not enough to refit; the t-stats on loadings come from the rolling-3y regression and decay quickly when sample observations are thin.
Citations.
- Brinson, G. P., Hood, L. R., & Beebower, G. L. (1986). “Determinants of Portfolio Performance.” Financial Analysts Journal, 42(4), 39–44.
- Fama, E. F., & French, K. R. (1993). “Common risk factors in the returns on stocks and bonds.” Journal of Financial Economics, 33(1), 3–56.
- Fama, E. F., & French, K. R. (2015). “A five-factor asset pricing model.” Journal of Financial Economics, 116(1), 1–22.
- Carhart, M. M. (1997). “On Persistence in Mutual Fund Performance.” Journal of Finance, 52(1), 57–82.
- Kenneth French data library, daily factor returns: Dartmouth Tuck research data.