Backtrader 文档学习- Observers
1.概述
在backtrader中运行的策略主要处理数据源和指标。
数据源被加载到Cerebro实例中,并最终成为策略的一部分(解析和提供实例的属性),而指标则由策略本身声明和管理。
到目前为止,所有backtrader示例图表都有三个默认绘制的东西,因为它们没有在任何地方声明,默认执行:
- 现金和价值(经纪人中的资金情况)
- 交易(也称为操作)
- 买入/卖出订单
它们是Observers观察者,存在于子模块backtrader.observers中。它们存在是因为Cerebro支持一个参数来自动添加(或不添加)它们到策略中: - stdstats(默认值:True)
如果默认值被使用,Cerebro将执行以下等效的用户代码:
import backtrader as bt...cerebro = bt.Cerebro() # default kwarg: stdstats=Truecerebro.addobserver(bt.observers.Broker)
cerebro.addobserver(bt.observers.Trades)
cerebro.addobserver(bt.observers.BuySell)
观察带有这三个默认观察者的常规图表(即使没有下达订单,没有交易发生,现金和组合价值也没有变化)
from __future__ import (absolute_import, division, print_function,unicode_literals)
%matplotlib inline
import backtrader as bt
import backtrader.feeds as btfeedsif __name__ == '__main__':cerebro = bt.Cerebro() # stdstats=Falsecerebro.addstrategy(bt.Strategy)data = bt.feeds.BacktraderCSVData(dataname='./datas/2006-day-001.txt')cerebro.adddata(data)cerebro.run()cerebro.plot(iplot=False)
图示:
在创建Cerebro实例时将stdstats的值更改为False(也可以在调用run时完成):
cerebro = bt.Cerebro(stdstats=False)
2.Accesing the Observers
观察者在默认情况下已经存在,并收集可用于统计目的的信息,这就是为什么可以通过策略的一个属性来访问观察者:
- stats
它只是一个占位符。如果回想一下如何添加默认的观察者:
...
cerebro.addobserver(backtrader.observers.Broker)
...
显然问题是如何访问Broker观察者。例如,如何从策略的next方法中执行此操作:
class MyStrategy(bt.Strategy):def next(self):if self.stats.broker.value[0] < 1000.0:print('WHITE FLAG ... I LOST TOO MUCH')elif self.stats.broker.value[0] > 10000000.0:print('TIME FOR THE VIRGIN ISLANDS ....!!!')
Broker观察者就像数据、指标和策略本身一样,也是一个Lines对象。在这种情况下,Broker有2条线:
- cash
- value
3.Observer Implementation
实现与指标非常相似:
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()
步骤:
- 从Observer(而不是Indicator)派生
- 根据需要声明行和参数(Broker有2行但没有参数)
- 将自动属性_owner,设置为持有观察者的策略
注意:_owner的属性
观察者开始工作:
- 在所有指标计算完成后
- 在策略next方法执行后
- 意味着:在循环结束时…观察到发生了什么
在Broker中,只是机械地记录在每个时间点上经纪人现金和组合价值。
4.Adding Observers to the Strategy
如上所指出的,Cerebro使用stdstats参数来决定是否添加3个默认的观察者,减轻了最终用户的工作。
添加其他观察者是可能的,无论是沿着stdstats还是删除它们。
让我们采用通常的策略,当close价格超过SimpleMovingAverage时购买,如果相反则卖出。
增加一个观察者:
- DrawDown 回撤,它是backtrader 生态系统中已经存在的观察者
from __future__ import (absolute_import, division, print_function,unicode_literals)import argparse
import datetime
import os.path
import time
import sysimport backtrader as bt
import backtrader.feeds as btfeeds
import backtrader.indicators as btind%matplotlib inlineclass MyStrategy(bt.Strategy):params = (('smaperiod', 15),)def log(self, txt, dt=None):''' Logging function fot this strategy'''dt = dt or self.data.datetime[0]if isinstance(dt, float):dt = bt.num2date(dt).date()print('%s, %s' % (dt.isoformat(), txt))def __init__(self):# SimpleMovingAverage on main data# Equivalent to -> sma = btind.SMA(self.data, period=self.p.smaperiod)sma = btind.SMA(period=self.p.smaperiod)# CrossOver (1: up, -1: down) close / smaself.buysell = btind.CrossOver(self.data.close, sma, plot=True)# Sentinel to None: new ordersa allowedself.order = Nonedef next(self):# Access -1, because drawdown[0] will be calculated after "next"self.log('DrawDown: %.2f' % self.stats.drawdown.drawdown[-1])self.log('MaxDrawDown: %.2f' % self.stats.drawdown.maxdrawdown[-1])# Check if we are in the marketif self.position:if self.buysell < 0:self.log('SELL CREATE, %.2f' % self.data.close[0])self.sell()elif self.buysell > 0:self.log('BUY CREATE, %.2f' % self.data.close[0])self.buy()def runstrat():cerebro = bt.Cerebro()data = bt.feeds.BacktraderCSVData(dataname='./datas/2006-day-001.txt')cerebro.adddata(data)cerebro.addobserver(bt.observers.DrawDown)cerebro.addstrategy(MyStrategy)cerebro.run()cerebro.plot(iplot=False)if __name__ == '__main__':runstrat()
图表的输出显示了回撤的演变
输出:
... ...
2006-12-12, DrawDown: 0.63
2006-12-12, MaxDrawDown: 2.62
2006-12-13, DrawDown: 0.63
2006-12-13, MaxDrawDown: 2.62
2006-12-14, DrawDown: 0.56
2006-12-14, MaxDrawDown: 2.62
2006-12-15, DrawDown: 0.22
2006-12-15, MaxDrawDown: 2.62
2006-12-18, DrawDown: 0.00
2006-12-18, MaxDrawDown: 2.62
2006-12-19, DrawDown: 0.00
2006-12-19, MaxDrawDown: 2.62
2006-12-20, DrawDown: 0.10
2006-12-20, MaxDrawDown: 2.62
2006-12-21, DrawDown: 0.39
2006-12-21, MaxDrawDown: 2.62
2006-12-22, DrawDown: 0.21
2006-12-22, MaxDrawDown: 2.62
2006-12-27, DrawDown: 0.28
2006-12-27, MaxDrawDown: 2.62
2006-12-28, DrawDown: 0.65
2006-12-28, MaxDrawDown: 2.62
2006-12-29, DrawDown: 0.06
2006-12-29, MaxDrawDown: 2.62
注意:
如文本输出和代码中所示,DrawDown观察者实际上有2条线:
- drawdown
- maxdrawdown
选择不绘制最大回撤线,但使其可供用户使用。
实际上maxdrawdown的最后一个值也可以通过一个直接属性(而不是一条线)来获得,该属性的名称为maxdd
5.Developing Observers
上面展示了Broker观察者的实现。为了产生有意义的观察者,实现可以使用以下信息:
-
self._owner是当前正在执行的策略
策略中的任何内容都可以用于观察者 -
策略中可用的默认内部内容可能有用:
- broker-> 属性,提供对策略创建订单的broker实例的访问
如在Broker中所见,通过调用方法getcash和getvalue收集现金和组合值
- broker-> 属性,提供对策略创建订单的broker实例的访问
-
_orderspending-> 策略创建的订单列表,broker已向策略通知事件。
BuySell观察者遍历列表,查找已执行(完全或部分)的订单,以创建给定时间点(索引0)的平均执行价格 -
_tradespending-> 交易列表(已完成的买入/卖出或卖出/买入对的集合),从买入/卖出订单编译而成
观察者可以通过self._owner.stats路径访问其他观察者。
6.Custom OrderObserver
标准的BuySell观察者只关心已执行的操作。可以创建一个观察者,显示订单何时创建以及它们是否过期。
为了可见性,显示将不会沿价格轴上绘制,而是在单独的轴上绘制。
from __future__ import (absolute_import, division, print_function,unicode_literals)import datetimeimport backtrader as bt
import backtrader.feeds as btfeeds
import backtrader.indicators as btind#直接放在一个程序中。
#from backtrader.orderobserver import OrderObserver
%matplotlib inlineclass MyStrategy(bt.Strategy):params = (('smaperiod', 15),('limitperc', 1.0),('valid', 7),)def log(self, txt, dt=None):''' Logging function fot this strategy'''dt = dt or self.data.datetime[0]if isinstance(dt, float):dt = bt.num2date(dt).date()print('%s, %s' % (dt.isoformat(), txt))def notify_order(self, order):if order.status in [order.Submitted, order.Accepted]:# Buy/Sell order submitted/accepted to/by broker - Nothing to doself.log('ORDER ACCEPTED/SUBMITTED', dt=order.created.dt)self.order = orderreturnif order.status in [order.Expired]:self.log('BUY EXPIRED')elif order.status in [order.Completed]:if order.isbuy():self.log('BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %(order.executed.price,order.executed.value,order.executed.comm))else: # Sellself.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %(order.executed.price,order.executed.value,order.executed.comm))# Sentinel to None: new orders allowedself.order = Nonedef __init__(self):# SimpleMovingAverage on main data# Equivalent to -> sma = btind.SMA(self.data, period=self.p.smaperiod)sma = btind.SMA(period=self.p.smaperiod)# CrossOver (1: up, -1: down) close / smaself.buysell = btind.CrossOver(self.data.close, sma, plot=True)# Sentinel to None: new ordersa allowedself.order = Nonedef next(self):if self.order:# pending order ... do nothingreturn# Check if we are in the marketif self.position:if self.buysell < 0:self.log('SELL CREATE, %.2f' % self.data.close[0])self.sell()elif self.buysell > 0:plimit = self.data.close[0] * (1.0 - self.p.limitperc / 100.0)valid = self.data.datetime.date(0) + \datetime.timedelta(days=self.p.valid)self.log('BUY CREATE, %.2f' % plimit)self.buy(exectype=bt.Order.Limit, price=plimit, valid=valid)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:continueif not order.isbuy():continue# Only interested in "buy" orders, because the sell orders# in the strategy are Market orders and will be immediately# executedif order.status in [bt.Order.Accepted, bt.Order.Submitted]:self.lines.created[0] = order.created.priceelif order.status in [bt.Order.Expired]:self.lines.expired[0] = order.created.pricedef runstrat():cerebro = bt.Cerebro()data = bt.feeds.BacktraderCSVData(dataname='./datas/2006-day-001.txt')cerebro.adddata(data)cerebro.addobserver(OrderObserver)cerebro.addstrategy(MyStrategy)cerebro.run()cerebro.plot(iplot=False)if __name__ == '__main__':runstrat()
自定义观察者只关心买入订单,因为这是一种只买入以试图获利的策略。卖出订单是市价订单,将立即执行。
Close-SMA CrossOver策略规则更改为:
- 创建一个价格低于此时刻收盘价1.0%以下的限价订单
- 订单的有效期为7(日历)天
如新子图(红色方块)所示,新增的Observer 对象,created 和 expired ,有几个订单已过期,还看到在创建和执行间隔了几天。
输出结果:
2006-01-26, BUY CREATE, 3605.01
2006-01-26, ORDER ACCEPTED/SUBMITTED
2006-01-26, ORDER ACCEPTED/SUBMITTED
2006-02-02, BUY EXPIRED
2006-03-10, BUY CREATE, 3760.48
2006-03-10, ORDER ACCEPTED/SUBMITTED
2006-03-10, ORDER ACCEPTED/SUBMITTED
2006-03-17, BUY EXPIRED
2006-03-30, BUY CREATE, 3835.86
2006-03-30, ORDER ACCEPTED/SUBMITTED
2006-03-30, ORDER ACCEPTED/SUBMITTED
2006-04-05, BUY EXECUTED, Price: 3835.86, Cost: 3835.86, Comm 0.00
2006-04-07, SELL CREATE, 3823.11
2006-04-07, ORDER ACCEPTED/SUBMITTED
2006-04-07, ORDER ACCEPTED/SUBMITTED
2006-04-10, SELL EXECUTED, Price: 3822.35, Cost: 3835.86, Comm 0.00
2006-04-20, BUY CREATE, 3821.40
2006-04-20, ORDER ACCEPTED/SUBMITTED
2006-04-20, ORDER ACCEPTED/SUBMITTED
2006-04-27, BUY EXPIRED
2006-05-04, BUY CREATE, 3804.65
2006-05-04, ORDER ACCEPTED/SUBMITTED
2006-05-04, ORDER ACCEPTED/SUBMITTED
2006-05-11, BUY EXPIRED
2006-06-01, BUY CREATE, 3611.85
2006-06-01, ORDER ACCEPTED/SUBMITTED
2006-06-01, ORDER ACCEPTED/SUBMITTED
2006-06-05, BUY EXECUTED, Price: 3611.85, Cost: 3611.85, Comm 0.00
2006-06-05, SELL CREATE, 3604.33
2006-06-05, ORDER ACCEPTED/SUBMITTED
2006-06-05, ORDER ACCEPTED/SUBMITTED
2006-06-06, SELL EXECUTED, Price: 3598.58, Cost: 3611.85, Comm 0.00
2006-06-21, BUY CREATE, 3491.57
2006-06-21, ORDER ACCEPTED/SUBMITTED
2006-06-21, ORDER ACCEPTED/SUBMITTED
2006-06-28, BUY EXPIRED
2006-07-24, BUY CREATE, 3596.60
2006-07-24, ORDER ACCEPTED/SUBMITTED
2006-07-24, ORDER ACCEPTED/SUBMITTED
2006-07-31, BUY EXPIRED
2006-09-12, BUY CREATE, 3751.07
2006-09-12, ORDER ACCEPTED/SUBMITTED
2006-09-12, ORDER ACCEPTED/SUBMITTED
2006-09-19, BUY EXPIRED
2006-09-20, BUY CREATE, 3802.90
2006-09-20, ORDER ACCEPTED/SUBMITTED
2006-09-20, ORDER ACCEPTED/SUBMITTED
2006-09-22, BUY EXECUTED, Price: 3802.90, Cost: 3802.90, Comm 0.00
2006-11-02, SELL CREATE, 3974.62
2006-11-02, ORDER ACCEPTED/SUBMITTED
2006-11-02, ORDER ACCEPTED/SUBMITTED
2006-11-03, SELL EXECUTED, Price: 3979.73, Cost: 3802.90, Comm 0.00
2006-11-06, BUY CREATE, 4004.77
2006-11-06, ORDER ACCEPTED/SUBMITTED
2006-11-06, ORDER ACCEPTED/SUBMITTED
2006-11-13, BUY EXPIRED
2006-12-11, BUY CREATE, 4012.36
2006-12-11, ORDER ACCEPTED/SUBMITTED
2006-12-11, ORDER ACCEPTED/SUBMITTED
2006-12-18, BUY EXPIRED
最后,应用新的观察者的策略代码,见前。
7.Saving/Keeping the statistics
保存/保持统计信息到目前为止,backtrader尚未实现任何机制来跟踪观察者的值并将其存储到文件中。最好的方法是:
- 在策略的start方法中打开文件
- 在策略的next方法中将值写入
考虑到DrawDown观察者,可以做:
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(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文件中。
注意:Writer的功能可以自动化此任务。