Overall Statistics |
Total Trades 39 Average Win 14.49% Average Loss -1.17% Compounding Annual Return 23.894% Drawdown 18.000% Expectancy 10.248 Net Profit 744.521% Sharpe Ratio 1.302 Probabilistic Sharpe Ratio 77.679% Loss Rate 16% Win Rate 84% Profit-Loss Ratio 12.36 Alpha 0.148 Beta 0.18 Annual Standard Deviation 0.13 Annual Variance 0.017 Information Ratio 0.297 Tracking Error 0.169 Treynor Ratio 0.938 Total Fees $599.80 Estimated Strategy Capacity $260000000.00 Lowest Capacity Asset QQQ RIWIV7K5Z9LX |
#https://lean-api-docs.netlify.app/index.html #https://www.quantconnect.com/forum/discussion/11566/kei-based-strategy/p1 #https://seekingalpha.com/article/4434713-sector-rotation-strategy-using-the-high-yield-spread #https://seekingalpha.com/article/4394646-this-sector-rotation-strategy-made-17-percent-year-since-1991 #self.AddData(Fred, Fred.ICEBofAML.USHighYieldMasterIIOptionAdjustedSpread) ## SIMON LesFlex June 2021 ## ## Modified by Vladimir from QuantConnect.Python import PythonQuandl ### Simon LesFlex June 2021 ### ### Key Short—Term Economic Indicators. The Key Economic Indicators (KEI) database contains monthly and quarterly statistics ### (and associated statistical methodological information) for the 33 OECD member and for a selection of non—member countries ### on a wide variety of economic indicators, namely: quarterly national accounts, industrial production, composite leading indicators, ### business tendency and consumer opinion surveys, retail trade, consumer and producer prices, hourly earnings, employment/unemployment, ### interest rates, monetary aggregates, exchange rates, international trade and balance of payments. Indicators have been prepared by national statistical ### agencies primarily to meet the requirements of users within their own country. In most instances, the indicators are compiled in accordance with ### international statistical guidelines and recommendations. However, national practices may depart from these guidelines, and these departures may ### impact on comparability between countries. There is an on—going process of review and revision of the contents of the database in order to maximise ### the relevance of the database for short—term economic analysis. ### For more information see: http://stats.oecd.org/OECDStat_Metadata/ShowMetadata.ashx?Dataset=KEI&Lang=en ### Reference Data Set: https://www.quandl.com/data/OECD/KEI_LOLITOAA_OECDE_ST_M-Leading-indicator-amplitude-adjusted-OECD-Europe-Level-ratio-or-index-Monthly import numpy as np import datetime as dt class QuandlImporterAlgorithm(QCAlgorithm): def Initialize(self): #self.quandlCode = "OECD/KEI_LOLITOAA_OECDE_ST_M" #Euro self.quandlCode = "OECD/KEI_LOLITOAA_OECD_ST_M" #Total ## Optional argument - personal token necessary for restricted dataset #Quandl.SetAuthCode("MLNarxdsMU92vk-ZJDvg") Quandl.SetAuthCode("RXk7Mxue6oH1TM-U8b7c") self.SetStartDate(2012,1,1) #Set Start Date self.SetEndDate(datetime.today() - timedelta(1)) #Set End Date self.SetCash(100000) #Set Strategy Cash self.SetWarmup(100) self.SetBenchmark("SPY") self.init = True self.stage = 0 self.kei = self.AddData(QuandlCustomColumns, self.quandlCode, Resolution.Daily, TimeZones.NewYork).Symbol self.sma = self.SMA(self.kei, 1) self.mom = self.MOMP(self.kei, 2) self.MKT = self.AddEquity('SPY', Resolution.Hour).Symbol self.bond = self.AddEquity('TLT', Resolution.Hour).Symbol self.previous_SPY = 0 #This dictionary with hold the symbols and the prices if needed self.symbols_dict = dict([ ('SPY',['SPY',0]),('XMMO',['XMMO',0]),('MTUM',['MTUM',0]),('FVAL',['FVAL',0]), ('DXD',['DXD',0]),('GLD',['GLD',0]),('DIA',['DIA',0]),('TLT',['TLT',0]),('QQQ',['QQQ',0]) ]) #use this dataframe to hold the stats about the stage self.columns = ['stage_date', 'stage_num','stage','stage_days','symbol','price'] self.stage_log_df = pd.DataFrame(columns=self.columns) for key in self.symbols_dict: self.AddEquity(self.symbols_dict[key][0], Resolution.Hour).Symbol self.Schedule.On(self.DateRules.WeekStart(self.MKT), self.TimeRules.AfterMarketOpen(self.MKT, 31), self.Rebalance) def Rebalance(self): if self.IsWarmingUp or not self.mom.IsReady or not self.sma.IsReady: return initial_asset = self.symbols_dict['QQQ'][0] if self.mom.Current.Value > 0 else self.symbols_dict['TLT'][0] if self.init: self.SetHoldings(initial_asset, 1) self.init = False keihist = self.History([self.kei], 1400) #returns the historical data for custom 90 day period. #keihist = self.History([self.kei],self.StartDate-timedelta(100),self.StartDate-timedelta(10)) #keihist = keihist['Value'].unstack(level=0).dropna() keihistlowt = np.nanpercentile(keihist, 15) keihistmidt = np.nanpercentile(keihist, 50) keihisthight = np.nanpercentile(keihist, 90) kei = self.sma.Current.Value keimom = self.mom.Current.Value #if (keimom < 0 and kei < keihistmidt and kei > keihistlowt) and not (self.Securities[self.bond].Invested): if (keimom < 0 and kei < keihistmidt and kei > keihistlowt) and not (self.stage == 5): # DECLINE self.Liquidate() self.stage = 5 self.SetHoldings(self.symbols_dict['GLD'][0], 1) self.add_stage_log("DECLINE", self.stage) elif (keimom > 0 and kei < keihistlowt) and not (self.stage == 1): # RECOVERY self.Liquidate() self.stage = 1 self.SetHoldings(self.symbols_dict['MTUM'][0], 1) self.add_stage_log("RECOVERY", self.stage) elif (keimom > 0 and kei > keihistlowt and kei < keihistmidt) and not (self.stage == 2): # EARLY self.Liquidate() self.stage = 2 self.SetHoldings(self.symbols_dict['XMMO'][0], 1) self.add_stage_log("EARLY", self.stage) elif (keimom > 0 and kei > keihistmidt and kei < keihisthight) and not (self.stage == 3): # REBOUND self.Liquidate() self.stage = 3 self.SetHoldings(self.symbols_dict['MTUM'][0], 1) self.add_stage_log("REBOUND", self.stage) elif (keimom < 0 and kei < keihisthight and kei > keihistmidt) and not (self.stage == 4): # LATE self.Liquidate() self.stage = 4 self.SetHoldings(self.symbols_dict['QQQ'][0], 1) self.add_stage_log("LATE", self.stage) elif (keimom < 0 and kei < 100 and not self.Securities[self.bond].Invested): self.Liquidate() self.stage = 5 self.SetHoldings(self.symbols_dict['TLT'][0], 1) self.add_stage_log("BONDS", self.stage) self.Plot("LeadInd", "SMA(LeadInd)", self.sma.Current.Value) self.Plot("LeadInd", "THRESHOLD", 100) self.Plot("MOMP", "MOMP(LeadInd)", self.mom.Current.Value) self.Plot("MOMP", "THRESHOLD", 0) self.Plot("Stage","recovery = 1 early = 2 rebound = 3 late = 4 decline = 5",self.stage) def OnEndOfAlgorithm(self): self.portfolio_holdings() self.write_stage_log() def portfolio_holdings(self): self.Log("-- Portfolio --") for kvp in self.Portfolio: if kvp.Value.Invested: symbol = kvp.Key #full security key holding = kvp.Value ticker = holding.Symbol.Value quantity = holding.Quantity avgprice = holding.AveragePrice cost = quantity * avgprice price =self.Portfolio[symbol].Price unrealized_profit = self.Portfolio[symbol].UnrealizedProfit net = (unrealized_profit / cost) * 100 s1 = '{:3.2f}'.format(avgprice) + ',' + '{:3.2f}'.format(price) + ',' + '{:3.2f}'.format(net) + ',' + '{:3.2f}'.format(unrealized_profit) self.Log(ticker + "," + str(quantity) + "," + s1) return() def add_stage_log(self, stage, stage_num): d1 = str(self.Time) entry_date = d1[0:10] for key in self.symbols_dict: self.stage_log_df = self.stage_log_df.append({ 'stage_date': entry_date, 'stage_num': stage_num, 'stage': stage, 'stage_days': 0, 'symbol': self.symbols_dict[key][0], 'price' : self.Securities[self.symbols_dict[key][0]].Price }, ignore_index = True) self.Log(d1 + "," + stage + "," + str(stage_num) ) def write_stage_log(self): #Convert each stage_date and stage_num group to a single row df_out = self.stage_log_df.set_index(['stage_date','stage_num','stage',self.stage_log_df.groupby(['stage_date','stage_num','stage']).cumcount()+1]).unstack().sort_index(level=1, axis=1) df_out.columns = df_out.columns.map('{0[0]}_{0[1]}'.format) df_out.reset_index() #calculate the gain and net change for each symbol in the row for x in range(1, (len(self.symbols_dict)+1)): gain_col = 'gain_' + str(x) price_col = 'price_' + str(x) net_col = "net_" + str(x) df_out[gain_col] = (df_out[price_col].shift(-1)- df_out[price_col]) #calculate the gain during the stage df_out[net_col] = (df_out[gain_col] / df_out[price_col]) * 100 #calculate the net change during the stage df_out.fillna(0,inplace=True) """ #self.stage_log_df['start_date'] = pd.to_datetime(self.stage_log_df['entry_date'], format="%Y-%m-%d") """ #for each row build output log string print_header = True for index, row in df_out.iterrows(): s1 = str(index[0]) + "," + str(index[1]) + "," + str(index[2]) + "," x1 = str(index[0]) + "," + str(index[1]) + "," + str(index[2]) + "," for x in range(1, (len(self.symbols_dict)+1)): symbol_col = 'symbol_' + str(x) gain_col = 'gain_' + str(x) price_col = 'price_' + str(x) net_col = "net_" + str(x) s2 = symbol_col + "," + price_col + "," + gain_col + "," + net_col + "," s1 = s1 + s2 symbol_val = row[symbol_col] gain_val = '{:3.2f}'.format(row[gain_col]) price_val = '{:3.2f}'.format(row[price_col]) net_val = '{:3.2f}'.format(row[net_col]) x2 = symbol_val + "," + price_val + "," + gain_val + "," + net_val + "," x1 = x1 + x2 if print_header : self.Log (s1) print_header = False self.Log (x1) # Quandl often doesn't use close columns so need to tell LEAN which is the "value" column. class QuandlCustomColumns(PythonQuandl): def __init__(self): # Define ValueColumnName: cannot be None, Empty or non-existant column name self.ValueColumnName = "Value" """ kei mo < 0 kei percentile kei mo > 0 High | Late (tech,hc, ind | Rebound (tech,utilities) | Medium | Decline (bonds) | Early (energy,financials,industrials) Quad1 | Low | Recovery (bonds) 0 recovery = 1 early = 2 rebound = 3 late = 4 decline = 5 """