• In Lecture 1 we stored data in variables and lists. This lecture covers repeating a calculation for every item in a list and making decisions based on the data.

  • A for loop applies the same steps to each item in a list, however long the list is.

  • An if statement runs code only when a condition holds, for example to flag a loss or to reject an order for a ticker we cannot trade.

  • From Lecture 6 on, pandas handles many of these loops, but the logic is the same.

What We'll Cover

  • Ch 4 (Working with Lists): for loops and indentation, range(), numerical lists, min()/max()/sum(), list comprehensions, slicing, copying lists, and tuples.

  • Ch 5 (if Statements): conditional tests, and/or, in/not in, Boolean values, if/if-else/if-elif-else, and if statements with lists.

  • The running example is a small watchlist of stocks (AAPL, MSFT, NVDA, JPM, XOM, KO) and two weeks of daily closing prices for one stock. All prices, ratios, and portfolio figures in this lecture are illustrative, not market data.

Ch 4: Working with Lists

  • Lecture 1 showed how to create a list, read items by their index, and add, remove, and sort items one at a time.

  • In practice we usually need to process every item in a list, for example to turn each price into a return or to add up the value of each position.

  • This chapter covers looping over a list, creating and summarizing lists of numbers, selecting part of a list, and tuples, a list-like type whose contents cannot change.

Looping Over a List

  • A for loop runs the same block of code once for each item in a list.

  • The loop below prints every ticker on the watchlist.

watchlist = ["AAPL", "MSFT", "NVDA", "JPM", "XOM", "KO"]

for ticker in watchlist:
    print(ticker)
AAPL
MSFT
NVDA
JPM
XOM
KO

How a for Loop Works

  • At the line for ticker in watchlist:, Python takes the first item of watchlist, stores it in the variable ticker, and runs the indented block below.

  • When the block finishes, Python goes back to the for line, stores the next item in ticker, and runs the block again.

  • The loop ends after the last item. With six tickers, the block runs six times.

  • The loop variable can have any name. Use a name that describes one item and give the list a name that describes the whole collection, as in for ticker in watchlist or for price in prices.

Inside vs. After the Loop

  • The indented lines after the for line form the body of the loop. The body runs once per item and can contain any number of lines.

  • The first line that is not indented is outside the loop. It runs once, after the loop finishes, so it is the place for a summary such as a total.

  • Below, the loop adds the market value of each position to a running total, and the line after the loop prints the result. total += value is shorthand for total = total + value. Python ignores the underscore in 12_500.00; it only makes the number easier to read.

  • In an f-string, {value:,.2f} displays a number with a thousands separator and 2 decimals. It changes only how the number is printed, not the value stored.

position_values = [12_500.00, 8_300.00, 21_750.00, 4_900.00]
total = 0

for value in position_values:
    total += value
    print(f"Added ${value:,.2f}; running total ${total:,.2f}")

print(f"Portfolio value: ${total:,.2f}")
Added $12,500.00; running total $12,500.00
Added $8,300.00; running total $20,800.00
Added $21,750.00; running total $42,550.00
Added $4,900.00; running total $47,450.00
Portfolio value: $47,450.00

Indentation

  • Many languages mark a block of code with braces { }. Python uses indentation instead: the indented lines after a line that ends in a colon form the block.

  • The convention is 4 spaces per level. Jupyter, Colab, and VS Code insert them automatically when you press Enter after a colon.

  • A common mistake is forgetting to indent the body. Python then raises an IndentationError and does not run the cell.

# This will fail: the loop body is not indented
for ticker in watchlist:
print(ticker)
  Cell In[3], line 3
    print(ticker)
    ^
IndentationError: expected an indented block after 'for' statement on line 2
  • The last line of an error message gives the error type and the problem (expected an indented block), so read it first. The lines above it show where Python found the problem.

  • Leaving out the colon at the end of the for line raises a SyntaxError.

  • Python cannot catch every indentation mistake. If the final print in the portfolio example were indented, it would run on every pass and print a "Portfolio value" line four times, three of them with incomplete totals. No error would be raised, so check which lines are inside a loop.

