Files
Ten31-Portal/backend/tests/test_loan_import.py
T
Jonathan Kirkwood 12a6e33bd8 Import credit-fund loans into NAV (eNAV HLD-LOANS schedule)
The eNAV importer read only the HLD securities sheet, so a credit fund's
loan book (its largest asset) never reached NAV. SATS Credit Fund showed
$3.6M of securities while holding a $46.3M loan to Fold Holdings.

Parse the HLD-LOANS schedule alongside HLD: each loan becomes a holding
and position valued at principal plus accrued interest, flowing into
holdings, invested cost, and the quarter NAV. Adds test coverage and
bumps the package to 0.2.47.
2026-09-02 08:50:31 -05:00

85 lines
3.2 KiB
Python

"""Credit-fund loan schedule: the eNAV 'HLD-LOANS' sheet must fold into holdings and NAV.
Regression for the SATS Credit Fund case where the Portal showed $3.6M of securities while
the fund held a $46.3M loan — because the loan lives on a separate schedule, not the HLD
Holdings Report. See import_router._parse_loans.
"""
import io
from datetime import date, datetime
import openpyxl
from ten31portal.routers.import_router import _parse_schedule_xlsx
def _enav_with_loan() -> bytes:
"""Minimal eNAV: an HLD securities sheet plus an HLD-LOANS loan schedule."""
wb = openpyxl.Workbook()
hld = wb.active
hld.title = "HLD"
hld["A1"] = datetime(2025, 12, 31)
hld.append([None])
hld.append(["SECURITY NAME", "QUANTITY", "COST BASIS - BOOK", "MARKET VALUE (BOOK)"])
hld.append(["BTC", 18.74, 1_734_584.37, 1_639_942.63])
hld.append(["Fold Holdings, Inc. - Warrant 03/26/25", 3_702_360, 0, 0])
hld.append(["TOTAL - LONG", None, 1_734_584.37, 3_597_442.63])
loans = wb.create_sheet("HLD-LOANS")
loans.append(["SATS CREDIT FUND LP"])
loans.append([None])
# Header + one loan row + a total row the parser must skip.
loans.append([
"COUNTERPART", "SECURITY NAME", "ISSUER NAME", "TRADE DATE",
"PRINCIPAL BALANCE", "TOTAL COST", "VALUE",
"VALUE PLUS ACCRUED INTEREST REC / (PAY)",
])
loans.append([
"Fold Holdings, Inc.", "Fold Holdings, Inc. (Floating Rate)", "Fold Holdings, Inc.",
datetime(2025, 3, 26), 46_279_500, 46_279_500, 46_279_500, 46_494_185.46,
])
loans.append(["TOTAL - LOAN", None, None, None, 46_279_500, 46_279_500, 46_279_500, 46_494_185.46])
out = io.BytesIO()
wb.save(out)
return out.getvalue()
def test_loan_schedule_folds_into_holdings_and_nav():
name, as_of, holdings, positions, errors = _parse_schedule_xlsx(_enav_with_loan())
assert errors == []
assert as_of == date(2025, 12, 31)
# The loan groups under its issuer, which the warrant already created: no duplicate holding.
assert [h["company_name"] for h in holdings].count("Fold Holdings, Inc.") == 1
loan = next(p for p in positions if p["security_name"] == "Fold Holdings, Inc. (Floating Rate)")
assert loan["company_name"] == "Fold Holdings, Inc."
assert loan["value_cents"] == 46_494_185_46 # principal + accrued interest, in cents
assert loan["cost_cents"] == 46_279_500_00
assert loan["investment_date"] == "2025-03-26"
assert loan["shares"] is None
# NAV is the sum of every position value: securities ($3.6M) + the loan ($46.5M).
nav = sum(p["value_cents"] or 0 for p in positions)
assert nav == 1_639_942_63 + 0 + 46_494_185_46
def test_no_loan_sheet_is_a_noop():
"""A workbook with no HLD-LOANS sheet parses the securities exactly as before."""
wb = openpyxl.Workbook()
hld = wb.active
hld.title = "HLD"
hld["A1"] = datetime(2025, 12, 31)
hld.append([None])
hld.append(["SECURITY NAME", "QUANTITY", "COST BASIS - BOOK", "MARKET VALUE (BOOK)"])
hld.append(["Acme - Series A", 10, 100.0, 900.0])
out = io.BytesIO()
wb.save(out)
_, _, holdings, positions, errors = _parse_schedule_xlsx(out.getvalue())
assert errors == []
assert len(positions) == 1
assert positions[0]["value_cents"] == 900_00