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
Derek Melchin
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
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.
S O'Keeffe
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
Derek Melchin
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
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.
S O'Keeffe
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
Derek Melchin
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
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.
S O'Keeffe
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
S O'Keeffe
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!