Back to Community
Equity Momentum

Since there seemed to be some interest in momentum models, I thought I'd make a more detailed and more configurable version. I wanted to show what parts are useful to modify and give some ideas for further research.

What is important to understand here is that it's not about maximizing the performance. It's about finding a robust approach, which is not overly dependent on exact parameters. We're trading broad concepts here, and in this case we're looking for robust ways to capture equity momentum.

Remember that the exact settings used for the backtest below are neither the 'best' nor 'worst'. If there is such a thing. It's meant to demonstrate a concept and teach a methodology. Work on it a bit, and I'm sure you can improve it.

The attached model has plenty of settings and comments in the code, which will hopefully help readers get started and experiment with momentum.

If you find things that could be improved here, that's great. Found an error? Please post it. This model is made to be shared and improved.

Clone Algorithm
796
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
# Backtest ID: 58e92871cd2608619e7e7809
There was a runtime error.
25 responses

Andreas,

1.In this version of Clenow Momentum strategy you introduced stock selection using two slopes with different periods and exclude_days parameter.
What is the idea behind those improvements and their influence on performance metrics ?

2.To my mind the code in line 176

data_end = -1 * context.exclude_days  

should be written

data_end = -1 * (context.exclude_days + 1)  

otherwise you will get a run-time error if context.exclude_days = 0

Hi Vladimir,

The main purpose with introducing these variables is to show common practices and what kind of things might be useful to tinker with. The aim is robustness.

The two slopes demonstrates a concept which could easily be built upon. It's quite a common approach, to weight different calculation windows for a momentum model. You could add more of them, you could add weighting factors, you could try punishing short term momentum in favor of long term, etc. It opens up for quite a bit of potential research.

The exclude_days variable is a very common approach seen in most academic literature on momentum. Most researchers exclude the last month's data to account for the snap-back effect. Some like using this, some not. There's plenty of research out there favoring either approach.

The exclude_days syntax is my mistake. You're absolutely correct. Thanks for spotting it, Vladimir!

Andreas,

Excellent code, I follow both of your momentum posts and have learned a lot from your strategies, thank you for sharing all your work and ideas.

Would you, or anyone else following know how to add to the code a way to move to multiple tickers when SPY is below its MA trend?

For example, instead of just IEF, move evenly to IEF, AGG, GLD? Is that an easy code change?

# identifier for the cash management etf, if used.  
context.use_bond_etf = True  
context.bond_etf = sid(23870)  

Can anyone explain why every trades is executed at different time? 4:30, 4:35,...and even at 5:00
I see it in transaction details

Pietro: It has to do with the Quantopian slippage model, which limits how large part of the volume you can take. Unless you disable the slippage model, it won't let you take a large order at the market, if there wasn't really sufficient liquidity to do that without impact. The code at the moment simply enters market orders, and lets the executions land where they may. Feel free to modify.

William: Shouldn't be difficult. Just split the etf_weight into the number of funds you want to allocate to, and enter target_percent orders for them. Easily done.

@ Andreas thank you for sharing your expertise with us. It is very much appreciated. And your books are great.

Cheers,
Warsame

@Andreas - Greatly appreciate your work and supplying us a great example to build from!

Thanks, guys. Your comments are much appreciated. I'm looking forward to see what improvements the community can do on the model. Remember that it's just a base version, and leaves plenty of room for improvements.

@Andreas - One of the things I haven't seen mentioned or incorporated in any of the posts whether in this thread or the other but is mentioned in your book is in regards to the gap filter. I know you are focused on providing a broad concept here for others to work off of. Yet I am curious; are you purposely leaving that out of the code due to a revised methodology on your part? Or are you still using that as a filter in your own personal algorithms and it is again just something the community can incorporate into this code if they'd like. I also must say I've enjoyed learning from your book and appreciate how active of a member you have been here in the Quantopian forums. I have been actively using this methodology with some modifications to live trade for a few months now and therefore am always looking for ways to improve upon it.

Thanks, Greg.

The gap filter I used in Stocks on the Move is probably the one thing I get the most questions about. In particular after readers run simulations and, correctly, conclude that it seemingly has no actual impact on results over time. That's absolutely correct. It doesn't.

The only point of the gap filter is that it makes it easier to actually execute and follow the rules. Without a gap filter or similar, you will get some pretty scary looking entry signals at times. That's not visible on an equity curve like the one above, but it will have an impact on your ability to actually pull the trigger on a trade. The gap filter also takes you out of trades where something out of the ordinary happens. A large move up, or a large move down. Either way, the scenario changes and it's comfortable to exit. I call this type of rules comfort rules.

For those who didn't (yet..?) read Stocks on the Move: The gap filter rule described there stated that any stock is excluded from the valid investment universe if it had a close to close gap in excess of 15% in the the past 90 days. At least that's what I remember at the moment, without checking my own book... As stated, it doesn't change any results in any meaningful way over time, but it makes it easier to follow the rules by removing some scary setups.

