Back to Community
Magic Formula

Magic formula has been discussed in this forum in the past, but not much backtest results have been shared so far. Here I've implemented Greenblatt's strategy, with minor modifications such as filtering out mining and pharmaceutical companies. I've run backtests in segments with different market cap ranges, which showed that eliminating small cap stocks under a billion dollar cap improves the overall return. Small cap baskets tend to get destroyed by a number of companies losing more than 30 percent of their values. As per Greenblatt's remark, the strategy has periods of underperformance compared to S&P, but in long run it does seem to come out slightly ahead.

I'm now interested in comparing the predictive power of fundamental ratios. The original formula weighs return on investment (ROI) and earnings yield (EY) equally, but I found some discussions arguing EY should be weighed more heavily. Ideally I would like to do some regression analysis on these two ratios and other fundamental metrics, but It's difficult to find free historical fundamental data to test this thesis, so I'm wondering if someone here with some experience can chime in.

Clone Algorithm
65
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: 59ff8319e79d284013b6580b
There was a runtime error.
4 responses

Hi, Jonh.
Thank you for sharing your code. It was the great starting point for me in learning Quantopian.
But, It seems that we can't use any more get_fundamentals() which is in your code.
I tried to modify your code that uses pipeline and I backtested it for same period.

I just modified 3 lines and add make_pipeline() in your code.
modified below.

  1. for stock in context.stocks.index: to for stock in context.output.index: (line 52 in your code) (To use pipeline's output.)
  2. data[stock].price: to data.current(stock, 'price'): (line 60 in your code) ( Because it looks that data[stock].price was deprecated.)
  3. context.output=pipeline_output('my_pipeline').sort_values(by='MF_rank', ascending=True).head(int(context.capacity)) (before_trading_start() in your code)

added below.

  1. my_pipe = make_pipeline()
  2. attach_pipeline(my_pipe, 'my_pipeline')
    (initialize() in your code)
  3. make_pipeline(): ...

But the result had some quite difference.(My result got low returns versus Benchmark's and got high MaxDrawdown.)

Could you please look at my code and help me?

Clone Algorithm
3
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: 5b06809b4b8cc9447c87b91b
There was a runtime error.

Best to rank using the mask:

EY_rank = earnings_yield.rank(ascending=False, mask=filter_plus)  
roic_rank = roic.rank(ascending=False, mask=filter_plus)

Remove close.latest not in original.

This is a little different.

Clone Algorithm
22
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: 5b06955044f983444ca12611
There was a runtime error.

Thanks, Blue Seahawk.
Your answer is really helpful for me!

@K, thank you for mentioning it, and here are some more possibilities then too.
Mainly run this and take a look at the log window. Visibility into the pipeline values.
This is just a start toward adding short if you wish, it would need some work.

The focus here is to provide options, tools & flexibility. For example, class Wild() for quick development in trying things, normalization of positive and negative weights separately to be able to add shorting if you wish (that's where things went south with that last minute addition of norm()), logging of pipeline min, mean, max and some highs & lows, forward filling of nans (addition of class was necessary there, to have a window to work with, rather than just latest), an example of percentile_between you might want to try some numbers in, examples of zscore, demean (as one way to obtain some negative values for short shares), a little bit more efficient route for 'today', the efficient pnl determination for long & short simultaneously helps makes an addition of shorting easier to work with if interested in moving toward qualifying in the contest for example.

Returns were not the point so this backtest is only a few days. Rather than going with this algo, you could use it to copy/paste various bits over to yours in trying some things.

Clone Algorithm
22
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: 5b080039a9f88b442f8e1b59
There was a runtime error.