Lists of Numbers

  • Financial data such as a price history or a series of returns is often stored as a list of numbers.

  • Python can generate integer sequences with range(), summarize a list with min(), max(), and sum(), and build a new list from an existing one with a list comprehension.

Counting with range()

  • range(start, stop) produces the integers from start up to, but not including, stop.

  • To count years 1 through 5, write range(1, 6). Forgetting that stop is excluded is a common cause of off-by-one errors.

for year in range(1, 6):
    print(f"Year {year}")
Year 1
Year 2
Year 3
Year 4
Year 5
  • With one argument, range(n) starts at 0 and produces n integers: 0 through n - 1.

  • A third argument sets the step. A 5-year bond that pays a coupon every 6 months pays in months 6, 12, 18, and so on up to 60.

  • A range produces its numbers on demand instead of storing them. Wrap it in list() to see or keep them.

print(list(range(5)))

coupon_months = list(range(6, 61, 6))
print(coupon_months)
print(f"Number of coupon payments: {len(coupon_months)}")
[0, 1, 2, 3, 4]
[6, 12, 18, 24, 30, 36, 42, 48, 54, 60]
Number of coupon payments: 10

Building a List in a Loop

  • A common pattern is to start with an empty list and append() one value on each pass through a loop.

  • Below we build a compounding schedule for a deposit of 10,000 dollars earning 5% a year, compounded annually. After year years the balance is principal * (1 + rate) ** year.

  • round(balance, 2) rounds each balance to the nearest cent before it is stored.

principal = 10_000
rate = 0.05
balances = []

for year in range(1, 6):
    balance = principal * (1 + rate) ** year
    balances.append(round(balance, 2))

print(balances)
[10500.0, 11025.0, 11576.25, 12155.06, 12762.82]

Summary Statistics: min(), max(), sum()

  • These built-in functions take a list of numbers and return its smallest value, its largest value, and its total.

  • To try them, we store ten trading days of closing prices for one stock in closes. The average close is sum(closes) / len(closes).

  • sum() can replace the running-total loop from earlier: sum(position_values) returns the same total, 47,450.

closes = [100.00, 101.50, 99.80, 102.30, 103.10,
          101.90, 104.20, 105.00, 102.60, 104.70]

print(f"Lowest close:  ${min(closes):.2f}")
print(f"Highest close: ${max(closes):.2f}")
print(f"Average close: ${sum(closes) / len(closes):.2f}")
Lowest close:  $99.80
Highest close: $105.00
Average close: $102.51

List Comprehensions

  • A list comprehension builds a new list from an existing sequence in a single expression: [expression for item in sequence].

  • It reads as "compute expression for each item and collect the results in a list."

  • The cell below builds the compounding schedule from above in one line.

balances = [round(principal * (1 + rate) ** year, 2) for year in range(1, 6)]
print(balances)
[10500.0, 11025.0, 11576.25, 12155.06, 12762.82]
  • The daily return on day t is that day's close divided by the previous close, minus one: closes[t] / closes[t - 1] - 1. The comprehension below applies this formula to closes.

  • The first day has no previous close, so t runs from 1 to len(closes) - 1, and ten prices give nine returns.

  • The result is an ordinary list, so max() and min() find the best and worst day. In an f-string, {x:.2%} multiplies by 100, rounds to 2 decimals, and adds a percent sign: 0.0251 prints as 2.51%.

  • In Lecture 6, pandas computes the same returns with a single method, pct_change().

daily_returns = [closes[t] / closes[t - 1] - 1 for t in range(1, len(closes))]

print(f"{len(closes)} prices -> {len(daily_returns)} returns")
print([round(r, 4) for r in daily_returns])
print(f"Best day:  {max(daily_returns):.2%}")
print(f"Worst day: {min(daily_returns):.2%}")
10 prices -> 9 returns
[0.015, -0.0167, 0.0251, 0.0078, -0.0116, 0.0226, 0.0077, -0.0229, 0.0205]
Best day:  2.51%
Worst day: -2.29%

Slicing

  • A slice selects a run of consecutive items: closes[start:stop].

  • As with range(), start is included and stop is excluded, so closes[0:3] returns the items at positions 0, 1, and 2.

  • Omit start to begin at the first item; omit stop to continue to the end.

