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 ) charges a fee in proportion to each period’s outstanding principal balance, while the second () charges a fee on all payments by the borrower, up to the contractual monthly payment in the first 12 months.2 ( and represent fees levied on institutional (whole loan) investors and retail (Note) investors, respectively.) The borrower can “prepay” a loan with term by paying off the entire principal in months. Given initial principal , monthly interest rate , payment periods , and payment sequence , the principal amortizes according to and the corresponding IRRs and satisfy where if and otherwise, and is the installment (or minimum payment) given by
Note that in the amortization formula, represents the principal at the “end” of period , i.e., after payment is made. I deliberately modeled it this way since 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., , as the principal can never increase.
Now all that remains is to specify . First assuming constant monthly payments given , we can use the following code to see how and vary with . Note that by construction, i.e., by the choice of , we have . Also note that the result doesn’t depend on , 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 and equal to 1.3% per annum and 1%, respectively. (According to Lending Club’s policy for whole loans, is variable but 1.3% per annum is the highest servicing fee it will charge. A lower will increase , up to the interest rate when .)
# 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 , which can be done recursively). But it’s clear that investors who are charged fees under scheme can achieve a higher return with early payment, i.e., . 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 , although the curve is upward-sloping for .
We can’t yet declare the matter settled as there’s a potential problem with the above graph. Empirically, given , 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 periods and one large payment to expunge all outstanding principal (with interest) in period .
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 , although he will face strictly lower returns for all . In particular, the slope of the curve is even more severe for small , corresponding to a larger “penalty” on returns of one additional month. In contrast, returns are higher under for , although 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 are identical for and .
To give a better sense of the role of prepayment protection, I’ll add a line for a scheme (call it ) 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, and are unknown and there is non-negligible risk of borrower default. If prepayment and default are both correlated with variable , then selecting on 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 .↩
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.↩
