# VCI — The Value Charts Indicator

So recently, I was made known of the Value Charts Indicator , which was supposed to be some form of alternative to the RSI. I decided to investigate it, and see if it’s worth using.

Before diving into a strategy, here’s how the indicator works:

```"VCI" <- function(OHLC, nLookback=40, nRange=8, pctRank=FALSE) {
if(nLookback > 7) {
varA <- runMax(Hi(OHLC), nRange) - runMin(Lo(OHLC), nRange)
varB <- lag(varA, nRange+1)
varC <- lag(varA, nRange*2)
varD <- lag(varA, nRange*3)
varE <- lag(varA, nRange*4)
LRange <- (varA+varB+varC+varD+varE)/25
}
if(nLookback <=7) {
absDiff <- abs(diff(Cl(OHLC)))
dailyRange <- Hi(OHLC) - Lo(OHLC)
tmp <- cbind(absDiff, dailyRange)
maxTmp <- pmax(tmp)
LRange <- SMA(maxTmp, 5)*.16
}
hilo <- (Hi(OHLC)+Lo(OHLC))/2
VO <- (Op(OHLC)-SMA(hilo, nLookback))/LRange
VH <- (Hi(OHLC)-SMA(hilo, nLookback))/LRange
VL <- (Lo(OHLC)-SMA(hilo, nLookback))/LRange
VC <- (Cl(OHLC)-SMA(hilo, nLookback))/LRange
out <- cbind(VO=VO, VH=VH, VL=VL, VC=VC)
colnames(out) <- c("VO", "VH", "VL", "VC")
return(out)
}
```

Long story short, if the lookback period is 8 bars or more, it is something akin to an average of various five lagged ranges, over five times the specified range. That is, define your first range computation as the difference between the highest high and lowest low, and then average that with that same computation lagged by nRange+1 bars, nRange*2 bars, and so on. At a shorter frame than 8 bars (that is, a special case), the computation is a moving average of the daily maximum between the daily range and the close-to-close range (E.G. with a high of 4 and low of 2, with close of 3 and previous close of 2, that daily value will be equal to 4-2=2), and then take a 5 period SMA of that, and multiply by .16. Although the initial indicator had the range dependent on the lookback period, I chose to decouple it for greater flexibility to the user.

This range calculation is then used as a denominator of a computation that is the difference of the current price minus the SMA value of the average of an (H+L)/2 price proxy. In short, it’s a variation on a theme of the classical z-score from statistics. In other words, (X-Xbar)/(normalizing value).

This z-score is computed for all four price strands.

In my current implementation, I have not yet implemented the functionality for zero-movement bars (though that can be done by request) if anyone sees value with this indicator.

To put this indicator through its paces, I threw about as plain-standard-vanilla strategy around it. The strategy activates upon the close price greater than SMA200 (the “conventional wisdom”), and buys when the indicator crosses under -2 and exits above 2, using a lookback period of 10 days, with a range period of 2 days (the settings the indicator creator(s) had in mind were that -4/+4 was relatively oversold/overbought, with -8/+8 being extremely so). The idea here was to get a bunch of relatively short-term trades going, and use the advantage of large numbers to see how well this indicator performs.

Here’s the strategy code:

