观察者和统计#

运行在 backtrader 内部的策略主要处理的是 数据源指标

数据源被添加到 Cerebro 实例中,并成为策略的输入的一部分(解析并作为实例的属性提供), 而指标则由策略本身声明和管理。

到目前为止,所有 backtrader 的示例图表都显示了三个被认为是理所当然的事物,因为它们没有在任何地方声明:

  • 现金和价值(代表经纪人中的资金情况)

  • 交易(也称为操作)

  • 买入/卖出订单

它们都是 观察者(Observers) ,存在于子模块 backtrader.observers 中。 它们之所以存在是因为 Cerebro 支持一个参数来自动添加(或不添加)它们到策略中:

  • stdstats (默认: True )

如果默认值被遵守, Cerebro 执行以下等效的用户代码:

import backtrader as bt

…```python cerebro = bt.Cerebro() # 默认参数: stdstats=True

cerebro.addobserver(bt.observers.Broker) cerebro.addobserver(bt.observers.Trades) cerebro.addobserver(bt.observers.BuySell)

让我们使用这3个默认的观察者来绘制常规图表(即使没有发出订单,因此没有交易发生,现金和投资组合值也没有变化)

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import backtrader as bt
import backtrader.feeds as btfeeds

if __name__ == '__main__':
    cerebro = bt.Cerebro(stdstats=False)
    cerebro.addstrategy(bt.Strategy)

    data = bt.feeds.BacktraderCSVData(dataname='../../datas/2006-day-001.txt')
    cerebro.adddata(data)

    cerebro.run()
    cerebro.plot()

现在让我们在创建 Cerebro 实例时将 stdstats 的值改为 False (也可以在调用 run 时设置):

cerebro = bt.Cerebro(stdstats=False)

现在图表是不同的了。

访问观察者#

```

如上所示,观察者在默认情况下已经存在,并收集可以用于统计目的的信息,这就是为什么可以通过策略的属性来访问观察者的原因:

  • stats

它只是一个占位符。如果我们回想一下如上所述添加默认的 观察者 之一的情况:

… cerebro.addobserver(backtrader.observers.Broker) …

显而易见的问题是如何访问 Broker 观察者。下面是一个例子,展示了如何从策略的 next 方法中进行访问:

class MyStrategy(bt.Strategy):

def next(self):

if self.stats.broker.value[0] < 1000.0:

print(‘白旗…我亏损太多了’)

elif self.stats.broker.value[0] > 10000000.0:

print(‘是时候去维京群岛了….!!!’)

Broker 观察者就像数据、指标和策略本身一样,也是一个 Lines 对象。在这种情况下, Broker 有两条线:

  • cash

  • value 观察者实现


实现与指示器非常相似:

class Broker(观察者):
   alias = ('现金价值',)
   lines = ('现金', '价值')

   plotinfo = dict(绘图=True, 副图=True)

   def next(self):
       self.lines.现金[0] = self._owner.broker.getcash()
       self.lines.价值[0] = value = self._owner.broker.getvalue()

步骤:

  • 观察者 派生(而不是从 指示器 派生)

  • 根据需要声明lines和params( Broker 有2个线但没有参数)

  • 将有一个自动属性 _owner ,它是持有观察者的策略

观察者开始行动:- 在计算完所有指标后 - 在执行策略的 next 方法后

  • 这意味着:在周期结束时…他们 观察 发生了什么

Broker 的情况下,它只是盲目地记录在每个时间点上的经纪人现金和资产组合价值。

向策略添加观察者#

如上所述, Cerebro 使用 stdstats 参数来决定是否添加3个默认的 观察者 ,以减轻最终用户的工作量。

可以向其中添加其他观察者,无论是沿着 stdstats 还是移除它们。

让我们采用通常的策略,即当 close 价格超过 SimpleMovingAverage 时买入,反之亦然。

再加上一项“添加”:

  • DrawDown ,这是 backtrader 生态系统中已经存在的观察者

以及部分数据输出:

