An article on quantitative framework backtrader to understand the Analyzer analyzer

Nobel laureate William Sharp

Introduction

Strategy performance evaluation is a very important part of quantitative trading. Investment requires not only understanding the rate of return of the strategy, but also the risk of the strategy. Backtrader provides a variety of analyzers that can output a variety of performance indicators to analyze the effect of strategy backtesting.

Instructions

cerebro.addanalyzer(bt.analyzers.AnnualReturn, _name='AnnualReturn')
cerebro.addanalyzer(bt.analyzers.SharpeRatio, riskfreerate=0.003, annualize=True, _name='SharpeRatio')
cerebro.addanalyzer(bt.analyzers.DrawDown, _name='DrawDown')

strats = cerebro.run()

# 第一个策略
strat = strats[0]

print("--------------- AnnualReturn -----------------")
print(strat.analyzers.AnnualReturn.get_analysis())
print("--------------- SharpeRatio -----------------")
print(strat.analyzers.SharpeRatio.get_analysis())
print("--------------- DrawDown -----------------")
print(strat.analyzers.DrawDown.get_analysis())

AnnualReturn represents the annualized return analyzer ,  SharpeRatio represents the Sharpe ratio analyzer, and DrawDown represents the backtest analyzer. The results of each analyzer are usually returned in the form of an OrderedDict dictionary, as shown below. You can get the required values ​​through keys, such as strat.analyzers.SharpeRatio.get_analysis()['sharperatio'].

--------------- AnnualReturn -----------------
OrderedDict([(2010, 0.0055023006637382466), (2011, 0.0015355496623354892), (2012, 0.004218277850025043), (2013, 0.09220659214070048), (2014, 0.05213380413050861), (2015, 0.012115724348371826), (2016, -0.017901621371985033), (2017, 0.06763449420829182), (2018, 0.01391412496311406), (2019, 0.05472002239157425), (2020, 1.8864865026259587), (2021, 0.36892175167157526), (2022, -0.2711252801992251)])
--------------- SharpeRatio -----------------
OrderedDict([('sharperatio', 0.3360518820606975)])
--------------- DrawDown -----------------
AutoOrderedDict([('len', 145), ('drawdown', 35.66222565509548), ('moneydown', 21054.40185546875), ('max', AutoOrderedDict([('len', 648), ('drawdown', 40.770090001923634), ('moneydown', 24070.00244140625)]))])

It can be seen that the results output by the Analyzer are only numerical values ​​and there is no drawing. For SharpeRatio example, the Sharpe ratio is calculated to be a value of 0.3360518820606975. In fact, Analyzers the lines object is not included, which also means that the analyzer object is very memory-friendly and does not occupy a lot of memory.

  • Further explanation: An analyzer analyzes the performance of a strategy rather than the performance of the entire system. For the analyzer class added through the addanalyzer function, at cerebro.run runtime, each analyzer instance will be linked to each strategy. If the backtest contains 3 strategies, then 3 instances of the same analyzer will be created, and each instance will are linked to different strategies

  • Some Analyzer analyzer objects use other analyzers to complete tasks, such as SharpeRatio using TimeReturn the output of the analyzer for further calculations, but these dependencies are not visible to the user.

Analyzers that come with backtrader

  • AnnualReturn: Annualized rate of return

  • Calmar:  Calmar ratio

  • DrawDown: retracement

  • TimeDrawDown:  Drawback at specified time granularity

  • GrossLeverage:  Total leverage

  • PositionsValue:  position value

  • PyFolio:  Generate data compatible with pyfolio

  • LogReturnsRolling:  rolling log returns

  • PeriodStats:  Basic statistical information for a given period

  • Returns:  Calculate total, average, compound, and annualized returns using logarithmic method

  • SharpeRatio:  Sharpe ratio

  • SharpeRatio_A:  annualized Sharpe ratio

  • SQN: System Quality Number

  • TimeReturn: rate of return in different periods

  • TradeAnalyzer: Trading statistics such as wins, losses, etc.

  • Transactions:  The subject matter, price, quantity and other information of each transaction

  • VWR:  variability weighted return, volatility-weighted return

Commonly used Analyzer analyzers include: AnnualReturn, DrawDown, PyFolio, SharpeRatio, TimeReturn.

Create a new Analyzer analyzer

If the analyzer that comes with backtrader cannot meet your needs, you can build your own Analyzer. Take SharpeRatio as an example, the reference code is as follows. Sharpe ratio is one of the most commonly used performance evaluation indicators. It reflects the excess return rate contributed by unit fluctuation. The bigger the better. The Sharpe ratio is the strategy's excess return relative to the risk-free return divided by the standard deviation of the return.

import backtrader as bt

from backtrader.utils.py3 import map
from backtrader.mathsupport import average, standarddev