print(closes[0:3])   # positions 0, 1, 2
print(closes[:3])    # same slice: start defaults to 0
print(closes[7:])    # position 7 to the end
[100.0, 101.5, 99.8]
[100.0, 101.5, 99.8]
[105.0, 102.6, 104.7]

Counting from the End

  • Negative positions count from the end, so closes[-5:] is the last five closes. Their average is the latest value of the 5-day moving average, a common trend indicator.
last_five = closes[-5:]

print(last_five)
print(f"5-day moving average: ${sum(last_five) / len(last_five):.2f}")
[101.9, 104.2, 105.0, 102.6, 104.7]
5-day moving average: $103.68

Slices in a for Loop

  • A slice is itself a list, so a for loop can run over it.

  • Suppose ranked lists the watchlist from best to worst performer this month. Looping over ranked[:3] reports only the top three.

ranked = ["NVDA", "MSFT", "JPM", "AAPL", "KO", "XOM"]

print("Top three performers:")
for ticker in ranked[:3]:
    print(f"  {ticker}")
Top three performers:
  NVDA
  MSFT
  JPM

Copies vs. Aliases

  • Suppose you want a team watchlist that starts with your tickers and can then be changed without affecting yours.

  • The assignment team_watchlist = my_watchlist does not copy anything. It gives the same list a second name.

my_watchlist = ["AAPL", "MSFT", "NVDA"]
team_watchlist = my_watchlist          # a second name, not a copy

team_watchlist.append("JPM")
print(f"Mine: {my_watchlist}")
print(f"Team: {team_watchlist}")
Mine: ['AAPL', 'MSFT', 'NVDA', 'JPM']
Team: ['AAPL', 'MSFT', 'NVDA', 'JPM']
  • JPM was added through team_watchlist, but it also appears in my_watchlist, because both names refer to the same list in memory. This is called aliasing.

  • Bugs caused by aliasing are hard to find because no error is raised; the data just changes in a place you did not expect.

  • To make an independent copy, slice the whole list with [:] or call my_watchlist.copy(). Work on a copy when you need to change a list but keep the original, for example raw prices before cleaning.

my_watchlist = ["AAPL", "MSFT", "NVDA"]
team_watchlist = my_watchlist[:]       # a new list with the same items

team_watchlist.append("JPM")
print(f"Mine: {my_watchlist}")
print(f"Team: {team_watchlist}")
Mine: ['AAPL', 'MSFT', 'NVDA']
Team: ['AAPL', 'MSFT', 'NVDA', 'JPM']

Tuples

  • A tuple is an ordered sequence like a list, but it is written with parentheses instead of square brackets.

  • A tuple is immutable: once created, its items cannot be changed, added, or removed.

  • Tuples suit small records that should not change, such as a quote that pairs a ticker with a price. Indexing and for loops work the same way as with a list.

quote = ("AAPL", 189.50)

print(quote)
print(f"Ticker: {quote[0]}")
print(f"Price:  {quote[1]}")
('AAPL', 189.5)
Ticker: AAPL
Price:  189.5

Trying to Change a Tuple

  • Assigning to an item of a tuple raises a TypeError. This prevents accidental changes to a record that should stay fixed.
# This will fail: tuples do not support item assignment
quote[1] = 192.25
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[16], line 2
      1 # This will fail: tuples do not support item assignment
----> 2 quote[1] = 192.25

TypeError: 'tuple' object does not support item assignment
  • To record a new price, create a new tuple and assign it to the same variable. Reassigning a variable is allowed; changing a tuple in place is not.
quote = ("AAPL", 192.25)
quote
('AAPL', 192.25)

Unpacking a Tuple

  • Unpacking assigns each item of a tuple to its own variable in one line. The number of variables on the left must match the number of items.
ticker, price = quote
print(f"{ticker} last traded at ${price:.2f}")
AAPL last traded at $192.25

A List of Tuples as a Table

  • A list of tuples works like a small table: each tuple is a row, and each position is a column.

  • Unpacking in the for line gives each column its own variable. Each holding below is a (ticker, shares, price) record.