And of course, my usual disclaimer: I'm never attempting to publish the best possible rules, or some sort of optimal Clenow Super Algo, which would obviously be quite silly despite the catchy name. My published research always aims to teach a concept and show valid methods of working. My intention is never that readers should simply take my rules and trade it, but rather to learn from it, take what they like, modify and change what they don't like, and make their own personal models. That's why I always publish all the rules.

I make some changes to see if it can be adopted to Long / Short. Seems to have lot of room for improvements. Perhaps some of you can chip in.

Clone Algorithm
7
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
# Backtest ID: 58f61070e3f9f76607895433
There was a runtime error.

I tried to port reduced version of this to the optimisation API. Sharing the initial attempt. If there are improvements let me know.

Clone Algorithm
0
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
# Backtest ID: 58f74663423dfa66ce3edd10
There was a runtime error.

Hi,
Just fused @Suminda 's algo above with the original to see if I could compare the two easily.
Set the flag context.opt_method to flip between the original rebalance and the new rebalance using the optimize api.

I was only interested in a last-3-year backtest, as that seemed problematic for the returns of the original,
so I lengthened the windows a bit, and it performed better...see enclosed backtest.

Notes:
-Whenever I tried to use the Q1500, I got worse results.
-Whenever I used the Optimize API (my_rebalance_opt), I got worse results,
but it was kinda cool how it bought massive #'s of longs and shorts!

alan

Clone Algorithm
13
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
# Backtest ID: 58fa59bbfb18a261824d58b9
There was a runtime error.

Some nifty code there.

In comparing two algorithms with same starting cash and different cash usage (or visa-versa or both) what you'll need to do is neutralize risk for an apples-to-apples comparison, otherwise returns are in different ball fields.

To start to understand how it works, I go at it from this angle: An algo from Jan 3 this year buying 1 share of SPY every day for a month will have the following returns with different values of starting capital, even though they use the same amount of cash in this case. No shorting, so cash is the only risked value here.

Starting Cash       Returns  
    5000              .6%  
    2000             1.5%  
      10             305%  

All have the same PnL ($30) even though returns are apples to oranges.
Returns are pegged only to starting capital no matter how much was used, I gather that is one of the industry's standard methods.

There's a tool that neutralizes both starting capital and cash, measuring PvR (Profit vs Risk) by keeping track of the maximum risk and charting the percentage result. It is PnL/MaximumRisk, performance of each required dollar on average. I score alogorithms with its PvR %/day value included in the output, which should also be removing time from the equation (different date ranges). Still not perfect yet the best I know.

In your code, with investment_set = 1 in both, the one using opt_method has a sudden dip into $3M margin near the end that you'll probably want to address, you can use track_orders and set a valid trading day just before, as its start date, to see what's happening there.

Hope that will help.

Hi Guys, very interesting result, I am new here, can someone please explain to me in plain english what this strategy is? Imagine I want to trade this strategy manually, thanks in advance

@mike sina this is a strategy based on Andreas Clenow's most excellent book Stocks on the Move. Do yourself a favor and pick up a copy. One of the best investing/trading books I have read, highly recommended!

@andreas is there a bug on line 239 in your originally posted strategy?

Should

        # allow rebalancing of existing, and new buys if can_buy, i.e. passed trend filter.  
        if (security in context.portfolio.positions) or (can_buy):  

be an 'and' instead of an 'or' for the can_buy?

        # allow rebalancing of existing, and new buys if can_buy, i.e. passed trend filter.  
        if (security in context.portfolio.positions) and (can_buy):  

@Kane Dijkman thanks so much for the reply, is there any chapter associated with this strategy that I can read it instead of reading the whole book? Really appreciate this, thanks

@mike sina there is a chapter that is about 4 pages that just covers the rules (should be obvious which chapter it is by its title), but the real value is in the thinking and approach behind the strategy that Andreas builds throughout the book. Its a pretty quick read too with a chapter that explains each rule. If you are considering putting any money behind this strategy it would be worth giving it a quick read.

@Kane Dijkman: Sure definitely will read this book , thanks

thanks for the well commented code, helped ,me to learn

@Kane Dijkman
Referring to the bug, I think the original code is correct as it allows you to rebalance always when can_buy is False (when the index filter is below the specified trend) which in turn allows you to rebalance yourself out of the market until can_buy is True again. If it was "and", then you wouldn't be able to sell your positions when the index drops below the trend.

Hey, @Joshua Lagan, thanks for response explaining the logic. Seems I misremembered the rule from Andreas book and thought we should close all positions if the index moves below its moving average. I just reread and see that he just says that "no new buys are allowed".

@Kane Dijkman
Anytime!

@Andrea Clenow can we Dumb down... your model say random stock picking... equal weighted re-balance monthly... and lets see... if it really... outperform the index... year on year....