Skip to content

Commit

Permalink
✨ Option to set frequency (#145)
Browse files Browse the repository at this point in the history
* ✨ Option to set frequency

- 🔥 Drop Python 3.7
- ⬆️ Update dependencies

* Fix annotations
  • Loading branch information
roniemartinez authored Jul 6, 2023
1 parent eea7a69 commit 7a08162
Show file tree
Hide file tree
Showing 10 changed files with 185 additions and 191 deletions.
4 changes: 3 additions & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@

version: 2
updates:
- package-ecosystem: "pip" # See documentation for possible values

# Version updates for Python
- package-ecosystem: "pip"
directory: "/" # Location of package manifests
schedule:
interval: "daily"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
strategy:
matrix:
os: [ ubuntu-latest, macos-latest, windows-latest ]
python-version: [ '3.7', '3.8', '3.9', '3.10', '3.11' ]
python-version: [ '3.8', '3.9', '3.10', '3.11' ]
include:
- os: ubuntu-latest
pip-cache: ~/.cache/pip
Expand Down
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,15 @@ print(

```bash
amortize -h
usage: amortize [-h] -P PRINCIPAL -r INTEREST_RATE [-s] (-n PERIOD | -a AMOUNT)
usage: amortize [-h] -P PRINCIPAL -r INTEREST_RATE [-s] [-f {daily,biweekly,weekly,semimonthly,monthly,quarterly,semiyearly,yearly}] (-n PERIOD | -a AMOUNT)

Python library for calculating amortizations and generating amortization schedules

options:
-h, --help show this help message and exit
-s, --schedule Generate amortization schedule
-f {daily,biweekly,weekly,semimonthly,monthly,quarterly,semiyearly,yearly}, --frequency {daily,biweekly,weekly,semimonthly,monthly,quarterly,semiyearly,yearly}
Payment frequency
-n PERIOD, --period PERIOD
Total number of periods
-a AMOUNT, --amount AMOUNT
Expand All @@ -101,13 +103,14 @@ required arguments:
-P PRINCIPAL, --principal PRINCIPAL
Principal amount
-r INTEREST_RATE, --interest-rate INTEREST_RATE
Interest rate per period
Interest rate per year
```

```bash
amortize -P 150000 -n 36 -r 0.1 # period
amortize -P 150000 -n 36 -r 0.1 -s # schedule
amortize -P 150000 -a 4840.08 -r 0.1 # amount
amortize -P 150000 -n 36 -r 0.1 # period
amortize -P 150000 -n 36 -r 0.1 -s # schedule
amortize -P 150000 -a 4840.08 -r 0.1 # amount
amortize -P 150000 -n 36 -r 0.1 -f weekly # period (specify payment frequency)
```

```bash
Expand Down
29 changes: 25 additions & 4 deletions amortization/amortize.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from typing import Any, List # pragma: no cover

from amortization.amount import calculate_amortization_amount # pragma: no cover
from amortization.enums import PaymentFrequency # pragma: no cover
from amortization.period import calculate_amortization_period
from amortization.schedule import amortization_schedule # pragma: no cover

Expand All @@ -10,6 +11,10 @@ def main() -> None: # pragma: no cover

from tabulate import tabulate

class ConvertFrequency(argparse.Action):
def __call__(self, parser: Any, namespace: Any, values: Any, option_string: Any = None) -> None:
setattr(namespace, self.dest, PaymentFrequency[values.upper()])

parser = argparse.ArgumentParser(
description="Python library for calculating amortizations and generating amortization schedules"
)
Expand All @@ -29,7 +34,7 @@ def main() -> None: # pragma: no cover
dest="interest_rate",
type=float,
required=True,
help="Interest rate per period",
help="Interest rate per year",
)
# optional parameters
parser.add_argument(
Expand All @@ -40,6 +45,16 @@ def main() -> None: # pragma: no cover
action="store_true",
help="Generate amortization schedule",
)
parser.add_argument(
"-f",
"--frequency",
dest="frequency",
default=PaymentFrequency.MONTHLY,
type=str,
action=ConvertFrequency,
choices=("daily", "biweekly", "weekly", "semimonthly", "monthly", "quarterly", "semiyearly", "yearly"),
help="Payment frequency",
)
mutually_exclusive = parser.add_mutually_exclusive_group(required=True)
mutually_exclusive.add_argument(
"-n",
Expand All @@ -61,7 +76,9 @@ def main() -> None: # pragma: no cover
parser.error("-s/--schedule requires -n/--period")
total_paid = total_interest = total_principal = 0.0
table: List[Any] = []
for row in amortization_schedule(arguments.principal, arguments.interest_rate, arguments.period):
for row in amortization_schedule(
arguments.principal, arguments.interest_rate, arguments.period, arguments.frequency
):
table.append(row)
total_paid += row[1]
total_interest += row[2]
Expand All @@ -76,8 +93,12 @@ def main() -> None: # pragma: no cover
)
)
elif arguments.amount:
period = calculate_amortization_period(arguments.principal, arguments.interest_rate, arguments.amount)
period = calculate_amortization_period(
arguments.principal, arguments.interest_rate, arguments.amount, arguments.frequency
)
print("Amortization period: {}".format(period))
else:
amount = calculate_amortization_amount(arguments.principal, arguments.interest_rate, arguments.period)
amount = calculate_amortization_amount(
arguments.principal, arguments.interest_rate, arguments.period, arguments.frequency
)
print("Amortization amount: {:,.2f}".format(amount))
15 changes: 12 additions & 3 deletions amortization/amount.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
def calculate_amortization_amount(principal: float, interest_rate: float, period: int) -> float:
from amortization.enums import PaymentFrequency


def calculate_amortization_amount(
principal: float, interest_rate: float, period: int, payment_frequency: PaymentFrequency = PaymentFrequency.MONTHLY
) -> float:
"""
Calculates Amortization Amount per period
>>> calculate_amortization_amount(150000, 0.1, 36)
4840.08
>>> calculate_amortization_amount(150000, 0.1, 36, PaymentFrequency.SEMIMONTHLY)
4495.63
:param principal: Principal amount
:param interest_rate: Interest rate per period
:param interest_rate: Interest rate per year
:param period: Total number of period
:param payment_frequency: Payment frequency per year
:return: Amortization amount per period
"""
adjusted_interest = interest_rate / 12
adjusted_interest = interest_rate / payment_frequency.value
x = (1 + adjusted_interest) ** period
return round(principal * (adjusted_interest * x) / (x - 1), 2)
12 changes: 12 additions & 0 deletions amortization/enums.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from enum import Enum


class PaymentFrequency(Enum):
DAILY = 365
BIWEEKLY = 104
WEEKLY = 52
SEMIMONTHLY = 24
MONTHLY = 12
QUARTERLY = 4
SEMIYEARLY = 2
YEARLY = 1
17 changes: 14 additions & 3 deletions amortization/period.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
from math import log

from amortization.enums import PaymentFrequency

def calculate_amortization_period(principal: float, interest_rate: float, amount: float) -> int:

def calculate_amortization_period(
principal: float,
interest_rate: float,
amount: float,
payment_frequency: PaymentFrequency = PaymentFrequency.MONTHLY,
) -> int:
"""
Calculates the number of period needed for a given amortization amount
>>> calculate_amortization_period(150000, 0.1, 4840.08)
36
>>> calculate_amortization_period(150000, 0.1, 4500, PaymentFrequency.WEEKLY)
34
:param principal: Principal amount
:param interest_rate: Interest rate per period
:param interest_rate: Interest rate per year
:param amount: Amortization amount per period
:param payment_frequency: Payment frequency per year
:return: Total number of period
"""
adjusted_interest = interest_rate / 12
adjusted_interest = interest_rate / payment_frequency.value
return round(log(amount / (amount - adjusted_interest * principal), 1 + adjusted_interest))
10 changes: 6 additions & 4 deletions amortization/schedule.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
from typing import Iterator, Tuple

from amortization.amount import calculate_amortization_amount
from amortization.enums import PaymentFrequency


def amortization_schedule(
principal: float, interest_rate: float, period: int
principal: float, interest_rate: float, period: int, payment_frequency: PaymentFrequency = PaymentFrequency.MONTHLY
) -> Iterator[Tuple[int, float, float, float, float]]:
"""
Generates amortization schedule
:param principal: Principal amount
:param interest_rate: Interest rate per period
:param interest_rate: Interest rate per year
:param period: Total number of periods
:param payment_frequency: Payment frequency per year
:return: Rows containing period, amount, interest, principal, balance, etc
"""
amortization_amount = calculate_amortization_amount(principal, interest_rate, period)
adjusted_interest = interest_rate / 12
amortization_amount = calculate_amortization_amount(principal, interest_rate, period, payment_frequency)
adjusted_interest = interest_rate / payment_frequency.value
balance = principal
for number in range(1, period + 1):
interest = round(balance * adjusted_interest, 2)
Expand Down
Loading

0 comments on commit 7a08162

Please sign in to comment.