As a capstone to chapter 1, let us build a function that serves as a robo-adivsor for a user’s investment portfolio.
There are two new ingredients to add to make a simplified robo-advisor work: type-conversion and input.
We won’t build a complete robo-advisor just yet, but we can make some progress towards building one using the material from this chapter.
Two basic data types within Python are integers and strings.
my_int = 1 my_str = '1'
The type of a variable can be checked with the
print( type(my_int) ) print( type(my_str) )
<class 'int'> <class 'str'>
Variable typing can be important to keep track of. Note that \(1\) and
'1' do not count as the same thing in Python.
'1' == 1
This matters in the event that you have a string number that you want to use as an integer (or floating point) number. Suppose you need to add the number 8 to your string number.
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-4-bc89c6d67d5b> in <module> ----> 1 8+'1' TypeError: unsupported operand type(s) for +: 'int' and 'str'
Strings containing integers can be converted into integers with the
int('1') + 8
Likewise strings containing decimal numbers can be converted into floating point numbers with the
float('1.23') + 8
Moreover, integers or floating point numbers can be converted to strings with the
Translating variables from one type to another will only be successul when the data in the variable can be reasonably converted. One could not do
int('banana') because there is no way to convert the string
'banana' into integer data.
The other new ingredient needed to get a basic form of robo-adivising to work is the
input() function. Our goal is to build a tool that asks a user a series of questions such that the tool can then recommend an investment strategy based on those questions. The
input() function is the ingredient we’ll need to ask for user input into the tool.
Consider the example:
color = input('What is your favorite color?\n') print('User entered the response:', color)
What is your favorite color? purple User entered the response: purple
input() function prints out a string, given as the argument to the function. The
input() function waits for a response and then returns that response. We store the returned value in a variable named
color. The second line of the code above simply prints out the response to verify that the
input() function worked correctly.
Responses to the
input() function are always strings.
This means that if we want to use the response as something else, like an integer, we need to convert the data.
Let’s move to another example to really see the
input() function in action. This time, we’ll ask for the user’s age and print a response that is conditional on the user’s age.
age = input('What is your age?\n') print('Equity allocation percent:', 100-int(age))
What is your age? 31 Equity allocation percent: 69
The output above represents the rule of 100. One frequently proposed piece of investment advice is for younger people to invest more in stocks (which generally grow more in value than bonds, but are riskier) and for older people to invest more in bonds (which are less risky and thus potentially safer to live off of in retirement). The rule of 100 is a trick for thinking about how much of a person’s portfolio should be in stocks: 100 minus your age is approximately the percentage of your wealth that should be in stocks. Or so the rule goes, at least. These notes are certainly not intended to serve as investment advice; the rule of 100 is just a convenient way to show up the utility of the
As another example of a conditional response, let’s ask the user how much risk they’re willing to take on.
def check_attitude(): attitude = input('How much risk are you willing to take on?\n') if attitude == 'not much': print("Okay, we'll invest mostly in bonds.") elif attitude == 'a lot': print("Okey, we'll invest mostly in equities.") else: print("I'm just a stupid robot, I can't understand you.")
The goal, of course, is to get a sense of how much risk the user is willing to take on. The robo-adivsor can allocate the user’s money into riskier investments (potentially earning higher returns) if the user is willing to bear the risk of those investment.
How much risk are you willing to take on? a lot Okey, we'll invest mostly in equities.
Of course, the problem here is that the input question, as stated above, is very open-ended. A user could in all likelihood enter a response that is not expected.
How much risk are you willing to take on? maybe a medium amount I'm just a stupid robot, I can't understand you.
Computers are getting better at open-ended questions. The rise of artificial intelligence, and in particular a topic called natural language understanding, is helping with that.
For our use case, we will give the user a bit more guidance with the response we want.
def get_risk_preference(): response = input("On a scale of 1-5, how much do you dislike risk? 1=not bothered by risk, 5=extremely worried about risk.\n") if response not in ['1', '2', '3', '4', '5']: print('Sorry, response must be 1, 2, 3, 4, or 5') print('I will guess that you meant to put 3') return 3 return int(response)
On a scale of 1-5, how much do you dislike risk? 1=not bothered by risk, 5=extremely worried about risk. 4
With these ideas, we can begin to form the start of a robo-advisor.
First, apply the rule of 100 to get a baseline for how the debt-equity mix should be allocated. Second, shift the allocation more towards stocks if the user is more okay with risk and shift the allocation more towards bonds if the user hates risk.
Note that we are allocating the task of asking for a user’s age to a separate function called
get_age(). We will return to it and the
get_risk_preference() shortly and the reason for dedicating this to a separate function will be made clear.
# get user's age def get_age() age = input('What is your age? Enter it as a numberic argument (e.g. "50", not "fifty")\n') return age
def get_allocation(): age = get_age() # apply rule of 100 equity_weight = 100-int(age) # get user's risk preference preference = get_risk_preference() # shift the allocation according to the user's preference if preference == 1: equity_weight += 10 elif preference == 2: equity_weight += 5 elif preference == 4: equity_weight -= 5 elif preference == 5: equity_weight -= 10 # correct weights to stick within 0-100 equity_weight = min(equity_weight, 100) equity_weight = max(equity_weight, 0) debt_weight = 100-equity_weight print('I would recommend', equity_weight, 'percent stocks and ', debt_weight, 'percent bonds') return [equity_weight, debt_weight]
What is your age? Enter it as a numberic argument (e.g. "50", not "fifty") 31 On a scale of 1-5, how much do you dislike risk? 1=not bothered by risk, 5=extremely worried about risk. 5 I would recommend 59 percent stocks and 41 percent bonds
while loops are the slightly trickier cousin of
for loops. A
for loop will iterate over a defined list/set of items. This may be a range, a Python
list, or something else. But we know the items over which a
for loop will iterate, which means we know exactly how many times a
for loop will iterate. For instance, if we say
for i in ['a', 'b', 'c']:
then we know that the loop will execute three times.
while loop in contrast will continue executing so long as a given condition or set of conditions is
True. The general syntax for a while loop is
while <either a condition or set of conditions>. For instance, if we do:
i = 0 while i < 3: print(i) i = i +1
0 1 2
then we can increment over an index variable
i. Let’s look at a second example that makes use of the
len() function. The
len() function will tell us the number of items in a list. So,
len(['a','b']) would be \(2\).
my_list =  # start by creating an empty list while len(my_list) < 4: print(my_list, len(my_list)) my_list.append('a')
 0 ['a'] 1 ['a', 'a'] 2 ['a', 'a', 'a'] 3
