clean up analysis

This commit is contained in:
Emil Lerch 2026-03-20 08:40:04 -07:00
parent b718c1ae39
commit ce72985054
Signed by: lobo
GPG key ID: A7B62D657EF764F8
2 changed files with 28 additions and 57 deletions

View file

@ -4,9 +4,7 @@ const cli = @import("common.zig");
const fmt = cli.fmt; const fmt = cli.fmt;
/// CLI `analysis` command: show portfolio analysis breakdowns. /// CLI `analysis` command: show portfolio analysis breakdowns.
pub fn run(allocator: std.mem.Allocator, config: zfin.Config, svc: *zfin.DataService, file_path: []const u8, color: bool, out: *std.Io.Writer) !void { pub fn run(allocator: std.mem.Allocator, svc: *zfin.DataService, file_path: []const u8, color: bool, out: *std.Io.Writer) !void {
_ = config;
// Load portfolio // Load portfolio
const file_data = std.fs.cwd().readFileAlloc(allocator, file_path, 10 * 1024 * 1024) catch { const file_data = std.fs.cwd().readFileAlloc(allocator, file_path, 10 * 1024 * 1024) catch {
try cli.stderrPrint("Error: Cannot read portfolio file\n"); try cli.stderrPrint("Error: Cannot read portfolio file\n");
@ -23,11 +21,12 @@ pub fn run(allocator: std.mem.Allocator, config: zfin.Config, svc: *zfin.DataSer
const positions = try portfolio.positions(allocator); const positions = try portfolio.positions(allocator);
defer allocator.free(positions); defer allocator.free(positions);
// Build prices map from cache const syms = try portfolio.stockSymbols(allocator);
defer allocator.free(syms);
// Build prices from cache
var prices = std.StringHashMap(f64).init(allocator); var prices = std.StringHashMap(f64).init(allocator);
defer prices.deinit(); defer prices.deinit();
// First pass: try cached candle prices
for (positions) |pos| { for (positions) |pos| {
if (pos.shares <= 0) continue; if (pos.shares <= 0) continue;
if (svc.getCachedCandles(pos.symbol)) |cs| { if (svc.getCachedCandles(pos.symbol)) |cs| {
@ -37,15 +36,16 @@ pub fn run(allocator: std.mem.Allocator, config: zfin.Config, svc: *zfin.DataSer
} }
} }
} }
// Build fallback prices for symbols without cached candle data
var manual_price_set = try zfin.valuation.buildFallbackPrices(allocator, portfolio.lots, positions, &prices);
defer manual_price_set.deinit();
var summary = zfin.valuation.portfolioSummary(allocator, portfolio, positions, prices, manual_price_set) catch { // Build summary via shared pipeline
try cli.stderrPrint("Error computing portfolio summary.\n"); var pf_data = cli.buildPortfolioData(allocator, portfolio, positions, syms, &prices, svc) catch |err| switch (err) {
return; error.NoAllocations, error.SummaryFailed => {
try cli.stderrPrint("Error computing portfolio summary.\n");
return;
},
else => return err,
}; };
defer summary.deinit(allocator); defer pf_data.deinit(allocator);
// Load classification metadata // Load classification metadata
const dir_end = if (std.mem.lastIndexOfScalar(u8, file_path, '/')) |idx| idx + 1 else 0; const dir_end = if (std.mem.lastIndexOfScalar(u8, file_path, '/')) |idx| idx + 1 else 0;
@ -78,10 +78,10 @@ pub fn run(allocator: std.mem.Allocator, config: zfin.Config, svc: *zfin.DataSer
var result = zfin.analysis.analyzePortfolio( var result = zfin.analysis.analyzePortfolio(
allocator, allocator,
summary.allocations, pf_data.summary.allocations,
cm, cm,
portfolio, portfolio,
summary.total_value, pf_data.summary.total_value,
acct_map_opt, acct_map_opt,
) catch { ) catch {
try cli.stderrPrint("Error computing analysis.\n"); try cli.stderrPrint("Error computing analysis.\n");
@ -101,51 +101,22 @@ 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", .{});
// Asset Class const sections = [_]struct { items: []const zfin.analysis.BreakdownItem, title: []const u8 }{
try cli.setBold(out, color); .{ .items = result.asset_class, .title = " Asset Class" },
try cli.setFg(out, color, cli.CLR_HEADER); .{ .items = result.sector, .title = " Sector (Equities)" },
try out.print(" Asset Class\n", .{}); .{ .items = result.geo, .title = " Geographic" },
try cli.reset(out, color); .{ .items = result.account, .title = " By Account" },
try printBreakdownSection(out, result.asset_class, label_width, bar_width, color); .{ .items = result.tax_type, .title = " By Tax Type" },
};
// Sector for (sections, 0..) |sec, si| {
if (result.sector.len > 0) { if (si > 0 and sec.items.len == 0) continue;
try out.print("\n", .{}); if (si > 0) try out.print("\n", .{});
try cli.setBold(out, color); try cli.setBold(out, color);
try cli.setFg(out, color, cli.CLR_HEADER); try cli.setFg(out, color, cli.CLR_HEADER);
try out.print(" Sector (Equities)\n", .{}); try out.print("{s}\n", .{sec.title});
try cli.reset(out, color); try cli.reset(out, color);
try printBreakdownSection(out, result.sector, label_width, bar_width, color); try printBreakdownSection(out, sec.items, label_width, bar_width, color);
}
// Geographic
if (result.geo.len > 0) {
try out.print("\n", .{});
try cli.setBold(out, color);
try cli.setFg(out, color, cli.CLR_HEADER);
try out.print(" Geographic\n", .{});
try cli.reset(out, color);
try printBreakdownSection(out, result.geo, label_width, bar_width, color);
}
// By Account
if (result.account.len > 0) {
try out.print("\n", .{});
try cli.setBold(out, color);
try cli.setFg(out, color, cli.CLR_HEADER);
try out.print(" By Account\n", .{});
try cli.reset(out, color);
try printBreakdownSection(out, result.account, label_width, bar_width, color);
}
// Tax Type
if (result.tax_type.len > 0) {
try out.print("\n", .{});
try cli.setBold(out, color);
try cli.setFg(out, color, cli.CLR_HEADER);
try out.print(" By Tax Type\n", .{});
try cli.reset(out, color);
try printBreakdownSection(out, result.tax_type, label_width, bar_width, color);
} }
// Unclassified // Unclassified

View file

@ -211,7 +211,7 @@ pub fn main() !u8 {
break; break;
} }
} }
try commands.analysis.run(allocator, config, &svc, analysis_file, color, out); try commands.analysis.run(allocator, &svc, analysis_file, color, out);
} else { } else {
try cli.stderrPrint("Unknown command. Run 'zfin help' for usage.\n"); try cli.stderrPrint("Unknown command. Run 'zfin help' for usage.\n");
return 1; return 1;