目录
一、认识backtrader中的代码
1.from future import (absolute_import, division, print_function,unicode_literals)
**
from future import (absolute_import, division, print_function,unicode_literals)
**
absolute_import:顾名思义为:加入绝对引入这个新特性。
说到绝对引入,当然就会想到相对引入。那么什么是相对引入呢?
其实个个引入,可以理解成绝对路径和相对路径的区别,例如C/C++语言中的头文件包含一样,#include""与#include<>的区别就是头文件查找路径先后的问题而已,所以就不过多介绍了。
- division:导入python未来支持的语言特征division(精确除法)。
当我们没有在程序中导入该特征时,"/“操作符执行的是截断除法(Truncating
Division),当我们导入精确除法之后,”/"执行的是精确除法,如下所示:
3/4
0
from future import division
3/4
0.75
当我们导入精确除法后,若要执行截断除法,可以使用"//"操作符:
3//4
0
- print_function:导入python未来支持的print函数。
python2.7
print “Hello world”
python3
print(“Hello world”)
- unicode_literals:
① Python的每个新版本都会增加一些新的功能,或者对原来的功能作一些改动。有些改动是不兼容旧版本的,也就是在当前版本运行正常的代码,到下一个版本运行就可能不正常了
② 从python 2.7到Python 3.x就有不兼容的一些改动,比如2.x里的字符串用’xxx’表示str,Unicode字符串用u’xxx’表示unicode,而在3.x中,所有字符串都被视为unicode,因此,写u’xxx’和’xxx’是完全一致的,而在2.x中以’xxx’表示的str就必须写成b’xxx’,以此表示“二进制字符串”。
要直接把代码升级到3.x是比较冒进的,因为有大量的改动需要测试。相反,可以在2.7版本中先在一部分代码中测试一些3.x的特性,如果没有问题,再移植到3.x不迟。
③ Python提供了__future__模块,把下一个新版本的特性导入到当前版本,于是我们就可以在当前版本中测试一些新版本的特性。举例说明如下:
④ 为了适应Python 3.x的新的字符串的表示方法,在2.7版本的代码中,可以通过unicode_literals来使用Python 3.x的新的语法:在python3中默认的编码采用了unicode, 并取消了前缀u. 如果代码要兼容python2/3
2. params = ((‘x’,1))
# 参数定义
params = (
('maPeriod', 20),
)
这是一个标准的Python元组,它里面又包含一些元组,例如这个写法:
params = (
(‘myparam’, 27),
(‘exitbars’, 5),
)
3. print(‘%s, %s’ % (dt.isoformat(), txt))
def log(self, txt, dt=None):
dt = dt or self.datas[0].datetime.date(0)
print('%s, %s' % (dt.isoformat(), txt))
print(‘%s, %s’ % (dt.isoformat(), txt))
就是把日期跟txt连接起来,如: print(‘Hello’,‘World!’)-> Hello World!
之后调用这个log函数,就会自动添加日期到txt前面
4. self.datas[0].close[0]

把上面代码拆分下:
‘1’ -> self.datas
‘2’ -> selft.datas[0]
‘3’ -> self.datas[0].close
‘4’ -> self.datas[0].close[0]
它们分别是什么意思呢?
我们在代码中依次添加,打印结果看看究竟
def __init__(self):
# 获得均线策略的均线值
self.ma = bt.indicators.SimpleMovingAverage(self.datas[0], period=self.params.maPeriod)
# 保存对收盘价线最新数据的引用
self.dataclose = self.datas[0].close
print('data',self.datas)
print('data[0]',self.datas[0])
print('dataclose',self.datas[0].close)
print('dataclose0',self.datas[0].close[0])
print('dataclose-1',self.datas[0].close[-1])
print('dataclose-2',self.datas[0].close[-2])
exit()
打印结果:
data [<backtrader.feeds.pandafeed.PandasData object at 0x0000023A98E919C0>]
data[0] <backtrader.feeds.pandafeed.PandasData object at 0x0000023A98E919C0>
dataclose <backtrader.linebuffer.LineBuffer object at 0x0000023A98E91120>
dataclose0 1183.0
dataclose-1 1185.8
dataclose-2 1163.0
从log中可以看出 1和2属于 pandafeed, 3属于LineBuffer,4属于具体的值
- Data Feed 数据馈送对象,pandafeed也是一种Data Feed
Backtrader 中有一个“Data Feed” 或 “Data Feeds” 概念(可将其称为“数据馈送对象” ),其实这个“Data Feed” 或 “Data Feeds”就是我们熟悉的数据表格或数据表格集合 。Data Feed 在 Backtrader 中扮演一个“数据传递者”的角色,给策略有序的提供数据以及数据的索引位置
- self.datas
大家在策略函数中经常用到的 self.datas 属性就是一个 Data Feeds,对应通过 Cerebro 导入的行情数据表格的集合(可能只导入了一只证券的行情数据,也可能导入了 N 只证券的行情数据)。在这个集合中,数据表格是按照导入的顺序依次确定索引位置,第一个导入的数据表格的索引位置为 0 ,之后的依次递增,

