我们需要使用backtrader来做量化交易回测,先解决一些前置问题:
- 需要回测哪几支股票,或者即时选取股票
- 即时选取股票以什么形式,代码还是名称
- 这些数据的格式是怎么样的,backtrader如何接收
在解决这些前置问题的过程中,我们先拿股票软件来对照着分析一下。
股票软件数据
股票软件的基本功能
我们打开各个股票软件,当前比较大的几个软件主要的基本功能都差不太多。
主页通常是个全景图,一般用九宫格的方式进行显示,
- 上层3个分别是上证系列(上证,上证50,科创),深证系列(深成指,综指,创业板)以及恒生,纳指等主要指数,
- 中层一般为沪市涨跌幅榜和涨速榜,深市的,以及板块(包括行业,概念,地区等)涨跌幅榜,
- 下层主要是自选股排行,新闻什么的。
第二页一般都是自选股,这里一般都是默认的自选股,自建板块等,以及最近浏览。
第三页一般是股票行情,或者是排行,这一页里可以显示所有的5千多支股票。
第四页通常是板块行情
第五页通常是主要指数
另外,在软件的右下角有一个文本输入框(有的软件不显示,但键盘动作就会弹出来)这里可以输入3种类型,包括数字代码,拼音缩写,中文名称,并提供一定的智能搜索功能,可以调出你所需要的股票,基金甚至指标等,右键又可以加入自选股。
选中任意一支股票或指数,再点日线,我们就可以看到它的日K线情况,一般而言,主图上都会有MA(平均线)指标,副图上会是成交量柱状图,更多的副图上可以是MACD, KDJ等指标。
简单总结需要的数据
一系列的列表,包括
- 沪深京全部的股票列表(当前有5400多支)
- 主要指数列表(上证,深成指,沪深300..)
- 板块列表(还分行业、概率、地区等)
- ETF/LOF 列表
- 自选股列表(可以创建多个组)
如何得到某支股票或指数
- 通过6位数字代号获取
- 通过拼音缩写获取
- 通过中文名称获取
backtrader所需要的K线数据(当前只考虑日K线)
- 周期范围(开始日期,结束日期)(结束日期默认是当前最近一个交易日)
- 周期范围内每一根K线的四维(开,高,低,收)以及成交量
这些需要的数据在股票软件里可以说是现成的,只要连网后,数据都是实时更新的。但是怎么把这些数据放在backtrader中进行使用,就需要我们自己动手来制作了。
如果我们需要的不只是在backtrader里学习策略的编写和改进,学习参数优化,学习评价;如果我们需要对更多的股票或者ETF进行回测;如果我们需要对很多支股票和很多个策略进行回测并得到它们的评价指标打分,从而选取合适的股票、合适的策略、合适的时机并且能指导我们建立完整的交易体系的话,我们需要对这些数据先进行比较深层次的实践。
Backtrader的数据
01_backtrader典型程序
这里涉及到backtrader的入门,以下一些文档是不错的
- Backtrader系列教程①:Backtrader is coming_backtrader中文教程-CSDN博客
- 量化投资之工具篇一:Backtrader从入门到精通(1)-手把手教你零基础实现-CSDN博客
- BackTrader 中文文档 --- 软件快速开始_backtrader中文教程-CSDN博客
这些文章可能有点早了,比如2023年末我们已经不能用tushare去获取股票/ETF的数据了。另外,现在AI助手已经快成标配了,所以我们的学习和实践有了更快速可能更有效率的方式:有问题就问AI。
以下是AI回答的一个backtrader的典型回测程序:
import backtrader as bt
# 创建策略
class MovingAverageCrossStrategy(bt.Strategy):
params = (
('short_window', 10), # 短期移动平均线窗口大小
('long_window', 30), # 长期移动平均线窗口大小
)
def __init__(self):
self.short_moving_average = bt.indicators.SMA( # 初始化移动平均线指标
self.data.close, period=self.params.short_window, plotname='Short SMA')
self.long_moving_average = bt.indicators.SMA(
self.data.close, period=self.params.long_window, plotname='Long SMA')
def next(self):
# 如果短期移动平均线上穿长期移动平均线,则买入
if self.short_moving_average[0] > self.long_moving_average[0] and not self.position:
self.buy()
# 如果短期移动平均线下穿长期移动平均线,则卖出
elif self.short_moving_average[0] < self.long_moving_average[0] and self.position:
self.close()
cerebro = bt.Cerebro() # 创建回测引擎
cerebro.addstrategy(MovingAverageCrossStrategy) # 添加策略
# 加载数据
data = bt.feeds.YahooFinanceData(dataname='AAPL', fromdate=datetime(2010, 1, 1),
todate=datetime(2020, 12, 31))
cerebro.adddata(data)
cerebro.broker.setcash(10000.0) # 设置初始资金
cerebro.addsizer(bt.sizers.FixedSize, stake=10) # 设置交易单位大小
cerebro.broker.setcommission(commission=0.001) # 设置交易费用
cerebro.run() # 运行回测
cerebro.plot() # 绘制结果
其实这个AI给出的代码有很多的问题,比如在 next()中,用了self.buy()买入并使用了self.close()来卖出,这不是股票交易,应该是期货的交易,因此可以在没有持仓的情况下进行卖空操作;又比如这里加载数据使用的是YahooFinanceData,然而我们早就不能使用Yahoo的东西了:
2021 年 11 月 1 日起,用户将无法从中国大陆使用 Yahoo 的产品与服务。
虽然有很多的问题,但仍然给了我们一个典型的backtrader的程序框架,我们从中可以了解到,要让一个backtrader程序跑起来,需要至少:
- 添加一个策略(这里是双均线策略)
- 加载一支股票数据(苹果公司2010年1月1日到2020年12月31日的日线数据)
- 设置交易资金、每次交易量、交易费用等
- 运行回测以及绘制结果
策略以及交易设置等问题在后续的章节中再进行实践,当前最重要的问题是怎么获取到这些数据。
02_数据来源
你如果在网上看文章,千万注意,22年之前的文章很大一部分是通过tushare来获取股票数据的,但是现在完全用不了的。于是,我们还是先来看下backtrader接受怎么样的数据。然后再来看下我们拿到的一些数据,如何做准备。
backtrader接收哪些数据,这个看源码最方便,点击bt.feeds ,来到backtrader\feeds\__init__.py文件,这里的import语句就说明它支持哪些类型的数据:
from .csvgeneric import *
from .btcsv import *
from .vchartcsv import *
from .vchart import *
from .yahoo import *
from .quandl import *
from .sierrachart import *
from .mt4csv import *
from .pandafeed import *
from .influxfeed import *
我们把这段代码直接交给AI解析就可以得到:
这些导入语句对应于backtrader库中不同类型的数据源。下面是每个模块对应的数据类型:
from .csvgeneric import *
: 导入与通用CSV文件格式相关的数据源。这允许用户从自定义格式的CSV文件中读取数据。
from .btcsv import *
: 导入与比特币CSV文件格式相关的数据源。这专门用于读取比特币交易数据,可能包含了比特币的价格、成交量等信息。
from .vchartcsv import *
: 导入与VChart CSV文件格式相关的数据源。VChart可能是一种特定于某些交易平台或软件的图表数据格式。
from .vchart import *
: 导入与VChart数据格式相关的数据源。这可能是与vchartcsv
相关的另一种格式,用于读取和分析VChart格式的数据。
from .yahoo import *
: 导入与Yahoo财经数据格式相关的数据源。这允许用户从Yahoo财经网站获取股票、指数、外汇等金融数据。
from .quandl import *
: 导入与Quandl数据格式相关的数据源。Quandl是一个提供各种金融、经济和替代数据的数据平台。
from .sierrachart import *
: 导入与Sierra Chart软件格式相关的数据源。Sierra Chart是一个强大的图表和交易软件,这个模块允许用户从Sierra Chart格式中读取数据。
from .mt4csv import *
: 导入与MetaTrader 4 (MT4) CSV文件格式相关的数据源。MT4是一个流行的外汇交易平台,这个模块用于读取MT4导出的CSV数据文件。
from .pandafeed import *
: 导入与Pandas数据结构相关的数据源。Pandas是一个强大的数据分析和操作库,这个模块允许用户使用Pandas DataFrame作为数据源。
from .influxfeed import *
: 导入与InfluxDB数据库格式相关的数据源。InfluxDB是一个时间序列数据库,用于存储和查询大量时间戳数据。这些模块提供了与不同数据格式和来源的接口,使得backtrader能够灵活地处理各种金融数据,从而满足不同用户的需要。
虽然看上去支持的数据来源很多,但其中比特币国家不允许,vchart,quandl,sierra,mt4都是一些交易平台,Yahoo也是不允许......最后看下来,能用的其实就2个:
- 用户自定义CSV
- pandas数据文件
最后,我们选择了只使用pandas数据文件作为数据来源。
03_数据格式
关于pandas-data的数据,参考文档中非常不错的一篇如下
- Backtrader系列教程②:数据篇_bt.feeds.pandasdata-CSDN博客
需要说明的是,这里的数据格式是分成二步的,第一步是输入(喂)给backtrader的源数据,这个就是简单的Pandas的DataFrame;而第二步是backtrader会把这些数据加工成为它自己独有的格式。那么在加工过程中,就涉及到参数的对应关系或位置,索引的使用等问题。
001_绘制股价图
首先,股票的基本数据已经有6个了,分别是:
- 日期
- 开盘价
- 最高价
- 最低价
- 收盘价
- 成交量
任何股票软件或是回测框架都一定会用到。通过前5个数据我们就能绘制出股价图,然后叠加上成交量的图。在一般的股票软件里,主图的K线是默认绘制的,但这样的情况下新手就很难理解。
这里我们可以直接在EXCEL里实践一下股价图的绘制:
1. 任选一段数据粘贴进excel,注意顺序按照时间-成交量-开盘价-最高-最低-收盘价排列
2. 选取数据后--插入--点击图表的右下角箭头,在更改图表类型中,选择股份图,没有成交量的选前三,有成交量的用第4个图。
3. 点击确定,调整一下轴以及数值,就得到了带成交量的股价图
002_backtrader的基本需求
关于backtrader的基本需求,还是直接看源码比较省事,我们还是直接复制代码交给AI来解析:
- 这段文档字符串描述了
PandasData
类的主要功能和参数配置。params = ( ('nocase', True), ('datetime', None), ('open', -1), ('high', -1), ('low', -1), ('close', -1), ('volume', -1), ('openinterest', -1), )
- params 是一个元组,定义了类的参数及其默认值。
nocase: 一个布尔值,默认为 True,表示在匹配列名时不区分大小写。
datetime: 定义了日期时间列的位置。它可以是 None(表示 DataFrame 的索引即为日期时间),-1(表示自动检测),或一个非负整数或字符串(表示具体的列标识符)。
其他参数如 open, high, low, close, volume, openinterest 等,它们的值可以是 None(表示列不存在)、-1(表示自动检测)或一个非负整数或字符串(表示具体的列标识符)。datafields = [ 'datetime', 'open', 'high', 'low', 'close', 'volume', 'openinterest' ]
datafields
列表定义了数据字段的名称,这些字段是金融数据中常见的几个指标:日期时间、开盘价、最高价、最低价、收盘价、成交量和未平仓合约数量。解析总结
这个
PandasData
类主要用于处理 Pandas DataFrame 格式的金融数据。它通过指定不同的参数来配置如何从 DataFrame 中读取各个数据字段。这个类的设计目的是为了方便地将 Pandas DataFrame 转换为可供量化交易系统使用的数据格式,使得开发者可以根据需要灵活地指定数据列的位置或名称。通过这种方式,
PandasData
类提供了一种灵活的方式来处理数据,使得用户可以根据自己的数据结构来定制数据加载方式。这对于金融数据分析和量化交易系统的开发来说是非常有用的。
AI基本上总结的很到位了,在实践的过程中我们会发现新增加的参数似乎大写或小写都没有关系,那是因为‘nocase’的默认值为True,即不区分大小写,但千万不要以为可以随意写。这里有一个真实的案例,就是咱把'volume'这个参数弄错了一个字母变成了'volumn',然后一直报错又找不到原因,浪费了几个小时。
前面说到股票的基本信息是6个,而这里有7个,多出来的一个是“openinterest”,AI给出的说明是“未平仓合约数量”,因此它应该是应用在期货交易中的一个参数或者说应对于能做空的市场,对于A股而言,这个参数不需要,因此大多文档都会直接把这一列赋值0。还有一种方式是在保存pandas格式的数据文件的时候,直接忽略这个参数,实践证明它也是可行的。
04_交给backtrader前的处理
由上面的内容,只要得到股票的6个基本数据就可以交给backtrader做回测了。最后,我们需要解决拿到手的原始数据处理成符合backtrader要求的格式的问题。
假设我们已经有了一笔数据的文件,我们需要处理:
- 获取文件内数据
- 日期是日期类型
- 索引用的是日期
- 只接收7个列,必须按顺序来,名称必须正确
001_表格数据存csv
这里准备了一组数据,很短,但是能用,把整张表格复制到excel中,另存为.csv的文件,要注意一下,由于里面有中文字符,需要存成utf-8的那种。
002_读取csv文件
现在可以使用Notebook来进行代码实践了,我们打开Notebook,新建一个,注意如果要在里面绘制图形,要点击open in... 并选择NbClassic的选项(2024年7月后更新过Notebook的)。
首先,通过pd.read_csv()把文件内容读取到。
fname = "demo_pandas_data_01.csv" # 根据自己保存的.csv名称来
import pandas as pd
df1 = pd.read_csv(fname)
df1.head(5)
--------------------------
时间 成交量 开盘 最高 最低 收盘
0 2022/12/15 366592 7.62 7.70 7.59 7.66
1 2022/12/16 484131 7.59 7.62 7.47 7.53
2 2022/12/19 560980 7.49 7.72 7.49 7.55
3 2022/12/20 301279 7.50 7.52 7.39 7.45
4 2022/12/21 306892 7.45 7.48 7.30 7.36
003_Pandas数据处理
根据前面所讲的,有一些内容要进行处理
- 名称必须正确(这里全是中文名,需要改)
- 索引是日期(这里索引是自动计数)
- 索引需要是datetime类型,需要转换(做成索引后,其列名就没有关系了)
- 名称顺序要正确
df1.columns = df1.columns.str.strip() # 中文字符两边可能有空格
df1 = df1.rename(columns={'成交量':'volume','开盘':'open','最高':'high','最低':'low','收盘':'close'}) # 更改列名
df1.index = pd.to_datetime(df1.时间) # 索引为datetime类型
df1['openinterest'] = 0
df1 = df1[['open','high','low','close','volume','openinterest']] # 按顺序排列
df1.head(5)
------------------------------
open high low close volume openinterest
时间
2022-12-15 7.62 7.70 7.59 7.66 366592 0
2022-12-16 7.59 7.62 7.47 7.53 484131 0
2022-12-19 7.49 7.72 7.49 7.55 560980 0
2022-12-20 7.50 7.52 7.39 7.45 301279 0
2022-12-21 7.45 7.48 7.30 7.36 306892 0
这样的数据我们就可以交给backtrader做回测了,我们会在下面语句把df1传给backtrader。
data = bt.feeds.PandasData(dataname=df1)
05_backtrader回测
简单做一个双均线策略,这里我们只要问AI要一个“请提供一个backtrader的回测程序代码,策略采用双均线,分别为5日和20日均线”,AI就会直接给你生成一段代码,重要的是,它能跑起来,我们只需要小小修改就可以了。
比如说,这段数据量太少,20日均线都出不来,我们需要把参数改为2,4;另外,默认交易的股票似乎太少,我们通过addsizer变成用50%的资金进行买入;默认的plot()是绘制一条线,于是我们在里面加上style = 'candle',就能出蜡烛线了,把candle改为candel则绘制是就是美国线......
import backtrader as bt
import datetime
import pandas as pd
# 创建策略类
class SmaCross(bt.Strategy):
# 定义策略参数
params = dict(
pfast=2, # 快速移动平均线周期
pslow=4 # 慢速移动平均线周期
)
def __init__(self):
sma1 = bt.ind.SMA(period=self.p.pfast) # 快速SMA
sma2 = bt.ind.SMA(period=self.p.pslow) # 慢速SMA
self.crossover = bt.ind.CrossOver(sma1, sma2) # 交叉指标
def next(self):
if not self.position: # 如果没有持仓
if self.crossover > 0: # 当快线上穿慢线时买入
self.buy()
elif self.crossover < 0: # 当快线下穿慢线时卖出
self.close()
# 加载数据
data = bt.feeds.PandasData(dataname=df1) # 请替换为你的数据文件路径
cerebro = bt.Cerebro() # 初始化Cerebro引擎
cerebro.adddata(data) # 将数据传入Cerebro引擎
cerebro.addstrategy(SmaCross) # 添加策略
cerebro.broker.setcash(10000) # 设置初始资金
cerebro.addsizer(bt.sizers.PercentSizer,percents=50) # 每次半仓交易
print(f'初始总资金: {cerebro.broker.getvalue():.2f}')
cerebro.run() # 运行回测
print(f'最终总资金: {cerebro.broker.getvalue():.2f}') # 打印出最终总资产
cerebro.plot(style='candle') # 绘制结果
-----------------------
初始总资金: 10000.00
最终总资金: 10224.40
这里,我们看到刚才自己处理的数据是能正常使用的,虽然只有30天数据,但也能完成回测。而且,居然赚钱了......
输出的图形是国际标准的,绿涨红跌,绿买红卖,与我们的A股正好相反,后面将在配置篇再对这些进行实践。
在这一篇里我们着眼于解决数据获取的问题,到目前为止,如果有数据文件给到我们手里了,我们就可以把它制作成backtrader需要的格式并完成回测,这一节的数据准备的任务就完成了~