关于程序员:Backtrader-数据篇

55次阅读

共计 7553 个字符,预计需要花费 19 分钟才能阅读完成。

Backtrader 数据篇

上一篇曾经学习了整个框架的大略划分模块,对于 Backtrader 这框架有了一个大略的意识,这篇将学习这框架基石 – 数据流程。<br/>

上面将会一一解答对于这框架 数据方面的疑难 <br/>
怎么将数据填充到框架种?<br/>
数据组织模式是怎么样?<br/>
怎么拜访数据?<br/>
在回测的过程种,数据流是以怎么模式传递?<br/>
怎么扩大其余回测指标?<br/>

数据填充

在简介篇,曾经晓得所有的模块有大脑对立管制治理,数据模块也不例外,所以首先要实例化 Cerebro,而后往这 Cerebro 塞数据,再对立调度治理。

上面数据是以 dataframe 数据格式为例的介绍,当然 backtrader 也反对其余数据标准的格局,都差不多。

backtrader 把标准的数据包装成了 Data Feeds , 其实就是一个回测数据汇合治理类。数据能够增加一个或者多个股票数据。

如何填充数据?

只须要指定数据源,回测时间段,即可增加多个股票数据源。

参数 形容
dataname 数据源
fromdate 回测开始工夫
todate 回测完结工夫

示例代码


# 股票查问开始和完结工夫
start_query = '2022-01-01'
end_query = '2022-09-01'
# 回测开始和完结工夫
start_date = datetime.datetime(2022, 7, 10)
end_date = datetime.datetime(2022, 8, 10)
cerebro = bt.Cerebro()
# 增加几个股票数据
codes = [
    '300015',
    '300347',
    '300760',
    '603127',
    '600438'
]
# 增加多个股票回测数据
for code in codes:
    data = sdb.stock_daily(code, start_query, end_query)
    data.index.names = ['datetime']
    date_feed = bt.feeds.PandasData(dataname=data, fromdate=start_date, todate=end_date)
    cerebro.adddata(date_feed, name=code)
    print('增加股票数据:code: %s' % code)

留神:查问的数据时间段与回测时间段是离开的,最终数据流是以回测时间段测试数据。

参数 name 是这张表的表名。
self.datas[0]._name 能够打印表名。

存储形式

把每个股票数据看作为一张表,其实就是一张指标维度和工夫维度的表,self.datas 就是汇合了多个股票的数据集,造成了一个三维数据源,别离是:数据表维度、工夫维度、指标维度。

如上图所示,Data Feeds 种的 self.datas 数据类型是 list. 每个元素是一张蕴含工夫维度和指标维度的股票数据表。

数据表维度

该维度其实就是 list 汇合,汇合了所有增加进来了股票数据,每个股票数据都有工夫维度和指标维度形成的数据表。<br/>
self.datas[N]

指标维度

该维度是回测时应用的指标,除了罕用指标还能够自定义指标。<br/>
self.datas[N].lines.xxx[M]

参数

字段 类型 形容
datetime float 日期,如果打印日期,<br/> 用 datetime.date[0]
open float 开盘价
high float 最高价
low float 最低价
close float 收盘价
volume float 成交量
openinterest float 持仓量,个别没应用
其余扩大指标 其余 自定义或扩大指标,列如 pe、pb

所有的指标

self.data.lines.getlinealiases()

控制台
>> lines ('close', 'low', 'high', 'open', 'volume', 'openinterest', 'datetime')

工夫维度

工夫维度其实是回测时间段,fromdate ~ todate 之间。<br/>
self.data[N].lines.datetime.date(M)

数据索引

self.datas 数据类型是 list<br/>

  1. 下标索引:self.datas[N],其中 N 为 0 到 N-1 时,是正向,N 为 -1 到 -N 时为反向
  2. 缩写索引,self.dataN,留神不是 datas。N 为 0 到 N-1。
  3. 表名索引,self.getdatabyname(‘name’),其中 name 为导入数据时 adddata(date_feed, name=code) 时设置的表名。
  4. 第一个数据集索引,self.datas[0] 等价 self.data0 等价 self.data<br/>

留神:self.datas 和 self.data 的区别,self.datas 是数据汇合 list. self.data 是第 0 个数据。self.dataN 的拜访是没有 s 的。

self.datas[N].lines

索引形式 <br/>
日期:self.datas[N].lines.datetime.date(N)
其余:self.datas[N].lines. 其余字段名(open、high、low、close、volume)[N]<br/>

留神:datetime 是以 float 数据类型存储,拜访是须要借助 xxx.date(N) 函数进行转换。

示例

# 拜访第一个数据表的 close 指标的索引 0
self.data[0].lines.close[0]

切片形式

self.data1.lines.close.get(ago=N, size=M))
参数 形容
ago 索引开始地位
size 切片大小

返回值:array 数组 [close[N-(M-1)],…,close[N-1],close[N]]<br/>
如果 N – (M – 1) <1,返回空数组 []。<br/>
列如:N = 3,M = 4,返回 [].

策略中的数据流

回测长度:N = self.data.buflen()<br/>

索引下标 0 的含意

索引下标 0 在 init() 函数和 next() 函数是不一样的,在 init() 函数中索引 0 是代表回测工夫 todate,在 next() 函数中 索引 0 是代表以后回测的工夫。

init()函数中的数据流

在 init() 函数中,索引 0 是 todate,索引 1 是 fromdate. 反对 正向和反向拜访的两种形式。

  • 正向索引的索引下标为 1、2 … N
  • 反向索引下标为 0、-1、-2 … -(N-1).

其中,对应下标值是雷同 <br/>

正向索引 反向索引
0 N
1 -(N-1)
2 -(N-2)
N-1 -1
N 0

next()函数中的数据流

