backtrader.analyzers.sharpe 源代码

#!/usr/bin/env python
"""Sharpe Ratio Analyzer Module - Sharpe ratio calculation.

This module provides the SharpeRatio analyzer for calculating the Sharpe
ratio of a strategy using a risk-free asset.

Classes:
    SharpeRatio: Analyzer that calculates Sharpe ratio.
    SharpeRatio_Annual: Annualized Sharpe ratio analyzer.

Example:
    >>> cerebro = bt.Cerebro()
    >>> cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpe')
    >>> results = cerebro.run()
    >>> print(results[0].analyzers.sharpe.get_analysis())
"""

import math

from ..analyzer import Analyzer
from ..dataseries import TimeFrame
from ..mathsupport import average, standarddev
from ..metabase import OwnerContext
from ..utils.py3 import itervalues
from .annualreturn import AnnualReturn
from .timereturn import TimeReturn


[文档] class SharpeRatio(Analyzer): # Relatively speaking, backtrader's Sharpe ratio calculation method is quite complex, considering many parameters """This analyzer calculates the SharpeRatio of a strategy using a risk-free asset, which is simply an interest rate See also: - https://en.wikipedia.org/wiki/Sharpe_ratio Params: - ``timeframe``: (default: ``TimeFrame.Years``) # Trading period - ``compression`` (default: ``1``) # Specific trading period Only used for sub-day timeframes to, for example, work on an hourly timeframe by specifying "TimeFrame.Minutes" and 60 as compression - ``riskfreerate`` (default: 0.01 -> 1%) # Risk-free rate used for Sharpe ratio calculation Expressed in annual terms (see ``convertrate`` below) - ``convertrate`` (default: ``True``) # Whether to convert risk-free rate from annual to monthly, weekly, daily, no intraday support Convert the ``riskfreerate`` from annual to monthly, weekly or daily rate. Sub-day conversions are not supported - ``factor`` (default: ``None``) # If not specified, will convert according to specified date, 1 year = 12 months = 52 weeks = 252 trading days If ``None``, the conversion factor for the risk-free rate from *annual* to the chosen timeframe will be chosen from a predefined table Days: 252, Weeks: 52, Months: 12, Years: 1 Else the specified value will be used - ``annualize`` (default: ``False``) # If set to True, will convert to annualized return If ``convertrate`` is `True`, the *SharpeRatio* will be delivered in the ``timeframe`` of choice. On most occasions, the SharpeRatio is delivered in annualized form. Convert the ``riskfreerate`` from annual to monthly, weekly or daily rate. Sub-day conversions are not supported - ``stddev_sample`` (default: ``False``) # Whether to subtract 1 when calculating standard deviation If this is set to ``True`` the *standard deviation* will be calculated decreasing the denominator in the mean by ``1``. This is used when calculating the *standard deviation* if it's considered that not all samples are used for the calculation. This is known as the *Bessels' correction* - ``daysfactor`` (default: ``None``) # Legacy code Old naming for ``factor``. If set to anything else than ``None`` and the ``timeframe`` is ``TimeFrame.Days`` it will be assumed this is old code and the value will be used - ``legacyannual`` (default: ``False``) # Only applies to years, use annualized return analyzer Use the ``AnnualReturn`` return analyzer, which as the name implies only works for years - ``fund`` (default: ``None``) # Net asset mode or fund mode, by default will auto-detect 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 key "sharperatio" holding the ratio """ # Default parameters params = ( ("timeframe", TimeFrame.Years), ("compression", 1), ("riskfreerate", 0.01), ("factor", None), ("convertrate", True), ("annualize", False), ("stddev_sample", False), # old behavior ("daysfactor", None), ("legacyannual", False), ("fund", None), ) # Default date conversion RATEFACTORS = { TimeFrame.Days: 252, TimeFrame.Weeks: 52, TimeFrame.Months: 12, TimeFrame.Years: 1, }
[文档] def __init__(self, *args, **kwargs): """Initialize the SharpeRatio analyzer. Sets up the return analyzer based on the legacyannual parameter. Args: *args: Positional arguments. **kwargs: Keyword arguments for analyzer parameters. """ # CRITICAL FIX: Call super().__init__() first to initialize self.p super().__init__(*args, **kwargs) # If using years, get annualized return, otherwise get daily return # Use OwnerContext so child analyzers can find this as their parent if self.p.legacyannual: with OwnerContext.set_owner(self): self.anret = AnnualReturn() else: with OwnerContext.set_owner(self): self.timereturn = TimeReturn( timeframe=self.p.timeframe, compression=self.p.compression, fund=self.p.fund )
[文档] def stop(self): """Calculate and store the Sharpe ratio when analysis ends. Performs the following calculations: 1. Retrieves returns from the sub-analyzer 2. Converts risk-free rate if needed 3. Calculates excess returns 4. Computes the Sharpe ratio 5. Optionally annualizes the result """ super().stop() # Calculate returns and Sharpe ratio in annual units if self.p.legacyannual: rate = self.p.riskfreerate retavg = average([r - rate for r in self.anret.rets]) retdev = standarddev(self.anret.rets) # TODO: change self.ratio to ratio # self.ratio = retavg / retdev ratio = retavg / retdev # If not calculating returns and Sharpe ratio in annual units else: # Get the returns from the subanalyzer # Get daily returns returns = list(itervalues(self.timereturn.get_analysis())) # Risk-free rate rate = self.p.riskfreerate # # Date defaults to None factor = None # Hack to identify old code # Get specific factor date, if daily period and daysfactor is not None, set factor = daysfactor if self.p.timeframe == TimeFrame.Days and self.p.daysfactor is not None: factor = self.p.daysfactor # Otherwise, if factor parameter is not None, equal to factor parameter value, otherwise find from defined factors based on trading period # By default, factor should be 252 else: if self.p.factor is not None: factor = self.p.factor # user specified factor elif self.p.timeframe in self.RATEFACTORS: # Get the conversion factor from the default table factor = self.RATEFACTORS[self.p.timeframe] # If factor is not None, need to convert annual risk-free rate to daily risk-free rate by default, if using daily period if factor is not None: # A factor was found if self.p.convertrate: # Standard: downgrade annual returns to a timeframe factor rate = pow(1.0 + rate, 1.0 / factor) - 1.0 else: # Else upgrade returns to yearly returns returns = [pow(1.0 + x, factor) - 1.0 for x in returns] # Number of trading days lrets = len(returns) - self.p.stddev_sample # Check if the ratio can be calculated if lrets: # Get the excess returns - arithmetic mean - original sharpe # Calculate daily excess returns ret_free = [r - rate for r in returns] # Calculate average of daily excess returns ret_free_avg = average(ret_free) # Calculate standard deviation of daily excess returns retdev = standarddev(ret_free, avgx=ret_free_avg, bessel=self.p.stddev_sample) # ret_avg = average(returns) # retdev = standarddev(returns, avgx=ret_avg,bessel=self.p.stddev_sample) try: # Calculate Sharpe ratio ratio = ret_free_avg / retdev # If factor is not None, annual risk-free rate was converted to daily, and need to calculate annualized Sharpe ratio if factor is not None and self.p.convertrate and self.p.annualize: # Convert Sharpe ratio from daily to annual ratio = math.sqrt(factor) * ratio except (ValueError, TypeError, ZeroDivisionError): ratio = None else: # no returns or stddev_sample was active and 1 return ratio = None # TODO: self.ratio is not used here, just use ratio for assignment, can also improve speed # self.ratio = ratio # Save Sharpe ratio self.rets["sharperatio"] = ratio
[文档] class SharpeRatioA(SharpeRatio): """Extension of the SharpeRatio which returns the Sharpe Ratio directly in annualized form The following param has been changed from `SharpeRatio` - ``annualize`` (default: ``True``) """ # Calculate annualized Sharpe ratio params = (("annualize", True),)