Pick one outcome with if, elif and else, and see how the order of the tests decides which one you get.
if runs a block of code when its test is True. else catches everything the if missed. elif (short for “else if”) adds more tests in between, and Python runs the first block whose test is True and skips the rest.
I show two rules built this way: one that labels a day up, flat or down, and one that turns an RSI reading into a position of +1, -1 or 0.
Step 1. Label a day up, flat or down
Four outcomes need if, two elif tests, and else. Python reads the tests top to bottom.
returns = [None, 0.1, 0.0, -0.1] # daily returns; None means no prior dayfor r in returns:if r isNone: # test 1 label ="no prior day"elif r >0: # test 2, reached only if test 1 was False label ="up"elif r ==0: # test 3, reached only if tests 1 and 2 were False label ="flat"else: # nothing above matched label ="down"print(r, "->", label)# -> None -> no prior day# -> 0.1 -> up# -> 0.0 -> flat# -> -0.1 -> down
None -> no prior day
0.1 -> up
0.0 -> flat
-0.1 -> down
Each value lands in exactly one branch. A day cannot come out both up and flat.
Step 2. The first true test wins
Once a test passes, Python stops looking. Here 0.06 passes both r > 0 and r > 0.05, and the one written first runs.
r =0.06if r >0: # 0.06 > 0 is True, so this block runs size ="gain"elif r >0.05: # never reached for 0.06 size ="big gain"else: size ="loss or flat"print(size) # -> gain
gain
Swap the two tests and the same number comes out differently.
r =0.06if r >0.05: # the tighter test first size ="big gain"elif r >0: size ="gain"else: size ="loss or flat"print(size) # -> big gain
big gain
The narrower test goes above the wider one. Otherwise the wider one swallows every value the narrower one was meant to catch.
Step 3. Put the None check first
None marks a missing value, and comparing it with a number raises an error.
r =Nonetry: r >0exceptTypeErroras e:print("TypeError:", e)# -> TypeError: '>' not supported between instances of 'NoneType' and 'int'
TypeError: '>' not supported between instances of 'NoneType' and 'int'
if r is None: at the top of the chain sends missing values into their own branch, so the comparisons below only see numbers. That is the order used in Step 1.
Step 4. Turn an RSI reading into a position
RSI is an indicator bounded between 0 and 100. Below 30 counts as oversold and above 70 as overbought. I map each reading to a position: +1 for long, -1 for short, 0 for flat.
rsi = [None, 22.0, 48.0, 74.0, 55.0] # RSI at each day's close; None before there is enough historypositions = []for x in rsi:if x isNone: # no reading yet, so hold nothing pos =0.0elif x <30: # oversold, go long pos =1.0elif x >70: # overbought, go short pos =-1.0else: # 30 to 70 inclusive pos =0.0 positions.append(pos)print(positions) # -> [0.0, 1.0, 0.0, -1.0, 0.0]
[0.0, 1.0, 0.0, -1.0, 0.0]
The else gives every reading a value. Drop it and pos keeps whatever it held on the previous pass through the loop.
p =Nonekept = []for x in [22.0, 48.0, 74.0]:if x <30: p =1.0elif x >70: p =-1.0# no else here kept.append(p)print(kept) # -> [1.0, 1.0, -1.0]
[1.0, 1.0, -1.0]
The middle reading is 48, which matches neither test, so it reuses the 1.0 set by the reading before it.
Your turn
Label a single RSI reading x = 25.0 as "very oversold" (below 20), "oversold" (below 30) or "neutral", and give None its own branch. Which test has to come before which?
TipShow answer
x =25.0if x isNone: # first, so the comparisons below only see numbers label ="no reading"elif x <20: # tighter test above the wider one label ="very oversold"elif x <30: label ="oversold"else: label ="neutral"print(label) # -> oversold
With the last two tests swapped, x = 15.0 prints "oversold", because 15.0 < 30 passes first.