交易监控

交易监控 #

我们已经知道如何使用 backtrader 买卖交易了。本节将介绍 backtrader 如何监控它每笔交易,如成本、利润和佣金。由于佣金的存在,利润还分为毛利润和净利润

设置佣金 #

让我们先设置一个合理佣金率 - 0.1% (买入和卖出都要收取的),一行代码即可。

# 0.1% ... 除以 100 以去掉百分号
cerebro.broker.setcommission(commission=0.001)

订单的成本和佣金 #

订单的成本和佣金可从订单回调 notify_order 中获取,它的 Order 参数 order.executed.comm 即为订单的已执行佣金,order.executed.value 即投入的成本。

class TestStrategy(bt.Strategy):
    def notify_order(self, order):
        if order.status in [order.Completed]:
            if order.isbuy():
                self.log(
                    '买入执行,价格:%.2f,成本:%.2f,佣金 %.2f' %
                    (order.executed.price,
                     order.executed.value,
                     order.executed.comm))
            else:
                self.log('卖出执行,价格:%.2f,成本:%.2f,佣金 %.2f' %
                         (order.executed.price,
                          order.executed.value,
                          order.executed.comm))

交易记录计算利润 #

对于利润的计算,使用交易记录会更简单,所谓成交记录,即成交撮合一笔记录一次。与订单类似,我们可通过 notify_trade 获取成交记录。它的回调参数是 Trade 类对象,利润相关属性有 trade.pnl(毛利润)和 trade.pnlcomm(净利润)。

class TestStrategy(bt.Strategy):
    def notify_trade(self, trade):
        if not trade.isclosed:
            return

        self.log('利润记录,毛利润 %.2f,净利润 %.2f' %
                 (trade.pnl, trade.pnlcomm))

因为只有是平仓交易,才有利润的说法,故通过 trade.isclosed 判断在平仓交易的情况才输出利润信息。

完整示例 #

import datetime  
import os.path  
import sys

import backtrader as bt

class TestStrategy(bt.Strategy):

    def log(self, txt, dt=None):
        dt = dt or self.datas[0].datetime.date(0)
        print('%s, %s' % (dt.isoformat(), txt))

    def __init__(self):
        self.dataclose = self.datas[0].close

        self.order = None
        self.buyprice = None
        self.buycomm = None

    def notify_order(self, order):
        if order.status in [order.Submitted, order.Accepted]:
            return

        if order.status in [order.Completed]:
            if order.isbuy():
                self.log(
                    '买入执行,价格:%.2f,成本:%.2f,佣金 %.2f' %
                    (order.executed.price,
                     order.executed.value,
                     order.executed.comm))

                self.buyprice = order.executed.price
                self.buycomm = order.executed.comm
            else:
                self.log('卖出执行,价格:%.2f,成本:%.2f,佣金 %.2f' %
                         (order.executed.price,
                          order.executed.value,
                          order.executed.comm))
            self.bar_executed = len(self)

        elif order.status in [order.Canceled, order.Margin, order.Rejected]:
            self.log('订单取消/保证金不足/拒绝')

        self.order = None

    def notify_trade(self, trade):
        if not trade.isclosed:
            return

        self.log('利润记录,毛利润 %.2f,净利润 %.2f' %
                 (trade.pnl, trade.pnlcomm))

    def next(self):
        self.log('收盘价,%.2f' % self.dataclose[0])
        if self.order:
            return

        if not self.position:
            if self.dataclose[0] < self.dataclose[-1]:
                if self.dataclose[-1] < self.dataclose[-2]:
                    self.log('创建买入订单,%.2f' % self.dataclose[0])
                      self.order = self.buy()
        else:
            if len(self) >= (self.bar_executed + 5):
                self.log('创建卖出订单,%.2f' % self.dataclose[0])
                self.order = self.sell()

if __name__ == '__main__':
    cerebro = bt.Cerebro()
    cerebro.addstrategy(TestStrategy)
    modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
    datapath = os.path.join(modpath, './datas/orcl-1995-2014.txt')

    data = bt.feeds.YahooFinanceCSVData(
        dataname=datapath,
        fromdate=datetime.datetime(2000, 1, 1),
        todate=datetime.datetime(2000, 12, 31),
        reverse=False)

    cerebro.adddata(data)
    cerebro.broker.setcash(100000.0)
    cerebro.broker.setcommission(commission=0.001)

    print('初始投资组合价值:%.2f' % cerebro.broker.getvalue())
    cerebro.run()
    print('最终投资组合价值:%.2f' % cerebro.broker.getvalue())

执行后的输出是:

