简单使用backtrader+pyfolio 做策略回测

发布于: 雪球转发:0回复:0喜欢:0

记录最近用backtrader+pyfolio 做策略回测。

安装 backtrader 和 pyfolio :

pip install backtrader
pip install pyfolio

导入 backtrader 和 pyfolio :
import backtraderas bt
import pyfolio as pf


在代码中,先定义策略 class(以EMA cross策略为例)。
EMACross:EMA(Exponential Moving Average)是指数移动平均值,趋向类指标,指数移动平均值是以指数式递减加权的移动平均。 其构造原理是:对收盘价进行加权算术平均,用于判断价格未来走势的变动趋势。与MACD指标、DMA指标相比,EMA指标由于其计算公式中着重考虑了当天价格(当期)行情的权重,决定了其作为一类趋势分析指标,在使用中克服了MACD指标对于价格走势的滞后性缺陷,同时,也在一定程度上消除了DMA指标在某些时候对于价格走势所产生的信号提前性。当EMA快线上穿EMA慢线为买入信号,EMA快线下穿EMA慢线为卖出信号
class MyStrategy(bt.Strategy):
    params = ()

    def log(self, txt, dt=None):
        ''' Logging function fot this strategy'''
        dt = dt or self.data.datetime[0]
        if isinstance(dt, float):
            dt = bt.num2date(dt)
        print('%s, %s' % (dt.isoformat(), txt))

    def __init__(self):
        pass

    def next(self):
        pass

class EMACross(MyStrategy):
    params = (('ema_fast', 9), ('ema_slow', 25),)

    def log(self, txt, dt=None):
        pass

    def __init__(self):
        
        ema_slow = btind.ExponentialMovingAverage(self.data.close, period=self.p.ema_slow)
        ema_fast = btind.ExponentialMovingAverage(self.data.close, period=self.p.ema_fast)
       
        # CrossOver (1: up, -1: down) close / sma
        self.buysell = btind.CrossOver(ema_fast, ema_slow, plot=True)
        
        # Sentinel to None: new ordersa allowed
        self.order = None

    def next(self):
        # Access -1, because drawdown[0] will be calculated after "next"
        self.log('DrawDown: %.2f' % self.stats.drawdown.drawdown[-1])
        self.log('MaxDrawDown: %.2f' % self.stats.drawdown.maxdrawdown[-1])

        # Check if we are in the market
        if self.position:
            if self.buysell < 0:
                self.log('SELL CREATE, %.2f' % self.data.close[0])
                self.sell()

        elif self.buysell > 0:
            self.log('BUY CREATE, %.2f' % self.data.close[0])
            self.buy()


定义首末时间段、行情数据、及策略, 运行回测:
        cerebro = bt.Cerebro()
        start_date = datetime.strptime(self.startDate, "%Y-%m-%d")   # 回测开始时间
        end_date = datetime.strptime(self.endDate, "%Y-%m-%d")   # 回测结束时间
        d = self.marketData.copy()[mdata_fields[1:]]
        d['date'] = [datetime.strptime(d.loc[i, 'date'], "%Y-%m-%d") for i in d.index]
        d = d.set_index('date', drop=False)
        data = bt.feeds.PandasData(dataname=d, fromdate=start_date, todate=end_date)   # 加载数据
        cerebro.adddata(data)   #
        # data = bt.feeds.BacktraderCSVData(dataname='../../datas/2006-day-001.txt')
        # cerebro.adddata(data)
        
        cerebro.addobserver(bt.observers.DrawDown)
        
        strategyObj = globals().get(strategy)
        if strategyObj is None:
            return None, None, None
        
        cerebro.addstrategy(strategyObj)
        
        cerebro.addanalyzer(btanalyzers.SharpeRatio, _name="sharpe")
        cerebro.addanalyzer(btanalyzers.DrawDown, _name="drawdown")
        cerebro.addanalyzer(btanalyzers.Returns, _name="returns")
        cerebro.addanalyzer(bt.analyzers.PyFolio, _name='pyfolio')
        results = cerebro.run()


