fix(bdc): portfolio_investments() no longer counts holdings twice - #1387
Merged
Merged
Conversation
PortfolioInvestments.from_xbrl makes one row per InvestmentIdentifierAxis
member. The members carry no hierarchy (no element labels, no domain-member
arcs), so a filer's subtotals and a second schedule's copies of its holdings
read as holdings. Against each filer's own undimensioned
InvestmentOwnedAtFairValue on its latest 10-K, the rows summed high on all
nine BDCs measured: FSK +48.4%, HTGC +21.0%, CSWC +18.9%, OBDC +17.7%,
ARCC +15.7%, MAIN +14.1%, TSLX +5.9%, MSIF +5.6%, GBDC +2.0%. MAIN's FY2025
10-K summed to 6,296,964,000 against 5,518,117,000 reported.
Rows are now excluded only when other rows account for their value exactly:
- company totals ("... and Total X", HTGC), matched within their own heading;
- parents equal to the sum of the rows extending their identifier (MAIN's
"Bolder Panther Group, LLC | Secured Debt" = tranches 1.1/1.2/1.3; ARCC's
"Ivy Hill Asset Management, L.P.");
- one identifier spelled two ways with the same fair value (CSWC);
- cost-less rows equal to a costed row: the affiliate roll-forward restating a
holding under its own member (OBDC "Blue Owl Credit SLF LLC(c)", FSK).
Separately, a foreign holding is tagged in USD and its own currency and the
last fact won (TSLX's Hippo XPA Bidco: SEK 214,115,000 for USD 23,226,000).
A value in the filer's reporting currency is no longer replaced.
After: MAIN -0.13%, MSIF 0.00%, ARCC +0.28%, CSWC +0.46%, GBDC +0.70%,
HTGC +1.37%, TSLX +1.74%. OBDC (+6.4%) and FSK (+11.5%) tag holdings in ways
no exact rule recognises; rather than guess, the collection now reports it:
reported_total_fair_value (the filer's figure), reconciliation_gap, and
excluded (rows left out, each with a reason). to_context() and the MCP
bdc_portfolio result warn when the gap exceeds 2%.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bead: edgartools-6xxb
Problem
PortfolioInvestments.from_xbrlcreates one row perInvestmentIdentifierAxismember. Those members have no hierarchy: there are no element labels and no domain-member arcs. So a filer's subtotals, and a second schedule's copies of its holdings, were read as holdings.On every BDC measured, the rows summed higher than the filer's own undimensioned
InvestmentOwnedAtFairValue(latest 10-K):MAIN's FY2025 10-K (
0001396440-26-000016) summed to $6,296,964,000 against a reported $5,518,117,000. The MCP tooledgar_fund(bdc_portfolio)reported the inflated number.Change
A row is excluded only when other rows account for its value exactly. Each rule comes from a real filing:
Bolder Panther Group, LLC | Secured Debtequals tranches 1.1, 1.2 and 1.3.Ivy Hill Asset Management, L.P.equals its instruments.Blue Owl Credit SLF LLC(c), FSK).Currency: a fair value, cost or principal in the filer's reporting currency is no longer replaced by a foreign-currency fact. Before this, TSLX's Hippo XPA Bidco counted SEK 214.1M instead of USD 23.2M.
Reporting the remaining gap: OBDC and FSK still tag holdings in ways no exact rule recognises, so instead of guessing, the collection now exposes:
reported_total_fair_value: the filer's own balance-sheet figure;reconciliation_gap: how far the rows' total is from that figure;excluded: the rows left out, each with a reason.to_context()and the MCPbdc_portfolioresult warn when the gap exceeds 2%. The MCP result also carriesreported_total_fair_value.I sampled the excluded rows by hand. They are company-level rows with no cost of their own (
SPF Borrower LLCas the total of 4 loans,OPH NEP Investment, LLCas the total of 3), or explicit "Total" rows.Verification
tests/issues/regression/test_6xxb_bdc_restated_rows.py, with 8 offline tests and 1 network ground-truth test.tests/bdc/test_bdc_industry.py. Its synthetic "grouping subtotal" row is now excluded, and the test asserts that. The sector totals it pins are unchanged.test_dt1f1_wrapped_item_headers(20-F lengths), also fails on main locally.test_bdc.py236 passed,test_bdc_nonaccrual.py20, plus theget_latest_bdc_report_year()treats a 200 as a complete report, and the 2026 one is missing ARES CAPITAL CORP #1146, AFFIRMATIVE_PATTERNS fails to extract MAIN non-accruals due to footnote wording change #835 and 6xxb files.Not in scope:
soi.tsvdataset path may have the same double count. It is a separate code path.🤖 Generated with Claude Code