document methodology differences between history and projections
This commit is contained in:
parent
1f71b1c6f3
commit
3b70275845
4 changed files with 26 additions and 4 deletions
|
|
@ -360,6 +360,15 @@ pub fn renderPortfolio(
|
||||||
fn renderWindowsBlock(out: *std.Io.Writer, color: bool, ws: timeline.WindowSet) !void {
|
fn renderWindowsBlock(out: *std.Io.Writer, color: bool, ws: timeline.WindowSet) !void {
|
||||||
if (ws.rows.len == 0) return;
|
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"
|
// Header row: " Change Δ % % / yr"
|
||||||
// Widths pinned to view.windows_*_width constants (12 / 18 / 10 / 10).
|
// Widths pinned to view.windows_*_width constants (12 / 18 / 10 / 10).
|
||||||
// Hard-coded here for format-string brevity; changes to those
|
// Hard-coded here for format-string brevity; changes to those
|
||||||
|
|
|
||||||
|
|
@ -266,6 +266,19 @@ pub fn run(
|
||||||
}
|
}
|
||||||
try out.print("\n", .{});
|
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
|
// Header row
|
||||||
try cli.printFg(out, color, cli.CLR_MUTED, "{s: <32}{s: >8}{s: >9}{s: >9}{s: >10}{s: >9}\n", .{
|
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",
|
"", "1 Year", "3 Year", "5 Year", "10 Year", "Week",
|
||||||
|
|
|
||||||
|
|
@ -1373,7 +1373,7 @@ fn appendWindowsBlock(
|
||||||
const today = points[points.len - 1].as_of_date;
|
const today = points[points.len - 1].as_of_date;
|
||||||
const ws = try timeline.computeWindowSet(arena, points, metric, today);
|
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(
|
const header_line = try std.fmt.allocPrint(
|
||||||
arena,
|
arena,
|
||||||
|
|
@ -1741,7 +1741,7 @@ test "renderHistoryLines: renders windows + chart + table in correct order" {
|
||||||
var chart_idx: ?usize = null;
|
var chart_idx: ?usize = null;
|
||||||
var table_idx: ?usize = null;
|
var table_idx: ?usize = null;
|
||||||
for (lines, 0..) |l, i| {
|
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, "Chart: Liquid") != null) chart_idx = i;
|
||||||
if (std.mem.indexOf(u8, l.text, "Recent snapshots") != null) table_idx = i;
|
if (std.mem.indexOf(u8, l.text, "Recent snapshots") != null) table_idx = i;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -829,7 +829,7 @@ fn buildHeaderSection(state: *State, app: *App, arena: std.mem.Allocator, lines:
|
||||||
const stock_pct = pctx.stock_pct;
|
const stock_pct = pctx.stock_pct;
|
||||||
|
|
||||||
try lines.append(arena, .{ .text = "", .style = th.contentStyle() });
|
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() });
|
try lines.append(arena, .{ .text = "", .style = th.contentStyle() });
|
||||||
|
|
||||||
// Column headers
|
// Column headers
|
||||||
|
|
@ -1222,7 +1222,7 @@ fn buildLines(state: *State, app: *App, arena: std.mem.Allocator) ![]const Style
|
||||||
|
|
||||||
// Header
|
// Header
|
||||||
try lines.append(arena, .{
|
try lines.append(arena, .{
|
||||||
.text = " Benchmark Comparison",
|
.text = " Benchmark Comparison (price-only weighted return)",
|
||||||
.style = th.headerStyle(),
|
.style = th.headerStyle(),
|
||||||
});
|
});
|
||||||
try lines.append(arena, .{ .text = "", .style = th.contentStyle() });
|
try lines.append(arena, .{ .text = "", .style = th.contentStyle() });
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue