• Lectures 1-2 covered variables, lists, for loops, and if statements. This lecture adds dictionaries, user input, and while loops.

  • A dictionary stores values under names (keys) instead of positions, so a stock's price can be looked up by its ticker.

  • input() asks the user for information, and a while loop repeats a block of code until a condition changes, such as an account reaching a target balance.

  • Dictionaries come back in Lecture 6, where a pandas DataFrame can be built directly from one.

What We'll Cover

  • Ch 6 (Dictionaries): key-value pairs; looking up, adding, updating, and deleting entries; get() for missing keys; looping over pairs, keys, and values; nesting lists and dictionaries.

  • Ch 7 (User Input and while Loops): input() and converting text to numbers; the modulo operator; while loops with quit commands, flags, break, and continue; making sure loops end; modifying lists and dictionaries inside a while loop.

  • Most examples use a small brokerage account: tickers on a watchlist (AAPL, MSFT, NVDA, JPM, XOM, KO, ...), their sectors and fundamentals, open positions, and a queue of orders. Some while loop examples grow a savings balance or pay down a loan. All prices and figures are illustrative.

Ch 6: Dictionaries

  • A list finds an item by its position: prices[2] is the third price, and you have to remember which stock that is.

  • A dictionary finds an item by its key: prices["NVDA"] is NVDA's price, no matter how many other stocks are stored.

  • Typical dictionaries in finance code are a quote board (ticker → price), a reference table (ticker → sector, exchange, currency), and the fields of a single trade (ticker, side, shares, price).

Key-Value Pairs

  • A dictionary is written as curly braces {} around comma-separated entries of the form key: value. Each entry is a key-value pair.

  • Keys must be unique and are usually strings, such as tickers or field names. Values can be any Python object, including a list or another dictionary; the nested structures at the end of this chapter are built this way.

  • Dictionaries are used in two common ways:

    • As a record: each key names a different attribute of one thing, such as a single position.
    • As a lookup table: every key is the same kind of thing (a ticker) and every value the same kind of fact (a price).
position = {"ticker": "AAPL", "shares": 50, "price": 189.50}  # a record
quotes = {"AAPL": 189.50, "MSFT": 415.20, "NVDA": 121.40}  # a lookup table
print(position)
print(quotes)
{'ticker': 'AAPL', 'shares': 50, 'price': 189.5}
{'AAPL': 189.5, 'MSFT': 415.2, 'NVDA': 121.4}

Lookups and Updates

  • Dictionaries are mutable, like lists and unlike tuples: after creating one, you can add, update, and delete entries.

Looking Up a Value

  • Index a dictionary by key, as you index a list by position: quotes["MSFT"] returns MSFT's price.

  • The result is an ordinary value and can be used in arithmetic or in an f-string.

  • In an f-string, a colon after the expression sets the display format: .2f shows two decimals and , adds thousands separators, so f"{9475.0:,.2f}" shows 9,475.00. (Plain print() drops trailing zeros: 189.50 prints as 189.5.)

Note: Inside an f-string written with double quotes, put keys in single quotes, as in f"{position['ticker']}". Reusing double quotes there is a syntax error before Python 3.12.

print(quotes["MSFT"])
value = position["shares"] * position["price"]
print(f"{position['ticker']} position value: ${value:,.2f}")
415.2
AAPL position value: $9,475.00

Adding Entries

  • Assigning to a key that does not exist yet adds a new pair: quotes["JPM"] = 198.60.

  • Dictionaries keep insertion order (guaranteed since Python 3.7), so new pairs appear at the end.

  • When the fields arrive one at a time, as when a trade ticket is filled in, start with an empty dictionary {} and assign each field as it becomes known.

