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
forloop applies the same steps to each item in a list, however long the list is.An
ifstatement 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):
forloops 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, andifstatements 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
forloop 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 ofwatchlist, stores it in the variableticker, and runs the indented block below.When the block finishes, Python goes back to the
forline, stores the next item inticker, 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 watchlistorfor price in prices.
Inside vs. After the Loop
The indented lines after the
forline 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 += valueis shorthand fortotal = total + value. Python ignores the underscore in12_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
IndentationErrorand 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
forline raises aSyntaxError.Python cannot catch every indentation mistake. If the final
printin 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 withmin(),max(), andsum(), and build a new list from an existing one with a list comprehension.
Counting with range()
range(start, stop)produces the integers fromstartup to, but not including,stop.To count years 1 through 5, write
range(1, 6). Forgetting thatstopis 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 producesnintegers: 0 throughn - 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
rangeproduces its numbers on demand instead of storing them. Wrap it inlist()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
yearyears the balance isprincipal * (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 issum(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
expressionfor eachitemand 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
tis that day's close divided by the previous close, minus one:closes[t] / closes[t - 1] - 1. The comprehension below applies this formula tocloses.The first day has no previous close, so
truns from 1 tolen(closes) - 1, and ten prices give nine returns.The result is an ordinary list, so
max()andmin()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 as2.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(),startis included andstopis excluded, socloses[0:3]returns the items at positions 0, 1, and 2.Omit
startto begin at the first item; omitstopto 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
forloop can run over it.Suppose
rankedlists the watchlist from best to worst performer this month. Looping overranked[: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_watchlistdoes 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 inmy_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 callmy_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
forloops 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
forline 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_tickerswith 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_holdingswith three tickers, then make an independent copy calledtrial_holdings. - Append two tickers to
trial_holdingsand print both lists to confirm thatcore_holdingsis unchanged. - Repeat with
trial_holdings = core_holdingsand 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
ifstatement 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
ifstatement, andifstatements 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 underelse:.The code has two levels of indentation: the
ifandelselines are inside the loop, and eachprintis 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
ifis a conditional test: an expression whose value is eitherTrueorFalse.You can evaluate a test on its own to see its value. When an
ifstatement 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 anifline, Python raises aSyntaxErrorand 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
andisTrueonly when both tests are true;orisTruewhen 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 enoughFalse True
orsuits 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.25checks 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
intests whether a value appears in a list;not intests 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
TrueandFalseare values of typebool(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
ifstatement comes in three common forms: a plainif,if-else, andif-elif-else.As with a
forloop, eachif,elif, andelseline ends with a colon, and the code it controls is indented below it.
A Plain if
- A plain
ifruns its block when the test isTrueand does nothing when it isFalse.
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-elsechooses between two actions: one when the test isTrueand the other when it isFalse.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-elsechain;elifis 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. Theelseblock runs only if every test isFalse.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
elifblocks 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
elifneeds only an upper bound; the lower bound is implied by the tests that already failed.The final
elsecovers every remaining case, here any trade of 250,000 dollars or more. A chain does not have to end withelse.
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
ifstatements. 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
Truetest and skips the rest.Use a chain when the cases cannot overlap (commission tiers) and separate
ifstatements 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
ifstatement inside aforloop applies a rule to every item in a list, for example to accept or reject each order or to keep only the negative returns. Anifbefore 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
iftest, a non-empty list counts asTrueand an empty list counts asFalse, soif 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
intest.
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 anif.
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
ifclause 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, notprice>=target. PEP 8, Python's official style guide, recommends this.Test Boolean values directly: write
if market_open:rather thanif market_open == True:, andif not pending_orders:rather thanif len(pending_orders) == 0:. (notturnsTrueintoFalseandFalseintoTrue.)For a complex condition, store each part in a Boolean variable with a descriptive name, so that the
ifline 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.25andalert_level = 90.00. - Write an
if-elsethat 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
priceand 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
into check whether it is onwatchlist. Print a message either way.
5-3. Return Buckets:
- Loop through
daily_returnsand use anif-elif-elsechain 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
approvedwith five tickers and a listincomingwith four orders; make at least one order lowercase and at least one not approved. - If
incomingis empty, printNo orders today.; otherwise, loop through it, normalize each ticker, and printfilledorrejectedfor 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.