From a0715b615c0db90bbfead68ac558703513037bbc Mon Sep 17 00:00:00 2001 From: Emil Lerch Date: Sat, 16 May 2026 14:37:30 -0700 Subject: [PATCH] document methodology differences between history and projections --- src/commands/history.zig | 9 +++++++++ src/commands/projections.zig | 13 +++++++++++++ src/tui/history_tab.zig | 4 ++-- src/tui/projections_tab.zig | 4 ++-- 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/commands/history.zig b/src/commands/history.zig index 8e50e88..f90e91a 100644 --- a/src/commands/history.zig +++ b/src/commands/history.zig @@ -360,6 +360,15 @@ pub fn renderPortfolio( fn renderWindowsBlock(out: *std.Io.Writer, color: bool, ws: timeline.WindowSet) !void { if (ws.rows.len == 0) return; + // Methodology note. The values in this block are + // snapshot-to-snapshot Liquid deltas (or whichever metric is + // focused) — they include contributions, withdrawals, and + // weight drift, distinct from the `projections` benchmark + // table which reports price-only weighted returns and so will + // disagree on weeks with significant cash movement or + // rebalancing. + try cli.printFg(out, color, cli.CLR_MUTED, " (snapshot-to-snapshot Δ; includes contributions, withdrawals, weight drift)\n", .{}); + // Header row: " Change Δ % % / yr" // Widths pinned to view.windows_*_width constants (12 / 18 / 10 / 10). // Hard-coded here for format-string brevity; changes to those diff --git a/src/commands/projections.zig b/src/commands/projections.zig index 979c547..f972430 100644 --- a/src/commands/projections.zig +++ b/src/commands/projections.zig @@ -266,6 +266,19 @@ pub fn run( } try out.print("\n", .{}); + // Section title. Includes a methodology note so a reader + // comparing these numbers against the `history` tab's window + // table doesn't get tripped up by the (legitimate) + // disagreement: this table reports each row as a weighted + // price-only return per period (per-symbol price change × + // current weight, summed) so SPY/AGG/Benchmark/Your + // Portfolio rows are apples-to-apples; history's window + // table reports snapshot-to-snapshot Liquid value deltas + // that include contributions, withdrawals, and weight drift. + try cli.setBold(out, color); + try out.print("Benchmark comparison (price-only weighted return)\n", .{}); + try cli.reset(out, color); + // Header row try cli.printFg(out, color, cli.CLR_MUTED, "{s: <32}{s: >8}{s: >9}{s: >9}{s: >10}{s: >9}\n", .{ "", "1 Year", "3 Year", "5 Year", "10 Year", "Week", diff --git a/src/tui/history_tab.zig b/src/tui/history_tab.zig index 34615a6..64ac48d 100644 --- a/src/tui/history_tab.zig +++ b/src/tui/history_tab.zig @@ -1373,7 +1373,7 @@ fn appendWindowsBlock( const today = points[points.len - 1].as_of_date; const ws = try timeline.computeWindowSet(arena, points, metric, today); - try lines.append(arena, .{ .text = " Change", .style = th.headerStyle() }); + try lines.append(arena, .{ .text = " Change (snapshot-to-snapshot Δ)", .style = th.headerStyle() }); const header_line = try std.fmt.allocPrint( arena, @@ -1741,7 +1741,7 @@ test "renderHistoryLines: renders windows + chart + table in correct order" { var chart_idx: ?usize = null; var table_idx: ?usize = null; for (lines, 0..) |l, i| { - if (std.mem.eql(u8, std.mem.trim(u8, l.text, " "), "Change")) windows_idx = i; + if (std.mem.indexOf(u8, l.text, "Change") != null) windows_idx = i; if (std.mem.indexOf(u8, l.text, "Chart: Liquid") != null) chart_idx = i; if (std.mem.indexOf(u8, l.text, "Recent snapshots") != null) table_idx = i; } diff --git a/src/tui/projections_tab.zig b/src/tui/projections_tab.zig index 101d1e7..d31d244 100644 --- a/src/tui/projections_tab.zig +++ b/src/tui/projections_tab.zig @@ -829,7 +829,7 @@ fn buildHeaderSection(state: *State, app: *App, arena: std.mem.Allocator, lines: const stock_pct = pctx.stock_pct; try lines.append(arena, .{ .text = "", .style = th.contentStyle() }); - try lines.append(arena, .{ .text = " Benchmark Comparison", .style = th.headerStyle() }); + try lines.append(arena, .{ .text = " Benchmark Comparison (price-only weighted return)", .style = th.headerStyle() }); try lines.append(arena, .{ .text = "", .style = th.contentStyle() }); // Column headers @@ -1222,7 +1222,7 @@ fn buildLines(state: *State, app: *App, arena: std.mem.Allocator) ![]const Style // Header try lines.append(arena, .{ - .text = " Benchmark Comparison", + .text = " Benchmark Comparison (price-only weighted return)", .style = th.headerStyle(), }); try lines.append(arena, .{ .text = "", .style = th.contentStyle() });