Module backtrader.analyzers.logreturnsrolling
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)
import collections
import math
import backtrader as bt
__all__ = ['LogReturnsRolling']
class LogReturnsRolling(bt.TimeFrameAnalyzerBase):
'''This analyzer calculates rolling returns for a given timeframe and
compression
Params:
- ``timeframe`` (default: ``None``)
If ``None`` the ``timeframe`` of the 1st data in the system will be
used
Pass ``TimeFrame.NoTimeFrame`` to consider the entire dataset with no
time constraints
- ``compression`` (default: ``None``)
Only used for sub-day timeframes to for example work on an hourly
timeframe by specifying "TimeFrame.Minutes" and 60 as compression
If ``None`` then the compression of the 1st data of the system will be
used
- ``data`` (default: ``None``)
Reference asset to track instead of the portfolio value.
.. note:: this data must have been added to a ``cerebro`` instance with
``addata``, ``resampledata`` or ``replaydata``
- ``firstopen`` (default: ``True``)
When tracking the returns of a ``data`` the following is done when
crossing a timeframe boundary, for example ``Years``:
- Last ``close`` of previous year is used as the reference price to
see the return in the current year
The problem is the 1st calculation, because the data has** no
previous** closing price. As such and when this parameter is ``True``
the *opening* price will be used for the 1st calculation.
This requires the data feed to have an ``open`` price (for ``close``
the standard [0] notation will be used without reference to a field
price)
Else the initial close will be used.
- ``fund`` (default: ``None``)
If ``None`` the actual mode of the broker (fundmode - True/False) will
be autodetected to decide if the returns are based on the total net
asset value or on the fund value. See ``set_fundmode`` in the broker
documentation
Set it to ``True`` or ``False`` for a specific behavior
Methods:
- get_analysis
Returns a dictionary with returns as values and the datetime points for
each return as keys
'''
params = (
('data', None),
('firstopen', True),
('fund', None),
)
def start(self):
super(LogReturnsRolling, self).start()
if self.p.fund is None:
self._fundmode = self.strategy.broker.fundmode
else:
self._fundmode = self.p.fund
self._values = collections.deque([float('Nan')] * self.compression,
maxlen=self.compression)
if self.p.data is None:
# keep the initial portfolio value if not tracing a data
if not self._fundmode:
self._lastvalue = self.strategy.broker.getvalue()
else:
self._lastvalue = self.strategy.broker.fundvalue
def notify_fund(self, cash, value, fundvalue, shares):
if not self._fundmode:
self._value = value if self.p.data is None else self.p.data[0]
else:
self._value = fundvalue if self.p.data is None else self.p.data[0]
def _on_dt_over(self):
# next is called in a new timeframe period
if self.p.data is None or len(self.p.data) > 1:
# Not tracking a data feed or data feed has data already
vst = self._lastvalue # update value_start to last
else:
# The 1st tick has no previous reference, use the opening price
vst = self.p.data.open[0] if self.p.firstopen else self.p.data[0]
self._values.append(vst) # push values backwards (and out)
def next(self):
# Calculate the return
super(LogReturnsRolling, self).next()
self.rets[self.dtkey] = math.log(self._value / self._values[0])
self._lastvalue = self._value # keep last value
Classes
class LogReturnsRolling (*args, **kwargs)
-
This analyzer calculates rolling returns for a given timeframe and compression
Params
timeframe
(default:None
) IfNone
thetimeframe
of the 1st data in the system will be used
Pass
TimeFrame.NoTimeFrame
to consider the entire dataset with no time constraintscompression
(default:None
)
Only used for sub-day timeframes to for example work on an hourly timeframe by specifying "TimeFrame.Minutes" and 60 as compression
If
None
then the compression of the 1st data of the system will be useddata
(default:None
)
Reference asset to track instead of the portfolio value.
!!! note "Note: this data must have been added to a
cerebro
instance with"addata
,resampledata
orreplaydata
firstopen
(default:True
)
When tracking the returns of a
data
the following is done when crossing a timeframe boundary, for exampleYears
:- Last <code>close</code> of previous year is used as the reference price to see the return in the current year
The problem is the 1st calculation, because the data has no previous closing price. As such and when this parameter is
True
the opening price will be used for the 1st calculation.This requires the data feed to have an
open
price (forclose
the standard [0] notation will be used without reference to a field price)Else the initial close will be used.
fund
(default:None
)
If
None
the actual mode of the broker (fundmode - True/False) will be autodetected to decide if the returns are based on the total net asset value or on the fund value. Seeset_fundmode
in the broker documentationSet it to
True
orFalse
for a specific behaviorMethods
- get_analysis
Returns a dictionary with returns as values and the datetime points for each return as keys
Expand source code
class LogReturnsRolling(bt.TimeFrameAnalyzerBase): '''This analyzer calculates rolling returns for a given timeframe and compression Params: - ``timeframe`` (default: ``None``) If ``None`` the ``timeframe`` of the 1st data in the system will be used Pass ``TimeFrame.NoTimeFrame`` to consider the entire dataset with no time constraints - ``compression`` (default: ``None``) Only used for sub-day timeframes to for example work on an hourly timeframe by specifying "TimeFrame.Minutes" and 60 as compression If ``None`` then the compression of the 1st data of the system will be used - ``data`` (default: ``None``) Reference asset to track instead of the portfolio value. .. note:: this data must have been added to a ``cerebro`` instance with ``addata``, ``resampledata`` or ``replaydata`` - ``firstopen`` (default: ``True``) When tracking the returns of a ``data`` the following is done when crossing a timeframe boundary, for example ``Years``: - Last ``close`` of previous year is used as the reference price to see the return in the current year The problem is the 1st calculation, because the data has** no previous** closing price. As such and when this parameter is ``True`` the *opening* price will be used for the 1st calculation. This requires the data feed to have an ``open`` price (for ``close`` the standard [0] notation will be used without reference to a field price) Else the initial close will be used. - ``fund`` (default: ``None``) If ``None`` the actual mode of the broker (fundmode - True/False) will be autodetected to decide if the returns are based on the total net asset value or on the fund value. See ``set_fundmode`` in the broker documentation Set it to ``True`` or ``False`` for a specific behavior Methods: - get_analysis Returns a dictionary with returns as values and the datetime points for each return as keys ''' params = ( ('data', None), ('firstopen', True), ('fund', None), ) def start(self): super(LogReturnsRolling, self).start() if self.p.fund is None: self._fundmode = self.strategy.broker.fundmode else: self._fundmode = self.p.fund self._values = collections.deque([float('Nan')] * self.compression, maxlen=self.compression) if self.p.data is None: # keep the initial portfolio value if not tracing a data if not self._fundmode: self._lastvalue = self.strategy.broker.getvalue() else: self._lastvalue = self.strategy.broker.fundvalue def notify_fund(self, cash, value, fundvalue, shares): if not self._fundmode: self._value = value if self.p.data is None else self.p.data[0] else: self._value = fundvalue if self.p.data is None else self.p.data[0] def _on_dt_over(self): # next is called in a new timeframe period if self.p.data is None or len(self.p.data) > 1: # Not tracking a data feed or data feed has data already vst = self._lastvalue # update value_start to last else: # The 1st tick has no previous reference, use the opening price vst = self.p.data.open[0] if self.p.firstopen else self.p.data[0] self._values.append(vst) # push values backwards (and out) def next(self): # Calculate the return super(LogReturnsRolling, self).next() self.rets[self.dtkey] = math.log(self._value / self._values[0]) self._lastvalue = self._value # keep last value
Ancestors
Class variables
var frompackages
var packages
var params
Methods
def on_dt_over(self)
-
Expand source code
def _on_dt_over(self): # next is called in a new timeframe period if self.p.data is None or len(self.p.data) > 1: # Not tracking a data feed or data feed has data already vst = self._lastvalue # update value_start to last else: # The 1st tick has no previous reference, use the opening price vst = self.p.data.open[0] if self.p.firstopen else self.p.data[0] self._values.append(vst) # push values backwards (and out)
Inherited members