Analyzing the DVI Indicator

The DVI indicator is a well-known indicator, created by David Varadi from CSS Analytics. It was introduced in 2009 as a good predictor for the S&P 500 over the past 30 years. Its performance on the S&P 500 has been studied in the blogosphere comprehensively. None of these studies, however, contained everything I was looking for, and since I have a few indicators on my todo list, I decided to use the DVI to create an approach for analyzing indicators.

The DVI indicator oscillates between 0 and 1. The basic strategy to trade it, is to enter short if the close is above 0.5, and a long otherwise. Despite its simplicity, this strategy is quite successful.

To get an idea of the performance, I started with the following chart which shows the expected returns (mean) over different time frames (from 1 to 8 days). The y-axis is the expected return (normalized to a daily return), while the x-axis is the DVI value (0 to 1 divided in 10 bins, using 0.1 as a step).


The utility code to produce this chart is available here and is interesting on its own – I love R! The chart was created using the following code:


getSymbols("SPY", from="1900-01-01")
aa = dvi.analysis(Cl(SPY["/2009"]), lags=seq(1,8), normalize=T, file.path="dvi.png")

My plan is to use 2010 onwards as out-of-sample set, that’s why these analysis are performed ending 2009.

Now, what clues this chart provides for trading? As expected (by the performance of the basic strategy), 0.5 seems like a good entry point. The returns are consistently positive below and negative above. The long positions (below 0.5) observe quite nice, although not perfect, improvement the lower the value of the indicator. Thus, one may consider strategies which increase the position size as the indicator moves lower.

Things are even more interesting for short positions. The most striking observation is that the returns are positive (losing trades) for DVI measurements higher than 0.9! That seems to suggest that once the value passes 0.9, exiting a short, or even entering a long, may improve the returns. Even the prior bar (at DVI of 0.8) is not that big on the 1-day portion – it may make sense to exit shorts even earlier. If you wonder how does this make sense – well, when the market trends up, it simply keeps pushing. I have seen a few of these short trades this year – quite painful.

If we switch from mean to median, the chart is completely different.


getSymbols("SPY", from="1900-01-01")
aa = dvi.analysis(Cl(SPY["/2009"]), lags=seq(1,8), normalize=T, file.path="dvi.median.png", func=median)

Positive returns for almost all values. What this means, is that when we go short, money are made not by increasing the winning number of short trades, but their relative win. This can be confirmed from the raw data returned by the function:


getSymbols("SPY", from="1900-01-01")
aa = dvi.analysis(Cl(SPY["/2009"]), lags=seq(1,8), normalize=T, file.path="dvi.median.png", func=mean)

bb = na.exclude(aa$raw.res$'1'$'0.6')

# result 209

# result 207

Are the results statistically significant? Not sure how to measure that, but … From the observation that the number of short and long trades is approximately the same even for winning shorts on average, we can conclude that the system is not much different than a coin toss in terms of successful trades. However:


getSymbols("SPY", from="1900-01-01")
aa = dvi.analysis(Cl(SPY["/2009"]), lags=seq(1,8), normalize=T, file.path="dvi.median.png", func=mean)

bb = na.exclude(aa$raw.res$'1'$'0.6')
cc = as.numeric(na.exclude(aa$rets$"1"))

t.test(bb, mu=mean(cc), conf.level=0.99, alternative="less")

# 	One Sample t-test
# data:  bb
# t = -2.9498, df = 415, p-value = 0.00168
# alternative hypothesis: true mean is less than 0.0002968406
# 99 percent confidence interval:
#           -Inf -2.223589e-05
# sample estimates:
#    mean of x 
# -0.001234948

# The above could be interpreted that the probability of obtaining a sample
# with this mean is very, very low.

res = 0
for(ii in 1:1000) {
   if(mean(sample(cc, size=NROW(bb)) < mean(bb)) {
      res = res + 1
# The result is consistently below 10, or less than 1%

Pretty interesting, right? My conclusion is that the DVI is really, really good on the SPY.


  1. Robert Young says:

    The problem: markets, modulo predicting gross money flows, are moved by events. Gray swans, if you will. Mr. Market, and his better known progeny such as the S&P, are moving as they are only because of the QE monies flowing from the Fed. The quantitative data don’t embed any knowledge of such events.

  2. mr says:

    Very interesting article.

    Spurred me to try to duplicate your process. I am very new to R, and I realize this is not a forum for R, but when I run your :

    aa = dvi.analysis… line, I get the error:
    Error in hasTsp(x) : attempt to set an attribute on NULL

    When I show traceback in RStudio it shows the following:
    lag.default(indicator, k = roc.n)
    lag(indicator, k = roc.n)
    na.exclude(lag(indicator, k = roc.n))
    merge(na.exclude(lag(indicator, k = roc.n)), na.exclude(rets), at dvi.analysis.r#9
    prepare.indicator(close, ind, roc.n = ll, normalize = normalize, at dvi.analysis.r#41
    dvi.analysis(Cl(SPY[“/2009”]), lags = seq(1, 8), normalize = T,

    > sessionInfo()
    R version 3.0.2 (2013-09-25)
    Platform: x86_64-w64-mingw32/x64 (64-bit)

    [1] LC_COLLATE=English_United States.1252 LC_CTYPE=English_United States.1252
    [3] LC_MONETARY=English_United States.1252 LC_NUMERIC=C
    [5] LC_TIME=English_United States.1252

    attached base packages:
    [1] stats graphics grDevices utils datasets methods base

    other attached packages:
    [1] quantmod_0.4-0 TTR_0.22-0.1 xts_0.9-7 zoo_1.7-10 Defaults_1.1-1

    loaded via a namespace (and not attached):
    [1] grid_3.0.2 lattice_0.20-23 tools_3.0.2

    Any idea why?


    1. ivannp says:

      No idea – there are small differences between the package versions you and I use, but I don’t think that’s the reason. Also, I am using Linux. The stack suggests that lag(indicator) returns NULL, or a type that na.exclude cannot use. What I would do, is to modify the function to return indicator, once it’s computed, and then try to repro running the failing commands one by one.

      1. mr says:

        Found it!

        I’m too new to R to understand why, but the problem had to do with referencing the 3rd column in the DVI assignment .

        When I replaced:
        ind = TTR:::DVI(close)$dvi


        It worked.

        Here is my debugging:
        > ind = TTR:::DVI(close)$dvi
        > names(ind)
        > t=DVI(close)
        > names(t)
        [1] “..1” “..2” “e1”
        > ind=DVI(close)[,3]
        > names(ind)
        [1] “e1”

        There doesn’t seem to be a colum “dvi”, rather it is “e1” ????

        BTW I’m running on windows.

        1. ivannp says:

          Thanks for letting me know – someone else might run into the same problem. However, are you sure the correct DVI function is invoked? Are you using the latest TTR package?

          require(quantmod) # loads TTR as well
          getSymbols(“SPY”, from=”1900-01-01″)
          # [1] “dvi.mag” “dvi.str” “dvi”

          I can’t imagine the colnames are different on windows, but who knows.

  3. hoojammyflip says:

    Nice work. This is very interesting. At worst, you have not uncovered anything, and there is no harm in that. Nothing ventured, nothing gained, you have to break a few eggs to make an omelette. However, its piqued my interest. Although technical analysis is voodoo mythology it may lead to anomalies which are significant. One significant thing I would note is that you are using daily data. How valid are the opening or closing prices? Maybe you could split data from say 1980-2013 into segments, testing every 5th day, training on the other 4. Just run the analysis on the whole dataset and then subset, removing every 5th day from the results and use those as your test data. Train on the remaining 80% of the data. This means that your training and test data automagically overlap. You can break the data out into years. Would be interesting to see whether something like this was profitable in the 80’s and isn’t so much nowadays. Best of luck.

  4. Mike Hager says:

    Is there a way we can get a real time DVI value just before market close in order to make any necessary trading positions?

    1. ivannp says:

      A way there is. The DVI indicator is perfect for the approach described in I think I posted code that does this pre-computation in parallel, but it might have been for another indicator. I may add something like a “trigger price” on the SPY page on my blog, but that will need some work and server time. Last but not least, I don’t recommend trading the DVI indicator in any way. 🙂

Leave a Reply