quotes["JPM"] = 198.60
quotes["XOM"] = 112.75
quotes
{'AAPL': 189.5, 'MSFT': 415.2, 'NVDA': 121.4, 'JPM': 198.6, 'XOM': 112.75}
trade = {}
trade["ticker"] = "NVDA"
trade["side"] = "buy"
trade["shares"] = 40
trade
{'ticker': 'NVDA', 'side': 'buy', 'shares': 40}

Updating Entries

  • Assigning to a key that already exists replaces its value. This is how a price or a share count is updated.

  • The new value can be computed from the old one: position["shares"] += 25 is short for position["shares"] = position["shares"] + 25.

quotes["NVDA"] = 124.10   # a new quote replaces the old one
position["shares"] += 25  # buy 25 more shares
print(quotes)
print(position)
{'AAPL': 189.5, 'MSFT': 415.2, 'NVDA': 124.1, 'JPM': 198.6, 'XOM': 112.75}
{'ticker': 'AAPL', 'shares': 75, 'price': 189.5}

Deleting Entries

  • del quotes["XOM"] deletes the key together with its value.

  • pop(key) also removes a pair and returns its value. Use it when you still need the value, e.g., to report the final price of a stock you stop tracking.

del quotes["XOM"]
last_nvda = quotes.pop("NVDA")
print(f"Stopped tracking NVDA at ${last_nvda:.2f}")
print(quotes)
Stopped tracking NVDA at $124.10
{'AAPL': 189.5, 'MSFT': 415.2, 'JPM': 198.6}

A Lookup Table

  • A lookup table holds the same fact for many items, for example the sector of every ticker we follow.

  • Write a long dictionary with one pair per line: indent the pairs, put a comma after every pair (the last one too, so adding a ticker later changes only one line), and put the closing brace on its own line.

  • A variable can serve as the key, so the same line of code works for any ticker.

sectors = {
    "AAPL": "Technology",
    "MSFT": "Technology",
    "NVDA": "Technology",
    "AMZN": "Consumer Discretionary",
    "JPM": "Financials",
    "XOM": "Energy",
    "KO": "Consumer Staples",
}
ticker = "JPM"
print(f"{ticker} is in the {sectors[ticker]} sector.")
JPM is in the Financials sector.

Missing Keys: KeyError and get()

  • Looking up a key that is not in the dictionary raises a KeyError. TSLA is not in sectors, so the lookup below fails.
# This will fail: there is no "TSLA" key in sectors
sectors["TSLA"]
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
Cell In[9], line 2
      1 # This will fail: there is no "TSLA" key in sectors
----> 2 sectors["TSLA"]

KeyError: 'TSLA'
  • get(key, default) returns the value if the key exists and default if it does not, so the program keeps running.

  • Without a default, get() returns None, a built-in object Python uses as a placeholder when there is nothing to return.

  • The in keyword (from Lecture 2) tests whether a key is present. It checks keys only, not values.

  • Use [] when a missing key would be a bug you want to see right away, and get() when a missing key is expected and a fallback value makes sense.

print(sectors.get("JPM", "Unknown"))
print(sectors.get("TSLA", "Unknown"))
print(sectors.get("TSLA"))
print("XOM" in sectors)     # a key
print("Energy" in sectors)  # a value, not a key
Financials
Unknown
None
True
False

Iterating over a Dictionary

  • A for loop can go through a dictionary's pairs, keys, or values, using the methods items(), keys(), and values().

Pairs with items()

  • items() gives the loop one key-value pair per pass. With two loop variables, as in for ticker, shares in holdings.items():, each pair is unpacked into a key and a value.

  • Use names that describe the data (ticker, shares) rather than generic ones (k, v).

  • In the next cell, holdings maps tickers to share counts. Each pass looks up the price in quotes and adds the position's value to the portfolio total.

holdings = {"AAPL": 75, "MSFT": 20, "JPM": 120}
total = 0
for ticker, shares in holdings.items():
    value = shares * quotes[ticker]
    total += value
    print(f"{ticker}: {shares} shares x ${quotes[ticker]:.2f} = ${value:,.2f}")
