An EMA Trading Strategy for a Low Volatility Portfolio


The process I’m going to follow is based on content from the University of Washington’s CFRM561 course Advanced Trading System Design. “Hypothesis driven development” is the core principle of this course, where each step in the development process involves hypothesizing testable ideas, and verifying these ideas before proceeding to the next stage. The stages involve identifying one or more market indicators, testing that the indicators are actually measuring the intended market phenomena, hypothesizing entry and exit signals based on the market indicator(s), confirming if the signals have predictive power, and then setting entry and exit rules based on the signals.

I am personally a fan of low volatility investing, insofar as it provides enhanced risk-adjusted returns vs. traditional cap-weighted market indices. Thus, I have reconstructed the CRSP lowest volatility decile portfolio using data from Yahoo! Finance, and will use this index as the baseline for evaluating the trading strategy. The goal here is to verify if a Exponentially-Weighed Moving Average [EMA] trading strategy can further enhance risk-adjusted returns by either increasing returns without increasing risk, decreasing risk without impacting returns, or both.

This post is a significantly condensed version of the full report. The full-version report, which is the actual assignment I submitted, can be found here. It contains all the details for each step, results of the hypothesis tests, including the signal strength, as well as the interim parameter optimization steps.


The first question related to the indicator that we need to ask ourselves is, “what do we think we’re measuring?”

It can be argued that the EMA is measuring the “true de-noised price level”, where the thinking is that the moving average “averages” the noise away, thereby revealing the true level.

It can also be argued that, assuming the EMA is measuring the “true” level, it is also measuring the price trend. The rationale here is that, if there’s a true level revealed, then the trend can be inferred from the time series of “true levels”.

In order to test that EMA is measuring the true level, the correlation between the low-vol index level and EMA indicator is tested for strength and statistical significance. To test the trend, it is compared against a measure of “true” slope by testing for high and statistically significant correlation and cointegration.


Prior to defining any signal, the main prediction that might be possible with an EMA is stating whether or not the future index level will be greater than or less than the current index level, instead of generating a point-forecast. For this tool, confusion matrices are used to measure if the future-index-level-forecasts are correctly classified as higher or lower than current index levels. The statistical significance of the confusion matrices are also verified to ensure that there is some information content.

In terms of the actual signal, the thought behind it is that if the current index level exceeds the EMA level, this will increase the subsequent EMA value (which was verified as a measure of “true” price level), which will ultimately increase the EMA trend. This would then be interpreted as a potential signal that the index level will rise.

The rationale for the converse stands, in that, if the current index level is less than the EMA level, this will decrease the subsequent EMA value (which is a measure of “true” price level), thereby indicating that the index level will potentially decrease.


The rules are simple: if it is likely that the price will increase based on the relationship between indicator and index, (i.e. the index level exceeds the EMA level) then a long position should be initiated. Conversely, if it is likely that the price will decrease based on the same relationship, then any long position(s) should be closed.

Benchmarks and Objectives of Strategy


The main goal is to verify if the risk-adjusted returns for the low volatility index can be enhanced. Thus, maximizing a measure of risk-adjusted performance is the objective of this strategy, since leverage can always be applied to increase absolute returns.


\; \; \; \; s.t. MIN(risk)

Given that the objective is to maximize risk-adjusted returns, the following statistics can be used to quantify risk-adjusted returns:

  • Sharpe Ratio [SR]
  • Profit-to-Max Drawdown [PMD]

During the trading simulations, PMD will be used to assess risk-adjusted returns as it is more sensitive to the tails than the SR. However, in order to evaluate the viability of the trading strategy, the SR will be used.


Results of the EMA trading strategy will be compared against a 100% long version of the low volatility index. This portfolio is the ‘buy-and-hold’ [BH] version of the index, whereas the trading strategy will be referred to as the Moving-Average Portfolio [MAPortfolio]. The BH portfolio is the “no effort” portfolio with which to measure if the trading signals are adding value. Success is not defined on absolute returns; it may be possible to observe absolute lower returns, however exhibit higher risk-adjusted returns.


Signal Strength Results

The Heidke Skill Score [HSS] is used to quantify signal strength, as it is more unforgiving than the “Probability of Detection”.

HSS for the up signal indicates that the skill level is relatively low for lag days of 15 or greater. Moreover, the skill level is highest for 1 day-forward with a 5 day lag, and drops off relatively rapidly for a 5-day lag when the number of forward days exceed 3 days.

The HSSs are much higher for the down signal, and appear to persist for 1 – 5 forward days, and lag days up to 60 days, assuming that less than 1% skill is not enough to make useful predictions. To clarify, the 60-day-persistence isn’t visible on the contour plot, however numerically the HSSs are more persistent for the down signal vs. the up signal. So far, this implies that the down signal contains more predictive power than the up signal.

All skill scores determined to be “strong” are also statistically significant, both for up and down signals.

Trading Strategy Results

After testing various lag-lengths exhibiting high HSSs for both up and down signals, as well as performing a parameter optimization, the conclusion detailed in the full report was that it is likely more reliable to set the lag length based only on the HSS strength instead of the backtest results due to poor out-of-sample performance [OOS]. The OOS results, as well as the code, for the high HSS lag lengths (5 days for the up signal and 5 days for the down signal) are displayed below.

decilePortfolioMatrixLevel <-,

filterRange <- "1995/"

