An article on quantitative framework backtrader to understand the observer

Introduction

The Backtrader observer is mainly used to observe various status indicators during the operation of the strategy, such as funds, buying and selling points, etc. After calling cerebro.plot(), you can easily visualize the changes in status indicators, as shown in the figure below Broker, Trades and BuySell's 3 observers can be used to view changes in cash and market capitalization, trading profit and loss, and buying and selling points during the backtesting process.

Instructions

  1. Add observer via cerebro.addobserver()

import backtrader as bt
# 查看收益序列
cerebro.addobserver(bt.observers.TimeReturn)
# 查看回撤序列
cerebro.addobserver(bt.observers.DrawDown)
  • addobserver(obscls,  args, **kwargs): The parameter obscls corresponds to the observer observer, args, **kwargs corresponds to the parameters supported by the observer

cerebro = bt.Cerebro(stdstats=False)
cerebro.addobserver(bt.observers.Broker)
cerebro.addobserver(bt.observers.Trades)
cerebro.addobserver(bt.observers.BuySell)
  • Cerebro will add Broker (Cash & Value), Trades, and BuySell 3 observers by default (stdstats=True). You can control non-default display through bt.Cerebro(stdstats=False) when instantiating cerebro.

  1. observers observer execution time: observers observers run and collect data after all indicators and the next method of the strategy are run. Therefore, the latest data of the observer [0] read in the strategy next method is later than the current time of next. a bar

  2. How to read data from observer

  • The observers belong to the lines object, which stores historical backtest data and can be operated like the market lines object. Observers can be accessed through the policy attribute self.stats

class MyStrategy(bt.Strategy):
    def next(self):
        # 当前时点的前一天的可用现金
        self.stats.broker.cash[0]
        self.stats.broker.value[0]
        # 获取当前时刻前一天的收益
        self.stats.timereturn.line[0]
  1. How to save data in observer

Backtrader currently does not have a mechanism to directly save observer data to files, and we need to implement it ourselves. The implementation method recommended by backtrader is:

  • Open the file in the start method of the policy

  • Write the corresponding values ​​in the next and stop methods of the policy

Taking the DrawDown observer mode as an example, the sample code is as follows:

class MyStrategy(bt.Strategy):

    def start(self):

        self.mystats = open('mystats.csv', 'wb')
        self.mystats.write('datetime,drawdown, maxdrawdown\n')

    def next(self):
        self.mystats.write(self.data.datetime.date(-1).strftime('%Y-%m-%d'))
        self.mystats.write(',%.2f' % self.stats.drawdown.drawdown[0])
        self.mystats.write(',%.2f' % self.stats.drawdown.maxdrawdown[0])
        self.mystats.write('\n')

    def stop(self):
        self.mystats.write(self.data.datetime.date(0).strftime('%Y-%m-%d'))
        self.mystats.write(',%.2f' % self.stats.drawdown.drawdown[0])
        self.mystats.write(',%.2f' % self.stats.drawdown.maxdrawdown[0])
        self.mystats.write('\n')

The observer that comes with backtrader

The built-in observers include:

  • Benchmark: records the return sequence of the performance benchmark. The data of the performance benchmark must be added to cerebro in advance through adddata, resampledata, replaydata and other adding functions. During visualization, the return sequence of the strategy itself and the return curve of the performance benchmark will be drawn at the same time.

  • Broker, Cash, Value:  The Broker observer records the available funds and total assets of the broker at each point in time. The cash and values ​​curves will be displayed simultaneously during visualization; if you want to display cash and values ​​separately, you can call backtrader.observers respectively. .Cash and backtrader.observers.Value

  • BuySell: records the buy and sell signals during the backtesting process, and will mark the buying and selling points on the price curve during visualization.

  • DrawDown: records the retracement sequence of the backtesting process, and draws the retracement curve during visualization.

  • TimeReturn:  records the return sequence during the backtesting process, and the TimeReturn return curve will be drawn during visualization.

  • Trades:  records the profit and loss of each transaction during the backtesting process, and draws the profit and loss points during visualization.

  • LogReturns: records the log returns of the strategy

  • LogReturns2: Extended LogReturns to support 2 data, data0 and data1

  • FundValue: records the fund value during the backtesting process

  • FundShares: records the fund shares during the backtesting process

Among them, commonly used observers include: Broker, BuySell, Trades, TimeReturn, DrawDown, Benchmark, etc.

Create a new observers observer

The Broker observer has 2 lines objects: cash and value. Its implementation is similar to the following:

class Broker(Observer):
    alias = ('CashValue',)
    lines = ('cash', 'value')

    plotinfo = dict(plot=True, subplot=True)

    def next(self):
        self.lines.cash[0] = self._owner.broker.getcash()
        self.lines.value[0] = value = self._owner.broker.getvalue()

As can be seen, the steps to customize the observer are as follows:

  • Custom observers inherit from bt.observer.Observer; they can also inherit from other existing observers.

  • Declare required lines and parameters. Parameters are optional. Store the corresponding data in the next method

  • Declare plotinfo and plotlines attributes for visual display in cerebro.plot()

  • There is an automatic attribute _owner indicating the strategy of holding the observer.

Further, we can customize the OrderObserver (refer to the official website): The standard BuySell observer only cares about the operations that have been performed. We can create an observer to view the order creation and expiration status, as shown below.

class OrderObserver(bt.observer.Observer):
    lines = ('created', 'expired',)

    plotinfo = dict(plot=True, subplot=True, plotlinelabels=True)

    plotlines = dict(
        created=dict(marker='*', markersize=8.0, color='lime', fillstyle='full'),
        expired=dict(marker='s', markersize=8.0, color='red', fillstyle='full')
    )

    def next(self):
        for order in self._owner._orderspending:
            if order.data is not self.data:
                continue

            if not order.isbuy():
                continue

            # Only interested in "buy" orders, because the sell orders
            # in the strategy are Market orders and will be immediately
            # executed

            if order.status in [bt.Order.Accepted, bt.Order.Submitted]:
                self.lines.created[0] = order.created.price

            elif order.status in [bt.Order.Expired]:
                self.lines.expired[0] = order.created.price

Of course, we can also inherit from other existing observers, reference code:

class MyBuySell(bt.observers.BuySell):
    # 将barplot默认值改为True
    params = (('barplot', True), ('bardist', 0.015))
    # 将三角形改为箭头
    plotlines = dict(
        buy=dict(marker=r'$\Uparrow$', markersize=8.0, color='#d62728' ),
        sell=dict(marker=r'$\Downarrow$', markersize=8.0, color='red')
    )

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 click and read it.

reference

Guess you like

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