print(f"Portfolio value: ${total:,.2f}")
AAPL: 75 shares x $189.50 = $14,212.50
MSFT: 20 shares x $415.20 = $8,304.00
JPM: 120 shares x $198.60 = $23,832.00
Portfolio value: $46,348.50

Keys with keys() and sorted()

  • keys() returns just the keys. Looping over a dictionary directly also goes through its keys, so for ticker in sectors: is the usual shorthand.

  • Inside the loop, sectors[ticker] gives the value for each key. The first cell below uses it to count the technology stocks.

  • To go through the keys in alphabetical order, wrap them in sorted(). It returns a sorted list and leaves the dictionary unchanged.

tech_count = 0
for ticker in sectors:
    if sectors[ticker] == "Technology":
        tech_count += 1
print(f"{tech_count} of {len(sectors)} stocks are in Technology")
3 of 7 stocks are in Technology
for ticker in sorted(sectors.keys()):
    print(f"{ticker}: {sectors[ticker]}")
AAPL: Technology
AMZN: Consumer Discretionary
JPM: Financials
KO: Consumer Staples
MSFT: Technology
NVDA: Technology
XOM: Energy

Values with values() and set()

  • values() returns just the values, for when the keys are not needed.

  • Values can repeat: three of our stocks are in Technology. Passing the values to set() keeps each distinct value once.

  • A set holds at most one copy of each item and has no positions or fixed order, so sort it before printing to get predictable output.

for sector in sectors.values():
    print(sector)
Technology
Technology
Technology
Consumer Discretionary
Financials
Energy
Consumer Staples
unique_sectors = set(sectors.values())
print(f"{len(unique_sectors)} distinct sectors:")
for sector in sorted(unique_sectors):
    print(f"- {sector}")
5 distinct sectors:
- Consumer Discretionary
- Consumer Staples
- Energy
- Financials
- Technology

Nested Data Structures

  • Nesting means storing lists and dictionaries inside other lists and dictionaries. A whole brokerage account (its positions, holdings, and per-stock data) can then be kept in one variable.

  • The right structure depends on whether you will loop over records or look items up by key. The three subsections below cover the most common cases.

Records in a List

  • Records with the same fields, such as the positions in an account, can be stored as a list of dictionaries, one dictionary per position.

  • A for loop then gives you one dictionary per pass, and you read its fields by key. Below we compute each position's cost basis: shares times the purchase price.

positions = [
    {"ticker": "AAPL", "shares": 75, "cost": 150.00},
    {"ticker": "JPM", "shares": 120, "cost": 165.00},
    {"ticker": "KO", "shares": 200, "cost": 58.50},
]
print(f"{len(positions)} positions; the first is {positions[0]}")
3 positions; the first is {'ticker': 'AAPL', 'shares': 75, 'cost': 150.0}
total_cost = 0
for pos in positions:
    cost_basis = pos["shares"] * pos["cost"]
    total_cost += cost_basis
    print(f"{pos['ticker']}: cost basis ${cost_basis:,.2f}")
print(f"Total cost basis: ${total_cost:,.2f}")
AAPL: cost basis $11,250.00
JPM: cost basis $19,800.00
KO: cost basis $11,700.00
Total cost basis: $42,750.00
  • The dictionaries inside the list can be changed in place. The next loop adds today's price to each position and computes its unrealized gain, shares times (price minus cost).

  • To reach one field of one record, use the list index and then the key: positions[0]["price"].

latest = {"AAPL": 192.00, "JPM": 198.60, "KO": 61.80}
for pos in positions:
    pos["price"] = latest[pos["ticker"]]
    gain = pos["shares"] * (pos["price"] - pos["cost"])
    print(f"{pos['ticker']}: unrealized gain ${gain:,.2f}")
AAPL: unrealized gain $3,150.00
JPM: unrealized gain $4,032.00
KO: unrealized gain $660.00
print(positions[0])
print(positions[0]["price"])
{'ticker': 'AAPL', 'shares': 75, 'cost': 150.0, 'price': 192.0}
192.0

Lists as Values

  • A dictionary value can be a list. This suits a field with several entries, such as the stocks a fund holds.

  • Get the list with its key, then loop over it as usual. Here each holding is also looked up in sectors.

fund = {
    "strategy": "dividend income",
    "benchmark": "S&P 500",
    "holdings": ["KO", "JPM", "XOM", "MSFT"],
}
print(f"Strategy: {fund['strategy']} (benchmark: {fund['benchmark']})")
for ticker in fund["holdings"]:
    print(f"- {ticker}: {sectors[ticker]}")
Strategy: dividend income (benchmark: S&P 500)
- KO: Consumer Staples
- JPM: Financials
- XOM: Energy
- MSFT: Technology

Dictionaries as Values

  • When each item has several named attributes and you want to find items by key, nest dictionaries: the outer key is the ticker, and the inner dictionary holds that stock's fields.

  • Give every inner dictionary the same keys, so that one loop can handle all of them.

  • Two keys in a row reach a single field: fundamentals["JPM"]["eps"] first selects JPM's inner dictionary, then its EPS (earnings per share).

  • Looping over items() gives each ticker together with its inner dictionary. The second cell below uses this to compute each stock's price-to-earnings (P/E) ratio, price divided by EPS.

Note: Each extra level of nesting adds another [...] to every lookup, and beyond two levels the code is hard to follow. For tables like this one (a row per ticker, a column per field), Lecture 6 introduces the pandas DataFrame.

fundamentals = {
    "AAPL": {"sector": "Technology", "price": 192.00, "eps": 6.40},
    "JPM": {"sector": "Financials", "price": 198.60, "eps": 16.20},
    "KO": {"sector": "Consumer Staples", "price": 61.80, "eps": 2.45},
}
fundamentals["JPM"]["eps"]
16.2
for ticker, info in fundamentals.items():
    pe_ratio = info["price"] / info["eps"]
    print(f"{ticker} ({info['sector']}): P/E = {pe_ratio:.1f}")
AAPL (Technology): P/E = 30.0
JPM (Financials): P/E = 12.3
KO (Consumer Staples): P/E = 25.2

In-Class Exercise

6-1. Quote Board:

  • Create a dictionary that maps five tickers of your choice to illustrative prices.
  • Add a sixth ticker, update one price, and delete one ticker, printing the dictionary after each step.
  • Finally, loop through the tickers in alphabetical order and print each one with its price to two decimals.

6-2. Sector Counts:

  • Using the sectors dictionary from this lecture, print the sector of each ticker in ["MSFT", "TSLA", "XOM", "META"], showing Unknown for tickers that are missing.
  • Then build a new dictionary that counts how many stocks fall in each sector, and print it. Hint: counts.get(sector, 0) gives the count so far, or 0 for a new sector.

6-3. Portfolio Weights:

  • Create a list of three dictionaries, each with the keys "ticker", "shares", and "price".
  • Compute each position's market value and the portfolio's total value.
  • Print each position's weight (its value divided by the total) as a percentage with one decimal.

6-4. Value Screen:

  • Build a dictionary of dictionaries for four stocks, with the inner keys "price", "eps", and "dividend" (annual dividend per share).
  • For each stock, print its P/E ratio and its dividend yield (dividend divided by price) as a percentage.
  • Then print only the tickers whose P/E is below 20.

Ch 7: User Input and while Loops

  • So far our programs have used only data written into the code. This chapter adds user input and loops that repeat until a task is done.

  • input() asks the user a question while the program runs and returns the answer, such as a ticker or a share count.

  • A while loop repeats a block of code as long as a condition holds, for example while a savings balance is below its goal.