MAPortfolioALL <- decilePortfolioMatrixLevel[filterRange]
startDate<-as.Date(index(MAPortfolioALL)[1], format="%Y-%m-%d")
endDate<-as.Date(index(last(MAPortfolioALL)), format="%Y-%m-%d")


stock(primary_id = c("MAPortfolio"),currency = "USD")

maStrategy <-"MAStrategy"

#Date, one day before prices
strategyDate <- min(index(MAPortfolioALL)) - 1

#Used to reset for testing
#if (!exists('.blotter')) .blotter <- new.env()
#if (!exists('.strategy')) .strategy <- new.env()

NumDeciles <- ncol(MAPortfolioALL)

MAPortfolio <- MAPortfolioALL[,maxDecile]
colnames(MAPortfolio) <- "MAPortfolio.Close"

maLag <- 5
maLagDown <- 5

#init portfolio and account
initPortf(name = maStrategy
          , symbols = list("MAPortfolio") #as defined in Financial instrument
          , initDate = strategyDate

initAcct(name = maStrategy
         ,portfolios = maStrategy
         ,initDate = strategyDate
         ,initEq = 1 

#order book, and strategy
initOrders(portfolio = maStrategy
           , initDate = strategyDate

#position limits
addPosLimit(maStrategy, symbol = "MAPortfolio", strategyDate, maxpos = NumSh, longlevels = NumSh, minpos = 0)

strategy( maStrategy, store = TRUE)

#add indicator
add.indicator(strategy = maStrategy
              , name = "EMA"
              , arguments = list(x = quote(mktdata$MAPortfolio.Close), n = quote(maLag))
              , label = "MAPortfolioma"

add.indicator(strategy = maStrategy
              , name = "EMA"
              , arguments = list(x = quote(mktdata$MAPortfolio.Close), n = quote(maLagDown))
              , label = "MAPortfoliomaDown"

#MAPortfolio is greater than N-Lag Moving Average
add.signal(strategy = maStrategy
           , name = "sigComparison"
           , arguments = list(columns = c("MAPortfolio.Close","MAPortfolioma")       
                              , relationship = "gt"
           , label = ""

#MAPortfolio is less-than-equal-to N-Lag Moving Average
add.signal(strategy = maStrategy
           , name = "sigComparison"
           , arguments = list(columns = c("MAPortfolio.Close","MAPortfoliomaDown")
                              , relationship = "lte"
           , label = ""

#Entry and exit rules

#buy MAPortfolio when above N-Lag Moving Average
add.rule(strategy = maStrategy
         , name = "ruleSignal"
         , arguments = list(sigcol = ""
                            , sigval = TRUE
                            , orderqty = NumSh
                            , ordertype = "market"
                            , orderside = NULL
                            , osFUN = "osMaxPos"
                            , symbol = "MAPortfolio"
         , type = "enter"

#sell MAPortfolio when above N-Lag Moving Average
add.rule(strategy = maStrategy
         , name = "ruleSignal"
         , arguments = list(sigcol = ""
                            , sigval = TRUE
                            , orderqty = "all"
                            , ordertype = "market"
                            , orderside = NULL
                            , osFUN = "osMaxPos"
                            , symbol = "MAPortfolio"
         , type = "exit"

applyStrategy(strategy = maStrategy
              , portfolios = maStrategy


BH MAPortfolio
Annualized Return 8.66 10.18
Annualized Standard Deviation 7.62 4.19
Semi-Deviation 5.61 3.04
Annualized Sharpe Ratio (Rf=0%) 1.14 2.43
Adjusted Sharpe ratio (Risk free = 0) -7.62 -18.36
Average Drawdown 0.84 0.49
Average Recovery 9.33 7.05
VaR 95 NA -0.25
VaR 99 -16.93 -2.99
ETL 95 -25.02 -0.25
ETL 99 -16.93 -2.99
Worst Loss -6.64 -4.17
Skewness 1.88 -0.84
Excess Kurtosis 152.83 37.56
## [1] "t-test p-value:  0.52"
## [1] "F-test p-value:  0"

One key observation is that the performance during the 2008 financial crisis was relatively spectacular, with a drawdown of only approximately 10%, vs. 40% for the BH index. During the start of the OOS performance, there was some underperformance, up until the dot-com crash in late 1999 and early 2000, where drawdowns for the MA portfolio were minimal, allowing returns to be maintained.

Mean returns are not statistically significantly different, however variance is statistically significantly lower, resulting in the superior Sharpe Ratio of 2.43, vs. 1.14 for the BH Index. Unfortunately, the trading strategy does not exhibit positive skew, whereas, surprisingly, the BH Index exhibits positive skew. However, excess kurtosis, though high for both portfolios, is significantly higher for the BH index. It appears as though the trading strategy is significantly reducing the impact of negative market events, allowing the preservation of accumulated returns, with the drawback of potentially missing out on any upside. Given that the objective is to enhance risk-adjusted returns, the biased parameter combination appears to successfully meet this criteria. One additional factor is the transaction costs, which were not considered in this study. These would need to be factored in to the backtests to verify that superior risk-adjusted returns are maintained.

© 2016 Erol Biceroglu


7 thoughts on “An EMA Trading Strategy for a Low Volatility Portfolio

  1. Hi Konrad, thank you for your feedback. The HSS can be calculated using the verification::table.stats() function. You need to pass a contingency table to it. Most of the code to run the HSS tables and contingency tables can be found on the main report in the Appendix: What’s missing is the code to replicate the CRSP Volatility Deciles (the Low Volatility Index). I’m going to post that code soon, so that this can be replicated in full.


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s