The risky bit about
while loops is that we could end up with code that never stops running! The trivial example is:
while True: print('this loop will never stop!')
True condition is given explicitly, it’s fairly obvious that the
while condition will always be
True and thus the loop will never stop. The trickier problem is when a condition turns out to be always
True and we don’t anticipate it in advance. For example:
i = 1 while i > 0: i = i + 1
this code will never end because
i will grow each iteration and consequently will always be greater than \(0\) (given the starting value is \(1\).
while loops with caution. However, they can be useful little tools. We’ll see an example applied to the
get_age() function shortly.
One of the best ways to write robust Python code is to employ the
except pair of commands. This gives us the ability to catch small problems and deal with them, rather than just “giving up” if an issue arises.
The organization for a
except block of code looks something like:
try: <stuff to try here> except Exception as e: <things to do if there is a problem>
try: 1 + 'nachos' except Exception as e: print(e) print("it's okay, not all code works")
unsupported operand type(s) for +: 'int' and 'str' it's okay, not all code works
When asking for user input, it can be helpful to pair
while loops and
except blocks. Note that in the
get_age() function above, we just assumed the user would behave and enter a numeric age (e.g.
22) rather than an age expressed by alphabetic characters (e.g.
'twenty two'). That was naughty of us! It’s much better to head off potential issues if they can be anticipated.
Consider the following block:
def get_age(): need_age = True while need_age: age = input('What is your age? Enter it as a numberic argument (e.g. "50", not "fifty")\n') try: age = int(age) # this will break (raise an exception) if age is not numeric need_age = False except Exception as e: print('Please try entering your age again') return age
What is your age? Enter it as a numberic argument (e.g. "50", not "fifty") fifty Please try entering your age again What is your age? Enter it as a numberic argument (e.g. "50", not "fifty") 30t Please try entering your age again What is your age? Enter it as a numberic argument (e.g. "50", not "fifty") 30
Concept check: The
get_age() function is now robust to users not entering in a numeric value for their age. Copy and paste the
get_risk_preference() function and modify it so that, rather than addressing errors by assuming that the response is
'3', it re-asks the user for a risk preference.