Back

'insight_seconds' essentially unlimited, but algorithm still selling

Hello,

I thought that making the 'insight_seconds' essentially unlimited in this algorithm would prevent it from selling.  Shouldn't 99999999999 seconds need to elapse after purchase before the algorithm would sell?  But I'm still seeing a lot of selling at seemingly random times.  Why hasn't making "insight_seconds = 99999999999" stopped all selling?

Thank you!
Sean

 

from Execution.ImmediateExecutionModel import ImmediateExecutionModel
from Portfolio.EqualWeightingPortfolioConstructionModel import EqualWeightingPortfolioConstructionModel

class QuantumHorizontalRegulators(QCAlgorithm):

def Initialize(self):
self.SetStartDate(2020, 5, 18) # Set Start Date
self.SetEndDate(2020, 5, 19)
self.SetCash(100000) # Set Strategy Cash
self.AddEquity("W5000", Resolution.Second)
self.scaning = False
self.lastToggle = None

self.__numberOfSymbols =100
self.SetUniverseSelection(FineFundamentalUniverseSelectionModel(self.CoarseSelectionFunction, self.FineSelectionFunction, None, None))
self.UniverseSettings.Resolution = Resolution.Second

self.AddAlpha(FadeTheGapModel(self))

self.SetExecution(ImmediateExecutionModel())

self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel())

self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.AfterMarketOpen("W5000", 0), self.toggleScan)
self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.AfterMarketOpen("W5000", 45), self.toggleScan)

def toggleScan(self):
self.scaning = not self.scaning
self.lastToggle = self.Time

if not self.scaning:
self.needs_reset = True


def CoarseSelectionFunction(self, coarse):
# Stocks with the most dollar volume traded yesterday
sortedByDollarVolume = sorted(coarse, key=lambda x: x.DollarVolume, reverse=True)
return [ x.Symbol for x in sortedByDollarVolume[:self.__numberOfSymbols] ]

def FineSelectionFunction(self, fine):
return [ x.Symbol for x in fine ]


class FadeTheGapModel(AlphaModel):
symbolData = {}

def __init__(self, algo):
self.algo = algo

def Update(self, algorithm, slice):
if algorithm.IsWarmingUp:
return []

# If it's the end of the day, update the yesterday close of each indicator
if not algorithm.Securities['W5000'].Exchange.ExchangeOpen:
for symbol in self.symbolData:
if symbol in slice.Bars:
self.symbolData[symbol].yest_close = slice.Bars[symbol].Close

if not self.algo.scaning:
# Reset max indicator
if self.algo.needs_reset:
for symbol in self.symbolData:
self.symbolData[symbol].max.Reset()
self.algo.needs_reset = False
return []

insights = []

insight_seconds = 99999999999

# Create insights for symbols up at least 10% on the day
for symbol in self.symbolData:
# If already invested, continue to next symbol
if algorithm.Securities[symbol].Invested or symbol not in slice.Bars or self.symbolData[symbol].max.Samples == 0:
continue

# Calculate return sign yesterday's close
yest_close = self.symbolData[symbol].yest_close
close = slice[symbol].Close
ret = (close - yest_close) / yest_close
high_of_day_break = close > self.symbolData[symbol].max.Current.Value
if ret >= 0.1 and high_of_day_break: # Up 10% on the day & breaks high of day
insights.append(Insight(symbol, timedelta(seconds=insight_seconds), InsightType.Price, InsightDirection.Up))

# Update max indicator for all symbols
for symbol in self.symbolData:
if symbol in slice.Bars:
self.symbolData[symbol].max.Update(slice.Time, slice.Bars[symbol].High)

return Insight.Group(insights)

def OnSecuritiesChanged(self, algorithm, changes):
if len(changes.AddedSecurities) > 0:
# Get history of symbols over lookback window
added_symbols = [x.Symbol for x in changes.AddedSecurities]
history = algorithm.History(added_symbols, 1, Resolution.Daily)['close']

for added in changes.AddedSecurities:
# Save yesterday's close
closes = history.loc[[str(added.Symbol.ID)]].values
if len(closes) < 1:
continue
self.symbolData[added.Symbol] = SymbolData(closes[0])

for removed in changes.RemovedSecurities:
# Delete yesterday's close tracker
self.symbolData.pop(removed.Symbol, None)

class SymbolData:
def __init__(self, yest_close):
self.yest_close = yest_close
self.max = Maximum(45*60) # 45 minutes

 

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.


Hi Sean,

The EqualWeightingPortfolioConstructionModel is responsible for the selling. By definition, it aims to balance the portfolio weight evenly among all the securities it's invested in. This means selling and buying as the prices of the securities converge/diverge. We can stop this behavior by providing the constructor a rebalance argument.

self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel(lambda time: None))

See the attached algorithm for a demonstration of this.

Best,
Derek Melchin

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.


Hi Derek,

Thank you you're amazing!  I wasn't thinking of the portfolio construction when looking at the selling.  I added the "(lambda time: None))" piece like in your demostration algorithm, but I'm still seeing the exact same results unfortunately.  I read all of the documentation about Portfolio Construction, but I don't think the other models (Null, Mean Variance, or Black Litterman) make sense for my algorithm.  I also looked at the Python implementation of Equal Weighted in GitHub, but I wasn't able to get any insights there either

Do you know why I'm still seeing the same selling even after putting the "(lambda time: None))" piece in?

Thanks!
Sean

from Execution.ImmediateExecutionModel import ImmediateExecutionModel
from Portfolio.EqualWeightingPortfolioConstructionModel import EqualWeightingPortfolioConstructionModel

class QuantumHorizontalRegulators(QCAlgorithm):

def Initialize(self):
self.SetStartDate(2020, 5, 18) # Set Start Date
self.SetEndDate(2020, 5, 19)
self.SetCash(100000) # Set Strategy Cash
self.AddEquity("W5000", Resolution.Second)
self.scaning = False
self.lastToggle = None

self.__numberOfSymbols =100
self.SetUniverseSelection(FineFundamentalUniverseSelectionModel(self.CoarseSelectionFunction, self.FineSelectionFunction, None, None))
self.UniverseSettings.Resolution = Resolution.Second

self.AddAlpha(FadeTheGapModel(self))

self.SetExecution(ImmediateExecutionModel())

self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel(lambda time: None))

self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.AfterMarketOpen("W5000", 0), self.toggleScan)
self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.AfterMarketOpen("W5000", 45), self.toggleScan)

def toggleScan(self):
self.scaning = not self.scaning
self.lastToggle = self.Time

if not self.scaning:
self.needs_reset = True


def CoarseSelectionFunction(self, coarse):
# Stocks with the most dollar volume traded yesterday
sortedByDollarVolume = sorted(coarse, key=lambda x: x.DollarVolume, reverse=True)
return [ x.Symbol for x in sortedByDollarVolume[:self.__numberOfSymbols] ]

def FineSelectionFunction(self, fine):
return [ x.Symbol for x in fine ]


class FadeTheGapModel(AlphaModel):
symbolData = {}

def __init__(self, algo):
self.algo = algo

def Update(self, algorithm, slice):
if algorithm.IsWarmingUp:
return []

# If it's the end of the day, update the yesterday close of each indicator
if not algorithm.Securities['W5000'].Exchange.ExchangeOpen:
for symbol in self.symbolData:
if symbol in slice.Bars:
self.symbolData[symbol].yest_close = slice.Bars[symbol].Close

if not self.algo.scaning:
# Reset max indicator
if self.algo.needs_reset:
for symbol in self.symbolData:
self.symbolData[symbol].max.Reset()
self.algo.needs_reset = False
return []

insights = []

insight_seconds = 99999999999

# Create insights for symbols up at least 10% on the day
for symbol in self.symbolData:
# If already invested, continue to next symbol
if algorithm.Securities[symbol].Invested or symbol not in slice.Bars or self.symbolData[symbol].max.Samples == 0:
continue

# Calculate return sign yesterday's close
yest_close = self.symbolData[symbol].yest_close
close = slice[symbol].Close
ret = (close - yest_close) / yest_close
high_of_day_break = close > self.symbolData[symbol].max.Current.Value
if ret >= 0.1 and high_of_day_break: # Up 10% on the day & breaks high of day
insights.append(Insight(symbol, timedelta(seconds=insight_seconds), InsightType.Price, InsightDirection.Up))

# Update max indicator for all symbols
for symbol in self.symbolData:
if symbol in slice.Bars:
self.symbolData[symbol].max.Update(slice.Time, slice.Bars[symbol].High)

return Insight.Group(insights)

def OnSecuritiesChanged(self, algorithm, changes):
if len(changes.AddedSecurities) > 0:
# Get history of symbols over lookback window
added_symbols = [x.Symbol for x in changes.AddedSecurities]
history = algorithm.History(added_symbols, 1, Resolution.Daily)['close']

for added in changes.AddedSecurities:
# Save yesterday's close
closes = history.loc[[str(added.Symbol.ID)]].values
if len(closes) < 1:
continue
self.symbolData[added.Symbol] = SymbolData(closes[0])

for removed in changes.RemovedSecurities:
# Delete yesterday's close tracker
self.symbolData.pop(removed.Symbol, None)

class SymbolData:
def __init__(self, yest_close):
self.yest_close = yest_close
self.max = Maximum(45*60) # 45 minutes

 

0

Hi Sean,

My apologies that the previous algorithm did not completely solve the issue. To stop the selling, we also need to add

self.Settings.RebalancePortfolioOnSecurityChanges = False

in the algorithm constructor.

See the attached backtest for reference. Note that the data resolution and universe size has been adjusted to streamline the debugging process.

Best,
Derek Melchin

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.


Hi Derek,

No need to apologize!  You are always a tremendous help.  It looks like this didn't do the trick either.  I'm getting extremely similar backtest results when adding that 

self.Settings.RebalancePortfolioOnSecurityChanges = False

line.  It looks like the first 3 buy orders spend all of the capital within 2 seconds of market open, and then those  3 positions start selling 12 seconds later so that the algorithm can submit more buy orders.  

Is it possible to put in something like 

if self.Portfolio.Invested
return

to stop the algorithm if the whole Portfolio is invested?  Or does that not work with these Portfolio Construction Models?  

Thanks!
Sean

 

0


Hi Sean,

This is the action we expect the portfolio to take. At 2020-05-18 09:30:02, the alpha model emits long insights for CCL, MRNA, and NVAX. The portfolio construction model (PCM) responds by allocating 1/3 of the portfolio to each of the securities. 12 seconds later (2020-05-18 09:30:14), the alpha model emits a long insight for UAL. Since there are now 4 securities we want to be long, the PCM releases orders to reduce the allocation in the previous 3 securities to 1/4 of the portfolio value in order to be long UAL with 1/4 of the portfolio.

View the Orders and Insights tabs on the backtest view page for reference.

If the goal is to evenly allocate the portfolio over all the securities that satisfy the entry condition without scaling in/out, we would need to know how many securities satisfy the condition during the first 45 minutes by the time the first insight is emitted. An alternative is to limit the number of held securities to n each trading day and allocate 1/n of the portfolio as the alpha model emits the first n insights of the day.

By adding

if self.Portfolio.Invested:
return

to the alpha model, we are breaking the separation of concerns principle. The portfolio state is not a concern of the alpha model. With the alternative allocation process discussed above, this snippet is not necessary.

Best,
Derek Melchin

2

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.


Hi Derek,

OK thank you so much for that explanation!  That makes sense that the model is really just sending insights and making sure that each insight is equal weighted in real time as it buys.  It has no command to limit the number of insights, positions, or spend.  It looks like the Accumulative Insight Portfolio Construction Model would be better because each buy order will be an equal amount and the rebalancing issue that is burning cash with so many extra sell orders will stop.  I owe you big time when I finally finish this!

Thanks,
Sean

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