Back to Community
Valuation based market timing

Hello all,

I attempted to implement the market timing strategy discussed in this post from Alpha Architect. I took Schiller’s CAPE ratio data from his website and calculated the inflation rate directly from the CPI data listed in the spreadsheet.

I applied the data to an Acquirer’s Multiple strategy that rebalances yearly, but also sells when the market is ‘expensive’ and buys back when the market is ‘cheap’.

I tested the system from 2003-06-01 to 2015-12-04. The simple Acquirer’s Multiple strategy has the following returns,

Total Returns Benchmark Returns Alpha Beta Sharpe Sortino Information Ratio Volatility Max Drawdown
358.4% 173.2% .14 1.06 1.20 1.56 .67 .22 53.5%

The Acquirer’s Multiple strategy with market timing has the following returns,

Total Returns Benchmark Returns Alpha Beta Sharpe Sortino Information Ratio Volatility Max Drawdown
304.6% 173.2% .11 0.92 1.04 1.32 .49 .21 42.4%

Although there is a significant reduction in Max DD the results seam underwhelming given the reduction in overall volatility is negligible and there is a reduction in total return.

I also calculated the Real Yield Metric but only using CAPE and CPI data going back to 1945. The results using this data are more impressive,

Total Returns Benchmark Returns Alpha Beta Sharpe Sortino Information Ratio Volatility Max Drawdown
484.5% 173.2% .26 0.93 1.70 2.18 1.16 .21 40.4%

I’ll leave it to the reader to decide whether this is a case of datasnooping or if using post-war inflation and market data is a more realistic measure for estimating future valuations.

Clone Algorithm
27
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: 5665b34be0b779116b020d25
There was a runtime error.
12 responses

My apologies,

I noticed an error in the calculation of the Real Yield metric. I've updated the data,
here are the updated results.

For Real Yield calculated from 1800 the results are

Total Returns Benchmark Returns Alpha Beta Sharpe Sortino Information Ratio Volatility Max Drawdown
336.2% 173.2% .14 0.88 1.14 1.50 .63 .21 42.6%

For Real Yield calculated from 1945 the results are

Total Returns Benchmark Returns Alpha Beta Sharpe Sortino Information Ratio Volatility Max Drawdown
441.7% 173.2% .23 0.87 1.59 2.02 1.04 .21 38.8%

Your question "whether this is a case of datasnooping or if using post-war inflation and market data is a more realistic measure for estimating future valuations."

Meanwhile pardon this side-note, wanted to point out the shorts around 2010 in case it was inadvertent. Might have been beneficial or neutral. Happened just after weekly returns took a jump, visible on the full backtest page.

Edit: I guess I should add a note about something to watch out for. There are times when we make a change and the chart might show, say, 40% lower returns, while the dollars activated to achieve that return could have been lower, the profit on each dollar put to work can be higher. The change made was very good and just seemed worse. PvR in the code below can help keep an eye on that. The algo above makes $5 per dollar compared to starting capital. Only $2 each vs. the $2M utilized (the amount needed to achieve that result), turn on the cash_low option to see it.

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: 5665f695cbe2db1163542ad4
There was a runtime error.

Hi Gary,

Thanks for pointing this out. I will take a closer look at this. At first glance though I don't understand how a short can enter the portfolio since whenever an order is sent in the weight is guaranteed to be >= 0. So how can order_target_percent place a short sell?

regards,
Mark

order_target_percent and their ilk do not take into account open orders, so if you are long and target 0%, then do that again before the first has fully executed, you might go short.

That's right.
This adds track_orders() to help you hone in on it with some lines added to only log sell/sold if a short would result.
The sell orders in excess of current shares happen more often than actual fill.
In possibly the most far-out use of the custom chart ever so far, when something is short, its security id is recorded. Only happened once, AAN 523.

