QuantConnect: Trading Multiple Stocks or Assets

In our previous getting started tutorials for QuantConnect we have focused only on trading a single stock. That stock was non-other than the worlds first trillion dollar company. But what if you can’t be in a monogamous relationship with just one asset? What if you want to trade on multiple assets at the same time using the same strategy? If that sounds like you, stick around because we are going to introduce a few more stocks to our strategy.

Scope

Once again we shall build on the tutorial that came before this one.  In that tutorial, we looked at adding a second timeframe to our simple RSI strategy. However, after adding the second timeframe, we found that opportunities became few and far between. To tackle this whilst maintaining our quality standards, we can simply look for opportunities elsewhere.

Initially, it might sound like a simple task. However, there are some intricacies we need to consider in order to get things working. For example, if you are working on multiple timeframes, it might not be immediately obvious how to work with consolidatorsor the event handlers that they call.

As such, in this tutorial, we should cover the following topics:

  • Using dictionaries and loops to create our indicators, charts, and consolidators for each stock.
  • Using a single call back with multiple consolidators.
  • Accessing additional information from a trade bar.

Possible Difficulties

Whilst we won’t cover any new concepts in this tutorial, there are some potential stumbling blocks that one could come up against when trying to add multiple timeframes. The most prominent of which is deciding how to update our RSI plots on the upper timeframe. (Last reminder, if you are wondering “what upper timeframe?” see the previous tutorial)

The challenge is that consolidator handlers receive tradebarsrather than the data slice you receive inonData(). We can see this by looking at the method (function) declarations in our algorithm:

The On Data call – (Standard on every script)

def OnData(self, data):

Our Consolidator Handler:

def On_W1(self,sender,bar):

To dive a bit deeper, data slices contain data for all the symbols you add during init. The symbol specific data can be accessed by giving the ticker/symbol as a keye.g. data["AAPL"].Close. Nice and straightforward.  On the other hand, a tradebar only contains the data for the instrument that was consolidated. As such, we either need to create a handler for every feed (which would be a bit unwieldy)  or we can use theget_Symbol()method to determine which stock the bar is coming from and then act appropriately. We will take the second option in the example code below.

Another head scratcher you may have is how to add all of the instruments and their respective indicators without duplicating line after line of code. We also have a similar conundrum when we want to handle the OnData()event. The code example shall tackle this by using a combination of for-loopsand dictionaries.

The Code

Code Commentary

The first change we have is simply to add a list of FANG stocks to join AAPLin our algorithm. This list of stocks is used everytime generate a for-loop.

Next, we create a series of dictionaries, these dictionaries are used to store data specific one each symbol in our list.  This can be seen in our first loop where we loop through the list adding consolidators, indicator, and charts to our dictionaries whilst using the symbol as the key. Using the symbols as the key allows us to easily retrieve or update the correct indicator or chart later when we start receiving data.

self.Indicators[Symbol]['RSI']['D'] = self.RSI(Symbol, RSI_Period)

In the example above, we are creating a Daily RSI indicator that can easily be retrieved later like so:

self.Indicators['AAPL']['RSI']['D']

In reality we never actually specify AAPL. Instead, we always pass theSymbolvariable from the for-loop. However, I have written AAPL here for illustration purposes as it is also a valid way to access it and also helps to cement the concept.

On_W1()

In the code, we have assigned the same handler to all of the individual consolidators. Determining what to do with the data is actually easier than it may first seem. In our example, we just use the bar.get_Symbol()method to get the symbol/ticker of the asset which the bar belongs to. From there it is easy to update the correct plot.

OnData()

As mentioned before the dataslice passed to the OnData()call contains a snapshot of data for all of our symbols at the same time. Therefore, just like during Initialize(), we can simply loop through our self.equtitieslist to asses the information we need, update the plots and make a decision whether we want to enter.

One additional thing worth mentioning is that we no longer use self.portfolio.invested to determine whether we are in a position. Instead, we switch to self.Securities[Symbol].Invested as we will allow more than one open position at a time. We just want to check that we are not placing a second order on the same stock.

Running The Code

After running the code you should see something that looks like this:

Chart Showing Multiple Instruments Example Code Results

As you can see we have a separate chart on the right-hand side for each of the RSI and Volume charts.  Overall we entered 5 trades over the test period. If you click on the “Trades” tab, you will see that we have placed trades across each of the assets in our list.

Image showing trades list