Backtrader
引言
前基于Python的量化回测框架有很多,开源框架有zipline、vnpy、pyalgotrader和backtrader等,而量化平台有Quantopian(国外)、聚宽、万矿、优矿、米筐、掘金等,这些量化框架或平台各有优劣。就个人而言,比较偏好用backtrader,因为它功能十分完善,有完整的使用文档,安装相对简单(直接pip安装即可)。优点是运行速度快,支持pandas的矢量运算;支持参数自动寻优运算,内置了talib股票分析技术指标库;支持多品种、多策略、多周期的回测和交易;支持pyflio、empyrica分析模块库、alphalens多因子分析模块库等;扩展灵活,可以集成TensorFlow、PyTorch和Keras等机器学习、神经网络分析模块。而不足之处在于,backtrader学习起来相对复杂,编程过程中使用了大量的元编程(类class),如果Python编程基础不扎实(尤其是类的操作),学起来会感到吃力。本文作为backtrader的入门系列之一,对其运行框架进行简要介绍,并以实际案例展示量化回测的过程。
Backtrader介绍
如果将backtrader包分解为核心组件,主要包括以下组成部分:
- 数据加载(Data Feed):将交易策略的数据加载到回测框架中。
- 交易策略(Strategy):该模块是编程过程中最复杂的部分,需要设计交易决策,得出买入/卖出信号。
- 回测框架设置( Cerebro):需要设置(i)初始资金(ii)佣金(iii)数据馈送(iv)交易策略交易头寸大小。
- 运行回测:运行Cerebro回测并打印出所有已执行的交易。
- 评估性能(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 来访问最后一个值。
Backtrader环境搭建
- 安装python环境 (anaconda)
- pip install backtrader[plotting]
- 新建jupyterProject文件夹,在其路径栏输入 jupyter lab,按enter键,等待启用jupyter
回测应用实例
量化回测说白了是使用历史数据去验证交易策略的性能,因此回测的第一步是搭建交易策略,这也是backtrader要设置的最重要和复杂的部分,策略设定好后,其余部分的代码编写是手到擒来。
构建策略(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)
数据加载(Data Feeds)
策略设计好后,第二步是数据加载,backtrader提供了很多数据接口,包括quandl(美股)、yahoo、pandas格式数据等,我们主要分析A股数据。
#先引入后面可能用到的包(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获取浦发银行(代码:600000)数据。
#使用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)
回测设置(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)
执行回测
输出回测结果。
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
可视化
对上述结果进行可视化,使用内置的matplotlib画图。至此,简单的单均线回测就完成了。下面图形展示了浦发银行在回测期间的价格走势、买卖点和交易总资金的变化等。
# 画图
cerebro.plot()
回测实例
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import datetime # For datetime objects
import os.path # To manage paths
import sys # To find out the script name (in argv[0])
# Import the backtrader platform
import backtrader as bt
# Create a Stratey
class TestStrategy(bt.Strategy):
params = (
('exitbars', 5),
('maperiod', 24),
('printlog', False),
)
def log(self, txt, dt=None, doprint=False):
''' Logging function for this strategy'''
if self.params.printlog or doprint:
dt = dt or self.datas[0].datetime.date(0)
print('[%s] %s' % (dt.isoformat(), txt))
def __init__(self):
# Keep a reference to the "close" line in the data[0] dataseries
self.dataclose = self.datas[0].close
# To keep track of pending orders and buy price/commission
self.order = None
self.buyprice = None
self.buycomm = None
# Add a MovingAverageSimple indicator
self.sma = bt.indicators.SimpleMovingAverage(
self.datas[0], period=self.params.maperiod)
# Indicators for the plotting show
bt.indicators.ExponentialMovingAverage(self.datas[0], period=25)
bt.indicators.WeightedMovingAverage(
self.datas[0], period=25).subplot = True
bt.indicators.StochasticSlow(self.datas[0])
bt.indicators.MACDHisto(self.datas[0])
rsi = bt.indicators.RSI(self.datas[0])
bt.indicators.SmoothedMovingAverage(rsi, period=10)
bt.indicators.ATR(self.datas[0]).plot = False
def notify_order(self, order):
if order.status in [order.Submitted, order.Accepted]:
# Buy/Sell order submitted/accepted to/by broker - Nothing to do
return
# Check if an order has been completed
# Attention: broker could reject order if not enough cash
if order.status in [order.Completed]:
if order.isbuy():
self.log(
'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
(order.executed.price,
order.executed.value,
order.executed.comm))
self.buyprice = order.executed.price
self.buycomm = order.executed.comm
else: # Sell
self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
(order.executed.price,
order.executed.value,
order.executed.comm))
self.bar_executed = len(self)
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
self.log('Order Canceled/Margin/Rejected')
self.order = None
def notify_trade(self, trade):
if not trade.isclosed:
return
self.log('OPERATION PROFIT: GROSS %.2f, NET %.2f' %
(trade.pnl, trade.pnlcomm))
def next(self):
# Simply log the closing price of the series from the reference
self.log('Close: %.2f' % self.dataclose[0])
# Check if an order is pending ... if yes, we cannot send a 2nd one
if self.order:
return
# Check if we are in the market
if not self.position:
# Not yet ... we MIGHT BUY if ...
if self.dataclose[0] > self.sma[0]:
# current close less than previous close
if self.dataclose[-1] < self.dataclose[-2]:
# previous close less than the previous close
# BUY, BUY, BUY!!! (with default parameters)
self.log('BUY CREATE: %.2f' % self.dataclose[0])
# Keep track of the created order to avoid a 2nd order
self.order = self.buy()
else:
# Already in the market ... we might sell
if self.dataclose[0] < self.sma[0]:
# SELL, SELL, SELL!!! (with all possible default parameters)
self.log('SELL CREATE: %.2f' % self.dataclose[0])
# Keep track of the created order to avoid a 2nd order
self.order = self.sell()
def stop(self):
self.log('(MA Period %2d) Ending Value %.2f' %
(self.params.maperiod, self.broker.getvalue()), doprint=True)
if __name__ == '__main__':
cerebro = bt.Cerebro()
# Add a strategy
cerebro.addstrategy(TestStrategy)
# cerebro.optstrategy(TestStrategy, maperiod=range(10, 31))
# 初始化数据的路径
modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
datapath = os.path.join(modpath, '.\\/datas\\/orcl-1995-2014.txt')
# Create a Data Feed,reverse 代表是否反转数据
data = bt.feeds.YahooFinanceCSVData(
dataname=datapath,
# Do not pass values before this date
fromdate=datetime.datetime(2000, 1, 1),
# Do not pass values after this date
todate=datetime.datetime(2000, 12, 31),
reverse=False)
# Add the Data Feed to Cerebro
cerebro.adddata(data)
# 改变账户初始金额
cerebro.broker.set_cash(100000.0)
# Set the commission - 0.1% ... divide by 100 to remove the % 交易佣金设置
cerebro.broker.setcommission(commission=0.001)
# 设置每笔交易交易的股票数量
cerebro.addsizer(bt.sizers.FixedSize, stake=10)
print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())
cerebro.run()
print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
# 画图
cerebro.plot()
先来回顾一下交易策略模块(Strategy)的构成。交易策略类代码包含参数或函数名如下:
(1)params-全局参数,可选:更改交易策略中变量/参数的值,可用于参数调优。
(2)log:日志,可选:记录策略的执行日志,可以打印出该函数提供的日期时间和txt变量。
(3) init:用于初始化交易策略的类实例的代码。
(4)notify_order,可选:跟踪交易指令(order)的状态。order具有提交,接受,买入/卖出执行和价格,已取消/拒绝等状态。
(5)notify_trade,可选:跟踪交易的状态,任何已平仓的交易都将报告毛利和净利润。
(6)next,必选:制定交易策略的函数,策略模块最核心的部分。
下面仍然以简单均线策略为例,重点介绍参数寻优和交易日志报告。
实现代码如下:
#先引入后面可能用到的包(package)
import pandas as pd
import numpy as np
import tushare as ts
import matplotlib.pyplot as plt
%matplotlib inline
#正常显示画图时出现的中文和负号
from pylab import mpl
mpl.rcParams['font.sans-serif']=['SimHei']
mpl.rcParams['axes.unicode_minus']=False
params是全局参数,maperiod是MA均值的长度,默认15天,printlog为打印交易日志,默认不输出结果,策略模块的核心在next()函数。
from datetime import datetime
import backtrader as bt
class MyStrategy(bt.Strategy):
params=(('maperiod',15),
('printlog',False),)
def __init__(self):
#指定价格序列
self.dataclose=self.datas[0].close
# 初始化交易指令、买卖价格和手续费
self.order = None
self.buyprice = None
self.buycomm = None
#添加移动均线指标
self.sma = bt.indicators.SimpleMovingAverage(
self.datas[0], period=self.params.maperiod)
#策略核心,根据条件执行买卖交易指令(必选)
def next(self):
# 记录收盘价
#self.log(f'收盘价, {dataclose[0]}')
if self.order: # 检查是否有指令等待执行,
return
# 检查是否持仓
if not self.position: # 没有持仓
#执行买入条件判断:收盘价格上涨突破15日均线
if self.dataclose[0] > self.sma[0]:
self.log('BUY CREATE, %.2f' % self.dataclose[0])
#执行买入
self.order = self.buy()
else:
#执行卖出条件判断:收盘价格跌破15日均线
if self.dataclose[0] < self.sma[0]:
self.log('SELL CREATE, %.2f' % self.dataclose[0])
#执行卖出
self.order = self.sell()
#交易记录日志(可省略,默认不输出结果)
def log(self, txt, dt=None,doprint=False):
if self.params.printlog or doprint:
dt = dt or self.datas[0].datetime.date(0)
print(f'{dt.isoformat()},{txt}')
#记录交易执行情况(可省略,默认不输出结果)
def notify_order(self, order):
# 如果order为submitted/accepted,返回空
if order.status in [order.Submitted, order.Accepted]:
return
# 如果order为buy/sell executed,报告价格结果
if order.status in [order.Completed]:
if order.isbuy():
self.log(f'买入:\n价格:{order.executed.price},\
成本:{order.executed.value},\
手续费:{order.executed.comm}')
self.buyprice = order.executed.price
self.buycomm = order.executed.comm
else:
self.log(f'卖出:\n价格:{order.executed.price},\
成本: {order.executed.value},\
手续费{order.executed.comm}')
self.bar_executed = len(self)
# 如果指令取消/交易失败, 报告结果
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
self.log('交易失败')
self.order = None
#记录交易收益情况(可省略,默认不输出结果)
def notify_trade(self,trade):
if not trade.isclosed:
return
self.log(f'策略收益:\n毛收益 {trade.pnl:.2f}, 净收益 {trade.pnlcomm:.2f}')
#回测结束后输出结果(可省略,默认输出结果)
def stop(self):
self.log('(MA均线: %2d日) 期末总资金 %.2f' %
(self.params.maperiod, self.broker.getvalue()), doprint=True)
下面定义一个主函数,用于对某股票指数(个股)在指定期间进行回测,使用tushare的旧接口获取数据,包含开盘价、最高价、最低价、收盘价和成交量。这里主要以3到30日均线为例进行参数寻优,考察以多少日均线与价格的交叉作为买卖信号能获得最大的收益。
def main(code,start,end='',startcash=10000,qts=500,com=0.001):
#创建主控制器
cerebro = bt.Cerebro()
#导入策略参数寻优
cerebro.optstrategy(MyStrategy,maperiod=range(3, 31))
#获取数据
df=ts.get_k_data(code,autype='qfq',start=start,end=end)
df.index=pd.to_datetime(df.date)
df=df[['open','high','low','close','volume']]
#将数据加载至回测系统
data = bt.feeds.PandasData(dataname=df)
cerebro.adddata(data)
#broker设置资金、手续费
cerebro.broker.setcash(startcash)
cerebro.broker.setcommission(commission=com)
#设置买入设置,策略,数量
cerebro.addsizer(bt.sizers.FixedSize, stake=qts)
print('期初总资金: %.2f' %
cerebro.broker.getvalue())
cerebro.run(maxcpus=1)
print('期末总资金: %.2f' % cerebro.broker.getvalue())
再定义一个画图函数,对相应股票(指数)在某期间的价格走势和累计收益进行可视化。
def plot_stock(code,title,start,end):
dd=ts.get_k_data(code,autype='qfq',start=start,end=end)
dd.index=pd.to_datetime(dd.date)
dd.close.plot(figsize=(14,6),color='r')
plt.title(title+'价格走势\n'+start+':'+end,size=15)
plt.annotate(f'期间累计涨幅:{(dd.close[-1]/dd.close[0]-1)*100:.2f}%', xy=(dd.index[-150],dd.close.mean()),
xytext=(dd.index[-500],dd.close.min()), bbox = dict(boxstyle = 'round,pad=0.5',
fc = 'yellow', alpha = 0.5),
arrowprops=dict(facecolor='green', shrink=0.05),fontsize=12)
plt.show()
以上证综指为例,回测期间为2010-01-01至2020-03-30,期间累计收益率为-15.31%,惨不忍睹。
面分别对3-30日均线进行回测,这里假设指数可以交易,初始资金为100万元,每次交易100股,注意如果指数收盘价乘以100超过可用资金,会出现交易失败的情况,换句话说在整个交易过程中,是交易固定数量的标的,因此仓位的大小跟股价有直接关系。
main('sh','2010-01-01','',1000000,100)
plot_stock('sh','上证综指','2010-01-01','2020-03-30')
Analyzers模块
Analyzers模块涵盖了评价一个量化策略的完整指标,如常见的夏普比率、年化收益率、最大回撤、Calmar比率等等。Analyzers模块原生代码能获取的评价指标如下图所示,其中TradeAnalyzer和PeriodStats又包含了不少指标。由于采用元编程,Analyzers的扩展性较强,可以根据需要添加自己的分析指标,如获取回测期间每一时刻对应的总资金。
策略模块编写
(1)params-全局参数,可选:更改交易策略中变量/参数的值,可用于参数调优。
(2)log:日志,可选:记录策略的执行日志,可以打印出该函数提供的日期时间和txt变量。
(3) init:用于初始化交易策略的类实例的代码。
(4)notify_order,可选:跟踪交易指令(order)的状态。order具有提交,接受,买入/卖出执行和价格,已取消/拒绝等状态。
(5)notify_trade,可选:跟踪交易的状态,任何已平仓的交易都将报告毛利和净利润。
(6)next,必选:制定交易策略的函数,策略模块最核心的部分。
(7)其他,包括start()、nextsstart()、stop()、prenext()、notify_fund()、notify_store()和notify_cashvalue。
面以技术分析指标RSI(不了解的请自行百度)的择时策略为例,当RSI<30时买入,RSI>70时卖出。为了简便起见,策略模块中只包含最核心的交易信号。
import pandas as pd
import backtrader as bt
from datetime import datetime
class MyStrategy(bt.Strategy):
params=(('short',30),
('long',70),)
def __init__(self):
self.rsi = bt.indicators.RSI_SMA(
self.data.close, period=21)
def next(self):
if not self.position:
if self.rsi < self.params.short:
self.buy()
else:
if self.rsi > self.params.long:
self.sell()
回测设置
回测系统设置与之前一样,主要是数据加载、交易本金、手续费、交易数量的设置,此处以tushare的旧接口获取股票002537的交易数据进行量化回测。
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import tushare as ts
#以股票002537为例
df=ts.get_k_data('002537',start='2010-01-01')
df.index=pd.to_datetime(df.date)
#df['openinterest'] = 0
df=df[['open','high','low','close','volume']]
data = bt.feeds.PandasData(dataname=df,
fromdate=datetime(2013, 1, 1),
todate=datetime(2020, 4, 17) )
# 初始化cerebro回测系统设置
cerebro = bt.Cerebro()
# 加载数据
cerebro.adddata(data)
# 将交易策略加载到回测系统中
cerebro.addstrategy(MyStrategy)
# 设置初始资本为100,000
cerebro.broker.setcash(100000.0)
#每次固定交易数量
cerebro.addsizer(bt.sizers.FixedSize, stake=1000)
#手续费
cerebro.broker.setcommission(commission=0.001)
运行回测
这里重点是Analyzers模块的调用与结果输出,调用模块是cerebro.addanalyzer(),再从模块中获取分析指标,如夏普比率是bt.analyzers.SharpeRatio,然后是给该指标重命名方便之后调用,即 _name=’SharpeRatio’。要获取分析指标,需要先执行回测系统,cerebro.run(),并将回测结果赋值给变量results,分析指标存储在results[0]里 (strat变量代替),通过strat.analyzers.SharpeRatio.get_analysis()即可获取相应数据,其他指标操作方法类似。
print('初始资金: %.2f' % cerebro.broker.getvalue())
cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name = 'SharpeRatio')
cerebro.addanalyzer(bt.analyzers.DrawDown, _name='DW')
results = cerebro.run()
strat = results[0]
print('最终资金: %.2f' % cerebro.broker.getvalue())
print('夏普比率:', strat.analyzers.SharpeRatio.get_analysis())
print('回撤指标:', strat.analyzers.DW.get_analysis())
输出结果:
初始资金: 100000.00
最终资金: 110215.33
夏普比率: OrderedDict([('sharperatio', 0.094)])
回撤指标: AutoOrderedDict([('len', 280), ('drawdown', 1.01), ('moneydown', 1126.60), ('max', AutoOrderedDict([('len', 280), ('drawdown', 3.61), ('moneydown', 4016.60)]))])
回测结果可视化
下面输出回测图表,一张大图上包含了三张图:
(1)资金变动图:可以看到在实施交易策略的数据期内,资金的盈利/损失。
(2)交易收益/亏损。蓝色(红色)点表示获利(亏损)交易以及获利(亏损)多少。
(3)价格图表。绿色和红色箭头分别表示交易策略的进入点和退出点。黑线是交易标的随时间变化的价格, 条形图表示每个条形图期间资金的交易量。
Analyzers模块指标可视化
其他
init
任何类在生成的时候都是先调用这一初始化构造函数。也就是说,在实例生成的时候,这个函数将被调用。
Birth: start
start方法在cerebro告诉strategy,是时候开始行动了,也就是说,通知策略激活的时候被调用。
Childhood: prenext
有些技术指标,比如我们提到的MA,存在一个窗口,也就是说,需要n天的数据才能产生指标,那么在没有产生之前呢?这个prenext方法就会被自动调用。
Adulthood: next
这个方法是最核心的,就是每次移动到下一的时间点,策略将会调用这个方法,所以,策略的核心往往都是写在这个方法里的。Death: stop
策略的生命周期结束,cerebro把这一策略退出。
策略当中的回调函数
Strategy 类就像真实世界的交易员一样,当交易执行的时候,他会得到一些消息,譬如order是否执行,一笔trader赚了多少钱,等等。这些消息都将在Strategy类中通过回调函数被得以知晓。这些回调函数如下:
notify_order(order):下的单子,order的任何状态变化都将引起这一方法的调用
notify_trade(trade):任何一笔交易头寸的改变都将调用这一方法
notify_cashvalue(cash, value):任何现金和资产组合的变化都将调用这一方法
notify_store(msg, *args, **kwargs):可以结合cerebro类进行自定义方法的调用
那么问题接踵而至,这里我们只关注前2种方法中监测对象的可变化方式。
trade指的是一笔头寸,trade是open的状态指当前时刻,这一标的的头寸从0变到某一非零值。trade是closed则刚好相反。
trade大概有如下常用属性
ref: 唯一id
size (int): trade的当前头寸
price (float): trade资产的当前价格
value (float): trade的当前价值
commission (float): trade的累计手续费
pnl (float): trade的当前pnl
pnlcomm (float): trade的当前pnl减去手续费
isclosed (bool): 当前时刻trade头寸是否归零
isopen (bool): 新的交易更新了trade
justopened (bool): 新开头寸
dtopen (float): trade open的datetime
dtclose (float): trade close的datetime
Orders
order是strategy发出的指令,让cerebro去执行。
strategy自身有buy, sell and close方法来生成order,cancel方法来取消一笔order。下单的方式有很多,后续会介绍,这里主要讲回调函数中,咱们可以获得哪些信息。
order.status可以返回order的当前状态
order.isbuy可以获得这笔order是否是buy
order.executed.price
order.executed.value
order.executed.comm
分别可以获得执行order的价格,总价,和手续费
class TestStrategy(bt.Strategy):
params = (
('maperiod', 15),
)
def log(self, txt, dt=None):
''' Logging function fot this strategy'''
dt = dt or self.datas[0].datetime.date(0)
print('%s, %s' % (dt.isoformat(), txt))
def __init__(self):
# Keep a reference to the "close" line in the data[0] dataseries
self.dataclose = self.datas[0].close
# To keep track of pending orders and buy price/commission
self.order = None
self.buyprice = None
self.buycomm = None
# Add a MovingAverageSimple indicator
self.sma = bt.indicators.SimpleMovingAverage(
self.datas[0], period=self.params.maperiod)
def start(self):
print("the world call me!")
def prenext(self):
print("not mature")
def notify_order(self, order):
if order.status in [order.Submitted, order.Accepted]:
# Buy/Sell order submitted/accepted to/by broker - Nothing to do
return
# Check if an order has been completed
# Attention: broker could reject order if not enougth cash
if order.status in [order.Completed]:
if order.isbuy():
self.log(
'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
(order.executed.price,
order.executed.value,
order.executed.comm))
self.buyprice = order.executed.price
self.buycomm = order.executed.comm
else: # Sell
self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
(order.executed.price,
order.executed.value,
order.executed.comm))
self.bar_executed = len(self)
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
self.log('Order Canceled/Margin/Rejected')
self.order = None
可以看到打印出来的结果中,有start和prenext,最后当然也有death
Backtrader的indicator
def __init__(self):
# Keep a reference to the "close" line in the data[0] dataseries
self.dataclose = self.datas[0].close
# To keep track of pending orders and buy price/commission
self.order = None
self.buyprice = None
self.buycomm = None
# Add a MovingAverageSimple indicator
self.sma = bt.indicators.SimpleMovingAverage(
self.datas[0], period=self.params.maperiod)
这里的最后,我们使用了一个backtrader内置的indicator,后续我们将尝试自己编写一个indicator。
数据的获取
datafeed,也就是cerebro的本源,数据
dataframe = pd.read_csv('dfqc.csv', index_col=0, parse_dates=True)
dataframe['openinterest'] = 0
data = bt.feeds.PandasData(dataname=dataframe,
fromdate = datetime.datetime(2015, 1, 1),
todate = datetime.datetime(2016, 12, 31)
)
# Add the Data Feed to Cerebro
cerebro.adddata(data)
2014-03-13 00:00:00.005,1.425,1.434,1.449,1.418,457767208.0
2014-03-14 00:00:00.005,1.429,1.422,1.436,1.416,196209439.0
2014-03-17 00:00:00.005,1.433,1.434,1.437,1.422,250946201.0
2014-03-18 00:00:00.005,1.434,1.425,1.437,1.424,245516577.0
2014-03-19 00:00:00.005,1.423,1.419,1.423,1.406,331866195.0
2014-03-20 00:00:00.005,1.412,1.408,1.434,1.407,379443759.0
2014-03-21 00:00:00.005,1.406,1.463,1.468,1.403,825467935.0
dataframe = pd.read_csv('dfqc.csv', index_col=0, parse_dates=True)
把csv读入pandas的参数,index_col=0表示第一列时间数据是作为pandas 的index的,parse_dates=Ture是自动把数据中的符合日期的格式变成datetime类型。为什么要这样呢?其实读入后的pandas长怎么样都是由backtrader规定的
pandas的要求的结构,我们就知道,不仅仅有self.datas[0].close,还会有self.datas[0].open。也确实如此。只是我们通常拿close作为一个价格基准
self.datas[0].close
返回的是一个lines。lines是backtrader一个很重要的概念,可以理解为时间序列流,这类数据,后面可以跟index,也就是说,可以有
self.datas[0].close[0]
self.datas[0].close[-1]
这里的index是有意义的,0代表当前时刻,-1代表前一时刻,1代表后一时刻,以此类推
所以在next中使用self.dataclose[0],self.dataclose[-1]
安装TA-lib
下载TA_Lib-0.4.19-cp37-cp37m-win_amd64
pip install TA_Lib-0.4.19-cp37-cp37m-win_amd64.whl