holdings = [("AAPL", 40, 192.25), ("JPM", 25, 201.10), ("KO", 100, 62.40)]
total_value = 0

for ticker, shares, price in holdings:
    value = shares * price
    total_value += value
    print(f"{ticker}: {shares} shares x ${price:.2f} = ${value:,.2f}")

print(f"Total value: ${total_value:,.2f}")
AAPL: 40 shares x $192.25 = $7,690.00
JPM: 25 shares x $201.10 = $5,027.50
KO: 100 shares x $62.40 = $6,240.00
Total value: $18,957.50

Note: A tuple with a single item needs a trailing comma: (0.05,) is a tuple, but (0.05) is just the number 0.05 in parentheses.

Choosing Between Lists and Tuples

  • Use a list for a collection that changes, such as a watchlist or a price history that gains a new close every day.

  • Use a tuple for a fixed record whose parts belong together, such as a quote or an executed trade.

  • If unsure, start with a list. Use a tuple when the data should not change after it is created.

In-Class Exercise

4-1. Watchlist Review:

  • Create a list my_tickers with five tickers of your choice.
  • Loop through it and print a line such as Reviewing MSFT... for each ticker.
  • After the loop, print one line reporting how many tickers were reviewed (use len()).

4-2. Savings Schedule:

  • Use a list comprehension with range() to build the year-end balances of 2,000 dollars deposited at 4% annual interest, compounded annually, for years 1 through 10.
  • Print the final balance and the total interest earned (final balance minus the deposit).

4-3. Returns and a Moving Average:

  • Start from prices = [50.00, 51.20, 50.60, 52.10, 53.00, 52.40, 54.30, 55.10].
  • Use a list comprehension to compute the daily returns, then print the best and worst day as percentages.
  • Use a slice to compute the average of the last three prices.

4-4. Copying a List:

  • Create a list core_holdings with three tickers, then make an independent copy called trial_holdings.
  • Append two tickers to trial_holdings and print both lists to confirm that core_holdings is unchanged.
  • Repeat with trial_holdings = core_holdings and explain the different result in a comment.

4-5. Trade Records:

  • Store one executed trade as a tuple (ticker, side, shares, price), for example ("MSFT", "BUY", 15, 410.30).
  • Unpack it into four variables and print a sentence describing the trade and its total cost.
  • Build a list of three such tuples and loop over it, printing one line per trade.

Ch 5: if Statements

  • Programs often need to check a condition before acting, for example whether a price is above a target or whether a ticker is on the list of stocks we are allowed to trade.

  • An if statement runs a block of code only when a condition holds. Inside a loop, it applies a rule to every item in a list.

  • This chapter covers conditional tests, the forms of the if statement, and if statements with lists.

Up Days and Down Days

  • The example below labels each of the nine daily returns from Ch 4 as an up day or a down day. The following sections explain each part in detail.

  • if daily_return > 0: tests whether the return is positive. If it is, Python runs the indented line below it; otherwise it runs the line under else:.

  • The code has two levels of indentation: the if and else lines are inside the loop, and each print is inside its branch.

for daily_return in daily_returns:
    if daily_return > 0:
        print(f"{daily_return:.2%}  up")
    else:
        print(f"{daily_return:.2%}  down")
1.50%  up
-1.67%  down
2.51%  up
0.78%  up
-1.16%  down
2.26%  up
0.77%  up
-2.29%  down
2.05%  up

Comparisons and Boolean Tests

  • The expression after if is a conditional test: an expression whose value is either True or False.

  • You can evaluate a test on its own to see its value. When an if statement does not behave as expected, check the value of its test first.

Equality: ==

  • A single equals sign (=) assigns a value. A double equals sign (==) tests whether two values are equal.
ticker = "MSFT"            # assignment: store "MSFT" in ticker

print(ticker == "MSFT")    # test: is ticker equal to "MSFT"?
print(ticker == "AAPL")
True
False
  • Writing = where a test needs == is a common mistake. In an if line, Python raises a SyntaxError and suggests the fix.
# This will fail: = assigns a value; a test needs ==
if ticker = "MSFT":
    print("Found MSFT")
  Cell In[22], line 2
    if ticker = "MSFT":
       ^
SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='?

Case-Insensitive Comparison

  • String comparisons are case-sensitive: "msft" and "MSFT" are different strings.

  • Tickers are usually written in uppercase, but user input and data files are not always consistent, so normalize a string before comparing it. .strip() removes leading and trailing spaces, and .upper() converts to uppercase.

  • These methods return a new string and leave the original unchanged, so store the cleaned version if you need it again.

user_ticker = " msft "
clean_ticker = user_ticker.strip().upper()

print(user_ticker == "MSFT")
print(clean_ticker == "MSFT")
print(f"Original: '{user_ticker}'  Cleaned: '{clean_ticker}'")
False
True
Original: ' msft '  Cleaned: 'MSFT'

Inequality: !=

  • != means "is not equal to." Use it when every case except one needs the same handling, for example any trade not settled in US dollars.
currency = "EUR"

if currency != "USD":
    print(f"Trade in {currency}: convert to USD before settlement")
Trade in EUR: convert to USD before settlement

Price and Return Thresholds

  • Numbers can be compared with ==, !=, <, <=, >, and >=.

  • Price alerts and risk limits use these comparisons. A stop-loss is a price at which a trader plans to sell to limit a loss, so the test is whether the current price is at or below it.

  • Returns are compared the same way. A fall of more than 2% means a return below -0.02.

price = 94.80
stop_loss = 95.00
target = 110.00
daily_return = -0.021

print(price <= stop_loss)     # at or below the stop-loss?
print(price >= target)        # at or above the profit target?
print(daily_return < -0.02)   # down more than 2% today?
True
False
True

Note: Be careful with == on decimal numbers. Computers store most decimals approximately, so two weights of 10% and 20% do not add up to exactly 30%. Rounding before comparing avoids the problem.

weight_a = 0.1
weight_b = 0.2

print(weight_a + weight_b)
print(weight_a + weight_b == 0.3)
print(round(weight_a + weight_b, 10) == 0.3)
0.30000000000000004
False
True

Combining Tests: and, or

  • and is True only when both tests are true; or is True when at least one is true.

  • Consider a value screen with two tests: a P/E ratio below 20 and a dividend yield above 2%. A stock with a P/E of 17.5 and a 1.2% yield passes the first test but not the second.

pe_ratio = 17.5
dividend_yield = 0.012

print(pe_ratio < 20 and dividend_yield > 0.02)   # both must hold
print(pe_ratio < 20 or dividend_yield > 0.02)    # one is enough
False
True
  • or suits alerts that should fire in several situations, such as a move of more than 3% in either direction. Parentheses around each test are optional but make the line easier to read.

  • Python also allows chained comparisons, written as in math: 0 <= weight <= 0.25 checks that a portfolio weight is between 0% and 25%.

daily_return = -0.034
weight = 0.18

print((daily_return > 0.03) or (daily_return < -0.03))   # big move?
print(0 <= weight <= 0.25)                               # within limit?
True
True

Membership: in and not in

  • in tests whether a value appears in a list; not in tests whether it is absent.

  • Use them to check a ticker against a watchlist or a list of approved tickers.

new_idea = "AMZN"

print("NVDA" in watchlist)
print(new_idea in watchlist)

if new_idea not in watchlist:
    watchlist.append(new_idea)
    print(f"Added {new_idea}: {watchlist}")
True
False
Added AMZN: ['AAPL', 'MSFT', 'NVDA', 'JPM', 'XOM', 'KO', 'AMZN']

Boolean Values

  • True and False are values of type bool (short for Boolean). They are capitalized and written without quotes.

  • A Boolean variable records a yes-or-no state, such as whether the market is open or whether a position is in profit. It can be assigned directly or from the result of a test.

  • The built-in type() reports the type of any value.

market_open = True
margin_enabled = False

last_price = 192.25
cost_basis = 180.00   # price paid per share
in_profit = last_price > cost_basis

print(market_open, margin_enabled, in_profit)
print(type(in_profit))
True False True
<class 'bool'>

