在backtrader中运行的策略主要处理数据源和指标。
数据源被加载到Cerebro实例中,并最终成为策略的一部分(解析和提供实例的属性),而指标则由策略本身声明和管理。
到目前为止,所有backtrader示例图表都有三个默认绘制的东西,因为它们没有在任何地方声明,默认执行:
import backtrader as bt
...
cerebro = bt.Cerebro() # default kwarg: stdstats=True
cerebro.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 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(iplot=False)
图示:
在创建Cerebro实例时将stdstats的值更改为False(也可以在调用run时完成):
cerebro = bt.Cerebro(stdstats=False)
观察者在默认情况下已经存在,并收集可用于统计目的的信息,这就是为什么可以通过策略的一个属性来访问观察者:
它只是一个占位符。如果回想一下如何添加默认的观察者:
...
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条线:
实现与指标非常相似:
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()
步骤:
注意:_owner的属性
观察者开始工作:
如上所指出的,Cerebro使用stdstats参数来决定是否添加3个默认的观察者,减轻了最终用户的工作。
添加其他观察者是可能的,无论是沿着stdstats还是删除它们。
让我们采用通常的策略,当close价格超过SimpleMovingAverage时购买,如果相反则卖出。
增加一个观察者:
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import argparse
import datetime
import os.path
import time
import sys
import backtrader as bt
import backtrader.feeds as btfeeds
import backtrader.indicators as btind
%matplotlib inline
class 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 / sma
self.buysell = btind.CrossOver(self.data.close, sma, plot=True)
# Sentinel to None: new ordersa allowed
self.order = None
def 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 market
if 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条线:
上面展示了Broker观察者的实现。为了产生有意义的观察者,实现可以使用以下信息:
self._owner是当前正在执行的策略
策略中的任何内容都可以用于观察者
策略中可用的默认内部内容可能有用:
_orderspending-> 策略创建的订单列表,broker已向策略通知事件。
BuySell观察者遍历列表,查找已执行(完全或部分)的订单,以创建给定时间点(索引0)的平均执行价格
_tradespending-> 交易列表(已完成的买入/卖出或卖出/买入对的集合),从买入/卖出订单编译而成
观察者可以通过self._owner.stats路径访问其他观察者。
标准的BuySell观察者只关心已执行的操作。可以创建一个观察者,显示订单何时创建以及它们是否过期。
为了可见性,显示将不会沿价格轴上绘制,而是在单独的轴上绘制。
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import datetime
import backtrader as bt
import backtrader.feeds as btfeeds
import backtrader.indicators as btind
#直接放在一个程序中。
#from backtrader.orderobserver import OrderObserver
%matplotlib inline
class 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 do
self.log('ORDER ACCEPTED/SUBMITTED', dt=order.created.dt)
self.order = order
return
if 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: # Sell
self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
(order.executed.price,
order.executed.value,
order.executed.comm))
# Sentinel to None: new orders allowed
self.order = None
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 / sma
self.buysell = btind.CrossOver(self.data.close, sma, plot=True)
# Sentinel to None: new ordersa allowed
self.order = None
def next(self):
if self.order:
# pending order ... do nothing
return
# Check if we are in the market
if 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:
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
def 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策略规则更改为:
如新子图(红色方块)所示,新增的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
最后,应用新的观察者的策略代码,见前。
保存/保持统计信息到目前为止,backtrader尚未实现任何机制来跟踪观察者的值并将其存储到文件中。最好的方法是:
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的功能可以自动化此任务。