In this Finance with Python, Quantopian, and Zipline tutorial, we're going to continue building our query and then our trading algorithm based on this data. To start, we currently are pulling the PB ratio and the PE ratio on all companies. We need some way to filter these down a bit.
Continuing with our before_trading_start
method:
def before_trading_start(context): context.fundamentals = get_fundamentals( query( fundamentals.valuation_ratios.pb_ratio, fundamentals.valuation_ratios.pe_ratio, ) .filter( fundamentals.valuation_ratios.pe_ratio < 14 ) .filter( fundamentals.valuation_ratios.pb_ratio < 2 ) .order_by( fundamentals.valuation.market_cap.desc() ) .limit(context.limit) ) update_universe(context.fundamentals.columns.values)
We limit this by then using .filter
, which then limits based on our logic. In this case, our first bit of logic is that we want the pe_ratio to be less than 14. Next, we require the pb_ratio (price to book) to be less than 2. Price to Book ratio is a ratio that compares the current price of the company to its "book" value. Book value is the value of the company's assets, things that could be sold to other companies, for example. A low book value is nice because we can be confident as investors that the price probably wont drop much because the assets of the company are worth more than that, ignoring the actual value of the "brand" and the business networking that they control, as well as revenues. If a company has a price to book value less than one, this means the assets of the company could be sold off, and there'd be more money left over than the company is currently being valued for. This might signal that this is a great investment, or it might signal there is serious trouble afoot.
Next, just in case we still have a massive number of companies being returned, we go ahead and use the .order_by
to sort the return by our choice, which is market cap, descending. This means we'll have the highest valued companies first. Then, finally, we limit the return to our context.limit that we set, so 10. This entire query together returns to us the top 10 highest valued companies that have both a PE ratio less than 14 and a PB ratio less than 2.
Finally, we use update_universe
to update the universe of plausible companies that we can invest in. In order to get information, like current prices, in our handle_data
method as code runs, we need the companies to be in our "universe."
Next up, let's write our handle_data
method:
We start with:
def handle_data(context, data): cash = context.portfolio.cash current_positions = context.portfolio.positions
We start by simply referencing our current cash, and any current positions that we have. Now, we want to iterate through the stocks in our "universe." So every company in our universe has a PE ratio less than 14 and a PB ratio less than 2. Here's how we'll iterate through all of these companies:
for stock in data: current_position = context.portfolio.positions[stock].amount stock_price = data[stock].price plausible_investment = cash / context.limit share_amount = int(plausible_investment / stock_price)
We use a for loop to iterate through "data," which contains every stock in our universe as the "key" (data is a python dictionary.)
First, we go to see if we already have a position in this company. Next, we check to see the current value of that company, which we then use to create the plausible investment size, in dollars. We make this value equal to our current cash, divided by context.limit, which is 10. From here, we calculate the share_amount, which is how many shares we'll buy. From here, we're ready to possibly make a purchase:
try: if stock_price < plausible_investment: if current_position == 0: if context.fundamentals[stock]['pe_ratio'] < 11: order(stock, share_amount) except Exception as e: print(str(e))
Full code up to now:
# Put any initialization logic here. The context object will be passed to # the other methods in your algorithm. def initialize(context): context.limit = 10 # Will be called on every trade event for the securities you specify. def before_trading_start(context): context.fundamentals = get_fundamentals( query( fundamentals.valuation_ratios.pb_ratio, fundamentals.valuation_ratios.pe_ratio, ) .filter( fundamentals.valuation_ratios.pe_ratio < 14 ) .filter( fundamentals.valuation_ratios.pb_ratio < 2 ) .order_by( fundamentals.valuation.market_cap.desc() ) .limit(context.limit) ) update_universe(context.fundamentals.columns.values) def handle_data(context, data): cash = context.portfolio.cash current_positions = context.portfolio.positions for stock in data: current_position = context.portfolio.positions[stock].amount stock_price = data[stock].price plausible_investment = cash / context.limit share_amount = int(plausible_investment / stock_price) try: if stock_price < plausible_investment: if current_position == 0: if context.fundamentals[stock]['pe_ratio'] < 11: order(stock, share_amount) except Exception as e: print(str(e))
We're ready to go ahead and run this now, so let's do that. The results should be something like (depending on the dates you use):
Well, that's not really the greatest. What happened? It looked like we were doing at least okay initially. Looking at our transactions, we should see it. What we did was buy the companies, and that worked at least decently, but we never actually sold. This means we didn't sell companies that went "bad," and we never made room for new purchases.
Let's go ahead and write in some selling logic next.