fintech-algorithms
Using a coding agent? Give it the skill: npx skills add IslamBaraka90/Fintech-Algorithms-Library What it does →

Through-the-Cycle PD

Install and import#

bash
npm install fintech-algorithms
ts
import { throughTheCyclePd } from "fintech-algorithms/credit-risk-and-default/probability-of-default/through-the-cycle-pd";

Signature#

throughTheCyclePd(annual_obligors, annual_defaults, current_year_index, minimum_years)

Turns a multi-year history of obligor counts and default counts into annual default rates, averages them into a through-the-cycle rate, and compares one selected year against that long-run level.

Parameters#

NameTypeNotes
annual_obligorsnumber[]The obligor population observed in each year of the history, oldest to newest. Every entry must be positive and at least two years are needed.
min_length: 2 · exclusive_min: 0
annual_defaultsnumber[]The number of defaults in each year, aligned with annual_obligors. Every entry must be a whole number between zero and that year's obligor count.
min_length: 2 · integer: true · min: 0
current_year_indexnumberZero-based position of the year treated as current, whose observed rate is compared against the long-run average.
integer: true · min: 0
minimum_yearsnumberThe shortest history the caller is willing to average over. The supplied history must be at least this long.
integer: true · exclusive_min: 0

Returns#

{ annual_default_rates: number[]; through_the_cycle_pd: number; pooled_default_rate: number; current_observed_default_rate: number; cycle_gap: number; observation_years: number; state: string; reason: string }

annual_default_rates is defaults over obligors for each year. through_the_cycle_pd is the unweighted mean of those rates, while pooled_default_rate divides total defaults by total obligors. current_observed_default_rate is the selected year's rate and cycle_gap is that rate minus the long-run average. observation_years counts the years used. state is current-above-long-run, current-below-long-run, or current-equals-long-run.

Errors#

  • When either history is not an array of at least two finite numbers — throws Error
  • When annual_obligors and annual_defaults have different lengths — throws Error
  • When minimum_years is not a positive integer, or exceeds the history length — throws Error
  • When current_year_index is not an integer inside the history — throws Error
  • When any obligor count is not positive — throws Error
  • When any default count is not a whole number between zero and that year's obligors — throws Error

Complexity: time O(n), space O(n).

Worked example#

verified This is the worked example published in the article, replayed by the test suite on every run. The output cannot drift.

Input#

annual_obligors
[1000, 1050, 980, 1100, 1080, 1120]

Showing 6 of 8 elements.

annual_defaults
[8, 11, 20, 15, 7, 25]

Showing 6 of 8 elements.

current_year_index
5
minimum_years
5

Call#

throughTheCyclePd(annual_obligors, annual_defaults, current_year_index, minimum_years)

Returns#

object with 8 fields: annual_default_rates, through_the_cycle_pd, pooled_default_rate, current_observed_default_rate, cycle_gap, observation_years, state, reason

{
  "annual_default_rates": [
    0.008,
    0.010476190476,
    0.020408163265,
    0.013636363636,
    0.006481481481,
    0.022321428571
  ],
  "through_the_cycle_pd": 0.013181297202,
  "pooled_default_rate": 0.013163972286,
  "current_observed_default_rate": 0.022321428571,
  "cycle_gap": 0.00914013137,
  "observation_years": 8,
  "state": "current-above-long-run",
  "reason": "simple-average-of-annual-one-year-rates"
}

Other exports#

This module also exports logisticPdModel, probitPdModel, pointInTimePd, mertonDistanceToDefault, campbellHilscherSzilagyiDistressProbability, bharathShumwayNaiveDistanceToDefault, calculate. Every module additionally exports run as an alias of its primary function, and a meta object carrying its catalog id, domain, family, shape and article URL.

Diagrams#

Through-the-Cycle PD — article hero
Through-the-Cycle PD — evidence clock
Through-the-Cycle PD — formula anatomy
Through-the-Cycle PD — method comparison
Through-the-Cycle PD — system map
Through-the-Cycle PD — validation layers

Calculation flow#

Through-the-Cycle PD calculation flow
flowchart LR
    S1["Freeze the default definition gradepool cohort entry h"]
    S2["Validate yearly obligor and default counts"]
    S3["Calculate each annual oneyear default rate"]
    S4["Take the simple arithmetic mean across years"]
    S5["Return annual rates TTC estimate pooled comparison cur"]
    S1 --> S2
    S2 --> S3
    S3 --> S4
    S4 --> S5
    S5 --> D{"history length and defaultdefinition consistency must pass"}
    D --> O["through_the_cycle_pd + diagnostics"]
    O --> A["Audit: throughthecyclepd equals the arithmetic mean of annualdefa"]

How it works#

This page states the contract — how to call it correctly. The article explains the concept: why it works, and where it breaks.

Read the article →

References#

The rest of the Probability of Default family#