Reading Input with input()

  • input(prompt) prints the prompt, then waits until the user types a reply and presses Enter. The reply is returned as a string.

  • End the prompt with a space or ": "; otherwise the reply appears directly after the prompt's last character.

  • Because the reply is a string, string methods work on it directly. For example, .upper() turns msft into MSFT, which matches a key in quotes from Ch 6. In the sample run, the user types msft.

  • In Colab or Jupyter, a cell that calls input() shows a text box and keeps running until you answer.

Note: Outputs in this chapter show sample runs: each prompt is followed by what the user typed. When you run a cell yourself, type your own answers.

ticker = input("Enter a ticker symbol: ").upper()
if ticker in quotes:
    print(f"{ticker} quote: ${quotes[ticker]:.2f}")
else:
    print(f"No quote for {ticker}")
Enter a ticker symbol: msft
MSFT quote: $415.20

Converting Input with int() and float()

  • input() always returns text. If the user types 150, as in this sample run, the program receives the string "150", not the number 150.
shares = input("How many shares of KO? ")
type(shares)
How many shares of KO? 150
str
  • Multiplying that string by a price raises a TypeError, because Python cannot multiply text by a decimal number.
# This will fail: shares holds the text "150", not a number
cost = shares * 61.80
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[25], line 2
      1 # This will fail: shares holds the text "150", not a number
----> 2 cost = shares * 61.80

TypeError: can't multiply sequence by non-int of type 'float'
  • Multiplying text by a whole number does not raise an error. It repeats the text instead, so the mistake can go unnoticed.

  • To avoid both problems, convert the string first: int() turns a string of digits into an integer, and float() turns text such as "61.80" into a decimal number.

print(shares * 2)  # text: repeated, not doubled
shares = int(shares)
print(shares * 2)  # number: real arithmetic
150150
300
  • Usually the conversion is done on the same line, as in float(input(...)). In the next cell, the user enters a limit price, the most they will pay per share (sample run: 61.80).
limit_price = float(input("Limit price for KO: "))
max_cost = shares * limit_price
print(f"Buying {shares} KO shares costs at most ${max_cost:,.2f}")
Limit price for KO: 61.80
Buying 150 KO shares costs at most $9,270.00
  • int() expects a plain whole number such as "1000". Text with a comma, a decimal point, or letters raises a ValueError, and users often type 1,000.

  • In the sample run below, the user types 1,000. Later in this chapter a while loop keeps asking until the answer is valid, and Lecture 5 shows how to catch the error with try/except.

# This will fail: int() cannot read the comma in "1,000"
shares = int(input("How many shares of KO? "))
How many shares of KO? 1,000
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[28], line 2
      1 # This will fail: int() cannot read the comma in "1,000"
----> 2 shares = int(input("How many shares of KO? "))

ValueError: invalid literal for int() with base 10: '1,000'

Remainders with the Modulo Operator

  • The modulo operator % gives the remainder of a division, and floor division // counts how many whole times one number fits into another.

  • Together they split a quantity into whole units and a remainder: 30 months is 30 // 12 = 2 years and 30 % 12 = 6 months.

months = 30
print(f"{months} months = {months // 12} years and {months % 12} months")
30 months = 2 years and 6 months
  • A remainder of 0 means the division is exact. Stock orders are often counted in round lots of 100 shares, and any leftover is an odd lot. // counts the round lots and % gives the odd lot. (The underscore in 1_000 only makes the number easier to read; Python treats it as 1000.)
lot_size = 100
for shares in [300, 250, 1_000, 75]:
    lots = shares // lot_size
    odd = shares % lot_size
    if odd == 0:
        print(f"{shares} shares: {lots} round lots")
    else:
        print(f"{shares} shares: {lots} round lots plus an odd lot of {odd}")
300 shares: 3 round lots
250 shares: 2 round lots plus an odd lot of 50
1000 shares: 10 round lots
75 shares: 0 round lots plus an odd lot of 75