初始投资组合价值:100000.00
2000-01-03T00:00:00, 收盘价,27.85
2000-01-04T00:00:00, 收盘价,25.39
2000-01-05T00:00:00, 收盘价,24.05
2000-01-05T00:00:00, 创建买入订单,24.05
2000-01-06T00:00:00, 买入执行,价格:23.61,成本:23.61,佣金 0.02
2000-01-06T00:00:00, 收盘价,22.63
2000-01-07T00:00:00, 收盘价,24.37
2000-01-10T00:00:00, 收盘价,27.29
2000-01-11T00:00:00, 收盘价,26.49
2000-01-12T00:00:00, 收盘价,24.90
2000-01-13T00:00:00, 收盘价,24.77
2000-01-13T00:00:00, 创建卖出订单,24.77
2000-01-14T00:00:00, 卖出执行,价格:25.70,成本:25.70,佣金 0.03
2000-01-14T00:00:00, 利润记录,毛利润 2.09,净利润 2.04
2000-01-14T00:00:00, 收盘价,25.18
...
...
...
2000-12-15T00:00:00, 创建卖出订单,26.93
2000-12-18T00:00:00, 卖出执行,价格:28.29,成本:28.29,佣金 0.03
2000-12-18T00:00:00, 利润记录,毛利润 -0.06,净利润 -0.12
2000-12-18T00:00:00, 收盘价,30.18
2000-12-19T00:00:00, 收盘价,28.88
2000-12-20T00:00:00, 收盘价,26.88
2000-12-20T00:00:00, 创建买入订单,26.88
2000-12-21T00:00:00, 买入执行,价格:26.23,成本:26.23,佣金 0.03
2000-12-21T00:00:00, 收盘价,27.82
2000-12-22T00:00:00, 收盘价,30.06
2000-12-26T00:00:00, 收盘价,29.17
2000-12-27T00:00:00, 收盘价,28.94
2000-12-28T00:00:00, 收盘价,29.29
2000-12-29T00:00:00, 收盘价,27.41
2000-12-29T00:00:00, 创建卖出订单,27.41
最终投资组合价值:100016.98

让我们将其中的 “利润记录” 的日志提取出来。

2000-01-14T00:00:00, 利润记录,毛利润 2.09,净利润 2.04
2000-02-07T00:00:00, 利润记录,毛利润 3.68,净利润 3.63
2000-02-28T00:00:00, 利润记录,毛利润 4.48,净利润 4.42
2000-03-13T00:00:00, 利润记录,毛利润 3.48,净利润 3.41
2000-03-22T00:00:00, 利润记录,毛利润 -0.41,净利润 -0.49
2000-04-07T00:00:00, 利润记录,毛利润 2.45,净利润 2.37
2000-04-20T00:00:00, 利润记录,毛利润 -1.95,净利润 -2.02
2000-05-02T00:00:00, 利润记录,毛利润 5.46,净利润 5.39
2000-05-11T00:00:00, 利润记录,毛利润 -3.74,净利润 -3.81
2000-05-30T00:00:00, 利润记录,毛利润 -1.46,净利润 -1.53
2000-07-05T00:00:00, 利润记录,毛利润 -1.62,净利润 -1.69
2000-07-14T00:00:00, 利润记录,毛利润 2.08,净利润 2.01
2000-07-28T00:00:00, 利润记录,毛利润 0.14,净利润 0.07
2000-08-08T00:00:00, 利润记录,毛利润 4.36,净利润 4.29
2000-08-21T00:00:00, 利润记录,毛利润 1.03,净利润 0.95
2000-09-15T00:00:00, 利润记录,毛利润 -4.26,净利润 -4.34
2000-09-27T00:00:00, 利润记录,毛利润 1.29,净利润 1.22
2000-10-13T00:00:00, 利润记录,毛利润 -2.98,净利润 -3.04
2000-10-26T00:00:00, 利润记录,毛利润 3.01,净利润 2.95
2000-11-06T00:00:00, 利润记录,毛利润 -3.59,净利润 -3.65
2000-11-16T00:00:00, 利润记录,毛利润 1.28,净利润 1.23
2000-12-01T00:00:00, 利润记录,毛利润 2.59,净利润 2.54
2000-12-18T00:00:00, 利润记录,毛利润 -0.06,净利润 -0.12

将这些 “净利润” 相加,最终的数字是 15.83。但系统告诉我们最终投资组合价值是 100016.98。显然,15.83 并不等于 16.98

这其实并没有任何错误,净利润 15.83 是已平仓的交易利润。而最后一天实际还有一个未平仓头寸。虽然已发送了卖出操作,还要等待下个 bar 才能成交。最终投资组合价值止于 2000-12-29 的收盘价。这个订单的成交价格将会在下一个交易日(即 2001-01-02)被设定。

假设,我们将数据源提供到下一个交易日。

2001-01-02T00:00:00, 卖出执行,价格:27.87,成本:27.87,佣金 0.03
2001-01-02T00:00:00, 利润记录,毛利润 1.64,净利润 1.59
2001-01-02T00:00:00, 收盘价,24.87
2001-01-02T00:00:00, 创建买入订单,24.87
最终投资组合价值:100017.41

现在将先前的净利润与已完成操作的净利润相加:

15.83 + 1.59 = 17.42

忽略四舍五入的误差,现在一切就对上了。