Skip to content

Instantly share code, notes, and snippets.

@DiTo97
Last active December 30, 2024 16:10
Show Gist options
  • Save DiTo97/8de5b0401ee131b3effc849c17b73e55 to your computer and use it in GitHub Desktop.
Save DiTo97/8de5b0401ee131b3effc849c17b73e55 to your computer and use it in GitHub Desktop.
savings simulation on poste.it options
import argparse
import bisect
from typing import Any
from gekko import GEKKO
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
) -> tuple[float, float]:
"""PostaFuturo da Grande deposit policy"""
netinterest = grossinterest * (1 - deduction / 100)
deposit = instalment * 12 * maxyears
balance = compounding(deposit, netinterest, maxyears)
return balance, deposit
def individual_monthly_savings(
instalment: float,
maxyears: int,
grossinterestmap: dict[int, float],
deduction: float
) -> tuple[float, float]:
"""Piccoli e Buoni savings plan"""
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)
deposit = 0.0
balance = 0.0
for month in range(1, maxmonth):
month = endmonth - month + 1
month = smallest_number_greater_or_equal(netinterestseq, month)
netinterest = netinterestmap[month]
deposit += instalment
balance += compounding(instalment, netinterest, month // 12)
return balance, deposit
def simulation(instalment: float) -> tuple[int, int, int, int]:
model = GEKKO(remote=False)
cumulative_deposit_maxyears = model.Var(value=5, lb=0, ub=10, integer=True)
individual_monthly_maxyears = model.Var(value=5, lb=0, ub=8, integer=True)
n = model.Var(value=1, lb=0, ub=2, integer=True)
m = model.Var(value=1, lb=0, ub=1, integer=True)
def objective() -> float:
cumulative_deposit_X = cumulative_deposit(
instalment, cumulative_deposit_maxyears.value.value, **cumulative_deposit_config
)
individual_monthly_X = individual_monthly_savings(
instalment, individual_monthly_maxyears.value.value, **individual_monthly_config
)
balance = n * cumulative_deposit_X[0] + m * individual_monthly_X[0]
deposit = n * cumulative_deposit_X[1] + m * individual_monthly_X[1]
return balance - deposit
model.Maximize(objective())
model.Equation(cumulative_deposit_maxyears + individual_monthly_maxyears <= 18)
model.Equation(n + m <= 2)
model.Equation(n * instalment * 12 <= 6000)
model.options.SOLVER = 1 # https://stackoverflow.com/a/71580462
model.solve(disp=False)
return (
cumulative_deposit_maxyears.value.value[0],
individual_monthly_maxyears.value.value[0],
n.value.value[0],
m.value.value[0],
)
def commandline() -> dict[str, Any]:
parser = argparse.ArgumentParser()
parser.add_argument(
'--instalment',
type=float,
default=50.0,
help="fixed monthly deposit amount"
)
args, _ = parser.parse_known_args()
args = vars(args)
return args
def main(instalment: float):
(
cumulative_deposit_maxyears,
individual_monthly_maxyears,
n,
m
) = map(int, simulation(instalment))
balance = cumulative_deposit(
instalment, cumulative_deposit_maxyears, **cumulative_deposit_config
)
print("# of cumulative deposit", n)
print("cumulative deposit", balance)
balance = individual_monthly_savings(
instalment, individual_monthly_maxyears, **individual_monthly_config
)
print("# of individual monthly", m)
print("individual monthly", balance)
if __name__ == "__main__":
args = commandline()
main(**args)

savings simulation

simulates two children savings options: cumulative deposit and individual monthly savings.

The script will help parents decide the best approach to maximize net profit for their children future.

overview

The script simulates two interesting poste.it savings options:

  1. cumulative deposit: policy with a fixed monthly rate, compounded annually, with a maximum annual deposit limit1;
  2. individual monthly savings: monthly deposits that mature independently with varying interest rates2.

what's the most profitable strategy?

usage

python savings-simulation.py --help

example

python savings-simulation.py --instalment 50.0 

Footnotes

  1. PostaFuturo da Grande deposit policy

  2. Piccoli e Buoni savings plan

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