Repeating with while Loops

  • A for loop makes one pass per item. A while loop checks a condition before each pass and stops the first time the condition is False.

  • Use a while loop when the number of passes is not known in advance.

  • Something inside the loop must eventually make the condition False. In the first example, a balance of 10,000 earns 7% a year until it reaches 15,000.

balance = 10_000.00
year = 0
while balance < 15_000.00:
    year += 1
    balance *= 1.07
    print(f"Year {year}: ${balance:,.2f}")
Year 1: $10,700.00
Year 2: $11,449.00
Year 3: $12,250.43
Year 4: $13,107.96
Year 5: $14,025.52
Year 6: $15,007.30
  • In year 6 the balance passes 15,000, so the condition balance < 15_000.00 becomes False and the loop stops. (balance *= 1.07 is short for balance = balance * 1.07.)

  • If only the final answer matters, leave out the print() inside the loop. The next cell finds how many years it takes for money to double at 7% a year.

  • The rule of 72 estimates the doubling time as 72 / 7 ≈ 10.3 years. Because the loop counts whole years, it reports the first year-end at which the balance has doubled.

balance = 10_000.00
rate = 0.07
years = 0
while balance < 20_000.00:
    balance *= 1 + rate
    years += 1
print(f"Doubled after {years} years: ${balance:,.2f}")
Doubled after 11 years: $21,048.52

A Quit Command

  • With input() inside a while loop, the user decides how many passes to make: the loop runs until they type a quit command, here q.

  • The first question is asked before the loop, so the condition has an answer to check. Each pass stores the answer, then asks again.

  • When the user types q (which .upper() turns into Q), the condition becomes False, so the loop ends and Q is not added to the list. In the sample run, the user adds two tickers and then quits.

watchlist = []
ticker = input("Add a ticker (q to quit): ").upper()
while ticker != "Q":
    watchlist.append(ticker)
    ticker = input("Add another ticker (q to quit): ").upper()
print(f"Watchlist: {watchlist}")
Add a ticker (q to quit): nvda
Add another ticker (q to quit): ko
Add another ticker (q to quit): q
Watchlist: ['NVDA', 'KO']

Flags for Several Stopping Rules

  • When a loop has more than one stopping rule, putting every rule in the while line makes the line long and hard to read.

  • A flag is a Boolean variable that stays True while the loop should keep going. Any branch inside the loop can set it to False.

  • In the next two cells, a savings balance grows 7% a year plus a deposit of 1,000 at each year-end. The loop stops when the balance reaches the goal or when time runs out. (Set max_years = 5 and rerun both cells to see the second rule end the loop.)

balance = 10_000.00
goal = 25_000.00
max_years = 10
print(f"Goal: ${goal:,.0f} within {max_years} years")
Goal: $25,000 within 10 years
year = 0
saving = True
while saving:
    year += 1
    balance = balance * 1.07 + 1_000
    if balance >= goal:
        print(f"Goal reached in year {year}: ${balance:,.2f}")
        saving = False
    elif year == max_years:
        print(f"Time is up after {year} years: ${balance:,.2f}")
        saving = False
Goal reached in year 8: $27,441.66

Leaving Early with break

  • break ends a loop immediately: the rest of the body is skipped and no further passes run.

  • A common pattern is while True:, a loop that would never end on its own, with a break at the point where the task is done.

  • The next loop asks for a share count until the answer is valid, which handles the 1,000 input from earlier. The string method isdigit() returns True only if every character is a digit. In the sample run, the first answer contains a comma.

  • break also works in for loops, e.g., to stop scanning a list of prices at the first one that crosses a stop-loss level.

while True:
    answer = input("Shares to buy (digits only): ")
    if answer.isdigit():
        break
    print("Please type digits only, with no commas or decimals.")
shares = int(answer)
print(f"Order size: {shares:,} shares")
Shares to buy (digits only): 1,000
Please type digits only, with no commas or decimals.
Shares to buy (digits only): 1000
Order size: 1,000 shares

