Variability Weighted Return (or VWR)
Following some hints about an improved SharpeRatio, backtrader has added this analyzer to its arsenal.
The literature is at:
Starting with the benefits of logarithmic returns and following on the side effects of having the standard deviation in the denominator of the SharpeRatio equation, the document develops the formula and expectations of this analyzer.
One of the most important properties may be:
- A consistent value across timeframes
The SharpeRatio
uses the arithmetic mean of excess returns versus a risk
free rate/asset divided by the standard deviation of the excess returns
versus the risk free rate/asset. This makes the final value dependent on the
number of samples and the standard deviation which could even be 0
. In this
case the SharpeRatio
would be infinite.
backtrader includes a sample for testing the SharpeRatio
using the sample
data which includes prices for 2005
and 2006
. The returned values for
different timeframes:
-
TimeFrame.Years
:11.6473
-
TimeFrame.Months
:0.5425
-
TimeFrame.Weeks
:0.457
-
TimeFrame.Days
:0.4274
Note
For consistency the ratio is annualized. The sharpe-timereturn
sample
and is executed with:
--annualize --timeframe xxx
Where xxx
stands for days
, weeks
, months
or years
(default)
In this sample there is something clear:
- The smaller the timeframe, the smaller the value of the
SharpeRatio
Which is caused by the number of samples which is larger for the smaller
timeframes and adds variability and hence increases the standard
deviation, which is the denominator in the SharpeRatio
equation.
There is a large sensibility to changes in the standard deviation
It is exactly this what the VWR
tries to solved by offering a consistent
value across timeframes. The same strategy offers the following values:
-
TimeFrame.Years
:1.5368
-
TimeFrame.Months
:1.5163
-
TimeFrame.Weeks
:1.5383
-
TimeFrame.Days
:1.5221
Note
The VWR
is returned (following the literature) always in annualized
form. The sample is executed with:
--timeframe xxx
Where xxx
stands for days
, weeks
, months
or years
The default is None
which uses the underlying timeframe of the data
which is days
Consistent values which show that the performance of the strategy when it comes to offering consistent returns can be evaluated on any timeframe.
Note
Theoretically the values should be the same, but this would require fine
tuning the tann
parameters (number of periods for annualization) to the
exact trading periods. This is not done here, because the purpose is just
looking at consistency.
Conclusion
A new tool which offers a timeframe independent approach to strategy evaluation is available for the users
Sample Usage
$ ./vwr.py --help
usage: vwr.py [-h] [--data DATA] [--cash CASH] [--fromdate FROMDATE]
[--todate TODATE] [--writercsv]
[--tframe {weeks,months,days,years}] [--sigma-max SIGMA_MAX]
[--tau TAU] [--tann TANN] [--stddev-sample] [--plot [kwargs]]
TimeReturns and VWR
optional arguments:
-h, --help show this help message and exit
--data DATA, -d DATA data to add to the system (default:
../../datas/2005-2006-day-001.txt)
--cash CASH Starting Cash (default: None)
--fromdate FROMDATE, -f FROMDATE
Starting date in YYYY-MM-DD format (default: None)
--todate TODATE, -t TODATE
Starting date in YYYY-MM-DD format (default: None)
--writercsv, -wcsv Tell the writer to produce a csv stream (default:
False)
--tframe {weeks,months,days,years}, --timeframe {weeks,months,days,years}
TimeFrame for the Returns/Sharpe calculations
(default: None)
--sigma-max SIGMA_MAX
VWR Sigma Max (default: None)
--tau TAU VWR tau factor (default: None)
--tann TANN Annualization factor (default: None)
--stddev-sample Consider Bessels correction for stddeviation (default:
False)
--plot [kwargs], -p [kwargs]
Plot the read data applying any kwargs passed For
example: --plot style="candle" (to plot candles)
(default: None)
Sample Code
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import argparse
import datetime
import backtrader as bt
TFRAMES = dict(
days=bt.TimeFrame.Days,
weeks=bt.TimeFrame.Weeks,
months=bt.TimeFrame.Months,
years=bt.TimeFrame.Years)
def runstrat(pargs=None):
args = parse_args(pargs)
# Create a cerebro
cerebro = bt.Cerebro()
if args.cash is not None:
cerebro.broker.set_cash(args.cash)
dkwargs = dict()
# Get the dates from the args
if args.fromdate is not None:
fromdate = datetime.datetime.strptime(args.fromdate, '%Y-%m-%d')
dkwargs['fromdate'] = fromdate
if args.todate is not None:
todate = datetime.datetime.strptime(args.todate, '%Y-%m-%d')
dkwargs['todate'] = todate
# Create the 1st data
data = bt.feeds.BacktraderCSVData(dataname=args.data, **dkwargs)
cerebro.adddata(data) # Add the data to cerebro
cerebro.addstrategy(bt.strategies.SMA_CrossOver) # Add the strategy
lrkwargs = dict()
if args.tframe is not None:
lrkwargs['timeframe'] = TFRAMES[args.tframe]
if args.tann is not None:
lrkwargs['tann'] = args.tann
cerebro.addanalyzer(bt.analyzers.Returns, **lrkwargs) # Returns
vwrkwargs = dict()
if args.tframe is not None:
vwrkwargs['timeframe'] = TFRAMES[args.tframe]
if args.tann is not None:
vwrkwargs['tann'] = args.tann
if args.sigma_max is not None:
vwrkwargs['sigma_max'] = args.sigma_max
if args.tau is not None:
vwrkwargs['tau'] = args.tau
cerebro.addanalyzer(bt.analyzers.VWR, **vwrkwargs) # VWR Analyzer
# Add a writer to get output
cerebro.addwriter(bt.WriterFile, csv=args.writercsv, rounding=4)
cerebro.run() # And run it
# Plot if requested
if args.plot:
pkwargs = dict(style='bar')
if args.plot is not True: # evals to True but is not True
npkwargs = eval('dict(' + args.plot + ')') # args were passed
pkwargs.update(npkwargs)
cerebro.plot(**pkwargs)
def parse_args(pargs=None):
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description='TimeReturns and SharpeRatio')
parser.add_argument('--data', '-d',
default='../../datas/2005-2006-day-001.txt',
help='data to add to the system')
parser.add_argument('--cash', default=None, type=float, required=False,
help='Starting Cash')
parser.add_argument('--fromdate', '-f',
default=None,
help='Starting date in YYYY-MM-DD format')
parser.add_argument('--todate', '-t',
default=None,
help='Starting date in YYYY-MM-DD format')
parser.add_argument('--writercsv', '-wcsv', action='store_true',
help='Tell the writer to produce a csv stream')
parser.add_argument('--tframe', '--timeframe', default=None,
required=False, choices=TFRAMES.keys(),
help='TimeFrame for the Returns/Sharpe calculations')
parser.add_argument('--sigma-max', required=False, action='store',
type=float, default=None,
help='VWR Sigma Max')
parser.add_argument('--tau', required=False, action='store',
type=float, default=None,
help='VWR tau factor')
parser.add_argument('--tann', required=False, action='store',
type=float, default=None,
help=('Annualization factor'))
parser.add_argument('--stddev-sample', required=False, action='store_true',
help='Consider Bessels correction for stddeviation')
# Plot options
parser.add_argument('--plot', '-p', nargs='?', required=False,
metavar='kwargs', const=True,
help=('Plot the read data applying any kwargs passed\n'
'\n'
'For example:\n'
'\n'
' --plot style="candle" (to plot candles)\n'))
if pargs is not None:
return parser.parse_args(pargs)
return parser.parse_args()
if __name__ == '__main__':
runstrat()