Skip to content

Understanding Test Suites & Thresholds

A test suite is a collection of compliance tests that run together to evaluate a CLO portfolio against its indenture requirements. CalcBridge provides pre-built test suites for common CLO structures and allows full customization for deal-specific requirements.

What is a Test Suite?

A test suite groups related compliance tests into a single executable unit. Each suite contains:

  • Test definitions - The specific compliance tests to run
  • Threshold configurations - Pass/fail boundaries for each test
  • Warning levels - Early warning thresholds (typically 90% of limit)
  • Execution order - Dependencies between tests
  • Reporting template - How results are formatted
flowchart TB
    subgraph Suite["Test Suite: Standard CLO"]
        direction TB
        A[Concentration Tests]
        B[Rating Tests]
        C[Coverage Tests]
        D[Quality Tests]
        E[Diversity Tests]
    end

    A --> F[Results]
    B --> F
    C --> F
    D --> F
    E --> F

    style Suite fill:#EFF6FF,stroke:#3B82F6
    style F fill:#DCFCE7,stroke:#22C55E

Test Categories

CalcBridge organizes compliance tests into five categories, each addressing different aspects of portfolio risk:

1. Concentration Tests

Concentration tests ensure the portfolio is not overly exposed to any single obligor, industry, or geographic region.

Test Name Description Typical Limit
Single Obligor Maximum exposure to any one borrower 2-10% of par
Top 5 Obligors Combined exposure to largest 5 borrowers 20-40% of par
Top 10 Obligors Combined exposure to largest 10 borrowers 40-60% of par
Single Industry Maximum exposure to any one industry 10-15% of par
Single Country Maximum exposure to any one country 15-25% of par
Second Lien Maximum second lien loan exposure 5-15% of par

How Single Obligor Limits Work

The single obligor limit is calculated as:

Obligor Concentration = (Obligor Par Value / Total Portfolio Par) * 100

If any obligor exceeds the limit, the test fails. CalcBridge evaluates every obligor in the portfolio and reports the highest concentration.

2. Rating Tests

Rating tests monitor the credit quality distribution of the portfolio.

Test Name Description Typical Limit
CCC/Caa Bucket Loans rated CCC+ or below 5-7.5% of par
WARF Weighted Average Rating Factor 2500-3000
Minimum Rating Percentage meeting minimum rating 90-95% of par
Split Ratings Loans with divergent agency ratings 10-15% of par

Rating Agency Mappings:

CalcBridge supports all major rating agencies with automatic cross-mapping:

Moody's S&P Fitch Numeric Score
Aaa AAA AAA 1
Aa1 AA+ AA+ 2
Aa2 AA AA 3
... ... ... ...
B3 B- B- 16
Caa1 CCC+ CCC+ 17
Caa2 CCC CCC 18
Caa3 CCC- CCC- 19

CCC Threshold

Loans rated Caa1/CCC+ (numeric score 17) or worse are considered "CCC bucket" loans. The composite rating uses the worst rating from available agencies.

Split Rating Handling

A split rating occurs when two or more rating agencies assign different credit ratings to the same loan. For example, a loan might be rated B1 by Moody's but BB+ by S&P -- a meaningful divergence that affects which compliance bucket the loan falls into.

Indentures specify how to resolve split ratings. CalcBridge supports all common methods via the split_rating_method parameter in the test suite configuration.

Resolution Methods

