import operator
from math import ceil,floor
class Stockpicking(QCAlgorithm):
def Initialize(self):
# training period
self.SetStartDate(2009,1,2) # Set Start Date
self.SetEndDate(2017,12,31) # Set End Date
# testing period
#self.SetStartDate(2018,1,1)
self.SetCash(50000) # Set Strategy Cash
#self.Settings.FreePortfolioValuePercentage=0.2 # reserved cash buffer
self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage) # for stock trading
###################
self.CoarseSize = 300 # number of stocks in selected coarse universe
self.FineSize = 10 # number of stocks in selected fine universe
self.num_portfolios = 6
benchmark="SPY"
#########################
# control flags
self.flags={ # use dictionary to only transfer reference and keep unique value
'rebalance': True, # monthly rebalance of coarse and fine selection function
'fineSelected': False, # monthly rebalance of OnData function
'num_rebalanced': 0 # record the number of rebalancing times
}
self.last_rebalance=self.Time
# auto initialization
self.AddEquity(benchmark) # schedule symbol
self.SetBenchmark(benchmark)
self.AddUniverse(self.SelectCoarse, self.SelectFine)
# universe selection happens only at midnight and cannot be changed
# the resolution that added assets use
self.UniverseSettings.Resolution = Resolution.Minute # there is no resolution above daily, use schedule instead
self.Schedule.On(self.DateRules.MonthStart(benchmark), self.TimeRules.AfterMarketOpen(benchmark), Action(self.Rebalancing))
self.AddAlpha(SpAlpha(self.flags,benchmark))
self.SetPortfolioConstruction(SpPortfolio())
self.SetExecution(SpExecution(self.flags))
def SelectCoarse(self, coarse):
if self.flags['rebalance']:
CoarseWithFundamental = [x for x in coarse if x.HasFundamentalData]
sortedByDollarVolume = sorted(CoarseWithFundamental, key=lambda x: x.DollarVolume, reverse=True)
top = sortedByDollarVolume[:self.CoarseSize]
return [c.Symbol for c in top]
else:
return []
def SelectFine(self, fine):
if self.flags['rebalance']:
self.flags['rebalance'] = False
self.flags['fineSelected'] = True
# filter the fine by deleting equities wit zero factor value
filtered = [x for x in fine if x.EarningReports.TotalDividendPerShare.ThreeMonths
and x.ValuationRatios.PriceChange1M
and x.ValuationRatios.BookValuePerShare
and x.ValuationRatios.FCFYield]
sortedByfactor1 = sorted(filtered, key=lambda x: x.EarningReports.TotalDividendPerShare.ThreeMonths, reverse=True) # descending
sortedByfactor2 = sorted(filtered, key=lambda x: x.ValuationRatios.PriceChange1M, reverse=False)
sortedByfactor3 = sorted(filtered, key=lambda x: x.ValuationRatios.BookValuePerShare, reverse=True)
sortedByfactor4 = sorted(filtered, key=lambda x: x.ValuationRatios.FCFYield, reverse=True)
num_stocks = floor(len(filtered)/self.num_portfolios)
stock_dict = {}
for i,ele in enumerate(sortedByfactor1):
rank1 = i
rank2 = sortedByfactor2.index(ele)
rank3 = sortedByfactor3.index(ele)
rank4 = sortedByfactor4.index(ele)
score = [ceil(rank1/num_stocks),
ceil(rank2/num_stocks),
ceil(rank3/num_stocks),
ceil(rank4/num_stocks)]
score = sum(score)
stock_dict[ele] = score
#self.Log("score" + str(score))
self.sorted_stock = sorted(stock_dict.items(), key=lambda d:d[1],reverse=True)
sorted_symbol = [self.sorted_stock[i][0] for i in range(len(self.sorted_stock))]
topFine = sorted_symbol[:self.FineSize]
self.flags['num_rebalanced'] += 1
selection=[i.Symbol for i in topFine]
tickers=[i.Value for i in selection]
return selection
else:
return []
def OnData(self, data):
pass
# this event fires whenever we have changes to our universe
def OnSecuritiesChanged(self, changes):
pass
def Rebalancing(self):
if self.Time-self.last_rebalance > timedelta(days=20):
self.last_rebalance=self.Time
self.flags['rebalance'] = True
####################################################
# Algorithm framework model that produces insights #
####################################################
class SpAlpha(AlphaModel):
def __init__(self,flags,benchmark):
self.Name='StockpickingAlpha'
self.flags=flags # global control flags input as reference
self.changes = None # securities changes
self.benchmark=benchmark
def Update(self, algorithm, slice):
# Updates this alpha model with the latest data from the algorithm.
# This is called each time the algorithm receives data for subscribed securities
# Generate insights on the securities in the universe.
insights = []
if self.changes != None and self.flags['fineSelected'] and self.flags['num_rebalanced'] > 0 :
self.flags['fineSelected'] = False
# purchase added securities
for security in self.changes.AddedSecurities:
if security.Symbol.Value!=self.benchmark:
insights.append(
Insight.Price(security.Symbol,
timedelta(days = 30),
InsightDirection.Up,
None,
None,
self.Name ) )
self.changes = None
return insights
def OnSecuritiesChanged(self, algorithm, changes):
# Handle security changes in from your universe model.
self.changes=changes
################################
# Portfolio construction model #
################################
class SpPortfolio(PortfolioConstructionModel):
def __init__(self):
self.changes = None # securities changes
# Create list of PortfolioTarget objects from Insights
def CreateTargets(self, algorithm, insights):
targets=[]
if self.changes != None:
for Insight in insights:
if Insight.Direction == InsightDirection.Up:
targets.append(PortfolioTarget.Percent(algorithm, Insight.Symbol,
0.8/float(len(self.changes.AddedSecurities))))
self.changes = None
return [t for t in targets if t is not None]
# OPTIONAL: Security change details
def OnSecuritiesChanged(self, algorithm, changes):
# Security additions and removals are pushed here.
# This can be used for setting up algorithm state.
# changes.AddedSecurities:
# changes.RemovedSecurities:
self.changes=changes
###################
# Execution Model #
###################
class SpExecution:
def __init__(self,flags):
self.flags=flags # global control flags input as reference
# Fill the supplied portfolio targets efficiently
def Execute(self, algorithm, targets):
for Target in targets:
###############
# open position
algorithm.MarketOrder(Target.Symbol, Target.Quantity, True) # False: wait till filled
# Optional: Securities changes event for handling new securities.
def OnSecuritiesChanged(self, algorithm, changes):
if self.flags['fineSelected']:
# liquidate removed securities
for security in changes.RemovedSecurities:
if security.Invested:
algorithm.Liquidate(security.Symbol)