better heuristics on cash-like thingies
All checks were successful
Generic zig build / build (push) Successful in 1m7s
All checks were successful
Generic zig build / build (push) Successful in 1m7s
This commit is contained in:
parent
ddd47dad66
commit
5ee2151a47
1 changed files with 29 additions and 1 deletions
|
|
@ -75,8 +75,10 @@ const FidelityCol = struct {
|
||||||
const symbol = 2;
|
const symbol = 2;
|
||||||
const description = 3;
|
const description = 3;
|
||||||
const quantity = 4;
|
const quantity = 4;
|
||||||
|
const last_price = 5;
|
||||||
const current_value = 7;
|
const current_value = 7;
|
||||||
const cost_basis_total = 13;
|
const cost_basis_total = 13;
|
||||||
|
const avg_cost_basis = 14;
|
||||||
const type_col = 15;
|
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
|
// Note: Fidelity's Type column says "Cash" vs "Margin" to indicate
|
||||||
// the account settlement type, NOT that the security is cash.
|
// the account settlement type, NOT that the security is cash.
|
||||||
// Only the ** suffix reliably identifies money market / cash holdings.
|
// 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
|
// Strip ** suffix from money market symbols for display
|
||||||
const symbol_clean = if (std.mem.endsWith(u8, symbol_raw, "**"))
|
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;
|
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 ───────────────────────────────────────
|
// ── Schwab CSV parser ───────────────────────────────────────
|
||||||
//
|
//
|
||||||
// Parses the per-account positions CSV exported from Schwab's website
|
// 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);
|
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" {
|
test "parseFidelityCsv stops at blank line" {
|
||||||
const csv =
|
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" ++
|
"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" ++
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue