centralize portfolio load

This commit is contained in:
Emil Lerch 2026-04-27 19:53:42 -07:00
parent fe0e10eaca
commit 68392efec4
Signed by: lobo
GPG key ID: A7B62D657EF764F8
4 changed files with 69 additions and 54 deletions

View file

@ -5,24 +5,12 @@ const fmt = cli.fmt;
/// CLI `analysis` command: show portfolio analysis breakdowns.
pub fn run(allocator: std.mem.Allocator, svc: *zfin.DataService, file_path: []const u8, color: bool, out: *std.Io.Writer) !void {
// Load portfolio
const file_data = std.fs.cwd().readFileAlloc(allocator, file_path, 10 * 1024 * 1024) catch {
try cli.stderrPrint("Error: Cannot read portfolio file\n");
return;
};
defer allocator.free(file_data);
var loaded = cli.loadPortfolio(allocator, file_path) orelse return;
defer loaded.deinit(allocator);
var portfolio = zfin.cache.deserializePortfolio(allocator, file_data) catch {
try cli.stderrPrint("Error: Cannot parse portfolio file\n");
return;
};
defer portfolio.deinit();
const positions = try portfolio.positions(allocator);
defer allocator.free(positions);
const syms = try portfolio.stockSymbols(allocator);
defer allocator.free(syms);
const portfolio = loaded.portfolio;
const positions = loaded.positions;
const syms = loaded.syms;
// Build prices from cache
var prices = std.StringHashMap(f64).init(allocator);

View file

@ -250,6 +250,60 @@ pub fn loadPortfolioPrices(
return result;
}
// Portfolio loading
/// Result of loading and parsing a portfolio file. Caller must call deinit().
pub const LoadedPortfolio = struct {
file_data: []const u8,
portfolio: zfin.Portfolio,
positions: []const zfin.Position,
syms: []const []const u8,
pub fn deinit(self: *LoadedPortfolio, allocator: std.mem.Allocator) void {
allocator.free(self.syms);
allocator.free(self.positions);
self.portfolio.deinit();
allocator.free(self.file_data);
}
};
/// Read, deserialize, and extract positions + symbols from a portfolio file.
/// Returns null (with stderr message) on read/parse errors.
pub fn loadPortfolio(allocator: std.mem.Allocator, file_path: []const u8) ?LoadedPortfolio {
const file_data = std.fs.cwd().readFileAlloc(allocator, file_path, 10 * 1024 * 1024) catch {
stderrPrint("Error: Cannot read portfolio file\n") catch {};
return null;
};
var portfolio = zfin.cache.deserializePortfolio(allocator, file_data) catch {
allocator.free(file_data);
stderrPrint("Error: Cannot parse portfolio file\n") catch {};
return null;
};
const positions = portfolio.positions(allocator) catch {
portfolio.deinit();
allocator.free(file_data);
stderrPrint("Error: Cannot compute positions\n") catch {};
return null;
};
const syms = portfolio.stockSymbols(allocator) catch {
allocator.free(positions);
portfolio.deinit();
allocator.free(file_data);
stderrPrint("Error: Cannot get stock symbols\n") catch {};
return null;
};
return .{
.file_data = file_data,
.portfolio = portfolio,
.positions = positions,
.syms = syms,
};
}
// Portfolio data pipeline
/// Result of the shared portfolio data pipeline. Caller must call deinit().

View file

@ -11,33 +11,18 @@ fn setIntentFg(out: *std.Io.Writer, color: bool, intent: fmt.StyleIntent) !void
pub fn run(allocator: std.mem.Allocator, svc: *zfin.DataService, file_path: []const u8, watchlist_path: ?[]const u8, force_refresh: bool, color: bool, out: *std.Io.Writer) !void {
// Load portfolio from SRF file
const data = std.fs.cwd().readFileAlloc(allocator, file_path, 10 * 1024 * 1024) catch |err| {
try cli.stderrPrint("Error reading portfolio file: ");
try cli.stderrPrint(@errorName(err));
try cli.stderrPrint("\n");
return;
};
defer allocator.free(data);
var loaded = cli.loadPortfolio(allocator, file_path) orelse return;
defer loaded.deinit(allocator);
var portfolio = zfin.cache.deserializePortfolio(allocator, data) catch {
try cli.stderrPrint("Error parsing portfolio file.\n");
return;
};
defer portfolio.deinit();
const portfolio = loaded.portfolio;
const positions = loaded.positions;
const syms = loaded.syms;
if (portfolio.lots.len == 0) {
try cli.stderrPrint("Portfolio is empty.\n");
return;
}
// Get stock/ETF positions (excludes options, CDs, cash)
const positions = try portfolio.positions(allocator);
defer allocator.free(positions);
// Get unique stock/ETF symbols and fetch current prices
const syms = try portfolio.stockSymbols(allocator);
defer allocator.free(syms);
var prices = std.StringHashMap(f64).init(allocator);
defer prices.deinit();

View file

@ -19,24 +19,12 @@ const stock_benchmark = "SPY";
const bond_benchmark = "AGG";
pub fn run(allocator: std.mem.Allocator, svc: *zfin.DataService, file_path: []const u8, color: bool, out: *std.Io.Writer) !void {
// Load portfolio
const file_data = std.fs.cwd().readFileAlloc(allocator, file_path, 10 * 1024 * 1024) catch {
try cli.stderrPrint("Error: Cannot read portfolio file\n");
return;
};
defer allocator.free(file_data);
var loaded = cli.loadPortfolio(allocator, file_path) orelse return;
defer loaded.deinit(allocator);
var portfolio = zfin.cache.deserializePortfolio(allocator, file_data) catch {
try cli.stderrPrint("Error: Cannot parse portfolio file\n");
return;
};
defer portfolio.deinit();
const positions = try portfolio.positions(allocator);
defer allocator.free(positions);
const syms = try portfolio.stockSymbols(allocator);
defer allocator.free(syms);
const portfolio = loaded.portfolio;
const positions = loaded.positions;
const syms = loaded.syms;
// Build prices from cache
var prices = std.StringHashMap(f64).init(allocator);