Advertisement
andrejpodzimek

Effective Interest Rate Calculator

Mar 30th, 2021 (edited)
570
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 5.17 KB | None | 0 0
  1. #!/usr/bin/python
  2.  
  3. from sys import argv
  4. from sys import exit
  5. from sys import stderr
  6. from math import inf
  7.  
  8. # The magic equation solved by this program:
  9. #
  10. # growth := 1 + interest
  11. #
  12. #                            growth * periodic_deposit + interest * outcome
  13. # interest ** periods == ------------------------------------------------------
  14. #                        growth * periodic_deposit + interest * initial_deposit
  15.  
  16. def check_inputs(periods, initial_deposit, periodic_deposit, outcome):
  17.   print(f'         # periods: {periods}', file=stderr)
  18.   print(f'   initial deposit: {initial_deposit}', file=stderr)
  19.   print(f'  periodic deposit: {periodic_deposit}', file=stderr)
  20.   print(f'           outcome: {outcome}', file=stderr)
  21.   errors = []
  22.   periods_ok, initial_deposit_ok, periodic_deposit_ok, outcome_ok = (
  23.       False, False, False, False)
  24.   try:
  25.     periods = int(periods)
  26.     if periods <= 0:
  27.       errors.append(f'[# periods]: expected > 0, got: {periods}')
  28.     else:
  29.       periods_ok = True
  30.   except Exception as error:
  31.     errors.append(f'[# periods]: {error}')
  32.   try:
  33.     initial_deposit = int(initial_deposit)
  34.     if initial_deposit < 0:
  35.       errors.append(f'[initial deposit]: expected >= 0, got: {initial_deposit}')
  36.     else:
  37.       initial_deposit_ok = True
  38.   except Exception as error:
  39.     errors.append(f'[initial deposit]: {error}')
  40.   try:
  41.     periodic_deposit = int(periodic_deposit)
  42.     if periodic_deposit < 0:
  43.       errors.append('[periodic deposit]: expected >= 0, got: '
  44.                     f'{periodic_deposit}')
  45.     else:
  46.       periodic_deposit_ok = True
  47.   except Exception as error:
  48.     errors.append(f'[periodic deposit]: {error}')
  49.   try:
  50.     outcome = int(outcome)
  51.     if outcome <= 0:
  52.       errors.append(f'[outcome]: expected > 0, got: {outcome}')
  53.     else:
  54.       outcome_ok = True
  55.   except Exception as error:
  56.     errors.append(f'[outcome]: {error}')
  57.   if initial_deposit_ok and periodic_deposit_ok:
  58.     if initial_deposit + periodic_deposit <= 0:
  59.       errors.append('[initial_deposit] + [periodic_deposit]: expected > 0, '
  60.                     f'got: {initial_deposit} + {periodic_deposit} <= 0')
  61.     elif periods_ok:  # elif, as this makes no sense with both deposits == 0
  62.       sum_of_payments = initial_deposit + periods * periodic_deposit
  63.       print(f'   sum of payments: {sum_of_payments}', file=stderr)
  64.       if outcome_ok:
  65.         if outcome < sum_of_payments:
  66.           errors.append('[initial deposit] + [periods] * [periodic deposit]: '
  67.                         'expected >= [outcome], got: '
  68.                         f'{outcome} < {sum_of_payments}')
  69.         else:
  70.           absolute_gain = outcome - sum_of_payments
  71.           relative_gain = f'{round(100 * absolute_gain / sum_of_payments, 6)}%'
  72.           print(f'     absolute gain: {absolute_gain} ', file=stderr)
  73.           print(f'     relative gain: {relative_gain}', file=stderr)
  74.   if errors:
  75.     raise ValueError('\n'.join(errors))
  76.   return periods, initial_deposit, periodic_deposit, outcome
  77.  
  78. class Savings:
  79.   def __init__(self, initial_deposit, periodic_deposit, outcome):
  80.     self.periodic_deposit = periodic_deposit / outcome  # normalized
  81.     self.outcome_periodic = self.periodic_deposit + 1  # 1 == outcome
  82.     self.initial_periodic = self.periodic_deposit + initial_deposit / outcome
  83.  
  84.   def ugly_fraction(self, interest):
  85.     den = interest * self.initial_periodic + self.periodic_deposit
  86.     return (((interest * self.outcome_periodic + self.periodic_deposit) / den)
  87.             if den else inf)
  88.  
  89. def percent(fn):
  90.   return lambda *args, **kwargs: f'{round(100 * fn(*args, **kwargs), 6)}%'
  91.  
  92. @percent
  93. def effective_interest(periods, initial_deposit, periodic_deposit, outcome):
  94.   savings = Savings(initial_deposit, periodic_deposit, outcome)
  95.   lower, upper = 0, .01
  96.   while savings.ugly_fraction(upper) > (1 + upper) ** periods:
  97.     upper *= 2
  98.   diff = upper - lower
  99.   last_diff = 2 * diff
  100.   while last_diff > diff:
  101.     interest = (upper + lower) / 2
  102.     if savings.ugly_fraction(interest) > (1 + interest) ** periods:
  103.       lower = interest
  104.     else:
  105.       upper = interest
  106.     diff, last_diff = upper - lower, diff
  107.   return interest
  108.  
  109. def usage():
  110.   return f'''
  111. Usage: {argv[0]} [# periods] [initial deposit] [periodic deposit] [outcome]
  112.  
  113. Examples:
  114.  
  115. * 0 coins initially, 1 coin periodically, 20 coins expected in 10 periods:
  116.  
  117.    $ {argv[0]} 10 0 1 20
  118.    {effective_interest(10, 0, 1, 20)}
  119.  
  120. * 1 coin initially, 1 coin periodically, 20 coinst expected in 10 periods:
  121.  
  122.    $ {argv[0]} 10 1 1 20
  123.    {effective_interest(10, 1, 1, 20)}
  124.  
  125. * 10 coins initially, 0 coins periodically, 20 coins expected in 10 periods:
  126.  
  127.    $ {argv[0]} 10 10 0 20
  128.    {effective_interest(10, 10, 0, 20)}
  129.  
  130. * 10 coins initially, 1 coin periodically, 20 coins expected in 10 periods:
  131.  
  132.    $ {argv[0]} 10 10 1 20
  133.    {effective_interest(10, 10, 1, 20)}
  134. '''
  135.  
  136. if len(argv) != 5:
  137.   print(usage(), file=stderr)
  138.   exit(1)
  139.  
  140. try:
  141.   inputs = check_inputs(*argv[1:])
  142.   print('effective interest: ', file=stderr, end='', flush=True)
  143.   print(effective_interest(*inputs))
  144. except ValueError as error:
  145.   print(f'\nERRORS:\n{error}', file=stderr)
  146.   print(usage(), file=stderr)
  147.   exit(2)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement