quant

backtrader性能评估

backtrader作者在这篇博客里测试了使用pypy能够显著提升backtrader回测速度,至少节约一半时间。

作者看reddit论坛有人说backtrader处理不了160万k线。

作者用backtrader进行了如下测试:
1 数据
作者首先生成100支股票,每支生成20000根k线,这样总共有200万根k线,分别放在100个csv文件里。

2 测试平台
i7 cpu 32G内存, win10
比较了两个python 解释器:传统的CPython 3.6.1 和快速的pypy 3.6.0.0

3 策略
测试策略就是一个简单的双均线策略,所有股票出现金叉买入,死叉卖出。

4 不执行交易的测试结果
所谓不执行交易,也就是策略的next方法为空,相当于只是加载数据。另外,不计算指标,只是简单的迭代。

4.1 在backtrader默认的批运行模式(runonce mode)下

CPython:高峰内存348M,总时间135秒,其中76秒用于预加载数据,其余是迭代时间。平均每秒处理14713根k线。

pypy: 高峰内存269M,总时间57秒,每秒处理34971根k线。相对CPython,时间节约了一半多。

4.2 在exactbars=True模式下
如果不用默认模式,在cerebro运行参数里设置exactbars=True,stdstats=False,则不会一次性预加载所有数据。来看看测试结果。

CPython:高峰内存75M,总时间114秒。平均每秒处理17494根k线。

pypy: 高峰内存49M,总时间66秒,每秒处理30025根k线。

相对4.1 内存节约明显,速度节约不明显。

5 执行交易的测试结果pypy
如果在策略next方法中,执行双均线交易逻辑,这是和实际策略一样的测试。运行在批模式下runonce mode。只报告pypy下的结果。
高峰内存消耗:1.3G
执行时间:156秒(含指标计算+交易),每秒处理12743根k线。

6 结论
backtrader默认可以处理百万级k线。
尽量使用pypy(例如,如果你不需要绘图输出的话)

7 测试8000支股票?
pypy下,如果回测8000支股票,每支20000根k线,相当于总计1.6亿根k线。所花时间粗略估计为上述80倍,即156×80=12480秒=3.5小时。内存可能消耗几个G。

SimpleMovingAverage

The code inside the SimpleMovingAverage indicator __init__ could look like:

def __init__(self):
    # Sum N period values - datasum is now a *Lines* object
    # that when queried with the operator [] and index 0
    # returns the current sum

    datasum = btind.SumN(self.data, period=self.params.period)

    # datasum (being *Lines* object although single line) can be
    # naturally divided by an int/float as in this case. It could
    # actually be divided by anothr *Lines* object.
    # The operation returns an object assigned to "av" which again
    # returns the current average at the current instant in time
    # when queried with [0]

    av = datasum / self.params.period

    # The av *Lines* object can be naturally assigned to the named
    # line this indicator delivers. Other objects using this
    # indicator will have direct access to the calculation

    self.line.sma = av

【手把手教你】入门量化回测最强神器backtrader

1 引言

目前基于Python的量化回测框架有很多,开源框架有zipline、vnpy、pyalgotrader和backtrader等,而量化平台有Quantopian(国外)、聚宽、万矿、优矿、米筐、掘金等,这些量化框架或平台各有优劣。就个人而言,比较偏好用backtrader,因为它功能十分完善,有完整的使用文档,安装相对简单(直接pip安装即可)。优点是运行速度快,支持pandas的矢量运算;支持参数自动寻优运算,内置了talib股票分析技术指标库;支持多品种、多策略、多周期的回测和交易;支持pyflio、empyrica分析模块库、alphalens多因子分析模块库等;扩展灵活,可以集成TensorFlow、PyTorch和Keras等机器学习、神经网络分析模块。而不足之处在于,backtrader学习起来相对复杂,编程过程中使用了大量的元编程(类class),如果Python编程基础不扎实(尤其是类的操作),学起来会感到吃力。本文作为backtrader的入门系列之一,对其运行框架进行简要介绍,并以实际案例展示量化回测的过程。

2 backtrader简介

如果将backtrader包分解为核心组件,主要包括以下组成部分:
(1)数据加载(Data Feed):将交易策略的数据加载到回测框架中。

