From 027f3b67ab2e7626c67870677cb9a4a5cca05be3 Mon Sep 17 00:00:00 2001 From: Emil Lerch Date: Mon, 29 Jun 2026 08:33:40 -0700 Subject: [PATCH] add docs relating to comprehension of dividend-focused holdings vs other --- TODO.md | 52 +-------------------- docs/explanation/returns-and-performance.md | 16 +++++++ docs/guides/classify-holdings.md | 7 +++ docs/guides/read-your-portfolio.md | 22 +++++++++ src/analytics/analysis.zig | 10 ++-- 5 files changed, 52 insertions(+), 55 deletions(-) diff --git a/TODO.md b/TODO.md index 2985438..7bb4bc8 100644 --- a/TODO.md +++ b/TODO.md @@ -143,57 +143,7 @@ bigger than the REST quote path (persistent connection, reconnect handling, a background task feeding the TUI) and squarely a paid-plan feature. Sequence it after the REST quote path proves out. -## Analysis: dividend equity / income-shaped equity - think about it - -Dividend-equity ETFs (SCHD, VYM, DGRO, NOBL, SDY, VIG, etc.) -bucket as Equity in `analysis.bucketSector`. That's correct for -risk-exposure analysis - they drop with the market in a -2008-style crash, regardless of the dividend stream - but it -loses the income-vs-growth distinction that retirement-planning -tools care about. - -Open question: is there a useful second dimension to add? -Possibilities: - -- **Yield-weighted breakdown.** Aggregate `current_yield` per - position, weight by market value, report a portfolio-level - yield. Doesn't change the asset-class taxonomy; adds a new - metric. -- **Income coverage of expenses.** "My dividends + bond coupons - cover X% of projected retirement spending." Closer to what the - income-side framing actually wants - answers the question - rather than redefining the buckets. -- **Income-equity sub-bucket within Equity.** A sub-row in the - Asset Category breakdown, not a 5th top-level bucket. Would - need a way to mark funds as "income-shaped" - probably a - per-symbol opt-in in `metadata.srf`. - -Not a bug. Not blocking anything. Could end up being a feature. -This is a note to revisit after using the 4-bucket view for a -while and seeing whether the missing dimension actually matters -in practice. - -Resist the temptation to: - -- **Add a 5th top-level bucket** ("Income Equity" / "Dividend - Equity"). The 4-bucket view is already the right answer for - "how much equity exposure do I have?". A 5th bucket - fragments the headline number. -- **Override SCHD to Fixed Income.** Wrong on risk grounds. - SCHD will lose 35-45% in an equity crash; treating it as FI - makes the user think they have downside protection they don't. -- **Add per-symbol "intent" metadata** (`held_for_income::true`). - Smell of putting framing into data. Intent is a property of - the holder's strategy, not the security. - -If a fix lands, it's probably a separate analysis section (yield -breakdown, income coverage) - not a change to the asset-class -taxonomy. - -The following items are acknowledged but not prioritized. Listed here -so they don't get lost; pick up opportunistically. - -### Infra / performance +## Infra / performance - **HTTP connection pooling.** Parallel server sync in `loadAllPrices` spawns up to 8 threads, each with its own HTTP connection. Could diff --git a/docs/explanation/returns-and-performance.md b/docs/explanation/returns-and-performance.md index 76f3a3d..937af9e 100644 --- a/docs/explanation/returns-and-performance.md +++ b/docs/explanation/returns-and-performance.md @@ -101,6 +101,22 @@ zfin reports Sharpe at **3Y and 10Y** rather than 1Y. When the two disagree, lean on the longer window to judge a holding's risk-adjusted quality. +### Risk character vs. asset class + +A dividend or low-volatility equity fund -- SCHD, VYM, USMV -- is still +equity: it falls with the market in a crash, so +[`analysis`](../reference/cli/analysis.md) buckets it under Equity, the +view that answers "how exposed am I to an equity drawdown?" But two +holdings of the *same* asset class can ride very differently, and that +difference lives in the volatility and max-drawdown columns of +[`review`](../reference/cli/review.md), not in the asset-class label. In +the [`pre-retirement-both`](../../examples/pre-retirement-both/) example, +SCHD and QQQ are both `US Large Cap`, yet SCHD's 5-year max drawdown runs +about half QQQ's at a few points lower volatility -- QQQ pays for the +bumpier ride with a higher return and Sharpe. Asset class answers *crash +exposure*; the risk columns answer *how bumpy the ride is*. They are +different questions, and zfin keeps them in different views on purpose. + ## Portfolio-level returns The portfolio summary's **Historical** line and the diff --git a/docs/guides/classify-holdings.md b/docs/guides/classify-holdings.md index a763614..662113f 100644 --- a/docs/guides/classify-holdings.md +++ b/docs/guides/classify-holdings.md @@ -95,6 +95,13 @@ symbol::VTI,sector::Diversified,geo::US,asset_class::US Large Cap,bucket::US Tot symbol::SCHD,sector::Diversified,geo::US,asset_class::US Large Cap,bucket::US Dividend ``` +Grouping like this also pays off in +[`zfin review`](read-your-portfolio.md#spotting-a-defensive-sleeve): a +`US Dividend` bucket typically shows lower volatility and a shallower +max drawdown than a broad-market or growth bucket, even when both are +the same asset class. Sorting `review` by sector lets you read each +sleeve's risk character at a glance. + ## Verify ```bash diff --git a/docs/guides/read-your-portfolio.md b/docs/guides/read-your-portfolio.md index 459bad4..9255511 100644 --- a/docs/guides/read-your-portfolio.md +++ b/docs/guides/read-your-portfolio.md @@ -98,6 +98,12 @@ The Sector and Asset Category axes need [`metadata.srf`](classify-holdings.md); the Tax Type axis needs [`accounts.srf`](set-up-accounts.md). Anything missing classification lands under "Unclassified" / "Unknown." +One thing the Asset Category axis deliberately does *not* capture is +**risk character**. SCHD and QQQ here both land in Equity (`US Large +Cap`), yet one rides far calmer than the other -- that distinction lives +in [`review`](#review-per-holding-performance-and-risk) below, not in +the allocation breakdown. + ### Umbrella exposure A personal **umbrella insurance** policy covers liability -- lawsuits @@ -164,6 +170,22 @@ with the rest of your data. The CLI honors them -- `zfin review --show-acked` includes acked findings in the table -- but only the TUI can add or remove them. +### Spotting a defensive sleeve + +Asset class tells you what falls in a downturn; the **Vol** and +**MaxDD** columns tell you how hard. The two come apart *within* a +single asset class. Here SCHD and QQQ are both `US Large Cap`, yet +`zfin review` shows SCHD riding far calmer -- its 5-year max drawdown is +roughly half QQQ's (about 16% vs. 33%), at a few points lower +volatility. QQQ earns more for the bumpier ride (higher return and +Sharpe); SCHD trades return for stability. That income/defensive +character is *measured* from each fund's own price history -- you don't +tag a fund with it, the risk columns reveal it. When several holdings +share that profile, give them a common +[`bucket::`](classify-holdings.md#fixing-uninformative-sectors) label +and sort by sector (`--sort sector`, the default) so they group +together and you can read the sleeve's risk character as a whole. + ## `exposure`: look-through to a single symbol ```bash diff --git a/src/analytics/analysis.zig b/src/analytics/analysis.zig index 2d5a522..cf93ce0 100644 --- a/src/analytics/analysis.zig +++ b/src/analytics/analysis.zig @@ -470,10 +470,12 @@ pub fn bucketSector(sector: []const u8) []const u8 { // bond-like income shape. The Asset Category breakdown // answers "what's exposed to equity drawdowns?" - and // dividend funds drop with the market in a 2008-style - // crash. The income-feels-like-bonds intuition belongs in - // a separate yield-weighted analysis (see TODO.md - // "Dividend equity / income-shaped equity"), not in the - // asset-class taxonomy. + // crash. Their calmer risk character (lower volatility, + // shallower drawdown) is real but shows up in the + // per-holding vol/drawdown columns of `review`, not in + // the asset-class taxonomy. See + // docs/explanation/returns-and-performance.md, "Risk + // character vs. asset class". if (std.mem.startsWith(u8, sector, "Equity")) return bucket_equity; if (std.mem.startsWith(u8, sector, "Debt")) return bucket_fixed_income; if (std.mem.startsWith(u8, sector, "Loan")) return bucket_fixed_income;