Hey Everyone!

Next up in our series is an algorithm that shows how to find uncorrelated assets. This allows you to find a portfolio that will, theoretically, be more diversified and resilient to extreme market events. When combined with other indicators and data sources, this can be an important component in building an algorithm that limits drawdown and remains profitable in choppy markets.

The first step is to experiment with the code we'll need in the research notebook. We don't need to pick any specific tickers here, just enough to make sure our code works and that everything will transfer seamlessly when dropped into an algorithm. In the notebook, we grab the historical data for the securities and use this to fetch returns data. Then, we calculate the correlation of the returns, which gives us a correlation matrix. The last step is to figure out which symbols have the lowest overall correlation with the rest of the symbols as a whole -- we want to find the five assets with the lowest average absolute correlation so that we can trade them without fearing that any pair are too highly correlated.

# Write our custom function
def GetUncorrelatedAssets(returns, num_assets):
# Get correlation
correlation = returns.corr()

# Find assets with lowest mean correlation, scaled by STD
selected = []
for index, row in correlation.iteritems():
corr_rank = row.abs().mean()/row.abs().std()
selected.append((index, corr_rank))

# Sort and take the top num_assets
selected = sorted(selected, key = lambda x: x[1])[:num_assets]

return selected

## Perform operations
qb = QuantBook()

tickers = ["SQQQ", "TQQQ", "TVIX", "VIXY", "SPLV",
symbols = [qb.AddEquity(x, Resolution.Minute) for x in tickers]

# Fetch history
history = qb.History(qb.Securities.Keys, 150, Resolution.Hour)

# Get hourly returns
returns = history.unstack(level = 1).close.transpose().pct_change().dropna()

# Get 5 assets with least overall correlation
selected = GetUncorrelatedAssets(returns, 5)

With this code, we can perform the same operation in an algorithm. To demonstrate this, we've built an algorithm that uses basic Universe Selection and Scheduled Events. The algorithm is initialized using familiar models -- Equal Weighting Portfolio Construction, Immediate Execution, and a Scheduled Event set to rebalance our holdings every day 5-minutes after market open.

def Initialize(self):
self.SetStartDate(2019, 1, 1) # Set Start Date

self.SetCash(1000000) # Set Strategy Cash

self.UniverseSettings.Resolution = Resolution.Minute





self.Schedule.On(self.DateRules.EveryDay('SPY'), self.TimeRules.AfterMarketOpen("SPY", 5), self.Recalibrate)

self.symbols = []

The universe is selected using the top 100 symbols by traded dollar volume.

def CoarseSelectionFunction(self, coarse):
sortedByDollarVolume = sorted(coarse, key=lambda x: x.DollarVolume, reverse=True)
filtered = [ x.Symbol for x in sortedByDollarVolume ][:100]

return filtered

Then we copy and paste our research notebook code into the OnSecuritiesChanged method, which allows us to perform our filtering on new symbols in the universe (technically we haven't done an exact copy-and-paste this time as we had to change the symbol argument being passed to qb.History). Additionally, we want to emit Flat Insights for any symbols that have been removed from the Universe so that we ensure our positions are closed.

def OnSecuritiesChanged(self, changes):
symbols = [x.Symbol for x in changes.AddedSecurities]

qb = self

# Copied from research notebook
# Fetch history
history = qb.History(symbols, 150, Resolution.Hour)

# Get hourly returns
returns = history.unstack(level = 1).close.transpose().pct_change().dropna()

# Get 5 assets with least overall correlation
selected = GetUncorrelatedAssets(returns, 5)

# Add to symbol dictionary for use in Recalibrate
self.symbols = [symbol for symbol, corr_rank in selected]

# Emit Flat Insights for removed securities
symbols = [x.Symbol for x in changes.RemovedSecurities]
insights = [Insight.Price(symbol, timedelta(minutes = 1), InsightDirection.Flat) for symbol in symbols if self.Portfolio[symbol].Invested]

Finally, we use the Scheduled Event to generate our Insights!

def Recalibrate(self):
insights = []

insights = [Insight.Price(symbol, timedelta(5), InsightDirection.Up, 0.03) for symbol in self.symbols]


That's it! The main thing to take away from this is how you can quickly move between the research environment and real algorithms even when we had to change a few things between the two in this example. This provides you with a lot of power in being able to apply research practices easily in algorithms, which hopefully will help you find new sources of alpha!

(Please note: this universe is not valid for the competition. Using coarse selection was done just for the purpose of demonstration)