class SharpeRatio(bt.Analyzer):
    params = (
           ('timeframe', bt.TimeFrame.Years), 
           ('riskfreerate', 0.01),
    )

    def __init__(self):
        super(SharpeRatio, self).__init__()
        self.anret = bt.analyzers.AnnualReturn()

    # analyzer与策略一样,都是从第0根bar开始运行
    def start(self):
        pass

    def prenext(self):
        pass
    
    def nextstart(self):
        pass

    def next(self):
        pass

    # 一般对策略整体的评价指标是在策略结束后开始计算的
    def stop(self):
        retfree = [self.p.riskfreerate] * len(self.anret.rets)
        retavg = average(list(map(operator.sub, self.anret.rets, retfree)))
        retdev = standarddev(self.anret.rets)

        self.ratio = retavg / retdev

    # Analyzer类特有方法,返回包含分析结果的类dict对象
    def get_analysis(self):
        return dict(sharperatio=self.ratio)

    # 支持与策略一样的信息打印函数
    def notify_order(self, order):
        pass

    def notify_trade(self, trade):
        pass
    
    def notify_cashvalue(self, cash, value):
        pass
    
    def notify_fund(self, cash, value, fundvalue, shares):
        pass

As can be seen from the above code, although Analyzer the object does not have a Lines object and does not need to iterate on lines, they follow the same operating mode as the strategy. Of course, there is also this get_analysis unique method for returning analysis results.

Using PyFolio in backtrader

cerebro.addanalyzer(bt.analyzers.PyFolio)
strats = cerebro.run()
# retrieve the 1st strategy
strat0 = strats[0]
pyfolio = strats.analyzers.getbyname('pyfolio')
returns, positions, transactions, gross_lev = pyfoliozer.get_pf_items()

import pyfolio as pf
pf.create_full_tear_sheet(returns)

pyfolio It can also be run outside of Jupyter Notebook, but it is best supported when running inside Jupyter.

Compare with baseline benchmark

import backtrader as bt

cerebro = bt.Cerebro()

spypd = bt.feeds.PandasData(dataname=spydata, name="spy")
cerebro.adddata(spypd)

cerebro.addanalyzer(bt.analyzers.TimeReturn, timeframe=bt.TimeFrame.Years, _name='TimeReturn')
cerebro.addanalyzer(bt.analyzers.TimeReturn, timeframe=bt.TimeFrame.Years, data=spypd, _name='BenchTimeReturn')

strats = cerebro.run()

time_return = strat.analyzers.getbyname('TimeReturn').get_analysis()
bench_time_return = strat.analyzers.getbyname('BenchTimeReturn').get_analysis()

print("--------------- TimeReturn -----------------")
print(time_return)
print("--------------- BenchTimeReturn -----------------")
print(bench_time_return)

Output log:

--------------- TimeReturn -----------------
OrderedDict([(datetime.date(2009, 12, 31), 0.0), (datetime.date(2010, 12, 31), 0.0055023006637382466), (datetime.date(2011, 12, 31), 0.0015355496623354892), (datetime.date(2012, 12, 31), 0.004218277850025043), (datetime.date(2013, 12, 31), 0.09220659214070048), (datetime.date(2014, 12, 31), 0.05213380413050861), (datetime.date(2015, 12, 31), 0.012115724348371826), (datetime.date(2016, 12, 31), -0.017901621371985033), (datetime.date(2017, 12, 31), 0.06763449420829182), (datetime.date(2018, 12, 31), 0.01391412496311406), (datetime.date(2019, 12, 31), 0.05472002239157425), (datetime.date(2020, 12, 31), 1.8864865026259587), (datetime.date(2021, 12, 31), 0.36892175167157526), (datetime.date(2022, 12, 31), -0.2711252801992251)])
--------------- SpyTimeReturn -----------------
OrderedDict([(datetime.date(2009, 12, 31), -0.011793865755532318), (datetime.date(2010, 12, 31), 0.12840988195524994), (datetime.date(2011, 12, 31), -0.001988071570576566), (datetime.date(2012, 12, 31), 0.134741065036728), (datetime.date(2013, 12, 31), 0.2968892471880906), (datetime.date(2014, 12, 31), 0.11289182180470925), (datetime.date(2015, 12, 31), -0.008124930541476227), (datetime.date(2016, 12, 31), 0.09643402233275422), (datetime.date(2017, 12, 31), 0.19384416771302204), (datetime.date(2018, 12, 31), -0.06347893319524989), (datetime.date(2019, 12, 31), 0.28785206349908), (datetime.date(2020, 12, 31), 0.16162313396749006), (datetime.date(2021, 12, 31), 0.2703540848726258), (datetime.date(2022, 12, 31), -0.13563244077211734)])

Note that not all Analyzers support Benchmark comparison.

Conclusion & Communication

Follow the WeChat public account: Zhuge Shuo Talk for more content. At the same time, you can also get invitations to join investment exchange groups and quantitative investment seminar groups, where you can communicate and discuss with many investment enthusiasts, quantitative practitioners, and technology experts, and quickly improve your investment level.

Writing articles is not easy. If you think this article is helpful to you, please give it a like.

reference

Guess you like

Origin blog.csdn.net/richardzhutalk/article/details/125239927