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).
Klinger Oscillator
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/
In OHLCV
data 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.
Breakdown
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.
Usage Ideas
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.
The Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
''' Author: www.backtest-rookies.com MIT License 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 SOFTWARE. ''' ''' PINE SCRIPT EXAMPLE //@version=3 study(title="Klinger Oscillator") sv = change(hlc3) >= 0 ? volume : -volume kvo = ema(sv, 34) - ema(sv, 55) sig = ema(kvo, 13) plot(kvo) plot(sig, color = green) ''' import backtrader as bt from datetime import datetime class KlingerOsc(bt.Indicator): lines = ('sig','kvo') params = (('kvoFast',34),('kvoSlow',55),('sigPeriod',13)) def __init__(self): self.plotinfo.plotyhlines = [0] self.addminperiod(55) 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) class testStrategy(bt.Strategy): def __init__(self): self.KOsc = KlingerOsc(self.data) #Create an instance of cerebro cerebro = bt.Cerebro() #Add our strategy cerebro.addstrategy(testStrategy) #Get Apple data from Yahoo Finance. data = bt.feeds.Quandl( dataname='AAPL', fromdate = datetime(2016,1,1), todate = datetime(2017,1,1), buffered= True, apikey="NBQhvwRzCgdB-6e7XNAD" ) #Add the data to Cerebro cerebro.adddata(data) # Run over everything cerebro.run() #Finally plot the end results cerebro.plot(style='candlestick') |
Code Commentary
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:
Source: https://www.backtrader.com/docu/concepts.html#lines-delayed-indexing
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 ahlc3
value and compare it with the previous bar’s hlc3
value to determine the change. This would be rather than looking at the difference between the open
andclose
or close
to 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.