## todo: ## -investigate other ways to interpolate the random severities on the grid ## I'm thinking that at eah severity that we add to the distribution, round it down ## and keep track of the missing mass: namely if X_i=S_i w.p p_i, then add ## X_i=lu*floor(S_i/lu) with probability p_i and propagate ## X_{i+1}=S_{i+1}+(S_i-lu*floor(S_i/lu)) with the right probability ## - investigate truncated distributions more (need to compute loss and recov distribution ## separately, for the 0-10 equity tranche, we need the loss on the 0-0.1 support and ## recovery with 0.1-1 support, so it's not clear that there is a big gain. ## - do the correlation adjustments when computing the deltas since it seems to be ## the market standard GHquad <- function(n){ n <- as.integer(n) Z <- double(n) w <- double(n) result <- .C("GHquad", n, Z=Z, w=w) result[[1]] <- NULL return(result) } 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[-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, N, defaultflag=FALSE){ ## recursive algorithm with first order correction ## p vector of default probabilities ## w vector of weigths ## S vector of severities ## N number of ticks in the grid ## defaultflag if true computes the default distribution n <- length(p) lu <- 1/(N-1) q <- rep(0, N) q[1] <- 1 for(i in 1:n){ if(defaultflag){ d <- w[i] /lu }else{ 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) } lossdistrib2.truncated <- function(p, w, S, N, cutoff=N){ ## recursive algorithm with first order correction ## p vector of default probabilities ## w vector of weigths ## S vector of severities ## N number of ticks in the grid (for best accuracy should ## be a multiple of the number of issuers) ## cutoff where to stop computing the exact probabilities ## (useful for tranche computations) ## this is actually slower than lossdistrib2. But in C this is ## twice as fast. ## for high severities, M can become bigger than N, and there is ## some probability mass escaping. n <- length(p) lu <- 1/(N-1) q <- rep(0, truncated) q[1] <- 1 M <- 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 <- p1*q[1:min(M, cutoff-d1)] q2 <- p2*q[1:min(M, cutoff-d2)] q[1:min(M, cutoff)] <- (1-p[i])*q[1:min(M, cutoff)] q[(d1+1):min(M+d1, cutoff)] <- q[(d1+1):min(M+d1, cutoff)]+q1 q[(d2+1):min(M+d2, cutoff)] <- q[(d2+1):min(M+d2, cutoff)]+q2 M <- M+d2 } return(q) } recovdist <- function(dp, pp, w, S, N){ ## 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 ## the pair of values floor(v/lu) and ceiling(v/lu) so that ## X_i has four non zero values n <- length(dp) q <- rep(0, N) q[1] <- 1 lu <- 1/(N-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 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) } lossdist.joint <- function(p, w, S, N, defaultflag=FALSE){ ## recursive algorithm with first order correction ## to compute the joint probability distribution of the loss and recovery ## inputs: ## p: vector of default probabilities ## w: vector of issuer weights ## S: vector of severities ## N: number of tick sizes on the grid ## defaultflag: if true computes the default distribution ## output: ## q: matrix of joint loss, recovery probability ## colSums(q) is the recovery distribution marginal ## rowSums(q) is the loss distribution marginal n <- length(p) lu <- 1/(N-1) q <- matrix(0, N, N) q[1,1] <- 1 for(k in 1:n){ if(defaultflag){ x <- w[k] / lu }else{ 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) } lossdist.prepay.joint <- function(dp, pp, w, S, N, defaultflag=FALSE){ ## recursive algorithm with first order correction ## to compute the joint probability distribition of the loss and recovery ## inputs: ## dp: vector of default probabilities ## pp: vector of prepay probabilities ## w: vector of issuer weights ## S: vector of severities ## N: number of tick sizes on the grid ## defaultflag: if true computes the default ## outputs ## q: matrix of joint loss and recovery probability ## colSums(q) is the recovery distribution marginal ## rowSums(q) is the loss distribution marginal n <- length(dp) lu <- 1/(N-1) q <- matrix(0, N, N) q[1,1] <- 1 for(k in 1:n){ y1 <- (1-S[k]) * w[k]/lu y2 <- w[k]/lu j1 <- floor(y1) j2 <- floor(y2) if(defaultflag){ x <- y2 i <- j2 }else{ x <- y2-y1 i <- floor(x) } ## weights <- c((i+1-x)*(j1+1-y1), (i+1-x)*(y1-j1), (x-i)*(y1-j1), (j1+1-y1)*(x-i)) weights1 <- c((i+1-x)*(j1+1-y1), (i+1-x)*(y1-j1), (x-i)*(y1-j1), (j1+1-y1)*(x-i)) dpsplit <- dp[k] * weights1 if(defaultflag){ weights2 <- c((i+1-x)*(j2+1-y2), (i+1-x)*(y2-j2), (x-i)*(y2-j2), (j2+1-y2)*(x-i)) ppsplit <- pp[k] * weights2 }else{ 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)] if(defaultflag){ qtemp[(i+1):N,(j2+1):N] <- qtemp[(i+1):N,(j2+1):N] + ppsplit[1] * q[1:(N-i),1:(N-j2)] qtemp[(i+1):N,(j2+2):N] <- qtemp[(i+1):N,(j2+2):N] + ppsplit[2] * q[1:(N-i), 1:(N-j2-1)] qtemp[(i+2):N,(j2+2):N] <- qtemp[(i+2):N,(j2+2):N] + ppsplit[3] * q[1:(N-i-1), 1:(N-j2-1)] qtemp[(i+2):N,(j2+1):N] <- qtemp[(i+2):N, (j2+1):N] + ppsplit[4] * q[1:(N-i-1), 1:(N-j2)] }else{ 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) } lossdistC <- function(p, w, S, N, defaultflag=FALSE){ ## C version of lossdistrib2, roughly 50 times faster .C("lossdistrib", as.double(p), as.integer(length(p)), as.double(w), as.double(S), as.integer(N), as.logical(defaultflag), q = double(N))$q } lossdistCblas <- function(p, w, S, N, defaultflag=FALSE){ ## C version of lossdistrib2, roughly 50 times faster .C("lossdistrib_blas", as.double(p), as.integer(length(p)), as.double(w), as.double(S), as.integer(N), as.logical(defaultflag), q = double(N))$q } lossdistCZ <- function(p, w, S, N, defaultflag=FALSE, rho, Z, wZ){ .C("lossdistrib_Z", as.double(p), as.integer(length(p)), as.double(w), as.double(S), as.integer(N), as.logical(defaultflag), as.double(rho), as.double(Z), as.integer(length(Z)), q = matrix(0, N, length(Z)))$q } lossdistC.truncated <- function(p, w, S, N, T=N){ ## C version of lossdistrib2, roughly 50 times faster .C("lossdistrib_truncated", as.double(p), as.integer(length(p)), as.double(w), as.double(S), as.integer(N), as.integer(T), q = double(T))$q } recovdistC <- function(dp, pp, w, S, N){ ## C version of recovdist .C("recovdist", as.double(dp), as.double(pp), as.integer(length(dp)), as.double(w), as.double(S), as.integer(N), q = double(N))$q } lossdistC.joint <- function(p, w, S, N, defaultflag=FALSE){ ## C version of lossdistrib.joint, roughly 20 times faster .C("lossdistrib_joint", as.double(p), as.integer(length(p)), as.double(w), as.double(S), as.integer(N), as.logical(defaultflag), q = matrix(0, N, N))$q } lossdistC.jointblas <- function(p, w, S, N, defaultflag=FALSE){ ## C version of lossdistrib.joint, roughly 20 times faster .C("lossdistrib_joint_blas", as.double(p), as.integer(length(p)), as.double(w), as.double(S), as.integer(N), as.logical(defaultflag), q = matrix(0, N, N))$q } lossdistC.jointZ <- function(dp, w, S, N, defaultflag = FALSE, rho, Z, wZ){ ## N is the size of the grid ## dp is of size n.credits ## w is of size n.credits ## S is of size n.credits by nZ ## rho is a double ## Z is a vector of length nZ ## w is a vector if length wZ r <- .C("lossdistrib_joint_Z", as.double(dp), as.integer(length(dp)), as.double(w), as.double(S), as.integer(N), as.logical(defaultflag), as.double(rho), as.double(Z), as.double(wZ), as.integer(length(Z)), q = matrix(0, N, N))$q } lossdistC.prepay.joint <- function(dp, pp, w, S, N, defaultflag=FALSE){ ## C version of lossdist.prepay.joint r <- .C("lossdistrib_prepay_joint", as.double(dp), as.double(pp), as.integer(length(dp)), as.double(w), as.double(S), as.integer(N), as.logical(defaultflag), q=matrix(0, N, N))$q return(r) } lossdistC.prepay.jointZ <- function(dp, pp, w, S, N, defaultflag = FALSE, rho, Z, wZ){ ## N is the size of the grid ## dp is of size n.credits ## pp is of size n.credits ## w is of size n.credits ## S is of size n.credits by nZ ## rho is a double ## Z is a vector of length nZ ## w is a vector if length wZ r <- .C("lossdistrib_prepay_joint_Z", as.double(dp), as.double(pp), as.integer(length(dp)), as.double(w), as.double(S), as.integer(N), as.logical(defaultflag), as.double(rho), as.double(Z), as.double(wZ), as.integer(length(Z)), q = matrix(0, N, N))$q } lossrecovdist <- function(defaultprob, prepayprob, w, S, N, defaultflag=FALSE, useC=TRUE){ if(missing(prepayprob)){ if(useC){ L <- lossdistC(defaultprob, w, S, N, defaultflag) R <- lossdistC(defaultprob, w, 1-S, N) }else{ L <- lossdistrib2(defaultprob, w, S, N, defaultflag) R <- lossdistrib2(defaultprob, w, 1-S, N) } }else{ if(useC){ L <- lossdistC(defaultprob+defaultflag*prepayprob, w, S, N, defaultflag) R <- recovdistC(defaultprob, prepayprob, w, S, N) }else{ L <- lossdistrib2(defaultprob+defaultflag*prepayprob, w, S, N, defaultflag) R <- recovdist(defaultprob, prepayprob, w, S, N) } } return(list(L=L, R=R)) } lossrecovdist.term <- function(defaultprob, prepayprob, w, S, N, defaultflag=FALSE, useC=TRUE){ ## computes the loss and recovery distribution over time L <- array(0, dim=c(N, ncol(defaultprob))) R <- array(0, dim=c(N, ncol(defaultprob))) if(missing(prepayprob)){ for(t in 1:ncol(defaultprob)){ temp <- lossrecovdist(defaultprob[,t], , w, S[,t], N, defaultflag, useC) L[,t] <- temp$L R[,t] <- temp$R } }else{ for(t in 1:ncol(defaultprob)){ temp <- lossrecovdist(defaultprob[,t], prepayprob[,t], w, S[,t], N, defaultflag, useC) L[,t] <- temp$L R[,t] <- temp$R } } return(list(L=L, R=R)) } lossrecovdist.joint.term <- function(defaultprob, prepayprob, w, S, N, defaultflag=FALSE, useC=TRUE){ ## computes the joint loss and recovery distribution over time Q <- array(0, dim=c(ncol(defaultprob), N, N)) if(useC){ if(missing(prepayprob)){ for(t in 1:ncol(defaultprob)){ Q[t,,] <- lossdistC.joint(defaultprob[,t], w, S[,t], N, defaultflag) } }else{ for(t in 1:ncol(defaultprob)){ Q[t,,] <- lossdistC.prepay.joint(defaultprob[,t], prepayprob[,t], w, S[,t], N, defaultflag) } } }else{ if(missing(prepayprob)){ for(t in 1:ncol(defaultprob)){ Q[t,,] <- lossdist.joint(defaultprob[,t], w, S[,t], N, defaultflag) } }else{ for(t in 1:ncol(defaultprob)){ Q[t,,] <- lossdist.prepay.joint(defaultprob[,t], prepayprob[,t], w, S[,t], N, defaultflag) } } } gc() return(Q) } dist.transform <- function(dist.joint){ ## compute the joint (D, R) distribution ## from the (L, R) distribution using D = L+R distDR <- array(0, dim=dim(dist.joint)) Ngrid <- dim(dist.joint)[2] for(t in 1:dim(dist.joint)[1]){ for(i in 1:Ngrid){ for(j in 1:Ngrid){ index <- i+j if(index <= Ngrid){ distDR[t,index,j] <- distDR[t,index,j] + dist.joint[t,i,j] }else{ distDR[t,Ngrid,j] <- distDR[t,Ngrid,j] + dist.joint[t,i,j] } } } distDR[t,,] <- distDR[t,,]/sum(distDR[t,,]) } return( distDR ) } shockprob <- function(p, rho, Z, log.p=F){ ## computes the shocked default probability as a function of the copula factor ## function is vectorized provided the below caveats: ## p and rho are vectors of same length n, Z is a scalar, returns vector of length n ## p and rho are scalars, Z is a vector of length n, returns vector of length n if(length(p)==1){ if(rho==1){ return(Z<=qnorm(p)) }else{ return(pnorm((qnorm(p)-sqrt(rho)*Z)/sqrt(1-rho), log.p=log.p)) } }else{ result <- double(length(p)) result[rho==1] <- Z<=qnorm(p[rho==1]) result[rho<1] <- pnorm((qnorm(p[rho<1])-sqrt(rho[rho<1])*Z)/sqrt(1-rho[rho<1]), log.p=log.p) return( result ) } } shockseverity <- function(S, Stilde=1, Z, rho, p){ ## computes the severity as a function of the copula factor Z result <- double(length(S)) result[p==0] <- 0 result[p!=0] <- Stilde * exp( shockprob(S[p!=0]/Stilde*p[p!=0], rho[p!=0], Z, TRUE) - shockprob(p[p!=0], rho[p!=0], Z, TRUE)) return(result) } 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 require(distr) if(p0==0){ return(0) } if(rho == 1){ distw <- DiscreteDistribution(Z, w) return(pnorm(q(distw)(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) } fit.probC <- function(Z, w, rho, p0){ r <- .C("fitprob", as.double(Z), as.double(w), as.integer(length(Z)), as.double(rho), as.double(p0), q = double(1)) return(r$q) } stochasticrecov <- function(R, Rtilde, Z, w, rho, porig, pmod){ ## if porig == 0 (probably matured asset) then return orginal recovery ## it shouldn't matter anyway since we never default that asset if(porig == 0){ return(rep(R, length(Z))) }else{ 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)))) } } stochasticrecovC <- function(R, Rtilde, Z, w, rho, porig, pmod){ r <- .C("stochasticrecov", as.double(R), as.double(Rtilde), as.double(Z), as.double(w), as.integer(length(Z)), as.double(rho), as.double(porig), as.double(pmod), q = double(length(Z))) return(r$q) } 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, Ngrid=nrow(L), scaled=FALSE){ ## computes the couponleg of a tranche ## if scaled is TRUE, scale it by the size of the tranche (K2-K1) ## can make use of the fact that the loss and recov distribution are ## truncated (in that case nrow(L) != Ngrid if(K1==K2){ return( 0 ) }else{ support <- seq(0, 1, length=Ngrid)[1:nrow(L)] size <- K2 - K1 - crossprod(trancheloss(support, K1, K2), L) - crossprod(trancherecov(support, K1, K2), R) sizeadj <- as.numeric(0.5 * (size + c(K2-K1, size[-length(size)]))) if(scaled){ return( 1/(K2-K1) * crossprod(sizeadj * cs$coupons, cs$df) ) }else{ return( crossprod(sizeadj * cs$coupons, cs$df) ) } } } tranche.cl.scenarios <- function(l, r, cs, K1, K2, scaled=FALSE){ ## computes the couponleg of a tranche for one scenario ## if scaled is TRUE, scale it by the size of the tranche (K2-K1) ## can make use of the fact that the loss and recov distribution are ## truncated (in that case nrow(L) != Ngrid if(K1==K2){ return( 0 ) }else{ size <- K2 - K1 - trancheloss(l, K1, K2) - trancherecov(r, K1, K2) sizeadj <- as.numeric(0.5 * (size + c(K2-K1, size[-length(size)]))) if(scaled){ return( 1/(K2-K1) * crossprod(sizeadj * cs$coupons, cs$df) ) }else{ return( crossprod(sizeadj * cs$coupons, cs$df) ) } } } funded.tranche.pv <- function(L, R, cs, K1, K2, scaled = FALSE){ if(K1==K2){ return(0) }else{ size <- K2 - K1 -trancheloss(L, K1, K2) - trancherecov(R, K1, K2) sizeadj <- as.numeric(0.5 * (size + c(K2-K1, size[-length(size)]))) interest <- crossprod(sizeadj * cs$coupons, cs$df) principal <- diff(c(0, trancherecov(R, K1, K2))) principal[length(principal)] <- principal[length(principal)] + size[length(size)] principal <- crossprod(cs$df, principal) if(scaled){ pv <- (interest + principal)/(K2-K1) }else{ pv <- (interest + principal) } return(pv) } } tranche.pl <- function(L, cs, K1, K2, Ngrid=nrow(L), scaled=FALSE){ ## computes the protection leg of a tranche ## if scaled if(K1==K2){ return(0) }else{ support <- seq(0, 1, length=Ngrid)[1:nrow(L)] cf <- K2 - K1 - crossprod(trancheloss(support, K1, K2), L) cf <- c(K2 - K1, cf) if(scaled){ return( 1/(K2-K1) * crossprod(diff(cf), cs$df)) }else{ return( crossprod(diff(cf), cs$df)) } } } tranche.pl.scenarios <- function(l, cs, K1, K2, scaled=FALSE){ ## computes the protection leg of a tranche ## if scaled if(K1==K2){ return(0) }else{ cf <- K2 - K1 - trancheloss(l, K1, K2) cf <- c(K2 - K1, cf) if(scaled){ return( 1/(K2-K1) * as.numeric(crossprod(diff(cf), cs$df))) }else{ return( as.numeric(crossprod(diff(cf), cs$df))) } } } tranche.pv <- function(L, R, cs, K1, K2, Ngrid=nrow(L)){ return( tranche.pl(L, cs, K1, K2, Ngrid) + tranche.cl(L, R, cs, K1, K2, Ngrid)) } tranche.pv.scenarios <- function(l, r, cs, K1, K2){ return( tranche.pl.scenarios(l, cs, K1, K2, TRUE) + tranche.cl.scenarios(l, r, cs, K1, K2, TRUE)) } adjust.attachments <- function(K, losstodate, factor){ ## computes the attachments adjusted for losses ## on current notional return( pmin(pmax((K-losstodate)/factor, 0),1) ) } tranche.pvvec <- function(K, L, R, cs){ r <- rep(0, length(K)-1) for(i in 1:(length(K)-1)){ r[i] <- 1/(K[i+1]-K[i]) * tranche.pv(L, R, cs, K[i], K[i+1]) } return( r ) } BClossdist <- function(defaultprob, issuerweights, recov, rho, Z, w, N=length(recov)+1, defaultflag=FALSE, n.int=500){ if(missing(Z)){ quadrature <- GHquad(n.int) Z <- quadrature$Z w <- quadrature$w } ## do not use if weights are not gaussian, results would be incorrect ## since shockseverity is invalid in that case (need to use stochasticrecov) LZ <- matrix(0, N, length(Z)) RZ <- matrix(0, N, length(Z)) L <- matrix(0, N, ncol(defaultprob)) R <- matrix(0, N, ncol(defaultprob)) for(t in 1:ncol(defaultprob)){ for(i in 1:length(Z)){ g.shocked <- shockprob(defaultprob[,t], rho, Z[i]) S.shocked <- shockseverity(1-recov, 1, Z[i], rho, defaultprob[,t]) temp <- lossrecovdist(g.shocked, , issuerweights, S.shocked, N) LZ[,i] <- temp$L RZ[,i] <- temp$R } L[,t] <- LZ%*%w R[,t] <- RZ%*%w } list(L=L, R=R) } BClossdistC <- function(defaultprob, issuerweights, recov, rho, Z, w, N=length(issuerweights)+1, defaultflag=FALSE){ L <- matrix(0, N, dim(defaultprob)[2]) R <- matrix(0, N, dim(defaultprob)[2]) rho <- rep(rho, length(issuerweights)) r <- .C("BClossdist", defaultprob, dim(defaultprob)[1], dim(defaultprob)[2], as.double(issuerweights), as.double(recov), as.double(Z), as.double(w), as.integer(length(Z)), as.double(rho), as.integer(N), as.logical(defaultflag), L=L, R=R) return(list(L=r$L,R=r$R)) } BCtranche.pv <- function(defaultprob, issuerweights, recov, cs, K1, K2, rho1, rho2, Z, w, N=length(issuerweights)+1){ ## computes the protection leg, couponleg, and bond price of a tranche ## in the base correlation setting if(K1==0){ if(rho1!=0){ stop("equity tranche must have 0 lower correlation") } } dK <- K2 - K1 dist2 <- BClossdistC(defaultprob, issuerweights, recov, rho2, Z, w, N) if(rho1!=0){ dist1 <- BClossdistC(defaultprob, issuerweights, recov, rho1, Z, w, N) } cl2 <- tranche.cl(dist2$L, dist2$R, cs, 0, K2) cl1 <- tranche.cl(dist1$L, dist1$R, cs, 0, K1) pl2 <- tranche.pl(dist2$L, cs, 0, K2) pl1 <- tranche.pl(dist1$L, cs, 0, K1) return(list(pl=(pl2-pl1)/dK, cl=(cl2-cl1)/dK, bp=100*(1+(pl2-pl1+cl2-cl1)/dK))) } BCtranche.delta <- function(portfolio, index, coupon, K1, K2, rho1, rho2, Z, w, N=length(portolio)+1, tradedate = Sys.Date()){ ## computes the tranche delta (on current notional) by doing a proportional ## blip of all the curves ## if K2==1, then computes the delta using the lower attachment only ## this makes sense for bottom-up skews eps <- 1e-4 portfolioplus <- portfolio portfoliominus <- portfolio cs <- couponSchedule(IMMDate(tradedate), index$maturity,"Q", "FIXED", coupon, 0, tradedate, IMMDate(tradedate, "prev")) for(i in 1:length(portfolio)){ portfolioplus[[i]]@curve@hazardrates <- portfolioplus[[i]]@curve@hazardrates * (1 + eps) portfoliominus[[i]]@curve@hazardrates <- portfoliominus[[i]]@curve@hazardrates * (1- eps) } dPVindex <- indexpv(portfolioplus, index, tradedate = tradedate, clean = FALSE)$bp - indexpv(portfoliominus, index, tradedate = tradedate, clean = FALSE)$bp defaultprobplus <- 1 - SPmatrix(portfolioplus, length(cs$dates)) defaultprobminus <- 1 - SPmatrix(portfoliominus, length(cs$dates)) if(K2==1){ K1adj <- adjust.attachments(K1, index$loss, index$factor) dPVtranche <- BCtranche.pv(defaultprobplus, issuerweights, recov, cs, 0, K1adj, 0, rho1, Z, w, N)$bp - BCtranche.pv(defaultprobminus, issuerweights, recov, cs, 0, K1adj, 0, rho1, Z, w, N)$bp delta <- (1 - dPVtranche/(100*dPVindex) * K1adj)/(1-K1adj) }else{ Kmod <- adjust.attachments(c(K1, K2), index$loss, index$factor) dPVtranche <- BCtranche.pv(defaultprobplus, issuerweights, recov, cs, Kmod[1], Kmod[2], rho1, rho2, Z, w, N)$bp - BCtranche.pv(defaultprobminus, issuerweights, recov, cs, Kmod[1], Kmod[2], rho1, rho2, Z, w, N)$bp delta <- dPVtranche/(100*dPVindex) } ## dPVindex <- BCtranche.pv(portfolioplus, index, coupon, 0, 1, 0, 0.5, lu)$bp- ## BCtranche.pv(portfoliominus, index, coupon, 0, 1, 0, 0.5, lu)$bp return( delta ) } BCstrikes <- function(portfolio, index, coupon, K, rho, N=101) { ## computes the strikes as a percentage of expected loss EL <- c() for(i in 2:length(K)){ EL <- c(EL, -BCtranche.pv(portfolio, index, coupon, K[i-1], K[i], rho[i-1], rho[i], N)$pl) } Kmodified <- adjust.attachments(K, index$loss, index$factor) return(cumsum(EL*diff(Kmodified))/sum(EL*diff(Kmodified))) } delta.factor <- function(K1, K2, index){ ## compute the factor to convert from delta on current notional to delta on original notional ## K1 and K2 original strikes factor <- (adjust.attachments(K2, index$loss, index$factor) -adjust.attachments(K1, index$loss, index$factor))/(K2-K1) return( factor ) } MFupdate.prob <- function(Z, w, rho, defaultprob){ ## update the probabilites based on a non gaussian factor ## distribution so that the pv of the cds stays the same. p <- matrix(0, nrow(defaultprob), ncol(defaultprob)) for(i in 1:nrow(defaultprob)){ for(j in 1:ncol(defaultprob)){ p[i,j] <- fit.prob(Z, w, rho[i], defaultprob[i,j]) } } return( p ) } MFupdate.probC <- function(Z, w, rho, defaultprob){ ## update the probabilities based on a non gaussian factor ## distribution so that the pv of the cds stays the same. p <- matrix(0, nrow(defaultprob), ncol(defaultprob)) for(i in 1:nrow(defaultprob)){ for(j in 1:ncol(defaultprob)){ p[i,j] <- .C("fitprob", as.double(Z), as.double(w), as.integer(length(Z)), as.double(rho[i]), as.double(defaultprob[i,j]), q = double(1))$q } } return( p ) } MFlossrecovdist.prepay <- function(w, Z, rho, defaultprob, defaultprobmod, prepayprob, prepayprobmod, issuerweights, recov, Ngrid=2*length(issuerweights)+1, defaultflag=FALSE){ ## computes the loss and recovery distribution using the modified factor distribution n.credit <- length(issuerweights) n.int <- length(w) Rstoch <- array(0, dim=c(n.int, n.credit, ncol(defaultprob))) for(t in 1:ncol(defaultprob)){ for(i in 1:n.credit){ Rstoch[,i,t] <- stochasticrecov(recov[i], 0, Z, w, rho, defaultprob[i,t], defaultprobmod[i,t]) } } parf <- function(i){ dpshocked <- apply(defaultprobmod, 2, shockprob, rho=rho, Z=Z[i]) ppshocked <- apply(prepayprobmod, 2, shockprob, rho=rho, Z=-Z[i]) S <- 1 - Rstoch[i,,] dist <- lossrecovdist.term(dpshocked, ppshocked, issuerweights, S, Ngrid, defaultflag) } L <- matrix(0, Ngrid, ncol(defaultprob)) R <- matrix(0, Ngrid, ncol(defaultprob)) for(i in 1:length(w)){ dist <- parf(i) L <- L + dist$L * w[i] R <- R + dist$R * w[i] } return( list(L=L, R=R) ) } MFlossdist.joint <- function(cl, w, Z, rho, defaultprob, defaultprobmod, issuerweights, recov, Ngrid=2*length(issuerweights)+1, defaultflag=FALSE){ ## rowSums(Q) is the default/loss distribution depending if ## defaultflag is TRUE or FALSE (default setting is FALSE) ## colSums(Q) is the recovery distribution ## so that recovery is the y axis and L/D is the x axis ## if we use the persp function, losses is the axes facing us, ## and R is the axis going away from us. n.credit <- length(issuerweights) n.int <- lenth(w) Rstoch <- array(0, dim=c(n.int, n.credit, ncol(defaultprob))) for(t in 1:ncol(defaultprob)){ for(i in 1:n.credit){ Rstoch[,i,t] <- stochasticrecov(recov[i], 0, Z, w, rho, defaultprob[i,t], defaultprobmod[i,t]) } } parf <- function(i){ pshocked <- apply(defaultprobmod, 2, shockprob, rho=rho, Z=Z[i]) S <- 1 - Rstoch[i,,] dist <- lossrecovdist.joint.term(pshocked, 0, issuerweights, S, Ngrid, defaultflag) gc() return(dist) } temp <- parSapply(cl, 1:length(w), parf) clusterCall(cl, gc) Q <- array(0, dim=c(ncol(defaultprob), Ngrid, Ngrid)) for(i in 1:length(w)){ Q <- Q + w[i]*array(temp[,i], dim=c(ncol(defaultprob), Ngrid, Ngrid)) } return( Q ) } MFlossdist.prepay.joint <- function(w, Z, rho, defaultprob, defaultprobmod, prepayprob, prepayprobmod, issuerweights, recov, Ngrid=2*length(issuerweights)+1, defaultflag=FALSE){ ## rowSums is the loss distribution ## colSums is the recovery distribution ## so that recovery is the y axis and L is the x axis ## if we use the persp function, losses is the axes facing us, ## and R is the axis going away from us. n.credit <- length(issuerweights) n.int <- length(w) Rstoch <- array(0, dim=c(n.credit, n.int, ncol(defaultprob))) for(t in 1:ncol(defaultprob)){ for(i in 1:n.credit){ Rstoch[i,,t] <- stochasticrecovC(recov[i], 0, Z, w, rho[i], defaultprob[i,t], defaultprobmod[i,t]) } } Q <- array(0, dim=c(ncol(defaultprob), Ngrid, Ngrid)) for(t in 1:ncol(defaultprob)){ S <- 1 - Rstoch[,,t] Q[t,,] <- lossdistC.prepay.jointZ(defaultprobmod[,t], prepayprobmod[,t], issuerweights, S, Ngrid, defaultflag, rho, Z, w) } return( Q ) } MFtranche.pv <- function(cl, cs, w, rho, defaultprob, defaultprobmod, issuerweights, recov, Kmodified, Ngrid=length(issuerweights)+1){ ## computes the tranches pv using the modified factor distribution ## p is the modified probability so that n.credit <- length(issuerweights) Rstoch <- array(0, dim=c(n.int, n.credit, ncol(defaultprob))) for(t in 1:ncol(defaultprob)){ for(i in 1:n.credit){ Rstoch[,i,t] <- stochasticrecov(recov[i], 0, Z, w, rho, defaultprob[i,t], defaultprobmod[i,t]) } } parf <- function(i){ pshocked <- apply(defaultprobmod, 2, shockprob, rho=rho, Z=Z[i]) S <- 1 - Rstoch[i,,] dist <- lossrecovdist.term(pshocked, 0, issuerweights, S, Ngrid) return( tranche.pvvec(Kmodified, dist$L, dist$R, cs)) } clusterExport(cl, list("Rstoch", "p"), envir=environment()) result <- parSapply(cl, 1:length(w), parf) return( list(pv=100*(1+result%*%w), pv.w=result)) }