Overall Statistics |
Total Trades 174 Average Win 6.34% Average Loss -2.71% Compounding Annual Return 16.842% Drawdown 25.700% Expectancy 1.215 Net Profit 1236.121% Sharpe Ratio 1.043 Probabilistic Sharpe Ratio 45.131% Loss Rate 34% Win Rate 66% Profit-Loss Ratio 2.34 Alpha 0.148 Beta -0.001 Annual Standard Deviation 0.142 Annual Variance 0.02 Information Ratio 0.214 Tracking Error 0.224 Treynor Ratio -117.187 Total Fees $3794.70 Estimated Strategy Capacity $85000000.00 Lowest Capacity Asset TLT SGNKIKYGE9NP |
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(2005, 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)