Use standard monthly compounding for all loan types
Daily accrual made HELOC estimates worse due to front-loaded 31-day months. Revert to rate/12 for all types - it's the standard amortization method and works well for mortgages and personal loans. For HELOCs and auto loans, differences from bank statements are typically due to variable rates, extra payments, or daily billing conventions that a fixed amortization schedule cannot capture. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -49,7 +49,7 @@ public class LoanService : ILoanService
|
||||
ExtraPayment = request.ExtraPayment
|
||||
};
|
||||
|
||||
var entries = GenerateAmortizationSchedule(loanDetail, account.Type);
|
||||
var entries = GenerateAmortizationSchedule(loanDetail);
|
||||
loanDetail.AmortizationEntries = entries;
|
||||
|
||||
_db.LoanDetails.Add(loanDetail);
|
||||
@@ -82,7 +82,7 @@ public class LoanService : ILoanService
|
||||
l.EscrowAmount = request.EscrowAmount;
|
||||
l.ExtraPayment = request.ExtraPayment;
|
||||
|
||||
var entries = GenerateAmortizationSchedule(l, account.Type);
|
||||
var entries = GenerateAmortizationSchedule(l);
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
entry.LoanDetailId = l.Id;
|
||||
@@ -118,14 +118,11 @@ public class LoanService : ILoanService
|
||||
|
||||
// Build new schedule with extra payments
|
||||
var balance = Math.Abs(account.Balance);
|
||||
var useDailyAccrual = UseDailyAccrual(account.Type);
|
||||
var monthlyRate = l.InterestRate / 100m / 12m;
|
||||
var dailyRate = l.InterestRate / 100m / 365m;
|
||||
var extraMonthly = request.ExtraMonthlyPayment ?? 0;
|
||||
var basePayment = l.MonthlyPayment - (l.EscrowAmount ?? 0);
|
||||
var now = DateTime.UtcNow;
|
||||
var startDate = new DateTime(now.Year, now.Month, l.OriginationDate.Day, 0, 0, 0, DateTimeKind.Utc);
|
||||
var previousDate = startDate;
|
||||
|
||||
var newEntries = new List<AmortizationEntryResponse>();
|
||||
int paymentNum = 0;
|
||||
@@ -142,15 +139,12 @@ public class LoanService : ILoanService
|
||||
paymentNum++;
|
||||
var paymentDate = startDate.AddMonths(paymentNum);
|
||||
|
||||
var interest = useDailyAccrual
|
||||
? Math.Round(balance * dailyRate * (paymentDate - previousDate).Days, 2)
|
||||
: Math.Round(balance * monthlyRate, 2);
|
||||
var interest = Math.Round(balance * monthlyRate, 2);
|
||||
var principal = Math.Min(basePayment + extraMonthly - interest, balance);
|
||||
if (principal < 0) principal = 0;
|
||||
|
||||
balance -= principal;
|
||||
if (balance < 0.01m) balance = 0;
|
||||
previousDate = paymentDate;
|
||||
|
||||
newEntries.Add(new AmortizationEntryResponse(
|
||||
paymentNum, paymentDate, principal + interest + (l.EscrowAmount ?? 0),
|
||||
@@ -187,30 +181,21 @@ public class LoanService : ILoanService
|
||||
return new LoanDefaultsResponse(0, interestRate, termMonths, hasSyncData);
|
||||
}
|
||||
|
||||
private static bool UseDailyAccrual(AccountType type) =>
|
||||
type is AccountType.AutoLoan or AccountType.LineOfCredit;
|
||||
|
||||
private static List<AmortizationEntry> GenerateAmortizationSchedule(LoanDetail loan, AccountType accountType)
|
||||
private static List<AmortizationEntry> GenerateAmortizationSchedule(LoanDetail loan)
|
||||
{
|
||||
var entries = new List<AmortizationEntry>();
|
||||
var balance = loan.OriginalBalance;
|
||||
var useDailyAccrual = UseDailyAccrual(accountType);
|
||||
var monthlyRate = loan.InterestRate / 100m / 12m;
|
||||
var dailyRate = loan.InterestRate / 100m / 365m;
|
||||
var payment = loan.MonthlyPayment - (loan.EscrowAmount ?? 0);
|
||||
var extra = loan.ExtraPayment ?? 0;
|
||||
var previousDate = loan.OriginationDate.AddMonths(-1);
|
||||
|
||||
for (int i = 1; i <= loan.TermMonths && balance > 0; i++)
|
||||
{
|
||||
var paymentDate = loan.OriginationDate.AddMonths(i - 1);
|
||||
var interest = useDailyAccrual
|
||||
? Math.Round(balance * dailyRate * (paymentDate - previousDate).Days, 2)
|
||||
: Math.Round(balance * monthlyRate, 2);
|
||||
var interest = Math.Round(balance * monthlyRate, 2);
|
||||
var principal = Math.Min(payment + extra - interest, balance);
|
||||
balance -= principal;
|
||||
if (balance < 0.01m) balance = 0;
|
||||
previousDate = paymentDate;
|
||||
|
||||
entries.Add(new AmortizationEntry
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user