AgentSkillsCN

bdc-xbrl-extraction

利用 XBRL 分类法从 BDC 10-K/10-Q 文件中提取投资明细数据。涵盖债务类投资(优先担保贷款、次级债务等)以及权益类投资(普通股、优先股、认股权证、有限责任公司份额)。可从 SEC Reg S-X 第 12-12 表(非关联方)和第 12-13 表(关联方)中提取相关信息。适用于从商业发展公司 SEC 文件中提取投资组合持仓、投资头寸、公允价值、贷款数据,或公司层面的数据时使用。可通过诸如“BDC 投资组合提取”、“投资明细表解析”、“投资公司持仓分析”、“私募信贷组合数据”、“贷款账簿分析”等短语进行触发。

SKILL.md
--- frontmatter
name: bdc-xbrl-extraction
description: Extract Schedule of Investments data from BDC 10-K/10-Q filings using XBRL taxonomy. Covers BOTH debt investments (senior secured loans, subordinated debt, etc.) AND equity investments (common stock, preferred, warrants, LLC units). Extracts from SEC Reg S-X Schedules 12-12 (unaffiliated) and 12-13 (affiliated). Use when extracting portfolio holdings, investment positions, fair values, loan data, or company-level data from Business Development Company SEC filings. Triggers on: BDC portfolio extraction, Schedule of Investments parsing, investment company holdings analysis, private credit portfolio data, loan book analysis.

BDC Schedule of Investments Extraction

Extract structured portfolio data from Business Development Company SEC filings.

Overview

BDCs file a Consolidated Schedule of Investments in 10-K/10-Q containing:

  • Debt Investments: Senior secured loans, subordinated debt, mezzanine, unitranche
  • Equity Investments: Common stock, preferred stock, warrants, LLC/LP units

Data comes from SEC Reg S-X Article 12:

  • Schedule 12-12: Investments in Securities of Unaffiliated Issuers
  • Schedule 12-13: Investments in and Advances to Affiliates

All use Inline XBRL (required since Aug 2022) with the US GAAP Investment Company Taxonomy.

Validated Results

Tested and validated on 4 major BDCs (Jan 2025):

BDCTickerDebtEquityTotal FVStatus
Ares CapitalARCC1,239427$35.6B
Main Street CapitalMAIN452204$5.4B
FS KKR CapitalFSK541153$19.9B
Hercules CapitalHTGC261258$7.3B
Total2,4931,042$68.2B

Critical Implementation Notes

1. Data is Split Across Multiple Statements

SOI data is NOT in one clean table. It's distributed across:

  • Schedule of Investments → Interest rates, spreads, maturity dates
  • Balance Sheet → Fair values
  • Balance Sheet Parenthetical → Cost basis

Must query all statements and join by investment ID.

2. Investment IDs are in Context Dimensions

Investment identifiers are NOT in fact values. They're in XBRL context dimensions:

python
# WRONG - won't find IDs
facts_df['investment_id']  # Doesn't exist as column

# CORRECT - access via contexts
context_ref = fact['context_ref']
dimensions = xbrl.contexts[context_ref].dimensions
investment_id = dimensions.get('InvestmentIdentifierAxis')

3. Namespace Prefixes Vary - Always Normalize

Different BDCs use different namespace prefixes:

  • us-gaap:InvestmentOwnedAtFairValue
  • InvestmentOwnedAtFairValue (no prefix)
  • arcc:CustomElement

Always strip namespace when matching:

python
def normalize_concept(concept: str) -> str:
    """Strip namespace prefix for consistent matching."""
    if not concept:
        return concept
    if ':' in concept:
        return concept.split(':', 1)[1]
    return concept

4. Classification Rate is 67-81%

Not all positions classify cleanly. Expect:

  • 67-81% classified as Debt or Equity
  • 19-33% unclassified (aggregate rollups, sector totals, etc.)

Unclassified positions are typically aggregate dimension members, not individual investments.

Output Structure

code
{TICKER}_10K_debt_investments.csv      # Loans with rate/maturity data
{TICKER}_10K_equity_investments.csv    # Equity with shares/ownership data  
{TICKER}_10K_affiliated_rollforward.csv # Affiliated investment activity
{TICKER}_10K_portfolio_summary.csv     # Totals by type, industry, affiliation

