Back

First time using framework. Feedback on code and usage?

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)

 

Update Backtest







0

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.


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)

 

0

Hi Kenneth,

This is a great simple strategy! I edited your algorithm a bit.

A few things:

  1. When the insight duration is in days, it is interpreted as the number of trading days the insight will be active. The average number of trading days per month is about 21, so 30 would overshoot our monthly rebalancing.
  2. If we set our insight durations for a month, we don't need to emit flat insights to exit our positons. We can simply let our portfolio construction model handle unloading positions for expired insights.
  3. In this case, we can use the helper method Insight.Price(symbol, duration, direction) to emit insights.
  4. We can handle duplicates in our symbols list by first checking whether that element already exists before we append it.
  5. When creating insights, we should loop over our list of symbols rather than the portfolio. This ensures that we are only considering symbols currently in our universe.

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

1

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.


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. 

0

Update Backtest





0

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.


Loading...

This discussion is closed