andrejpodzimek

Effective Interest Rate Calculator

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