use priceSymbol() rather than raw symbol when calculating tax percentages
This commit is contained in:
parent
7bc19eafb7
commit
972b7436c0
1 changed files with 30 additions and 3 deletions
|
|
@ -499,8 +499,10 @@ fn bucketForSymbol(
|
|||
return "Unclassified";
|
||||
}
|
||||
|
||||
/// Walk the open lots for `symbol` and compute the share of market
|
||||
/// value held in taxable accounts. Returns null when:
|
||||
/// Walk the open lots for `symbol` (the allocation's `priceSymbol()`)
|
||||
/// and compute the share of market value held in taxable accounts.
|
||||
/// Lots are matched by `priceSymbol()`, so ticker-aliased lots count.
|
||||
/// Returns null when:
|
||||
/// - account_map is null (no classification metadata available)
|
||||
/// - the symbol has no open lots
|
||||
/// - all lots have unknown accounts (none match in the map)
|
||||
|
|
@ -515,7 +517,12 @@ fn computeTaxPct(
|
|||
var taxable_shares: f64 = 0;
|
||||
var classified_shares: f64 = 0;
|
||||
for (portfolio.lots) |lot| {
|
||||
if (!std.mem.eql(u8, lot.symbol, symbol)) continue;
|
||||
// Match on priceSymbol() (the ticker:: alias when set, else
|
||||
// the raw symbol), because the caller passes the allocation's
|
||||
// symbol, which is itself priceSymbol(). Matching raw
|
||||
// lot.symbol would miss ticker-aliased lots (e.g. a CUSIP with
|
||||
// ticker::VTTHX), yielding a wrong/empty tax%.
|
||||
if (!std.mem.eql(u8, lot.priceSymbol(), symbol)) continue;
|
||||
if (!lot.lotIsOpenAsOf(as_of)) continue;
|
||||
if (lot.security_type != .stock) continue; // options/cash/CDs handled elsewhere
|
||||
const acct = lot.account orelse continue;
|
||||
|
|
@ -910,6 +917,26 @@ test "computeTaxPct: mixed accounts produces partial tax%" {
|
|||
try testing.expectApproxEqAbs(@as(f64, 0.6), result.?, 0.001);
|
||||
}
|
||||
|
||||
test "computeTaxPct: matches ticker-aliased lots via priceSymbol()" {
|
||||
// Regression: the caller passes the allocation's symbol, which is
|
||||
// priceSymbol(). A CUSIP lot aliased to a ticker must be matched
|
||||
// by priceSymbol(), not raw lot.symbol, or its shares are dropped
|
||||
// and the tax% comes out wrong (or null).
|
||||
var lots = [_]zfin.Lot{
|
||||
.{ .symbol = "02315N600", .shares = 60, .open_date = Date.fromYmd(2022, 1, 10), .open_price = 200, .account = "Brokerage", .ticker = "VTTHX" },
|
||||
.{ .symbol = "02315N600", .shares = 40, .open_date = Date.fromYmd(2022, 1, 10), .open_price = 200, .account = "Roth IRA", .ticker = "VTTHX" },
|
||||
};
|
||||
const portfolio: zfin.Portfolio = .{ .lots = lots[0..], .allocator = testing.allocator };
|
||||
var entries = [_]analysis.AccountTaxEntry{
|
||||
.{ .account = "Brokerage", .tax_type = .taxable },
|
||||
.{ .account = "Roth IRA", .tax_type = .roth },
|
||||
};
|
||||
const am: analysis.AccountMap = .{ .entries = entries[0..], .allocator = testing.allocator };
|
||||
// Caller passes priceSymbol() ("VTTHX"), not the raw CUSIP.
|
||||
const result = computeTaxPct("VTTHX", portfolio, am, Date.fromYmd(2026, 1, 1));
|
||||
try testing.expectApproxEqAbs(@as(f64, 0.6), result.?, 0.001);
|
||||
}
|
||||
|
||||
test "annualizedFromResult: null result returns null" {
|
||||
try testing.expect(annualizedFromResult(null, false) == null);
|
||||
try testing.expect(annualizedFromResult(null, true) == null);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue