better heuristics on cash-like thingies
All checks were successful
Generic zig build / build (push) Successful in 1m7s

This commit is contained in:
Emil Lerch 2026-04-10 09:50:22 -07:00
parent ddd47dad66
commit 5ee2151a47
Signed by: lobo
GPG key ID: A7B62D657EF764F8

View file

@ -75,8 +75,10 @@ const FidelityCol = struct {
const symbol = 2;
const description = 3;
const quantity = 4;
const last_price = 5;
const current_value = 7;
const cost_basis_total = 13;
const avg_cost_basis = 14;
const type_col = 15;
};
@ -130,7 +132,9 @@ pub fn parseFidelityCsv(allocator: std.mem.Allocator, data: []const u8) ![]Broke
// Note: Fidelity's Type column says "Cash" vs "Margin" to indicate
// the account settlement type, NOT that the security is cash.
// Only the ** suffix reliably identifies money market / cash holdings.
const is_cash = std.mem.endsWith(u8, symbol_raw, "**");
// Also treat positions as cash when both price and cost basis are $1.00
// (e.g. FDRXX "FID GOV CASH RESERVE" no ** suffix).
const is_cash = std.mem.endsWith(u8, symbol_raw, "**") or isUnitPriceCash(cols[FidelityCol.last_price], cols[FidelityCol.avg_cost_basis]);
// Strip ** suffix from money market symbols for display
const symbol_clean = if (std.mem.endsWith(u8, symbol_raw, "**"))
@ -180,6 +184,14 @@ fn parseDollarAmount(raw: []const u8) ?f64 {
return if (negative) -val else val;
}
/// Returns true when both the last price and average cost basis parse to exactly $1.00,
/// indicating a money-market or cash-equivalent position (e.g. FDRXX).
fn isUnitPriceCash(price_raw: []const u8, cost_raw: []const u8) bool {
const price = parseDollarAmount(price_raw) orelse return false;
const cost = parseDollarAmount(cost_raw) orelse return false;
return price == 1.0 and cost == 1.0;
}
// Schwab CSV parser
//
// Parses the per-account positions CSV exported from Schwab's website
@ -1293,6 +1305,22 @@ test "parseFidelityCsv basic" {
try std.testing.expectApproxEqAbs(@as(f64, 10000.00), positions[1].cost_basis.?, 0.01);
}
test "parseFidelityCsv treats $1.00 price+cost as cash" {
const csv =
"Account Number,Account Name,Symbol,Description,Quantity,Last Price,Last Price Change,Current Value,Today's Gain/Loss Dollar,Today's Gain/Loss Percent,Total Gain/Loss Dollar,Total Gain/Loss Percent,Percent Of Account,Cost Basis Total,Average Cost Basis,Type\n" ++
"Z123,Individual,FDRXX,FID GOV CASH RESERVE,8500,$1.00,,$8500.00,,,,,10%,$8500.00,$1.00,Cash,\n";
const allocator = std.testing.allocator;
const positions = try parseFidelityCsv(allocator, csv);
defer allocator.free(positions);
try std.testing.expectEqual(@as(usize, 1), positions.len);
try std.testing.expectEqualStrings("FDRXX", positions[0].symbol);
try std.testing.expect(positions[0].is_cash);
try std.testing.expect(positions[0].quantity == null);
try std.testing.expectApproxEqAbs(@as(f64, 8500.00), positions[0].current_value.?, 0.01);
}
test "parseFidelityCsv stops at blank line" {
const csv =
"Account Number,Account Name,Symbol,Description,Quantity,Last Price,Last Price Change,Current Value,Today's Gain/Loss Dollar,Today's Gain/Loss Percent,Total Gain/Loss Dollar,Total Gain/Loss Percent,Percent Of Account,Cost Basis Total,Average Cost Basis,Type\n" ++