Applying Research
Uncorrelated Assets
Create Hypothesis
According to Modern Portfolio Thoery, asset combinations with negative or very low correlation could have lower total portfolio variance given the same level of return. Thus, uncorrelated assets allows you to find a portfolio that will, theoretically, be more diversified and resilient to extreme market events. We're testing this statement in real life scenario, while hypothesizing a portfolio with uncorrelated assets could be a consistent portfolio. In this example, we'll compare the performance of 5-least-correlated-asset portfolio (proposed) and 5-most-correlated-asset portfolio (benchmark), both equal weighting.
Get Historical Data
To begin, we retrieve historical data for researching.
- Instantiate a
QuantBook
. - Select the desired tickers for research.
- Call the
add_equity
method with the tickers, and their corresponding resolution. - Call the
history
method withqb.securities.keys
for all tickers, time argument(s), and resolution to request historical data for the symbol.
qb = QuantBook()
assets = ["SHY", "TLT", "SHV", "TLH", "EDV", "BIL", "SPTL", "TBT", "TMF", "TMV", "TBF", "VGSH", "VGIT", "VGLT", "SCHO", "SCHR", "SPTS", "GOVT"]
for i in range(len(assets)): qb.add_equity(assets[i],Resolution.MINUTE)
If you do not pass a resolution argument, Resolution.MINUTE
is used by default.
history = qb.history(qb.securities.keys(), datetime(2021, 1, 1), datetime(2021, 12, 31), Resolution.DAILY)

Prepare Data
We'll have to process our data to get their correlation and select the least and most related ones.
- Select the close column and then call the
unstack
method, then callpct_change
to compute the daily return. - Write a function to obtain the least and most correlated 5 assets.
returns = history['close'].unstack(level=0).pct_change().iloc[1:]
def get_uncorrelated_assets(returns, num_assets): # Get correlation correlation = returns.corr() # Find assets with lowest and highest absolute sum correlation selected = [] for index, row in correlation.iteritems(): corr_rank = row.abs().sum() selected.append((index, corr_rank)) # Sort and take the top num_assets sort_ = sorted(selected, key = lambda x: x[1]) uncorrelated = sort_[:num_assets] correlated = sort_[-num_assets:] return uncorrelated, correlated selected, benchmark = get_uncorrelated_assets(returns, 5)

Test Hypothesis
To test the hypothesis: Our desired outcome would be a consistent and low fluctuation equity curve should be seen, as compared with benchmark.
- Construct a equal weighting portfolio for the 5-uncorrelated-asset-portfolio and the 5-correlated-asset-portfolio (benchmark).
- Call
cumprod
to get the cumulative return. - Plot the result.
port_ret = returns[[x[0] for x in selected]] / 5 bench_ret = returns[[x[0] for x in benchmark]] / 5
total_ret = (np.sum(port_ret, axis=1) + 1).cumprod() total_ret_bench = (np.sum(bench_ret, axis=1) + 1).cumprod()
plt.figure(figsize=(15, 10)) total_ret.plot(label='Proposed') total_ret_bench.plot(label='Benchmark') plt.title('Equity Curve') plt.legend() plt.show()

