Intro
Superior algo returns can be thought of as being the result of two components: a great strategy regarding ‘what stocks to buy’ (the stock selection component, SEL) and a ‘clever timing’ (the in & out component, I/O) regarding when we are ‘in’ the market and hold the stocks versus when we are ‘out’ of the market and hold alternative assets such as bonds. We often focus on optimizing SEL and tend to neglect I/O; thus, for an important discussion of recent I/O tactics, see here.
Focus of this thread: Optimal SEL + I/O combinations
It is worthwhile to separately optimize SEL and I/O. However, the ultimate total return will also be determined by a certain synergy or dissonance between the two components. So, it seems that we won’t get around the arduous task of individually testing (all possible) combinations to identify optimal SEL + I/O pairs, which is the eventual focus of this thread. I reckon a preparatory step can be to dig up all the hidden SEL and I/O treasures from this forum and beyond to see what inputs are available for the combinations.
Ultimate objective
Let's get rich together, why not?
Damiano Bolzoni
Leandro Maia I ran some backtests with your version and it buys different stocks than the version you "fixed". It will go in and out on the same dates, so you would expect the same stocks are being bought (I disabled the in market rebelancing for your version to run those tests).
I didn't notice any difference in the way you select the universe, but you have this lines of code:
if self.month == self.Time.month:
return Universe.Unchanged
My undertsanding is that your algo will recalculate the momentum stocks at the beginning of the month, but then if the rebalancing takes place 20 days later (because it depends on when the initial stock purchase is performed), the set of momentum stocks could be different. I will work on making the rebalacing a bit "stronger" and less random.
Of course it's always difficult to compare moomentum strategies, also because of the well knwn phenomenon of the "day of rebalancing". Usually rebalancing at the beginning of the month delivers higher returns with these strategies.
Thunder Chicken
Hello,
I am trying to figure out why the attached back test isn't rebalancing monthly. I would appreciate any help.
Thank you!
Goldie Yalamanchi
Thunder Chicken that code was to make sure it entered the market when the algo started as long as be_in is 999 or whatever. Otherwise it would wait until the wait period to enter.
As far as the monthly re-enter, there are various versions of the algo (and getting very difficult to track), if you follow the last version that Leandro Maia attached and uncomment this code below you can have it rebalance monthly:
#if self.be_in and self.reb_count > 0 and (self.dcount - self.reb_count) == 20:
# self.rebalance()
# self.reb_count = self.dcount
So there is a lot going on with the various versions. The version we are talking about is the Earnings Rockets stocks which was supposed to be always by the definition of the universe selection something that should be re-evalauted every month to make sure the fundamentals are maintained. At least the original value momentum algo had that. The IN/OUT algo is supposed to pull you in or out of the market based on if the signals are bearish or bullish.
Intersecting these particular in/out + value/momentum monthly rebalancing is a challenge. I think as I said if you use Leandro's last algo with of course the code above commented back in it will rebalance in. My code will make sure the algo will start you into the market and not wait an initial period of 15 days when the algo first starts that would be annoying.
Here is a version I have used that combines the monthly rebalancing + start off into the market without any wait (as long as out signal isnt out)(hopefully I am not contributing to any confusion to all the versions out there now):
Leandro Maia
Damiano,
the lines below set the universe selection to happen every month start. But the momentum calculation and ranking is done on each trading day. You can change them to have a selection every week or even every day, but simulation time will increase.
if self.month == self.Time.month:
return Universe.Unchanged
Damiano Bolzoni
Hi Leandro Maia so what do you think it's the reason of the different stock selection when you run the strategy (without rebalacing, which should lead to the same selection on the same date)? Perhaps the other one does not recalculate nor ranks stocks every day? I don't believe the universe should change that often...it's picking among stocks with a $2Bn market cap....
Leandro Maia
Damiano,
in my opinion the difference is certainly the universe selection. As I wrote before, in the orginal version universe is selected one day after rebalance but then only used when the next in-signal is triggered, which could mean months. So doing the selection once a month will certainly change the selected stocks, mainly due to the coarse filter which is based on daily volumes. The ideal would be to use an avarage dollar volume filter, as it was possible in Quantopian and as proposed by Simone above. I still want to implement Simone's idea, but adding a warmup for the volume indicator, which is currently missing.
Damiano Bolzoni
Hi Leandro Maia I'm still "warming up" with the QuantConnect platform (I'm one of the "Quantopian orphans") and so this comment/remark might be wrong....I noticed in your code this initialization:
self.formation_days = 70
which seems to have an effect on the number of closing prices used to calculate stock returns. The original code posted in the "Momentum Strategy with Market Cap and EV/EBITDA" discussion has that variable sets at 200...is there a reason I'm missing for that choice?
Thanks!
Leandro Maia
Damiano,
I inherited self.formation_days = 70 from Peter's original version and he probably inherited it from Goldie. I tested using 200 but it doesn't improve the results.
Damiano Bolzoni
Ah! I see. So first of all, in the 2008-2020 timeframe, using a value of 200 will actually deliver worse results. But...between 2008 and 2019, it will deliver extra returns (more than 10% additional returns), with a minimal increase in drawdown. Moreover, the 2010-2019 timeframe was the longest bull market ever, and so momentum strategies "suffer" from that.
Typically for momentum strategies, you want a 120 trading-day lookback at a minimum. The best results are achieved with 10 months or even 1 year. I'm going to test a value of 250 now.
Thank you!
Peter Guenther
Great work here, all, and Happy New Year!
Thunder Chicken , I understand that you are after the SEL[EarningsRockets] + I/O[DistilledBear] combo. Please find an implementation attached.
Leandro Maia , spot on, the 70 days setting is from the Goldie/Vladimir algo in the Quality Companies in an Uptrend thread. Good hint that the original had 200 days here.
@all: I have recoded the algo to make the below settings regarding universe selection and momentum filtering. If you have thoughts on that or would do things differently, let me know, will be interesting to hear!
Rebalance universe (see line 121):
(1) every 20 days, see ((self.DCOUNT-self.reb_count)==self.setrebalancefreq)
(2) one day before the wait_days are up, see (self.DCOUNT == self.OUTDAY + self.wait_days – 1)
Note: I have also set self.OUTDAY to -14 initially (line 69), so that the universe is updated on the first day the algo runs (since according to the formula in (2), 0 == -14 + 15 – 1 is TRUE).
Determine momentum and trade (see line 218):
(1) when we change from 'out' to 'in', see (self.BE_IN and not self.BE_IN_PRIOR)
(2) when we are 'in', every 20 days, see (self.BE_IN and (self.DCOUNT==self.reb_count)). Note that I set self.reb_count=self.DCOUNT at the end of the universe fine filter (line 150) so this condition means that the universe filter was running and then also the momentum filter shall be updated.
Additional note: self.BE_IN_PRIOR is set to 0 initially (line 65) so that we directly begin buying equity according to (1) unless the algo directly says ‘out’ on the first day.
The orders in the backtest show that there is swapping going on 20 days after the last re-shuffle. So that looks good. You can experiment with different re-shuffle frequencies by changing self.setrebalancefreq in line 83 (e.g. to 40 = every two months).
It's interesting that the total performance is lower compared to the prior backtests which, due to an error, had a less frequent re-shuffling. Damiano makes an interesting point here, indicating that a strict beginning of month reshuffling might be a better choice—that will be worth a shot to see how it compares to my current settings.
By the way, the algo creates a log which you can download as a text file after the backtest is complete for manual inspection. It tracks which 100 stocks were fine-filtered and when (when you uncomment line 148), as well as which stocks are the chosen 10 stocks after the momentum filter and which stocks are sold because they dropped out of the universe+momentum selection/filters. Due to log file size constraints, when you save the 100 fine-filtered stocks, it will only give you the first two years of the backtesting period, so I have commented that out but can still be interesting to look at.
An additional note: The SEL[QQQ] + I/O[DistilledBear] combo (1843%; see the In & Out thread) outperforms the attached implementation of the SEL[EarningsRockets] + I/O[DistilledBear] combo (1378%). However, the EarningsRockets may have the advantage that we do not commit to the Tech sector, which may be under pressure going forward (I think Goldie made that point earlier since valuations are high). Instead, we select stocks based on their fundamentals, independent of sector.
I reckon it’s also noteworthy that the performance is so much lower compared with the initial version I have posted which used a ‘dated’ filtered universe. Does this mean that regular updating does not necessarily lead to better results?
One final note: The leverage sometimes goes up to 1.05. At the end, in Nov and Dec 2020, it even goes to 1.10. We might need to set the weight (line 238) to something below 0.99/len(chosen_df), unless someone has another cool move to keep leverage strictly below 1 (?).
Any bugs/unintended outcomes, give me a shout!
Peter Guenther
Just one follow up regarding additional findings: When I set self.setrebalancefreq = 60, the results are:
Total returns: 1634% (better)
Sharpe ratio: 1.1 (slightly better); Max drawdown: 25.3% (better); Compounding annual return: 24.5% (better)
However, the leverage issue (going above 1 and up to 1.1) seems to get worse.
Damiano Bolzoni
Peter Guenther I was also looking into "taming" leverage. I found this post on QuantConnect (I knew how to do it on Quantopian :-), but haven't tested it yet.
https://www.quantconnect.com/forum/discussion/8501/setting-leverage-in-the-framework/p1An additional comment about the day the algorithm trades when it's IN the market. At the moment the trading takes place at 11:30am. The reason why the last day/first day of the month usually deliver more returns could be (nobody really confirmed it over the years :-) that (big) funds are usually rebalancing around those days, so the needle moves more. However, by trading at 11:30am, those entities could have already "dumped" everything they had to. So trading at 11:30am it's sort of trading on the second day of the month, which seems to deliver the best results :-)
https://blog.thinknewfound.com/2015/04/new-research-paper-minimizing-timing-luck-portfolio-tranching/Damiano Bolzoni
Peter Guenther I honestly believe that we shouldn't try to optimize too much the momentum side of the algo. We can only test it over 12 years, which do not include the 2000 meltdown and on the other hand include the longest bull market ever....
The version that never rebalanced in market would deliver astonishing returns because it would hold TSLA from end of April onward...that was just a lucky coincidence...
Leandro Maia
Peter and Damiano,
the only way I was able to control the leverage runup was doing, during rebalance, SetHoldings for all the symbols: the ones that will be kept and the new ones.
Peter Guenther
Damiano Bolzoni , great stuff, thanks for forwarding the link regarding the leverage control approach. I will have a look and see whether I can get it integrated. Good insights concerning beginning-of-month trading and the impact of TSLA driving returns in the initial algo version; important points, thanks for sharing!
Leandro Maia , nice, I will try that approach. I had exchanged the individual stock loops through dictionary expressions to achieve a reduction in execution time. But I have to check again whether reversing the approach might help regarding the leverage issue.
I also have tried to play around with the self.formation_days, increasing it from 70 to 126 (~ half a year) in the algo version I mentioned above with self.setrebalancefreq = 60. It seems to help performance a little bit. But I reckon I have to fix the leverage issue first to actually be able to compare the returns.
Total returns: 1828% (better)
Sharpe ratio: 1.13 (better); Max drawdown: 22.4% (better); Compounding annual return: 25.5% (better)
Radu Spineanu
Has anyone figured out how to make this last version consume less memory? cc Leandro Maia
Leandro Maia
Radu,
I don't have any suggestion at the moment. I guess its a consequence of adding universe selection and simulating from 2008...
Damiano Bolzoni
Radu Spineanu I noticed that from one versiono onward, the resolution was set to "Minute". The algorithm would take more than 1 hour to complete (and I didn't check, but memory might go up as well), I set it back to "Hour" and now I can complete a run in 10 mins or so. Of course this affects returns, but it's honestly negligible because directionally you will get the same results.
Radu Spineanu
Damiano Bolzoni Leandro Maia Unfortunately changing the resoution to Hour or simulating from 2020 on didn't do anything. It must be the universe selection.
I tried to understand some of the changes the QuantConnect team did to improve the memory requirements of a previous implementation and got lost. I'll have to try again.
Derek Melchin
Hi everyone,
To speed up the execution of the algorithm, we can:
To clean up the OnSecuritiesChanged method, consider moving the warm-up logic to inside the SymbolData class. Lastly, we recommend avoiding try-except blocks as they can make debugging more difficult.
See the attached backtest for reference. It has the changes listed above.
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.
Peter Guenther
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!