Going 10% long on top 3-day momentum, and 90% short on 3-day bottom momentum US equities during Coronavirus (COVID-19) outbreak.
Going 10% long on top 3-day momentum, and 90% short on 3-day bottom momentum US equities during Coronavirus (COVID-19) outbreak.
@Dzenan,
if you set universe to QTradableStocksUS, as you intended to,
my_momentum = Momentum(window_length = 3, mask = base_universe)
the results will be not as bright.
@Vladimir
thanks for your comment!
screen=(base_universe) is set in return Pipeline function. All assets must be tradable(QTradableStocksUS) after all.
Removing base_universe from the pipeline yields in much better results.
Hi @Dzenan Hamzic
Very interesting strategy!
However, this strategy might only apply to a very very volatile bear market. I post a backtest based on your algo during the financial crisis last time (2007-2009) on the bottom.
As soon as the market turns its head to a bull (or up-trend) market, the strategy suffers huge losses and very large volatility. Then a very sharp stop during the market real bottom is very much necessary.
Just my two cents.
May i ask if this part of the algo is correct for a momentum factor:
# Filter to select securities to short.
shorts = my_momentum.top(10)
# Filter to select securities to long.
longs = my_momentum.bottom(10)
I am reading it as... if the stock has risen a lot, short it; if the stock price has fallen badly, long it. The code seems like mean reversion rather than momentum. For momentum factor, shouldn't it be long the higher return stock, short the lowest return stock?
Check out out[:] = close[-1] / close[0] being the momentum calculator. When prices are rising, will the ratio be above or below 1? There lies your answer.
@ Alexander Katyk
Thanks for your reply. So this thread on momentum factor is wrong? In fact, the algo is actually testing mean reversion strategy rather than momentum.
I'm new to Python and Quantopian, and probably this algo has been vetted by way more experienced users, but i think i am correct in reading this (please correct me if im wrong though).
close[-1] is the most recent price in the lookback period denoted by window_length parameter.
close[0] is the oldest price in the same lookback period.
Referring to this other algo: https://www.quantopian.com/posts/long-short-pipeline-multi-factor
Their momentum factor is computed as " close[-20] / close[0] " for a 252 window_length, which means a one-year return, but excluding the most recent 1 month (20 trading days); therefore, technically a 232 days period. Thereafter, they long the tail via "context.longs = ranks.tail(200)" after ranking the momentum factor (along with other factors) in default ascending order . This supports my understanding of the direction of 'close[-1]'.
So back to the algo in this thread, if i got the direction of close[-1] and close[0] correctly, the next question will be the direction of the built-in function .top() and .bottom(). According to the documentation, the definition for .top() is "Construct a Filter matching the top N asset values of self each day." I am understanding it as, finding the top N asset with the HIGHEST factor value, rather than functioning like .head().
https://www.quantopian.com/docs/api-reference/pipeline-api-reference#zipline.pipeline.Factor.top
Referring to Q's lecture series:
https://www.quantopian.com/lectures/example-long-short-equity-algorithm
".top()" is used to filter out list of asset to LONG via "longs = combined_factor.top(TOTAL_POSITIONS//2, mask=universe)", which would support my interpretation of the direction of the .top(), that is, to obtain the N assets with the highest factor value.
If my understanding of the direction of "close[-1]" and ".top()" function is correct, then there is a mistake with the long short rules in the algo from this thread, it should be swapped.
Again, please correct me if i am wrong about this fundamentally important concept. I have no confidence in myself, given that people with way more experience than me have vetted this code.
close[-20] grabs the 20th price from the end of the series. So the question becomes is close[0], being the first price in the series, the oldest or newest price? I thought it goes from newest to oldest but could be wrong.
Also a good post on price discussion: https://www.quantopian.com/posts/current-price-vs-last-price
Yup, so according to your notebook, pipeline churns out the oldest data at the top and the newer data at the bottom. Hence, with python slicing, close[-1] gets you the most recent price out of your series and it would mean that close[0] gets you the oldest price. Then the question will be about .top() and .bottom(), which if i am correct (from my previous reply), then there is mistake in the long short rule for this algo and that the backtest results are wrong for a momentum algo.
Can somebody explain to me how does [1] computes the momentum? Specially when called by [2]!!
I am a computer scientist, very use to "standard" programming, however, I am still having a very hard time understading how the variables a linked in this code.
Some of my confusing thoughts:
- there is no .top() neigther .bottom() function within the class Momentum that I can see.
- "window_length=3" is not a parameter of the class as well (?), instead, "(CustomFactor)" is.
- what "out[:]" is doing? by the name, I can imagine this is the class output somehow, but what is the [:}
Please, somebody help me to understand the mechanics of this code!! It is very frustrating having to guess where "hidden" variables are so I can assume they here and there.
Cheers!
[1]
class Momentum(CustomFactor):
# Default inputs
inputs = [USEquityPricing.close]
# Compute momentum
def compute(self, today, assets, out, close):
out[:] = close[-1] / close[0]
[2]
```
my_momentum = Momentum(window_length=3)
# Filter to select securities to short.
shorts = my_momentum.top(10)
# Filter to select securities to long.
longs = my_momentum.bottom(10)
@Roger
Yes, there's a lot of hidden magic. In order to find it you can follow the classes, for instance:
The Momentum above inherits from the class CustomFactor, which in turn inherits (amongst others) from Factor and the latter has functions called "top", "bottom" and many others. The class Factor inherits from ComputableTerm and there is your declaration of window_length. One way to inspect the underlying code is to check out zipline on github
I haven't found the definition of "out" yet...
And a quick demonstration what the [:] is good for. Basically, if you say
something = something_else
you redefine something with something_else, but if you say
something[:] = something_else
you populate something with something_else
I modified the strategy to take volume into account, and I changed the ratio of longing and shorting. This backtest is for 2020 so far.
I'm a quant newbie here, trying to run this using pylivetrader. what's the alternative to "quantopian.optimize" since it's not supported
@Anthony
You can just iterate over the weights and order each of the symbols using order_target_percent. I don't know how ordering works there and would need more details, like which order methods are available (percentage of portfolio, target size, or only number of shares) and if you can place an order when you don't have enough cash ready at that time
@andres
I also got an error message in another timespan. It has probably something to do with the optimizer having problems with the universe - aka mostly penny stocks. You can avoid this using try and except:
try:
order_optimal_portfolio(
objective=opt.TargetWeights(wts),
constraints=[],
)
except Exception as e:
print(str(e))
@All others who think these are valid results
I ran a test where I invested $ 1,000 in 2005 and got a return of 190,578,981 % = ONE HUNDRED AND NINETY MILLION PERCENT. That means, if I now invest 1000 bucks in this strategy I will be a Billionaire in 15 Years!!!
Sounds too good to be true? Well, it probably is. This post might be enlightening...
so this is like a small cap strategy? Since the factor would almost surely capture those small caps with large volatility.
@Tentor
first of thank you for your responses. It really does help us understand this algo more.
I wonder how this algo would run with nonpenny stocks and high volume maybe 20M+
My point is, it's not a strategy at all, more like a hack of the backtester. Try using QTradableStocksUS as a filter and the returns go away. If you let the algo look for the biggest price movements unfiltered, it will naturally find many penny stocks. its not uncommon that a penny stock moves several hundred percent in one day. That often means the price rises from $ 0.001 to $ 0.008 - 700 % and still a fraction of a penny. Its just very difficult to simulate realistic trading with prices like that.
Does it suffer from survivorship bias? All those small cap traded are those survived. I see a lot of warnings because many trades cannot be made in the backtest.
@Anthony
Dollar volume is more meaningful (price*volume) because if someone orders 20M of a stock worth $ 0.001 that's just 20,000 dollar being traded. Qtu has a minimum of $2.5 M dollar volume. Here's a test with qtu as universe. A working strategy should work at least a bit in a different universe...
Of course I could be wrong and we have a real goldmine here - a trading strategy that only works with penny stocks and makes you very rich. The problem is, how will you know? I tried a few different versions and some of them got a negative return of several thousand percent. That not only means your investment is gone but you also owe your broker such a huge mountain of debt, you will perhaps never be able to pay...
@tentor,
This makes a lot of sense.
I wonder if we can add another screen and just retrieve the penny stocks that have a substantial jump in volume within past x time to choose from, rather than all tradeable stocks.