@FIRMCEO I’m not sure what the error you are getting is. However, you may want to re-think your approach and not use loops. That may be what is getting you into trouble, but in any case, it can be made more ‘pythonic’ using the built in pandas methods. Probably faster too.
From what I can tell, you are trying to select the 20 lowest priced stocks where the price is between a MIN_PRICE and MAX_PRICE and the last price is below the 20 day moving average. Please correct me if that’s not quite right.
So, I’ll step through how I’d approach this.
First get a list of ‘tradable’ symbols. You did this and it resulted in a list of more than 10,000 stocks. I’d suggest adding a few more qualifiers to get that list to less than 5000. That happens to be about the max number of symbols that can be fetched in a single ‘get_bars’ call. One could break the list into chunks, but starting with a smaller list to begin with seems to make sense.
# Get a dataframe of active assets
asset_list = api.list_assets(status='active', asset_class='us_equity')
asset_df = pd.DataFrame([asset._raw for asset in asset_list])
# Select tradable assets based upon asset fields
# These criteria also reduces the list to a managable size ~4600 symbols
tradable_assets = asset_df.query('exchange!="OTC" and tradable and marginable and shortable')
Next, set the start and end dates and fetch about a years worth of daily bars. Using the
normalize method strips the sub-second time values and also aligns the dates to midnight. When fetching the data you will want to use
adjustment='split' . Otherwise, any stock splits will look like a big price change and screw up your SMA_200. Also notice the
df method. This is a built in method to convert the data into a convenient dataframe.
# Set start and end times to fetch about a years worth of daily data
end_time = pd.to_datetime('now').tz_localize('America/New_York').normalize()
start_time = end_time - pd.Timedelta("365D")
# Fetch the daily bars for our tradable_assets
daily_bars = api.get_bars(symbol=tradable_assets.symbol,
# Convert back to market time for easier reading
daily_bars = daily_bars.tz_convert('America/New_York')
# Add a column for the date for convenience
daily_bars['date'] = daily_bars.index
Now that we have all our data in single dataframe, calculate the 200 day average close price for each symbol. Then put the SMA_200, the last close, and the last date into a new dataframe. Other trading indicators and data could also be put into this dataframe. The idea is to put anything here you need to base your trading decision on.
# Calculate the 200 day sma for each symbol
last_200_bars = daily_bars.groupby('symbol').tail(200)
sma_200 = last_200_bars.groupby('symbol').close.mean()
# Fetch the last close price and last date for each symbol
last_close = daily_bars.groupby('symbol').close.last()
last_date = daily_bars.groupby('symbol').date.last()
# Put the last_date, last_close, sma_200 into a single dataframe for convenience
symbols_to_check = pd.concat([last_date, last_close, sma_200], axis=1)
# Add column names
symbols_to_check.columns = ['date', 'close', 'sma_200']
Finally, with all our trade ‘decision data’ in one place, simply use the query method to select symbols which meet your criteria. Use the
nsmallest method to just take the lowest priced symbols.
# Select the symbols which meet our criteria
# Then take the 20 symbols with the lowest close price
MIN_PRICE = 10
MAX_PRICE = 500
symbols_to_trade = symbols_to_check.query('(@MIN_PRICE < close < @MAX_PRICE) and (close < sma_200)').nsmallest(20, 'close')
That results in a selection of rows from the symbols_to_trade dataframe. If you just want a list of symbols, then do this
# If one just wants a list of symbols
symbols_to_trade_list = symbols_to_trade.index.to_list()
That should do it. It takes 4-5 minutes to fetch a years worth of data for the ~4600 tradable_assets. To test and debug perhaps start with just 100 or so symbols. This could be done using the
daily_bars = api.get_bars(symbol=tradable_assets.symbol.sample(100),
A final note. There is the potential that the latest bar may not be todays bar. Very unlikely, but it should maybe be part of the query criteria that the last date is todays date.
You probably weren’t looking for a complete re-write but this may give you some ideas.