| 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]