Securing The Bag
LAB7

invest.png

Welcome

Financial literacy is something we all need. Without it, our chances of making a good living and securing a happy future are lower than they’d otherwise be. It’s a huge topic, but we can learn bits at a time. One area worth getting a handle on is the power of compound interest. If you are able to invest at a good rate for long enough, your wealth can grow substantially.

Let’s use programming to help us experiment with compound interest. Programming speeds up time so we can think faster and try out lots of ideas.

In this lab, we’ll come up with ways to compute the amount of money we’ll have at the end of an investment cycle, and determine how many months it will take to reach a desired goal. We’ll look at the effects of making deposits along the way, and what happens if taxes are taken out every year versus only at the end. We’ll also see whether the number of times interest is compounded per year makes much of a difference or not.

Because exponents—which humans are usually pretty bad at—are involved, some of the results may surprise you. Or not. The point is that programming gives you ways to figure stuff out in the way you want them figured out. It even gives your an avenue to do crazy things such as asking what happens if you compound interest $n$ times per year, where $n$ is a negative number (which you can try in one of the challenges).

What We Will Learn

Formatting strings Arithmetic += -= ** Algorithm design Locales

Activity

Start by making a new folder ~/cmsi1010/lab07. Create the file interest.py with the contents:

print("This will be a program to compute compound interest.")

Run it!

The Basics

We will start simply with a program that computes the amount of money earned each year starting with 1000 monetary units (see how we’re not biased toward any particular culture’s currency?) with 5% interest over 10 years. Let’s write this code together. Edit the file to read (we’ll explain it, especially that += operator, in class):

balance = 1000
interest_rate = 0.05
years = 10

print("At the start you have", balance)
for year in range(1, years + 1):
    interest_earned = balance * interest_rate
    balance += interest_earned
    print("After year", year, "you have", balance)

Run it and see:

At the start you have 1000
After year 1 you have 1050.0
After year 2 you have 1102.5
After year 3 you have 1157.625
After year 4 you have 1215.50625
After year 5 you have 1276.2815624999998
After year 6 you have 1340.0956406249998
After year 7 you have 1407.1004226562497
After year 8 you have 1477.4554437890622
After year 9 you have 1551.3282159785153
After year 10 you have 1628.894626777441
What is going on with Year 5?

Here’s the deal: computer arithmetic is not always exact. It is subject to roundoff error. The details of why this happens will come in a later course, but you can ask an AI chatbot about it.

Formatting

We can make improvements. The first one is to format the output nicely. Python formats with something it calls an f-string. Let’s print to hundredths of a monetary unit:

balance = 1000
interest_rate = 0.05
years = 10

print(f"At the start you have {balance:.2f}")
for year in range(1, years + 1):
    interest_earned = balance * interest_rate
    balance += interest_earned
    print(f"After year {year} you have {balance:.2f}")

Run it. Much better, right!

At the start you have 1000.00
After year 1 you have 1050.00
After year 2 you have 1102.50
After year 3 you have 1157.62
After year 4 you have 1215.51
After year 5 you have 1276.28
After year 6 you have 1340.10
After year 7 you have 1407.10
After year 8 you have 1477.46
After year 9 you have 1551.33
After year 10 you have 1628.89
Now do the following exercises where you spend some time changing the variables and re-running the program:
  • Try 3% (rate of 0.03) for 20 years.
  • Try 7% for 30 years.
  • Try starting with 1 monetary unit and doubling your money every year (rate = 1) for 20 years.
  • Try a negative interest rate (say, -0.075) starting with 500,000 monetary units.
Write the code first, then to see if everything worked out
1806.11
7612.26
1,048,576.00
after 30 years you are down to 48,219.42
Negative interest?

Why not? Programming gives you the power to explore. We are not necessarily bound by the physical world. You have played video games before, right?

Making Contributions

Now let’s see what happens if at the end of every year, we make a deposit of 100 monetary units. How long does it take to double your money now?

balance = 1000
interest_rate = 0.05
years = 10
deposit = 100

