1#!/usr/bin/env python
2# -*- coding: utf-8; py-indent-offset:4 -*-
3###############################################################################
4#
5# Copyright (C) 2015, 2016, 2017 Daniel Rodriguez
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation, either version 3 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program.  If not, see <http://www.gnu.org/licenses/>.
19#
20###############################################################################
21from __future__ import (absolute_import, division, print_function,
22                        unicode_literals)
23
24
25from . import Filter
26
27
28__all__ = ['Renko']
29
30
31class Renko(Filter):
32    '''Modify the data stream to draw Renko bars (or bricks)
33
34    Params:
35
36      - ``hilo`` (default: *False*) Use high and low instead of close to decide
37        if a new brick is needed
38
39      - ``size`` (default: *None*) The size to consider for each brick
40
41      - ``autosize`` (default: *20.0*) If *size* is *None*, this will be used
42        to autocalculate the size of the bricks (simply dividing the current
43        price by the given value)
44
45      - ``dynamic`` (default: *False*) If *True* and using *autosize*, the size
46        of the bricks will be recalculated when moving to a new brick. This
47        will of course eliminate the perfect alignment of Renko bricks.
48
49      - ``align`` (default: *1.0*) Factor use to align the price boundaries of
50        the bricks. If the price is for example *3563.25* and *align* is
51        *10.0*, the resulting aligned price will be *3560*. The calculation:
52
53          - 3563.25 / 10.0 = 356.325
54          - round it and remove the decimals -> 356
55          - 356 * 10.0 -> 3560
56
57    See:
58      - http://stockcharts.com/school/doku.php?id=chart_school:chart_analysis:renko
59
60    '''
61
62    params = (
63        ('hilo', False),
64        ('size', None),
65        ('autosize', 20.0),
66        ('dynamic', False),
67        ('align', 1.0),
68    )
69
70    def nextstart(self, data):
71        o = data.open[0]
72        o = round(o / self.p.align, 0) * self.p.align  # aligned
73        self._size = self.p.size or float(o // self.p.autosize)
74        self._top = int(o) + self._size
75        self._bot = int(o) - self._size
76
77    def next(self, data):
78        c = data.close[0]
79        h = data.high[0]
80        l = data.low[0]
81
82        if self.p.hilo:
83            hiprice = h
84            loprice = l
85        else:
86            hiprice = loprice = c
87
88        if hiprice >= self._top:
89            # deliver a renko brick from top -> top + size
90            self._bot = bot = self._top
91
92            if self.p.size is None and self.p.dynamic:
93                self._size = float(c // self.p.autosize)
94                top = bot + self._size
95                top = round(top / self.p.align, 0) * self.p.align  # aligned
96            else:
97                top = bot + self._size
98
99            self._top = top
100
101            data.open[0] = bot
102            data.low[0] = bot
103            data.high[0] = top
104            data.close[0] = top
105            data.volume[0] = 0.0
106            data.openinterest[0] = 0.0
107            return False  # length of data stream is unaltered
108
109        elif loprice <= self._bot:
110            # deliver a renko brick from bot -> bot - size
111            self._top = top = self._bot
112
113            if self.p.size is None and self.p.dynamic:
114                self._size = float(c // self.p.autosize)
115                bot = top - self._size
116                bot = round(bot / self.p.align, 0) * self.p.align  # aligned
117            else:
118                bot = top - self._size
119
120            self._bot = bot
121
122            data.open[0] = top
123            data.low[0] = top
124            data.high[0] = bot
125            data.close[0] = bot
126            data.volume[0] = 0.0
127            data.openinterest[0] = 0.0
128            return False  # length of data stream is unaltered
129
130        data.backwards()
131        return True  # length of stream was changed, get new bar
132