0%

backtrader回测框架

用了很多量化平台,比如通达信,聚宽,但是感觉不得劲,最近几天尝试了一下backtrader这个框架,感觉很不错,但是文档感觉写的不是很好,暂时准备用这个框架搭建一下自己的量化交易框架。

构造akshare接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def akshare_data_get(code, adjust, start_date, end_date):
stock_hfq_df = ak.stock_zh_a_hist(symbol=code, adjust=adjust, start_date=start_date, end_date=end_date).iloc[:, :6]
# 处理字段命名,以符合 Backtrader 的要求
stock_hfq_df.columns = [
'date',
'open',
'close',
'high',
'low',
'volume',
]
# 把 date 作为日期索引,以符合 Backtrader 的要求
stock_hfq_df.index = pd.to_datetime(stock_hfq_df['date'])
start_date = datetime(int(start_date[0:4]), int(start_date[4:6]), int(start_date[6:8])) # 回测开始时间
end_date = datetime(int(end_date[0:4]), int(end_date[4:6]), int(end_date[6:8])) # 回测结束时间
data = bt.feeds.PandasData(dataname=stock_hfq_df, fromdate=start_date, todate=end_date) # 规范化数据格式
print("下载并构造好了数据")
return data, start_date, end_date

定义策略

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
class MyStrategy(bt.Strategy):
"""
主策略程序
"""
params = (("n_days_before", 12),
('printlog', True),) # 全局设定交易策略的参数, maperiod是 MA 均值的长度

def __init__(self):
"""
初始化函数
"""
self.data_close = self.datas[0].close # 指定价格序列
# 初始化交易指令、买卖价格和手续费
self.order = None
self.buy_price = None
self.buy_comm = None
# 添加移动均线指标
# self.sma = bt.indicators.SimpleMovingAverage(
# self.datas[0], period=self.params.maperiod
# )

def next(self):
"""
主逻辑
"""