print(f"At the start you have {balance:.2f}")
for year in range(1, years + 1):
    interest_earned = balance * interest_rate
    balance += interest_earned
    balance += deposit
    print(f"After year {year} you have {balance:.2f}")
At the start you have 1000.00
After year 1 you have 1150.00
After year 2 you have 1307.50
After year 3 you have 1472.88
After year 4 you have 1646.52
After year 5 you have 1828.84
After year 6 you have 2020.29
After year 7 you have 2221.30
After year 8 you have 2432.37
After year 9 you have 2653.98
After year 10 you have 2886.68

Note that you got in 4 years what took 10 years earlier. And you ended up with quite a bit more at the end. Keep making those contributions!

Taxes

Now, let’s look at...taxes! Let’s keep the numbers simple for now. We’ll assume we have some great investment that make 13% per year. But each year, you are taxed 25% of the interested earned. Let’s start with 10,000 units and make a 1,000-unit contribution at the end of the year (after taxes are taken out) and see where we are after 30 years:

balance = 10000
interest_rate = 0.13
years = 30
tax_rate = 0.25
deposit = 1000

print(f"At the start you have {balance:.2f}")
for year in range(1, years + 1):
    interest_earned = balance * interest_rate
    taxes = interest_earned * tax_rate
    balance += interest_earned
    balance -= taxes
    balance += deposit
    print(f"After year {year} you have {balance:.2f}")

You should get 319,883.75 if you run the program. Now here’s a twist: what would happen if the taxes were not taken out every year, but only at the end?

balance = 10000
interest_rate = 0.13
years = 30
tax_rate = 0.25
deposit = 1000

total_interest_earned = 0
print(f"At the start you have {balance:.2f}")
for year in range(1, years + 1):
    interest_earned = balance * interest_rate
    total_interest_earned += interest_earned
    balance += interest_earned
    balance += deposit
taxes = total_interest_earned * tax_rate
balance -= taxes
print(f"After {year} years and after taxes, you have {balance:.2f}")

Woah. Were you surprised at the difference?

Exercise: There’s a fun experiment you can do here. Suppose you started with $1 and doubled your money every year for 30 years. Suppose the tax rate was 33%. If you were taxed at the end of the 30 years, how much would you have? Modify the program to find out. Now try to predict how much you would have if you were taxed every year. Run the program to find out.

Improving the Code

We’ve been experimenting and mucking around in a single program. It’s now time to think about designing something useful for people. What if we packaged all that work we did into a nice function? That way other people could import this function and use it in the apps that they build themselves? ❤️❤️❤️ We’ll build it step-by-step in class, but here’s the result:

def investment_value(start, interest_rate, tax_rate, deposit, years):
    balance = start
    for _ in range(1, years + 1):
        interest_earned = balance * interest_rate
        taxes = interest_earned * tax_rate
        balance += (interest_earned - taxes + deposit)
    return balance

Go ahead and replace all the code you wrote so far with this simple function above. We can now make some calls to see if we got things right. Add these lines of code underneath your function:

print(investment_value(1000, 0.05, 0, 0, 10))  # should be 1628.89
print(investment_value(1000, 0.05, 0, 100, 10))  # should be 2886.68
print(investment_value(10000, 0.13, 0.25, 1000, 30))  # should be 319883.75
print(investment_value(1, 1, 0, 0, 20)) # should be 1048576.0

Hey, don’t the calls look hard to read? Let’s remember from Lab 1 how we named the arguments? Is this better?

print(investment_value(start=1000, interest_rate=0.05, tax_rate=0, deposit=0, years=10))  # should be 1628.89
print(investment_value(start=1000, interest_rate=0.05, tax_rate=0, deposit=100, years=10))  # should be 2886.68
print(investment_value(start=10000, interest_rate=0.13, tax_rate=0.25, deposit=1000, years=30))  # should be 319883.75
print(investment_value(start=1, interest_rate=1, tax_rate=0, deposit=0, years=20)) # should be 1048576.0

Achieving Goals

