Skip to content

Instantly share code, notes, and snippets.

@aaiezza
Last active October 25, 2020 15:54
Show Gist options
  • Save aaiezza/99d0c85da60665869430599a6a9b36ac to your computer and use it in GitHub Desktop.
Save aaiezza/99d0c85da60665869430599a6a9b36ac to your computer and use it in GitHub Desktop.
Function for determining how long an optimal Debt Snowball will take to pay off. Takes only current principals and interest rates. Does not take minimum payments or account for compounding.
loanData <- data.frame(
description = c(
'1-03 Direct Loan - Unsubsidized',
'1-02 Direct Loan - Subsidized',
'Direct Unsubsidized Stafford',
'Direct Unsubsidized Stafford',
'1-01 Direct Loan - Unsubsidized',
'Direct Unsubsidized Stafford',
'Direct Subsidized Stafford',
'Direct Unsubsidized Stafford',
'Direct Subsidized Stafford',
'Direct Subsidized Stafford',
'1-04 Direct Loan - Unsubsidized',
'Direct Unsubsidized Stafford',
'Federal Perkins Loan',
'Direct Grad PLUS',
'Direct Unsubsidized Stafford',
'Direct Unsubsidized Stafford' ),
token = c(
3,
2,
129,
126,
1,
124,
123,
130,
125,
128,
4, #
127,
0,
132,
133,
131 ),
group = c(
'navient',
'navient',
'stafford',
'stafford',
'navient',
'stafford',
'stafford',
'stafford',
'stafford',
'stafford',
'navient',
'stafford',
'perkins_loan',
'grad_plus',
'stafford',
'stafford' ),
principal = c(
0, # 3
1047.14, # 2
2035.85, # 129
0, # 126
0, # 1
0, # 124
5103.08, # 123
5072.45, # 130
5190.13, # 125
5213.86, # 128
4724.67, # 4
0, # 127
3234.80, # 0
0, # 132
20284.95, # 133
6731.86 # 131
),
rate = c(
0.06550,
0.03150,
0.03610,
0.06550,
0.06550,
0.06550,
0.03150,
0.03610,
0.03150,
0.03610,
0.03610,
0.06550,
0.05000,
0.06960,
0.05590,
0.05960 ) )
loanData <- loanData[order(-loanData$rate,-loanData$principal),]
debtSnowball <- function( loanData, monthlyPayment,
iterations = Inf, daysInMonth = 30, startingMonth = 1,
monthlyPaymentCushion = 1 )
{
paidAmounts <- list( interest = 0, originalPrincipal = loanData$principal,
principal = 0, total = 0, payments = integer( nrow(loanData) ),
years = integer( nrow(loanData) ), allocation = list() )
# loanData <- loanData[order(loanData$principal),]
while ( sum( loanData$principal ) > 0 && sum( paidAmounts$payments ) < iterations )
{
# Calculate daily interest
loanData$dailyInterest <- with( loanData, principal * rate / 365.242199 )
# Calculate monthly interest
loanData$monthlyInterest <- with( loanData, dailyInterest * daysInMonth )
# Calculate total balance
loanData$balance <- with( loanData, principal + monthlyInterest )
# Calculate min payment
# Simplified to $1 more than the monthly interest
loanData$minPayment <- with( loanData,
ifelse( monthlyInterest + monthlyPaymentCushion > balance,
balance, monthlyInterest + monthlyPaymentCushion ) )
# Sanity check
if ( sum( loanData$minPayment ) > monthlyPayment )
stop( "Monthly payment must be greater than all minimum payments" )
# Initialize ACTUAL payments
loanData$actualPayment <- sapply( rownames(loanData), function( l )
{
min( loanData[l,c('balance','minPayment')] )
} )
# Calculate Sum of Actual Payments to be applied
calculateSAP <- function() { SAP <<- sum( loanData$actualPayment ) }
calculateSAP()
# Calculate ACTUAL payment
for ( l in 1:nrow(loanData) )
{
if ( loanData[l,'balance'] > 0 )
{
loanData[l,'actualPayment'] <- min( loanData[l,'balance'],
loanData[l,'actualPayment'] + (monthlyPayment - SAP) )
calculateSAP()
# Allocate the rest of the monthly budget
if ( SAP <= 0 )
break
}
}
# APPLY the payment and update totals
paidAmounts$interest <- round( paidAmounts$interest + loanData$monthlyInterest, 2 )
paidAmounts$principal <- round( paidAmounts$principal +
( loanData$actualPayment - loanData$monthlyInterest ), 2 )
paidAmounts$total <- round( paidAmounts$total + loanData$actualPayment, 2 )
snowballLoan <- min( which( loanData$principal > 0 ) )
paidAmounts$payments[snowballLoan] <- paidAmounts$payments[snowballLoan] + 1
paidAmounts$years[snowballLoan] <- round(
paidAmounts$payments[snowballLoan] * daysInMonth / 365, 2 )
paidAmounts$allocation[[sum(paidAmounts$payments)]] <- loanData
loanData$principal <- with( loanData, (principal + monthlyInterest) - actualPayment )
}
return( paidAmounts )
}
dS <- function( data = loanData, ... )
{
test <<- debtSnowball( loanData = data, ... )
test.1 <<- test$allocation[[1]]
output <- as.data.frame( test[-length(test)] )
output <- rbind( output, sapply( test[-length(test)], sum ) )
rownames(output)[nrow(output)] <- "Totals"
return( output )
}
dS( monthlyPayment = 1062.78, monthlyPaymentCushion = 0 )
aggregate( . ~ group, test.1, sum )
test.1[test.1$group == 'navient',]
sapply( test.1[-(1:3)], sum )
sum(test.1[test.1$group %in% c('grad_plus','stafford'),'actualPayment'])
@aaiezza
Copy link
Author

aaiezza commented Oct 25, 2020

Debt Snowball Pseudocode

data = loan balances, interest rates

Create a function that iterates through
monthly payments with inputs:

  • loan balances and rates
  • monthly payment amount ($1062.78)
  • starting month
    and outputs:
  • The total amount paid
  • The total amount of interest paid
  • The total amount of principal paid

If combined minimum payments >
monthly payment amount, throw error
While balances on any loans > 0:
{

  1. calculate for every loan:
  • daily interest
  • monthly interest
  • min payment
  • remaining balance (minus all previous payments and add monthly interest)
  • actual payment VIA:
  1. for every loan starting with highest balance:
    if remaining balance == 0:
    quit loop
    else if remaining balance greater than 0:
    if remaining balance less than min payment:
    actual payment = remaining balance
    else actual payment = min payment
    else ignore
  2. calculate the sum of the
    actual payments to be applied (SAP)
  3. for every loan starting with lowest balance:
    if remaining balance == 0:
    next
    else: set actual payment to
    previous actual payment +
    total monthly payment - SAP
  4. update running totals:
  • total interest paid
  • total principal paid
  • total amount paid
  • reduce the remaining balances of loans by
    actual monthly payment
    }

return all of our values

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment