Lectures 1-2 covered variables, lists,
forloops, andifstatements. This lecture adds dictionaries, user input, andwhileloops.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 awhileloop 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;whileloops with quit commands, flags,break, andcontinue; making sure loops end; modifying lists and dictionaries inside awhileloop.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
whileloop 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 formkey: 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:
.2fshows two decimals and,adds thousands separators, sof"{9475.0:,.2f}"shows9,475.00. (Plainprint()drops trailing zeros:189.50prints as189.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"] += 25is short forposition["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 insectors, 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 anddefaultif it does not, so the program keeps running.Without a default,
get()returnsNone, a built-in object Python uses as a placeholder when there is nothing to return.The
inkeyword (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, andget()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 keyFinancials Unknown None True False
Iterating over a Dictionary
- A
forloop can go through a dictionary's pairs, keys, or values, using the methodsitems(),keys(), andvalues().
Pairs with items()
items()gives the loop one key-value pair per pass. With two loop variables, as infor 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,
holdingsmaps tickers to share counts. Each pass looks up the price inquotesand 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, sofor 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
forloop 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
sectorsdictionary from this lecture, print the sector of each ticker in["MSFT", "TSLA", "XOM", "META"], showingUnknownfor 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
whileloop 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()turnsmsftintoMSFT, which matches a key inquotesfrom Ch 6. In the sample run, the user typesmsft.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, andfloat()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 arithmetic150150 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 aValueError, and users often type1,000.In the sample run below, the user types
1,000. Later in this chapter awhileloop keeps asking until the answer is valid, and Lecture 5 shows how to catch the error withtry/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 and30 % 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 in1_000only 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
forloop makes one pass per item. Awhileloop checks a condition before each pass and stops the first time the condition isFalse.Use a
whileloop 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.00becomesFalseand the loop stops. (balance *= 1.07is short forbalance = 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 awhileloop, the user decides how many passes to make: the loop runs until they type a quit command, hereq.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 intoQ), the condition becomesFalse, so the loop ends andQis 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
whileline makes the line long and hard to read.A flag is a Boolean variable that stays
Truewhile the loop should keep going. Any branch inside the loop can set it toFalse.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 = 5and 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 = FalseGoal reached in year 8: $27,441.66
Leaving Early with break
breakends 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 abreakat the point where the task is done.The next loop asks for a share count until the answer is valid, which handles the
1,000input from earlier. The string methodisdigit()returnsTrueonly if every character is a digit. In the sample run, the first answer contains a comma.breakalso works inforloops, 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
continueends the current pass and goes back to the condition check. Unlikebreak, 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
continueskips theprint().month % 12 != 0isTruein 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
whileloop whose condition never becomesFalseruns forever. Such an infinite loop hangs the program, and the code after the loop never runs.The loop below never ends:
monthgrows, butbalancenever changes, sobalance > 0staysTrue. 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. Writewhile 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
forloop 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 theforloop still moves on to the next position, so every other ticker is skipped. No error is raised.A
whileloop avoids this because it checks the list itself before every pass. An empty list counts asFalse, so a loop that starts withwhile 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
filledlist.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, callremove()in awhileloop until the value is gone.Example:
alertsholds one entry per price alert, and we delete every alert for NVDA. The list methodcount()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
whileloop 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
forloop 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
whileloop 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
whileloop over months 1 to 36, withcontinueto 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 arestrictedlist. - Use a
whileloop to move every order to eitherfilledorrejected, usingcontinueto skip the fill step for rejected orders. - Print both lists at the end.
7-5. Target Allocation with Checks:
- Use a
whileloop andinput()to build a dictionary that maps tickers to whole-number target weights in percent. - Stop when the user types
doneor 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.