Hello,
Just getting started with QC so I tried something simple after going through boot camp. This is a very simple strategy that just picks top shareholder yield companies, holds more a month and then rebalances. Clearly this is not a great strategy but I wanted something simple for learning the algorithm framework.
Can folks more familiar for the Algo framework comment on my usage? Is there anything obvious I could be doing better to exploit the framework? Any obvious errors? Code below. My main concern is making sure I am using the framework apropriately and taking full advantage of it. Thanks!
""" Shareholder Yield Algo.
This is for learning the QuantConnect Algo Framework. I don't expect it to be useful outside of that.
The goal of this algo is:
- create a universe of liquid stocks with the best share holder yield (dividends + buybacks) relative to the market.
- buy an equal weighted portfolio of these stocks.
- rebalance monthly based on share holder yield.
Classes:
- ShareHolderYieldAlgo() is the main class that configures and instantiates the QuantConnect algo framework.
- LiquidTotalYieldUniverseSelectionModel() is a class which creates a liquid universe of stocks ranking by
dollar volume, then filters the list down for the top X yielders based on share holder yield.
- LongShareHolderYieldAlphaModel() is an alpha model class which emits buy and sell signals based on changes
to the universe selection.
"""
from Selection.FundamentalUniverseSelectionModel import FundamentalUniverseSelectionModel
class ShareHolderYieldAlgo(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2005, 1, 1) # Set Start Date
self.SetCash(100000) # Set Strategy Cash
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverseSelection(LiquidTotalYieldUniverseSelectionModel())
self.AddAlpha(LongShareHolderYieldAlphaModel())
self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel())
self.SetExecution(ImmediateExecutionModel())
class LiquidTotalYieldUniverseSelectionModel(FundamentalUniverseSelectionModel):
def __init__(self):
super().__init__(True, None, None)
self.lastMonth = -1
# Use this to determined how many stocks we take off the dollar volume sorted list.
self.dollarVolumeListSize = 200
# Number of securities to select and return from universe.
self.selectionSize = 10
def SelectCoarse(self, algorithm, coarse):
if self.lastMonth == algorithm.Time.month:
return Universe.Unchanged
self.lastMonth = algorithm.Time.month
sortedByDollarVolume = sorted([x for x in coarse if x.HasFundamentalData],
key=lambda x: x.DollarVolume, reverse=True)
return [x.Symbol for x in sortedByDollarVolume[:self.dollarVolumeListSize]]
def SelectFine(self, algorithm, fine):
sortedByYields = sorted(fine, key=lambda f: f.ValuationRatios.TotalYield, reverse=True)
universe = sortedByYields[:self.selectionSize]
return [f.Symbol for f in universe]
class LongShareHolderYieldAlphaModel(AlphaModel):
def __init__(self):
self.lastMonth = -1
self.signalTimeDelta = 30
self.symbols = []
def _processChanges(self, changes):
# safety check, make sure list elements are unique.
symbols = list(set(self.symbols))
# I feel like this update is problematic and could lead to bugs.
# Need to figure out a better way to just swap the list out whole
# sale.
for security in changes.RemovedSecurities:
symbols.remove(security.Symbol)
for security in changes.AddedSecurities:
symbols.append(security.Symbol)
self.symbols = symbols
def Update(self, algorithm, data):
# Skip if we have already run this month.
if self.lastMonth == algorithm.Time.month:
return []
self.lastMonth = algorithm.Time.month
insights = []
# Create liquidate (flat) signals for stocks in our portfolio that are no longer in our strategy as of this
# update.
for kv in algorithm.Portfolio:
holding = kv.Value
symbol = holding.Symbol
if holding.Invested and symbol not in self.symbols:
insights.append(Insight(symbol, timedelta(self.signalTimeDelta), InsightType.Price, InsightDirection.Flat, None, None))
# Create buy signals for stocks selected in our strategy.
for symbol in self.symbols:
insights.append(Insight(symbol, timedelta(self.signalTimeDelta), InsightType.Price, InsightDirection.Up, None, None))
return insights
def OnSecuritiesChanged(self, algorithm, changes):
self._processChanges(changes)
Kenneth Caruso
Hmm indenting did not persist, let me try again.
""" Shareholder Yield Algo. This is for learning the QuantConnect Algo Framework. I don't expect it to be useful outside of that. The goal of this algo is: - create a universe of liquid stocks with the best share holder yield (dividends + buybacks) relative to the market. - buy an equal weighted portfolio of these stocks. - rebalance monthly based on share holder yield. Classes: - ShareHolderYieldAlgo() is the main class that configures and instantiates the QuantConnect algo framework. - LiquidTotalYieldUniverseSelectionModel() is a class which creates a liquid universe of stocks ranking by dollar volume, then filters the list down for the top X yielders based on share holder yield. - LongShareHolderYieldAlphaModel() is an alpha model class which emits buy and sell signals based on changes to the universe selection. """ from Selection.FundamentalUniverseSelectionModel import FundamentalUniverseSelectionModel class ShareHolderYieldAlgo(QCAlgorithm): def Initialize(self): self.SetStartDate(2005, 1, 1) # Set Start Date self.SetCash(100000) # Set Strategy Cash self.UniverseSettings.Resolution = Resolution.Daily self.AddUniverseSelection(LiquidTotalYieldUniverseSelectionModel()) self.AddAlpha(LongShareHolderYieldAlphaModel()) self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel()) self.SetExecution(ImmediateExecutionModel()) class LiquidTotalYieldUniverseSelectionModel(FundamentalUniverseSelectionModel): def __init__(self): super().__init__(True, None, None) self.lastMonth = -1 # Use this to determined how many stocks we take off the dollar volume sorted list. self.dollarVolumeListSize = 200 # Number of securities to select and return from universe. self.selectionSize = 10 def SelectCoarse(self, algorithm, coarse): if self.lastMonth == algorithm.Time.month: return Universe.Unchanged self.lastMonth = algorithm.Time.month sortedByDollarVolume = sorted([x for x in coarse if x.HasFundamentalData], key=lambda x: x.DollarVolume, reverse=True) return [x.Symbol for x in sortedByDollarVolume[:self.dollarVolumeListSize]] def SelectFine(self, algorithm, fine): sortedByYields = sorted(fine, key=lambda f: f.ValuationRatios.TotalYield, reverse=True) universe = sortedByYields[:self.selectionSize] return [f.Symbol for f in universe] class LongShareHolderYieldAlphaModel(AlphaModel): def __init__(self): self.lastMonth = -1 self.signalTimeDelta = 30 self.symbols = [] def _processChanges(self, changes): # safety check, make sure list elements are unique. symbols = list(set(self.symbols)) # I feel like this update is problematic and could lead to bugs. # Need to figure out a better way to just swap the list out whole # sale. for security in changes.RemovedSecurities: symbols.remove(security.Symbol) for security in changes.AddedSecurities: symbols.append(security.Symbol) self.symbols = symbols def Update(self, algorithm, data): # Skip if we have already run this month. if self.lastMonth == algorithm.Time.month: return [] self.lastMonth = algorithm.Time.month insights = [] # Create liquidate (flat) signals for stocks in our portfolio that are no longer in our strategy as of this # update. for kv in algorithm.Portfolio: holding = kv.Value symbol = holding.Symbol if holding.Invested and symbol not in self.symbols: insights.append(Insight(symbol, timedelta(self.signalTimeDelta), InsightType.Price, InsightDirection.Flat, None, None)) # Create buy signals for stocks selected in our strategy. for symbol in self.symbols: insights.append(Insight(symbol, timedelta(self.signalTimeDelta), InsightType.Price, InsightDirection.Up, None, None)) return insights def OnSecuritiesChanged(self, algorithm, changes): self._processChanges(changes)
Rahul Chowdhury
Hi Kenneth,
This is a great simple strategy! I edited your algorithm a bit.
A few things:
Keep in mind, it's possible to attach back tests to comments and posts on the forums. Attaching back tests makes cloning and debugging algorithms much more convenient.
Check out the back test below to see the changes I made
Kenneth Caruso
Thanks for the feedback! Your suggestions greatly simplifiy the code. One question though about setting the TTL on the insights. By setting it at the average (21 days) won't we liquidate the portfolio temporarily on months which have 22 days? This seems non-optimal for frictional costs and taxes.
Kenneth Caruso
The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.
To unlock posting to the community forums please complete at least 30% of Boot Camp.
You can continue your Boot Camp training progress from the terminal. We hope to see you in the community soon!