Skip to content

Note

This post is kept for historical reasons. The indicator and sample have been updated in the sources and PivotPoint can now auto-couple itself, removing boilerplate for the user code.

A new post will be written referencing this one. Meanwhile, please check the updated sample in the sources.

Pivot Point and Cross Plotting

An interesting request came across:

  • PivotPoint

It is interesting because of how the indicator is defined. Literature can be found at PivotPoint at StockCharts. PivotPoints use the close, high and low prices of a past timeframe. For example for the daily timeframe:

  • Pivot Points for daily charts use the prior month’s data

This may seem troublesome because for each timeframe there needs to be a definition of what other timeframe has to be used. Looking at the formulas opens up another question:

Pivot Point (P) = (High + Low + Close)/3
Support 1 (S1) = (P x 2) - High
Support 2 (S2) = P  -  (High  -  Low)
Resistance 1 (R1) = (P x 2) - Low
Resistance 2 (R2) = P + (High  -  Low)

Even if the text is full of references to prior period and past … the formulas seem to reference the current point in time. Let’s follow the advice of the text and use previous in our first attempt at the PivotPoint. But first let’s tackle the problem of the different timeframes by doing this:

  • The indicator will not tackle the problem

Although this may seem confusing, one has to take into account that indicators have to remain as stupid as possible and be made up of actual formulas. The problem will be solved as follows:

data = btfeeds.ADataFeed(..., timeframe=bt.TimeFrame.Days)
cerebro.adddata(data)
cerebro.resampledata(data, timeframe=bt.TimeFrame.Months)

And later in the Strategy:

class MyStrategy(bt.Strategy):
    def __init__(self):
        self.pp = PivotPoint(self.data1)  # the resampled data

Now it’s clear. The system will have the data, plus an extra input resampled to the needed timeframe. And the PivotPoint indicator will work with the resampled data, which is already in the needed monthly timeframe for the original data timeframe which is daily.

The indicator can be developed. Let’s start by following the text indications and not the formulas and look back 1 period.

class PivotPoint1(bt.Indicator):
    lines = ('p', 's1', 's2', 'r1', 'r2',)

    def __init__(self):
        h = self.data.high(-1)  # previous high
        l = self.data.low(-1)  # previous low
        c = self.data.close(-1)  # previous close

        self.lines.p = p = (h + l + c) / 3.0

        p2 = p * 2.0
        self.lines.s1 = p2 - h  # (p x 2) - high
        self.lines.r1 = p2 - l  # (p x 2) - low

        hilo = h - l
        self.lines.s2 = p - hilo  # p - (high - low)
        self.lines.r2 = p + hilo  # p + (high - low)

The strategy will look at parameter usepp1 to use this PivotPoint1

    def __init__(self):
        if self.p.usepp1:
            self.pp = PivotPoint1(self.data1)
        else:
            self.pp = PivotPoint(self.data1)

And the output is controlled by a simple next method

    def next(self):
        txt = ','.join(
            ['%04d' % len(self),
             '%04d' % len(self.data0),
             '%04d' % len(self.data1),
             self.data.datetime.date(0).isoformat(),
             '%.2f' % self.pp[0]])

        print(txt)

Let’s execute:

./ppsample --usepp1

And the output:

0041,0041,0002,2005-02-28,2962.79
0042,0042,0002,2005-03-01,2962.79
...

Immediately something is clear: index 41 already belongs to the 2nd month. That means we have skipped calculation of the indicator by 1 month. It is now clear why the text in StockCharts is always mentioning that the calculation happens with the previous month but the formulas seem to reference the current moment.

  • The developers probably faced the same design decisions with multiple datas with multiple timeframes.

    At the current daily point, only the closed bar from the previous month can be delivered.

That’s why the next method is looking at index [0]. All this has a very easy fix and that’s writing the formulas just like StockCharts documents them.

class PivotPoint(bt.Indicator):
    lines = ('p', 's1', 's2', 'r1', 'r2',)
    plotinfo = dict(subplot=False)

    def __init__(self):
        h = self.data.high  # current high
        l = self.data.low  # current high
        c = self.data.close  # current high

        self.lines.p = p = (h + l + c) / 3.0

        p2 = p * 2.0
        self.lines.s1 = p2 - h  # (p x 2) - high
        self.lines.r1 = p2 - l  # (p x 2) - low

        hilo = h - l
        self.lines.s2 = p - hilo  # p - (high - low)
        self.lines.r2 = p + hilo  # p + (high - low)

An execution without usepp1:

./ppsample

And the new output is:

0021,0021,0001,2005-01-31,2962.79
0022,0022,0001,2005-02-01,2962.79
...

Et voilá! The 1st month had 20 trading days and once complete the indicator has calculated the values and can be delivered. The only printed line is p and if the value is the same in the 2 lines is because the value remains fixed for the entire next month. Quoting StockCharts:

