|
import argparse |
|
import bisect |
|
from typing import Any |
|
|
|
|
|
cumulative_deposit_config = { |
|
"grossinterest": 2.6, |
|
"deduction": 3.5 + 1.5, |
|
} |
|
|
|
|
|
individual_monthly_config = { |
|
"grossinterestmap": { |
|
18: 2.5, |
|
24: 2.5, |
|
36: 2.5, |
|
48: 2.5, |
|
60: 2.5, |
|
72: 2.75, |
|
84: 2.75, |
|
96: 3.0, |
|
108: 3.0, |
|
120: 3.0, |
|
132: 3.0, |
|
144: 3.0, |
|
156: 3.0, |
|
168: 3.0, |
|
180: 3.0, |
|
192: 4.0, |
|
204: 6.0, |
|
216: 6.0 |
|
}, |
|
"deduction": 12.5, |
|
} |
|
|
|
|
|
def compounding( |
|
deposit: float, netinterest: float, maxyears: int, instalment: float = 0.0 |
|
) -> float: |
|
"""compounding interest effect""" |
|
for _ in range(maxyears): |
|
deposit += instalment |
|
deposit *= (1 + netinterest / 100) |
|
|
|
return deposit |
|
|
|
|
|
def smallest_number_greater_or_equal(sequence: list[int], number: int) -> int: |
|
"""finds the smallest number in a sequence that is greater than or equal to a number""" |
|
index = bisect.bisect_left(sequence, number) |
|
|
|
if index < len(sequence): |
|
return sequence[index] |
|
|
|
message = f"sequence has no number greater than or equal to {number}" |
|
raise ValueError(message) |
|
|
|
|
|
def cumulative_deposit( |
|
instalment: float, maxyears: int, grossinterest: float, deduction: float |
|
) -> float: |
|
"""PostaFuturo da Grande deposit policy""" |
|
assert 0 < maxyears, "deposit policy must last more than 0 years" |
|
|
|
if instalment * 12 > 6000: |
|
message = "instalment exceeds annual limit of 6000 €" |
|
raise ValueError(message) |
|
|
|
maxyears = min(maxyears, 10) |
|
|
|
netinterest = grossinterest * (1 - deduction / 100) |
|
|
|
deposit = instalment * 12 * maxyears |
|
balance = compounding(deposit, netinterest, maxyears) |
|
|
|
return balance |
|
|
|
|
|
def individual_monthly_savings( |
|
instalment: float, |
|
maxyears: int, |
|
grossinterestmap: dict[int, float], |
|
deduction: float |
|
) -> float: |
|
"""Piccoli e Buoni savings plan""" |
|
assert 0 < maxyears, "savings plan must last more than 0 years" |
|
|
|
netinterestmap = { |
|
key: value * (1 - deduction / 100) for key, value in grossinterestmap.items() |
|
} |
|
|
|
netinterestseq = sorted(netinterestmap.keys()) |
|
|
|
endmonth = 18 * 12 |
|
maxmonth = min(maxyears * 12, endmonth - 18) |
|
|
|
balance = 0 |
|
|
|
for month in range(1, maxmonth): |
|
month = endmonth - month + 1 |
|
month = smallest_number_greater_or_equal(netinterestseq, month) |
|
|
|
netinterest = netinterestmap[month] |
|
|
|
balance += compounding(instalment, netinterest, month // 12) |
|
|
|
return balance |
|
|
|
|
|
def commandline() -> dict[str, Any]: |
|
parser = argparse.ArgumentParser() |
|
|
|
parser.add_argument( |
|
'--instalment', |
|
type=float, |
|
default=50.0, |
|
help="fixed monthly deposit amount" |
|
) |
|
|
|
parser.add_argument( |
|
'--cumulative-deposit-maxyears', |
|
type=int, |
|
default=10, |
|
help="max duration of cumulative deposit" |
|
) |
|
|
|
parser.add_argument( |
|
'--individual-monthly-maxyears', |
|
type=int, |
|
default=8, |
|
help="max duration of individual monthly savings" |
|
) |
|
|
|
args, _ = parser.parse_known_args() |
|
args = vars(args) |
|
|
|
return args |
|
|
|
|
|
def main( |
|
instalment: float, |
|
cumulative_deposit_maxyears: int, |
|
individual_monthly_maxyears: int |
|
): |
|
balance = cumulative_deposit( |
|
instalment, cumulative_deposit_maxyears, **cumulative_deposit_config |
|
) |
|
print("cumulative deposit", balance) |
|
|
|
balance = individual_monthly_savings( |
|
instalment, individual_monthly_maxyears, **individual_monthly_config |
|
) |
|
print("individual monthly", balance) |
|
|
|
|
|
if __name__ == "__main__": |
|
args = commandline() |
|
main(**args) |