0%

backtrader入门

1. 简介

backtrader 是一个用于回测和交易的python框架,它功能丰富,可以让你聚焦在设计可重用的交易策略、指标和分析上,而不用花大量时间在构建基础框架上面。

优点:

  • github开源,策略编写简单快速
  • 安装方便,除了matplotlib外,不依赖其他外部lib
  • 支持ib等券商实时交易
  • 数据来源支持csv文件,在线数据源或pandas格式,同时支持多数据来源、多策略
  • 支持TA-lib指标,方便支持自定义指标的开发,集成pyfolio分析模块等
  • 支持品种多,运行速度快:pandas 矢量运算、多策略并行运算

缺点:

  • gpl 3.0授权,更改框架需要开源
  • 画图界面风格比较老旧
  • 框架代码抽象比较多,使用了大量的元编程,学习比较费劲

2. 安装

1
2
3
4
pip install backtrader
git clone https://github.com/mementum/backtrader.git
cd samples/commission-schemes
python commission-schemes.py --plot

出现如下提示&图形,表明安装正常。

1
2
3
4
5
6
7
8
2006-03-09, BUY CREATE, 3757.59
2006-03-10, BUY EXECUTED, Price: 3754.13, Cost: 2000.00, Comm 2.00
2006-04-11, SELL CREATE, 3788.81
2006-04-12, SELL EXECUTED, Price: 3786.93, Cost: 2000.00, Comm 2.00
2006-04-12, TRADE PROFIT, GROSS 328.00, NET 324.00
2006-04-20, BUY CREATE, 3860.00
2006-04-21, BUY EXECUTED, Price: 3863.57, Cost: 2000.00, Comm 2.00
.......

图形
如果出现如下错误,说明matplotlib安装不对,ImportError: cannot import name 'warnings' from 'matplotlib.dates'
解决办法:pip install matplotlib==3.2.2 -i https://pypi.tuna.tsinghua.edu.cn/simple

3. 代码框架介绍

backtrader代码运行主要包括以下组成部分:

  1. Strategy交易策略模块:需要设计交易策略,根据信号进行买入/卖出
  • 设计策略中用于生成交易信号的指标
  • 编写买入、卖出的交易逻辑
  • 按需打印交易信息
  1. DataFeeds数据模块: 将目标金融数据加载到回测框架中
  2. Cerebro回测框架设置:根据需要设置初始资金,佣金,数据来源,交易策略,交易头寸大小。通过Broker经纪商模块设置
  3. 运行回测:运行Cerebro回测并打印出所有已执行的交易
  4. 按需添加策略分析指标Analyzers或观测器Observers评估效果: 以图形和风险收益等指标对交易策略的回测结果进行评价

Lines说明:Lines是backtrader回测的数据,由一系列的点组成,通常包括以下类别的数据:Open(开盘价), High(最高价), Low(最低价), Close(收盘价), Volume(成交量), OpenInterest(未平仓量,没有的话设置为0)。Data Feeds(数据加载)、Indicators(技术指标)和Strategies(策略)都会生成 Lines。价格数据中的所有”Open” (开盘价)按时间组成一条 Line。所以,一组含有以上6个类别的价格数据,共有6条 Lines。如果算上“DateTime”(时间,可以看作是一组数据的主键),一共有7条 Lines。当访问一条 Line 的数据时,会默认指向下标为 0 的数据,用于访问当前时刻。backtrader会将”0”一直指向当前值,下标 -1 来访问上一个值。比如:

1
2
3
self.dataclose[0] # 当日的收盘价
self.dataclose[-1] # 昨天的收盘价
self.dataclose[-2] # 前天的收盘价

其他说明:

1
2
3
4
5
6
7
self.sma5 = bt.indicators.SimpleMovingAverage(
self.datas[0], period=5) # 返回MA5的相关数据

cerebro.broker.setcash(100000.0) # 设定初始资金
cerebro.broker.setcommission(0.005) # 每次交易都需要支付一定的佣金
cerebro.broker.getvalue() # 目前的资金
cerebro.addsizer(bt.sizers.FixedSize, stake=100) # 设定每次交易买入的股数

以commission-schemes.py为例进行说明,代码如下,对应位置加上了注释说明。

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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
import argparse 
import datetime

# 导入backtrader相关包
import backtrader as bt
import backtrader.feeds as btfeeds
import backtrader.indicators as btind

# 交易策略
class SMACrossOver(bt.Strategy):
params = (
('stake', 1), # 交易股数
('period', 30), # 周期长度
)