Skipping a Pass with continue

  • continue ends the current pass and goes back to the condition check. Unlike break, it does not end the loop.

  • Example: a student loan of 20,000 at 6% a year (0.5% a month) is repaid at 400 a month. The loop runs month by month, but we want a report only at each year-end, so in the other 11 months continue skips the print().

  • month % 12 != 0 is True in every month except months 12, 24, 36, and so on.

  • The loop ends once the balance reaches zero or below, so the final payment is a little more than what was still owed.

balance = 20_000.00  # student loan
monthly_rate = 0.06 / 12
payment = 400.00
month = 0
while balance > 0:
    month += 1
    balance = balance * (1 + monthly_rate) - payment
    if month % 12 != 0:
        continue  # not a year-end, so skip the report
    print(f"End of year {month // 12}: ${balance:,.2f} owed")
print(f"Repaid in month {month}")
End of year 1: $16,299.33 owed
End of year 2: $12,370.41 owed
End of year 3: $8,199.17 owed
End of year 4: $3,770.65 owed
Repaid in month 58

Making Sure the Loop Ends

  • A while loop whose condition never becomes False runs forever. Such an infinite loop hangs the program, and the code after the loop never runs.

  • The loop below never ends: month grows, but balance never changes, so balance > 0 stays True. It is shown as text rather than as a code cell so that it cannot be run by accident.

balance = 20_000.00
month = 0
while balance > 0:
    month += 1  # bug: balance is never updated
  • If a cell seems stuck, stop it with Runtime → Interrupt execution in Colab, the Interrupt command in Jupyter's Kernel menu, or Ctrl+C in a terminal.
  • Common causes of infinite loops:

    • The variable in the condition is never updated, as above.
    • The update never moves the condition toward False. At 0.5% a month, a 20,000 loan accrues 100 of interest each month, so a payment of 100 or less never brings the balance down.
    • The condition tests for an exact value, e.g., while balance != 0:. The balance can jump from positive to negative without ever equaling 0, so the loop keeps going. Write while balance > 0: instead.
  • A simple safeguard is a cap on the number of passes. In the next cell, the payment only covers the interest, but the loop still stops after 600 months (50 years).

balance = 20_000.00
payment = 100.00  # equals the monthly interest
month = 0
while balance > 0 and month < 600:
    month += 1
    balance = balance * (1 + 0.06 / 12) - payment
print(f"Stopped after {month} months; ${balance:,.2f} still owed")
Stopped after 600 months; $20,000.00 still owed

Modifying Lists and Dictionaries in a while Loop

  • Suppose we want to empty a list of tickers one at a time. Removing each item inside a for loop over the same list does not work, as the next cell shows.
tickers = ["AAPL", "MSFT", "NVDA", "JPM"]
for ticker in tickers:
    tickers.remove(ticker)  # try to empty the list...
print(tickers)              # ...but half the tickers survive
['MSFT', 'JPM']
  • Each remove() shifts the later items one position to the left, but the for loop still moves on to the next position, so every other ticker is skipped. No error is raised.

  • A while loop avoids this because it checks the list itself before every pass. An empty list counts as False, so a loop that starts with while pending: stops when the list is empty.

Moving Orders from Pending to Filled

  • Each pass takes the oldest pending order, fills it at the current price, and moves it to the filled list.

  • pop(0) removes and returns the first item, so orders are handled first in, first out.

pending = [
    {"ticker": "AAPL", "side": "buy", "shares": 100},
    {"ticker": "XOM", "side": "sell", "shares": 50},
    {"ticker": "KO", "side": "buy", "shares": 200},
]
filled = []
fill_prices = {"AAPL": 189.50, "XOM": 112.75, "KO": 61.80}
print(f"{len(pending)} orders pending")
3 orders pending
while pending:
    order = pending.pop(0)
    order["price"] = fill_prices[order["ticker"]]
    filled.append(order)
    print(f"Filled: {order}")
