The good folks over at QuantStart wrote an excellent article detailing a strategy that periodically rebalances a portfolio according to some weighting scheme that is specified a priori. In this article, I want to demonstrate how a similar strategy can be implemented in Odin.
The basic idea behind periodic rebalancing is to ensure that one strategy or portfolio of equities does not inflate (or deflate) too high (low) in value compared to some threshold value. When periodically rebalancing portfolios in a dollar neutral fund, for instance, we can ensure that not too much equity is being spent on the long side when compared to the shorts. In the QuantStart article, the strategy sought to rebalance on a monthly basis holdings in the SPY (S&P 500) and AGG (U.S. bonds) ETFs. The idea here was that a combination of these two assets would experience smaller volatility in times of financial crisis due to bonds being fairly resistant to substantial downturns in the equities domain.
Defining the Strategies
In this article I will show how to implement this strategy for a portfolio consisting of sixty percent SPY and forty percent AGG. To begin, I will show the strategy class.
# strategy.py import pandas as pd from odin.strategy import AbstractStrategy from odin.strategy.templates import BuyAndHoldStrategy from odin.utilities.mixins.strategy_mixins import LongStrategyMixin class RebalanceETFStrategy(LongStrategyMixin): def compute_proportion(self, feats): """Implementation of abstract base class method.""" filled = self.portfolio.portfolio_handler.filled_positions if feats.name not in filled: # We do not own the asset. if feats.name == "SPY": return 0.6 elif feats.name == "AGG": return 0.4 else: # We do own the asset. return 1.0 def buy_indicator(self, feats): """Implementation of abstract base class method.""" return True def sell_indicator(self, feats): """Implementation of abstract base class method.""" return False def exit_indicator(self, feats): """Implementation of abstract base class method.""" s = feats.name pos = self.portfolio.portfolio_handler.filled_positions[s] date = self.portfolio.data_handler.current_date # Sell the asset on an (approximately) quarterly basis. return pos.compute_holding_period(date).days > 63 def generate_features(self): """Implementation of abstract base class method.""" symbols = self.portfolio.data_handler.bars.ix[ "adj_price_open", -1, : ].dropna().index return pd.DataFrame(index=symbols) def generate_priority(self, feats): """Implementation of abstract base class method.""" return self.portfolio.data_handler.bars.ix[ "adj_price_open", -1, : ].dropna().index
There are several important elements of this strategy that should be noted. First, this is a long-only strategy, which is determined by the
LongStrategyMixin inheritance structure. In the
compute_proportion function, we see that sixty percent of the equity is allocated to SPY whereas forty percent is allocated AGG; if we need to liquidate the position, then the entire position is sold.
exit_indicator function is also important. Unlike the QuantStart tutorial, we will perform quarterly, as opposed to monthly, rebalancing. This is achieved by acknowledging that there are 252 trading days in each year; by dividing this by four, we arrive a 63 trading days per quarter. As a result, after sixty-three days have elapsed, we sell our positions; on the following trading day, we will reinvest our equity appropriately using our predetermined allocations.
As a point of comparison, we also consider a strategy that simply buys and holds the SPY ETF. This serves to show illustrate the performance of the quarterly rebalancing SPY / AGG strategy compared to a simpler one. This benchmark is enacted through the following strategy:
# strategy.py class BuyAndHoldSpyderStrategy(BuyAndHoldStrategy): def buy_indicator(self, feats): # Only buy the SPY and ignore the AGG. return feats.name in ("SPY", )
The performance of the quarterly rebalanced strategy is essentially the same as the monthly rebalancing one detailed on QuantStart, which I think is a reasonable result of the analysis. Notice in the equity curve plotted below that that indeed compared to buying and holding the SPY the reweighting strategy does not yield as much return over the time period of evaluation (which is from January 1, 2006 through December 31, 2016). Note however that during the financial crisis of 2008 and early 2009, the bond component of the strategy does offset some of the major (fifty percent!) losses seen in the S&P 500.
These findings are further affirmed in some of the summary statistics of the backtest. These are shown in the following table:
|Max. Drawdown||Max. Drawdown Duration||Sharpe Ratio||Annualized Returns|
Notice that because the rebalanced portfolio decorrelates itself from the massive drawdowns suffered by the S&P 500 during 2008, it actually results in three desirable attributes.
- The maximum drawdown is reduced from 55% of the total equity of the portfolio to 34%. That is itself a 38% improvement over the buy and hold strategy.
- The maximum duration of the drawdown is also reduced from 1222 trading days (which is nearly five years!) to just under three years.
- The Sharpe ratio (which corresponds to the amount of risk for the level of reward) is improved from 0.47 to 0.52. Neither of these are particularly good, but it’s important to note.
Of course, if you were simply more interested in the growth of your money year-over-year, you would have done better holding SPY. This simpler strategy resulted in 7.6% annualized growth of the portfolio over the time period, whereas the combination of SPY and AGG only saw a yield of 5.4%.
In conclusion, this post has shown how reweighting the portfolio to ensure a suitable composition of assets to satisfy your risk tolerance can lead to improvements in several key metrics of portfolio management. In this case, we saw that holding bonds and large market capitalization stocks resulted in a portfolio with a higher Sharpe ratio and lower drawdowns.