Now, a big change. Instead of computing how much money we have after a given number of years, let’s compute how many years it takes to reach a desired sum of money. Remember from the previous lab that we don’t know beforehand how many times we need to loop, so we need a while loop! Let’s make a new function

def years_to_reach_goal(start, interest_rate, tax_rate, deposit, goal):
    years = 0
    balance = start
    while balance < goal:
        interest_earned = balance * interest_rate
        taxes = interest_earned * tax_rate
        balance += (interest_earned - taxes + deposit)
        years += 1
    return years

You know what the next exercise is: write a handful of calls, playing around with the numbers.

Exercise: Seriously, experiment with the numbers. Try different interest rates, starting amounts, and goals. If you found any combination of values surprising, make a note of the scenario and share it with a friend.

Locales

So far we’ve avoided using currency values like dollars, euros, or rubles. That’s because the computation doesn’t require that information. That information is only for display. The particular way that currency, dates, and similar things are displayed is part of what’s called a locale. Most Python environments have very, very few locales that they support, but there are some. Try this code in the Python REPL:

import locale
for loc in ['en_US', 'ru_RU', 'fr_FR', 'de_DE', 'es_ES', 'it_IT', 'fr_CA']:
    locale.setlocale(locale.LC_ALL, loc)
    print(locale.currency(35888382152.22815, grouping=True))

What do you think happened?

Exercise: Try integrating the importing of locale and the use of locale.setlocale and locale.currency into your current lab work.

Challenges

Now it’s your turn. Here are some ideas for you to extend the activities above:

Further Study

This was our longest lab yet. If you are still getting your programming legs, you might want to review some of the previously assigned readings from Think Python. Then continue with the following:

You might also have started exploring programming with AI chatbots. Read about how this is changing (and not changing) programming.

Summary

We’ve covered:

  • The += operator
  • String formatting with f-strings
  • Locales

Recall Practice

Here are some questions useful for your spaced repetition learning. Many of the answers are not found on this page. Some will have popped up in lecture. Others will require you to do your own research.

  1. If x is 5, what is the value of x after x += 3?
    8
  2. What is the value of x after x -= 2 if x was 5 before?
    3
  3. Assuming x is 5, what are the values of the two strings "x is {x}" and f"x is {x}"?
    "x is {x}"
    "x is 5"
  4. If x is 3.141592653589793238, what is the string f"{x:.5f}"?
    "3.14159"
  5. If x is 3.141592653589793238, what is the string f"{x:10.5f}"?
    "   3.14159"
  6. Explain why a for-loop is used to calculate the ending balance of an investment, but a while-loop is used to for computing the time needed to reach a goal.
    A for-loop is used when we know how many times we want to repeat the operation (e.g., for a fixed number of years), while a while-loop is used when we do not know how many iterations are needed until a condition is met (e.g., until the balance reaches a goal).
  7. What is the effect of compounding interest more frequently (e.g., monthly vs. annually) on the final amount of an investment?
    Compounding interest more frequently generally results in a higher final amount because interest is calculated and added to the principal more often, leading to interest being earned on previously earned interest.
  8. How does the tax rate affect the final balance of an investment?
    A higher tax rate reduces the amount of interest earned each year, leading to a lower final balance compared to a scenario with a lower or no tax rate.
  9. What is the difference between taxing the interest earned on an investment every year versus taxing the interest at the end?
    Taxing the interest every year reduces the balance more frequently, leading to a lower final amount compared to taxing at the end, where the full interest is accumulated before tax is applied.
  10. What is the significance of the deposit parameter in the investment functions?
    The deposit parameter allows for additional contributions to be made to the investment at the end of each period, which can significantly increase the final balance over time.
  11. What is the difference between locale.setlocale(locale.LC_ALL, 'en_US') and locale.setlocale(locale.LC_ALL, 'ru_RU')?
    The first sets the locale to US English, while the second sets it to Russian as used in Russia. This affects how numbers, dates, and currencies are formatted.
  12. What is the purpose of the locale.currency function?
    It formats a number as a currency string according to the current locale settings, including grouping and decimal separators.