CFDL

Credit loan pool (level-pay)

Outputs below are real: the notebook runs against the credit pack's benchmark model, which CFDL validates against an independent reference. To run it yourself, see the Python SDK guide.

Open In Colab

A homogeneous level-pay loan pool with CPR prepayments, CDR defaults, loss severity, a recovery lag, a servicing strip and prepayment penalties — priced at a discount to par.

This notebook uses one of the benchmark models, which CFDL validates against an independent reference to the penny.

# On Colab, install the SDK and fetch the models this notebook reads.
# Inside a checkout both are already present and this cell does nothing.
import subprocess, sys
from pathlib import Path

REPO = "https://github.com/cfdl-dev/cfdl"


def repo_root() -> Path:
    """The checkout holding benchmarks/ and packs/, cloning it if need be.

    Searching a bounded set of ancestors means a plain `python` run outside a
    checkout fails with an explanation rather than walking to the filesystem
    root. On a hosted runtime there is no checkout to find, so fetch one.
    """
    here = Path.cwd().resolve()
    for candidate in (here, *here.parents):
        if (candidate / "Cargo.toml").exists() and (candidate / "packs").is_dir():
            return candidate

    if "google.colab" not in sys.modules:
        raise RuntimeError(
            f"No CFDL checkout found above {here}. This notebook reads a model "
            f"from benchmarks/ and pack definitions from packs/, so run it "
            f"inside a clone of {REPO}."
        )

    subprocess.run([sys.executable, "-m", "pip", "install", "-q", "cfdl-sdk[viz]"], check=True)

    # Packs and benchmark models track the engine, so take the checkout at the
    # tag matching the wheel pip just resolved. `main` runs ahead of the last
    # release and its packs may use metric ops the released engine rejects.
    import importlib
    from importlib.metadata import PackageNotFoundError, version

    importlib.invalidate_caches()
    try:
        tag = f"v{version('cfdl-sdk')}"
    except PackageNotFoundError:
        tag = None

    clone = ["git", "clone", "--depth", "1", "-q", REPO]
    target = Path("/content/cfdl")
    if not target.exists():
        pinned = tag is not None and not subprocess.run(
            clone + ["--branch", tag, str(target)]
        ).returncode
        if not pinned:
            # A dev or pre-release wheel has no matching tag; main is the best
            # available, and the notebook may fail if the two have diverged.
            print(f"warning: no {tag} tag for this SDK build; falling back to main.")
            subprocess.run(clone + [str(target)], check=True)
    return target


ROOT = repo_root()
PACKS = ROOT / "packs"

import cfdl_sdk

Compile

Compile the model directory to IR.

model_dir = ROOT / "benchmarks/credit/level_pay_pool"
model = cfdl_sdk.compile(model_dir, packs_dir=PACKS)
print("streams:", len(model.ir["streams"]))
streams: 8

Run

Run with the benchmark's configuration and apply the credit pack's domain metrics.

results = model.run(
    config=str(model_dir / "run.json"),
    pack="credit",
)
print("status:", results.status, "| warnings:", len(results.warnings))
status: ok | warnings: 0

Cash flows

The engine returns per-period signed cash flows; cashflows() gives a wide DataFrame indexed by period.

cf = results.cashflows()
print('shape:', cf.shape)
cf.head()
shape: (126, 22)
         account.asset.buyer.balance  asset.buyer.credit_loan_balance_lag_auto_a  \
period                                                                             
2026-01                 2.463766e+07                               25000000.0      
2026-02                 2.427892e+07                               25000000.0      
2026-03                 2.392374e+07                               25000000.0      
2026-04                 2.357208e+07                               25000000.0      
2026-05                 2.322393e+07                               25000000.0      

         domain.credit.balance_outstanding  domain.credit.gross_collections  \
period                                                                        
2026-01                       2.442971e+07                    457194.867943   
2026-02                       2.411241e+07                    452225.142236   
2026-03                       2.379807e+07                    447301.537920   
2026-04                       2.348667e+07                    442423.647210   
2026-05                       2.317816e+07                    437591.065867   

         domain.credit.net_collections  domain.credit.original_balance  domain.credit.pool_factor  \
period                                                                                              
2026-01                  446795.723595                      24750000.0                   0.987059   
2026-02                  441976.718401                      24750000.0                   0.974239   
2026-03                  437202.339282                      24750000.0                   0.961538   
2026-04                  432472.191964                      24750000.0                   0.948956   
2026-05                  427785.885597                      24750000.0                   0.936491   

         domain.credit.principal_collections  domain.credit.principal_paid_negated  \
period                                                                               
2026-01                        320285.175229                         -3.202852e+05   
2026-02                        317299.872656                         -6.375850e+05   
2026-03                        314341.003541                         -9.519261e+05   
2026-04                        311408.337988                         -1.263334e+06   
2026-05                        308501.648088                         -1.571836e+06   

         domain.credit.principal_paid_to_date  ...  entity.asset.buyer.net_cash_flow  \
