Quant Quickstart II: Working with Multiple Securities

Leo Smigel
March 16, 2020

In our previous post, we discussed how to backtest a simple crossover strategy for a single stock using Backtrader and Intrinio. In this post, we're going to build on this framework and graph the RSI of multiple securities. Before we can start graphing, we need to get the data. I will once again use the sandbox so anyone can play along.

If we haven't met yet, my name is Leo Smigel and I'm a quantamental investor that uses Backtrader and Intrinio to make money in the markets.  You can find the code and associated files for this post in the Intrinio section on the Analyzing Alpha Github.

Getting the Data

Let's start by activating our environment and opening the Python interpreter in the directory where we want to save our data files. If any of this is new to you, please read part one of this series.

source env/bin/activate
python3

Now that you're in the Python interpreter, let's import CSV and the Intrinio API so we can save the price data to comma-separated files.

import csv
import intrinio_sdk

With the Intrinio SDK imported, we need a list of stocks to work with. The Intrinio Developer Sandbox provides us with the Dow 30. Let's create a list with these stocks in alphabetical order so we can iterate through them and save the pricing data as CSV files.

STOCKS = [
'AAPL', 'AXP', 'BA', 'CAT', 'CSCO',
'CVX', 'DIS', 'DWDP', 'GE', 'GS',
'HD', 'IBM', 'INTC', 'JNJ', 'JPM',
'KO', 'MCD', 'MMM', 'MRK', 'MSFT',
'NKE', 'PFE', 'PG', 'TRV', 'UNH',
'UTX', 'V', 'VZ', 'WMT', 'XOM'
]

With a list of the Dow 30, we can now iterate through them using code similar to part one of this series. Let's grab the security_api so we can do just that.

intrinio_sdk.ApiClient().configuration.api_key['api_key'] = APIKEY,
security_api = intrinio_sdk.SecurityApi()

Now let's loop through our list to get each stock's OHLCV data, and then save it to a CSV file.

for stock in STOCKS:
    # Loop through the API response creating a list in the format we need.
    api_response = security_api.get_security_stock_prices(stock)
    prices = []
        for row in api_response.stock_prices_dict:
        date = row['date']
        o = row['adj_open']
        h = row['adj_high']
        l = row['adj_low']
        c = row['adj_close']
        v = row['adj_volume']
        prices.append([date, o, h, l, c, v])

    # Reverse the list so the data is oldest to newest
    prices = reversed(prices)

    # Write the list to a csv file
    filename = stock + '.csv'
    with open(filename, mode='w') as f:
    writer = csv.writer(f, delimiter=',')
    writer.writerows(prices)

You should now have the Dow 30 price data as comma-separated-files in the directory you ran the Python interpreter from.

Graphing the Data

With the data in place, let's expand upon our previous backtesting code to include multiple securities. We'll first grab backtrader. We don't need to include intrinio_sdk or csv here unless you want to lump everything into one file.

import backtrader as bt

Next up, we'll create a dictionary for the Dow 30. I copied and pasted from the dictionary above.

STOCKS = [
'AAPL', 'AXP', 'BA', 'CAT', 'CSCO',
'CVX', 'DIS', 'DWDP', 'GE', 'GS',
'HD', 'IBM', 'INTC', 'JNJ', 'JPM',
'KO', 'MCD', 'MMM', 'MRK', 'MSFT',
'NKE', 'PFE', 'PG', 'TRV', 'UNH',
'UTX', 'V', 'VZ', 'WMT', 'XOM'
]

With a list of our stock tickers, we can now create our strategy. Backtrader uses an object-oriented approach. We create a class called MyStrategy and define a few params. Parameters are simply a dictionary to store values to make our lives easier. Backtrader does work behind the scenes to make these params available to us.

Without getting too technical, we use initialize to more efficiently calculate the RSI. We'll get into this later. __init__ loops through the data files provided from Intrinio and calculates an RSI.

class MyStrategy(bt.Strategy):
    params = dict(
    num_positions=5,
    weekdays=5,
    weekcarry=True,
    rsi_period=5
)

def __init__(self):
    self.rsi = {}
    for d in self.datas:
        self.rsi[d] = bt.ind.RSI(d, period=self.p.rsi_period)

Now that our strategy has been created, we can run our backtest. You'll notice I've added commissions and I'm looping through the CSV files and adding them as datas. Note how this time I've added a name to the data. We'll use this in the future. Also, to make this more readable, I'm only adding three stocks for our graph via slicing: for stock in STOCKS[:3].

if name__ == '__main':
    cerebro = bt.Cerebro()
    cerebro.broker.setcash(2500.0)
    cerebro.broker.setcommission(commission=0.001)
# Read in the csv file we created and add the data.
    for stock in STOCKS[:3]:
        filename = stock + '.csv'
        data = bt.feeds.GenericCSVData(
            dataname=filename,
            dtformat=('%Y-%m-%d'),
            datetime=0,
            high=2,
            low=2,
            open=1,
            close=4,
            volume=5,
            openinterest=-1
        )

        cerebro.adddata(data, name=stock)

     # Add the strategy
     cerebro.addstrategy(MyStrategy)
     # Run the strategy printing the starting and ending values, and plot the results.

     print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())
    cerebro.run()
    print('Ending Portfolio Value: %.2f' % cerebro.broker.getvalue())
    cerebro.plot()

When you execute the code, it'll create a graph that looks like the following.

Quant quickstart

Congratulations! In the next post in this series, we'll rank the Dow 30 by RSI and create a mean reversion strategy. For those of you that can't wait, try to see if you can do it on your own before next month's post. All of the code you'll need can be found on the stop-loss strategy backtest on Analyzing Alpha.