backtrader.analyzers.timereturn 源代码

#!/usr/bin/env python
"""TimeReturn Analyzer Module - Time-based returns calculation.

This module provides the TimeReturn analyzer for calculating returns
over specified time periods.

Classes:
    TimeReturn: Analyzer that calculates returns by timeframe.

Example:
    >>> cerebro = bt.Cerebro()
    >>> cerebro.addanalyzer(bt.analyzers.TimeReturn, timeframe=bt.TimeFrame.Years)
"""

from ..analyzer import TimeFrameAnalyzerBase


[文档] class TimeReturn(TimeFrameAnalyzerBase): """This analyzer calculates the Returns by looking at the beginning and end of the timeframe Params: - ``timeframe`` (default: ``None``) If ``None`` the ``timeframe`` of the first 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 first data in 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 `data` the following is done when crossing a timeframe boundary, for example, ``Years``: - Last ``close`` the previous year is used as the reference price to see the return in the current year The problem is the first 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 first calculation. This requires the data feed to have an ``open`` price (for ``close`` the standard [0] notations will be used without a reference to a field price) Else the initial close will be used. # When calculating returns for the first period, whether to use the first opening price. If parameter is False, the first closing price 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 """ # Parameters params = ( ("data", None), ("firstopen", True), ("fund", None), ) # __init__ method to support parameter initialization after metaclass removal
[文档] def __init__(self, *args, **kwargs): """Initialize the TimeReturn analyzer. Args: *args: Positional arguments. **kwargs: Keyword arguments for analyzer parameters. """ super().__init__(*args, **kwargs)
# Start
[文档] def start(self): """Initialize the analyzer at the start of the backtest. Sets the fund mode and initializes the starting value for return calculations. """ super().start() if self.p.fund is None: self._fundmode = self.strategy.broker.fundmode else: self._fundmode = self.p.fund # PERFORMANCE OPTIMIZATION: Cache p.data reference (called 691K+ times) self._p_data = self.p.data # Start value self._value_start = 0.0 # End value self._lastvalue = None # If parameter data is None 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
# Notify fund information
[文档] def notify_fund(self, cash, value, fundvalue, shares): """Update current value based on fund notification. Args: cash: Current cash amount. value: Current portfolio value. fundvalue: Current fund value. shares: Number of fund shares. """ # PERFORMANCE OPTIMIZATION: Use cached _p_data reference p_data = self._p_data if not self._fundmode: # Record current value if p_data is None: self._value = value # the portofolio value if tracking no data else: self._value = p_data[0] # the data value if tracking data else: if p_data is None: self._value = fundvalue # the fund value if tracking no data else: self._value = p_data[0] # the data value if tracking data
# On datetime over
[文档] def on_dt_over(self): """Handle timeframe boundary crossing. Updates the starting value for return calculation when crossing into a new timeframe period. """ # next is called in a new timeframe period # PERFORMANCE OPTIMIZATION: Use cached _p_data reference p_data = self._p_data if p_data is None or self._lastvalue is not None: self._value_start = self._lastvalue # update value_start to last else: # The 1st tick has no previous reference, use the opening price if self.p.firstopen: self._value_start = p_data.open[0] else: self._value_start = p_data[0]
# Call next
[文档] def next(self): """Calculate and store the return for the current period. Calculates the return as (current_value / start_value) - 1 and stores it in the results dictionary keyed by datetime. """ # Calculate the return super().next() # self.dtkey is an attribute set in analyzer, usually the end date of a period self.rets[self.dtkey] = (self._value / self._value_start) - 1.0 # self.rets[self.dtkey] = (float(self._value) / float(self._value_start)) - 1.0 self._lastvalue = self._value # keep last value