Skip to content

Learn

Stock Portfolio Research

Attribute multi-period stock portfolio excess returns

Multi-period excess return is not generally the sum of each period’s active contribution. Portfolio and benchmark wealth compound separately, so a reconciliation needs an explicit linking method and visible costs or interaction terms. Start with matched beginning weights and total returns, then show how the period effects reproduce the final relative result.

This guide uses two invented stock periods to derive an exact arithmetic reconciliation. It is an external analysis, not verified CoTrading attribution functionality. Market and currency are common, external flows are excluded and the example does not repeat TWR/MWR calculations or introduce an FX layer.

Define the relative-return question

Specify whether excess means portfolio cumulative return minus benchmark cumulative return, or the ratio of their growth factors minus one. They are different quantities. This example uses the arithmetic difference between the two compounded returns and reports contributions in percentage points.

Use the same security identities, periods and total-return convention, including any distributions. Both paths are net of their explicitly modeled costs: portfolio charges are specified below, while benchmark cost is assumed zero. This is a declared theoretical comparison, not a claim that an actual benchmark investment is free to trade.

Reconcile external cash movements separately before attribution. A contribution is not an investment return or a security effect. The fixture rejects nonzero external flows under its contract; a real account with flows needs an appropriate prepared return series before entering this calculation.

Obtain beginning weights and matched returns

For each period, save portfolio and benchmark starting weights, security total returns, cost fractions and classification mapping. Do not use ending weights to explain the period that produced them. Changes in weight during a period require a more detailed timing model than one beginning-weight multiplication.

Validate every weight and cost as finite before checking the budget. The Python Decimal documentation describes finite-number checks and decimal arithmetic used here. NaN must not slip through a sum-to-one comparison, and negative or nonfinite modeled costs are rejected.

The weights in each path sum to one, with no separate cash exposure in the main fixture. The example uses two fully observed securities. It is not an automatic mapper for real index membership, corporate actions or missing securities; those input relationships must be established first.

Calculate one-period effects

In period one, portfolio weights are A 0.6 and B 0.4, benchmark weights 0.5 each. A returns 0.10 and B zero. Portfolio cost is 0.001 of beginning capital, giving net portfolio return 0.059; benchmark return is 0.05.

Security active contributions are (portfolio_weight - benchmark_weight) * security_return. A contributes 0.01, B zero and fees −0.001, summing to active return 0.009. Keep fees as a named effect rather than reducing one stock’s contribution to conceal them.

Period two portfolio weights are A 0.3 and B 0.7, benchmark weights remain equal. A returns −0.05, B 0.04 and portfolio cost is 0.002. Portfolio net return is 0.011 and benchmark return −0.005. Effects are A +0.01, B +0.008 and fees −0.002, totaling 0.016.

EffectPeriod onePeriod two
A+1.0 pp+1.0 pp
B0 pp+0.8 pp
Fees−0.1 pp−0.2 pp
Active return+0.9 pp+1.6 pp

This is a direct security-level exposure calculation, not a claimed allocation/selection model with independently estimated causal effects. It explains the declared realized inputs under their beginning-weight assumptions.

Reconcile the compounding interaction

Portfolio cumulative return is 1.059*1.011 - 1 = 7.0649%. Benchmark cumulative return is 1.05*0.995 - 1 = 4.475%. Their arithmetic difference is 2.5899 percentage points, whereas the unlinked period active returns sum to 2.5 points.

The difference is 0.0899 points. For two periods it equals Rp1*Rp2 - Rb1*Rb2, the difference between the two compounding cross-products. It is a temporal interaction, not unexplained stock selection and not permission to add a fictitious winning trade.

The CFA Society Netherlands attribution framework discusses multiperiod linking and residuals. Our explicit identity below is an original transparent choice; it does not claim to implement a named smoothing algorithm or certify all attribution conventions.

Apply an exact ordered linking identity

For period t, multiply each effect by the portfolio growth factors before t and benchmark growth factors after t. Summing those linked effects telescopes to the difference of the two total growth factors. In two periods, the multipliers are 0.995 for period one and 1.059 for period two.

multiplier[t] = product(1 + r for r in portfolio_returns[:t]) \
              * product(1 + r for r in benchmark_returns[t+1:])
linked_effect[name] = sum(effect[t][name] * multiplier[t]
                          for t in periods)

The linked results are A 2.054000 points, B 0.847200 points and fees −0.311300 points. They sum exactly to 2.589900 points in the decimal fixture. The cost effect is linked too; adding raw fees to linked stock effects would break reconciliation.

This linking allocation is order-dependent in how it distributes time interaction among effects, even though the total identity is exact. State the method instead of presenting linked security numbers as uniquely determined economic truth. Other declared linking schemes may distribute effects differently.

Show one of two complete presentations

One valid presentation lists raw A 2.0 points, raw B 0.8, raw fees −0.3 and temporal interaction +0.0899. Another lists the three linked effects above with no remaining temporal residual. Both reconcile the same excess return.

Do not add the interaction again to already linked totals, because linking has allocated it into those numbers. Keep the raw-to-linked adjustment table so the reader can see where the 0.0899 went. A residual should be explained or retained, not silently spread across favored holdings after inspection.

If costs, dividends or unavailable data remain unclassified, create explicit buckets. The separate classification test maps an unmapped security’s 0.003 effect into Unknown and preserves the 0.023 total. Dropping it would make the apparent explained amount smaller without acknowledging missing coverage.

Test numerical and scope boundaries

The script rejects NaN and infinity in weights before normalization. It also rejects nonpositive portfolio or benchmark growth factors under this positive-wealth linking contract. It does not manufacture a percentage attribution for an unsupported destroyed-account continuation.

A nonzero external-flow test is blocked rather than treated as return. Missing security identities or mismatched periods likewise need resolution before applying the formula. A correct identity on incorrectly aligned data is not a reliable performance explanation.

The example assumes one holding return per period with known beginning exposure. Frequent trades, changing cash and distributions may require finer subperiods or an independently reconciled contribution ledger. Do not claim that one matrix multiplication captures every real execution path.

Execute and accept the attribution

Download the complete example. Use Python 3.12 or later and run python R632.py. Standard-library Decimal assertions verify both period bridges, cumulative returns, excess 2.5899 points, interaction 0.0899 points, exact linked stock/fee effects, unknown classification and nonfinite-input rejection.

Archive return definitions, beginning weights, costs, periods, effect tables, multipliers and remaining residuals. A reviewer should reconstruct both total returns and the relative result without choosing hidden adjustments. Preserve model limitations beside the reconciliation.

Keep the original security-level table before grouping. A sector summary can otherwise hide an omitted security, a duplicated mapping or a reclassification made after the period. Require grouped effects plus an explicit Unknown bucket to equal the security-level total. Also store the mapping’s effective date: a current industry label need not describe the historical portfolio. These checks concern the completeness of explanation, not whether an unexplained amount is economically harmless. A small residual should still retain its origin and units instead of being erased by display rounding.

Record who approved classification corrections.

Recalculate when weights, benchmark, costs, return treatment, classification or period boundaries change. This synthetic analysis demonstrates explicit multiperiod arithmetic only; it does not prove causal investment skill, actual historical outperformance or product support.

FAQ

Can period active returns simply be added?

Not to reproduce the difference between compounded total returns without accounting for temporal interaction.

Should fees disappear into stock contributions?

No. Keep their definition and linking treatment explicit.