| Overall Statistics |
|
Total Trades 62 Average Win 5.36% Average Loss -2.60% Compounding Annual Return 16.806% Drawdown 13.300% Expectancy 1.295 Net Profit 140.199% Sharpe Ratio 1.218 Probabilistic Sharpe Ratio 64.027% Loss Rate 25% Win Rate 75% Profit-Loss Ratio 2.06 Alpha 0.141 Beta 0.03 Annual Standard Deviation 0.118 Annual Variance 0.014 Information Ratio 0.179 Tracking Error 0.202 Treynor Ratio 4.745 Total Fees $364.92 |
import pandas as pd
class ETFSharpeRotationAlgorithm(QCAlgorithm):
# This strategy was sourced from https://www.rotationinvest.com/blog/etf-rotation-strategy-using-sharpe-ratio
#
# Rotating Funds: SPY, EFA, GLD
# Cash Filter: TLT
# Strategy Rules: Invest in the top 1 fund based on the trailing 3-month Sharpe Ratio
# If the top fund is below it's 150-day moving average then invest in the bond ETF (TLT)
def Initialize(self):
self.SetStartDate(2015, 1, 1) # Set Start Date
self.SetCash(100000) # Set Strategy Cash
trading_days_per_month = 21
self.sharpe_lookback = 3 * trading_days_per_month
sma_lookback = 150
lookback = max(self.sharpe_lookback, sma_lookback)
rotation_tickers = ['SPY', 'EFA', 'GLD']
self.sma_by_rotation_symbol = {}
self.history_by_symbol = {}
for ticker in rotation_tickers:
symbol = self.AddEquity(ticker, Resolution.Daily).Symbol
self.sma_by_rotation_symbol[symbol] = self.SMA(symbol, sma_lookback, Resolution.Daily)
history = self.History(symbol, lookback, Resolution.Daily).loc[symbol].close
for time, close in history[-lookback:].iteritems():
self.sma_by_rotation_symbol[symbol].Update(time, close)
self.history_by_symbol[symbol] = history[-self.sharpe_lookback:]
# Setup a consolidator for sharpe calculation
consolidator = TradeBarConsolidator(timedelta(1))
consolidator.DataConsolidated += self.CustomDailyHandler
self.SubscriptionManager.AddConsolidator(symbol, consolidator)
self.cash_filter_symbol = self.AddEquity("TLT", Resolution.Daily).Symbol
self.month = -1
def CustomDailyHandler(self, sender, consolidated):
row = pd.Series([consolidated.Close], index=[consolidated.Time])
appended = self.history_by_symbol[consolidated.Symbol].append(row)
self.history_by_symbol[consolidated.Symbol] = appended.iloc[-self.sharpe_lookback:]
def OnData(self, data):
# Ensure we have data
for symbol, _ in self.sma_by_rotation_symbol.items():
if not data.ContainsKey(symbol) or data[symbol] is None:
return
if not data.ContainsKey(self.cash_filter_symbol) or data[self.cash_filter_symbol] is None:
return
# Rebalance monthly
if data.Time.month == self.month:
return
self.month = data.Time.month
# Select the symbol with the highest trailing sharpe ratio
sharpe_by_symbol = {}
for symbol, history in self.history_by_symbol.items():
sharpe = history.pct_change().mean() / history.pct_change().std()
sharpe_by_symbol[symbol] = sharpe
top_symbol = sorted(sharpe_by_symbol.items(), key=lambda item: item[1], reverse=True)[0][0]
# If the top_symbol is above it's SMA, invest it in. Otherwise, invest in the cash filter (TLT)
sma = self.sma_by_rotation_symbol[top_symbol].Current.Value
if data[symbol].Price >= sma:
for symbol, _ in self.sma_by_rotation_symbol.items():
if self.Securities[symbol].Invested and symbol != top_symbol:
self.Liquidate(symbol)
if self.Securities[self.cash_filter_symbol].Invested:
self.Liquidate(self.cash_filter_symbol)
self.SetHoldings(top_symbol, 1)
else:
for symbol, _ in self.sma_by_rotation_symbol.items():
self.Liquidate(symbol)
self.SetHoldings(self.cash_filter_symbol, 1)