Trustee Tapes¶
Trustee tapes are periodic statement files from deal trustees containing loan-level portfolio data. They are the authoritative source of record for a CLO's portfolio composition and serve as the basis for reconciliation against the manager's internal books.
Upload Process¶
Supported Formats¶
Trustee tapes are uploaded as Excel files. The system:
- Validates the file format and structure
- Parses using trustee-specific normalization rules
- Extracts loan-level data (CUSIP, par, price, ratings, spread)
- Stores normalized data for reconciliation
File Requirements¶
| Requirement | Detail |
|---|---|
| Format | Excel (.xlsx) |
| Max Size | 100MB |
| Required Fields | CUSIP or ISIN, par value |
| Statement Date | Must be provided at upload |
Trustee-Specific Parsing¶
Each trustee has distinct file layouts. The parser normalizes all formats into a common loan schema using the alias profile system.
US Bank¶
Standard CLO trustee report format with: - Loan identifier columns (CUSIP, LoanX ID) - Financial data (par, price, spread, rates) - Rating columns (Moody's, S&P, Fitch) - Borrower and industry classification
Deutsche Bank¶
European trustee format with: - ISIN-based identification - Spread and rate conventions - Industry and country mappings
Wilmington Trust¶
US trustee format similar to US Bank with: - Customized column naming - Additional reference fields
BNY Mellon¶
US trustee format with: - "Current Par" naming convention for par value - Abbreviated rating column headers ("Mdy", "S&P") - Borrower-centric naming
European Trustee Support
CalcBridge is expanding trustee support to include BNP Paribas and HSBC for European CLO coverage. See Regional Differences for details on European trustee format variations.
Loan Data Model¶
Each parsed loan includes:
| Field | Description |
|---|---|
cusip | CUSIP identifier |
isin | ISIN identifier (European CLOs) |
loanx_id | LoanX identifier |
borrower_name | Borrower name |
par_value | Par (face) value |
price | Current price |
rating_moodys | Moody's rating |
rating_sp | S&P rating |
rating_fitch | Fitch rating |
spread | Spread (basis points) |
base_rate | Base rate (e.g., SOFR) |
all_in_rate | Total interest rate |
maturity_date | Loan maturity date |
industry | Industry classification |
country | Country of domicile |
days_past_due | Days past due |
Data Field Reference¶
This section provides a comprehensive reference for every field that appears in trustee tapes, grouped by the compliance tests they feed into. Use this reference when building parsers, configuring alias profiles, or debugging reconciliation discrepancies.
Loan Identification Fields¶
These fields uniquely identify a loan position within the portfolio.
| Field | Description | Example | Used In |
|---|---|---|---|
| CUSIP | 9-character US identifier (issuer + issue + check digit) | 12345ABC9 | All tests -- primary key for US CLOs |
| ISIN | 12-character international identifier (country + CUSIP/NSIN + check) | US12345ABC90 | All tests -- primary key for European CLOs |
| LoanX ID | LoanX/Markit unique loan identifier | LX123456 | Cross-referencing with market data |
| Loan Name | Obligor and facility description | Acme Corp TL-B | Concentration tests, reporting |
| Obligor | Legal borrower entity name | Acme Corporation | Single obligor concentration test |
| Obligor ID | Unique identifier for the borrower entity | OBL-12345 | Obligor deduplication across facilities |
| Industry | Moody's 33-industry or S&P GICS code | Banking & Finance | Industry concentration test |
| Country | Domicile of the obligor | US or DE | Geographic concentration test |
| Facility Type | Type of loan facility | TL-B | Eligibility criteria, second lien test |
| Seniority | Lien position (first lien, second lien) | First Lien | Second lien concentration test |
| Agent Bank | Administrative agent for the loan | JPMorgan Chase | Reference data reconciliation |
CUSIP vs ISIN
US CLOs use CUSIP as the primary identifier. European CLOs use ISIN. Some European tapes include both, but many include only ISIN. CalcBridge's loan matching must support either identifier, falling back from CUSIP to ISIN when CUSIP is not available.
# Loan matching priority
def match_loan(tape_loan, internal_loan):
if tape_loan.cusip and internal_loan.cusip:
return tape_loan.cusip == internal_loan.cusip
elif tape_loan.isin and internal_loan.isin:
return tape_loan.isin == internal_loan.isin
elif tape_loan.loanx_id and internal_loan.loanx_id:
return tape_loan.loanx_id == internal_loan.loanx_id
else:
# Fuzzy match on name + maturity as last resort
return fuzzy_match(tape_loan, internal_loan)
Financial Fields (OC/IC Tests)¶
These fields feed directly into overcollateralisation and interest coverage calculations. They are the most reconciliation-sensitive fields -- even small discrepancies can affect compliance test results.
| Field | Description | Example | Used In |
|---|---|---|---|
| Par Value | Face value of the position | $5,000,000 | OC numerator, concentration denominators |
| Current Balance | Outstanding principal (may differ from par if amortising) | $4,800,000 | Amortisation tracking, OC adjustments |
| Purchase Price | Price paid as percentage of par | 98.5 | Discount obligation tracking |
| Market Price | Current secondary market trading price | 95.25 | CCC haircut calculation, mark-to-market |
| Spread | Credit spread over base rate (in basis points) | SOFR+350 | WAS test, IC numerator |
| Coupon | Total interest rate (base rate + spread) | 8.85% | IC test numerator |
| Accrued Interest | Unpaid interest since last payment date | $12,500 | IC numerator component |
| Principal Payments | Scheduled and unscheduled principal received | $200,000 | Cash flow tracking, amortisation |
| Interest Payments | Interest received since last period | $110,625 | IC actual vs projected |
| Recovery Rate | Estimated recovery for defaulted assets | 50% | Defaulted asset valuation in OC |
| Market Value | Par value multiplied by market price | $4,762,500 | Mark-to-market reporting |
| Floor | Interest rate floor on the loan | 1.00% | All-in-rate calculation |
| PIK Amount | Payment-in-kind interest added to principal | $25,000 | OC tracking (PIK increases par) |
OC Test Calculation
The OC test uses par value (not market value) in its standard formulation:
OC Ratio = Adjusted Collateral Balance / Tranche Outstanding Balance
Adjusted Collateral Balance =
Sum of all loan par values
- Defaulted asset haircuts
- CCC excess haircuts (par above the CCC bucket limit, valued at market)
- Discount obligations (if purchase price < threshold)
+ Principal cash
Discrepancies in par value directly affect the OC numerator. A $500K par value difference on a $500M portfolio is only 0.1%, but if the OC cushion is 0.5%, that discrepancy consumes 20% of the headroom.
Credit Fields (WARF/CCC Tests)¶
These fields determine the credit quality profile of the portfolio and feed into rating-dependent compliance tests.
| Field | Description | Example | Used In |
|---|---|---|---|
| Rating (Moody's) | Moody's corporate family rating | B1 | WARF (US deals), CCC bucket |
| Rating (S&P) | S&P issuer credit rating | B+ | WARF (EU deals), CCC bucket |
| Rating (Fitch) | Fitch issuer default rating | B+ | WARF (EU deals), CCC bucket |
| Rating Date | Date of most recent rating action | 2024-03-15 | Stale rating detection |
| Rating Watch | Watchlist status (positive, negative, developing) | Negative | Early warning, potential WARF impact |
| Rating Outlook | Medium-term rating outlook | Stable | Trend analysis |
| Default Status | Whether the loan is in default | N | OC exclusion, recovery rate application |
| Default Date | Date default was declared | 2024-01-10 | Time-in-default tracking |
| Composite Rating | Derived rating using worst-of or average methodology | B2 | WARF input (deal-specific methodology) |
Composite Rating Methodology Varies
Different indentures specify different composite rating methodologies:
- Worst-of: Use the lowest rating from available agencies
- Middle-of-three: If all three agencies rate, use the middle rating
- Average: Average the numeric scores of available ratings
- Moody's-only: Only use the Moody's rating (common in US)
- S&P-primary: Use S&P, fall back to Fitch (common in EU)
CalcBridge must implement all methodologies and apply the correct one per deal. Using the wrong composite methodology produces incorrect WARF calculations.
# Composite rating calculation
def calculate_composite_rating(
moodys: str | None,
sp: str | None,
fitch: str | None,
methodology: str,
) -> str:
ratings = [r for r in [moodys, sp, fitch] if r is not None]
if methodology == "worst_of":
return max(ratings, key=rating_to_numeric)
elif methodology == "middle_of_three" and len(ratings) == 3:
return sorted(ratings, key=rating_to_numeric)[1]
elif methodology == "moodys_primary":
return moodys or sp or fitch
elif methodology == "sp_primary":
return sp or fitch or moodys
Maturity Fields (WAL Test)¶
These fields determine the time profile of the portfolio and feed into the Weighted Average Life calculation.
| Field | Description | Example | Used In |
|---|---|---|---|
| Maturity Date | Legal final maturity of the loan | 2029-06-15 | WAL calculation |
| Expected Life | Expected repayment date (prepayment-adjusted) | 2027-12-31 | WAL (if prepay-adjusted methodology) |
| Amortisation Schedule | Scheduled principal repayment pattern | 1% quarterly | Cash flow modelling, amortisation tracking |
| Call Protection | Non-call period end date | 2025-06-15 | Refinancing risk assessment |
| Next Payment Date | Date of next scheduled payment | 2024-04-15 | Cash flow projection |
| Payment Frequency | How often payments are made | Quarterly | Cash flow modelling |
| Original Term | Original term of the loan in months | 84 | Vintage analysis |
WAL Calculation
The Weighted Average Life is calculated as:
WAL = Sum(Principal Payment(i) * Time to Payment(i)) / Total Principal
Where:
- Principal Payment(i) = expected principal received at time i
- Time to Payment(i) = years from measurement date to payment date
The WAL test has a maximum threshold (e.g., 5.5 years). Longer WAL means more duration risk. During amortisation, WAL typically decreases as near-term maturities roll off.
Concentration Fields¶
These fields are used to compute concentration metrics across obligors, industries, and geographies.
| Field | Description | Example | Used In |
|---|---|---|---|
| Obligor Group | Parent entity grouping (for related borrowers) | Acme Holdings | Single obligor test (aggregated) |
| Industry Code | Numeric or alphanumeric industry classification | 3 or 40301020 | Industry concentration test |
| Industry Description | Human-readable industry name | Banking, Finance, Insurance & Real Estate | Reporting |
| Sub-Industry | More granular industry classification | Commercial Banking | Detailed concentration analysis |
| Country Code | ISO 3166-1 alpha-2 country code | US, DE, GB | Geographic concentration test |
| Region | Geographic region grouping | North America | Regional concentration test |
| Currency | Currency denomination of the loan | USD, EUR | Currency concentration (European CLOs) |
| Lien Position | First lien, second lien, unsecured | 1st Lien | Second lien and unsecured limits |
| Covenant Status | Covenant-lite or covenant-heavy | Cov-Lite | Covenant-lite concentration limit |
Settlement and Trade Fields¶
These fields track the settlement status of positions and are important for reconciliation timing differences.
| Field | Description | Example | Used In |
|---|---|---|---|
| Trade Date | Date the position was traded | 2024-03-01 | Trade reconciliation |
| Settlement Date | Date the trade settles (T+7 typical for loans) | 2024-03-08 | Settlement reconciliation |
| Settlement Status | Whether the trade has settled | Settled | Position reconciliation |
| Settle Amount | Dollar amount exchanged at settlement | $4,925,000 | Cash reconciliation |
| Delayed Draw Amount | Unfunded commitment on a delayed draw term loan | $1,000,000 | Unfunded exposure tracking |
| Revolver Commitment | Total commitment on a revolving facility | $2,000,000 | Unfunded exposure tracking |
| Revolver Funded | Funded portion of a revolving facility | $500,000 | OC, concentration (funded portion) |
Settlement Timing Discrepancies
Loans settle on a T+7 to T+10 basis (compared to T+1 for bonds). This creates a window where the manager's internal books show a position that the trustee tape does not yet reflect, or vice versa. CalcBridge's reconciliation engine includes timing rules to detect and classify these as expected discrepancies rather than errors.
Trustee Format Variations¶
Field names vary significantly across trustees. This is the core reason CalcBridge's alias profile system exists. The following table maps common concepts to their actual column names in each trustee's file format.
Identifier Fields¶
| Concept | BNY Mellon | US Bank | Deutsche Bank | Wilmington |
|---|---|---|---|---|
| CUSIP | CUSIP | CUSIP | CUSIP | CUSIP |
| ISIN | ISIN | -- | ISIN | -- |
| Obligor name | Borrower | Obligor Name | Issuer | Borrower Name |
| LoanX ID | Markit ID | LoanX ID | LX ID | LoanX |
| Facility name | Facility | Facility Name | Tranche | Facility |
Financial Fields¶
| Concept | BNY Mellon | US Bank | Deutsche Bank | Wilmington |
|---|---|---|---|---|
| Par value | Current Par | Par Amount | Outstanding Balance | Par Value |
| Purchase price | Purch Price | Purchase Price | Buy Price | Cost |
| Market price | Bid Price | Market Price | Mark | Market Price |
| Spread | Spread | Current Spread | Margin | Spread (bps) |
| Base rate | Index | Base Rate | Reference Rate | Index Rate |
| Coupon | Rate | All-In Rate | Coupon | Current Rate |
| Floor | Floor | LIBOR Floor | Floor Rate | Floor |
Rating Fields¶
| Concept | BNY Mellon | US Bank | Deutsche Bank | Wilmington |
|---|---|---|---|---|
| Moody's rating | Mdy | Moody's | Moody Rating | Moodys |
| S&P rating | S&P | S&P Rating | SP Rating | S&P |
| Fitch rating | Fitch | Fitch Rating | Fitch Rating | Fitch |
Classification Fields¶
| Concept | BNY Mellon | US Bank | Deutsche Bank | Wilmington |
|---|---|---|---|---|
| Industry | Industry | Moody Industry | Industry Code | Industry |
| Country | Country | Country | Domicile | Country |
| Seniority | Lien | Seniority | Ranking | Lien Position |
| Facility type | Type | Loan Type | Instrument Type | Facility Type |
Date Fields¶
| Concept | BNY Mellon | US Bank | Deutsche Bank | Wilmington |
|---|---|---|---|---|
| Maturity date | Maturity | Maturity Date | Final Maturity | Maturity Date |
| Trade date | Trade Dt | Trade Date | Trade Date | Trade Date |
| Settlement date | Settle Dt | Settle Date | Value Date | Settlement Date |
| Next payment | Next Pmt | Next Payment Date | Next Coupon Date | Next Payment |
Alias Profile Configuration
When adding support for a new trustee, create an alias profile JSON in calcbridge/config/clo_aliases/ that maps trustee-specific column names to CalcBridge's normalized schema. See Alias Profiles for the configuration format and examples.
European Trustee Considerations¶
European trustee tapes have several characteristics that differ from US tapes and require specific parser handling.
ISIN as Primary Identifier¶
European tapes may not include a CUSIP at all. The ISIN is the primary identifier, and CalcBridge's matching logic must handle this.
Multi-Currency Positions¶
Some European CLOs hold GBP-denominated loans within a EUR-denominated deal. The trustee tape may include a currency column, and par values must be converted to the deal currency for compliance calculations.
Euribor Conventions¶
European tapes reference Euribor rather than SOFR. The base rate field may contain values like EURIBOR 3M or EUR3M, which must be normalised.
Different Date Formats¶
European tapes sometimes use DD/MM/YYYY rather than MM/DD/YYYY. The parser must detect and handle both conventions.
Date Format Ambiguity
A date like 01/03/2024 is January 3rd in US format but March 1st in European format. CalcBridge's parser uses heuristics (values > 12 in the first position indicate DD/MM) and trustee-specific configuration to resolve ambiguity. Always verify date parsing when onboarding a new European trustee.
Data Quality Indicators¶
Trustee tapes sometimes contain data quality issues that CalcBridge must handle gracefully.
| Issue | Detection | CalcBridge Handling |
|---|---|---|
| Missing ratings | Rating field is blank or "NR" | Flag as unrated; apply worst-case rating factor for WARF |
| Stale prices | Market price date is > 30 days old | Flag in reconciliation; use last available price |
| Duplicate CUSIPs | Same CUSIP appears multiple times | Aggregate par values; may indicate multiple facilities |
| Negative par | Par value is negative | Flag as error; may indicate a short position or data issue |
| Zero spread | Spread is 0 or blank | May be a fixed-rate bond; check facility type |
| Missing maturity | Maturity date is blank | Flag as error; WAL cannot be calculated without maturity |
| Future trade date | Trade date is after the statement date | Flag as timing issue; position may not have settled |
Parser Architecture¶
CalcBridge's trustee tape parser follows a pipeline architecture:
flowchart LR
A[Raw Excel<br/>File] --> B[Trustee<br/>Detection]
B --> C[Alias Profile<br/>Loading]
C --> D[Column<br/>Mapping]
D --> E[Type<br/>Conversion]
E --> F[Validation<br/>Rules]
F --> G[Normalized<br/>Loan Schema]
style B fill:#DBEAFE,stroke:#3B82F6
style D fill:#DBEAFE,stroke:#3B82F6
style G fill:#DCFCE7,stroke:#22C55E - Trustee Detection -- Identify the trustee from file structure or user selection
- Alias Profile Loading -- Load the appropriate alias profile for the detected trustee
- Column Mapping -- Map trustee-specific column names to normalized field names
- Type Conversion -- Convert string values to appropriate types (dates, decimals, ratings)
- Validation Rules -- Apply data quality checks and flag issues
- Normalized Output -- Produce a consistent loan schema for reconciliation
# Parser pipeline example
class TrusteeTapeParser:
def parse(
self,
file_path: str,
trustee: str,
statement_date: date,
) -> ParseResult:
# Load raw data
df = pd.read_excel(file_path)
# Load alias profile
profile = self.alias_registry.get_profile(trustee)
# Map columns
df = df.rename(columns=profile.mappings)
# Convert types
df = self.type_converter.convert(df, profile.type_hints)
# Validate
issues = self.validator.validate(df, statement_date)
# Build normalized loan objects
loans = [NormalizedLoan(**row) for row in df.to_dict("records")]
return ParseResult(loans=loans, issues=issues)
Related Documentation¶
- Discrepancies - Reconciliation issue management
- Reconciliation API - REST endpoints
- Alias Profiles - Header normalization configuration
- Regional Differences - European vs US trustee format differences