cerebro = bt.Cerebro()
cerebro.addstrategy(TestStrategy2)
codes=['600862.SH','300326.SZ','300394.SZ']
#加载最近两日交易数据
for code in codes:
feed = Addmoredata(dataname = get_data(code,'20200506'),name=code)
cerebro.adddata(feed)
cerebro.run()
数据查看:
class TestStrategy(bt.Strategy):
def __init__(self):
# 打印数据集和数据集对应的名称
print("-------------self.datas-------------")
print(self.datas)
print("-------------self.data-------------")
print(self.data._name, self.data) # 返回第一个导入的数据表格,缩写形式
print("-------------self.data0-------------")
print(self.data0._name, self.data0) # 返回第一个导入的数据表格,缩写形式
print("-------------self.datas[0]-------------")
print(self.datas[0]._name, self.datas[0]) # 返回第一个导入的数据表格,常规形式
print("-------------self.datas[1]-------------")
print(self.datas[1]._name, self.datas[1]) # 返回第二个导入的数据表格,常规形式
print("-------------self.datas[-1]-------------")
print(self.datas[-1]._name, self.datas[-1]) # 返回最后一个导入的数据表格
print("-------------self.datas[-2]-------------")
print(self.datas[-2]._name, self.datas[-2]) # 返回倒数第二个导入的数据表格
data1 = pd.read_csv('111.csv')
cerebro = bt.Cerebro()
st_date = datetime.datetime(2019,1,2)
ed_date = datetime.datetime(2021,1,28)
# 添加 600466.SH 的行情数据
datafeed1 = bt.feeds.PandasData(dataname=data1,
fromdate=st_date,
todate=ed_date)
cerebro.adddata(datafeed1, name='600466.SH')
# 添加 603228.SH 的行情数据
datafeed2 = bt.feeds.PandasData(dataname=data2,
fromdate=st_date,
todate=ed_date)
cerebro.adddata(datafeed2, name='603228.SH')
cerebro.addstrategy(TestStrategy)
rasult = cerebro.run()
# 访问第一个数据集的 close 线
self.data.lines.close # 可省略 lines 简写成:self.data.close
self.data.lines_close # 可省略 lines 简写成:self.data_close
# 访问第二个数据集的 open 线
self.data1.lines.close # 可省略 lines 简写成:self.data1.close
self.data1.lines_close # 可省略 lines 简写成:self.data1_close
# 注:只有从 self.datas 调用 line 时可以省略 lines,调用 indicators 中的 line 时不能省略
获取数据方法:
如果你能清楚的记住数据表格中每条线的位置,也可以通过索引位置(整数)来访问,同样支持简写形式:
1、完整形式:self.datas[X].lines[Y];
2、简写形式:self.dataX.lines[Y]、self.dataX_Y;
3、说明:X 对应单个数据表格在数据表格集合中的索引位置,Y 对应某条线在数据表格中的索引位置 。
class TestStrategy(bt.Strategy):
def __init__(self):
print("--------- 打印 self 策略本身的 lines ----------")
print(self.lines.getlinealiases())
print("--------- 打印 self.datas 第一个数据表格的 lines ----------")
print(self.datas[0].lines.getlinealiases())
# 计算第一个数据集的s收盘价的20日均线,返回一个 Data feed
self.sma = bt.indicators.SimpleMovingAverage(self.datas[0].close, period=20)
print("--------- 打印 indicators 对象的 lines ----------")
print(self.sma.lines.getlinealiases())
print("---------- 直接打印 indicators 对象的所有 lines -------------")
print(self.sma.lines)
print("---------- 直接打印 indicators 对象的第一条 lines -------------")
print(self.sma.lines[0])
def next(self):
print('验证索引位置为 6 的线是不是 datetime')
print(bt.num2date(self.datas[0].lines[6][0]))
# num2date() 作用是将数字形式的时间转为 date 形式
cerebro = bt.Cerebro()
st_date = datetime.datetime(2019,1,2)
ed_date = datetime.datetime(2021,1,28)
datafeed1 = bt.feeds.PandasData(dataname=data1,
fromdate=st_date,
todate=ed_date)
cerebro.adddata(datafeed1, name='600466.SH')
datafeed2 = bt.feeds.PandasData(dataname=data2,
fromdate=st_date,
todate=ed_date)
cerebro.adddata(datafeed2, name='603228.SH')
cerebro.addstrategy(TestStrategy)
rasult = cerebro.run()
1、索引规则:索引位置编号结合了时间信息,
0 号位置永远指向当前时间点的数据,
-1 号位置指向前一个时间点的数据,
然后依次回退 (backwards)-2、-3、-4、-5、......;
1 号位置指向下一天的数据,然后依次向前(forwards)2、3、4、......;
2、切片方法:get(ago=0, size=1) 函数,
其中 ago 对应数据点的索引位置,即从 ago 时间点开始往前取 size 个数据点。
默认情况下是取当前最新时点(ago=0)的那一个数据(size=1);
3、在编写策略时,上面提到的对数据点的索引切片操作一般在 next() 函数中涉及较多,
而 __init__() 中涉及较少,
因为__init__() 中一般是对 一整条 line 进行操作(运算)。
class TestStrategy(bt.Strategy):
def __init__(self):
self.count = 0 # 用于计算 next 的循环次数
# 打印数据集和数据集对应的名称
print("------------- init 中的索引位置-------------")
print("0 索引:",'datetime',self.data1.lines.datetime.date(0), 'close',self.data1.lines.close[0])
print("-1 索引:",'datetime',self.data1.lines.datetime.date(-1),'close', self.data1.lines.close[-1])
print("-2 索引",'datetime', self.data1.lines.datetime.date(-2),'close', self.data1.lines.close[-2])
print("1 索引:",'datetime',self.data1.lines.datetime.date(1),'close', self.data1.lines.close[1])
print("2 索引",'datetime', self.data1.lines.datetime.date(2),'close', self.data1.lines.close[2])
print("从 0 开始往前取3天的收盘价:", self.data1.lines.close.get(ago=0, size=3))
print("从-1开始往前取3天的收盘价:", self.data1.lines.close.get(ago=-1, size=3))
print("从-2开始往前取3天的收盘价:", self.data1.lines.close.get(ago=-2, size=3))
print("line的总长度:", self.data1.buflen())
def next(self):
print(f"------------- next 的第{self.count+1}次循环 --------------")
print("当前时点(今日):",'datetime',self.data1.lines.datetime.date(0),'close', self.data1.lines.close[0])
print("往前推1天(昨日):",'datetime',self.data1.lines.datetime.date(-1),'close', self.data1.lines.close[-1])
print("往前推2天(前日)", 'datetime',self.data1.lines.datetime.date(-2),'close', self.data1.lines.close[-2])
print("前日、昨日、今日的收盘价:", self.data1.lines.close.get(ago=0, size=3))
print("往后推1天(明日):",'datetime',self.data1.lines.datetime.date(1),'close', self.data1.lines.close[1])
print("往后推2天(明后日)", 'datetime',self.data1.lines.datetime.date(2),'close', self.data1.lines.close[2])
print("已处理的数据点:", len(self.data1))
print("line的总长度:", self.data0.buflen())
self.count += 1
cerebro = bt.Cerebro()
st_date = datetime.datetime(2019,1,2) # 起始时间
ed_date = datetime.datetime(2021,1,28) # 结束时间
datafeed1 = bt.feeds.PandasData(dataname=data1,
fromdate=st_date,
todate=ed_date)
cerebro.adddata(datafeed1, name='600466.SH')
datafeed2 = bt.feeds.PandasData(dataname=data2,
fromdate=st_date,
todate=ed_date)
cerebro.adddata(datafeed2, name='603228.SH')
cerebro.addstrategy(TestStrategy)
rasult = cerebro.run()
__init__() 中:
访问的是整条 line,索引编号也是对整条 line 上所有数据点进行编号的,
所以 0 号位置对应导入的行情数据中最晚的那个时间点 2021-01-28,
然后依次 backwards;
1 号位置对应最早的那个时间点 2019-01-02,
然后依次 forwards ;
通过 get() 切片时,如果是从 ago=0 开始取,不会返回数据,从其他索引位置开始取,能返回数据 。
next() 中:
1、由于 next() 是按回测时间点依次循环运行的,
所以 next() 中数据点的索引位置是随着回测依次推进而动态变化的:backwards 时对应回测过的、已处理过的那部分 line, forwards 时对应还未回测的那部分 line ;
2、在 next() 中,只要记住 0 是当前回测的时间点(今日),
然后站在当前时刻回首过往:-1 是昨日、-2 是前日,依次类推 ;或者站在当前时刻期盼未来:1 是明日、2 是明后日,以此类推 。
获取 line 长度:
1、self.data0.buflen() 返回整条线的总长度,固定不变;
2、在 next() 中调用 len(self.data0),返回的是当前已处理(已回测)的数据长度,会随着回测的推进动态增长。
DataFeeds 数据模块
默认的导入方式
step1:调用 DataFeeds 模块中的方法读取数据;
step2:将读取的数据传给大脑。
# 读取和导入 CSV 文件
data = bt.feeds.GenericCSVData(dataname='filename.csv', ...)
cerebro.adddata(data, name='XXX')
# 读取和导入 dataframe 数据框 - 方式1
data = bt.feeds.PandasData(dataname=df, ...)
cerebro.adddata(data, name='XXX')
# 读取和导入 dataframe 数据框 - 方式2
data = bt.feeds.PandasDirectData(dataname=df, ...)
cerebro.adddata(data, name='XXX')
# 以 GenericCSVData 为例进行参数说明(其他导入函数参数类似)
bt.feeds.GenericCSVData(
dataname='daily_price.csv', # 数据源,CSV文件名 或 Dataframe对象
fromdate=st_date, # 读取的起始时间
todate=ed_date, # 读取的结束时间
nullvalue=0.0, # 缺失值填充
dtformat=('%Y-%m-%d'), # 日期解析的格式
# 下面是数据表格默认包含的 7 个指标,取值对应指标在 daily_price.csv 中的列索引位置
datetime=0, # 告诉 GenericCSVData, datetime 在 daily_price.csv 文件的第1列
high=3,
low=4,
open=2,
close=5,
volume=6,
openinterest=-1) # 如果取值为 -1 , 告诉 GenericCSVData 该指标不存在
Backtrader 中的数据表格默认情况下包含 7 条 line,这 7 条 line 的位置也是固定的,
依次为 ('close', 'low', 'high', 'open', 'volume', 'openinterest', 'datetime') ,
那导入的数据表格必须包含这 7 个指标吗?指标的排列顺序也必须一致吗?
当然不是!其实你只要告诉 GenericCSVData、PandasData 、PandasDirectData 这 7 个指标在数据源中位于第几列,如果没有这个指标,那就将位置设置为 -1
(如果是dataframe, None 表示指标不存在,-1 表示按位置或名称自动匹配指标),所以你要做的是让 Backtrader 知道指标在数据源的哪个位置上 。
自定义读取函数
如果你觉得每次都要设置这么多参数来告知指标位置很麻烦,那你也可以重新自定义数据读取函数,
自定义的方式就是继承数据加载类 GenericCSVData、PandasData 再构建一个新的类,然后在新的类里统一设置参数:
class My_CSVData(bt.feeds.GenericCSVData):
params = (
('fromdate', datetime.datetime(2019,1,2)),
('todate', datetime.datetime(2021,1,28)),
('nullvalue', 0.0),
('dtformat', ('%Y-%m-%d')),
('datetime', 0),
('time', -1),
('high', 3),
('low', 4),
('open', 2),
('close', 5),
('volume', 6),
('openinterest', -1)
)
cerebro = bt.Cerebro()
data = My_CSVData(dataname='daily_price.csv')
cerebro.adddata(data, name='600466.SH')
rasult = cerebro.run()
新增指标
在回测时,除了常规的高开低收成交量这些行情数据外,还会用到别的指标,
比如选股回测时会用到很多选股因子(PE、PB 、PCF、......),那这些数据又该如何添加进 Backtrader 的数据表格呢?
往 Backtrader 的数据表格里添加指标,就是给数据表格新增列,也就是给数据表格新增 line:
以导入 DataFrame 为例,
在继承原始的数据读取类 bt.feeds.PandasData 的基础上
,设置 lines 属性和 params 属性,
新的 line 会按其在 lines 属性中的顺序依次添加进数据表格中,具体对照下面例子的输出部分:
class PandasData_more(bt.feeds.PandasData):
lines = ('pe', 'pb', ) # 要添加的线
# 设置 line 在数据源上的列位置
params=(
('pe', -1),
('pb', -1),
)
# -1表示自动按列明匹配数据,也可以设置为线在数据源中列的位置索引 (('pe',6),('pb',7),)
class TestStrategy(bt.Strategy):
def __init__(self):
print("--------- 打印 self.datas 第一个数据表格的 lines ----------")
print(self.data0.lines.getlinealiases())
print("pe line:", self.data0.lines.pe)
print("pb line:", self.data0.lines.pb)
data1['pe'] = 2 # 给原先的data1新增pe指标(简单的取值为2)
data1['pb'] = 3 # 给原先的data1新增pb指标(简单的取值为3)
# 导入的数据 data1 中
cerebro = bt.Cerebro()
st_date = datetime.datetime(2019,1,2)
ed_date = datetime.datetime(2021,1,28)
datafeed1 = PandasData_more(dataname=data1,
fromdate=st_date,
todate=ed_date)
cerebro.adddata(datafeed1, name='600466.SH')
cerebro.addstrategy(TestStrategy)
rasult = cerebro.run()
扩展PandasData类,加载更多列数据
#pandas的数据格式
from backtrader.feeds import PandasData
class Addmoredata(PandasData):
lines = ('turnover_rate','pe','pb',)
params = (('turnover_rate',7),('pe',8),('pb',9),)
扩展GenericCSVData加载csv格式数据
from backtrader.feeds import GenericCSVData
class AddCsvData(GenericCSVData):
lines = ('turnover_rate','pe','pb',)
params = (('turnover_rate',7),('pe',8),('pb',9),)
import backtrader as bt
from datetime import datetime
class TestStrategy1(bt.Strategy):
def log(self, txt, dt=None):
dt = dt or self.datas[0].datetime.date(0)
print('%s, %s' % (dt.isoformat(), txt))
def next(self):
self.log(f"换手率:{self.datas[0].turnover_rate[0]},\
市净率:{self.datas[0].pb[0]},市盈率:{self.datas[0].pe[0]}")
cerebro = bt.Cerebro()
cerebro.addstrategy(TestStrategy1)
feed = Addmoredata(dataname = get_data('300002.SZ','20200420'))
#如果是读取csv数据使用下式
#feed = AddCsvData(dataname = 'test.csv',dtformat=('%Y-%m-%d'))
cerebro.adddata(feed)
cerebro.run()
多只股票数据加载测试:
class TestStrategy2(bt.Strategy):
def log(self, txt, dt=None):
dt = dt or self.datas[0].datetime.date(0)
print('%s, %s' % (dt.isoformat(), txt))
def next(self):
for data in self.datas:
print(data._name)
self.log(f"换手率:{data.turnover_rate[0]},\
市净率:{data.pb[0]},市盈率:{data.pe[0]}")
cerebro = bt.Cerebro()
cerebro.addstrategy(TestStrategy2)
codes=['600862.SH','300326.SZ','300394.SZ']
#加载最近两日交易数据
for code in codes:
feed = Addmoredata(dataname = get_data(code,'20200506'),name=code)
cerebro.adddata(feed)
cerebro.run()