diff --git a/src/analytics/risk.zig b/src/analytics/risk.zig index 62a7756..a09cd21 100644 --- a/src/analytics/risk.zig +++ b/src/analytics/risk.zig @@ -163,7 +163,7 @@ pub fn portfolioSummary( // Only apply price_ratio to live/fetched prices. Manual/fallback prices // (avg_cost) are already in the correct terms for the share class. const is_manual = if (manual_prices) |mp| mp.contains(pos.symbol) else false; - const price = if (is_manual) raw_price else raw_price * (pos.price_ratio orelse 1.0); + const price = if (is_manual) raw_price else raw_price * pos.price_ratio; const mv = pos.shares * price; total_value += mv; total_cost += pos.total_cost; diff --git a/src/cache/store.zig b/src/cache/store.zig index 0d65712..813cc15 100644 --- a/src/cache/store.zig +++ b/src/cache/store.zig @@ -906,13 +906,12 @@ test "portfolio: price_ratio round-trip" { try std.testing.expectEqualStrings("02315N600", portfolio.lots[0].symbol); try std.testing.expectEqualStrings("VTTHX", portfolio.lots[0].ticker.?); try std.testing.expectEqualStrings("VTTHX", portfolio.lots[0].priceSymbol()); - try std.testing.expect(portfolio.lots[0].price_ratio != null); - try std.testing.expectApproxEqAbs(@as(f64, 5.185), portfolio.lots[0].price_ratio.?, 0.001); + try std.testing.expectApproxEqAbs(@as(f64, 5.185), portfolio.lots[0].price_ratio, 0.001); try std.testing.expectEqualStrings("VANGUARD TARGET 2035", portfolio.lots[0].note.?); - // Regular lot — no price_ratio + // Regular lot — no price_ratio (default 1.0) try std.testing.expectEqualStrings("AAPL", portfolio.lots[1].symbol); - try std.testing.expect(portfolio.lots[1].price_ratio == null); + try std.testing.expectApproxEqAbs(@as(f64, 1.0), portfolio.lots[1].price_ratio, 0.001); try std.testing.expect(portfolio.lots[1].ticker == null); // Round-trip: serialize and deserialize again @@ -923,7 +922,7 @@ test "portfolio: price_ratio round-trip" { defer portfolio2.deinit(); try std.testing.expectEqual(@as(usize, 2), portfolio2.lots.len); - try std.testing.expectApproxEqAbs(@as(f64, 5.185), portfolio2.lots[0].price_ratio.?, 0.001); + try std.testing.expectApproxEqAbs(@as(f64, 5.185), portfolio2.lots[0].price_ratio, 0.001); try std.testing.expectEqualStrings("VTTHX", portfolio2.lots[0].ticker.?); - try std.testing.expect(portfolio2.lots[1].price_ratio == null); + try std.testing.expectApproxEqAbs(@as(f64, 1.0), portfolio2.lots[1].price_ratio, 0.001); } diff --git a/src/models/portfolio.zig b/src/models/portfolio.zig index f3d32e1..b4d5cf4 100644 --- a/src/models/portfolio.zig +++ b/src/models/portfolio.zig @@ -66,7 +66,7 @@ pub const Lot = struct { /// (from the `ticker` symbol) is multiplied by this ratio to get the actual /// institutional NAV. E.g. if VTTHX (investor) is $27.78 and the institutional /// class trades at $144.04, price_ratio = 144.04 / 27.78 ≈ 5.185. - price_ratio: ?f64 = null, + price_ratio: f64 = 1.0, /// The symbol to use for price fetching (ticker if set, else symbol). pub fn priceSymbol(self: Lot) []const u8 { @@ -129,7 +129,7 @@ pub const Position = struct { /// Currently positions() takes the ratio from the first lot that has one. /// Supporting dual-holding of investor + institutional shares of the same /// ticker would require a different grouping key in positions(). - price_ratio: ?f64 = null, + price_ratio: f64 = 1.0, }; /// A portfolio is a collection of lots. @@ -250,7 +250,7 @@ pub const Portfolio = struct { entry.value_ptr.account = "Multiple"; } // Propagate price_ratio from the first lot that has one - if (entry.value_ptr.price_ratio == null and lot.price_ratio != null) { + if (entry.value_ptr.price_ratio == 1.0 and lot.price_ratio != 1.0) { entry.value_ptr.price_ratio = lot.price_ratio; } } @@ -537,11 +537,10 @@ test "positions propagates price_ratio from lot" { for (pos) |p| { if (std.mem.eql(u8, p.symbol, "VTTHX")) { try std.testing.expectApproxEqAbs(@as(f64, 150.0), p.shares, 0.01); - try std.testing.expect(p.price_ratio != null); - try std.testing.expectApproxEqAbs(@as(f64, 5.185), p.price_ratio.?, 0.001); + try std.testing.expectApproxEqAbs(@as(f64, 5.185), p.price_ratio, 0.001); } else { try std.testing.expectEqualStrings("AAPL", p.symbol); - try std.testing.expect(p.price_ratio == null); + try std.testing.expectApproxEqAbs(@as(f64, 1.0), p.price_ratio, 0.001); } } }