I ran your code earlier today, then later with no changes there was an error about DECO on the order line here. As a rough workaround I added the line above it. The only thing I can figure is, since you are pulling in as many as 500 sids, the Morningstar data was updated in the meantime resulting in a different set of securities adding DECO and maybe it became delisted. PvR/info() is turned off. Check the log window. Clone/run this.

        for s in context.portfolio.positions:  
            if s not in data: continue  
            order_target_percent(s,0)  
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: 566639854bb94c114d384e42
There was a runtime error.

Hi Gary,

Thanks a lot for taking the time to look into this. Taking Simon’s comment into account I added this line,

if s not in get_open_orders():

to both the buy and sell functions, this removes the inadvertent short in the backtest, without changing the performance results.

Clone Algorithm
21
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: 56683ba78f7305115ae104e5
There was a runtime error.

Next, I took a look at the leverage/cash_low issue that you mentioned. The leverage in the algorithm is the result of a number of zombie trades. It seems when a company gets bought the Q backtester is not able to execute the sell order on the name, so it just stays in the list of open orders.

I added some code to explicitly exclude those companies, you can see in the associated backtest that the leverage stays at or below one and the cash_low is above 0 (not sure why it dips below right at the end, will need to have a look at that at some point).

Interestingly, the companies that get bought out are generally profitable trades, so the Acquirer’s multiple algorithm is doing what its supposed to, i.e. finding companies that are so cheap that they are likely to get bought. Not sure how to appropriately handle this case in Q, let me know if you have some ideas.

Regards,
Mark

Clone Algorithm
21
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: 56683fc62d47c01161c5cd9e
There was a runtime error.

You increased profits on each dollar put to work by 27%, congrats. (The last algo might appear to be worse, it is better on a per-dollar-utilized basis, PvR, Profit vs. Risk).

A couple of the stocks on your no-buy list show up in google finance with a price of 0.0000 right now even tho they've been trading around 10 and 50, LCC and CVH. Consider maybe skipping anything below $2 or $5 or some threshold, keep a count of the number of times each in a dictionary (that it happens), and if the count reaches some ceiling, append to no_buy_list, starting out empty.

Hi Gary,

Again thank you for taking a look at this. The thing is that LCC and CVH do not go to zero they get bought out, this is a situation that the Acquirer’s multiple actually seeks out so I’m not sure it makes sense to exclude these trades. I posted the previous backtest to illustrate that these kinds of trades were the cause of the extra leverage. I added the trades back in and added some logging to the latest backtest to list all open orders at the end of the backtest period. It shows in general that they are profitable trades. For example LCC was bought out by American Airlines (AAL) in 2013, the last price it traded at was$22.35 which is above the $16.36 that the algorithm paid for it. In reality you would be given shares in AAL and be able to sell them (presumably at a similar profit), however this is not modelled in the backtest. So, for the most part the money tied up in the zombie trades could be converted to cash and made available to the algorithm. Your ‘pvr’ metric doesn’t account for this which is throwing off the measure to some degree.

Clone Algorithm
21
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: 5669a28d561cf9115bc26109
There was a runtime error.

While acquisitions are an interesting thing to tackle for sure, regarding the last line, pvr essentially operates on pnl which is not changed by open orders. Perhaps you are saying in the real world one would be able to trade some acquired shares that in the backtest environment are no longer in data , looking forward to a run-it-ready code example if humanly possible to model in a backtest, and until then, pvr is not throwing anything off, it is passive and just factually measuring what's there, basically pnl divided by most_risked i.e. amount needed in the brokerage account to achieve that result. The only difference from the standard returns calc is the denominator, transacted instead of starting capital.

Hi, I was hoping to look at this algorithmn, but it uses a .csv from dropbox which is not available. Any ideas on how to run it w/o that or where to get the source data?

Hello,
When I cloned this algorithm nd tried too runa backtest, I got this error. How do I get the dropbox content?

thanks

-kamal

Something went wrong. Sorry for the inconvenience. Try using the built-in debugger to analyze your code. If you would like help, send us an email.
Exception: Problem reaching https://dl.dropboxusercontent.com/u/90042659/Quantopian-RealYield-1945.csv
There was a runtime error on line 26.