def log(self, txt, dt=None):
''' Logging function fot this strategy'''
dt = dt or self.datas[0].datetime.date(0)
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
return

# Check if an order has been completed
# Attention: broker could reject order if not enougth cash
if order.status in [order.Completed, order.Canceled, order.Margin]:
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))

# 打印交易信息,可选
def notify_trade(self, trade):
if trade.isclosed:
self.log('TRADE PROFIT, GROSS %.2f, NET %.2f' %
(trade.pnl, trade.pnlcomm))

# 初始化策略属性、指标,必须
def __init__(self):
sma = btind.SMA(self.data, period=self.p.period)
# > 0 crossing up / < 0 crossing down
self.buysell_sig = btind.CrossOver(self.data, sma)

# 交易逻辑实现,必须
def next(self):
if self.buysell_sig > 0:
self.log('BUY CREATE, %.2f' % self.data.close[0])
self.buy(size=self.p.stake) # keep order ref to avoid 2nd orders

elif self.position and self.buysell_sig < 0:
self.log('SELL CREATE, %.2f' % self.data.close[0])
self.sell(size=self.p.stake)


def runstrategy():
args = parse_args()

# 实例化cerebro
cerebro = bt.Cerebro()

# Get the dates from the args
fromdate = datetime.datetime.strptime(args.fromdate, '%Y-%m-%d')
todate = datetime.datetime.strptime(args.todate, '%Y-%m-%d')

# 从csv文件读取数据
data = btfeeds.BacktraderCSVData(
dataname=args.data,
fromdate=fromdate,
todate=todate)

# 将数据传给cerebro
cerebro.adddata(data)

# 添加策略
cerebro.addstrategy(SMACrossOver, period=args.period, stake=args.stake)

# 设置初始资金
cerebro.broker.setcash(args.cash)

commtypes = dict(
none=None,
perc=bt.CommInfoBase.COMM_PERC,
fixed=bt.CommInfoBase.COMM_FIXED)

# 设置交易佣金
cerebro.broker.setcommission(commission=args.comm,
mult=args.mult,
margin=args.margin,
percabs=not args.percrel,
commtype=commtypes[args.commtype],
stocklike=args.stocklike)

# 启动回测
cerebro.run()

# 绘图
if args.plot:
cerebro.plot(numfigs=args.numfigs, volume=False)


# 从命令行解析参数
def parse_args():
parser = argparse.ArgumentParser(
description='Commission schemes',
formatter_class=argparse.ArgumentDefaultsHelpFormatter,)

parser.add_argument('--data', '-d',
default='../../datas/2006-day-001.txt',
help='data to add to the system')

parser.add_argument('--fromdate', '-f',
default='2006-01-01',
help='Starting date in YYYY-MM-DD format')

parser.add_argument('--todate', '-t',
default='2006-12-31',
help='Starting date in YYYY-MM-DD format')

parser.add_argument('--stake', default=1, type=int,
help='Stake to apply in each operation')

parser.add_argument('--period', default=30, type=int,
help='Period to apply to the Simple Moving Average')

parser.add_argument('--cash', default=10000.0, type=float,
help='Starting Cash')

parser.add_argument('--comm', default=2.0, type=float,
help=('Commission factor for operation, either a'
'percentage or a per stake unit absolute value'))

parser.add_argument('--mult', default=10, type=int,
help='Multiplier for operations calculation')

parser.add_argument('--margin', default=2000.0, type=float,
help='Margin for futures-like operations')

parser.add_argument('--commtype', required=False, default='none',
choices=['none', 'perc', 'fixed'],
help=('Commission - choose none for the old'
' CommissionInfo behavior'))

parser.add_argument('--stocklike', required=False, action='store_true',
help=('If the operation is for stock-like assets or'
'future-like assets'))

parser.add_argument('--percrel', required=False, action='store_true',
help=('If perc is expressed in relative xx% rather'
'than absolute value 0.xx'))

# -p或--plot 需要画图
parser.add_argument('--plot', '-p', action='store_true',
help='Plot the read data')

parser.add_argument('--numfigs', '-n', default=1,
help='Plot using numfigs figures')

return parser.parse_args()

# 主函数
if __name__ == '__main__':
runstrategy()

交流

以下是发文章的几个地方。欢迎关注微信公众号,最新的文章会优先发布在微信公众号上。

参考

坚持原创分享,您的支持将鼓励我继续创作更多优质内容!