| Overall Statistics |
|
Total Trades 590 Average Win 1.43% Average Loss -1.12% Compounding Annual Return 15.261% Drawdown 29.800% Expectancy 0.438 Net Profit 314.318% Sharpe Ratio 0.69 Probabilistic Sharpe Ratio 9.328% Loss Rate 37% Win Rate 63% Profit-Loss Ratio 1.28 Alpha -0.006 Beta 1.172 Annual Standard Deviation 0.174 Annual Variance 0.03 Information Ratio 0.129 Tracking Error 0.097 Treynor Ratio 0.102 Total Fees $791.31 Estimated Strategy Capacity $5000000.00 Lowest Capacity Asset ILMN RWQR2INKP0TH |
#region imports
from AlgorithmImports import *
#endregion
#This is a Template of dynamic stock selection.
#You can try your own fundamental factor and ranking method by editing the CoarseSelectionFunction and FineSelectionFunction
from QuantConnect.Data.UniverseSelection import *
class BasicTemplateAlgorithm(QCAlgorithm):
def __init__(self):
# set the flag for rebalance
self.reb = 1
# Number of stocks to pass CoarseSelection process
self.num_coarse = 100
# Number of stocks to long/short
self.num_fine = 10
self.symbols = None
self.mom = {} # Dict of Momentum indicator keyed by Symbol
self.lookback = 252 # Momentum indicator lookback period
def Initialize(self):
self.SetCash(100000)
self.SetStartDate(2009, 7, 1) # Set Start Date
self.SetEndDate(2019, 7, 1) # Set Start Date
# self.SetStartDate(2000,1,1)
# if not specified, the Backtesting EndDate would be today
# self.SetEndDate(2017,1,1)
self.spy = self.AddEquity("SPY", Resolution.Daily).Symbol
# self.sma = self.SMA("SPY", 5, Resolution.Daily)
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction,self.FineSelectionFunction)
# Schedule the rebalance function to execute at the begining of each month
self.Schedule.On(self.DateRules.MonthStart(self.spy),
self.TimeRules.AfterMarketOpen(self.spy,5), Action(self.rebalance))
def CoarseSelectionFunction(self, coarse):
# if the rebalance flag is not 1, return null list to save time.
if self.reb == 0:
return Universe.Unchanged
# return self.long + self.short
selected = [x for x in coarse if (x.HasFundamentalData)
and (float(x.Price) > 1)]
sortedByDollarVolume = sorted(selected, key=lambda x: x.DollarVolume, reverse=True)
top = sortedByDollarVolume[:self.num_coarse]
topSymbols = [i.Symbol for i in top]
return topSymbols
def FineSelectionFunction(self, fine):
# return null list if it's not time to rebalance
if self.reb == 0:
return Universe.Unchanged
self.reb = 0
toPop = [security for security in self.mom if security not in [i.Symbol for i in fine]]
for security in toPop:
self.mom.pop(security)
for security in fine:
if security.Symbol not in self.Securities:
self.AddEquity(security.Symbol, Resolution.Daily)
self.mom[security.Symbol] = Momentum(self.lookback)
addedSymbols = [k for k,v in self.mom.items() if not v.IsReady]
history = self.History(addedSymbols, 1 + self.lookback, Resolution.Daily)
history = history.close.unstack(level=0)
for symbol in addedSymbols:
ticker = str(symbol)
if ticker in history:
for time, value in history[ticker].items():
try:
item = IndicatorDataPoint(symbol, time, value)
self.mom[symbol].Update(item)
except:
self.Debug(value)
filtered_fine = [x for x in fine if
x.Symbol in self.mom and self.mom[x.Symbol].IsReady
and x.OperationRatios.OperationMargin.ThreeMonths
# # and x.ValuationRatios.EVToForwardEBIT
# # and x.FinancialStatements.IncomeStatement.GrossProfit.ThreeMonths
# and x.FinancialStatements.IncomeStatement.TotalRevenue.ThreeMonths
# and x.FinancialStatements.IncomeStatement.SellingGeneralAndAdministration.ThreeMonths
# and x.OperationRatios.RevenueGrowth.ThreeMonths
# and x.ValuationRatios.CashReturn
# and x.OperationRatios.OperationIncomeGrowth.ThreeMonths
# and x.OperationRatios.NetMargin.ThreeMonths
# and x.OperationRatios.ROIC.ThreeMonths
]
self.Debug('remained to select %d'%(len(filtered_fine)))
operationMargin = sorted(filtered_fine, key=lambda x: x.OperationRatios.OperationMargin.Value, reverse=True)
# EVToForwardEBIT = sorted(filtered_fine, key=lambda x: x.ValuationRatios.EVToForwardEBIT, reverse=True)
# grossMargin = sorted(filtered_fine, key=lambda x: x.FinancialStatements.IncomeStatement.GrossProfit.ThreeMonths/max(x.FinancialStatements.IncomeStatement.TotalRevenue.ThreeMonths,1), reverse=True)
# sgaMargin = sorted(filtered_fine, key=lambda x: x.FinancialStatements.IncomeStatement.SellingGeneralAndAdministration.ThreeMonths/max(x.FinancialStatements.IncomeStatement.TotalRevenue.ThreeMonths,1), reverse=True)
# revenueGrowth = sorted(filtered_fine, key=lambda x: x.OperationRatios.RevenueGrowth.ThreeMonths, reverse=True)
# operationIncomeGrowth = sorted(filtered_fine, key=lambda x: x.OperationRatios.OperationIncomeGrowth.ThreeMonths, reverse=True)
# netMargin = sorted(filtered_fine, key=lambda x: x.OperationRatios.NetMargin.ThreeMonths, reverse=True)
# roic = sorted(filtered_fine, key=lambda x: x.OperationRatios.ROIC.ThreeMonths, reverse=True)
stock_dict = {}
mom = sorted(filtered_fine, key=lambda x: self.mom[x.Symbol].Current.Value, reverse=True)
# mom = sorted([v for k,v in self.mom.items() if v.IsReady], key=lambda x: x.Current.Value, reverse=True)
for i,ele in enumerate(operationMargin):
# operationMargin_R = operationMargin.index(ele)
# EVToForwardEBIT_R = EVToForwardEBIT.index(ele)
# grossMargin_R = grossMargin.index(ele)
# sgaMargin_R = sgaMargin.index(ele)
# roic_R = roic.index(ele)
# operationIncomeGrowth_R = operationIncomeGrowth.index(ele)
# revenueGrowth_R = revenueGrowth.index(ele)
#score = sum([i, operationMargin_R,roic_R])
mom_r = mom.index(ele)
score = sum([mom_r])
stock_dict[ele] = score
# sort the stocks by their scores
self.sorted_stock = sorted(stock_dict.items(), key=lambda d:d[1],reverse=False)
sorted_symbol = [x[0] for x in self.sorted_stock]
# sotre the top stocks into the long_list and the bottom ones into the short_list
self.long = [x.Symbol for x in sorted_symbol[:self.num_fine]]
self.short = [x.Symbol for x in sorted_symbol[-self.num_fine:]]
return self.long + self.short
def OnData(self, data):
# pass
for symbol, mom in self.mom.items():
mom.Update(self.Time, self.Securities[symbol].Close)
def rebalance(self):
self.Debug("rebalance")
if self.Time.month % 4 > 0:
return
# if this month the stock are not going to be long/short, liquidate it.
long_short_list = self.long + self.short
for i in self.Portfolio.Values:
if (i.Invested) and (i not in long_short_list):
self.Liquidate(i.Symbol)
# Assign each stock equally. Alternatively you can design your own portfolio construction method
for i in self.long:
self.SetHoldings(i, 1/self.num_fine)
# always short
# for i in self.short:
# self.SetHoldings(i, -1/self.num_fine)
# tactical short
# if self.Securities["SPY"].Price < self.sma.Current.Value:
# for i in self.short:
# self.SetHoldings(i, -1/self.num_fine)
# else:
# for i in self.Portfolio.Values:
# if (i.Invested) and (i.IsShort):
# self.Liquidate(i.Symbol)
self.reb = 1