输出的参数和形式很多,为了将结果集成在自己网页版的测试量化平台中 ADOG

因此考虑了几种可能的形式,总结如下,经过比较最后选用最后一种, 比较简洁美观。

1、由backTrader的.plot()方法。

cerebro.plot()方法, 直接输出如下(不美观):


添加图片注释,不超过 140 字(可选)

2、上面是matplotlib画出,需要在网页中显示,方法如下:

figs = cerebro.plot()
buffer = BytesIO()
figs[0][0].savefig(buffer)
plot_data = buffer.getvalue()
# 将matplotlib图片转换为HTML
imb = base64.b64encode(plot_data)   # 对plot_data进行编码
ims = imb.decode()
imd = "data:image/png;base64," + ims

然后在网页中:
<div align="center"> <img src="{{ img }}"> </div>


这样上面的图可以显示下网页中。
3、用pyfolio 做策略评估。
用pyfolio的create_full_tear_sheet() 方法,计算指标,绘制图形结果。
import pyfolio as pf
....

(接着上面,继续....)

tbl, df, _, _, fig, _ = pf.create_full_tear_sheet(returns)   


returns 是第1 中backTrader 生成的回测结果。
因为pyfolio 方法输出图形在ipython中显示,也不够美观。同样为了输出动态显示结果在自己的网页中,因此修改pyfolio源码。将pf.create_full_tear_sheet(returns) 的中间生成对象(包括图片、表格)等封装输出,方便直接调用。
这样,用上面第2 的方法后,可以在网页中使用。
4、输出结果用plotly输出图片。
plotly是我一直比较喜欢的工具,在下面提及的专栏中多次讨论。
用backTrader 和 pyfolio 进行回测评估完后,用plotly 作图和表,在自己的网页中表现。
在pyfolio 源码 tears.py 增加自己的图表输出:
def getAdogEval(returns):
    out=plotting.show_perf_stats(returns)
    return out


然后:
if isBtPlotly:
            ret = returns.to_frame()
            ret['hv'] = [positions.loc[i, 'Data0']   if i in positions.index else None for i in ret.index]
            ret['cash'] = [positions.loc[i, 'cash']   if i in positions.index else None for i in ret.index]
            ret['pv'] = ret['hv'] + ret['cash']
            ret['pchg'] = ret['return']
            ret['signal'] = [transactions.loc[i, 'amount'] if i in transactions.index else None for i in ret.index]
            
            ret['date'] = [datetime.strftime(i, '%Y-%m-%d') for i in ret.index]
            ret.reset_index(drop=True, inplace=True)
            deal = pd.merge(left=self.marketData.copy(), right=ret, on='date', how='left')
            evalInfo = self.eval(deal)
            if (df is not None) & (len(df) > 0):
                df.fillna(0, inplace=True)
                
                df.reset_index(drop=False, inplace=True)
                df['Backtest'] = [df.loc[i, 'Backtest'] if type(df.loc[i, 'Backtest']) == type('str') else '%0.2f' % (df.loc[i, 'Backtest']) for i in df.index]
                # df = df.astype.round(2)
                # btInfo.reset_index(drop=False, inplace=True)
            bt_div = self.commPlotly(deal, evalInfo, btInfo=btInfo, tblstr=tbl, summary=df, isEMA=False, title='Strategy:' + strategy, isShow=isShow)


其中 self.commPlotly() 是ployly 的普通画图。

最后效果如下, 下图左侧是 pyfolio 统计的回测结果和评估,右侧是根据输出明细数据的图形。




如有问题,请个人主页扫码或点击“ 知shi星球(阿岛格)”加入讨论。

(视频) 阿岛格:低门槛搭建你的个人量化平台-演示
(视频) 阿岛格:打造个人的量化平台(期权)