List indices must be integers or slices, not Bar // Having trouble resolving

So I have had some working code for awhile but when trying to get back and do some refactoring, I am running into a ‘list indices must be integers or slices, not Bar’ and cannot seem to figure out how to resolve it.

I am doing a simple iteration through tickers based on certain conditions to generate a list but now sure if its the dates I am passing through or have I am formulating the dataframe.

def get_tickers(api: tradeapi.REST):
    assets = api.list_assets(status="active", asset_class="us_equity")
    symbols = [asset.symbol for asset in assets if asset.tradable]
    
    '''Getting the list of symbols that can be traded'''
    start_time = (pd.to_datetime(datetime.now()) - pd.Timedelta("500000T")) 
    start_time = start_time.replace(second=0, microsecond=0).tz_localize('GMT').tz_convert('America/New_York')

    end_time = (pd.to_datetime(datetime.now()))
    end_time = end_time.replace(second=0, microsecond=0).tz_localize('GMT').tz_convert('America/New_York')
    limit = 250
    
    j = 0
    symbol_list = []
    price_list = []
    for i in tqdm(range(200, len(symbols)+1, 200)):
        get_symbols = symbols[j:i]
        daily_bars = api.get_bars(symbol=get_symbols, timeframe=TimeFrame.Day, start=start_time.isoformat(), end=end_time.isoformat(), limit=limit, adjustment='raw')
        for bar in daily_bars:
            try:
                if daily_bars[bar] != []:
                    df = daily_bars[bar].df
                    
                    # macd_line = macd(df["close"], window_slow=26, window_fast=12, fillna=True)
                    # macd_signal_line = macd_signal(df["close"], window_slow=26, window_fast=12, window_sign=9, fillna=True)
                    sma200_line = sma_indicator(df["close"], window=200, fillna=True)
                    
                    
                    cond_1 = df.iloc[-1]["close"] >= signal_min_share_price
                    cond_2 = df.iloc[-1]["close"] <= signal_max_share_price
                    cond_3 = df.iloc[-1]["close"] < round(sma200_line[-1],2)
                    
                    if cond_1 and cond_2 and cond_3:
                        symbol_list.append(bar)
                        price_list.append(df.iloc[-1]["close"])
            except Exception as e:
                logging.warning(e)
                pass

        j = i
    symbols_to_trade_df = pd.DataFrame({
        "Stocks": symbol_list,
        "Price": price_list,
    })
    symbols_to_trade = symbols_to_trade_df.sort_values(['Price'], ascending=[False])
    symbols_to_trade = symbols_to_trade.reset_index(drop=True)
    print(symbols_to_trade[:20])
    print(start_time)
    print(end_time)
    return symbols_to_trade.loc[:, "Stocks"].to_list()

Tried updating that for loop variabel and dictionary, still no luck iterating through the list:

for i in tqdm(range(200, len(symbols)+1, 200)):
        get_symbols = symbols[j:i]
        daily_bars = api.get_bars(symbol=get_symbols, timeframe=TimeFrame.Day, start=start_time.isoformat(), end=end_time.isoformat(), limit=limit, adjustment='raw')
        for bar, daily_bar in daily_bars:

            try:                
                # macd_line = macd(df["close"], window_slow=26, window_fast=12, fillna=True)
                # macd_signal_line = macd_signal(df["close"], window_slow=26, window_fast=12, window_sign=9, fillna=True)
                # bollinger_band_high = bollinger_hband(df["close"], window_slow=34, window_dev=2.1, fillna=True)
                # bollinger_band_low = bollinger_lband(df["close"], window_slow=34, window_dev=2.1, fillna=True)
                sma200_line = sma_indicator(daily_bar["close"], window=200, fillna=True)
                
                cond_1 = daily_bar.iloc[-1]["close"] >= signal_min_share_price
                cond_2 = daily_bar.iloc[-1]["close"] <= signal_max_share_price
                cond_3 = daily_bar.iloc[-1]["close"] < round(sma200_line[-1],2)
                
                if cond_1 and cond_2 and cond_3:
                    symbol_list.append(bar)
                    price_list.append(daily_bar.iloc[-1]["close"])
            except Exception as e:
                logging.warning(e)
                pass

        j = i
    symbols_to_trade_df = pd.DataFrame({
        "Stocks": symbol_list,
        "Price": price_list,
    })

@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, 
                          timeframe='1Day', 
                          start=start_time.isoformat(), 
                          end=end_time.isoformat(), 
                          adjustment='split').df

# 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 sample method.

daily_bars = api.get_bars(symbol=tradable_assets.symbol.sample(100), 
                          timeframe='1Day', 
                          start=start_time.isoformat(), 
                          end=end_time.isoformat(), 
                          adjustment='split').df

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.

Good luck.

1 Like

A generalized approach to selecting a group of stocks to buy or sell or hold etc is as follows.

Once one fetches the required data, then create a single ‘indicators’ dataframe with any indicators that will be used in the selection logic. One can easily add technical indicators with the help of a package such as ta and the agg method. I like to wrap any technical indicators in small functions for clarity and control. Something like this

!pip install ta
import ta

def bollinger_high(close_prices):
  bb = ta.volatility.BollingerBands(close=close_prices, window=20, window_dev=2)
  return bb.bollinger_hband().tail(1)

def bollinger_low(close_prices):
  bb = ta.volatility.BollingerBands(close=close_prices, window=20, window_dev=2)
  return bb.bollinger_lband().tail(1)

def sma_200(close_prices):
  sma = ta.trend.SMAIndicator(close=close_prices, window=200)
  return sma.sma_indicator().tail(1)

def macd_diff(close_prices):
  macd = ta.trend.MACD(close=close_prices, 
                      window_slow=26, 
                      window_fast=12, 
                      window_sign=9, 
                      fillna=False)
  return macd.macd_diff().tail(1)

Next create create the indicators dataframe an add any desired columns something like this. The groupby, agg, and last methods do the heavy lifting. Simply put the desired function into the agg method.

# Generalized approach to placing all indicators into a single dataframe
# Create an empty dataframe and add columns for each indicator using the agg methd
indicators = pd.DataFrame()

indicators['last_close'] = daily_bars.groupby('symbol').close.last()
indicators['last_volume'] = daily_bars.groupby('symbol').volume.last()

indicators['sma_200'] = daily_bars.groupby('symbol').close.agg(sma_200)
indicators['bb_high'] = daily_bars.groupby('symbol').close.agg(bollinger_high)
indicators['bb_low'] = daily_bars.groupby('symbol').close.agg(bollinger_low)
indicators['macd_diff'] = daily_bars.groupby('symbol').close.agg(macd_diff)

Finally, define the selection logic and use the query method to select symbols. The only reason to create a string first is to allow multi lines for readability. The nlargest or nsmallest methods can be used to limit the size of the selection.

# Create the 'buy' selection criteria as a string
# This can be any arbitrary logic

buy_criteria = "macd_diff > 0 \
and last_close*last_volume > 1000000 \
and last_close > sma_200 \
and last_close < bb_high \
and last_close > bb_low \
"

symbols_to_buy = indicators.query(buy_criteria).nsmallest(20, 'last_close')
symbols_to_buy_list = symbols_to_trade.index.to_list()

This is an approach I’ve been using for years. It may be something to try?

2 Likes