Quick Start

CLI Usage

bash
python extract_soi.py ARCC --identity "Your Name your@email.com"
python extract_soi.py MAIN 10-Q --identity "Your Name your@email.com"
python extract_soi.py FSK 10-K 2023 --identity "Your Name your@email.com"

Python Usage

python
from edgar import Company, set_identity

set_identity("Your Name your@email.com")

company = Company("ARCC")
filing = company.get_filings(form="10-K").latest()
xbrl = filing.xbrl()

# Use the extraction functions from extract_soi.py
from extract_soi import extract_all_investments
debt_df, equity_df, affiliated_df, summary_df = extract_all_investments(xbrl)

Key XBRL Elements

Core Elements (Both Debt and Equity)

FieldElement (no namespace)Description
Fair ValueInvestmentOwnedAtFairValueCurrent fair value
CostInvestmentOwnedAtCostAmortized cost basis
% Net AssetsInvestmentOwnedPercentOfNetAssetsPosition size

Debt-Specific Elements

FieldElementDescription
PrincipalInvestmentOwnedBalancePrincipalAmountOutstanding principal
Interest RateInvestmentInterestRateStated rate
SpreadInvestmentBasisSpreadVariableRateSpread over base
FloorInvestmentInterestRateFloorRate floor
PIK RateInvestmentInterestRatePaidInKindPIK component
MaturityInvestmentMaturityDateDue date

Equity-Specific Elements

FieldElementDescription
SharesInvestmentOwnedBalanceSharesShares/units held

Extensible Enumerations

FieldElementNotes
Issuer NameInvestmentIssuerNameExtensibleEnumerationPortfolio company
Investment TypeInvestmentTypeExtensibleEnumerationLoan type, stock class
IndustryInvestmentIndustrySectorExtensibleEnumerationSector
AffiliationInvestmentIssuerAffiliationExtensibleEnumerationAffiliated/Unaffiliated
Rate TypeInvestmentVariableInterestRateTypeExtensibleEnumerationSOFR, Prime, etc.

Investment ID Format Examples

Investment IDs from context dimensions vary by BDC:

code
# ARCC format
"Actfy Buyer, Inc., First lien senior secured loan"
"ABC Company, Class A common stock"

# MAIN format  
"XYZ Holdings LLC - Senior Secured Term Loan"
"Portfolio Co Inc - Common Equity"

# FSK format
"Company Name | First Lien Term Loan | SOFR+500"

Entity resolution will require fuzzy matching or normalization.

Debt vs Equity Classification

The script classifies using multiple signals:

  1. Investment Type Keywords

    • Debt: loan, debt, note, bond, credit, mezzanine, senior, subordinated
    • Equity: stock, equity, share, warrant, unit, membership, preferred, common
  2. Field Presence

    • Has Principal + no Shares → Debt
    • Has Shares + no Principal → Equity
  3. Investment Type Axis (for totals)

    • DebtSecuritiesMember
    • EquitySecuritiesMember

Common Issues & Solutions

"No Schedule of Investments facts found"

Cause: Querying wrong concepts or not accessing contexts correctly

Solution:

  1. Query all facts, filter by normalized concept name
  2. Access investment IDs from xbrl.contexts[context_ref].dimensions

"Namespace prefix issues"

Cause: Concept has us-gaap: prefix in some filings, not others

Solution: Always use normalize_concept() to strip prefixes

"Low classification rate"

Cause: Aggregate dimension members (sector totals, rollups) aren't individual investments

Solution: This is expected. Focus on classified positions for analysis.

"Investment ID parsing"

Cause: ID formats vary widely across BDCs

Solution: For entity resolution/overlap analysis, will need:

  • Fuzzy matching
  • Company name normalization
  • Possibly LEI/CIK lookup

Data Quality Expectations

Based on testing across 4 BDCs:

MetricExpected Coverage
Fair Value100%
Cost Basis97-100%
Principal (debt)70-75%
Shares (equity)25-30%
Interest Rate65-70%
Classification Rate67-81%

See Also