Hi there,
I'm brand new here and very excited to explore Quantopian. Since signing up, I've managed to create a few simple algorithms and am trying to get deeper into Quantopian's capabilities by coding something a bit more dynamic. I've been working on the below algorithm for days and can't seem to solve the problem I'm having, so despite my every attempt to figure this out on my own, I think it's time I ask the community for help. Thanks in advance.
First the code, then the issue.
Logic Overview: This should take a group of stocks, sell any that have increased by 3% in the last day, and the buy any from the group that have decreased by 3% in the last day. Very simple, but I'm trying to practice using multiple securities and yesterday's values.
def initialize(context):
# Define all stocks involved in this algorithm (by sid)
context.stocks_allowed = [sid(35143), sid(31461), sid(39625), sid(25226), sid(35892), sid(25582), sid(39270), sid(18184), sid(23817), sid(39989), sid(21104), sid(27606), sid(24103), sid(17370), sid(19726), sid(40807), sid(17287), sid(24757), sid(12742), sid(26232), sid(42176), sid(21863)]
context.max_notional = 1000000.1
context.min_notional = -1000000.0
# Define the criteria by which the algorithm will purchase or sell a given security (% of change up/down)
context.percentage_change_to_sell = 3
context.percentage_change_to_buy = 3
# Don't fully understand this, but was told to do this in order to get "yesterday's closing price" for a given security
set_universe(universe.DollarVolumeUniverse(90.0, 90.1))
# handle_data broken into a series of synchronous functions
def handle_data(context, data):
sell_stocks(context, data)
buy_stocks(context, data)
# The first function called by handle_data
def sell_stocks(context, data):
# First, ensure that the portfolio contains one or more securities (otherwise, there's nothing to sell and this function becomes pointless)
if len(context.portfolio.positions) != 0:
# Convert the sell criteria (eg: 3 [meaning a 3% increase in price] becomes 1.03)
converted_percentage_of_change_to_sell = 1 + (float(context.percentage_change_to_sell)/100)
for this_stock in context.portfolio.positions:
# This should produce yesterday's closing price for a given security
price_history = history(bar_count=3, frequency='1d', field='close_price')
yesterdays_close = price_history[this_stock][-2]
# If it's increased in value enough, sell all shares
if (yesterdays_close) < (this_stock.last_sale_price * converted_percentage_of_change_to_sell):
order_target(this_stock, 0)
# The second function called by handle_data
def buy_stocks(context, data):
# Convert the buy criteria (eg: 3 [meaning 3% decrease in price] becomes 0.97)
converted_percentage_of_change_to_buy = 1 - (float(context.percentage_change_to_buy)/100)
# Establish list of stocks to purchase
stocks_to_purchase = {}
# Determine available cash before transactions (to divide evenly among the securities to buy)
wallet_snapshot = context.portfolio.cash
# Go through the allowed stocks list and determine which meet the criteria (which ones have decreased in price today)
for this_stock in context.stocks_allowed:
price_history = history(bar_count=3, frequency='1d', field='close_price')
yesterdays_close = price_history[this_stock][-2]
if (this_stock.last_sale_price < (yesterdays_close * converted_percentage_of_change_to_buy)) and (this_stock not in context.portfolio.positions):
stocks_to_purchase.append(this_stock)
# Actually purchase the stocks
for this_stock in stocks_to_purchase:
number_of_shares = (wallet_snapshot / len(stocks_to_purchase)) / this_stock.last_sale_price
order(this_stock, +number_of_shares)
The Problem: I'm getting the following build error on line 38: Runtime exception: AttributeError: 'Security' object has no attribute 'last_sale_price.' According to the API docs, last_sale_price is an attribute of positions (available through context.portfolio.positions). How is it that I am receiving this error? The only thing I can think of is that I'd have no positions, but this is all wrapped in:
if len(context.portfolio.positions) != 0:
...so in such cases, it should never get to line 38; furthermore, such an error should be a runtime error, not a build error, should it not?
Thanks in advance for any advice.