量化交易学习(三十七)backtrader文档——观察者(2)

今天这篇是backtrader文档的学习笔记。主要介绍了观察者。

官方文档链接:https://www.backtrader.com/docu/observers-and-statistics/observers-and-statistics/#developing-observers

开发观察者

Broker观察者的实现如上一篇文中所示。为了创建有意义的观察者,实现需要使用以下信息:

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

这样,观察者可以获取策略中的任何内容

  • 策略中可能有用的默认内部事物:

    • broker -> 属性提供对策略创建订单所依据的经纪商实例的访问权限。

    Broker中所示,现金和投资组合价值是通过调用getcash方法和getvalue方法获取的。

    • _orderspending -> 列出由策略创建且经纪商已向策略通知事件的订单。

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

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

观察者显然可以通过self._owner.stats访问其他观察者。

自定义订单观察者

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

为了保证可见性,图像不会沿着价格绘制,而是在单独的轴上绘制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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 CrossOver 策略更改为:

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

  • 有效期为 7(日历)天

由此产生的图表。

0d26047b8415c6ce2629c3d0a6f3b437.png

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
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尚未实现任何机制来跟踪将观察者的值存储到文件中。最好的方法是:

  • 在策略的start方法中打开文件
  • 在策略的next方法中将值记录下来

考虑到DrawDown观察者,可以这样做:

1
2
3
4
5
6
7
8
9
10
11
12
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 方法可以自动执行此任务。


这一篇就到这里啦。欢迎大家点赞、转发、私信。还没有关注我的朋友可以关注 江达小记

江达小记