Making Decisions with if

  • An if statement comes in three common forms: a plain if, if-else, and if-elif-else.

  • As with a for loop, each if, elif, and else line ends with a colon, and the code it controls is indented below it.

A Plain if

  • A plain if runs its block when the test is True and does nothing when it is False.
price = 112.40
target = 110.00

if price >= target:
    print(f"ALERT: price {price:.2f} has reached the target of {target:.2f}")
ALERT: price 112.40 has reached the target of 110.00

if-else

  • if-else chooses between two actions: one when the test is True and the other when it is False.

  • The cell below checks whether the account has enough cash to pay for a buy order before sending it.

cash = 5_000.00
shares = 30
price = 189.50
order_cost = shares * price

if order_cost <= cash:
    print(f"Order sent: {shares} shares for ${order_cost:,.2f}")
else:
    print(f"Rejected: costs ${order_cost:,.2f}, cash is ${cash:,.2f}")
Rejected: costs $5,685.00, cash is $5,000.00

Three or More Cases: if-elif-else

  • For more than two cases, use an if-elif-else chain; elif is short for "else if."

  • Python checks the tests from top to bottom, runs the block of the first test that is True, and skips the rest. The else block runs only if every test is False.

  • The example below classifies a daily return as a rally, a sell-off, or a normal day.

daily_return = -0.0229

if daily_return >= 0.02:
    label = "rally"
elif daily_return <= -0.02:
    label = "sell-off"
else:
    label = "normal day"

print(f"{daily_return:.2%}: {label}")
-2.29%: sell-off

A Commission Schedule

  • A chain can include as many elif blocks as needed. Suppose a broker charges a lower commission rate on larger trades: 0.50% below 10,000 dollars, 0.25% below 50,000, 0.10% below 250,000, and 0.05% from 250,000 up.

  • Because the tests run in order, each elif needs only an upper bound; the lower bound is implied by the tests that already failed.

  • The final else covers every remaining case, here any trade of 250,000 dollars or more. A chain does not have to end with else.

for trade_value in [5_000, 30_000, 72_000, 400_000]:
    if trade_value < 10_000:
        commission_rate = 0.0050
    elif trade_value < 50_000:
        commission_rate = 0.0025
    elif trade_value < 250_000:
        commission_rate = 0.0010
    else:
        commission_rate = 0.0005
    commission = trade_value * commission_rate
    print(f"${trade_value:,}: {commission_rate:.2%} -> ${commission:,.2f}")
$5,000: 0.50% -> $25.00
$30,000: 0.25% -> $75.00
$72,000: 0.10% -> $72.00
$400,000: 0.05% -> $200.00

Independent Tests vs. a Chain

  • A chain runs at most one block. This is correct when the cases are mutually exclusive; for example, each trade falls into exactly one commission tier.

  • When several conditions can be true at the same time and each needs a response, use separate if statements. Python checks each one regardless of the results of the others.

  • In the example below, one position is more than 25% of the portfolio, down more than 10% since purchase, and facing an earnings announcement this week, so all three checks print an alert.

position_weight = 0.31     # share of the portfolio in this stock
return_since_buy = -0.14   # return since the shares were bought
earnings_this_week = True

if position_weight > 0.25:
    print("Alert: position exceeds 25% of the portfolio")
if return_since_buy < -0.10:
    print("Alert: position is down more than 10% since purchase")
if earnings_this_week:
    print("Alert: earnings announcement this week")
Alert: position exceeds 25% of the portfolio
Alert: position is down more than 10% since purchase
Alert: earnings announcement this week
  • Written as a chain, the same checks report only the first alert, because Python stops at the first True test and skips the rest.

  • Use a chain when the cases cannot overlap (commission tiers) and separate if statements when they can (risk alerts).

if position_weight > 0.25:
    print("Alert: position exceeds 25% of the portfolio")
elif return_since_buy < -0.10:
    print("Alert: position is down more than 10% since purchase")
elif earnings_this_week:
    print("Alert: earnings announcement this week")
Alert: position exceeds 25% of the portfolio

Combining Loops and Conditions

  • An if statement inside a for loop applies a rule to every item in a list, for example to accept or reject each order or to keep only the negative returns. An if before the loop can check whether the list is empty.

Handling an Empty List

  • Before looping over a list, you can check whether it has any items.

  • In an if test, a non-empty list counts as True and an empty list counts as False, so if pending_orders: means "if there are any pending orders."

pending_orders = []

if pending_orders:
    for order in pending_orders:
        print(f"Sending order for {order}")
else:
    print("No pending orders. Nothing to send.")
No pending orders. Nothing to send.

Checking Orders Against a Tradable List

  • Brokers and funds restrict which securities can be traded, so every incoming order should be checked against the approved list before it is sent.

  • These orders come from different sources and are not consistently formatted, so each ticker is normalized before the in test.

tradable = ["AAPL", "MSFT", "NVDA", "AMZN", "JPM", "XOM", "KO"]
orders = ["nvda", "KO", "TSLA", " aapl", "BRK.B"]

for order in orders:
    symbol = order.strip().upper()
    if symbol in tradable:
        print(f"{symbol}: accepted")
    else:
        print(f"{symbol}: rejected, not on the tradable list")
NVDA: accepted
KO: accepted
TSLA: rejected, not on the tradable list
AAPL: accepted
BRK.B: rejected, not on the tradable list

Filtering a List

  • Filtering builds a new list that keeps only the items that pass a test. One way is to start with an empty list and call append() inside an if.
accepted = []

for order in orders:
    symbol = order.strip().upper()
    if symbol in tradable:
        accepted.append(symbol)

print(accepted)
['NVDA', 'KO', 'AAPL']
  • A list comprehension can also filter. Adding an if clause at the end, [expression for item in sequence if test], keeps only the items that pass the test.
down_days = [r for r in daily_returns if r < 0]

print(f"{len(down_days)} down days out of {len(daily_returns)}")
print([f"{r:.2%}" for r in down_days])
3 down days out of 9
['-1.67%', '-1.16%', '-2.29%']

Writing Readable Conditions

  • Put spaces around comparison operators: write price >= target, not price>=target. PEP 8, Python's official style guide, recommends this.

  • Test Boolean values directly: write if market_open: rather than if market_open == True:, and if not pending_orders: rather than if len(pending_orders) == 0:. (not turns True into False and False into True.)

  • For a complex condition, store each part in a Boolean variable with a descriptive name, so that the if line reads like a sentence.

pe_ratio = 14.2
dividend_yield = 0.034
debt_to_equity = 0.6

is_cheap = pe_ratio < 15
pays_dividend = dividend_yield > 0.02
low_leverage = debt_to_equity < 1.0

if is_cheap and pays_dividend and low_leverage:
    print("Passes the value screen")
Passes the value screen

In-Class Exercise

5-1. Price Alert:

  • Set price = 87.25 and alert_level = 90.00.
  • Write an if-else that prints an alert if the price is at or below the alert level, and otherwise prints how far above the level the price is, in dollars.
  • Change price and re-run the cell to test both branches.

5-2. Ticker Lookup:

  • A user types the ticker " Jpm", with a leading space and mixed case.
  • Normalize it and use in to check whether it is on watchlist. Print a message either way.

5-3. Return Buckets:

  • Loop through daily_returns and use an if-elif-else chain to count rally days (+2% or more), sell-off days (-2% or less), and normal days.
  • Print the three counts and check that they add up to len(daily_returns).

5-4. Order Desk:

  • Create a list approved with five tickers and a list incoming with four orders; make at least one order lowercase and at least one not approved.
  • If incoming is empty, print No orders today.; otherwise, loop through it, normalize each ticker, and print filled or rejected for each order.
  • Test the empty case by setting incoming = [].

5-5. Value Screen:

  • Start from stocks = [("AAPL", 29.5, 0.005), ("JPM", 12.1, 0.022), ("XOM", 13.4, 0.034), ("NVDA", 48.0, 0.001), ("KO", 23.7, 0.029)], where each tuple is (ticker, P/E ratio, dividend yield).
  • Collect the tickers with a P/E below 20 and a dividend yield above 2% in a list, then print it.
  • If no stock passes, print a message instead. Test this by lowering the P/E cutoff to 10.