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";
|
return "Unclassified";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Walk the open lots for `symbol` and compute the share of market
|
/// Walk the open lots for `symbol` (the allocation's `priceSymbol()`)
|
||||||
/// value held in taxable accounts. Returns null when:
|
/// 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)
|
/// - account_map is null (no classification metadata available)
|
||||||
/// - the symbol has no open lots
|
/// - the symbol has no open lots
|
||||||
/// - all lots have unknown accounts (none match in the map)
|
/// - all lots have unknown accounts (none match in the map)
|
||||||
|
|
@ -515,7 +517,12 @@ fn computeTaxPct(
|
||||||
var taxable_shares: f64 = 0;
|
var taxable_shares: f64 = 0;
|
||||||
var classified_shares: f64 = 0;
|
var classified_shares: f64 = 0;
|
||||||
for (portfolio.lots) |lot| {
|
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.lotIsOpenAsOf(as_of)) continue;
|
||||||
if (lot.security_type != .stock) continue; // options/cash/CDs handled elsewhere
|
if (lot.security_type != .stock) continue; // options/cash/CDs handled elsewhere
|
||||||
const acct = lot.account orelse continue;
|
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);
|
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" {
|
test "annualizedFromResult: null result returns null" {
|
||||||
try testing.expect(annualizedFromResult(null, false) == null);
|
try testing.expect(annualizedFromResult(null, false) == null);
|
||||||
try testing.expect(annualizedFromResult(null, true) == null);
|
try testing.expect(annualizedFromResult(null, true) == null);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue