This week we have a Backtrader port of a built-in Tradingview Indicator. Although a simple indicator, the port addresses a fundamental concept that is often used in Pine-script but may not be so obvious how to emulate in Backtrader. This is especially true if like me, you tend to dive in head first and do not pay enough attention to the docs. So without further ado, let’s take a look at the Klinger Volume Oscillator (KVO).
The Klinger Volume Oscillator is (as with so many technical indicators) named after it’s inventor/developer Stephen J. Klinger. Internet research suggests that Mr Klinger wanted to develop an indicator that was able to identify long-term money flow trends but also be sensitive enough to capture short-term reversals in price. Source: https://www.multicharts.com/support/base/volume-based-gt-klinger-volume-oscillator/
OHLCVdata sets, volume provides the best indicator of money flow. It is likely due to this reason that it became the central focus of this oscillator. For example, if you have more buying volume than selling, the price will go up. Conversely, if you have more selling volume than buying, the price will go down. In other words, volume IS the measure of money flows into/out of an asset.
The Klinger Volume Oscillator has two main lines, the KVO line and a Signal line. The KVO line is generated by subtracting a slow-moving EMA from a fast-moving EMA (Exponential Moving Average). Volume data is used to feed into the two EMA’s but it is tweaked slightly before adding it. If the price is increasing then the volume is added as a positive value. If the price is falling, then the volume is added as a negative value.
The signal line is simply a 13 period EMA of the KVO line. This is similar to how a Stochastic works.
The following bullets describe some common ways to interpret the KVO Oscillator.
- KVO and Signal Crossovers: Since we have two lines, an obvious option would be to go long when the KVO crossed above the signal line and go short when the KVO line crosses under the signal line.
- Zero Crossovers: When the KVO or Signal lines (you pick) crosses over the zero line.
- Divergence: When price is making a new high or low but the KVO line is not making a new high/low.
- Measure the strength of the movement, aka the “volume force” as confirmation to determine if a price movement has conviction.
Copyright (c) 2018 backtest-rookies.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
PINE SCRIPT EXAMPLE
sv = change(hlc3) >= 0 ? volume : -volume
kvo = ema(sv, 34) - ema(sv, 55)
sig = ema(kvo, 13)
plot(sig, color = green)
import backtrader as bt
from datetime import datetime
lines = ('sig','kvo')
params = (('kvoFast',34),('kvoSlow',55),('sigPeriod',13))
self.plotinfo.plotyhlines = 
self.data.hlc3 = (self.data.high + self.data.low + self.data.close) / 3
# This works - Note indexing should be () rather than 
# See: https://www.backtrader.com/docu/concepts.html#lines-delayed-indexing
self.data.sv = bt.If((self.data.hlc3(0) - self.data.hlc3(-1)) / self.data.hlc3(-1) >=0, self.data.volume, -self.data.volume)
self.lines.kvo = bt.indicators.EMA(self.data.sv, period=self.p.kvoFast) - bt.indicators.EMA(self.data.sv, period=self.p.kvoSlow)
self.lines.sig = bt.indicators.EMA(self.lines.kvo, period=self.p.sigPeriod)
self.KOsc = KlingerOsc(self.data)
#Create an instance of cerebro
cerebro = bt.Cerebro()
#Add our strategy
#Get Apple data from Yahoo Finance.
data = bt.feeds.Quandl(
fromdate = datetime(2016,1,1),
todate = datetime(2017,1,1),
#Add the data to Cerebro
# Run over everything
#Finally plot the end results
All of the magic for this indicator happens in the
__init__method. We can take advantage of Backtrader’s ability to create data feeds and lines in
__init__ without needing to call
next()at all. One concept that is really straightforward in Tradingview is to reference a previous value from the same data feed. This is also simple in Backtrader BUT you need to read the documentation and if you are new, you might not know what to search for.
To emulate Tradingview, we cannot use conventional Backtrader indexing here. I.e
self.data.hlc3[-1]. If you do this, an index error will be raised. This is something I missed when developing the indicator. The trick is to deliver a delayed line to the calculation using
() instead of
. For more information on this see here:
Finally, to determine the volume force, the indicator looks at changes in the typical price to determine whether we have buying or selling volume. We use a
hlc3 value and compare it with the previous bar’s
hlc3value to determine the change. This would be rather than looking at the difference between the
close values.If the typical price is rising, the volume force is positive and is therefore added to the formula. If the typical price is falling, the volume is negative and as such, subtracted.
On The Chart