period                                         ...                                     
2026-01                          3.202852e+05  ...                     -2.430320e+07   
2026-02                          6.375850e+05  ...                      4.419767e+05   
2026-03                          9.519261e+05  ...                      4.372023e+05   
2026-04                          1.263334e+06  ...                      4.324722e+05   
2026-05                          1.571836e+06  ...                      4.277859e+05   

         model.net_cash_flow  stream.credit.loan.defaults.auto_a  \
period                                                             
2026-01        -2.430320e+07                       -42053.563818   
2026-02         4.419767e+05                       -41444.058407   
2026-03         4.372023e+05                       -40840.599975   
2026-04         4.324722e+05                       -40243.133886   
2026-05         4.277859e+05                       -39651.605981   

         stream.credit.loan.interest.auto_a  stream.credit.loan.penalty.auto_a  \
period                                                                           
2026-01                       135188.876529                        1720.816184   
2026-02                       133229.509847                        1695.759733   
2026-03                       131289.582287                        1670.952092   
2026-04                       129368.918209                        1646.391013   
2026-05                       127467.343513                        1622.074267   

         stream.credit.loan.prepay.auto_a  stream.credit.loan.recoveries.auto_a  \
period                                                                            
2026-01                     172081.618419                                   0.0   
2026-02                     169575.973277                                   0.0   
2026-03                     167095.209193                                   0.0   
2026-04                     164639.101296                                   0.0   
2026-05                     162207.426682                                   0.0   

         stream.credit.loan.sched_principal.auto_a  stream.credit.loan.servicing.auto_a  \
period                                                                                    
2026-01                            148203.556810                          -10399.144348   
2026-02                            147723.899379                          -10248.423834   
2026-03                            147245.794348                          -10099.198637   
2026-04                            146769.236692                           -9951.455247   
2026-05                            146294.221405                           -9805.180270   

         stream.credit.purchase.price.auto_a  
period                                        
2026-01                          -24750000.0  
2026-02                                  0.0  
2026-03                                  0.0  
2026-04                                  0.0  
2026-05                                  0.0  

[5 rows x 22 columns]
# Requires the [viz] extra (pip install cfdl-sdk[viz]).
results.plot.cumulative()
<Axes: xlabel='period', ylabel='cumulative amount'>

Chart produced by the preceding cell

Metrics

Core metrics (NPV/IRR/MOIC/...) plus the pack's domain metrics, with their source labeled.

results.metrics_frame()
                                     metric         value currency         source
0                 domain.credit.collections  3.072748e+07      USD  domain:credit
1        domain.credit.collections_multiple  1.241514e+00           domain:credit
2                    domain.credit.interest  6.499895e+06      USD  domain:credit
3                   domain.credit.penalties  8.220768e+04      USD  domain:credit
4                   domain.credit.principal  2.297806e+07      USD  domain:credit
5                    domain.credit.purchase  2.475000e+07      USD  domain:credit
6                  domain.credit.recoveries  1.167320e+06      USD  domain:credit
7                   domain.credit.servicing  4.999919e+05      USD  domain:credit
8                   domain.credit.wal_years  4.056967e+00           domain:credit
9                  entity.asset.buyer.total  5.477491e+06      USD           core
10                                model.irr  5.630900e-02                    core
11                               model.moic  1.225381e+00                    core
12                                model.npv -2.959752e+05      USD           core
13                    model.payback_periods  8.000000e+01                    core
14                      model.payback_years  6.750000e+00                    core
15                              model.total  5.477491e+06      USD           core
16                          model.wal_years  3.843940e+00                    core
17                 run.annual_discount_rate  6.000000e-02                    core
18                     run.periods_per_year  1.200000e+01                    core
19  stream.credit.loan.defaults.auto_a.t... -2.021940e+06      USD           core
20  stream.credit.loan.interest.auto_a.t...  6.499895e+06      USD           core
21  stream.credit.loan.penalty.auto_a.total  8.220768e+04      USD           core
22   stream.credit.loan.prepay.auto_a.total  8.220768e+06      USD           core
23  stream.credit.loan.recoveries.auto_a...  1.167320e+06      USD           core
24  stream.credit.loan.sched_principal.a...  1.475729e+07      USD           core
25  stream.credit.loan.servicing.auto_a.... -4.999919e+05      USD           core
26  stream.credit.purchase.price.auto_a.... -2.475000e+07      USD           core

What-if

Show the collections multiple and the principal-weighted WAL.

m = results.metrics()
print("collections multiple:", round(m["domain.credit.collections_multiple"], 4))
print("WAL (years):", round(m["domain.credit.wal_years"], 3))
collections multiple: 1.2415
WAL (years): 4.057