backtrader簡介
backtrader是基於Python的量化回測框架,優點是運行速度快,支持pandas的矢量運算;支持參數自動尋優運算,內置了talib股票分析技術指標庫;支持多品種、多策略、多周期的回測和交易;支持pyflio、empyrica分析模塊庫、alphalens多因子分析模塊庫等;擴展靈活,可以集成TensorFlow、PyTorch和Keras等機器學習、神經網絡分析模塊。
如果將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 來訪問最后一個值。
編寫回測應用
構建策略(Strategy)
每一個策略都是一個類,是一個繼承bt.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)
self.dataclose[0] # 當日的收盤價 self.dataclose[-1] # 昨天的收盤價 self.dataclose[-2] # 前天的收盤價
獲取數據加載數據
#使用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)
這里需要注意backtrader對每種數據的來源都有一定的標准。backtrader對於pandas的標准就是這些列的名字是open,close,high,low,volume,openinterest。
我們通過ts獲取數據的列為:open close high low volume code,我們還需要在此基礎上添加一列openinterest,我們這里沒有用到openinterest,所以把它設置為零就可以了:df['openinterest']=0。
另外backtrader要求pandas下的DataFeed、DataFrame的index是時間,所以設置:df.index=pd.to_datetime(df.date)
最后:df=df[['open','high','low','close','volume','openinterest']]
創建並設置模型
#回測期間 start=datetime(2010, 3, 31) end=datetime(2020, 3, 31) # 加載數據 data = bt.feeds.PandasData(dataname=dataframe,fromdate=start,todate=end) # 初始化cerebro回測系統設置 cerebro = bt.Cerebro() #將數據傳入回測系統 cerebro.adddata(data) # 將交易策略加載到回測系統中 cerebro.addstrategy(my_strategy1) # 設置初始資本為10,0000 startcash = 100000 cerebro.broker.setcash(startcash) # 設置交易手續費為 0.2% cerebro.broker.setcommission(commission=0.002)
執行回測並可視化
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)}') cerebro.plot()
源碼
# -*- coding:utf-8 -*- #正常顯示畫圖時出現的中文和負號 from pylab import mpl mpl.rcParams['font.sans-serif']=['SimHei'] import pandas as pd from datetime import datetime import backtrader as bt import tushare as ts #策略 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) def get_data(code,start='2010-01-01',end='2020-03-31'): df=ts.get_k_data(code,autype='qfq',start=start,end=end) #獲取的數據格式: open close high low volume code #將date變成DataFrame的index df.index=pd.to_datetime(df.date) df['openinterest']=0 df=df[['open','high','low','close','volume','openinterest']] return df if __name__ == "__main__": #獲取數據 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回測系統設置 cerebro = bt.Cerebro() #將數據傳入回測系統 cerebro.adddata(data) # 將交易策略加載到回測系統中 cerebro.addstrategy(my_strategy1) # 設置初始資本為10,000 startcash = 100000 cerebro.broker.setcash(startcash) # 設置交易手續費為 0.2% cerebro.broker.setcommission(commission=0.002) 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)}') cerebro.plot()
backtrader官方文檔:
https://www.backtrader.com/docu/
refer:
https://zhuanlan.zhihu.com/p/122183963