Once Pivot Points are set, they do not change and remain in play throughout ...

The indicator can already be used. Let’s go for plotting. A plotting parameter has already been set

    plotinfo = dict(subplot=False)

The calculated values are in line with the data scale and just like a Moving Average it can be plotted along the data (hence subplot=False)

An execution with --plot:

./ppsample --plot

!image

The blistering barnacles are attacking again. The indicator has been plotted on the monthly data (its source), which gives no visual indication on the daily chart, where it would be really helpful.

But backtrader supports cross-plotting from one data to another. Although a small addition in 1.2.8.88 was needed to support cross-plotting to a data of a different timeframe.

This is achieved by having plotmaster say which the plot target is, by adding it to the plotinfo attribute of the indicator:

./ppsample --plot --plot-on-daily

!image

The visual feedback is now useful to understand what PivotPoint is offering.

Script Code and Usage

Available as sample in the sources of backtrader:

$ ./ppsample.py --help
usage: ppsample.py [-h] [--data DATA] [--usepp1] [--plot] [--plot-on-daily]

Sample for pivot point and cross plotting

optional arguments:
  -h, --help       show this help message and exit
  --data DATA      Data to be read in (default:
                   ../../datas/2005-2006-day-001.txt)
  --usepp1         Have PivotPoint look 1 period backwards (default: False)
  --plot           Plot the result (default: False)
  --plot-on-daily  Plot the indicator on the daily data (default: False)

The code for PivotPoint

from __future__ import (absolute_import, division, print_function,)
#                        unicode_literals)

import backtrader as bt


class PivotPoint1(bt.Indicator):
    lines = ('p', 's1', 's2', 'r1', 'r2',)

    def __init__(self):
        h = self.data.high(-1)  # previous high
        l = self.data.low(-1)  # previous low
        c = self.data.close(-1)  # previous close

        self.lines.p = p = (h + l + c) / 3.0

        p2 = p * 2.0
        self.lines.s1 = p2 - h  # (p x 2) - high
        self.lines.r1 = p2 - l  # (p x 2) - low

        hilo = h - l
        self.lines.s2 = p - hilo  # p - (high - low)
        self.lines.r2 = p + hilo  # p + (high - low)


class PivotPoint(bt.Indicator):
    lines = ('p', 's1', 's2', 'r1', 'r2',)
    plotinfo = dict(subplot=False)

    def __init__(self):
        h = self.data.high  # current high
        l = self.data.low  # current high
        c = self.data.close  # current high

        self.lines.p = p = (h + l + c) / 3.0

        p2 = p * 2.0
        self.lines.s1 = p2 - h  # (p x 2) - high
        self.lines.r1 = p2 - l  # (p x 2) - low

        hilo = h - l
        self.lines.s2 = p - hilo  # p - (high - low)
        self.lines.r2 = p + hilo  # p + (high - low)

The code for the script.

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import argparse

import backtrader as bt
import backtrader.feeds as btfeeds
import backtrader.utils.flushfile

from pivotpoint import PivotPoint, PivotPoint1


class St(bt.Strategy):
    params = (('usepp1', False),
              ('plot_on_daily', False))

    def __init__(self):
        if self.p.usepp1:
            self.pp = PivotPoint1(self.data1)
        else:
            self.pp = PivotPoint(self.data1)

        if self.p.plot_on_daily:
            self.pp.plotinfo.plotmaster = self.data0

    def next(self):
        txt = ','.join(
            ['%04d' % len(self),
             '%04d' % len(self.data0),
             '%04d' % len(self.data1),
             self.data.datetime.date(0).isoformat(),
             '%.2f' % self.pp[0]])

        print(txt)


def runstrat():
    args = parse_args()

    cerebro = bt.Cerebro()
    data = btfeeds.BacktraderCSVData(dataname=args.data)
    cerebro.adddata(data)
    cerebro.resampledata(data, timeframe=bt.TimeFrame.Months)

    cerebro.addstrategy(St,
                        usepp1=args.usepp1,
                        plot_on_daily=args.plot_on_daily)
    cerebro.run()
    if args.plot:
        cerebro.plot(style='bar')


def parse_args():
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
        description='Sample for pivot point and cross plotting')

    parser.add_argument('--data', required=False,
                        default='../../datas/2005-2006-day-001.txt',
                        help='Data to be read in')

    parser.add_argument('--usepp1', required=False, action='store_true',
                        help='Have PivotPoint look 1 period backwards')

    parser.add_argument('--plot', required=False, action='store_true',
                        help=('Plot the result'))

    parser.add_argument('--plot-on-daily', required=False, action='store_true',
                        help=('Plot the indicator on the daily data'))

    return parser.parse_args()


if __name__ == '__main__':
    runstrat()