(2)交易策略(Strategy):该模块是编程过程中最复杂的部分,需要设计交易决策,得出买入/卖出信号。
(3)回测框架设置( Cerebro):需要设置(i)初始资金(ii)佣金(iii)数据馈送(iv)交易策略(v)交易头寸大小。
(4)运行回测:运行Cerebro回测并打印出所有已执行的交易。
(5)评估性能(Analyzers):以图形和风险收益等指标对交易策略的回测结果进行评价。

“Lines”是backtrader回测的数据,由一系列的点组成,通常包括以下类别的数据:Open(开盘价), High(最高价), Low(最低价), Close(收盘价), Volume(成交量), OpenInterest(无的话设置为0)。Data Feeds(数据加载)、Indicators(技术指标)和Strategies(策略)都会生成 Lines。价格数据中的所有”Open” (开盘价)按时间组成一条 Line。所以,一组含有以上6个类别的价格数据,共有6条 Lines。如果算上“DateTime”(时间,可以看作是一组数据的主键),一共有7条 Lines。当访问一条 Line 的数据时,会默认指向下标为 0 的数据。最后一个数据通过下标 -1 来访问,在-1之后是索引0,用于访问当前时刻。因此,在回测过程中,无需知道已经处理了多少条/分钟/天/月,”0”一直指向当前值,下标 -1 来访问最后一个值。

3 回测应用实例

量化回测说白了是使用历史数据去验证交易策略的性能,因此回测的第一步是搭建交易策略,这也是backtrader要设置的最重要和复杂的部分,策略设定好后,其余部分的代码编写是手到擒来。

01构建策略(Strategy)

交易策略类代码包含重要的参数和用于执行策略的功能,要定义的参数或函数名如下:

(1)params-全局参数,可选:更改交易策略中变量/参数的值,可用于参数调优。

(2)log:日志,可选:记录策略的执行日志,可以打印出该函数提供的日期时间和txt变量。

(3) __init__:用于初始化交易策略的类实例的代码。

(4)notify_order,可选:跟踪交易指令(order)的状态。order具有提交,接受,买入/卖出执行和价格,已取消/拒绝等状态。

(5)notify_trade,可选:跟踪交易的状态,任何已平仓的交易都将报告毛利和净利润。

(6)next,必选:制定交易策略的函数,策略模块最核心的部分。

下面以一个简单的单均线策略为例,展示backtrader的使用过程,即当收盘价上涨突破20日均线买入(做多),当收盘价下跌跌穿20日均线卖出(做空)。为简单起见,不报告交易回测的日志,因此log、notify_order和notify_trade函数省略不写。

class my_strategy1(bt.Strategy):
    #全局设定交易策略的参数
    params=(
        ('maperiod',20),
           )

    def __init__(self):
        #指定价格序列
        self.dataclose=self.datas[0].close
        # 初始化交易指令、买卖价格和手续费
        self.order = None
        self.buyprice = None
        self.buycomm = None

        #添加移动均线指标,内置了talib模块
        self.sma = bt.indicators.SimpleMovingAverage(
                      self.datas[0], period=self.params.maperiod)
    def next(self):
        if self.order: # 检查是否有指令等待执行, 
            return
        # 检查是否持仓   
        if not self.position: # 没有持仓
            #执行买入条件判断:收盘价格上涨突破20日均线
            if self.dataclose[0] > self.sma[0]:
                #执行买入
                self.order = self.buy(size=500)         
        else:
            #执行卖出条件判断:收盘价格跌破20日均线
            if self.dataclose[0] < self.sma[0]:
                #执行卖出
                self.order = self.sell(size=500)

02数据加载(Data Feeds)

策略设计好后,第二步是数据加载,backtrader提供了很多数据接口,包括quandl(美股)、yahoo、pandas格式数据等,我们主要分析A股数据。

mpl.rcParams['axes.unicode_minus']=False

#先引入后面可能用到的包(package)
import pandas as pd  
from datetime import datetime
import backtrader as bt
import matplotlib.pyplot as plt
%matplotlib inline   

#正常显示画图时出现的中文和负号
from pylab import mpl
mpl.rcParams['font.sans-serif']=['SimHei']

#使用tushare旧版接口获取数据
import tushare as ts 
def get_data(code,start='2010-01-01',end='2020-03-31'):
    df=ts.get_k_data(code,autype='qfq',start=start,end=end)
    df.index=pd.to_datetime(df.date)
    df['openinterest']=0
    df=df[['open','high','low','close','volume','openinterest']]
    return df

dataframe=get_data('600000')

