fixed income vs equity on analysis
This commit is contained in:
parent
6428537bb7
commit
bbdc46702f
2 changed files with 51 additions and 6 deletions
|
|
@ -70,10 +70,19 @@ pub fn run(allocator: std.mem.Allocator, svc: *zfin.DataService, file_path: []co
|
||||||
};
|
};
|
||||||
defer result.deinit(allocator);
|
defer result.deinit(allocator);
|
||||||
|
|
||||||
try display(result, file_path, color, out);
|
const benchmark = @import("../analytics/benchmark.zig");
|
||||||
|
const split = benchmark.deriveAllocationSplit(
|
||||||
|
pf_data.summary.allocations,
|
||||||
|
cm.entries,
|
||||||
|
pf_data.summary.total_value,
|
||||||
|
portfolio.totalCash(),
|
||||||
|
portfolio.totalCdFaceValue(),
|
||||||
|
);
|
||||||
|
|
||||||
|
try display(result, split.stock_pct, split.bond_pct, file_path, color, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn display(result: zfin.analysis.AnalysisResult, file_path: []const u8, color: bool, out: *std.Io.Writer) !void {
|
pub fn display(result: zfin.analysis.AnalysisResult, stock_pct: f64, bond_pct: f64, file_path: []const u8, color: bool, out: *std.Io.Writer) !void {
|
||||||
const label_width = fmt.analysis_label_width;
|
const label_width = fmt.analysis_label_width;
|
||||||
const bar_width = fmt.analysis_bar_width;
|
const bar_width = fmt.analysis_bar_width;
|
||||||
|
|
||||||
|
|
@ -82,6 +91,13 @@ pub fn display(result: zfin.analysis.AnalysisResult, file_path: []const u8, colo
|
||||||
try cli.reset(out, color);
|
try cli.reset(out, color);
|
||||||
try out.print("========================================\n\n", .{});
|
try out.print("========================================\n\n", .{});
|
||||||
|
|
||||||
|
// Equities vs Fixed Income summary
|
||||||
|
{
|
||||||
|
try cli.setFg(out, color, cli.CLR_MUTED);
|
||||||
|
try out.print(" Equities {d:.1}% / Fixed Income {d:.1}%\n\n", .{ stock_pct * 100, bond_pct * 100 });
|
||||||
|
try cli.reset(out, color);
|
||||||
|
}
|
||||||
|
|
||||||
const sections = [_]struct { items: []const zfin.analysis.BreakdownItem, title: []const u8 }{
|
const sections = [_]struct { items: []const zfin.analysis.BreakdownItem, title: []const u8 }{
|
||||||
.{ .items = result.asset_class, .title = " Asset Class" },
|
.{ .items = result.asset_class, .title = " Asset Class" },
|
||||||
.{ .items = result.sector, .title = " Sector (Equities)" },
|
.{ .items = result.sector, .title = " Sector (Equities)" },
|
||||||
|
|
@ -221,7 +237,7 @@ test "display shows all sections" {
|
||||||
.unclassified = @constCast(&unclassified),
|
.unclassified = @constCast(&unclassified),
|
||||||
.total_value = 100000.0,
|
.total_value = 100000.0,
|
||||||
};
|
};
|
||||||
try display(result, "test.srf", false, &w);
|
try display(result, 0.80, 0.20, "test.srf", false, &w);
|
||||||
const out = w.buffered();
|
const out = w.buffered();
|
||||||
try std.testing.expect(std.mem.indexOf(u8, out, "Portfolio Analysis") != null);
|
try std.testing.expect(std.mem.indexOf(u8, out, "Portfolio Analysis") != null);
|
||||||
try std.testing.expect(std.mem.indexOf(u8, out, "Asset Class") != null);
|
try std.testing.expect(std.mem.indexOf(u8, out, "Asset Class") != null);
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,25 @@ fn loadDataFinish(app: *App, pf: zfin.Portfolio, summary: zfin.valuation.Portfol
|
||||||
// ── Rendering ─────────────────────────────────────────────────
|
// ── Rendering ─────────────────────────────────────────────────
|
||||||
|
|
||||||
pub fn buildStyledLines(app: *App, arena: std.mem.Allocator) ![]const StyledLine {
|
pub fn buildStyledLines(app: *App, arena: std.mem.Allocator) ![]const StyledLine {
|
||||||
return renderAnalysisLines(arena, app.theme, app.analysis_result);
|
// Compute equity/fixed split from classification + portfolio
|
||||||
|
var stock_pct: f64 = 0;
|
||||||
|
var bond_pct: f64 = 0;
|
||||||
|
if (app.portfolio_summary) |summary| {
|
||||||
|
if (app.portfolio) |pf| {
|
||||||
|
const benchmark = @import("../analytics/benchmark.zig");
|
||||||
|
const cm_entries = if (app.classification_map) |cm| cm.entries else &.{};
|
||||||
|
const split = benchmark.deriveAllocationSplit(
|
||||||
|
summary.allocations,
|
||||||
|
cm_entries,
|
||||||
|
summary.total_value,
|
||||||
|
pf.totalCash(),
|
||||||
|
pf.totalCdFaceValue(),
|
||||||
|
);
|
||||||
|
stock_pct = split.stock_pct;
|
||||||
|
bond_pct = split.bond_pct;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return renderAnalysisLines(arena, app.theme, app.analysis_result, stock_pct, bond_pct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Render analysis tab content. Pure function — no App dependency.
|
/// Render analysis tab content. Pure function — no App dependency.
|
||||||
|
|
@ -79,6 +97,8 @@ pub fn renderAnalysisLines(
|
||||||
arena: std.mem.Allocator,
|
arena: std.mem.Allocator,
|
||||||
th: theme.Theme,
|
th: theme.Theme,
|
||||||
analysis_result: ?zfin.analysis.AnalysisResult,
|
analysis_result: ?zfin.analysis.AnalysisResult,
|
||||||
|
stock_pct: f64,
|
||||||
|
bond_pct: f64,
|
||||||
) ![]const StyledLine {
|
) ![]const StyledLine {
|
||||||
var lines: std.ArrayList(StyledLine) = .empty;
|
var lines: std.ArrayList(StyledLine) = .empty;
|
||||||
|
|
||||||
|
|
@ -92,6 +112,15 @@ pub fn renderAnalysisLines(
|
||||||
return lines.toOwnedSlice(arena);
|
return lines.toOwnedSlice(arena);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Equities vs Fixed Income summary
|
||||||
|
if (stock_pct > 0 or bond_pct > 0) {
|
||||||
|
try lines.append(arena, .{
|
||||||
|
.text = try std.fmt.allocPrint(arena, " Equities {d:.1}% / Fixed Income {d:.1}%", .{ stock_pct * 100, bond_pct * 100 }),
|
||||||
|
.style = th.mutedStyle(),
|
||||||
|
});
|
||||||
|
try lines.append(arena, .{ .text = "", .style = th.contentStyle() });
|
||||||
|
}
|
||||||
|
|
||||||
const bar_width: usize = 30;
|
const bar_width: usize = 30;
|
||||||
const label_width: usize = 24;
|
const label_width: usize = 24;
|
||||||
|
|
||||||
|
|
@ -214,7 +243,7 @@ test "renderAnalysisLines with data" {
|
||||||
.unclassified = &.{},
|
.unclassified = &.{},
|
||||||
.total_value = 200000,
|
.total_value = 200000,
|
||||||
};
|
};
|
||||||
const lines = try renderAnalysisLines(arena, th, result);
|
const lines = try renderAnalysisLines(arena, th, result, 0.80, 0.20);
|
||||||
// Should have header section + asset class items
|
// Should have header section + asset class items
|
||||||
try testing.expect(lines.len >= 5);
|
try testing.expect(lines.len >= 5);
|
||||||
// Find "Portfolio Analysis" header
|
// Find "Portfolio Analysis" header
|
||||||
|
|
@ -237,7 +266,7 @@ test "renderAnalysisLines no data" {
|
||||||
const arena = arena_state.allocator();
|
const arena = arena_state.allocator();
|
||||||
const th = theme.default_theme;
|
const th = theme.default_theme;
|
||||||
|
|
||||||
const lines = try renderAnalysisLines(arena, th, null);
|
const lines = try renderAnalysisLines(arena, th, null, 0, 0);
|
||||||
try testing.expectEqual(@as(usize, 5), lines.len);
|
try testing.expectEqual(@as(usize, 5), lines.len);
|
||||||
try testing.expect(std.mem.indexOf(u8, lines[3].text, "No analysis data") != null);
|
try testing.expect(std.mem.indexOf(u8, lines[3].text, "No analysis data") != null);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue