Calculating Returns for Two Kinds of Servicing Fee Schemes (Supplement)
Allen Sirolly / September 8, 2017
This post is a supplement to A Note on Lending Club and Institutional Investment, which shows that institutional investors tend to fund loans that will be prepaid. Below is a short mathematical justification for why it makes sense for them to do so, given Lending Club’s policy on servicing fees. I also include retail investors for comparison.
Suppose we’d like to compute the internal rate of return1 (IRR) for a loan investment based on two different fee schemes. The first (call it R1) charges a fee s1 in proportion to each period’s outstanding principal balance, while the second (R2) charges a fee s2 on all payments by the borrower, up to the contractual monthly payment in the first 12 months.2 (R1 and R2 represent fees levied on institutional (whole loan) investors and retail (Note) investors, respectively.) The borrower can “prepay” a loan with term T by paying off the entire principal in m<T months. Given initial principal P0, monthly interest rate r, payment periods m, and payment sequence {Ai}mi=1, the principal amortizes according to Pi=(1+r)Pi−1−Ai=(1+r)iP0−i∑j=1(1+r)i−jAj and the corresponding IRRs R1 and R2 satisfy P0=m∑i=1(1+R112)−i(Ai−s1Pi)P0=m∑i=1(1+R212)−i(Ai−s2Yi), where Yi=min(I,Ai) if i≤12 and Ai otherwise, and I is the installment (or minimum payment) given by I=P0r(1+r)T(1+r)T−1.
Note that in the amortization formula, Pi represents the principal at the “end” of period i, i.e., after payment Ai is made. I deliberately modeled it this way since R1 fees are calculated on end-of-month principal, per Lending Club policy.3 I also assume that payments are large enough to at least cover interest, i.e., Ai≥rPi−1, as the principal can never increase.
Now all that remains is to specify {Ai}. First assuming constant monthly payments given m, Ai=P0r(1+r)m(1+r)m−1≡A, we can use the following code to see how R1 and R2 vary with m. Note that by construction, i.e., by the choice of {Ai}, we have Pm=0. Also note that the result doesn’t depend on P0, though I’ve kept it to make the calculations more explicit.
A = function(P0, r, m) {
# P0 -- initial principal
# r -- interest rate (monthly)
# m -- number of payment periods
P0 * r * (1 + r)^m / ((1 + r)^m - 1)
}
P_i = Vectorize(
# Compute outstanding principal after i payments
function(A, P0, r, i) {
if (i==0) return(P0)
(1 + r)^i * P0 - A * sum((1 + r)^((i-1):0))
},
vectorize.args = 'i')
R1 = Vectorize(
function(s, P0, r, m) {
# s -- service fee rate (monthly, on remaining principal)
# P0, r, m -- args to A()
A_ = A(P0, r, m)
fun = function(z) (P0 - sum((1 + z/12)^-(1:m) *
(A_ - s * P_i(A_, P0, r, 1:m))))^2
# solve for IRR
optimize(fun, interval=c(0, .5))$minimum
},
vectorize.args = 'm')
R2 = Vectorize(
function(s, P0, r, m) {
# s -- service fee rate (monthly, on payments)
# P0, r, m -- args to A()
A_ = rep(A(P0, r, m), m)
Y_ = replace(A_, 1:min(m, 12), A(P0, r, 36))
fun = function(z) (P0 - sum((1 + z/12)^-(1:m) *
(A_ - s * Y_)))^2
# solve for IRR
optimize(fun, interval=c(0, .5))$minimum
},
vectorize.args = 'm')
The test case below is a loan with $1000 principal and 15% annual interest rate, with fees s1 and s2 equal to 1.3% per annum and 1%, respectively. (According to Lending Club’s policy for whole loans, s1 is variable but 1.3% per annum is the highest servicing fee it will charge. A lower s1 will increase R1, up to the interest rate when s1=0.)
# Returns for institutional (whole loan) investors
r_inst = R1(s=.013/12, P0=1000, r=.15/12, m=1:36)
# Returns for retail (Note) investors
r_ret = R2(s=.01, P0=1000, r=.15/12, m=1:36)
matplot(1:36, cbind(r_inst, r_ret), las=1, type='l',
xlab='Months until full payment (m)',
ylab='Internal rate of return')
legend('topright', c('R1','R2'), lty=c(1,2), col=c('black','red'),
inset=.01)
This isn’t a complete picture since it doesn’t account for returns from reinvestment (purchasing new Notes or whole loans after receiving each Ai, which can be done recursively). But it’s clear that investors who are charged fees under scheme R1 can achieve a higher return with early payment, i.e., ΔmR1<0. The gains are relatively small but may be significant for investors with very large portfolios. With prepayment protection, early payment is also the most desirable outcome under scheme R2, although the curve is upward-sloping for m>17.
We can’t yet declare the matter settled as there’s a potential problem with the above graph. Empirically, given m, the borrower tends to back-load prepayment to later periods, so the assumption of fixed payments isn’t very realistic. (I checked this using Lending Club’s payments data, but I’ll defer the evidence to a future post.) A more realistic flow of payments would be constant installments for m−1 periods and one large payment to expunge all outstanding principal (with interest) in period m.
It’s straightforward to modify the functions above to accomodate non-constant payments:
P_i = Vectorize(
# Compute outstanding principal after i payments
function(A_seq, P0, r, i) {
# A_seq -- sequence of payments (length m)
if (i==0) return(P0)
(1 + r)^i * P0 - sum(A_seq[1:i] * (1 + r)^((i-1):0))
},
vectorize.args = 'i')
gen_A_seq = function(P0, r, m, Term=36) {
# Sequence of m - 1 installments, plus final payment (1+r)*P_{m-1}
Inst = A(P0, r, Term)
A_seq = rep(Inst, m - 1)
c(A_seq, (1 + r) * P_i(A_seq, P0, r, m - 1))
}
R1 = Vectorize(
function(s, P0, r, m) {
# s -- service fee rate (monthly, on remaining principal)
# P0, r, m -- args to A()
A_seq = gen_A_seq(P0, r, m)
fun = function(z) (P0 - sum((1 + z/12)^-(1:m) *
(A_seq - s * P_i(A_seq, P0, r, 1:m))))^2
# solve for IRR
optimize(fun, interval=c(0, .5))$minimum
},
vectorize.args = 'm')
R2 = Vectorize(
function(s, P0, r, m=36) {
# s -- service fee rate (monthly, on payments)
# P0, r, m -- args to A()
A_seq = gen_A_seq(P0, r, m)
Y_seq = replace(A_seq, 1:min(m, 12), A(P0, r, 36))
fun = function(z) (P0 - sum((1 + z/12)^-(1:m) *
(A_seq - s * Y_seq)))^2
# solve for IRR
optimize(fun, interval=c(0, .5))$minimum
},
vectorize.args = 'm')
# Returns for institutional (whole loan) investors
r_inst = R1(s=.013/12, P0=1000, r=.15/12, m=1:36)
# Returns for retail (Note) investors
r_ret = R2(s=.01, P0=1000, r=.15/12, m=1:36)
matplot(1:36, cbind(r_inst, r_ret), las=1, type='l',
xlab='Months until full payment (m)',
ylab='Internal rate of return')
legend('topright', c('R1','R2'), lty=c(1,2), col=c('black','red'),
inset=.01)
With this more realistic payment sequence, an investor will still desire early payment under R1, although he will face strictly lower returns for all m∉{1,36}. In particular, the slope of the curve R1(m) is even more severe for small m, corresponding to a larger “penalty” on returns of one additional month. In contrast, returns are higher under R2 for m≤12, although m>12 yields lower returns since prepayment protection will not extend to the large final payment. Note that the endpoints of the two curves are the same as before since the two versions of {Ai} are identical for m=1 and m=T.
To give a better sense of the role of prepayment protection, I’ll add a line for a scheme (call it Ra2) which charges a 1% fee on all borrower payments. The difference is stark:
R2a = Vectorize(
function(s, P0, r, m=36) {
# s -- service fee rate (monthly, on payments)
# P0, r, m -- args to A()
A_seq = gen_A_seq(P0, r, m)
fun = function(z) (P0 - sum((1 + z/12)^-(1:m) *
(1 - s) * A_seq))^2
# solve for IRR
optimize(fun, interval=c(0, .5))$minimum
},
vectorize.args = 'm')
# Returns for retail (Note) investors, no prepayment protection
r_ret_no_protection = R2a(s=.01, P0=1000, r=.15/12, m=1:36)
matplot(1:36, cbind(r_inst, r_ret, r_ret_no_protection), las=1, type='l',
col=c('black','red','darkred'),
xlab='Months until full payment (m)',
ylab='Internal rate of return')
legend('bottomright', c('R1','R2','R2a'), lty=1:3, col=c('black','red','darkred'),
inset=.01)
Keep in mind that these examples only evaluate ex-post returns; when an investor is actually choosing loans on Lending Club, m and {Ai} are unknown and there is non-negligible risk of borrower default. If prepayment and default are both correlated with variable X, then selecting on X may diminish gains from prepayment compared to above.
The internal rate of return is the interest rate at which the present value of cash flows equals the initial capital. I elected to use a nominal rate, but one could just as well use the effective rate. They’re related by (1+Rnom/12)12=1+Reff.↩
I’ll subsequently refer to this as prepayment protection, which was implemented by Lending Club beginning in Q4 2014. See https://www.lendingclub.com/public/rates-and-fees.action.↩
Although I suppose this would really be dependent on the timing of loan origination. In any case, realize that I’m abstracting away some of the nonessential details.↩