Skip to content

TSR & Attribution

Total shareholder return is the rate of return a shareholder earns on a position over a window. EqtyTrk computes both raw TSR and a five-bar drag waterfall that decomposes TSR into its driving components.

Total Shareholder Return

TSR=PT+iDiP0P0

Where P0 and PT are start and end prices (split-adjusted), and Di is each dividend-per-share paid in (t0,tT].

EqtyTrk's /v1/companies/{ticker}/tsr endpoint computes the dividend-reinvested variant: each dividend buys additional shares at the ex-date close. Final value = (1 + accumulated shares) × PT.

TSR drag waterfall

The waterfall decomposes TSR into five additive components on a per-original-shareholder basis:

PTP0=RRevenueNMMarginPEMultiple1/D1 / Dilution

Where R=RevT/Rev0, NM=(NI/Rev)T/(NI/Rev)0, PE=(MC/NI)T/(MC/NI)0, D=SharesT/Shares0.

Multiplying by the original-holder's starting position (Shares0) gives the dollar value of capital appreciation. Dividends layer on top.

How the chain decomposes

The waterfall is path-dependent. Each bar is the incremental delta from applying that one factor on top of the prior factors:

MCafter rev=MC0RMCafter margin=MCafter revNMMCafter multiple=MCafter marginPEMCafter shares=MCafter multiple1/D

Component contributions (signed dollar amounts):

rev contrib=MCafter revMC0margin contrib=MCafter marginMCafter revmultiple contrib=MCafter multipleMCafter margindilution contrib=MCafter sharesMCafter multipledividend contrib=DPStotal×Shares0

The dilution bar is positive for buybacks (which transfer value to holders that don't sell), negative for net share issuance.

Per-original-shareholder framing

This is the load-bearing convention: the waterfall describes value gained by an original shareholder, not by current shareholders.

Why: when a company buys back shares, it transfers value to the holders who don't sell. Their stake is worth more even though the corporate market cap may have grown by less. The two framings give different totals:

FramingTotal value created
Current-shareholder (corporate)MCTMC0+Dividends (using ending shares)
Per-original-shareholderShares0×(PTP0+DPStotal)

For AAPL FY20→FY25 the two framings diverge by ~$485B — roughly the value transferred to non-selling holders through buybacks.

EqtyTrk uses per-original-shareholder framing throughout the attribution view. It matches the actual experience of an investor who held through the period.

Worked example: AAPL FY20 → FY25

EndpointValue
Start (2020-09-26)P0$112, Shares017.0B, MC0$1.90T
End (2025-09-27)PT$250, SharesT15.1B, MCT$3.77T
Revenue$274B$391B (R=1.43)
Net margin20.9%24.0% (NMT/NM0=1.148)
P/E33.2×40.2× (PET/PE0=1.211)
Shares17.0B15.1B (D=0.888, 1/D=1.126)
DPS total (5y)$5.50

Running the multiplicative chain:

StepRunning MCContrib
Start$1.90T
After revenue$2.72T+$813B
After margin$3.12T+$401B
After multiple$3.77T+$657B
After buybacks$4.25T+$475B
Dividends+$94B
Total+$2.44T, or +128%

Note that "After shares" ($4.25T) exceeds the corporate market cap ($3.77T) because the original holder still owns 17.0B shares at $250 — they weren't diluted by buybacks.

How to read it

  • Revenue + margin > multiple change signals operations-driven appreciation. Healthy. In this example revenue ($813B) and margin ($401B) together account for roughly half the gain.
  • Multiple change > revenue + margin signals re-rating. Sustainability depends on whether the new multiple is justified by forward growth.
  • Negative dilution (net issuance) is value destruction unless paired with a productive use of proceeds (acquisition that contributed to margin or revenue).
  • Positive dilution (buybacks) is value transfer to holders who didn't sell. Whether it's a good use of cash depends on whether buybacks were funded from earnings or debt, and whether the shares were bought below intrinsic value.

Limitations

  • DPS approximation. The dividend contrib uses DPStotal×Shares0 rather than tracking per-payment shares. Slight overcount when shares were lower at the time of earlier dividends; slight undercount the other way.
  • No reinvestment. The waterfall assumes the original shareholder pocketed dividends rather than reinvested. For comparison with reinvested-TSR, the reinvested number is typically a few percentage points higher.
  • Path-dependent. Reordering the chain (margin before revenue, etc.) shifts each component's contribution while preserving the total. The convention is revenue → margin → multiple → shares; swapping them changes individual bars.
  • Multi-period DPS. Treats all dividends as paid against Shares0. Real-world dividend yields evolve.
  • Insufficient history. When a company is younger than the requested window, or revenue is zero / non-positive at the start endpoint, the decomposition isn't computable. The UI surfaces an empty state with an explanation.
  • Degenerate margins. When net income is non-positive at either endpoint, the margin + multiple math is degenerate. The waterfall collapses to a single "operations" bar plus the dilution and dividend bars. A note surfaces in the UI when this happens.

Implementation notes

  • compute_tsr_drag() in src/eqtytrk/attribution/tsr_drag.py
  • Frontend rendering: frontend/src/components/AttributionView.tsx
  • Picks the FY-end nearest to (today − N years) using a ±90 day window. Returns None when historical price data or financials are missing.
  • Falls back to a collapsed "operations" bar when net income is non-positive at either endpoint (margin/multiple math is degenerate).
  • Price lookup walks back up to 14 calendar days from the FY end to handle weekends and holidays (_price_on_or_before).

References

EqtyTrk methodology reference. Data from SEC EDGAR.