Login Page - Create Account

Support Board


Date/Time: Fri, 29 Mar 2024 07:53:01 +0000



[User Discussion] - Offering To The Community: Klinger Volume Oscillator With Filters

View Count: 5563

[2015-07-13 04:04:18]
bjohnson777 (Brett Johnson) - Posts: 284
Since I've been studying volume indicators the past few weeks, I took on the Klinger Volume Oscillator (KVO) request in another thread. It has some interesting properties that might help me create my own. After many hours of searching bad web pages and banging my head against the table, I've finally been able to produce the PROPER KVO version. It seems EVERYONE else gets it wrong for some reason. I'll explain more in the next post. I request KVO be added to the main SC distro once its checked over.

From the "Display Study Documentation" button:

Klinger Volume Oscillator With Filters (KVO). A price and volume based oscillator developed by Stephen J. Klinger to handle both short term and long term time frame analysis of money flowing into and out of a security. It's output is very similar to MACD and can be traded in the same way. It is somewhat unique as its algorithm keeps track of current direction and accounts for it in the calculations. Klinger defined volume as a force behind price movement and accumulation/distribution as the overall volume change from period to period. He labeled it Volume Force. A positive Volume Force is more accumulation than distribution, a negative volume force is more distribution than accumulation. As a trend starts to end, the Volume Force should noticeably reduce before the price reversal. Klinger has some guidelines for his oscillator. The most reliable signals from KVO are in the direction of the current price trend so don't trade against the trend. KVO divergence from current price action is a trend weakening signal (similar to MACD divergence). Klinger uses a separate 89 period EMA for price to determine the current price trend. An up trend is price above EMA89, and a down trend is price below EMA89. In an up trend, if KVO tops then down crosses the trigger line, that's a signal that the trend is near completion (invert that for a down trend). Klinger recommends other indicators like Stochastic, RSI, and MACD to help confirm movement into over bought/sold conditions.

Brett Johnson's Programming Rant: Most people are totally clueless about how to properly implement this indicator. Initially I thought I found a web page that properly explained it, but continued searching over the next couple hours proved me very wrong. I've found several variations that are incorrect. I've found some web pages with 3 different versions written in 3 different stock chart languages. I've found a couple web pages showing the proper algorithm in math form (well, mostly), but then they screw up the code several lines down. Nobody seems to pay attention. Some people seem to give up and implement a simplified version. If you're trying to program this indicator yourself and are stuck, check my source code. It's open and excessively documented. There's no reason for a simple algorithm to be this screwed up on so many other platforms and web pages.

This KVO version has multiple smoothing options to handle an extremely noisy indicator.

This KVO version has a Volume Filter option to handle cases when a volume spike smashes the indicator making it hard to read. Large institutions sometimes do this to hide their activity afterward. The "Straight" option passes volume data through without any filtering. The "Log" option runs each volume bar through the log function making a type of "log scale". The "Square Root" option runs each volume bar through the square root function making a type of "square root scale". The "MA Clip" option will clip volume above a chosen moving average. The final Smoothing option will apply a moving average to take some of the noise out of the final OBV line.

To use KVO in its original form, turn off all extra options and use the default values.

Observations. To say that this is a noisy indicator is an understatement. Turn on MA Clip 3 and all the filters to their default values (or higher) to greatly clean this up. In addition to the recommended indicators, also add trend lines as a price shouldn't be traded until it breaks the trend line. Always remember, just because KVO moves doesn't mean that price will move with it. This indicator doesn't do very well with ranging data. To be fair, that's true of many indicators. This indicator was originally developed in the mid 1990's for daily bars that almost always had volume. Low time scale intraday bars (like 1 minute) with 0 volume will make the KVO line very noisy. Zoom out time wise until all the bars have enough volume for a clean signal.

-----
Minor update 2018-10-15. No major function changes.
Regular compiles moved to "Brett Johnson's Standard Tool Kit" DLL.
Offering To The Community: Brett Johnson's Standard Tool Kit
Date Time Of Last Edit: 2018-10-15 13:36:45
attachmentKlingerVolumeOscillatorWithFilters.cpp - Attached On 2018-10-15 13:36:16 UTC - Size: 20.42 KB - 581 views
attachmentKlingerVolumeOscillatorWithFilters_64.dll - Attached On 2018-10-15 13:36:25 UTC - Size: 846 KB - 462 views
[2015-07-13 04:05:48]
bjohnson777 (Brett Johnson) - Posts: 284
I'm creating this extra post for the archives so it can be found by web searches.

I should have been finished programming the KVO in about an hour if everyone else would have done their research correctly. Reading my "Programming Rant" from above, it's not hard to feel my frustration.

If you're trying to fix or program your own KVO, you're in luck. This is the only version that's properly implemented from the start to finish. This is also the only version with extensive smoothing to clean up the signals... and it's open sourced with easy to understand documentation.

The various versions have a lot of little screw up in the peripheral setup code, but the worst has to do with the Volume Force Scaling Factor. I've documented the problems in the source code. Here's the main piece of that section.


//Do the KVO calculations.
  
