library(statmod) lossdistrib <- function(p){ ## basic recursive algorithm of Andersen, Sidenius and Basu n <- length(p) q <- rep(0, (n+1)) q[1] <- 1 for(i in 1:n){ q[-(n+1)] <- p[i]*q[-(n+1)]+(1-p[i])*q[-1] q[1] <- (1-p[i])*q[1] } return(q) } lossdistrib.fft <- function(p){ ## computes loss distribution using the fft ## complexity is of order O(n*m)+O(m*log(m)) ## where m is the size of the grid and n the number of probabilities. ## this is slower than the recursive algorithm theta <- 2*pi*1i*(0:n)/(n+1) Phi <- 1 - p + p%o%exp(theta) v <- apply(Phi, 2, prod) return(1/(n+1)*Re(fft(v))) } lossdistrib2 <- function(p, w, S, lu){ #recursive algorithm with first order correction #p vector of default probabilities #w vector of weigths #S vector of severities #lu loss unit n <- length(p) N <- 1/lu + 1 eps <- 1e-10 q <- rep(0, N+eps) q[1] <- 1 for(i in 1:n){ d <- S[i] * w[i] / lu d1 <- floor(d) d2 <- ceiling(d) p1 <- p[i]*(d2-d) p2 <- p[i] - p1 q1 <- c(rep(0,d1), p1*q[1:(N-d1)]) q2 <- c(rep(0,d2), p2*q[1:(N-d2)]) q <- q1 + q2 + (1-p[i])*q } q[length(q)] <- q[length(q)]+1-sum(q) return(q) } recovdist <- function(dp, pp, w, S, lu){ ## computes the recovery distribution for a sum of independent variables ## R=\sum_{i=1}^n X_i ## where X_i = 0 w.p 1-dp[i]-pp[i] ## = w[i]*(1-S[i]) w.p dp[i] ## = w[i] w.p pp[i] ## each non zero value v is interpolated on the grid as ## two values floor(v/lu) and ceiling(v/lu) so that ## X_i has four non zero values n <- length(dp) N <- 1/lu + 1 q <- rep(0, ceiling(N)) q[1] <- 1 for(i in 1:n){ d1 <- w[i]*(1-S[i])/lu d1l <- floor(d1) d1u <- ceiling(d1) d2 <- w[i] / lu d2l <- floor(d2) d2u <- ceiling(d2) dp1 <- dp[i] * (d1u-d1) dp2 <- dp[i] - dp1 pp1 <- pp[i] * (d2u - d2) pp2 <- pp[i] - pp1 cat(dp1, dp2, pp1, pp2, "\n") q1 <- c(rep(0, d1l), dp1 * q[1:(N-d1l)]) q2 <- c(rep(0, d1u), dp2 * q[1:(N-d1u)]) q3 <- c(rep(0, d2l), pp1 * q[1:(N-d2l)]) q4 <- c(rep(0, d2u), pp2 *q[1:(N-d2u)]) q <- q1+q2+q3+q4+(1-dp[i]-pp[i])*q } return(q) } lossdistrib.joint <- function(p, w, S, lu){ ## recursive algorithm with first order correction ## to compute the joint probability distribution of the loss and recovery ## p vector of default probabilities ## w vector of issuer weights ## S vector of severities ## lu loss unit n <- length(p) N <- ceiling(1/lu + 1) q <- matrix(0, N, N) q[1,1] <- 1 for(k in 1:n){ x <- S[k] * w[k]/lu y <- (1-S[k]) * w[k]/lu i <- floor(x) j <- floor(y) weights <- c((i+1-x)*(j+1-y), (i+1-x)*(y-j), (x-i)*(y-j), (j+1-y)*(x-i)) psplit <- p[k] * weights qtemp <- matrix(0, N, N) qtemp[(i+1):N,(j+1):N] <- qtemp[(i+1):N,(j+1):N] + psplit[1] * q[1:(N-i),1:(N-j)] qtemp[(i+1):N,(j+2):N] <- qtemp[(i+1):N,(j+2):N] + psplit[2] * q[1:(N-i), 1:(N-j-1)] qtemp[(i+2):N,(j+2):N] <- qtemp[(i+2):N,(j+2):N] + psplit[3] * q[1:(N-i-1), 1:(N-j-1)] qtemp[(i+2):N, (j+1):N] <- qtemp[(i+2):N, (j+1):N] + psplit[4] * q[1:(N-i-1), 1:(N-j)] q <- qtemp + (1-p[k])*q } q[length(q)] <- q[length(q)]+1-sum(q) return(q) } lossdistribprepay.joint <- function(dp, pp, w, S, lu){ ## recursive algorithm with first order correction ## to compute the joint probability distribition of the loss and recovery ## dp vector of default probabilities ## pp vector of prepay probabilities ## w vector of issuer weights ## S vector of severities ## lu loss unit n <- length(dp) N <- ceiling(1/lu + 1) q <- matrix(0, N, N) q[1,1] <- 1 for(k in 1:n){ x <- S[k] * w[k]/lu y1 <- (1-S[k]) * w[k]/lu y2 <- w[k]/lu i <- floor(x) j1 <- floor(y1) j2 <- floor(y2) weights <- c((i+1-x)*(j1+1-y1), (i+1-x)*(y1-j1), (x-i)*(y1-j1), (j1+1-y1)*(x-i)) dpsplit <- dp[k] * weights ppsplit <- pp[k] * c(j2+1-y2, y2-j2) qtemp <- matrix(0, N, N) qtemp[(i+1):N,(j1+1):N] <- qtemp[(i+1):N,(j1+1):N] + dpsplit[1] * q[1:(N-i),1:(N-j1)] qtemp[(i+1):N,(j1+2):N] <- qtemp[(i+1):N,(j1+2):N] + dpsplit[2] * q[1:(N-i), 1:(N-j1-1)] qtemp[(i+2):N,(j1+2):N] <- qtemp[(i+2):N,(j1+2):N] + dpsplit[3] * q[1:(N-i-1), 1:(N-j1-1)] qtemp[(i+2):N,(j1+1):N] <- qtemp[(i+2):N, (j1+1):N] + dpsplit[4] * q[1:(N-i-1), 1:(N-j1)] qtemp[, (j2+1):N] <- qtemp[,(j2+1):N]+ppsplit[1]*q[,1:(N-j2)] qtemp[, (j2+2):N] <- qtemp[,(j2+2):N]+ppsplit[2]*q[,1:(N-j2-1)] q <- qtemp + (1-pp[k]-dp[k]) * q } q[length(q)] <- q[length(q)] + 1 - sum(q) return(q) } lossdistribC <- function(p, w, S, lu){ ## C version of lossdistrib2, roughly 50 times faster dyn.load("lossdistrib.dll") .C("lossdistrib", as.double(p), as.integer(length(p)), as.double(w), as.double(S), as.double(lu), q = double(1/lu+1))$q } recovdistC <- function(dp, pp, w, S, lu){ ## C version of recovdist dyn.load("lossdistrib.dll") .C("recovdist", as.double(dp), as.double(pp), as.integer(length(dp)), as.double(w), as.double(S), as.double(lu), q = double(ceiling(1/lu+1)))$q } lossdistribC.joint <- function(p, w, S, lu){ ## C version of lossdistrib.joint, roughly 20 times faster N <- ceiling(1/lu+1) dyn.load("lossdistrib.dll") .C("lossdistrib_joint", as.double(p), as.integer(length(p)), as.double(w), as.double(S), as.double(lu), q = matrix(0, N, N))$q } lossdistribprepayC.joint <- function(dp, pp, w, S, lu){ ## C version of lossdistribprepay.joint dyn.load("lossdistrib.dll") N <- ceiling(1/lu+1) .C("lossdistrib_prepay_joint", as.double(dp), as.double(pp), as.integer(length(dp)), as.double(w), as.double(S), as.double(lu), q=matrix(0, N, N))$q } lossrecovdist <- function(defaultprob, prepayprob, w, S, lu, useC=TRUE){ if(prepayprob==0){ if(useC){ L <- lossdistribC(defaultprob, w, S, lu) R <- lossdistribC(defaultprob, w, 1-S, lu) }else{ L <- lossdistrib2(defaultprob, w, S, lu) R <- lossdistrib2(defaultprob, w, 1-S, lu) } }else{ L <- lossdistribC(defaultprob, w, S, lu) R <- recovdistC(defaultprob, prepayprob, w, S, lu) } return(list(L=L, R=R)) } lossrecovdist.term <- function(defaultprob, prepayprob, S, lu, useC=TRUE){ ## computes the loss and recovery distribution over time L <- array(0, dim=c(1/lu+1, ncol(defaultprob))) R <- array(0, dim=c(1/lu+1, ncol(defaultprob))) if(prepayprob==0){ for(t in 1:ncol(defaultprob)){ temp <- lossrecovdist(defaultprob[,t], 0, S[,t], lu, useC) L[,t] <- temp$L R[,t] <- temp$R } } return(list(L=L, R=R)) } shockprob <- function(p, rho, Z, log.p=F){ ## computes the shocked default probability as a function of the copula factor pnorm((qnorm(p)-sqrt(rho)*Z)/sqrt(1-rho), log.p=log.p) } shockseverity <- function(S, Stilde=1, Z, rho, p){ #computes the severity as a function of the copula factor Z Stilde * exp( shockprob(S/Stilde*p, rho, Z, TRUE) - shockprob(p, rho, Z, TRUE)) } dshockprob <- function(p,rho,Z){ dnorm((qnorm(p)-sqrt(rho)*Z)/sqrt(1-rho))*dqnorm(p)/sqrt(1-rho) } dqnorm <- function(x){ 1/dnorm(qnorm(x)) } fit.prob <- function(Z, w, rho, p0){ #if the weights are not perfectly gaussian, find the probability p such # E_w(shockprob(p, rho, Z)) = p0 eps <- 1e-12 dp <- (crossprod(shockprob(p0,rho,Z),w)-p0)/crossprod(dshockprob(p0,rho,Z),w) p <- p0 while(abs(dp) > eps){ dp <- (crossprod(shockprob(p,rho,Z),w)-p0)/crossprod(dshockprob(p,rho,Z),w) phi <- 1 while ((p-phi*dp)<0 || (p-phi*dp)>1){ phi <- 0.8*phi } p <- p - phi*dp } return(p) } stochasticrecov <- function(R, Rtilde, Z, w, rho, porig, pmod){ ptilde <- fit.prob(Z, w, rho, (1-R)/(1-Rtilde) * porig) return(abs(1-(1-Rtilde) * exp(shockprob(ptilde, rho, Z, TRUE) - shockprob(pmod, rho, Z, TRUE)))) } pos <- function(x){ pmax(x, 0) } trancheloss <- function(L, K1, K2){ pos(L - K1) - pos(L - K2) } trancherecov <- function(R, K1, K2){ pos(R - 1 + K2) - pos(R - 1 +K1) } tranche.cl <- function(L, R, cs, K1, K2){ ## computes the couponleg of a tranche if(K1==K2){ return( 0 ) }else{ n <- nrow(L) support <- seq(0, 1, length=n) accrued <- rep(0, length(cs$df)) for(t in 1:length(cs$df)){ accrued[t] <- (K2 - K1 - crossprod(trancheloss(support, K1, K2), L[,t]) + crossprod(trancherecov(support, K1, K2), R[,t])) } return( crossprod(accrued * cs$coupons, cs$df)/(K2 - K1) ) } } tranche.pl <- function(L, R, cs, K1, K2){ ## computes the protection leg of a tranche if(K1==K2){ return(0) }else{ n <- nrow(L) support <- seq(0, 1, length=n) cf <- rep(0, length(cs$df)) for(t in 1:length(cs$df)){ cf[t] <- K2 - K1 - crossprod(trancheloss(support, K1, K2), L[,t]) } cf <- c(1, cf / (K2-K1)) return( crossprod(diff(cf), cs$df)) } } tranche.bp <- function(L, R, cs, K1, K2){ return( 100 * ( 1 + tranche.pl(L, R, cs, K1, K2) + tranche.cl(L, R, cs, K1, K2)) ) } adjust.attachments <- function(K, losstodate, factor){ return( pmin(pmax((K-losstodate)/factor, 0),1) ) } tranche.bpvec <- function(K, L, R, cs){ r <- rep(0, length(K)-1) for(i in 1:(length(K)-1)){ r[i] <- tranche.bp(L, R, cs, K[i], K[i+1]) } return( r ) } BClossdist <- function(SurvProb, recov, rho, lu, n.int=100){ quadrature <- gauss.quad.prob(n.int, "normal") Z <- quadrature$nodes w <- quadrature$weights n <- as.integer(1/lu+1) LZ <- matrix(0, n, n.int) RZ <- matrix(0, n, n.int) L <- matrix(0, n, ncol(SurvProb)) R <- matrix(0, n, ncol(SurvProb)) for(t in 1:ncol(SurvProb)){ g <- 1 - SurvProb[, t] for(i in 1:length(Z)){ g.shocked <- shockprob(g, rho, Z[i]) S.shocked <- shockseverity(1-recov, 1, Z[i], rho, g) temp <- lossrecovdist(g.shocked, 0, S.shocked, lu) LZ[,i] <- temp$L RZ[,i] <- temp$R } L[,t] <- LZ%*%w R[,t] <- RZ%*%w } list(L=L, R=R) } BClossdistC <- function(SurvProb, recov, rho, lu, n.int=100){ dyn.load("lossdistrib.dll") quadrature <- gauss.quad.prob(n.int, "normal") Z <- quadrature$nodes w <- quadrature$weights N <- as.integer(1/lu+1) L <- matrix(0, N, dim(SurvProb)[2]) R <- matrix(0, N, dim(SurvProb)[2]) r <- .C("BClossdist", SurvProb, as.integer(dim(SurvProb)[1]), as.integer(dim(SurvProb)[2]), as.double(recov), as.double(Z), as.double(w), as.integer(n.int), as.double(rho), as.double(lu), L=L, R=R) return(list(L=r$L,R=r$R)) }