Module backtrader.filters.renko

Expand source code
#!/usr/bin/env python
# -*- coding: utf-8; py-indent-offset:4 -*-
###############################################################################
#
# Copyright (C) 2015-2023 Daniel Rodriguez
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
###############################################################################
from __future__ import (absolute_import, division, print_function,
                        unicode_literals)


from . import Filter


__all__ = ['Renko']


class Renko(Filter):
    '''Modify the data stream to draw Renko bars (or bricks)

    Params:

      - ``hilo`` (default: *False*) Use high and low instead of close to decide
        if a new brick is needed

      - ``size`` (default: *None*) The size to consider for each brick

      - ``autosize`` (default: *20.0*) If *size* is *None*, this will be used
        to autocalculate the size of the bricks (simply dividing the current
        price by the given value)

      - ``dynamic`` (default: *False*) If *True* and using *autosize*, the size
        of the bricks will be recalculated when moving to a new brick. This
        will of course eliminate the perfect alignment of Renko bricks.

      - ``align`` (default: *1.0*) Factor use to align the price boundaries of
        the bricks. If the price is for example *3563.25* and *align* is
        *10.0*, the resulting aligned price will be *3560*. The calculation:

          - 3563.25 / 10.0 = 356.325
          - round it and remove the decimals -> 356
          - 356 * 10.0 -> 3560

      - ``roundstart`` (default: *True*)  If *True*, round the initial start
        value to int. Else keep the original value, which should aid when
        backtesting penny stocks

    See:
      - http://stockcharts.com/school/doku.php?id=chart_school:chart_analysis:renko

    '''

    params = (
        ('hilo', False),
        ('size', None),
        ('autosize', 20.0),
        ('dynamic', False),
        ('align', 1.0),
        ('roundstart', True),
    )

    def nextstart(self, data):
        o = data.open[0]
        o = round(o / self.p.align, 0) * self.p.align  # aligned
        self._size = self.p.size or float(o // self.p.autosize)
        if self.p.roundstart:
            o = int(o)

        self._top = o + self._size
        self._bot = o - self._size

    def next(self, data):
        c = data.close[0]
        h = data.high[0]
        l = data.low[0]

        if self.p.hilo:
            hiprice = h
            loprice = l
        else:
            hiprice = loprice = c

        if hiprice >= self._top:
            # deliver a renko brick from top -> top + size
            self._bot = bot = self._top

            if self.p.size is None and self.p.dynamic:
                self._size = float(c // self.p.autosize)
                top = bot + self._size
                top = round(top / self.p.align, 0) * self.p.align  # aligned
            else:
                top = bot + self._size

            self._top = top

            data.open[0] = bot
            data.low[0] = bot
            data.high[0] = top
            data.close[0] = top
            data.volume[0] = 0.0
            data.openinterest[0] = 0.0
            return False  # length of data stream is unaltered

        elif loprice <= self._bot:
            # deliver a renko brick from bot -> bot - size
            self._top = top = self._bot

            if self.p.size is None and self.p.dynamic:
                self._size = float(c // self.p.autosize)
                bot = top - self._size
                bot = round(bot / self.p.align, 0) * self.p.align  # aligned
            else:
                bot = top - self._size

            self._bot = bot

            data.open[0] = top
            data.low[0] = top
            data.high[0] = bot
            data.close[0] = bot
            data.volume[0] = 0.0
            data.openinterest[0] = 0.0
            return False  # length of data stream is unaltered

        data.backwards()
        return True  # length of stream was changed, get new bar

Classes

class Renko (data)

Modify the data stream to draw Renko bars (or bricks)

Params

  • hilo (default: False) Use high and low instead of close to decide if a new brick is needed

  • size (default: None) The size to consider for each brick

  • autosize (default: 20.0) If size is None, this will be used to autocalculate the size of the bricks (simply dividing the current price by the given value)

  • dynamic (default: False) If True and using autosize, the size of the bricks will be recalculated when moving to a new brick. This will of course eliminate the perfect alignment of Renko bricks.

  • align (default: 1.0) Factor use to align the price boundaries of the bricks. If the price is for example 3563.25 and align is 10.0, the resulting aligned price will be 3560. The calculation:

    • 3563.25 / 10.0 = 356.325
    • round it and remove the decimals -> 356
    • 356 * 10.0 -> 3560
  • roundstart (default: True) If True, round the initial start value to int. Else keep the original value, which should aid when backtesting penny stocks

See

Expand source code
class Renko(Filter):
    '''Modify the data stream to draw Renko bars (or bricks)

    Params:

      - ``hilo`` (default: *False*) Use high and low instead of close to decide
        if a new brick is needed

      - ``size`` (default: *None*) The size to consider for each brick

      - ``autosize`` (default: *20.0*) If *size* is *None*, this will be used
        to autocalculate the size of the bricks (simply dividing the current
        price by the given value)

      - ``dynamic`` (default: *False*) If *True* and using *autosize*, the size
        of the bricks will be recalculated when moving to a new brick. This
        will of course eliminate the perfect alignment of Renko bricks.

      - ``align`` (default: *1.0*) Factor use to align the price boundaries of
        the bricks. If the price is for example *3563.25* and *align* is
        *10.0*, the resulting aligned price will be *3560*. The calculation:

          - 3563.25 / 10.0 = 356.325
          - round it and remove the decimals -> 356
          - 356 * 10.0 -> 3560

      - ``roundstart`` (default: *True*)  If *True*, round the initial start
        value to int. Else keep the original value, which should aid when
        backtesting penny stocks

    See:
      - http://stockcharts.com/school/doku.php?id=chart_school:chart_analysis:renko

    '''

    params = (
        ('hilo', False),
        ('size', None),
        ('autosize', 20.0),
        ('dynamic', False),
        ('align', 1.0),
        ('roundstart', True),
    )

    def nextstart(self, data):
        o = data.open[0]
        o = round(o / self.p.align, 0) * self.p.align  # aligned
        self._size = self.p.size or float(o // self.p.autosize)
        if self.p.roundstart:
            o = int(o)

        self._top = o + self._size
        self._bot = o - self._size

    def next(self, data):
        c = data.close[0]
        h = data.high[0]
        l = data.low[0]

        if self.p.hilo:
            hiprice = h
            loprice = l
        else:
            hiprice = loprice = c

        if hiprice >= self._top:
            # deliver a renko brick from top -> top + size
            self._bot = bot = self._top

            if self.p.size is None and self.p.dynamic:
                self._size = float(c // self.p.autosize)
                top = bot + self._size
                top = round(top / self.p.align, 0) * self.p.align  # aligned
            else:
                top = bot + self._size

            self._top = top

            data.open[0] = bot
            data.low[0] = bot
            data.high[0] = top
            data.close[0] = top
            data.volume[0] = 0.0
            data.openinterest[0] = 0.0
            return False  # length of data stream is unaltered

        elif loprice <= self._bot:
            # deliver a renko brick from bot -> bot - size
            self._top = top = self._bot

            if self.p.size is None and self.p.dynamic:
                self._size = float(c // self.p.autosize)
                bot = top - self._size
                bot = round(bot / self.p.align, 0) * self.p.align  # aligned
            else:
                bot = top - self._size

            self._bot = bot

            data.open[0] = top
            data.low[0] = top
            data.high[0] = bot
            data.close[0] = bot
            data.volume[0] = 0.0
            data.openinterest[0] = 0.0
            return False  # length of data stream is unaltered

        data.backwards()
        return True  # length of stream was changed, get new bar

Ancestors

Class variables

var frompackages
var packages
var params

Methods

def next(self, data)
Expand source code
def next(self, data):
    c = data.close[0]
    h = data.high[0]
    l = data.low[0]

    if self.p.hilo:
        hiprice = h
        loprice = l
    else:
        hiprice = loprice = c

    if hiprice >= self._top:
        # deliver a renko brick from top -> top + size
        self._bot = bot = self._top

        if self.p.size is None and self.p.dynamic:
            self._size = float(c // self.p.autosize)
            top = bot + self._size
            top = round(top / self.p.align, 0) * self.p.align  # aligned
        else:
            top = bot + self._size

        self._top = top

        data.open[0] = bot
        data.low[0] = bot
        data.high[0] = top
        data.close[0] = top
        data.volume[0] = 0.0
        data.openinterest[0] = 0.0
        return False  # length of data stream is unaltered

    elif loprice <= self._bot:
        # deliver a renko brick from bot -> bot - size
        self._top = top = self._bot

        if self.p.size is None and self.p.dynamic:
            self._size = float(c // self.p.autosize)
            bot = top - self._size
            bot = round(bot / self.p.align, 0) * self.p.align  # aligned
        else:
            bot = top - self._size

        self._bot = bot

        data.open[0] = top
        data.low[0] = top
        data.high[0] = bot
        data.close[0] = bot
        data.volume[0] = 0.0
        data.openinterest[0] = 0.0
        return False  # length of data stream is unaltered

    data.backwards()
    return True  # length of stream was changed, get new bar
def nextstart(self, data)
Expand source code
def nextstart(self, data):
    o = data.open[0]
    o = round(o / self.p.align, 0) * self.p.align  # aligned
    self._size = self.p.size or float(o // self.p.autosize)
    if self.p.roundstart:
        o = int(o)

    self._top = o + self._size
    self._bot = o - self._size