0

I am trying to find the best rate of savings to achieve a down payment on a $1million house in 36 months. savings need to be within 100$ of required down payment. downpayment is 25% of total cost. I have to search for an integer between 0 and 10000 (using integer division), and then convert it to a decimal percentage (using float division) to use when we are calculating the current_savings​ after 36 months.

This is my code which is not working (I am really new to programming)

annual_salary = 150000
total_cost = 1000000
low=0
high=10000
portion_saved=(low+high)/20000.0
epsilon=100
current_savings = 0
portion_down_payment=0.25*total_cost
steps = 0
rate = 0.04
number_of_months = 0
semi_annual_raise = 0.07

while current_savings-portion_down_payment>=epsilon and number_of_months<=36:
current_savings += annual_salary * portion_saved / 12
current_savings += current_savings * rate / 12
number_of_months += 1
if number_of_months % 6 == 0:
annual_salary += annual_salary * semi_annual_raise

if current_savings<portion_down_payment:
    low=portion_saved
else:
    high=portion_saved
portion_saved=(low+high)/20000.0
steps+=1

print("Best savings rate: ", portion_saved)
print("Steps in bisection search", steps)

Any help is greatly appreciated !!!

2
  • 1
    Please expand on "not working" with a minimal reproducible example. Commented Jul 23, 2017 at 13:19
  • This might be easier if you broke your code into functions. Have a function which, given a savings rate, returns the balance after 36 months. Have a function which (given two endpoints, a target value, and a function) does bisection search (alternatively a Newton-Raphson solver would be more efficient, given that the input function is smooth and not just monotonic). Commented Jul 23, 2017 at 13:52

1 Answer 1

1

The monthly income does not vary with savings rate, so it makes sense to only calculate it once:

# calculate income per month over 36 months
base_monthly_salary = 150000 // 12
semiannual_raise = 0.07
monthly_incomes = [base_monthly_salary * (1. + semiannual_raise) ** (month // 6) for month in range(36)]

If the monthly savings do not earn interest, the problem is trivial:

target_amount = 1000000. * 0.25
savings_rate = target_amount / sum(monthly_incomes)    # 0.4659859

so you would have to save 46.6% of income.

If monthly savings earn interest, the problem is more interesting (bad pun absolutely intended).

def savings_balance(monthly_incomes, monthly_interest_rate, savings_rate):
    total = 0.
    for amt in monthly_incomes:
        # At the end of each month,
        total *= 1. + monthly_interest_rate  # we earn interest on what was already saved
        total += amt * savings_rate          # and add a fixed fraction of our monthly income
    return total

Let's test it based on our calculation above,

savings_balance(monthly_incomes, 0.0, 0.4659859)   # 249999.9467

so that looks like what we expected.

You can think of this function as iteratively evaluating a 36th-degree polynomial. Given known monthly_incomes and interest_rate, we want to find savings_rate to produce a desired total, ie find the only real positive root of polynomial - target == 0. There is no analytic solution if interest_rate > 0., so we will try for a numeric solution.

target_amount = 1000000. * 0.25

# Make up a number: annual savings interest = 1.9%
monthly_interest_rate = 0.019 / 12.

# Our solver expects a single-argument function to solve, so let's make it one:
def fn(x):
    return savings_balance(monthly_incomes, monthly_interest_rate, x)

def bisection_search(fn, lo, hi, target, tolerance=0.1):
    # This function assumes that fn is monotonically increasing!

    # check the end-points - is a solution possible?
    fn_lo = fn(lo)
    assert not target < -tolerance + fn_lo, "target is unattainably low"
    if abs(target - fn_lo) <= tolerance:
        return lo
    fn_hi = fn(hi)
    assert not fn_hi + tolerance < target, "target is unattainably high"
    if abs(target - fn_hi) <= tolerance:
        return hi

    # a solution is possible but not yet found -
    #   repeat until we find it
    while True:
        # test the middle of the target range
        mid = (lo + hi) / 2
        fn_mid = fn(mid)
        # is this an acceptable solution?
        if abs(target - fn_mid) <= tolerance:
            return mid
        else:
            # do we need to look in the lower or upper half?
            if target < fn_mid:
                # look lower - bring the top down
                hi = mid
            else:
                # look higher - bring the bottom up
                lo = mid

and now we run it like

# From above, we know that
# when interest = 0.0 we need a savings rate of 46.6%
#
# If interest > 0. the savings_rate should be smaller,
# because some of target_amount will be covered by generated interest.
#
# For a small annual_interest_rate over an N year term,
# the effective accrued interest rate will be close to
# N * annual_interest_rate / 2  ->  1.5 * 1.9% == 2.85%
#
# So we expect the required savings rate to be
# about 46.6% * (1. - 0.0285) == 45.3%

bisection_search(fn, 0.40, 0.47, target_amount)  # 0.454047973

which gives a savings rate of 45.4%.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.