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###############################################################################
21'''
22
23.. module:: lineroot
24
25Definition of the base class LineRoot and base classes LineSingle/LineMultiple
26to define interfaces and hierarchy for the real operational classes
27
28.. moduleauthor:: Daniel Rodriguez
29
30'''
31from __future__ import (absolute_import, division, print_function,
32                        unicode_literals)
33
34import operator
35
36from .utils.py3 import range, with_metaclass
37
38from . import metabase
39
40
41class MetaLineRoot(metabase.MetaParams):
42    '''
43    Once the object is created (effectively pre-init) the "owner" of this
44    class is sought
45    '''
46
47    def donew(cls, *args, **kwargs):
48        _obj, args, kwargs = super(MetaLineRoot, cls).donew(*args, **kwargs)
49
50        # Find the owner and store it
51        # startlevel = 4 ... to skip intermediate call stacks
52        ownerskip = kwargs.pop('_ownerskip', None)
53        _obj._owner = metabase.findowner(_obj,
54                                         _obj._OwnerCls or LineMultiple,
55                                         skip=ownerskip)
56
57        # Parameter values have now been set before __init__
58        return _obj, args, kwargs
59
60
61class LineRoot(with_metaclass(MetaLineRoot, object)):
62    '''
63    Defines a common base and interfaces for Single and Multiple
64    LineXXX instances
65
66        Period management
67        Iteration management
68        Operation (dual/single operand) Management
69        Rich Comparison operator definition
70    '''
71    _OwnerCls = None
72    _minperiod = 1
73    _opstage = 1
74
75    IndType, StratType, ObsType = range(3)
76
77    def _stage1(self):
78        self._opstage = 1
79
80    def _stage2(self):
81        self._opstage = 2
82
83    def _operation(self, other, operation, r=False, intify=False):
84        if self._opstage == 1:
85            return self._operation_stage1(
86                other, operation, r=r, intify=intify)
87
88        return self._operation_stage2(other, operation, r=r)
89
90    def _operationown(self, operation):
91        if self._opstage == 1:
92            return self._operationown_stage1(operation)
93
94        return self._operationown_stage2(operation)
95
96    def qbuffer(self, savemem=0):
97        '''Change the lines to implement a minimum size qbuffer scheme'''
98        raise NotImplementedError
99
100    def minbuffer(self, size):
101        '''Receive notification of how large the buffer must at least be'''
102        raise NotImplementedError
103
104    def setminperiod(self, minperiod):
105        '''
106        Direct minperiod manipulation. It could be used for example
107        by a strategy
108        to not wait for all indicators to produce a value
109        '''
110        self._minperiod = minperiod
111
112    def updateminperiod(self, minperiod):
113        '''
114        Update the minperiod if needed. The minperiod will have been
115        calculated elsewhere
116        and has to take over if greater that self's
117        '''
118        self._minperiod = max(self._minperiod, minperiod)
119
120    def addminperiod(self, minperiod):
121        '''
122        Add a minperiod to own ... to be defined by subclasses
123        '''
124        raise NotImplementedError
125
126    def incminperiod(self, minperiod):
127        '''
128        Increment the minperiod with no considerations
129        '''
130        raise NotImplementedError
131
132    def prenext(self):
133        '''
134        It will be called during the "minperiod" phase of an iteration.
135        '''
136        pass
137
138    def nextstart(self):
139        '''
140        It will be called when the minperiod phase is over for the 1st
141        post-minperiod value. Only called once and defaults to automatically
142        calling next
143        '''
144        self.next()
145
146    def next(self):
147        '''
148        Called to calculate values when the minperiod is over
149        '''
150        pass
151
152    def preonce(self, start, end):
153        '''
154        It will be called during the "minperiod" phase of a "once" iteration
155        '''
156        pass
157
158    def oncestart(self, start, end):
159        '''
160        It will be called when the minperiod phase is over for the 1st
161        post-minperiod value
162
163        Only called once and defaults to automatically calling once
164        '''
165        self.once(start, end)
166
167    def once(self, start, end):
168        '''
169        Called to calculate values at "once" when the minperiod is over
170        '''
171        pass
172
173    # Arithmetic operators
174    def _makeoperation(self, other, operation, r=False, _ownerskip=None):
175        raise NotImplementedError
176
177    def _makeoperationown(self, operation, _ownerskip=None):
178        raise NotImplementedError
179
180    def _operationown_stage1(self, operation):
181        '''
182        Operation with single operand which is "self"
183        '''
184        return self._makeoperationown(operation, _ownerskip=self)
185
186    def _roperation(self, other, operation, intify=False):
187        '''
188        Relies on self._operation to and passes "r" True to define a
189        reverse operation
190        '''
191        return self._operation(other, operation, r=True, intify=intify)
192
193    def _operation_stage1(self, other, operation, r=False, intify=False):
194        '''
195        Two operands' operation. Scanning of other happens to understand
196        if other must be directly an operand or rather a subitem thereof
197        '''
198        if isinstance(other, LineMultiple):
199            other = other.lines[0]
200
201        return self._makeoperation(other, operation, r, self)
202
203    def _operation_stage2(self, other, operation, r=False):
204        '''
205        Rich Comparison operators. Scans other and returns either an
206        operation with other directly or a subitem from other
207        '''
208        if isinstance(other, LineRoot):
209            other = other[0]
210
211        # operation(float, other) ... expecting other to be a float
212        if r:
213            return operation(other, self[0])
214
215        return operation(self[0], other)
216
217    def _operationown_stage2(self, operation):
218        return operation(self[0])
219
220    def __add__(self, other):
221        return self._operation(other, operator.__add__)
222
223    def __radd__(self, other):
224        return self._roperation(other, operator.__add__)
225
226    def __sub__(self, other):
227        return self._operation(other, operator.__sub__)
228
229    def __rsub__(self, other):
230        return self._roperation(other, operator.__sub__)
231
232    def __mul__(self, other):
233        return self._operation(other, operator.__mul__)
234
235    def __rmul__(self, other):
236        return self._roperation(other, operator.__mul__)
237
238    def __div__(self, other):
239        return self._operation(other, operator.__div__)
240
241    def __rdiv__(self, other):
242        return self._roperation(other, operator.__div__)
243
244    def __floordiv__(self, other):
245        return self._operation(other, operator.__floordiv__)
246
247    def __rfloordiv__(self, other):
248        return self._roperation(other, operator.__floordiv__)
249
250    def __truediv__(self, other):
251        return self._operation(other, operator.__truediv__)
252
253    def __rtruediv__(self, other):
254        return self._roperation(other, operator.__truediv__)
255
256    def __pow__(self, other):
257        return self._operation(other, operator.__pow__)
258
259    def __rpow__(self, other):
260        return self._roperation(other, operator.__pow__)
261
262    def __abs__(self):
263        return self._operationown(operator.__abs__)
264
265    def __neg__(self):
266        return self._operationown(operator.__neg__)
267
268    def __lt__(self, other):
269        return self._operation(other, operator.__lt__)
270
271    def __gt__(self, other):
272        return self._operation(other, operator.__gt__)
273
274    def __le__(self, other):
275        return self._operation(other, operator.__le__)
276
277    def __ge__(self, other):
278        return self._operation(other, operator.__ge__)
279
280    def __eq__(self, other):
281        return self._operation(other, operator.__eq__)
282
283    def __ne__(self, other):
284        return self._operation(other, operator.__ne__)
285
286    def __nonzero__(self):
287        return self._operationown(bool)
288
289    __bool__ = __nonzero__
290
291    # Python 3 forces explicit implementation of hash if
292    # the class has redefined __eq__
293    __hash__ = object.__hash__
294
295
296class LineMultiple(LineRoot):
297    '''
298    Base class for LineXXX instances that hold more than one line
299    '''
300    def reset(self):
301        self._stage1()
302        self.lines.reset()
303
304    def _stage1(self):
305        super(LineMultiple, self)._stage1()
306        for line in self.lines:
307            line._stage1()
308
309    def _stage2(self):
310        super(LineMultiple, self)._stage2()
311        for line in self.lines:
312            line._stage2()
313
314    def addminperiod(self, minperiod):
315        '''
316        The passed minperiod is fed to the lines
317        '''
318        # pass it down to the lines
319        for line in self.lines:
320            line.addminperiod(minperiod)
321
322    def incminperiod(self, minperiod):
323        '''
324        The passed minperiod is fed to the lines
325        '''
326        # pass it down to the lines
327        for line in self.lines:
328            line.incminperiod(minperiod)
329
330    def _makeoperation(self, other, operation, r=False, _ownerskip=None):
331        return self.lines[0]._makeoperation(other, operation, r, _ownerskip)
332
333    def _makeoperationown(self, operation, _ownerskip=None):
334        return self.lines[0]._makeoperationown(operation, _ownerskip)
335
336    def qbuffer(self, savemem=0):
337        for line in self.lines:
338            line.qbuffer(savemem=1)
339
340    def minbuffer(self, size):
341        for line in self.lines:
342            line.minbuffer(size)
343
344
345class LineSingle(LineRoot):
346    '''
347    Base class for LineXXX instances that hold a single line
348    '''
349    def addminperiod(self, minperiod):
350        '''
351        Add the minperiod (substracting the overlapping 1 minimum period)
352        '''
353        self._minperiod += minperiod - 1
354
355    def incminperiod(self, minperiod):
356        '''
357        Increment the minperiod with no considerations
358        '''
359        self._minperiod += minperiod
360