Backtrader: On Balance Volume

The code in today’s post contains an “On Balance Volume” indicator for use in Backtrader that can be extracted and used in your own projects.

On Balance Volume

On balance volume, or OBV for short is a volume based indicator that looks at close prices to arrive at a final calculation. Investopedia provides a nice little summary and historical background to the indicator.

On-balance volume (OBV) is a momentum indicator that uses volume flow to predict changes in stock price. Joseph Granville first developed the OBV metric in the 1960s. He believed that when volume increases sharply without a significant change in the stock’s price, the price will eventually jump upward, and vice versa.

On balance volume always builds on its own previous value. In other words, it is a cumulative indicator. If the close price increases, then volume is added to the previous indicator value. If the close price decreases volumeis subtracted from the previous indicator value. It should be noted that it compares the current close value with the close[-1] (previous close) and not the current open value vs current close value. This means that we can actually have a green candle day and still deduct volume. An example of this might be caused when price gaps down and closes up.

If you are looking for strategy ideas, head over to stockcharts.com. The have an OBV article which gives some great tips on how to interpret the indicator, flag divergences and confirm Trends.

Further Reading

  1. Wikipedia: https://en.wikipedia.org/wiki/On-balance_volume
  2. Investopedia: https://www.investopedia.com/terms/o/onbalancevolume.asp
  3. Stock Charts: https://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:on_balance_volume_obv

The Code

'''
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.
'''

import backtrader as bt
from datetime import datetime

class OnBalanceVolume(bt.Indicator):
    '''
    REQUIREMENTS
    ----------------------------------------------------------------------
    Investopedia:
    ----------------------------------------------------------------------
    https://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:on_balance_volume_obv

    1. If today's closing price is higher than yesterday's closing price,
       then: Current OBV = Previous OBV + today's volume

    2. If today's closing price is lower than yesterday's closing price,
       then: Current OBV = Previous OBV - today's volume

    3. If today's closing price equals yesterday's closing price,
       then: Current OBV = Previous OBV
    ----------------------------------------------------------------------
    '''

    alias = 'OBV'
    lines = ('obv',)

    plotlines = dict(
        obv=dict(
            _name='OBV',
            color='purple',
            alpha=0.50
        )
    )

    def __init__(self):

        # Plot a horizontal Line
        self.plotinfo.plotyhlines = [0]

    def nextstart(self):
        # We need to use next start to provide the initial value. This is because
        # we do not have a previous value for the first calcuation. These are
        # known as seed values.

        # Create some aliases
        c = self.data.close
        v = self.data.volume
        obv = self.lines.obv

        if c[0] > c[-1]:
            obv[0] = v[0]

        elif c[0] < c[-1]: obv[0] = -v[0] else: obv[0] = 0 def next(self): # Aliases to avoid long lines c = self.data.close v = self.data.volume obv = self.lines.obv if c[0] > c[-1]:
            obv[0] = obv[-1] + v[0]
        elif c[0] < c[-1]:
            obv[0] = obv[-1] - v[0]
        else:
            obv[0] = obv[-1]


class testStrategy(bt.Strategy):

    def __init__(self):
        self.obv = OnBalanceVolume(self.data)

    def next(self):
        print("{}: Prev: {}: Close: {}, Volume: {} OBV: {}".format(
            self.data.datetime.date(),
            self.data.close[-1],
            self.data.close[0],
            self.data.volume[0],
            self.obv.lines.obv[0]))

#Create an instance of cerebro
cerebro = bt.Cerebro()

#Add our strategy
cerebro.addstrategy(testStrategy)

#Get Apple data from Quandl.
data = bt.feeds.Quandl(
    dataname='AAPL',
    fromdate = datetime(2016,1,1),
    todate = datetime(2017,1,1),
    buffered= True,
    )

#Add the data to Cerebro
cerebro.adddata(data)

# Run over everything
cerebro.run()

#Finally plot the end results
cerebro.plot(style='candlestick')

Commentary

The requirements for the indicator are very simple. However, there is one thing that makes this indicator a little trickier to implement. This is the is the fact it is a cumulative indicator. I.e. The OBVline builds on its previous value. As such we cannot construct the OBVline within __init__(). Instead, we need to build the line on each call of next(). Further, during the first calculation, we do not have a previous value to add or subtract our volume to/from. As such, we need to use the nextstart()method (function) to provide a seed value. A seed value is just another way of saying first value. This allows us to kick-start the indicator. The official docs describe nextstart()as:

This method will be called once, exactly when the minimum period for all datas/indicators have been meet. The default behavior is to call next

In our Indicator, when nextstart()is called, we use a slightly different calculation. We just assign volume as a positive or negative value without adding it to the previous value.

See this excellent blog post for more information on building this type of indicator: https://www.backtrader.com/blog/posts/2018-01-27-recursive-indicators/recursive-indicator.html

Quandl Data: Please note that the data in this post uses the Quandl API. Using this API without an API key means that you are limited to the number calls you can make per day. To have unlimited access to their free data, sign up for a free API key and then add the apikeykeyword to the Quandl data call like so:

data = bt.feeds.Quandl( 
    dataname='AAPL', 
    fromdate = datetime(2016,1,1), 
    todate = datetime(2017,1,1),
    apikey="INSERT YOUR API KEY"
    )

Reference: https://www.quandl.com/?modal=register

Why are the values different to platform xyz?

If you compare the results of this indicator with another platform such as Tradingview, the indicator values are unlikely to match. This is because

  1. The OHLC data may not be exactly the same. For example for edge cases, one platform could show an asset closed slightly up whilst another data closed down or flat.
  2. More importantly, the values are cumulative. They build on the previous line. As such, the data on the two platforms must start on the exact same day to match properly.

However, having said that, we can get a general feel for whether we have garbage output. Take a look at the following comparison between the indicator and Tradingview.

On Balance Volume Comparison between Backtrader and Tradingview

In the screenshot, we can see that although the values reported are different the form/shape of the lines match up quite nicely.

Find This Post Useful?

If this post saved you time and effort, please consider support the site! There are many ways to support us and some won’t even cost you a penny. 

Backtest Rookies is a registered with Brave publisher!

Brave users can drop us a tip.

Alternatively, support us by switching to Brave using this referral link and we will receive some BAT! 


https://brave.com/bac691

Referral Link

Enjoying the content and thinking of subscribing to Tradingview? Support this site by clicking the referral link before you sign up! 

https://tradingview.go2cloud.org/SHpB


Donate with PayPal using any payment method you are comfortable with! 


3HxNVyh5729ieTfPnTybrtK7J7QxJD9gjD

0x9a2f88198224d59e5749bacfc23d79507da3d431

M8iUBnb8KamHR18gpgi2sSG7jopddFJi8S