//Typical Price handled by In_InputData and Price[] already. Using "High+Low+Close" without the "divide by 3" can create false movements.
//Variable names cleaned up for clarity and understanding:
//DM[] or Daily Measurement = HighLow[]
//CM or Cumulative Measurement = HighLowSum
//ScalingFactor = that part of the VolumeForce equation where a lot of people screw up. Watch the parenthesis everyone. Order of operation is very important here. The absolute value of ScalingFactor is designed to be above 1.0 most of the time. The longer the run, the higher ScalingFactor gets to 2.0.
//The WRONG Scaling Factor: abs(2.0 * (HighLow[sc.Index]/HighLowSum) -1.0); The range on this one is mostly 0.0 to 1.0. A value of 0.0 in a running average will whiplash the MA line. 0 vales are generally useless to everyone.
//The right Scaling Factor: abs(2.0 * ((HighLow[sc.Index]/HighLowSum) -1.0)); The range on this one is mostly 1.0 to 2.0. This means the values are passed through ranging from untouched to doubled. The whiplash potential is much less and the signal will be cleaner.

//Load HighLow array.
HighLow[sc.Index] = sc.High[sc.Index] - sc.Low[sc.Index];

//not enough data yet to finish the calculation.
if(sc.Index < 1) {return;}
  
//Determine if the current trend direction.
if(Price[sc.Index] == Price[sc.Index-1]) {Trend = TrendPrevious;} //no trend, leave it unchanged
else if(Price[sc.Index] > Price[sc.Index-1]) {Trend = 1;} //up trend
else {Trend = -1;} //down trend

//Handle price summation.
if(Trend == TrendPrevious) {HighLowSum += HighLow[sc.Index];} //continue trending
else { //trend reversal
  HighLowSum = HighLow[sc.Index-1] + HighLow[sc.Index]; //initialize with the previous and current values
  TrendPrevious = Trend; //register trend change
  }

/*
Scaling Factor divide by 0 problems. These should be rare but can happen on low volume securities. This is poorly addressed in all the documentation, so it needs to be analyzed in depth.
Under normal circumstances, HighLowSum is initialized with 2 bars of HighLow[] and should keep increasing with each additional bar until reversal. The HighLow[i]/HighLowSum number is a fraction that should always be getting smaller until the next reversal because of the constantly increasing denominator.
Real world data shows the Scaling Factor range to mostly be between 1.0 and 2.0 with the number approaching 2.0 the longer it goes without a reversal.
If HighLowSum is 0.0, then there was probably a recent reversal reset on junk data.
To find out what the Scaling Factor would be in general after a reversal, set up a test with boring data, in this case 1.0 for the 2 previous HighLow[] bars:
ScalingFactor = abs(2.0 * ((1.0/(1.0+1.0)) - 1.0)) = 1.0
We already know the range is mostly 1.0-2.0 and generally starts out low. The value of 1.0 is a good choice for "pass through and don't mess with it".
To find out what the Scaling Factor would be with a whiplash reset, set up another test with 1.0 and 0.1:
ScalingFactor = abs(2.0 * ((0.1/(1.0+0.1)) - 1.0)) = 1.8
This starts out very high in the range. A near full boost starting out on garbage amplifying a bad signal is a bad idea.
To find out what the Scaling Factor would be with the previous whiplashed flipped around, set up another test with 0.1 and 1.0:
ScalingFactor = abs(2.0 * ((1.0/(0.1+1.0)) - 1.0)) = 0.18
This damps the output and would bring the final equation value down close to 0.0. Unnecessary zeros in a moving average cause whiplash. This isn't a very good choice either.
If we take the middle of the last 2 extremes, we get 0.8. This isn't far from 1.0 in the boring example. In general, it looks like setting Scaling Factor to 1.0 on bad data and just letting it pass through would probably be the most logical choice.
Keep in mind there's always the possibility volume will be 0, thus zeroing out the equation and handling the problem by itself.
*/

//Calculate a proper Scaling Factor
if(HighLowSum == 0.0) {ScalingFactor = 1.0;} //handle any divide by 0 problems
else {
  ScalingFactor = 2.0 * ((HighLow[sc.Index]/HighLowSum) -1.0);
  if(ScalingFactor < 0.0) {ScalingFactor *= -1.0;} //ScalingFactor must be positive.    
  }
  
//Do the VolumeForce Calculation
VolumeForce[sc.Index] = VolumeFiltered[sc.Index] * ScalingFactor * Trend * In_OutputMultiplier.GetFloat();

[2015-07-13 11:37:18]
crazybears - Posts: 314
Hi Bjohnson777

thanks for all of your studies posted , great job
[2016-10-15 06:49:48]
bjohnson777 (Brett Johnson) - Posts: 284
Today's DLL was compiled with the M$VC++ change over.
You may need to update your SC version.
Keep your previous DLL version until you've tested the new compile.
Most changes were made to shut up useless M$VC++ warnings when compiling.
There are a few compile warnings left about "argument" that can be ignored.
No real functionality changes have been made.
[2018-10-15 13:37:21]
bjohnson777 (Brett Johnson) - Posts: 284
Fixed source code to cleanly compile with 1820. No functionality change.

To post a message in this thread, you need to log in with your Sierra Chart account:

Login

Login Page - Create Account