```require(IKTrading)
require(quantstrat)
require(PerformanceAnalytics)

initDate="1990-01-01"
from="2003-01-01"
to=as.character(Sys.Date())
options(width=70)

source("demoData.R")

#trade sizing and initial equity settings

strategy.st <- portfolio.st <- account.st <- "VCI_test"
rm.strat(portfolio.st)
rm.strat(strategy.st)
initPortf(portfolio.st, symbols=symbols, initDate=initDate, currency='USD')
initAcct(account.st, portfolios=portfolio.st, initDate=initDate, currency='USD',initEq=initEq)
initOrders(portfolio.st, initDate=initDate)
strategy(strategy.st, store=TRUE)

#parameters
pctATR=.02
period=10

nRange=2
nLookback=10
pctRank=FALSE

sellThresh=2

nSMA=200

#indicators
arguments=list(HLC=quote(HLC(mktdata)), n=period),
label="atrX")

arguments=list(OHLC=quote(OHLC(mktdata)), nLookback=nLookback,
nRange=nRange, pctRank=pctRank),
label="vci")

arguments=list(x=quote(Cl(mktdata)), n=nSMA),
label="sma")

#signals
arguments=list(columns=c("Close", "SMA.sma"), relationship="gt"),
label="filter")

relationship="lt", cross=FALSE),
label="VCIltThresh")

arguments=list(columns=c("filter", "VCIltThresh"), cross=TRUE),
label="longEntry")

arguments=list(column="VC.vci", threshold=sellThresh,
relationship="gt", cross=TRUE),
label="longExit")

arguments=list(columns=c("Close", "SMA.sma"), relationship="lt"),
label="filterExit")

#rules
arguments=list(sigcol="longEntry", sigval=TRUE, ordertype="market",
orderside="long", replace=FALSE, prefer="Open", osFUN=osDollarATR,
type="enter", path.dep=TRUE)

arguments=list(sigcol="longExit", sigval=TRUE, orderqty="all", ordertype="market",
orderside="long", replace=FALSE, prefer="Open"),
type="exit", path.dep=TRUE)

arguments=list(sigcol="filterExit", sigval=TRUE, orderqty="all", ordertype="market",
orderside="long", replace=FALSE, prefer="Open"),
type="exit", path.dep=TRUE)

#apply strategy
t1 <- Sys.time()
out <- applyStrategy(strategy=strategy.st,portfolios=portfolio.st)
t2 <- Sys.time()
print(t2-t1)

#set up analytics
updatePortf(portfolio.st)
dateRange <- time(getPortfolio(portfolio.st)\$summary)[-1]
updateAcct(portfolio.st,dateRange)
updateEndEq(account.st)
```

And here are the results:

```> (aggPF <- sum(tStats\$Gross.Profits)/-sum(tStats\$Gross.Losses))
[1] 1.684617
> (aggCorrect <- mean(tStats\$Percent.Positive))
[1] 69.466
[1] 2801
> (meanAvgWLR <- mean(tStats\$Avg.WinLoss.Ratio[tStats\$Avg.WinLoss.Ratio < Inf], na.rm=TRUE))
[1] 0.753

> print(t(durStats))
[,1]
Min      1
Q1       5
Med      9
Mean    11
Q3      14
Max     57
WMin     1
WQ1      5
WMed     8
WMean    9
WQ3     12
WMax    41
LMin     1
LQ1      5
LMed    15
LMean   15
LQ3     22
LMax    57

> SharpeRatio.annualized(portfRets)
[,1]
Annualized Sharpe Ratio (Rf=0%) 0.8951308
> Return.annualized(portfRets)
[,1]
Annualized Return 0.06821319
> maxDrawdown(portfRets)
[1] 0.108064

> round(apply.yearly(dailyRetComparison, Return.cumulative),3)
strategy    SPY
2003-12-31    0.058  0.066
2004-12-31    0.056  0.079
2005-12-30    0.034  0.025
2006-12-29    0.148  0.132
2007-12-31    0.094  0.019
2008-12-31   -0.022 -0.433
2009-12-31    0.149  0.192
2010-12-31   -0.055  0.110
2011-12-30    0.072 -0.028
2012-12-31    0.072  0.126
2013-12-31    0.057  0.289
2014-08-22    0.143  0.075
> round(apply.yearly(dailyRetComparison, SharpeRatio.annualized),3)
strategy    SPY
2003-12-31    2.379  3.641
2004-12-31    0.751  0.706
2005-12-30    0.476  0.238
2006-12-29    2.083  1.312
2007-12-31    0.909  0.123
2008-12-31   -0.943 -1.050
2009-12-31    2.023  0.719
2010-12-31   -0.548  0.614
2011-12-30    0.854 -0.122
2012-12-31    1.015  0.990
2013-12-31    0.655  2.594
2014-08-22    2.869  1.137
> round(apply.yearly(dailyRetComparison, maxDrawdown),3)
strategy   SPY
2003-12-31    0.014 0.025
2004-12-31    0.079 0.085
2005-12-30    0.058 0.074
2006-12-29    0.068 0.077
2007-12-31    0.073 0.102
2008-12-31    0.029 0.520
2009-12-31    0.041 0.280
2010-12-31    0.108 0.167
2011-12-30    0.052 0.207
2012-12-31    0.043 0.099
2013-12-31    0.072 0.062
2014-08-22    0.047 0.058
```

In short, it has the statistical profile of a standard mean-reverting strategy–lots of winners, losers slightly larger than winners, losers last longer in the market than winners as well. In terms of Sharpe Ratio, it’s solid but not exactly stellar. Overall, the strategy generally sports much better risk control than the raw SPY, but the annualized return to drawdown ratio isn’t quite up to the same level as some strategies tested on this blog in the past.

This is the equity curve comparison.

The equity profile seems to be pretty standard fare–winners happen over time, but a drawdown can wipe some of them (but not all) pretty quickly, as the system continues to make new equity highs. Solid, but not stellar.

Here’s an example of the equity curve of an individual instrument (the usual XLB):

Something to note is that the indicator is fairly choppy, and does best in a strong uptrend, when terms like oversold, pullback, and so on, are actually that, as opposed to a change in trend, or a protracted cyclic downtrend in a sideways market.

Here’s a picture of the strategy on XLB in 2012.

As you can see, the indicator at the 2-bar range isn’t exactly the smoothest, but with proper position-sizing rules (I use position sizing based on a 10-day ATR), the disadvantage of chopping across a threshold can be largely mitigated.

OVerall, while this indicator doesn’t seem to be much better than the more conventional RSIs, it nevertheless seems to be an alternative, and for those that want to use it, it’s now in my IKTrading package.

## 17 thoughts on “VCI — The Value Charts Indicator”

1. http://nightlypatterns.wordpress.com
I enjoyed reading, what about trying to backtest this: half capital used with this new indicator and half with rsi, rebalanced at each new trade to see if we can get some equity curve improvement from diversification and Shannon effect. Do you know the correlation between the two indicators?
This is my blog:
http://nightlypatterns.wordpress.com

• That’s definitely something I’ll look at in the future. Good suggestion!

2. cidiel says:

thanks for sharing, oddly i get an error when just copy/paste your code and trying to run.. seems like there is a conflict of some kind with the indicator names, i just can’t seem to find it. interesting post nonetheless

• You need to be using the latest version of TTR from R-forge.

3. cidiel says:

already using latest TTR. think i figured it out though… when i ran above and looked at the mkt data it created the SMA column as SMA.200.sma, and your signals that are added are just SMA.sma if i change that to SMA.200.sma then everything runs fine. not sure why on mine it adds the number to the SMA column.. but i guess just a heads up if anyone else runs into similar problem.

• Thanks, it fixed the problem.

4. Vilius says:

Hi, I am just learning to use quantstrat and I don’t know how to plot equity curves of strategy and bentchmark (SPY in this case). Actually, I don’t even know how to see the daily Equity changes. Could you provide me with functions to plot equity curve comparison?

• There are multiple examples on my blog.

5. Flo123 says:

Hello Ilya,

I copy/past the code and add the demoData.R file but for some reason I have the following error. Is the code above deprecated ?

> out <- applyStrategy(strategy=strategy.st,portfolios=portfolio.st)
Error in if (length(j) == 0 || (length(j) == 1 && j == 0)) { :
missing value where TRUE/FALSE needed
1: In match.names(columns, colnames(data)) :
all columns not located in Close SMA.sma for SPY.Open SPY.High SPY.Low SPY.Close SPY.Volume SPY.Adjusted atr.atrX VO.vci VH.vci VL.vci VC.vci SPY.Close.SMA.200.sma
2: In min(j, na.rm = TRUE) :
no non-missing arguments to min; returning Inf
3: In max(j, na.rm = TRUE) :
no non-missing arguments to max; returning -Inf

• Flo123 says:

All indicators seems loaded correctly : lagATR, VCI and SMA.

• Flo123 says:

Sorry but I’ve set verbose and debug applyStrategy()’s option to TRUE but still I can’t figure out what the j variable is in: if (length(j) == 0 || (length(j) == 1 && j == 0)) {}
add.indicator() function work fine, how I could investigate this error ?
Thank you,

• You may want to update your TTR or quantstrat. This code works for me.

• Flo123 says:

Ok, thanks.