Overall Statistics |
Total Trades 2407 Average Win 1.06% Average Loss -0.94% Compounding Annual Return 11.496% Drawdown 43.500% Expectancy 0.111 Net Profit 206.051% Sharpe Ratio 0.533 Probabilistic Sharpe Ratio 3.859% Loss Rate 48% Win Rate 52% Profit-Loss Ratio 1.14 Alpha 0.13 Beta -0.062 Annual Standard Deviation 0.231 Annual Variance 0.054 Information Ratio 0.056 Tracking Error 0.284 Treynor Ratio -1.988 Total Fees $153399.65 |
# https://quantpedia.com/strategies/catching-falling-knife-stocks/ # # The investment universe consists of all US based stocks after excluding the least liquid stocks (priced under $0.5) # and closed-end funds. Each month, the investor selects stocks that have suffered losses of 50 percent or more in the # last 500 trading days in relation to the benchmark, which is the S&P 500 index. He/she then classifies the stocks from # this sub-universe according to their position within the industry with regards to the Debt/Equity ratio. Stocks that have # a Debt/Equity ratio within at least 10% of the lowest in the industry are selected for the investment portfolio. # The portfolio is rebalanced monthly and stocks are weighted equally. from collections import deque import numpy as np class Catching_Falling_Knife_Stocks_TVIX(QCAlgorithm): def Initialize(self): self.SetStartDate(2010, 1, 1) self.SetEndDate(datetime.now()) self.SetCash(1000000) # Adjust the cash buffer from the default 2.5% to 10% # self.Settings.FreePortfolioValuePercentage = 0.1 self.last_course_month = -1 self.traded_this_month = False self.course_count = 1000 self.period = 500 self.SetWarmUp(self.period) self.long = [] self.spy = self.AddEquity('SPY', Resolution.Daily).Symbol self.data = deque(maxlen=self.period) # SPY price self.UniverseSettings.Resolution = Resolution.Daily self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction) self.AddEquity("TVIX", Resolution.Daily) # Weight of portfolio without TVIX self.weightStocks = 0.95 def CoarseSelectionFunction(self, coarse): if self.IsWarmingUp: return if self.last_course_month == self.Time.month: return Universe.Unchanged self.last_course_month = self.Time.month selected = sorted([x for x in coarse if x.HasFundamentalData and x.Price > 5], key=lambda x: x.DollarVolume, reverse=True) return [x.Symbol for x in selected[:self.course_count]] def FineSelectionFunction(self, fine): selected = [x for x in fine if x.OperationRatios.TotalDebtEquityRatio.ThreeMonths > 0] if len(selected) == 0: return group = {} returns = {} debt_to_equity = {} for stock in selected: symbol = stock.Symbol industry_group_code = stock.AssetClassification.MorningstarIndustryGroupCode if industry_group_code == 0: continue # Adding stocks in groups if not industry_group_code in group: group[industry_group_code] = [] group[industry_group_code].append(symbol) # Debt to equity ratio debt_to_equity[symbol] = stock.OperationRatios.TotalDebtEquityRatio.ThreeMonths # Return calc hist = self.History([symbol], self.period, Resolution.Daily) if 'close' in hist.columns: closes = hist['close'] if len(closes) == self.period: returns[symbol] = self.Return(closes) #else: return if len(group) == 0: return if len(returns) == 0: return if len(debt_to_equity) == 0: return if len(self.data) == self.data.maxlen: spy_prices = [x for x in self.data] spy_ret = self.Return(spy_prices) for industry_code in group: industry_debt_to_equity_10th_percentile = np.percentile([debt_to_equity[sym] for sym in group[industry_code]], 10) # Stocks that suffered losses of 50 percent or more than s&p # and # stocks that have a Debt/Equity ratio within at least 10% of the lowest in the industry long = [sym for sym in group[industry_code] if sym in returns and returns[sym] <= (spy_ret - 0.5) and sym in debt_to_equity and debt_to_equity[sym] <= industry_debt_to_equity_10th_percentile] debts = [debt_to_equity[x] for x in long] if len(debts) != 0: foo = 4 for symbol in long: self.long.append(symbol) else: return self.traded_this_month = False return self.long def OnData(self, data): if self.Securities.ContainsKey(self.spy): price = self.Securities[self.spy].Price if price != 0: self.data.append(price) if self.traded_this_month == True: return count = len(self.long) if count == 0: return self.Liquidate() for symbol in self.long: self.SetHoldings(symbol, self.weightStocks * 1 / count) if 1 - self.weightStocks > 0: self.SetHoldings("TVIX", 1 - self.weightStocks) self.long.clear() self.traded_this_month = True def Return(self, history): return (history[-1] - history[0]) / history[0]