...
2006-12-14T23:59:59+00:00, 最大绘制下降: 2.62
2006-12-15T23:59:59+00:00, 绘制下降: 0.22
2006-12-15T23:59:59+00:00, 最大绘制下降: 2.62
2006-12-18T23:59:59+00:00, 绘制下降: 0.00
2006-12-18T23:59:59+00:00, 最大绘制下降: 2.62
2006-12-19T23:59:59+00:00, 绘制下降: 0.00
2006-12-19T23:59:59+00:00, 最大绘制下降: 2.62
2006-12-20T23:59:59+00:00, 绘制下降: 0.10
2006-12-20T23:59:59+00:00, 最大绘制下降: 2.62
2006-12-21T23:59:59+00:00, 绘制下降: 0.39
2006-12-21T23:59:59+00:00, 最大绘制下降: 2.62
2006-12-22T23:59:59+00:00, 绘制下降: 0.21
2006-12-22T23:59:59+00:00, 最大绘制下降: 2.62
2006-12-27T23:59:59+00:00, 绘制下降: 0.28
2006-12-27T23:59:59+00:00, 最大绘制下降: 2.62
2006-12-28T23:59:59+00:00, 绘制下降: 0.65
2006-12-28T23:59:59+00:00, 最大绘制下降: 2.62
2006-12-29T23:59:59+00:00, 绘制下降: 0.06
2006-12-29T23:59:59+00:00, 最大绘制下降: 2.62

注意

正如在文本输出和代码中所见到的, DrawDown 观察者实际上有两行:

  • drawdown

  • maxdrawdown

选择的是不绘制 maxdrawdown 行,但仍然使其对用户可用。

实际上, maxdrawdown 的最后一个值也可以通过一个直接属性(不是一条线)获得,其名称为 maxdd

开发观察者 ====================上面展示了 Broker 观察者的实现。为了产生一个有意义的观察者,实现可以使用以下信息:

  • self._owner 是当前正在执行的策略

    因此,观察者可以访问策略内的所有内容

  • 策略中可用的默认内部内容:

    • broker -> 属性,提供对策略创建的经纪人实例的访问权限

      如在 Broker 中所见,通过调用 getcashgetvalue 方法来获取现金和投资组合的价值

  • _orderspending -> 列表,由策略创建的订单,并且经纪人已通知策略相关事件

    BuySell 观察者遍历该列表,寻找已被执行(全部或部分)以创建给定时间点(索引0)的平均执行价格的订单

  • _tradespending -> 交易列表(一组已完成的买入/卖出或卖出/买入对),这些交易是由买入/卖出订单编译而成

一个 Observer 显然可以通过 self._owner.stats 路径访问其他观察者。自定义 OrderObserver#

标准的 BuySell 观察者只关心已执行的操作。我们可以创建一个观察者,显示订单的创建情况和是否过期。

为了增加 可见性 ,显示不会在价格上绘制,而是在一个独立的轴上。

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import math

import backtrader as bt


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

自定义观察者只关心 买入 订单,因为这是一种只购买以试图获利的策略。卖出订单是市价单,将立即执行。

Close-SMA 交叉策略的改变为:

  • 创建一个限价订单,价格低于当前信号时的收盘价的1.0%

  • 订单的有效期为7(日历)天

结果图表。

几个订单已过期,可以在新的子图中看到(红色方块),我们还可以发现在“创建”和“执行”之间有几天时间。

最后是应用新的 观察者 的策略代码:

`python 代码见:./observers-orderobserver.py `

保存/保留统计数据#

到目前为止, backtrader 并没有实现任何机制来跟踪观察者的值并将它们存储到文件中。最好的方法是:

  • 在策略的 start 方法中打开一个文件

  • 在策略的 next 方法中将值写入文件

DrawDown 观察者为例,可以这样做:

```python class MyStrategy(bt.Strategy): ``````python def start(self):

self.mystats = open(‘mystats.csv’, ‘wb’) self.mystats.write(‘datetime,drawdown, maxdrawdownn’)

def next(self):

self.mystats.write(self.data.datetime.date(0).strftime(‘%Y-%m-%d’)) self.mystats.write(‘,%.2f’ % self.stats.drawdown.drawdown[-1]) self.mystats.write(‘,%.2f’ % self.stats.drawdown.maxdrawdown-1]) self.mystats.write(’n’)

```

为了保存索引为0的值,一旦处理完所有的观察者,可以将一个自定义观察者添加为系统的最后一个观察者,以将值保存到一个CSV文件中。

注意

写入器 功能可以自动完成这个任务。