量化交易学习(六十)backtrader日内交易回测

一直想试试用backtrader实现日内交易(做T)的回测,之前一直用的是日线的回测,日线回测写起来最简单,不用考虑T+1,不过它的所有操作要等到下一天开盘才能操作,时间上会滞后很多,很多时候策略给出了正确的信号,但是下单时晚了一天结果就大不相同了。

今天这篇实现一个最简单的日内交易回测,一开盘就买,收盘前卖出,不做选股和择时,为之后搭建日内交易策略作基础。

数据选用2024年1月2日至2024年5月29日的1分钟线数据。策略代码 strategy.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
import datetime
import backtrader as bt
import traceback

class IntraDayStrategy(bt.Strategy):
def __init__(self):
self.order=None
self.trade = None
self.myposition={'buy_date':None,'size':0,'available_size':0}

def log(self, txt, dt=None):
dt = dt or self.datas[0].datetime.datetime(0)
print('%s, %s' % (dt.isoformat(), txt))
# 记录交易收益情况
def notify_trade(self, trade):
if trade.isclosed:
print('毛收益 %0.2f, 扣佣后收益 % 0.2f, 佣金 %.2f, 市值 %.2f, 现金 %.2f' %
(trade.pnl, trade.pnlcomm, trade.commission, self.broker.getvalue(), self.broker.getcash()))


def notify_order(self, order):
if order.status in [order.Submitted, order.Accepted]:
# 订单状态 submitted/accepted,无动作
return

# 订单完成
if order.status in [order.Completed]:
if order.isbuy():
self.log('买单执行,%s, %.2f, %i' % (order.data._name,
order.executed.price, order.executed.size))
self.myposition['buy_date']=order.data.datetime.date(0)
# 更新可用仓位
self.myposition['available_size']=self.myposition['size']
# 更新刚买入的仓位
self.myposition['size']=order.executed.size

elif order.issell():
self.log('卖单执行, %s, %.2f, %i' % (order.data._name,
order.executed.price, order.executed.size))
self.myposition['buy_date']=None
self.myposition['available_size']=0 # 重置可用仓位
print('佣金 %.2f, 市值 %.2f, 现金 %.2f' %
( order.executed.comm, self.broker.getvalue(), self.broker.getcash()))

else:
self.log('订单作废 %s, %s, isbuy=%i, size %i, open price %.2f' %
(order.data._name, order.getstatusname(), order.isbuy(), order.created.size, order.data.open[0]))
self.order=None # 重置订单状态

def next(self):
if self.order:
return
# 没有当天没有买入则一开盘就买入
# 有持仓则收盘前1分钟平昨日仓
if self.myposition['buy_date']!=self.data.datetime.date(0):
cash = self.broker.get_cash()
# 不全仓买入,以实现日内交易,避免下一天买入时资金不够
size=0
if cash > 90000:
size = int(cash * 0.40 / 100 / self.data.close[0]) * 100
else :
size = int(cash * 0.90 / 100 / self.data.close[0]) * 100

try:
if(self.data.datetime.time() == datetime.time(hour=9,minute=31,second=00)):
self.buy(size=size,exectype=bt.Order.Market)
except Exception:
traceback.print_exc()
else:

# 收盘前一分钟平可用仓
if self.data.datetime.time() == datetime.time(hour=14,minute=58,second=00) \
and self.myposition['available_size'] > 0:
self.sell(size=self.myposition['available_size'])

main.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
from backtrader import bt
import datetime

import mysqlDataFeed
import strategy
# pip install python-dateutil
from dateutil.relativedelta import relativedelta

class EastMoneyCommission(bt.CommInfoBase):
# 交易费用=佣金+印花税+过户费=净佣金+交易规费+印花税+过户费
params = (
('dismiss5',False),# 佣金少于5块时免不免5
('commission',0.00015), # 佣金费率万分之1.5
('stamp_duty',0.0005), # 印花税 万分之5
('transfer_fee',0.00001), # 过户费 十万分之1
('transaction_fees',0.0000541), # 交易规费
('percabs',True) # 为True则使用小数,为False则使用百分数
)

def _getcommission(self, size, price, pseudoexec):
# 买入
if size>0:
fee= size*price * (self.p.commission+self.p.transaction_fees+self.p.transfer_fee)
# 卖出
elif size<0:
fee = -size * price * (self.p.commission + self.p.transaction_fees + self.p.stamp_duty + self.p.transfer_fee)
else:
return 0
# 是否免5
if self.params.dismiss5:
return fee
else:
if fee < 5:
return 5
return fee

def main(symbol):
cerebro = bt.Cerebro()
cerebro.addstrategy(strategy.IntraDayStrategy)
today=datetime.datetime.today()
data=mysqlDataFeed.MySQLData(
dataname=symbol,
timeframe=bt.TimeFrame.Minutes,
fromdate=today-relativedelta(months=6),
todate=today,
adj_base_date=today
)
cerebro.adddata(data)

comminfo = EastMoneyCommission(
dismiss5=True,
commission=0.00015,
stamp_duty=0,
transfer_fee=0,
transaction_fees=0
)

cerebro.broker.addcommissioninfo(comminfo)
cerebro.broker.setcash(100000)
cerebro.run()
cerebro.plot()


if __name__=='__main__':
print(datetime.datetime.now())
main('SHSE.510300')
print(datetime.datetime.now())

数据源的代码就不再贴了,大家可以看 量化交易学习(二十四)下载股票数据保存到数据库、量化交易学习(二十五)backtrader使用mysql数据源 这两篇文章,回测的结果如下:

Figure_1.png

58d9e6d25264a5499a326788f0201cbc.png
08fa320e710ba643955a8e3e5f2d5b92.png


希望这篇文章能帮助到大家。如果你有任何问题或建议,欢迎留言讨论,私信。感谢你的阅读,觉得不错,点个赞哦!还没有关注我的朋友可以关注 江达小记

江达小记