| Overall Statistics |
|
Total Trades 1446 Average Win 0.27% Average Loss -0.33% Compounding Annual Return 33.673% Drawdown 28.300% Expectancy 0.584 Net Profit 446.534% Sharpe Ratio 1.342 Probabilistic Sharpe Ratio 67.560% Loss Rate 12% Win Rate 88% Profit-Loss Ratio 0.81 Alpha 0.318 Beta -0.136 Annual Standard Deviation 0.225 Annual Variance 0.051 Information Ratio 0.652 Tracking Error 0.294 Treynor Ratio -2.226 Total Fees $1486.64 |
#This is a Template of dynamic stock selection.
#You can try your own fundamental factor and ranking method by editing the CoarseSelectionFunction and FineSelectionFunction
import operator
from math import ceil,floor
from itertools import groupby
from datetime import datetime, timedelta
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 = 675
# Number of stocks to long/short
self.num_fine = 8
self.symbols = None
def Initialize(self):
self.SetCash(100000)
self.SetStartDate(2015,1,1)
# if not specified, the Backtesting EndDate would be today
self.SetEndDate(2020,11,5)
self.spy = self.AddEquity("SPY", Resolution.Daily).Symbol
self.SetBenchmark('SPY')
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction,self.FineSelectionFunction)
self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Cash)
# Schedule the rebalance function to execute at the begining of each month
self.Schedule.On(self.DateRules.WeekStart(self.spy),
self.TimeRules.AfterMarketOpen(self.spy,180), Action(self.rebalance))
def CoarseSelectionFunction(self, coarse):
# if the rebalance flag is not 1, return null list to save time.
if self.reb != 1:
return self.long
# make universe selection once a month
# drop stocks which have no fundamental data or have too low prices
selected = [x for x in coarse if (x.HasFundamentalData)
and (float(x.Price) > 10)
#and (x.Market == "usa")
and (x.DollarVolume > 1e6)]
sortedByDollarVolume = sorted(selected, key=lambda x: x.DollarVolume, reverse=True)
top = sortedByDollarVolume[:self.num_coarse]
return [i.Symbol for i in top]
def FineSelectionFunction(self, fine):
# return null list if it's not time to rebalance
if self.reb != 1:
return self.long
self.reb = 0
# drop stocks which don't have the information we need.
# you can try replacing those factor with your own factors here
filtered_fine = [x for x in fine if x.OperationRatios.OperationMargin.ThreeMonths
#and x.OperationRatios.OperationMargin.OneYear
and x.OperationRatios.RevenueGrowth.OneYear
and x.OperationRatios.RevenueGrowth.ThreeMonths
and x.OperationRatios.RevenueGrowth.ThreeYears
and x.OperationRatios.AssetsTurnover.OneYear
and x.OperationRatios.AssetsTurnover.ThreeMonths
and x.OperationRatios.EBITDAMargin.ThreeMonths
and x.OperationRatios.EBITDAMargin.SixMonths
and x.OperationRatios.EBITDAMargin.OneYear
and x.CompanyReference.PrimaryExchangeID in ["NYS","NAS"]
#and (x.CompanyReference.CountryId == "USA")
#and x.FinancialStatements.BalanceSheet.TotalEquity.TwelveMonths
#and x.EarningRatios.FCFPerShareGrowth.OneYear
and x.OperationRatios.CFOGrowth.OneYear
and x.ValuationRatios.PBRatio
and x.ValuationRatios.PSRatio
#and x.ValuationRatios.PCFRatio
#and x.ValuationRatios.PERatio
and x.OperationRatios.ROE.ThreeMonths
and x.OperationRatios.ROIC.ThreeMonths
and x.OperationRatios.ROA.ThreeMonths
#and x.OperationRatios.FinancialLeverage.OneYear
#and x.FinancialStatements.IncomeStatement.ResearchAndDevelopment.ThreeMonths=> 0
#and x.ValuationRatios.TrailingDividendYield < 0.005
#and x.OperationRatios.NetIncomeGrowth.Value!=0
and x.OperationRatios.StockholdersEquityGrowth.OneYear
and x.SecurityReference.IsPrimaryShare > 0
#and x.MarketCap > 5e10
#and x.FinancialStatements.IncomeStatement.TotalRevenue.TwelveMonths
#and x.SecurityReference.SecurityType == "ST00000001"
and (x.AssetClassification.MorningstarSectorCode!= 309)
and (x.AssetClassification.MorningstarSectorCode!=206)
and (x.AssetClassification.MorningstarIndustryGroupCode!=31055)]
#for i in filtered_fine:
#i.rd_ratio = (i.FinancialStatements.IncomeStatement.ResearchAndDevelopment.ThreeMonths / i.FinancialStatements.IncomeStatement.NetIncome.ThreeMonths)
#i.MarketCapi = (i.EarningReports.BasicAverageShares.TwelveMonths *
# i.EarningReports.BasicEPS.TwelveMonths *
# i.ValuationRatios.PERatio)
#rd_ratio[i] = (i.FinancialStatements.IncomeStatement.ResearchAndDevelopment.ThreeMonths /
# i.FinancialStatements.IncomeStatement.NetIncome.OneYear)
#market_cap[i] = (i.EarningReports.BasicAverageShares.ThreeMonths *
# i.EarningReports.BasicEPS.TwelveMonths *
# i.ValuationRatios.PERatio)
self.Debug('remained to select %d'%(len(filtered_fine)))
# rank stocks by three factor.
sortedByfactor1 = sorted(filtered_fine, key=lambda x: x.OperationRatios.OperationMargin.ThreeMonths, reverse=False)
sortedByfactor2 = sorted(filtered_fine, key=lambda x: x.OperationRatios.RevenueGrowth.ThreeMonths, reverse=False)
sortedByfactor3 = sorted(filtered_fine, key=lambda x: x.OperationRatios.AssetsTurnover.OneYear, reverse=False)
sortedByfactor4 = sorted(filtered_fine, key=lambda x: x.OperationRatios.EBITDAMargin.SixMonths, reverse=False)
sortedByfactor6 = sorted(filtered_fine, key=lambda x: x.OperationRatios.RevenueGrowth.ThreeYears, reverse=False)
sortedByfactor7 = sorted(filtered_fine, key=lambda x: x.OperationRatios.StockholdersEquityGrowth.OneYear, reverse=False)
sortedByfactor8 = sorted(filtered_fine, key=lambda x: x.OperationRatios.CFOGrowth.OneYear, reverse=False)
sortedByfactor9 = sorted(filtered_fine, key=lambda x: x.OperationRatios.RevenueGrowth.OneYear, reverse=False)
sortedByfactor10 = sorted(filtered_fine, key=lambda x: x.ValuationRatios.PBRatio, reverse=False)
sortedByfactor11 = sorted(filtered_fine, key=lambda x: x.ValuationRatios.PSRatio, reverse=False)
sortedByfactor12 = sorted(filtered_fine, key=lambda x: x.ValuationRatios.PCFRatio, reverse=False)
#sortedByfactor13 = sorted(filtered_fine, key=lambda x: x.OperationRatios.EBITDAMargin.ThreeMonths, reverse=False)
sortedByfactor14 = sorted(filtered_fine, key=lambda x: x.ValuationRatios.PERatio, reverse=False)
sortedByfactor15 = sorted(filtered_fine, key=lambda x: x.OperationRatios.AssetsTurnover.ThreeMonths, reverse=False)
sortedByfactor16 = sorted(filtered_fine, key=lambda x: x.OperationRatios.ROE.ThreeMonths, reverse=False)
sortedByfactor17 = sorted(filtered_fine, key=lambda x: x.OperationRatios.ROIC.ThreeMonths, reverse=False)
sortedByfactor18 = sorted(filtered_fine, key=lambda x: x.OperationRatios.ROA.ThreeMonths, reverse=False)
sortedByfactor19 = sorted(filtered_fine, key=lambda x: x.OperationRatios.EBITDAMargin.OneYear, reverse=False)
sortedByfactor20 = sorted(filtered_fine, key=lambda x: x.OperationRatios.AssetsTurnover.ThreeMonths, reverse=False)
sortedByfactor21 = sorted(filtered_fine, key=lambda x: x.OperationRatios.GrossMargin.OneYear, reverse=False)
sortedByfactor22 = sorted(filtered_fine, key=lambda x: x.AssetClassification.SizeScore, reverse=False)
sortedByfactor23 = sorted(filtered_fine, key=lambda x: x.AssetClassification.StyleScore, reverse=False)
sortedByfactor24 = sorted(filtered_fine, key=lambda x: x.AssetClassification.GrowthScore, reverse=False)
#sortedByfactor22 = sorted(filtered_fine, key=lambda x: x.OperationRatios.CapExSalesRatio.OneYear, reverse=False)
#sortedByfactor21 = sorted(filtered_fine, key=lambda x: x.ValuationRatios.TrailingDividendYield, reverse=True)
#sortedByfactor15 = sorted(filtered_fine, key=lambda x: x.ValuationRatios.TrailingDividendYield, reverse=True)
#sortedByfactor10 = sorted(filtered_fine, key=lambda x: x.OperationRatios.OperationMargin.ThreeMonths, reverse=False)
sortedByMarketCapi = sorted(filtered_fine, key=lambda x: x.MarketCap)
#sortedByrd_ratio = sorted(filtered_fine, key=lambda x: x.rd_ratio)
stock_dict = {}
# assign a score to each stock, you can also change the rule of scoring here.
for i,ele in enumerate(sortedByfactor1):
margin = i
revenue_growth3m = sortedByfactor2.index(ele)
assets_turnover = sortedByfactor3.index(ele)
ebitda_margin6m = sortedByfactor4.index(ele)
revenue_growth3 = sortedByfactor6.index(ele)
equity_growth1 = sortedByfactor7.index(ele)
cfgrowth = sortedByfactor8.index(ele)
revenue_growth1 = sortedByfactor9.index(ele)
pb_ratio = sortedByfactor10.index(ele)
ps_ratio = sortedByfactor11.index(ele)
pcf_ratio = sortedByfactor12.index(ele)
#ebitda_margin3m = sortedByfactor13.index(ele)
ebitda_margin1 = sortedByfactor19.index(ele)
pe_ratio = sortedByfactor14.index(ele)
assets_turnover3m = sortedByfactor20.index(ele)
ROE = sortedByfactor16.index(ele)
ROIC = sortedByfactor17.index(ele)
ROA = sortedByfactor18.index(ele)
gross_margin = sortedByfactor21.index(ele)
size_score = sortedByfactor22.index(ele)
style_score = sortedByfactor23.index(ele)
growth_score = sortedByfactor24.index(ele)
#capex_turnover = sortedByfactor22.index(ele)
#div_yield = sortedByfactor21.index(ele)
#leverage = sortedByfactor16.index(ele)
#dividend_yield = sortedByfactor15.index(ele)
#rd_ratio = sortedByrd_ratio.index(ele)
size = sortedByMarketCapi.index(ele)
#equity_growth = sortedByfactor10.index(ele)
#margin1 = sortedByfactor10.index(ele)
score = [(gross_margin)*26,
(revenue_growth3m)*15,
(assets_turnover)*86,
(ebitda_margin1)*29,
(revenue_growth3)*97,
(equity_growth1)*9,
#(cfgrowth)*1,
(revenue_growth1)*65,
(pe_ratio)*40,
(pb_ratio)*20,
(ps_ratio)*35,
(pcf_ratio)*35,
(ebitda_margin6m)*1,
#(size)*90]
(ROE)*2,
(ROIC)*2,
(ROA)*2,
#(leverage)*1]
#(assets_turnover3m)*88,
(size_score)*98,
(style_score)*3,
(growth_score)*3]
#(capex_turnover)*1]
#(margin)*26]
#(div_yield)*300]
#(dividend_yield)*2]
#(margin1)*14]
#(equity_growth)*10]
score = sum(score)
stock_dict[ele] = score
# sort the stocks by their scores
self.sorted_stock = sorted(stock_dict.items(), key=lambda d:d[1],reverse=True)
sorted_symbol = [x[0] for x in self.sorted_stock]
# sort 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]]
return self.long
def OnData(self, data):
pass
def rebalance(self):
# if this month the stock are not going to be long/short, liquidate it.
long_list = self.long
for i in self.Portfolio.Values:
if (i.Invested) and (i.Symbol not in long_list):
self.Liquidate(i.Symbol) and self.RemoveSecurity(i.Symbol)
# Alternatively, you can liquidate all the stocks at the end of each month.
# Which method to choose depends on your investment philosiphy
# if you prefer to realized the gain/loss each month, you can choose this method.
#self.Liquidate()
# 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)
self.reb = 1
# for i in self.short:
# self.SetHoldings(i, -0.9/self.num_fine)
#for kvp in self.Portfolio:
# symbol = kvp.Key
# holding = kvp.Value
# #quantity = self.Portfolio.HoldingsCost
# self.Debug(str(self.Time) + str(holding.Symbol))
#self.Debug(str(self.Time) + str(holding.Symbol))
# self.Debug(len(str(holding.Symbol)))
#for kvp in self.Securities:
# symbol = kvp.Key
# security = kvp.Value
# quantity = self.Portfolio.Count
# #self.Debug(str(security.Symbol))
# self.Debug(str(self.Time) + str(security.Symbol) + str(quantity))
# #self.Debug(str(kvp.Key))
#for kvp in self.Portfolio:
# security_holding = kvp.Value
# symbol = security_holding.Symbol.Value
# Quantity of the security held
# quantity = security_holding.Quantity
# Average price of the security holdings
# price = security_holding.AveragePrice
# self.Debug(str(self.Time) + str(symbol) + " " + str(quantity))
invested = [x.Key for x in self.Portfolio if x.Value.Invested]
for symbol in invested:
security_holding = self.Portfolio[symbol]
#count = symbol.Value.Count
quantity = security_holding.Quantity
price = security_holding.AveragePrice
self.Debug(str(symbol) + " " + str(quantity))