Backtrader从0到1——Data Feeds【入门篇】

Backtrader从0到1------Data Feeds【入门篇】

  • [0. 前言](#0. 前言)
  • [1. 数据源访问](#1. 数据源访问)
  • [2. 添加CSV格式数据](#2. 添加CSV格式数据)
  • [3. 参数持久化](#3. 参数持久化)
  • [4. 添加Pandas数据](#4. 添加Pandas数据)
  • [5. 总结](#5. 总结)

0. 前言


所谓Data Feeds,翻译过来就是数据推送。

笔者在学习Data Feeds模块时,发现官网上很多部分的代码都过分冗余,并且真正有实际应用意义的内容也不多。本文将着重介绍CSV格式文件的加载方式,以及pandas数据的加载方式。

笔者在加载这些数据时,已经形成了一套自己的体系,包括CSV文件内容格式,如何读取,以及自定义CSV类型参数持久化,都有自己的一套规范。希望大家在学习时,也能形成一套自己的使用体系。

学习完本文,将保证你能熟练的将CSV数据加载进Backtrader,处理不同级别时间的数据也将不是问题。

有一些提升部分,如低级别数据的聚合,本文将不会涉及,如有需要,也可以出一篇高级篇,专门介绍一下。这部分内容可用于搭建更复杂的交易系统(如多级别联动),但是对于单周期级别上的策略,就有点画蛇添足了。

话不多说,让我们进入Data Feeds的世界!!!


1. 数据源访问


通过Backtrader从0到1------第一个回测策略的学习,我们已经能将数据源lines添加进Cerebro实例中了。在策略中访问数据源有如下几种方式:

1. 访问lines

  • self.datas[i]表示访问插入的第ilines
  • self.data等价于self.data0等价于self.datas[0],表示访问插入的第一个lines,是一种便捷访问方式。

2. 访问lines中的特定line,也是特定列(以访问第一个lines中的line为例)

  • self.datas[0].列名,就可以拿到对应的列。一个列就是一个bar,一个line,它的indexBacktrader中特有的(上篇文章讲过)。

2. 添加CSV格式数据


1. 准备CSV数据

  • 这里选择从akshare中拉取豆油的30分钟k线数据。并保存为CSV格式。
python 复制代码
import akshare as ak

# 获取螺纹钢主力合约 1 分钟数据(代码格式:交易所_品种主力,如 "SHFE.rb主力")
df = ak.futures_zh_minute_sina(
    symbol="Y0",  	 # 合约代码
    period="30",      # 1/5/15/30/60 分钟
)

print(df.head())
df.to_csv('SoybeanOil.csv', index=False)
  • 查看CSV文件:
  • 可以看到拿到的数据一共有7列,其中hold是持仓量,还有一个翻译是openinterest
  • pd.to_csv时,切记带上index=True选项,不然你将看到8列数据,带一列自增索引。
  • 今后我们的CSV文件格式,就按图片上来,需要有一行标题行,一行中数据以逗号隔开。第一列是datetime,第二列是open,以此类推。(当然,文件中到底有没有标题行,以什么为分割符,都可以在读取时通过参数来控制,这里我统一认为文件要带标题行,以,为分割符,即是为了规范,也是为了好看)。

2. 加载CSV文件进Cerebro

  • 使用bt.feeds.GenericCSVData()方法,加载数据,保证加载的列和CSV文件中的列一一对应(这里只是为了规范一点,好看一点,不是语法要求):
python 复制代码
data = bt.feeds.GenericCSVData(dataname='SoybeanOil.csv',
                                        nullvalue=0.0,  # 空值替换为0.0
                                        dtformat=('%Y-%m-%d %H:%M:%S'), # 和csv文件中的日期格式适配
                                        
                                        datetime=0,     # 指定为第0列
                                        open=1,         # 指定为第1列 
                                        high=2,          # 以此类推
                                        low=3,
                                        close=4,
                                        volume=5,
                                        openinterest=6,
                                        timeframe=bt.TimeFrame.Minutes,  # 分钟级数据
                                )
  • 参数详解:
    • dataname:CSV文件路径或或类似文件的对象;
    • datetime:(默认值0)包含日期(或日期时间)字段的列;
    • open(默认值1),high(默认值2),low(默认值3), close(默认值4),volume(默认值5),openinterest (默认值6);
      • open=1为例,表示指定索引(Python索引)为1的列,列名为open
      • 如果传递负值(例如:-1),则表示该字段在CSV数据中不存在。
    • nullvalue:(默认值:float('NaN'))将空值替换为一个值,例如nullvalue=0.0就是将空值替换为0.0;
    • dtformat:(默认值:%Y-%m-%d %H:%M:%S)和CSV文件中日期的格式适配;
    • timeframe:(默认值:TimeFrame.Days)可选项有:Ticks, Seconds, Minutes, Days, Weeks, Months and Years。例子中我们加载的是30分钟级别数据,所以填bt.TimeFrame.Minutes
  • 没有用到的参数:
    • headers:(默认值:True)指示传递的数据是否有初始标题行;
    • separator:(默认: ",")指定分隔符。
    • time:(默认值:-1)如果与日期时间字段分开,如:date,time,则需要用该参数指定时间所在列;
    • tmformat:(默认值:%H:%M:%S)令time列的格式和CSV文件中时间的格式适配。
    • compression:(默认值:1)每根柱状图的实际小柱状图数量。仅在数据重采样/回放时用的上。

如果没用用到的参数不理解,特别是compression,没有关系,因为很复杂的场景下(如多级别联动),才会用到。

3. 代码测试,看看数据是否被成功载入了

  • 这里添加的策略很简单,就是将加载的信息,遍历一遍:
python 复制代码
import backtrader as bt

class MyStrategy(bt.Strategy):
    def log(self, txt, dt=None):
        ''' Logging function for this strategy'''
        # 注意要和datetime类型适配,使用datetime(0)表示当前时间
        dt = dt or self.datas[0].datetime.datetime(0) 
        # dt.isoformat(sep=" ")格式化输出,以空格为date和time的分隔符
        print('%s, %s' % (dt.isoformat(sep=" "), txt)) 

    def __init__(self):
        # 为每一列,创建一个bar
        self.dataopen = self.datas[0].open      # 开盘价bar
        self.datahigh = self.datas[0].high      # 最高价bar
        self.datalow = self.datas[0].low        # 最低价bar
        self.dataclose = self.datas[0].close    # 收盘价bar
        self.datavolume = self.datas[0].volume  # 成交量bar
        self.dataopeninterest = self.datas[0].openinterest  # 持仓量bar


    def next(self):
        # 打印该行全部信息
        self.log('open:%.2f, high:%.2f, low:%.2f, close:%.2f, volume:%.2f, openinterest:%.2f'  
                 % (self.dataopen[0], self.datahigh[0], self.datalow[0], self.dataclose[0], self.datavolume[0], self.dataopeninterest[0]))


if __name__ == "__main__":
    data = bt.feeds.GenericCSVData(dataname='SoybeanOil.csv',
                                        nullvalue=0.0,  # 空值替换为0.0
                                        dtformat=('%Y-%m-%d %H:%M:%S'), # 和csv文件中的日期格式适配
                                        
                                        datetime=0,     # 指定为第0列
                                        open=1,         # 指定为第1列 
                                        high=2,          # 以此类推
                                        low=3,
                                        close=4,
                                        volume=5,
                                        openinterest=6,
                                        timeframe=bt.TimeFrame.Minutes,  # 分钟级数据
                                )
    
    cerebro = bt.Cerebro()

    cerebro.adddata(data)
    cerebro.broker.setcash(100000)
    cerebro.broker.setcommission(0.0002)

    cerebro.addstrategy(MyStrategy)

    cerebro.run()
  • 输出结果:
0 复制代码
2024-12-02 22:00:00, open:8058.00, high:8070.00, low:8050.00, close:8070.00, volume:63069.00, openinterest:340406.00
2024-12-02 22:30:00, open:8070.00, high:8070.00, low:8040.00, close:8052.00, volume:53939.00, openinterest:340349.00 
2024-12-02 23:00:00, open:8050.00, high:8064.00, low:8042.00, close:8062.00, volume:60184.00, openinterest:338767.00 
2024-12-03 09:30:00, open:8062.00, high:8116.00, low:8062.00, close:8086.00, volume:107417.00, openinterest:333335.00
2024-12-03 10:00:00, open:8086.00, high:8096.00, low:8060.00, close:8066.00, volume:54452.00, openinterest:331981.00 
2024-12-03 10:45:00, open:8066.00, high:8086.00, low:8056.00, close:8058.00, volume:63507.00, openinterest:331925.00 
2024-12-03 11:15:00, open:8058.00, high:8070.00, low:8002.00, close:8010.00, volume:95367.00, openinterest:334204.00 
2024-12-03 13:45:00, open:8010.00, high:8038.00, low:8004.00, close:8014.00, volume:42692.00, openinterest:333286.00 
...
  • 可以看到,数据被成功载入了。

今后这段策略代码就可以拿来做测试,看看数据的加载情况。


3. 参数持久化


不难发现,有很多参数都是固定的,或者说我们希望,即使不传参,这个参数也应该有我们规定的默认值。这就要用到参数持久化了。

1. 通过子类化,实现参数持久化

  • 该子类继承bt.feeds.GenericCSVData
python 复制代码
import backtrader as bt

class MyDatatype(bt.feeds.GenericCSVData):

  params = (
    ('nullvalue', 0.0),
    ('dtformat', ('%Y-%m-%d %H:%M:%S')),
    ('datetime', 0),
    ('open', 1),
    ('high', 2),
    ('low', 3),
    ('close', 4),
    ('volume', 5),
    ('openinterest', 6),
    ('timeframe', bt.TimeFrame.Days),   # 默认是Day级别k线数据
)
  • 这样一来,我们只需要传很少的参数就可以了:
python 复制代码
data = MyDatatype(dataname="SoybeanOil.csv", timeframe=bt.TimeFrame.Minutes)

2. 完整代码测试

python 复制代码
import backtrader as bt
from Datatype import MyDatatype

class MyStrategy(bt.Strategy):
    def log(self, txt, dt=None):
        ''' Logging function for this strategy'''
        # 注意要和datetime类型适配,使用datetime(0)表示当前时间
        dt = dt or self.datas[0].datetime.datetime(0) 
        # dt.isoformat(sep=" ")格式化输出,以空格为date和time的分隔符
        print('%s, %s' % (dt.isoformat(sep=" "), txt)) 

    def __init__(self):
        # 为每一列,创建一个bar
        self.dataopen = self.datas[0].open      # 开盘价bar
        self.datahigh = self.datas[0].high      # 最高价bar
        self.datalow = self.datas[0].low        # 最低价bar
        self.dataclose = self.datas[0].close    # 收盘价bar
        self.datavolume = self.datas[0].volume  # 成交量bar
        self.dataopeninterest = self.datas[0].openinterest  # 持仓量bar


    def next(self):
        # 打印该行全部信息
        self.log('open:%.2f, high:%.2f, low:%.2f, close:%.2f, volume:%.2f, openinterest:%.2f'  
                 % (self.dataopen[0], self.datahigh[0], self.datalow[0], self.dataclose[0], self.datavolume[0], self.dataopeninterest[0]))


if __name__ == "__main__":
	# 这里用到了参数持久化
    data = MyDatatype(dataname="SoybeanOil.csv", timeframe=bt.TimeFrame.Minutes)
    
    cerebro = bt.Cerebro()

    cerebro.adddata(data)
    cerebro.broker.setcash(100000)
    cerebro.broker.setcommission(0.0002)

    cerebro.addstrategy(MyStrategy)

    cerebro.run()
  • 效果和之前一模一样,但是参数变简洁了,非常Nice!!!

4. 添加Pandas数据


1. 准备数据

python 复制代码
import pandas as pd

pd.read_csv("SoybeanOil.csv", index_col=0, date_format="'%Y-%m-%d %H:%M:%S'")
  • 看一眼数据的样子:
  • 对于Pandas数据,尽量保证索引是时间类型,这是Backtrader的要求。

2. 看看部分源码

python 复制代码
class PandasData(feed.DataBase):
    '''
    The ``dataname`` parameter inherited from ``feed.DataBase`` is the pandas
    DataFrame
    '''

    params = (
        # datetime 的可能值(必须已经存在)
        #  None : datetime 是Pandas的索引
        #  -1 : 自动检测位置,匹配名字相同的列
        #  >= 0 : 从0开始,不包括索引,匹配Pandas的第几列
        #  string : 手动传入列名称,进行匹配
        ('datetime', None),

        # 可能值:
        #  None : 该列不存在
        #  -1 : 自动检测位置,匹配名字相同的列
        #  >= 0 : numeric index to the colum in the pandas dataframe
        #  string : 手动传入列名称,进行匹配
        ('open', -1),
        ('high', -1),
        ('low', -1),
        ('close', -1),
        ('volume', -1),
        ('openinterest', -1),
    )
  • Backtrader支持的Pandas数据需要通过PandasData类构造,之后add的也是这个类的实例化对象。
  • 参数的解释已经写在代码注释中了,这里说一下一般的传参方式:
    • datetime参数一般是不传的,让他为默认值None,因为上面准备的Pandas数据索引已经是时间类型了;
    • 剩余六个基本参数,如果该列在Pandas数据中存在,就传它的列索引(不带datetime列,索引从0开始),或者列名;如果不存在就传None。(毕竟都用Pandas了,还是传列名吧,可读性强,可移植性高)。
  • 不同于加载CSV数据,这里不需要再指定timeframe了,因为该信息已经包含在Pandas的datetime索引中了。

3. 完整代码演示

python 复制代码
import backtrader as bt
import pandas as pd

class MyStrategy(bt.Strategy):
    def log(self, txt, dt=None):
        ''' Logging function for this strategy'''
        # 注意要和datetime类型适配,使用datetime(0)表示当前时间
        dt = dt or self.datas[0].datetime.datetime(0) 
        # dt.isoformat(sep=" ")格式化输出,以空格为date和time的分隔符
        print('%s, %s' % (dt.isoformat(sep=" "), txt)) 

    def __init__(self):
        # 为每一列,创建一个bar
        self.dataopen = self.datas[0].open      # 开盘价bar
        self.datahigh = self.datas[0].high      # 最高价bar
        self.datalow = self.datas[0].low        # 最低价bar
        self.dataclose = self.datas[0].close    # 收盘价bar
        self.datavolume = self.datas[0].volume  # 成交量bar
        self.dataopeninterest = self.datas[0].openinterest  # 持仓量bar


    def next(self):
        # 打印该行全部信息
        self.log('open:%.2f, high:%.2f, low:%.2f, close:%.2f, volume:%.2f, openinterest:%.2f'  
                 % (self.dataopen[0], self.datahigh[0], self.datalow[0], self.dataclose[0], self.datavolume[0], self.dataopeninterest[0]))


if __name__ == "__main__":
    data = pd.read_csv("SoybeanOil.csv", index_col=0, date_format='%Y-%m-%d %H:%M:%S')
    # 这里Pandas数据中,成交量列名是hold,不是openinterest,需要显示传入,让参数适配
    data = bt.feeds.PandasData(dataname=data, openinterest='hold')
    
    cerebro = bt.Cerebro()

    cerebro.adddata(data)
    cerebro.broker.setcash(100000)
    cerebro.broker.setcommission(0.0002)

    cerebro.addstrategy(MyStrategy)

    cerebro.run()
  • 输出:
0 复制代码
2024-12-02 22:00:00, open:8058.00, high:8070.00, low:8050.00, close:8070.00, volume:63069.00, openinterest:340406.00
2024-12-02 22:30:00, open:8070.00, high:8070.00, low:8040.00, close:8052.00, volume:53939.00, openinterest:340349.00 
2024-12-02 23:00:00, open:8050.00, high:8064.00, low:8042.00, close:8062.00, volume:60184.00, openinterest:338767.00 
2024-12-03 09:30:00, open:8062.00, high:8116.00, low:8062.00, close:8086.00, volume:107417.00, openinterest:333335.00
2024-12-03 10:00:00, open:8086.00, high:8096.00, low:8060.00, close:8066.00, volume:54452.00, openinterest:331981.00 
2024-12-03 10:45:00, open:8066.00, high:8086.00, low:8056.00, close:8058.00, volume:63507.00, openinterest:331925.00 
2024-12-03 11:15:00, open:8058.00, high:8070.00, low:8002.00, close:8010.00, volume:95367.00, openinterest:334204.00 
2024-12-03 13:45:00, open:8010.00, high:8038.00, low:8004.00, close:8014.00, volume:42692.00, openinterest:333286.00
...

5. 总结


至此,绝大部分的数据加载问题已经难不住我们了。对比这两种数据加载方式,Pandas加载显然比CSV的加载更加灵活,但是CSV加载则更加规范。

本人习惯使用更为规范的CSV加载,即使有现成的Pandas数据,也可以先pd.to_csv()保存成CSV格式,预处理一下,再加载进Backtrader。


相关推荐
金融数据出海15 小时前
Spring Boot对接马来西亚股票数据源API
java·spring boot·后端·金融
尖枫5082 天前
学习笔记:金融经济学 第1讲
笔记·学习·金融
阳光普照世界和平3 天前
金融行业软件介绍
人工智能·金融·区块链
阳光普照世界和平3 天前
金融简单介绍及金融诈骗防范
金融·生活
ArimaMisaki3 天前
量化策略分类、优劣势及对抗风险解析
人工智能·金融·分类·数据挖掘·游戏策划
OneBlock Community4 天前
了解 DeFi:去中心化金融的入门指南与未来展望
金融·去中心化·区块链
IT科技观察5 天前
金融壹账通推出大模型一体机,加速金融行业AI落地与应用
大数据·人工智能·金融·云计算
AI糊涂是福5 天前
DeepSeek 在金融领域的应用解决方案
机器学习·金融·deep learning
成长之路5146 天前
【工具变量】全国各省及地级市绿色金融指数数据集(2000-2023年)
金融