从上面图可以很清楚看到self.datas就是传入backtrader框架中针对当前策略的所有行情数据,比如策略中需要传了3只股票的数据,self.datas就是这3只股票的数据集,self.data[0],则就对应着传入的第一只股票的行情数据表格。
- self.datas[0].close
列是“lines”,Backtrader 将数据表格的列拆成了一个个 line 线对象,一列,一个指标,’该指标的时间序列都可以作为一条线 line。Backtrader 默认情况下要求导入的数据表格要包含 7 个字段:‘datetime’、 ‘open’、 ‘high’、 ‘low’、 ‘close’、 ‘volume’、 ‘openinterest’ ,这 7 个字段序列就对应了 7 条 line 。其实给列赋予“线”的概念也很好理解,回测过程中用到的时间序列行情数据可视化后就是一条条曲线:close 曲线、 open 曲线、high 曲线 …

- self.datas[0].close[0]
self.datas[0].close[0]并不是代表line中的最后一条数据。指backtrader next方法运行到当前bar(行是“Bars”,当前bar可以看成当前行或者当前k线/当前日期)的收盘价。
所以索引 0 号位置永远指向当前时间点的数据,-1 号位置指向前一个时间点的数据

5. def notify_order(self, order)
def notify_order(self, order):
# 如果订单在提交或者允许的情况下,直接返回
if order.status in [order.Submitted, order.Accepted]:
# 做多/做空 订单 已提交/已执行 到/被代理 - 无事可做
return
# 检查订单是否已经完成
# 注意:如果没有足够资金,代理可能拒绝订单
# 如果下单完成了
if 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))
elif order.issell():
self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
(order.executed.price,
order.executed.value,
order.executed.comm))
# 如果下单的情况在另外状态:订单取消/保证金不足/拒绝
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
self.log('Order Canceled/Margin/Rejected')
# Write down: no pending order
# 将订单状态重置为None
self.order = None
在订单状态发生变化时,触发策略的 notify_order方法,在方法中可以查询订单状态相关信息,或执行一些动作。该方法在next方法前触发
在 def notify_order(self, order): 方法中,通过参数order 访问 order信息
order.status 订单状态(int)
order.getstatusname() 订单状态名称
order.getstatusname(order.status) 通过订单状态int,获取订单状态名称
订单状态 0~8
[Created、Submitted、Accepted、Partial、Completed、Canceled、Expired、Margin、Rejected]
在订单通知事件中,可以通过 order.executed.price、order.executed.size 访问订单价格和执行数量。
order.isbuy() 、order.issell() 判断是买卖单。
order.alive() 判断是否在状态 Partial 或 Accepted 中。
order.data 可以访问该订单的行情数据对象。
6. def notify_trade(self, trade):
# 通知每笔交易收益情况(可省略)
def notify_trade(self, trade):
# 没有交易的话,直接return
if not trade.isclosed:
return
# 交易结束,输出信息,第一个为毛利润,第二个为净利润(扣除手续费)
self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %
(trade.pnl, trade.pnlcomm))
每笔交易完会通知收益情况,比如这笔交易是盈利多少(trade.pnl),扣除手续费盈利多少(trade.pnlcomm)。
如下面log情况
7.def next(self):
# 每个bar都会执行一次,即回测的每个日期都会执行一次
def next(self):
# 检查订单是否挂起。。。如果是,我们无法发送第二个
if self.order:
print('===有正在执行的订单', self.order)
return
# 空仓
if not self.position.size:
if self.datas[0].close[0] >= self.ma[0]:
self.order = self.buy(size=200)
print(f"====买入>>>买入价{self.datas[0].close[0]} > 均线价{self.ma[0]} 买入后仓位={self.getposition().size}")
else:
if self.datas[0].close[0] < self.ma[0]:
self.order = self.sell(size=200)
print(f"====卖出>>>买出价{self.datas[0].close[0]} < 均线价{self.ma[0]} 买入后仓位={self.getposition().size}")
主逻辑函数,根据何种逻辑进行买卖,还有什么时候买卖都是写到这个函数中
总结
`要真正掌握一个东西,还真得细嚼。
通过理解每句代码的含义,可以让你更加深刻的理解backtrader框架,包括它是如何设计的。
本文详细剖析了backtrader回测框架中的关键代码,包括`from future import`导入的特性,参数设置,打印函数,数据访问方式,以及订单和交易通知函数的用法。通过理解这些核心概念,有助于深入理解和应用backtrader进行股票量化交易策略的开发。

3399

被折叠的 条评论
为什么被折叠?