#回测期间
start=datetime(2010, 3, 31)
end=datetime(2020, 3, 31)
# 加载数据
data = bt.feeds.PandasData(dataname=dataframe,fromdate=start,todate=end)

03 回测设置(Cerebro)

回测设置主要包括几项:回测系统初始化,数据加载到回测系统,添加交易策略, broker设置(如交易资金和交易佣金),头寸规模设置作为策略一部分的交易规模等,最后显示执行交易策略时积累的总资金和净收益。

# 初始化cerebro回测系统设置                           
cerebro = bt.Cerebro()  
#将数据传入回测系统
cerebro.adddata(data) 
# 将交易策略加载到回测系统中
cerebro.addstrategy(my_strategy1) 
# 设置初始资本为10,000
startcash = 10000
cerebro.broker.setcash(startcash) 
# 设置交易手续费为 0.2%
cerebro.broker.setcommission(commission=0.002) 

04 执行回测

输出回测结果。

print(f'净收益: {round(pnl,2)}')

d1=start.strftime('%Y%m%d')
d2=end.strftime('%Y%m%d')
print(f'初始资金: {startcash}\n回测期间:{d1}:{d2}')
#运行回测系统
cerebro.run()
#获取回测结束后的总资金
portvalue = cerebro.broker.getvalue()
pnl = portvalue - startcash
#打印结果
print(f'总资金: {round(portvalue,2)}')

初始资金: 10000
回测期间:20100331:20200331
总资金: 12065.36
净收益: 2065.36

05可视化

对上述结果进行可视化,使用内置的matplotlib画图。至此,简单的单均线回测就完成了。下面图形展示了浦发银行在回测期间的价格走势、买卖点和交易总资金的变化等。当然,本文着重以最简化的例子展示backtrader的框架和运行过程,要想更详细的展示回测过程和结果,还需要加入其他函数和模块,关于Analyzers分析模块的应用请留意下一篇推文。

%matplotlib inline #在jupyter notebook上运行
cerebro.plot(style='candlestick')

4 结语

backtrader是目前功能最完善的Python量化回测框架之一(单机版),得到欧洲很多银行、基金等金融机构的青睐,并应用于实盘交易中。作为入门序列之一,本文简单介绍了backtrader框架的各个组成部分,然后以20日单均线策略为例,展示了回测系统的编程和运行。公众号接下来将以专题的形式为大家全面介绍backtrader的应用。学习没有捷径,要想全面而深入地学习backtrader回测框架,最好的方法是研读其官方文档。

backtrader FAQ:绘图运行出matplotlib错?你想不到的坑

不少朋友运行backtrader回测程序,调用cerebro.plot()命令时,会出如下错误
cannot import name ‘warnings’ from ‘matplotlib.dates’
左查又查找不到原因。其实原因可能很简单,就是matplotlib版本不兼容。backtrader与matplot 3.3不兼容,要降级到3.2,运行如下命令可降级:
pip uninstall matplotlib
pip install matplotlib==3.2.2

需要backtrader技术教程请点
————————————————
版权声明:本文为CSDN博主「扫地僧量化」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qtbgo/article/details/109687577

量化回测框架Backtrader【2】-数据导入(附:Tushare介绍)

免责声明:本文不构成投资建议。投资有风险,入市须谨慎。

量化回测的第一步就是导入数据,Backtrader中称这个为data feeds,支持多种数据导入方式

  • 通用CSV格式
  • panda数据
  • Backtrader CSV Backtrader 为测试自创的格式,
  • 一系列的第三方数据包括(yahoo等)

由于后面两种方式暂时还不会用到,所以只介绍前面两种方式。

一,通用参数

由于所有的数据导入类都派生于同一个基类,所以所有的数据导入类都支持通用参数。

  • dataname (默认值: 无) 必须提供
    含义随数据类型(文件位置,代码,…)而异。
  • name (默认值: ‘’)
    用于绘图。 如果未指定,则会从数据名派生(例如:文件路径的最后一部分)
  • fromdate (默认值: 最早的时间)
    Python datetime对象,忽略最早时间之前的任何时间
  • todate (默认值: 最晚的时间)
    Python datetime对象,忽略最晚时间之后的任何时间
  • timeframe (默认值: TimeFrame.Days)
    时间间隔,可选值: TicksSecondsMinutesDaysWeeksMonths and Years
  • compression (默认值: 1)
    每个bar里面实际包含的bar数量(bar是时间的颗粒度,相当于k线图上的一个柱子),仅在数据重采样/回放中有效。
  • sessionstart (默认值: None)
    指示数据的会话开始时间。 可能被类用于诸如重采样之类的目的
  • sessionend (默认值: None)
    指示数据的会话结束时间。 可能被类用于诸如重采样之类的目的

二,通用CSV格式数据导入

函数名:GenericCSVData

独有参数:

  • dataname 数据文件名
  • datetime (默认值: 0) 日期数据所在列
  • time (默认值: -1) 时间数据所在列(-1代表没有)
  • open (默认值: 1) , high (默认值: 2), low (默认值: 3), close (默认值: 4), volume (默认值: 5), openinterest (默认值: 6)
    分别表示开盘,最高价,最低价,收盘价,交易量,持仓量所在列(-1代表没有)
  • nullvalue (默认值: float(‘NaN’))
    用来替换缺失值的值
  • dtformat (默认值: %Y-%m-%d %H:%M:%S)
    日期格式
  • tmformat (默认值: %H:%M:%S)
    时间格式

再举例子前先介绍一个可以获取股票行情数据的平台,tushare。

Tushare是一个免费、开源的python财经数据接口包。主要实现对股票等金融数据从数据采集清洗加工数据存储的过程,能够为金融分析人员提供快速、整洁、和多样的便于分析的数据,为他们在数据获取方面极大地减轻工作量,使他们更加专注于策略和模型的研究与实现上。考虑到Python pandas包在金融量化分析中体现出的优势,Tushare返回的绝大部分的数据格式都是pandas DataFrame类型,非常便于用pandas/NumPy/Matplotlib进行数据分析和可视化。当然,如果您习惯了用Excel或者关系型数据库做分析,您也可以通过Tushare的数据存储功能,将数据全部保存到本地后进行分析。应一些用户的请求,从0.2.5版本开始,Tushare同时兼容Python 2.x和Python 3.x,对部分代码进行了重构,并优化了一些算法,确保数据获取的高效和稳定。

我对比了一下网上其他的数据源,相对来说这个平台的数据还是比较齐全的,日线数据是免费的,对于入门级的基础研究也够用了,高级一点的数据可能就需要去赚积分了。具体可以看他们的官网

欢迎大家通过我的推荐去注册,谢谢!Tushare注册

下面举个从tushare获取日线数据的例子:

import tushare as ts
import pandas as pd

pro = ts.pro_api(token='your token id')
df = pro.daily(ts_code='000001.SZ', start_date='20110101', end_date='20210101').iloc[::-1]
df.to_csv('stock_data.csv', index=False )

tushare的pro版本是需要注册使用的,注册成功以后会获得一个token id,用你申请到的token id替换代码中的‘your token id’。

获取日线数据的api是pro.daily(), 参数:

  • ts_code:股票代码
  • start_date:数据开始日期
  • end_date:数据结束日期

返回的是一个pandas的dataframe。由于tushare返回的数据是时间反序的,即最新的日期在最前面,这跟BackTrader对数据的要求正好相反,所以用‘iloc[::-1]’使它颠倒一下顺序。最后一行代码就是调用pandas的转存为csv的接口保存为csv文件供以后使用。

最终应该可以在notebook的根目录(如果运行在jupyter上)下生成名为‘stock_data.csv’的中国平安(股票代码:000001.SZ)从20110101到20210101的日线数据文件。

接下来我们就可以用这个数据文件测试BackTrader的数据导入接口。

from datetime import datetime
import backtrader as bt
import backtrader.feeds as btfeeds

class SmaCross(bt.SignalStrategy):
    def __init__(self):
        sma1, sma2 = bt.ind.SMA(period=10), bt.ind.SMA(period=30)
        crossover = bt.ind.CrossOver(sma1, sma2)
        self.signal_add(bt.SIGNAL_LONG, crossover)

cerebro = bt.Cerebro()
cerebro.addstrategy(SmaCross)

data = btfeeds.GenericCSVData(
    dataname='stock_data.csv',
    fromdate=datetime(2011, 1, 1),
    todate=datetime(2012, 12, 31),
    nullvalue=0.0,
    dtformat=('%Y%m%d'),
    datetime=1,
    open=2,
    high=3,
    low=4,    
    close=5,
    volume=9,
    openinterest=-1
)

cerebro.adddata(data)

