01 引言
backtrader是目前功能最完善的Python量化回測框架之一,但學起來可能也是最費力的之一,對Python的元編程要求比較高。《
【手把手教你】入門量化回測最強神器backtrader(一)
》簡單介紹了backtrader框架的各個組成部分,然后以20日單均線策略為例,展示了策略模塊編寫和回測系統的運行。本文在該文的基礎上,仍以均線策略為例,進一步介紹策略模塊中交易日志的編寫和策略參數的尋優。公眾號后台回復“backtrader”可獲取《backtrader入門指南》的中文文檔。
02 回測實例
先來回顧一下交易策略模塊(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
策略模塊編寫:
可以與《
【手把手教你】入門量化回測最強神器backtrader(一)
》對比來看,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%,慘不忍睹。
plot_stock('sh','上證綜指','2010-01-01','2020-03-30')
下面分別對3-30日均線進行回測,這里假設指數可以交易,初始資金為100萬元,每次交易100股,注意如果指數收盤價乘以100超過可用資金,會出現交易失敗的情況,換句話說在整個交易過程中,是交易固定數量的標的,因此倉位的大小跟股價有直接關系。
main('sh','2010-01-01','',1000000,100)
從輸出結果不難看出,使用21日均線可以獲得最大收益,期末總資金為1106552(初始資金1000000)。
下面分期間進一步回測,考察同一標的,不同區間參數是否有顯著差異。將上證綜指分為兩個期間,2010-2015(期間收益率59.27%),205-2020(期間收益率-44%)。
plot_stock('sh','上證綜指','2010-01-01','2015-06-12')
main('sh','2010-01-01','2015-06-12',1000000,100)
結果顯示是21日均線最優。
plot_stock('sh','上證綜指','2015-06-13','')
main('sh','2015-06-13','',1000000,100)
這一期間指數收益率是-44%,采用均線策略進行交易最后獲得的收益也是負的,最好的結果是采用16日均線,虧損相對小些。
再看一下個股情況,以浦發銀行為例:
#浦發銀行股票
plot_stock('600000','浦發銀行','2010-01-01','2020-03-30')
注意,這里初始資金設置為10000,每次交易1000股,這個可以根據自己需要進行設定。從下面結果可以看出整個回測期間收益率沒有很高,這個與倉位設定有關,每次交易數量如果設定太大可能導致交易失敗,如果設置太小,導致資金沒有充分利用,總的收益會比較小。
main('600000','2010-01-01','',10000,1000)
從結果可以看出,浦發銀行的最優參數是14日均線,看來使用均線進行交易,最優參數的選擇可能因標的期間選擇的不同而存在差異。
下面以繼續以浦發銀行股票為例,輸出整個回測過程中的交易日志,即報告買入賣出價格、成本、手續費、策略收益等。
# 初始化cerebro回測系統設置
cerebro = bt.Cerebro()
#獲取數據
df=ts.get_k_data('600000',autype='qfq',start='2010-01-01',end='2020-03-30')
df.index=pd.to_datetime(df.date)
df=df[['open','high','low','close','volume']]
data = bt.feeds.PandasData(dataname=df,
fromdate=datetime(2010, 1, 1),
todate=datetime(2020, 3, 30) )
# 加載數據
cerebro.adddata(data)
# 將交易策略加載到回測系統中
#設置printlog=True,表示打印交易日志log
cerebro.addstrategy(MyStrategy,maperiod=14,printlog=True)
# 設置初始資本為10,000
cerebro.broker.setcash(10000.0)
# 設置交易手續費為 0.1%
cerebro.broker.setcommission(commission=0.001)
#設置買入設置,策略,數量
cerebro.addsizer(bt.sizers.FixedSize, stake=1000)
#回測結果
cerebro.run()
#獲取最后總資金
portvalue = cerebro.broker.getvalue()
#Print out the final result
print(f'總資金: {portvalue:.2f}')
輸出結果為
2010-01-25,買入:
價格:6.437,成本:6437.0,手續費:6.437
2010-01-27,SELL CREATE, 6.22
2010-01-28,賣出:
價格:6.193, 成本: 6437.0, 手續費6.193
2010-01-28,策略收益:
毛收益 -244.00, 凈收益 -256.63
......
2020-03-09,賣出:
價格:11.0, 成本: 11230.0,手續費11.0
2020-03-09,策略收益:
毛收益 -230.00, 凈收益 -252.23
2020-03-30,(MA均線:14日) 期末總資金 13253.89
總資金: 13253.89
回測圖形(使用matplotlib畫的原生圖有點丑)
%matplotlib inline
cerebro.plot()
03 結語
backtrader作為目前功能最完善的Python開源框架之一,其Strategy模塊具有很高的靈活性和擴展性。本文以單均線交易策略為例,簡單介紹了backtrader交易模塊(Strategy)中策略參數尋優(params)和交易日志(log)報告的編寫方法,希望為大家學習backtrader起到拋磚引玉的作用。下一篇推文將深入介紹backtrader回測系統評價指標和可視化。最后再強調一句,學習沒有捷徑,要想全面而深入地學習backtrader回測框架,最好的方法是研讀其官方文檔。公眾號后台回復“backtrader”可獲取《backtrader入門指南》的中文文檔。
