Skip to content

fix(bdc): portfolio_investments() no longer counts holdings twice - #1387

Merged
dgunning merged 1 commit into
mainfrom
fix/6xxb-bdc-overcount
Sep 30, 2026
Merged

dgunning merged 1 commit into
mainfrom
fix/6xxb-bdc-overcount

Conversation

@dgunning

Copy link
Copy Markdown
Owner

Bead: edgartools-6xxb

Problem

PortfolioInvestments.from_xbrl creates one row per InvestmentIdentifierAxis member. 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):

BDC before after
FSK +48.4% +11.47% ⚠︎
HTGC +21.0% +1.37%
CSWC +18.9% +0.46%
OBDC +17.7% +6.35% ⚠︎
ARCC +15.7% +0.28%
MAIN +14.1% −0.13%
TSLX +5.9% +1.74%
MSIF +5.6% 0.00%
GBDC +2.0% +0.70%

MAIN's FY2025 10-K (0001396440-26-000016) summed to $6,296,964,000 against a reported $5,518,117,000. The MCP tool edgar_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:

  • Company totals such as "… and Total X" (HTGC). The sum is matched only against rows under the same heading.
  • Parent rows that equal the sum of the rows extending their identifier:
    • MAIN: Bolder Panther Group, LLC | Secured Debt equals tranches 1.1, 1.2 and 1.3.
    • ARCC: Ivy Hill Asset Management, L.P. equals its instruments.
  • One identifier spelled two ways with the same fair value (CSWC).
  • Rows without a cost whose fair value equals a costed row. These are the affiliate roll-forward restating a holding under its own member name (OBDC 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 MCP bdc_portfolio result warn when the gap exceeds 2%. The MCP result also carries reported_total_fair_value.

I sampled the excluded rows by hand. They are company-level rows with no cost of their own (SPF Borrower LLC as the total of 4 loans, OPH NEP Investment, LLC as the total of 3), or explicit "Total" rows.

Verification

  • New regression test: tests/issues/regression/test_6xxb_bdc_restated_rows.py, with 8 offline tests and 1 network ground-truth test.
    • It covers each rule, plus a silence check: a company named "Total Quality Logistics", a parent that is not the sum of its children, and two equal-valued costed tranches are all kept.
    • It checks that the most precise reported total wins (ARCC tags it at decimals −6 and −5), that the reporting currency beats a foreign one, and that a gap is surfaced rather than hidden.
    • The network test pins MAIN's reported $5,518,117,000, a gap under 0.2%, and 76 excluded rows.
  • Updated 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.
  • Fast lane: 7,808 passed. The one failure, test_dt1f1_wrapped_item_headers (20-F lengths), also fails on main locally.
  • BDC network tests, one file per call: test_bdc.py 236 passed, test_bdc_nonaccrual.py 20, plus the get_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.
  • Lint: ruff reports no new findings.

Not in scope:

  • The DERA soi.tsv dataset path may have the same double count. It is a separate code path.
  • The remaining OBDC and FSK gaps.

🤖 Generated with Claude Code

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>
@dgunning
dgunning enabled auto-merge (squash) September 30, 2026 17:23
@dgunning
dgunning merged commit 049de29 into main Sep 30, 2026
9 of 10 checks passed
@dgunning
dgunning deleted the fix/6xxb-bdc-overcount branch September 30, 2026 17:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant