Initial commit

This commit is contained in:
kdusek
2025-12-09 12:13:01 +01:00
commit 8e654ed209
13332 changed files with 2695056 additions and 0 deletions

View File

@@ -0,0 +1,106 @@
"""
13F filing module for investment funds.
This module provides classes and functions for working with 13F filings
that report investment fund portfolio holdings.
"""
import logging
import pandas as pd
# Define constants
THIRTEENF_FORMS = ['13F-HR', "13F-HR/A", "13F-NT", "13F-NT/A", "13F-CTR", "13F-CTR/A"]
log = logging.getLogger(__name__)
# We'll define these functions without directly importing them at the module level
# to avoid circular imports
def get_ThirteenF():
"""Dynamically import ThirteenF to avoid circular imports."""
from edgar.thirteenf import ThirteenF as OriginalThirteenF
return OriginalThirteenF
# Create property-like functions that provide lazy loading
def ThirteenF():
"""Get the ThirteenF class, dynamically importing it to avoid circular imports."""
return get_ThirteenF()
def get_thirteenf_portfolio(filing) -> pd.DataFrame:
"""
Extract portfolio holdings from a 13F filing.
Args:
filing: The 13F filing to extract data from
Returns:
DataFrame containing portfolio holdings
"""
try:
# Create a ThirteenF from the filing
thirteenf_class = get_ThirteenF()
thirteenf = thirteenf_class(filing, use_latest_period_of_report=True)
# Check if the filing has an information table
if not thirteenf.has_infotable():
log.info("Filing %s does not have an information table", filing.accession_no)
return pd.DataFrame()
# Extract the information table
infotable = thirteenf.infotable
if infotable is None:
log.warning("Could not extract information table from filing %s", filing.accession_no)
return pd.DataFrame()
# Convert to DataFrame
df = pd.DataFrame(infotable)
# Clean up and organize data
if not df.empty:
# Update column names for consistency
if 'nameOfIssuer' in df.columns:
df = df.rename(columns={
'nameOfIssuer': 'name',
'titleOfClass': 'title',
'cusip': 'cusip',
'value': 'value_usd',
'sshPrnamt': 'shares',
'sshPrnamtType': 'share_type',
'investmentDiscretion': 'investment_discretion',
'votingAuthority': 'voting_authority'
})
# Add ticker mapping if possible
try:
from edgar.reference import cusip_ticker_mapping
cusip_map = cusip_ticker_mapping(allow_duplicate_cusips=False)
df['ticker'] = df['cusip'].map(cusip_map.Ticker)
except Exception as e:
log.warning("Error adding ticker mappings: %s", e)
df['ticker'] = None
# Calculate percent of portfolio
if 'value_usd' in df.columns:
total_value = df['value_usd'].sum()
if total_value > 0:
df['pct_value'] = df['value_usd'] / total_value * 100
else:
df['pct_value'] = 0
# Sort by value
df = df.sort_values('value_usd', ascending=False).reset_index(drop=True)
return df
except Exception as e:
log.warning("Error extracting holdings from 13F filing: %s", e)
# Return empty DataFrame if extraction failed
return pd.DataFrame()
# Functions for export
__all__ = [
'ThirteenF',
'THIRTEENF_FORMS',
'get_thirteenf_portfolio',
]