[docs]def__init__(self,default_prob:np.ndarray,correlations:np.ndarray)->None:"""__init__ Args: default_prob (np.ndarray): ``(n_banks,)`` array of the bank risk-neutral default probabilities. correlations (np.ndarray): ``(n_banks, n_banks)`` array of the correlation matrix of the banks' asset returns. """self.default_prob=default_probself.correlations=correlations
[docs]defestimate(self,default_threshold:float=0.15,random_seed:int=0,n_simulated_returns:int=500_000,n_simulations:int=1_000,)->float:"""estimate Args: default_threshold (float, optional): the threshold used to calculate the total losses to total liabilities. Defaults to 0.15. random_seed (int, optional): the random seed used in Monte Carlo simulation for reproducibility. Defaults to 0. n_simulated_returns (int, optional): the number of simulations to compute the distrituion of joint defaults. Defaults to 500,000. n_simulations (int, optional): the number of simulations to compute the probability of losses. Defaults to 1,000. Returns: float: The distress insurance premium against a systemic financial distress. """# Use the class to avoid impacting the global numpy staterng=RandomState(random_seed)n_banks=len(self.default_prob)# Simulate correlated normal distributionsnorm=NormalDist()default_thresholds=np.fromiter((norm.inv_cdf(i)foriinself.default_prob),self.default_prob.dtype,count=n_banks,)R=np.linalg.cholesky(self.correlations).Tz=np.dot(rng.normal(0,1,size=(n_simulated_returns,n_banks)),R)default_dist=np.sum(z<default_thresholds,axis=1)# an array where the i-th element is the frequency of i banks jointly default# where len(frequency_of_join_defaults) is n_banks+1frequency_of_join_defaults=np.bincount(default_dist,minlength=n_banks+1)dist_joint_defaults=frequency_of_join_defaults/n_simulated_returnsloss_given_default=np.empty(shape=(n_banks,n_simulations))foriinrange(n_banks):# fmt: offlgd=np.sum(rng.triangular(0.1,0.55,1,size=(i+1,n_simulations)),axis=0)loss_given_default[i:]=lgd# fmt: on# Maximum losses are N. Divide this into N*100 intervals.# Find the probability distribution of total losses in the default caseintervals=100loss_given_default*=intervalsprob_losses=np.zeros(n_banks*intervals)foriinrange(n_banks):forjinrange(n_simulations):# Multiply losses_given_default(i,j) by intervals to find the right slot# in the prob_losses. Then we increment this by probability of i defaultsidx=math.ceil(loss_given_default[i,j])prob_losses[idx]+=dist_joint_defaults[i+1]# Convert to probabilitiesprob_losses=prob_losses/n_simulationspct_threshold=int(default_threshold*100)# Find the probability that the losses are great than 0.15 the total liabilities i.e. > 0.15*Nprob_great_losses=np.sum(prob_losses[pct_threshold*n_banks:])exp_losses=np.dot(np.array(range(pct_threshold*n_banks,intervals*n_banks)),prob_losses[pct_threshold*n_banks:],)/(100*prob_great_losses)returnexp_losses*prob_great_losses