cerebro.run()
cerebro.plot(iplot=False)

在上一讲的代码的基础上修改了数据导入的接口

上图显示了我们从tushare下载到的数据,

日期格式是‘年月日’且没有分割,所以dtformat为’%Y%m%d’

‘datetime’,‘open’,‘high’,‘low’,‘close’,‘volume’分别是对应的列序(第一列的列序为0)

如果运行没有问题的话应该会输出如下的图表

三,panda数据导入

独有参数:

  • nocase (默认值:True) 匹配列名时不区分大小写

参数dataname 传入Pandas DataFrame变量

参数datetimeopen , high low close volumeopeninterest 用列名或列序指定

如果索引列是日期,则参数datetime可以不提供

例子如下:

from datetime import datetime
import backtrader as bt
import backtrader.feeds as btfeeds
import tushare as ts
import pandas as pd

class SmaCross(bt.SignalStrategy):
    def __init__(self):
        sma1, sma2 = bt.ind.SMA(period=10), bt.ind.SMA(period=30)
        crossover = bt.ind.CrossOver(sma1, sma2)
        self.signal_add(bt.SIGNAL_LONG, crossover)

cerebro = bt.Cerebro()
cerebro.addstrategy(SmaCross)

pro = ts.pro_api(token='936d1029be68a59a3e77eeb9e4eb1ea3c36502bd4b4bf9e1aae91bd8')
df = pro.daily(ts_code='000001.SZ', start_date='20110101', end_date='20210101').iloc[::-1]
df.trade_date=pd.to_datetime(df.trade_date) #由于trade_date是字符串,BackTrader无法识别,需要转一下
data = btfeeds.PandasData(
    dataname=df,
    fromdate=datetime(2011, 1, 1),
    todate=datetime(2012, 12, 31),
    datetime='trade_date',
    open='open',
    high='high',
    low='low',    
    close='close',
    volume='vol',
    openinterest=-1
)

cerebro.adddata(data)

cerebro.run()
cerebro.plot(iplot=False)

由于tushare返回的就是pandas的dataframe,所以这里可以直接使用,只是trade_date列需要转成datatime格式,输出跟上个例子一样就不重复贴了。

四,导入扩展数据类别

BackTrader的data feeds默认导入的数据类别是datetimeopen , high low close volumeopeninterest。除了这些基本的数据,我们可能会使用其他的数据类别作为策略的依据,BackTrader也支持数据类别的扩展,方法如下:

from backtrader.feeds import GenericCSVData

class GenericCSV_extend(GenericCSVData):

    # 添加change数据
    lines = ('change',)

    # 添加参数,由于openinterest默认是index 7 所以我们默认index 8
    params = (('change', 8),)

从GenericCSVData类继承创建新类,新类里添加lines(下一讲会介绍这个lines),再为这个数据类别添加新的参数。使用这个新创的类就可以导入新的数据了,示例如下:

from datetime import datetime
import backtrader as bt
from backtrader.feeds import GenericCSVData
import backtrader.indicators as btind

class GenericCSV_extend(GenericCSVData):

    # 添加change数据
    lines = ('change',)

    # 添加参数,由于openinterest默认是index 7 所以我们默认index 8
    params = (('change', 8),)


class SmaCross(bt.SignalStrategy):
    def __init__(self):
        sma1, sma2 = bt.ind.SMA(period=10), bt.ind.SMA(period=30)        
        crossover = bt.ind.CrossOver(sma1, sma2)
        self.signal_add(bt.SIGNAL_LONG, crossover)
        
        #为了显示新加的change添加一个change的移动均值
        btind.SMA(self.data.change, period=1, subplot=True)

cerebro = bt.Cerebro()
cerebro.addstrategy(SmaCross)

data = GenericCSV_extend(
    dataname='stock_data.csv',

    fromdate=datetime(2011, 1, 1),
    todate=datetime(2012, 12, 31),

    nullvalue=0.0,

    dtformat=('%Y%m%d'),

    datetime=1,
    open=2,
    high=3,
    low=4,    
    close=5,   
    change=7, #新导入数据change
    volume=9,
    openinterest=-1
)

cerebro.adddata(data)

cerebro.run()
cerebro.plot(iplot=False)

依然是基于上面的例子,我们添加了原本没有被导入的每日价格涨跌数据,另外添加了显示新数据的代码,输出应该是这样的

通过比较可以看到新的数据显示在了最下面。