From e9922e30c1c0290adf234fe0c01b3e8167d98738 Mon Sep 17 00:00:00 2001 From: Robert Dyer Date: Thu, 8 May 2025 14:14:23 -0400 Subject: [PATCH] support other interest compounding types (#24) Co-authored-by: Chris Weiss --- README.md | 5 +++++ apply-interest.js | 32 +++++++++++++++++++++++++++----- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index c8eb517..7118e49 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,11 @@ As an example, if your loan is at 4.5% interest and you want to insert an interest transaction on the 28th of the month, set the account note to `interestRate:0.045 interestDay:28`. +By default, interest is calculated using the 30/360 method where interest is +computed monthly using 30/360 (or 1/12) of the interest rate. If you need to +compute interest using the ACTUAL/ACTUAL method, set `interest:actual` in the +note. If you need to compute interest daily, set `interest:daily`. + You can optionally change the payee used for the interest transactions by setting `INTEREST_PAYEE_NAME` in the `.env` file. diff --git a/apply-interest.js b/apply-interest.js index 024eb46..4d9d3b8 100644 --- a/apply-interest.js +++ b/apply-interest.js @@ -2,6 +2,11 @@ const api = require('@actual-app/api'); const { closeBudget, ensurePayee, getAccountBalance, getAccountNote, getLastTransactionDate, getTagValue, openBudget, showPercent } = require('./utils'); require("dotenv").config(); +function daysInYear(year) { + // Check if the year is a leap year + return ((year % 4 === 0 && year % 100 > 0) || year %400 == 0) ? 366 : 365; +} + (async () => { await openBudget(); @@ -16,9 +21,12 @@ require("dotenv").config(); const note = await getAccountNote(account); if (note) { - if (note.indexOf('interestRate:') > -1 && note.indexOf('interestDay:') > -1) { - let interestRate = parseFloat(getTagValue(note, 'interestRate')); - const interestDay = parseInt(getTagValue(note, 'interestDay')); + let interestRate = parseFloat(getTagValue(note, 'interestRate', 0.0)); + const interestDay = parseInt(getTagValue(note, 'interestDay', 0)); + + if (interestRate && interestDay) { + const kind = getTagValue(note, 'interest', 'monthly'); + const isDaily = kind == 'daily'; const interestTransactionDate = new Date(); if (interestTransactionDate.getDate() < interestDay) { @@ -35,8 +43,22 @@ require("dotenv").config(); if (!lastDate) continue; const daysPassed = Math.floor((interestTransactionDate - new Date(lastDate)) / 86400000); + let period = 12; + let numPeriods = 1 + switch (kind) { + case 'daily': + period = daysInYear(interestTransactionDate.getFullYear()); + numPeriods = daysPassed; + break; + case 'actual': + period = daysInYear(interestTransactionDate.getFullYear()) / daysPassed; + break; + default: + break; + } + const balance = await getAccountBalance(account, interestTransactionDate); - const compoundedInterest = Math.round(balance * (Math.pow(1 + interestRate / 12, 1) - 1)); + const compoundedInterest = Math.round(balance * (Math.pow(1 + interestRate / period, numPeriods) - 1)); interestRate = showPercent(interestRate); @@ -52,7 +74,7 @@ require("dotenv").config(); payee: payeeId, amount: compoundedInterest, cleared: true, - notes: `Interest for 1 month at ${interestRate}`, + notes: `Interest for ${daysPassed} days, ${balance / 100.0} at ${interestRate} (${isDaily ? "daily" : "monthly"})`, }]); } }