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 24import math 25 26from backtrader.utils.py3 import itervalues 27 28from backtrader import Analyzer, TimeFrame 29from backtrader.mathsupport import average, standarddev 30from backtrader.analyzers import TimeReturn, AnnualReturn 31 32 33class SharpeRatio(Analyzer): 34 '''This analyzer calculates the SharpeRatio of a strategy using a risk free 35 asset which is simply an interest rate 36 37 See also: 38 39 - https://en.wikipedia.org/wiki/Sharpe_ratio 40 41 Params: 42 43 - ``timeframe``: (default: ``TimeFrame.Years``) 44 45 - ``compression`` (default: ``1``) 46 47 Only used for sub-day timeframes to for example work on an hourly 48 timeframe by specifying "TimeFrame.Minutes" and 60 as compression 49 50 - ``riskfreerate`` (default: 0.01 -> 1%) 51 52 Expressed in annual terms (see ``convertrate`` below) 53 54 - ``convertrate`` (default: ``True``) 55 56 Convert the ``riskfreerate`` from annual to monthly, weekly or daily 57 rate. Sub-day conversions are not supported 58 59 - ``factor`` (default: ``None``) 60 61 If ``None``, the conversion factor for the riskfree rate from *annual* 62 to the chosen timeframe will be chosen from a predefined table 63 64 Days: 252, Weeks: 52, Months: 12, Years: 1 65 66 Else the specified value will be used 67 68 - ``annualize`` (default: ``False``) 69 70 If ``convertrate`` is ``True``, the *SharpeRatio* will be delivered in 71 the ``timeframe`` of choice. 72 73 In most occasions the SharpeRatio is delivered in annualized form. 74 Convert the ``riskfreerate`` from annual to monthly, weekly or daily 75 rate. Sub-day conversions are not supported 76 77 - ``stddev_sample`` (default: ``False``) 78 79 If this is set to ``True`` the *standard deviation* will be calculated 80 decreasing the denominator in the mean by ``1``. This is used when 81 calculating the *standard deviation* if it's considered that not all 82 samples are used for the calculation. This is known as the *Bessels' 83 correction* 84 85 - ``daysfactor`` (default: ``None``) 86 87 Old naming for ``factor``. If set to anything else than ``None`` and 88 the ``timeframe`` is ``TimeFrame.Days`` it will be assumed this is old 89 code and the value will be used 90 91 - ``legacyannual`` (default: ``False``) 92 93 Use the ``AnnualReturn`` return analyzer, which as the name implies 94 only works on years 95 96 - ``fund`` (default: ``None``) 97 98 If ``None`` the actual mode of the broker (fundmode - True/False) will 99 be autodetected to decide if the returns are based on the total net 100 asset value or on the fund value. See ``set_fundmode`` in the broker 101 documentation 102 103 Set it to ``True`` or ``False`` for a specific behavior 104 105 Methods: 106 107 - get_analysis 108 109 Returns a dictionary with key "sharperatio" holding the ratio 110 111 ''' 112 params = ( 113 ('timeframe', TimeFrame.Years), 114 ('compression', 1), 115 ('riskfreerate', 0.01), 116 ('factor', None), 117 ('convertrate', True), 118 ('annualize', False), 119 ('stddev_sample', False), 120 121 # old behavior 122 ('daysfactor', None), 123 ('legacyannual', False), 124 ('fund', None), 125 ) 126 127 RATEFACTORS = { 128 TimeFrame.Days: 252, 129 TimeFrame.Weeks: 52, 130 TimeFrame.Months: 12, 131 TimeFrame.Years: 1, 132 } 133 134 def __init__(self): 135 if self.p.legacyannual: 136 self.anret = AnnualReturn() 137 else: 138 self.timereturn = TimeReturn( 139 timeframe=self.p.timeframe, 140 compression=self.p.compression, 141 fund=self.p.fund) 142 143 def stop(self): 144 super(SharpeRatio, self).stop() 145 if self.p.legacyannual: 146 rate = self.p.riskfreerate 147 retavg = average([r - rate for r in self.anret.rets]) 148 retdev = standarddev(self.anret.rets) 149 150 self.ratio = retavg / retdev 151 else: 152 # Get the returns from the subanalyzer 153 returns = list(itervalues(self.timereturn.get_analysis())) 154 155 rate = self.p.riskfreerate # 156 157 factor = None 158 159 # Hack to identify old code 160 if self.p.timeframe == TimeFrame.Days and \ 161 self.p.daysfactor is not None: 162 163 factor = self.p.daysfactor 164 165 else: 166 if self.p.factor is not None: 167 factor = self.p.factor # user specified factor 168 elif self.p.timeframe in self.RATEFACTORS: 169 # Get the conversion factor from the default table 170 factor = self.RATEFACTORS[self.p.timeframe] 171 172 if factor is not None: 173 # A factor was found 174 175 if self.p.convertrate: 176 # Standard: downgrade annual returns to timeframe factor 177 rate = pow(1.0 + rate, 1.0 / factor) - 1.0 178 else: 179 # Else upgrade returns to yearly returns 180 returns = [pow(1.0 + x, factor) - 1.0 for x in returns] 181 182 lrets = len(returns) - self.p.stddev_sample 183 # Check if the ratio can be calculated 184 if lrets: 185 # Get the excess returns - arithmetic mean - original sharpe 186 ret_free = [r - rate for r in returns] 187 ret_free_avg = average(ret_free) 188 retdev = standarddev(ret_free, avgx=ret_free_avg, 189 bessel=self.p.stddev_sample) 190 191 try: 192 ratio = ret_free_avg / retdev 193 194 if factor is not None and \ 195 self.p.convertrate and self.p.annualize: 196 197 ratio = math.sqrt(factor) * ratio 198 except (ValueError, TypeError, ZeroDivisionError): 199 ratio = None 200 else: 201 # no returns or stddev_sample was active and 1 return 202 ratio = None 203 204 self.ratio = ratio 205 206 self.rets['sharperatio'] = self.ratio 207 208 209class SharpeRatio_A(SharpeRatio): 210 '''Extension of the SharpeRatio which returns the Sharpe Ratio directly in 211 annualized form 212 213 The following param has been changed from ``SharpeRatio`` 214 215 - ``annualize`` (default: ``True``) 216 217 ''' 218 219 params = ( 220 ('annualize', True), 221 ) 222