在 next() 函数中,索引 0 永远是以后的工夫节点,索引 0 随着以工夫维度的循环,不停的挪动。backtrader 是曾经回测过的了,forward 是还没有回测到的。

自定义读取数据

如果你感觉每次都要设置这么多参数来告知指标地位很麻烦,那你也能够从新自定义数据读取函数,自定义的形式就是继承数据加载类 GenericCSVData、PandasData 再构建一个新的类,而后在新的类里对立设置参数:

class My_PandasData(bt.feeds.PandasData):
    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)
    )

新增指标

backtrader 除了提供根本的指标外(‘close’, ‘low’, ‘high’, ‘open’, ‘volume’, ‘openinterest’, ‘datetime’),还提供给用户自定义扩大。例如:macd,pe,pb 等等指标。<br/>

如何扩大?<br/>

  1. 继承 bt.feeds.PandasData 类,定义新指标。
  2. 数据源扩大新指标对应的列,并初始化新指标值。

示例

# 继承
class PandasData_more(bt.feeds.PandasData):
    lines = ('pe', 'pb',)  # 要增加的线
    # 设置 line 在数据源上的列地位
    params = (('pe', -1),
        ('pb', -1),
    )

# 数据源新增列
# 股票查问开始和完结工夫
start_query = '2022-01-01'
end_query = '2022-09-01'
# 回测开始和完结工夫
start_date = datetime.datetime(2022, 7, 11)
end_date = datetime.datetime(2022, 8, 10)
cerebro = bt.Cerebro()
# 增加几个股票数据
codes = [
    '300015',
    '300347',
    '300760',
    '603127',
    '600438'
]
# 增加多个股票回测数据
for code in codes:
    data = sdb.stock_daily(code, start_query, end_query)
    data.index.names = ['datetime']
    # 新增指标列
    data['pe'] = 3
    data['pb'] = 4
    date_feed = PandasData_more(dataname=data, fromdate=start_date, todate=end_date)
    cerebro.adddata(date_feed, name=code)
    print('增加股票数据:code: %s' % code)

cerebro.addstrategy(TestStrategy)
cerebro.run()

罕用函数手册

函数 形容
self.data.buflen() 回测总长度
len(self.data) 曾经解决的数据地位
self.data.lines.getlinealiases() 指标列表
bt.Strategy.init() 一次调用,策略初始化函数 <br/> 计算指标值,交易信号等耗时操作
bt.Strategy.next() 屡次调用,策略回测调用函数 <br/> 循环所有的回测时间段
self.datas[N].lines.datetime.date(N) 日期索引
self.datas[N].lines.close.date(N) 指标 close 索引
self.getdatabyname(‘name’) 表名索引,表名倡议以 code 命名
self.dataT.lines.close.get(ago=N, size=M)) 切片函数
bt.num2date() 工夫 datatime 格局将其转为“xxxx-xx-xx xx:xx:xx”这种模式

测试示例

import datetime

import backtrader as bt

import stock_db as sdb


class PandasData_more(bt.feeds.PandasData):
    lines = ('pe', 'pb',)  # 要增加的线
    # 设置 line 在数据源上的列地位
    params = (('pe', -1),
        ('pb', -1),
    )


class My_PandasData(bt.feeds.PandasData):
    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)
    )


class TestStrategy(bt.Strategy):
    # 可选,设置回测的可变参数:如挪动均线的周期
    # params = (#     (..., ...),  # 最初一个“,”最好别删!# )

    def log(self, txt, dt=None):
        """
        可选,构建策略打印日志的函数:可用于打印订单记录或交易记录等
        :param txt:
        :param dt:
        :return:
        """
        dt = dt or self.datas[0].datetime.date(0)
        print('%s,%s' % (dt.isoformat(), txt))

    def __init__(self):
        """必选,初始化属性、计算指标等"""

        print(type(self.datas[0].lines.datetime[0]))
        print(type(self.datas[0].lines.volume[0]))
        self.count = 0
        print("------------- init 中的索引地位 -------------")
        print('lines', self.data1.lines.getlinealiases())
        print('datas type', type(self.datas))
        print("0 索引:", 'datetime', self.datas[1].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=3, size=3))
        print("从 - 2 开始往前取 3 天的收盘价:", self.data1.lines.close.get(ago=-2, size=3))
        print("回测的总长度:", self.data.buflen())
        pass

    def notify_order(self, order):
        """
        可选,打印订单信息
        :param order:
        :return:
        """
        pass

    def notify_trade(self, trade):
        """
        可选,打印交易信息
        :param trade:
        :return:
        """
        pass

    def next(self):
        """
        必选,编写交易策略逻辑
        :return:
        """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.data))
        print("回测的总长度:", self.data.buflen())
        self.count += 1
        pass


# 股票查问开始和完结工夫
start_query = '2022-01-01'
end_query = '2022-09-01'
# 回测开始和完结工夫
start_date = datetime.datetime(2022, 7, 11)
end_date = datetime.datetime(2022, 8, 10)
cerebro = bt.Cerebro()
# 增加几个股票数据
codes = [
    '300015',
    '300347',
    '300760',
    '603127',
    '600438'
]
# 增加多个股票回测数据
for code in codes:
    data = sdb.stock_daily(code, start_query, end_query)
    data.index.names = ['datetime']
    # 新增指标列
    data['pe'] = 3
    data['pb'] = 4
    date_feed = PandasData_more(dataname=data, fromdate=start_date, todate=end_date)
    cerebro.adddata(date_feed, name=code)
    print('增加股票数据:code: %s' % code)

cerebro.addstrategy(TestStrategy)
cerebro.run()
# cerebro.plot()

if __name__ == '__main__':
    pass

写于 2022 年 10 月 05 日 14:24:59

本文由 mdnice 多平台公布

正文完
 0