We can clearly see from the results, the proposed uncorrelated-asset-portfolio has a lower variance/fluctuation, thus more consistent than the benchmark. This proven our hypothesis.
Set Up Algorithm
Once we are confident in our hypothesis, we can export this code into backtesting. One way to accomodate this model into research is to create a scheduled event which uses our model to pick stocks and goes long.
def initialize(self) -> None: #1. Required: Five years of backtest history self.set_start_date(2014, 1, 1) #2. Required: Alpha Streams Models: self.set_brokerage_model(BrokerageName.ALPHA_STREAMS) #3. Required: Significant AUM Capacity self.set_cash(1000000) #4. Required: Benchmark to SPY self.set_benchmark("SPY") self.set_portfolio_construction(EqualWeightingPortfolioConstructionModel()) self.set_execution(ImmediateExecutionModel()) self.assets = ["SHY", "TLT", "IEI", "SHV", "TLH", "EDV", "BIL", "SPTL", "TBT", "TMF", "TMV", "TBF", "VGSH", "VGIT", "VGLT", "SCHO", "SCHR", "SPTS", "GOVT"] # Add Equity ------------------------------------------------ for i in range(len(self.assets)): self.add_equity(self.assets[i], Resolution.MINUTE) # Set Scheduled Event Method For Our Model. In this example, we'll rebalance every month. self.schedule.on(self.date_rules.month_start(), self.time_rules.before_market_close("SHY", 5), self.every_day_before_market_close)
Now we export our model into the scheduled event method. We will switch qb
with self
and replace methods with their QCAlgorithm
counterparts as needed. In this example, this is not an issue because all the methods we used in research also exist in QCAlgorithm
.
def every_day_before_market_close(self) -> None: qb = self # Fetch history on our universe history = qb.history(qb.securities.keys(), 252*2, Resolution.DAILY) if history.empty: return # Select the close column and then call the unstack method, then call pct_change to compute the daily return. returns = history['close'].unstack(level=0).pct_change().iloc[1:] # Get correlation correlation = returns.corr() # Find 5 assets with lowest absolute sum correlation selected = [] for index, row in correlation.iteritems(): corr_rank = row.abs().sum() selected.append((index, corr_rank)) sort_ = sorted(selected, key = lambda x: x[1]) selected = [x[0] for x in sort_[:5]] # ============================== insights = [] for symbol in selected: insights.append( Insight.price(symbol, Expiry.END_OF_MONTH, InsightDirection.UP) ) self.emit_insights(insights)
Examples
The below code snippets concludes the above jupyter research notebook content.
# Instantiate a QuantBook. qb = QuantBook() # Select the desired tickers for research. assets = ["SHY", "TLT", "SHV", "TLH", "EDV", "BIL", "SPTL", "TBT", "TMF", "TMV", "TBF", "VGSH", "VGIT", "VGLT", "SCHO", "SCHR", "SPTS", "GOVT"] # Call the add_equity method with the tickers, and its corresponding resolution. Then store their Symbols. Resolution.MINUTE is used by default. for i in range(len(assets)): qb.add_equity(assets[i],Resolution.MINUTE) # Call the history method with qb.securities.keys for all tickers, time argument(s), and resolution to request historical data for the symbol. history = qb.history(qb.securities.keys(), datetime(2021, 1, 1), datetime(2021, 12, 31), Resolution.DAILY) # Select the close column and then call the unstack method, then call pct_change to compute the daily return. returns = history['close'].unstack(level=0).pct_change().iloc[1:] # Write a function to obtain the least and most correlated 5 assets. def get_uncorrelated_assets(returns, num_assets): # Get correlation correlation = returns.corr() # Find assets with lowest and highest absolute sum correlation selected = [] for index, row in correlation.iteritems(): corr_rank = row.abs().sum() selected.append((index, corr_rank)) # Sort and take the top num_assets sort_ = sorted(selected, key = lambda x: x[1]) uncorrelated = sort_[:num_assets] correlated = sort_[-num_assets:] return uncorrelated, correlated selected, benchmark = get_uncorrelated_assets(returns, 5) # Construct a equal weighting portfolio for the 5-uncorrelated-asset-portfolio and the 5-correlated-asset-portfolio (benchmark). port_ret = returns[[x[0] for x in selected]] / 5 bench_ret = returns[[x[0] for x in benchmark]] / 5 # Call cumprod to get the cumulative return. total_ret = (np.sum(port_ret, axis=1) + 1).cumprod() total_ret_bench = (np.sum(bench_ret, axis=1) + 1).cumprod() # Plot the result. plt.figure(figsize=(15, 10)) total_ret.plot(label='Proposed') total_ret_bench.plot(label='Benchmark') plt.title('Equity Curve') plt.legend() plt.show()
The below code snippets concludes the algorithm set up.
class UncorrelatedAssetsDemo(QCAlgorithm): def initialize(self) -> None: #1. Required: Five years of backtest history self.set_start_date(2014, 1, 1) self.set_end_date(2019, 1, 1) #2. Required: Alpha Streams Models: self.set_brokerage_model(BrokerageName.ALPHA_STREAMS) #3. Required: Significant AUM Capacity self.set_cash(1000000) #4. Required: Benchmark to SPY self.set_benchmark("SPY") self.set_portfolio_construction(EqualWeightingPortfolioConstructionModel()) self.set_execution(ImmediateExecutionModel()) self.assets = ["SHY", "TLT", "IEI", "SHV", "TLH", "EDV", "BIL", "SPTL", "TBT", "TMF", "TMV", "TBF", "VGSH", "VGIT", "VGLT", "SCHO", "SCHR", "SPTS", "GOVT"] # Add Equity ------------------------------------------------ for i in range(len(self.assets)): self.add_equity(self.assets[i], Resolution.MINUTE).symbol # Set Scheduled Event Method For Our Model. In this example, we'll rebalance every month. self.schedule.on(self.date_rules.month_start(), self.time_rules.before_market_close("SHY", 5), self.every_day_before_market_close) def every_day_before_market_close(self) -> None: qb = self # Fetch history on our universe history = qb.history(qb.securities.Keys, 252*2, Resolution.DAILY) if history.empty: return # Select the close column and then call the unstack method, then call pct_change to compute the daily return. returns = history['close'].unstack(level=0).pct_change().iloc[1:] # Get correlation correlation = returns.corr() # Find 5 assets with lowest absolute sum correlation selected = [] for index, row in correlation.iterrows(): corr_rank = row.abs().sum() selected.append((index, corr_rank)) sort_ = sorted(selected, key = lambda x: x[1]) selected = [x[0] for x in sort_[:5]] # ============================== insights = [] for symbol in selected: insights.append( Insight.price(symbol, Expiry.END_OF_MONTH, InsightDirection.UP) ) self.emit_insights(insights)