Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Using a simple moving average to time markets has been a successful strategy over a very long period of time. Nothing to brag home about, but it cuts the drawdown of a buy and hold by about a half, sacrificing less than 1% of the CAGR in the process. In two words, simple yet effective. Here are the important numbers (using the S&P 500 index from 1994 to 2013, inclusive):

Statitistic SMA 200 Buy and Hold
CAGR 6.51% 7.13%
Sharpe Ratio 0.56 0.37
Sortino Ratio 0.05 0.04
Pct in Market 70.14 99.94
Winning Pct 54.61% 53.84%
Avg Drawdown 2.13% 2.48%
Avg Annual Drawdown 9.12% 15.83%
Max Drawdown 28.28% 56.78%
Gain to Pain 0.71 0.45

The moving average system is better by far. First, we can leverage it 2:1 (thus, bringing CAGR up to approximately 13%) and still, our maximum drawdown is going to be comparable to the buy and hold. Furthermore, it’s in the market only 70% – less risk and probably the returns can be further boosted by putting the money to work in alternative assets.

One problem with these type of systems are whipsaws. The system works well when the price stays away from the moving average. However, in close proximity, one may have to enter/exit in succession losing money on the way.

One way to address this issue is to use an alternative “line” to trigger the exits (or the entries, for that matter). It could be a percentage band, but that’s hardly universal. Better to bring volatility into picture.

Let’s do something more robust as an illustration.

```require(quantmod)
require(btutils) # For construct.indicator

gspc = getSymbols("^GSPC",from="1900-01-01",auto.assign=F)

# Compute the returns
rets = ROC(Cl(gspc),type="discrete")

# The moving average

# The standard deviation

# Long trades are entered when the average turns positive
upper.band = 0.0

# Long trades are closed when the average return goes below -0.5 standard deviations
lower.band = -0.05*stddev

# For the "uncushioned" version use
# lower.band = 0

uu = ifelse(sma>upper.band,1,0)
dd = ifelse(sma<lower.band,-1,0)
long.entries = (uu == 1 & lag(uu) == 0)
long.exits = (dd == -1 & lag(dd) == 0)
short.entries = long.entries
short.exits = long.entries
short.entries[] = FALSE
short.exits[] = FALSE

# Given entries and exits (both for long and short positions), this function builds
# the corresponding indicator. It's pure C++, so it's fast too.
ind = btutils::construct.indicator(long.entries, long.exits, short.entries, short.exits)
```

First we apply the moving average not to the prices, but to the returns (an interpretation of David Varadi’s Error Adjusted Momentum). The “cushioned” system goes long when the average becomes positive, but to exit, it leaves some cushion below the moving average. What do we gain?

Statitistic Cushioned EA EA SMA 200 Buy and Hold
CAGR 10.93% 9.11% 6.51% 7.13%
Sharpe Ratio 0.80 0.69 0.56 0.37
Sortino Ratio 0.07 0.06 0.05 0.04
Pct in Market 80.28% 76.29% 70.14% 99.94%
Winning Pct 55.21% 55.00% 54.61% 53.84%
Avg Drawdown 1.88% 2.05% 2.13% 2.48%
Avg Annual Drawdown 8.90% 9.26% 9.12% 15.83%
Max Drawdown 19.39% 19.57% 28.28% 56.78%
Gain to Pain 1.23 0.98 0.71 0.45

To compare apples to apples, we added two systems. The first one (dubbed EA) uses error adjusted returns to compute the SMA, but enters and exits when the SMA crosses the 0 line. The “cushioned” version is the system implemented by the above code.

Even after taking into account that the new strategies stays longer in the market, there seems to be slight improvement. But that’s not the point. The “cushioned” approach did 4 trades in total – it exited for the two bear markets. That’s about 4 trades. The non-cushioned approach had 80 trades. Mission accomplished in other words.

The post Trading Moving Averages with Less Whipsaws appeared first on Quintuitive.