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星球(阿岛格)”加入讨论。
(视频) 阿岛格:低门槛搭建你的个人量化平台-演示
(视频) 阿岛格:打造个人的量化平台(期权)