print(f"Orders still pending: {len(pending)}")
Filled: {'ticker': 'AAPL', 'side': 'buy', 'shares': 100, 'price': 189.5}
Filled: {'ticker': 'XOM', 'side': 'sell', 'shares': 50, 'price': 112.75}
Filled: {'ticker': 'KO', 'side': 'buy', 'shares': 200, 'price': 61.8}
Orders still pending: 0

Deleting Every Copy of a Ticker

  • remove() deletes only the first match. To delete every copy of a value, call remove() in a while loop until the value is gone.

  • Example: alerts holds one entry per price alert, and we delete every alert for NVDA. The list method count() returns how many times a value appears.

alerts = ["NVDA", "KO", "NVDA", "JPM", "XOM", "NVDA"]
print(f"NVDA alerts before: {alerts.count('NVDA')}")
while "NVDA" in alerts:
    alerts.remove("NVDA")
print(f"Remaining alerts: {alerts}")
NVDA alerts before: 3
Remaining alerts: ['KO', 'JPM', 'XOM']

Building a Dictionary from User Answers

  • A while loop can also collect key-value pairs from the user, one pair per pass.

  • Here the user enters target portfolio weights in percent, and the loop ends once the weights add up to 100. In the sample run, the user enters three stocks.

targets = {}
total_weight = 0.0
while total_weight < 100:
    ticker = input("Ticker: ").upper()
    weight = float(input(f"Target weight for {ticker} (%): "))
    targets[ticker] = weight
    total_weight += weight
print(targets)
Ticker: aapl
Target weight for AAPL (%): 40
Ticker: jpm
Target weight for JPM (%): 35
Ticker: ko
Target weight for KO (%): 25
{'AAPL': 40.0, 'JPM': 35.0, 'KO': 25.0}
  • Once the dictionary is filled, an ordinary for loop converts the weights into dollar amounts for an account worth 50,000.

Note: The while loop above does not check the user's answers. If the weights add up to more than 100 (say 60 and then 60), it stops at 120. Exercise 7-5 asks you to handle this case.

account_value = 50_000
for ticker, weight in targets.items():
    dollars = account_value * weight / 100
    print(f"{ticker}: {weight:.0f}% = ${dollars:,.0f}")
AAPL: 40% = $20,000
JPM: 35% = $17,500
KO: 25% = $12,500

In-Class Exercise

7-1. Order Ticket:

  • Ask the user for a ticker, a number of shares, and a limit price, converting each answer to a suitable type.
  • Print a one-line summary with the ticker in uppercase and the maximum cost formatted with commas and two decimals.
  • Use % to report whether the order is a whole number of 100-share round lots.

7-2. Years to Target:

  • Start with 5,000 that grows 6% per year. Use a while loop to print the balance at the end of each year until it reaches at least 8,000, then report how many years that took.
  • Add a cap so the loop also stops after 30 years, and check that it ends when the growth rate is 0%.

7-3. Coupon Calendar:

  • A 3-year bond with a face value of 1,000 pays a coupon of 20 every 6 months and repays the face value at maturity.
  • Use a while loop over months 1 to 36, with continue to skip the months without a payment.
  • Print each payment month with the cash received, then the total received over the bond's life.

7-4. Order Book with Restrictions:

  • Create a list of pending orders (dictionaries with "ticker", "side", and "shares"), including two orders for a ticker on a restricted list.
  • Use a while loop to move every order to either filled or rejected, using continue to skip the fill step for rejected orders.
  • Print both lists at the end.

7-5. Target Allocation with Checks:

  • Use a while loop and input() to build a dictionary that maps tickers to whole-number target weights in percent.
  • Stop when the user types done or the weights add up to 100. If a weight would push the total above 100, print a warning and ask again instead of storing it.
  • Print the final allocation in alphabetical order.