在内部backtrader 运行的策略主要处理 data feeds 和 指针。
Data feeds 被添加到Cerebro 实例中,并最终成为策略输入的一部分(解析并用作实例的属性),而指针则由策略本身声明和管理。
到目前为止,所有backtrader 示例图表都绘制了3个东西,这些东西似乎被认为是理所当然的,因为它们没有在任何地方声明:
-
现金和价值(经纪人中的钱发生了什么)
-
交易(又名运营)
-
买入/卖出订单
它们存在于Observers 子模块 backtrader.observers中。它们之所以存在,是因为 Cerebro 支持一个参数来自动将它们添加(或不添加)到策略中:
stdstats(默认值:True)
如果遵循默认值,Cerebro将运行以下等效的用户代码:
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)
让我们看看通常的图表,其中包含这3个默认 observers (即使没有发出订单,因此没有交易发生,现金和投资组合价值也没有变化)
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实例时将 False 的值stdstats更改为 (也可以在调用run时完成):
cerebro = bt.Cerebro(stdstats=False)
现在的图表不同了。
访问 Observers
如上所示, Observers 在默认情况下已经存在,并且收集可用于统计目的的信息,这就是为什么可以通过策略的属性 observers (称为:
stats
它只是一个占比特。如果我们还记得上面列出的缺省Observers 之一:
... cerebro.addobserver(backtrader.observers.Broker) ...
显而易见的问题是如何访问Brokerobserver。例如,如何从策略的方法完成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 observer就像数据一样,指针和策略本身也是一个Lines对象。在本例中,Broker有2个lines:
-
cash -
value
Observer 实施
实现与指针非常相似:
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) -
根据需要声明 lines 和参数(
Broker有 2 个 lines 但没有参数) -
将有一个自动属性
_owner,它是持有 observer
Observers 行动:
-
在计算完所有指针后
-
在运行策略
next方法后 -
这意味着:在周期结束时...他们观察 发生了什么
在这种情况下,Broker 它只是盲目地记录经纪人在每个时间点的现金和投资组合价值。
向战略添加Observers
如上所述,Cerebro 正在使用参数 stdstats 来决定是否添加3个缺省 Observers,减轻最终用户的工作。
在混合物中添加其他 Observers 是可能的,无论是沿着stdstats 还是删除它们。
让我们采用通常的策略,当价格高于aSimpleMovingAverage时close买入,如果相反true,则卖出。
通过一个「添加」:
- DrawDown是生态系统中
backtrader已经存在observer
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
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)
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()
if __name__ == '__main__':
runstrat()
视觉输出显示回撤的演变
与部分文本输出:
... 2006-12-14T23:59:59+00:00, MaxDrawDown: 2.62 2006-12-15T23:59:59+00:00, DrawDown: 0.22 2006-12-15T23:59:59+00:00, MaxDrawDown: 2.62 2006-12-18T23:59:59+00:00, DrawDown: 0.00 2006-12-18T23:59:59+00:00, MaxDrawDown: 2.62 2006-12-19T23:59:59+00:00, DrawDown: 0.00 2006-12-19T23:59:59+00:00, MaxDrawDown: 2.62 2006-12-20T23:59:59+00:00, DrawDown: 0.10 2006-12-20T23:59:59+00:00, MaxDrawDown: 2.62 2006-12-21T23:59:59+00:00, DrawDown: 0.39 2006-12-21T23:59:59+00:00, MaxDrawDown: 2.62 2006-12-22T23:59:59+00:00, DrawDown: 0.21 2006-12-22T23:59:59+00:00, MaxDrawDown: 2.62 2006-12-27T23:59:59+00:00, DrawDown: 0.28 2006-12-27T23:59:59+00:00, MaxDrawDown: 2.62 2006-12-28T23:59:59+00:00, DrawDown: 0.65 2006-12-28T23:59:59+00:00, MaxDrawDown: 2.62 2006-12-29T23:59:59+00:00, DrawDown: 0.06 2006-12-29T23:59:59+00:00, MaxDrawDown: 2.62
注意
如文本输出和代码所示,DrawDownobserver实际上有 2 个lines:
-
drawdown -
maxdrawdown
选择不是绘制maxdrawdownline,而是使其仍然可供用户使用。
实际上,的 last 值maxdrawdown 也可以在直接属性(不是 line)中使用,其名称为 maxdd
开发 Observers
observer的Broker实施情况如上所示。为了生成有意义的observer,实现可以使用以下信息:
-
self._owner是当前正在运行的策略因此,策略中的任何内容都可供 observer
-
原则中可用的缺省内部内容可能很有用:
broker->属性,用于访问策略创建订单的代理实例
如 所示
Broker,现金和投资组合价值是通过调用方法getcash和getvalue_orderspending->列出由策略创建的订单,并且经纪人已向策略通知事件。
BuySellobserver遍历清单,查找已运行(全部或部分)的订单,以创建给定时间点(索引 0)的平均运行价格_tradespending->交易清单(一组已完成的买入/卖出或卖出/买入对),该列表是根据买入/卖出订单编译的
Observer显然可以通过路径访问其他observersself._owner.stats。
自订订单观察器
标准BuySellobserver只关心已运行的操作。我们可以创建一个observer显示订单的创建时间和到期时间。
为了提高可见性,显示不会沿价格绘制,而是在单独的轴上绘制。
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
自定义 observer 只关心买入 订单,因为这是一种仅为了获利而买入的策略。卖出订单是市价订单,将立即运行。
Close-SMA 交叉策略更改为:
-
创建一个限价订单,其价格低于信号时刻 close 价格的1.0%
-
订单有效期为 7(日历)天
生成的图表。
在新子图表(红色方块)中可以看到,有几个订单已经过期,我们也可以理解,在“创建”和“运行”之间恰好有几天。
最后,此策略的代码应用新的observer
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 orderobserver import OrderObserver
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)
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)
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()
if __name__ == '__main__':
runstrat()
保存/保留统计信息
截至目前backtrader ,尚未实施任何机制来跟踪 observers 的值将其存储到档中。最好的方法:
-
在策略的方法中
startOpen档 -
在策略的方法中
next写下值
考虑到DrawDownobserver,它可以像这样完成
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 的值,一旦处理完所有 observers ,就可以将写入档的自定义 observer 作为 last observer 添加到系统中,以将值保存到 csv 档。
注意
Writer功能可以自动运行此任务。