Method Description Example (Moody's B1, S&P BB+) Used In
Lower of Two Uses the worse (lower quality) rating B1 (lower quality) Most US CLOs
Higher of Two Uses the better (higher quality) rating BB+ (higher quality) Some European CLOs
Worst of All Uses the worst rating across all available agencies Worst across all available Conservative indentures
Specified Agency Uses only one agency's rating regardless of others As specified by indenture Agency-specific deals

Why Split Ratings Matter

Split ratings are not cosmetic. In the example above, B1 (Moody's) has a rating factor of 2,220 while BB+ (S&P equivalent Ba1) has a factor of 940. Using the wrong resolution method can swing a WARF calculation by hundreds of points and flip a test from pass to fail. See Rating Agency Methodologies for full factor tables.

Configuration

Set the split rating resolution method in the test suite threshold configuration:

{
    "rating": {
        "split_rating_method": "lower_of_two",
        "ccc_bucket_limit_pct": 7.5,
        "maximum_warf": 2850,
        "rating_agencies": ["moodys", "sp"],
        "warf_rating_source": "moodys"
    }
}

Implementation

import numpy as np
import pandas as pd


# Numeric scale: lower number = better credit quality
NUMERIC_SCALE = {
    "Aaa": 1, "AAA": 1,
    "Aa1": 2, "AA+": 2, "Aa2": 3, "AA": 3, "Aa3": 4, "AA-": 4,
    "A1": 5, "A+": 5, "A2": 6, "A": 6, "A3": 7, "A-": 7,
    "Baa1": 8, "BBB+": 8, "Baa2": 9, "BBB": 9, "Baa3": 10, "BBB-": 10,
    "Ba1": 11, "BB+": 11, "Ba2": 12, "BB": 12, "Ba3": 13, "BB-": 13,
    "B1": 14, "B+": 14, "B2": 15, "B": 15, "B3": 16, "B-": 16,
    "Caa1": 17, "CCC+": 17, "Caa2": 18, "CCC": 18, "Caa3": 19, "CCC-": 19,
    "Ca": 20, "CC": 20, "C": 21, "D": 21,
}


def resolve_split_rating(
    df: pd.DataFrame,
    method: str = "lower_of_two",
    specified_agency: str | None = None,
) -> pd.Series:
    """
    Resolve split ratings to a single composite rating per loan.

    Args:
        df: DataFrame with 'rating_moodys' and 'rating_sp' columns.
        method: One of 'lower_of_two', 'higher_of_two',
                'worst_of_all', or 'specified_agency'.
        specified_agency: Required when method is 'specified_agency'.
                          One of 'moodys' or 'sp'.

    Returns:
        Series of resolved ratings (using Moody's notation as the
        canonical output scale).
    """
    moodys_numeric = df["rating_moodys"].map(NUMERIC_SCALE)
    sp_numeric = df["rating_sp"].map(NUMERIC_SCALE)

    # Build reverse lookup: numeric -> Moody's rating
    numeric_to_moodys = {v: k for k, v in NUMERIC_SCALE.items() if len(k) <= 4}

    if method == "lower_of_two":
        # Higher numeric = worse credit quality = lower rating
        composite_numeric = np.maximum(
            moodys_numeric.fillna(sp_numeric),
            sp_numeric.fillna(moodys_numeric),
        )
    elif method == "higher_of_two":
        composite_numeric = np.minimum(
            moodys_numeric.fillna(sp_numeric),
            sp_numeric.fillna(moodys_numeric),
        )
    elif method == "worst_of_all":
        # Same as lower_of_two for two agencies; extensible to Fitch
        composite_numeric = np.maximum(
            moodys_numeric.fillna(sp_numeric),
            sp_numeric.fillna(moodys_numeric),
        )
    elif method == "specified_agency":
        if specified_agency == "moodys":
            return df["rating_moodys"]
        elif specified_agency == "sp":
            return df["rating_sp"]
        else:
            raise ValueError(f"Unknown agency: {specified_agency}")
    else:
        raise ValueError(f"Unknown split rating method: {method}")

    return composite_numeric.map(numeric_to_moodys)

Impact on Other Tests

The resolved composite rating feeds into multiple downstream tests:

flowchart LR
    A[Moody's<br/>Rating] --> C[Split Rating<br/>Resolution]
    B[S&P<br/>Rating] --> C
    C --> D[Composite<br/>Rating]
    D --> E[WARF<br/>Calculation]
    D --> F[CCC Bucket<br/>Test]
    D --> G[Minimum Rating<br/>Test]
    D --> H[Diversity Score<br/>Calculation]

    style C fill:#FEF3C7,stroke:#F59E0B
    style D fill:#EFF6FF,stroke:#3B82F6

Split Rating Resolution Must Happen Before All Rating Tests

The composite rating must be computed once at the start of the compliance test run and reused across all rating-dependent tests. Do not resolve split ratings independently in each test -- this wastes compute and risks inconsistency if the resolution logic is implemented differently in different test functions.

3. Coverage Tests

Coverage tests verify that the portfolio provides adequate protection for debt holders.

Test Name Description Typical Minimum
Senior OC Ratio Senior overcollateralization 115-125%
Mezzanine OC Ratio Mezzanine overcollateralization 105-115%
Junior OC Ratio Junior overcollateralization 100-110%
Senior IC Ratio Senior interest coverage 120-150%
Mezzanine IC Ratio Mezzanine interest coverage 110-130%

OC Ratio Calculation:

OC Ratio = (Collateral Principal Balance / Tranche Principal Balance) * 100

Example:
- Collateral Balance: $500,000,000
- Senior Tranche: $400,000,000
- Senior OC Ratio: ($500M / $400M) * 100 = 125%

4. Quality Tests

Quality tests assess the overall characteristics of the loan portfolio.

Test Name Description Typical Range
WAS Weighted Average Spread Minimum 3.0-4.0%
WAL Weighted Average Life Maximum 4.0-6.0 years
WARF Weighted Average Rating Factor Maximum 2500-3000
Minimum Coupon Weighted average coupon Minimum 4.0-5.0%

WAS Calculation:

# Weighted Average Spread
WAS = Sum(Loan Par * Loan Spread) / Sum(Loan Par)

# Example
Portfolio:
  - Loan A: $10M par, 3.50% spread
  - Loan B: $15M par, 4.00% spread
  - Loan C: $25M par, 3.75% spread

WAS = ($10M * 3.50% + $15M * 4.00% + $25M * 3.75%) / $50M
WAS = ($350K + $600K + $937.5K) / $50M
WAS = 3.775%

5. Diversity Tests

Diversity tests measure how well the portfolio is diversified across obligors and industries.

Test Name Description Typical Minimum
Moody's Diversity Score Effective number of independent obligors 40-60
Industry Diversity Number of distinct industries 15-20
Obligor Count Minimum number of obligors 100-150

Diversity Score Calculation:

The Moody's diversity score approximates the effective number of independent obligors using the Herfindahl-Hirschman Index (HHI):

# Simplified diversity score calculation
HHI = Sum(Obligor Weight ^ 2)
Diversity Score = 1 / HHI

# Example
Portfolio with 4 obligors:
  - Obligor A: 40% weight -> 0.16
  - Obligor B: 30% weight -> 0.09
  - Obligor C: 20% weight -> 0.04
  - Obligor D: 10% weight -> 0.01

HHI = 0.16 + 0.09 + 0.04 + 0.01 = 0.30
Diversity Score = 1 / 0.30 = 3.33

Full Moody's Calculation

The actual Moody's diversity score calculation includes industry correlation adjustments. CalcBridge implements the complete methodology when industry data is available.

Threshold Configuration

Setting Threshold Values

Thresholds are configured per test suite and can be customized for each deal:

{
  "suite_name": "Standard CLO v2",
  "description": "Standard compliance tests for US BSL CLOs",
  "thresholds": {
    "concentration": {
      "single_obligor_limit_pct": 10.0,
      "top_5_obligor_limit_pct": 40.0,
      "single_industry_limit_pct": 15.0,
      "second_lien_limit_pct": 10.0
    },
    "rating": {
      "ccc_bucket_limit_pct": 7.5,
      "maximum_warf": 2850
    },
    "coverage": {
      "minimum_senior_oc_ratio": 120.0,
      "minimum_mezzanine_oc_ratio": 108.0,
      "minimum_senior_ic_ratio": 150.0
    },
    "quality": {
      "minimum_was": 3.25,
      "maximum_wal": 5.5
    },
    "diversity": {
      "minimum_diversity_score": 45.0,
      "minimum_obligor_count": 120
    }
  },
  "warning_threshold_pct": 90.0
}

Warning Levels

CalcBridge uses a three-tier status system:

Status Description Default Trigger
Pass Test is within acceptable limits < 90% of threshold
Warning Test is approaching threshold 90-100% of threshold
Fail Test has breached threshold > 100% of threshold

Customizing Warning Levels

The default warning threshold is 90% of the limit. For critical tests, you may want to set this lower (e.g., 80%) to get earlier warnings.

Test Direction

Some tests have minimum thresholds (higher is better), while others have maximum thresholds (lower is better):

Test Type Direction Pass Condition
Concentration Maximum Current < Threshold
CCC Bucket Maximum Current < Threshold
OC Ratio Minimum Current > Threshold
IC Ratio Minimum Current > Threshold
WAS Minimum Current > Threshold
WAL Maximum Current < Threshold
Diversity Minimum Current > Threshold

Pre-Built Test Suites

CalcBridge includes several pre-built test suites:

Standard CLO Suite

The most commonly used suite for US Broadly Syndicated Loan (BSL) CLOs:

Suite: Standard CLO
Tests: 24
Categories:
  - Concentration (8 tests)
  - Rating (4 tests)
  - Coverage (6 tests)
  - Quality (3 tests)
  - Diversity (3 tests)

Middle Market CLO Suite

Adjusted thresholds for middle market loan portfolios:

Suite: Middle Market CLO
Tests: 20
Key Differences:
  - Higher single obligor limits (15% vs 10%)
  - Lower diversity requirements (30 vs 45)
  - Higher CCC bucket tolerance (10% vs 7.5%)

European CLO Suite

Compliance tests for European CLO structures:

Suite: European CLO
Tests: 22
Key Differences:
  - Currency-adjusted calculations
  - EU regulatory requirements
  - Retention rule compliance

Creating Custom Test Suites

Via API

curl -X POST https://api.calcbridge.io/api/v1/compliance/suites \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Custom Deal Suite",
    "description": "Custom thresholds for Deal ABC 2024-1",
    "base_suite": "standard_clo",
    "threshold_overrides": {
      "concentration": {
        "single_obligor_limit_pct": 8.0
      },
      "rating": {
        "ccc_bucket_limit_pct": 5.0
      }
    },
    "enabled_tests": [
      "single_obligor",
      "top_5_obligor",
      "ccc_bucket",
      "senior_oc_ratio",
      "was",
      "wal",
      "diversity_score"
    ]
  }'

Via UI

  1. Navigate to Settings > Compliance > Test Suites
  2. Click Create New Suite
  3. Select a base suite to start from
  4. Adjust thresholds as needed
  5. Enable/disable individual tests
  6. Save and assign to workbooks

Best Practices

Threshold Management Best Practices

  1. Start from indenture documents - Always verify thresholds against the actual deal documents

  2. Version control suites - Create new suite versions rather than modifying existing ones

  3. Document changes - Record why thresholds were adjusted

  4. Test conservatively - Set warning levels earlier rather than later

  5. Review quarterly - Audit suite configurations during quarterly reporting

Example: Typical CLO Compliance Tests

Here is a complete example of a typical CLO test suite configuration:

# ComplianceCalculator default thresholds
STANDARD_CLO_THRESHOLDS = {
    # Concentration Limits
    "single_obligor_limit_pct": Decimal("10.0"),
    "top_5_obligor_limit_pct": Decimal("40.0"),
    "single_industry_limit_pct": Decimal("15.0"),

    # Rating Limits
    "ccc_bucket_limit_pct": Decimal("7.5"),

    # Coverage Minimums
    "minimum_oc_ratio": Decimal("120.0"),
    "minimum_ic_ratio": Decimal("150.0"),

    # Quality Metrics
    "minimum_was": Decimal("3.0"),
    "maximum_wal": Decimal("5.0"),

    # Diversity Minimums
    "minimum_diversity_score": Decimal("40.0"),
}

Next: Running Compliance Tests