# self.log(f'收盘价, {data_close[0]}') # 记录收盘价
if self.order: # 检查是否有指令等待执行,
return
# 检查是否持仓
if not self.position: # 没有持仓
# pdb.set_trace()
# print(self.datas[0].datetime.date(0))
# 获取当前的资金
sizer = self.broker.get_cash() / self.datas[0].close[0]
# print("sizer: ", sizer)
# print("当前现金为:", sizer * self.datas[0].close[0])
# print("当前收盘价为:", self.datas[0].close[0])
# print("计算出来的sizer为:", sizer)
# 执行买入条件判断:收盘价格上涨突破15日均线
if self.data_close[-self.params.n_days_before] - self.data_close[-2] > 0.10 * self.data_close[-self.params.n_days_before]:
if self.data_close[0] > self.data_close[-1]:
if self.data_close[-1] > self.data_close[-2]:
# self.log("BUY CREATE, %.2f" % self.data_close[0])
# 执行买入
self.order = self.buy(size=sizer // 100 * 100)
pass
else:
# print(self.datas[0].datetime.date(0))
sizer = self.broker.getposition(data = self.datas[0]).size
# 获取当前持仓的手股数
# print("当前持仓为:", sizer)
# print(50*'#')
# pdb.set_trace()
# print(self.data_close[0])
# print(self.broker.getposition(data = self.datas[0]).size)
# print(self.datas[0])
# 当前价格大于买入价格的10%则卖出
if self.data_close[0] > (1.0+0.10) * self.buyprice:
# 这里buyprice需要检查一下是否是购买的价格
# print("买入价格", self.buyprice)
# print(50*'#')
# self.log("SELL CREATE, %.2f" % self.data_close[0])
# 执行卖出
self.order = self.sell(size=sizer)
# self.order = self.order_target_percent(target=0.9)

def log(self, txt, dt=None, do_print=False):
"""
Logging function fot this strategy
"""
if self.params.printlog or do_print:
dt = dt or self.datas[0].datetime.date(0)
print('%s, %s' % (dt.isoformat(), txt))

def notify_order(self, order):
"""
记录交易执行情况
"""
# 如果 order 为 submitted/accepted,返回空
if order.status in [order.Submitted, order.Accepted]:
return
# 如果order为buy/sell executed,报告价格结果
if order.status in [order.Completed]:
if order.isbuy():
# print(50*"#")
# pdb.set_trace()
# self.log(
# f"买入:\n价格:{order.executed.price},\
# 成本:{-order.executed.value},\
# 手续费:{order.executed.comm}"
# )
self.log("买入价格:%.2f"% order.executed.price)
self.log("买入股数:%.2f"% order.executed.size)
self.log("买入成本:%.2f"% -order.executed.value)
self.log("手续费:%.2f"% -order.executed.comm)
self.buyprice = order.executed.price
self.buycomm = order.executed.comm
else:
self.log("卖出价格:%.2f"% order.executed.price)
self.log("卖出股数:%.2f"% order.executed.size)
# pdb.set_trace()
self.log("卖出成本:%.2f"% (-order.executed.price * order.executed.size))
self.log("手续费:%.2f"% -order.executed.comm)
self.bar_executed = len(self)

# 如果指令取消/交易失败, 报告结果
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
self.log("交易失败")
self.order = None

def notify_trade(self, trade):
"""
记录交易收益情况
"""
if not trade.isclosed:
return
self.log(f"策略收益:\n毛收益 {trade.pnl:.2f}, 净收益 {trade.pnlcomm:.2f}")
self.log(50*"#")

def stop(self):
"""
回测结束后输出结果
"""
self.log("(与前%2d日数据对比) 期末总资金 %.2f" % (self.params.n_days_before, self.broker.getvalue()), do_print=True)

设置参数

1
2
3
4
5
6
7
code = "002594"
adjust = "hfq"
start_cash = 100000
stake = 100
commission_fee = 0.005
start_date_str = "20210101"
end_date_str = "20220101"

开始回测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 获取数据
data, start_date, end_date = akshare_data_get(code=code, adjust=adjust, start_date=start_date_str, end_date=end_date_str)

# 创建主控制器
cerebro = bt.Cerebro()

# 将数据加载至回测系统
cerebro.adddata(data)

# 讲策略加载至回测系统
cerebro.addstrategy(MyStrategy)

# broker设置资金
cerebro.broker.setcash(start_cash)

# broker手续费
cerebro.broker.setcommission(commission=commission_fee)

# 设置买入数量
cerebro.addsizer(bt.sizers.FixedSize, stake=stake)

print("期初总资金: %.2f" % cerebro.broker.getvalue())
cerebro.run(maxcpus=8)
print("期末总资金: %.2f" % cerebro.broker.getvalue())

backtrader逻辑分析

得分析一下backtrader的源码才能更好了解,网上那些文档都没有写清楚,后续再补充吧

以002594比亚迪进行回测分析

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
期初总资金: 100000.00
2021-01-11, 买入价格:235.59
2021-01-11, 买入股数:400.00
2021-01-11, 买入成本:-94236.00
2021-01-11, 手续费:-471.18
2021-01-26, 卖出价格:258.00
2021-01-26, 卖出股数:-400.00
2021-01-26, 卖出成本:103200.00
2021-01-26, 手续费:-516.00
2021-01-26, 策略收益:
毛收益 8964.00, 净收益 7976.82
2021-01-26, ##################################################
2021-03-03, 买入价格:206.00
2021-03-03, 买入股数:500.00
2021-03-03, 买入成本:-103000.00
2021-03-03, 手续费:-515.00
2021-06-15, 卖出价格:232.54
2021-06-15, 卖出股数:-500.00
2021-06-15, 卖出成本:116270.00
2021-06-15, 手续费:-581.35
2021-06-15, 策略收益:
毛收益 13270.00, 净收益 12173.65
2021-06-15, ##################################################
2021-07-22, 买入价格:247.30
2021-07-22, 买入股数:400.00
2021-07-22, 买入成本:-98920.00
2021-07-22, 手续费:-494.60
2021-08-03, 卖出价格:300.00
2021-08-03, 卖出股数:-400.00
2021-08-03, 卖出成本:120000.00
2021-08-03, 手续费:-600.00
2021-08-03, 策略收益:
毛收益 21080.00, 净收益 19985.40
2021-08-03, ##################################################
2021-09-27, 买入价格:255.15
2021-09-27, 买入股数:500.00
2021-09-27, 买入成本:-127575.00
2021-09-27, 手续费:-637.88
2021-10-18, 卖出价格:285.40
2021-10-18, 卖出股数:-500.00
2021-10-18, 卖出成本:142700.00
2021-10-18, 手续费:-713.50
2021-10-18, 策略收益:
毛收益 15125.00, 净收益 13773.62
2021-10-18, ##################################################
2021-12-23, 买入价格:283.18
2021-12-23, 买入股数:500.00
2021-12-23, 买入成本:-141590.00
2021-12-23, 手续费:-707.95
2021-12-31, (与前12日数据对比) 期末总资金 146246.54
期末总资金: 146246.54

Todo

  1. 因为我主要做A股,把印花税之类的完善一下,当前这个税是不对的
If you like my blog, please donate for me.