前言
-
做量化交易有三个重要环节:
第一、数据源
有关数据源部分可以了解我之前一个专栏:python股票量化交易入门
第二、数据回测
第三、实盘交易其中数据回测,目前市面上有一些第三方云平台,以聚宽、优矿、米筐这3个最常见,这3个平台都是Python API的结构,并且有完善的测试结果的可视化工具。
但是,第三方云平台都是封装了底层代码,如果在你代码运行出现问题想调试的时候,就不能直接修改源码去解决,并且如果你想定制化你个人的一些回测都会受到一定的限制;另外,第三方云平台的网络服务器编译往往没有本地编译快;还有第三方云平台一般都存在收费情况。
结合第三方云平台的几个问题,你是否想构造一套属于自己的回测框架?或者有没有好的开源的本地回测框架可以使用?
一、backtrader是什么?
用于回测和交易的功能丰富的 Python 框架
backtrader让您可以专注于编写可重复使用的交易策略、指标和分析器,而不必花时间构建基础设施。
二、backtrader的组成
- 数据加载(DataFeed):
将交易的数据加载到回测框架中 - 交易策略(Strategy):
自己想回测的交易策略,需要得出买卖信号 - 回测框架设置(Cerebro):
需要设置初始资金,交易佣金,交易策略,交易头寸大小等 - 运行回测
运行Cerebro回测,并打印回测结果 - 评估性能(Analyzers
添加计算评估指标并以图形方式展示
三、开始使用backtrader
先用一个简单但有标准的回测代码来进一步了解backtrader。
数据使用tushare的数据
# -*- coding: UTF-8 -*-
"""
@Project :
@File :backtraderTest1.py
@Author :johnny2004
@Date :2022-05-25 0:21
@desc : 使用开源框架backtrader回测简单的均线策略
"""
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import datetime
import pandas as pd
from matplotlib import pyplot as plt
import tushare as ts
import backtrader as bt
# 设置图表可以中文显示
plt.rcParams['font.sans-serif'] = ['SimHei']
# tushare认证和初始化pro接口
ts.set_token('xxx')
pro = ts.pro_api()
def get_data(code, startDate='2015-01-01', endDate=datetime.date.today()):
data = pro.query('daily', ts_code=code, start_date=startDate, end_date=endDate)
# 对股票数据进行整合,符合backtrader
data.index = pd.to_datetime(data['trade_date'])
data['openinterest'] = 0
data['volume'] = data['vol']
data = data[['open', 'high', 'low', 'close', 'volume', 'openinterest']]
data = data.sort_index()
return data
''' 第二步:构建策略 策略:上穿20日均线买入,跌穿20均线就卖出'''
class MyStrategy(bt.Strategy):
params = (
('maPeriod', 20),
)
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):
self.ma = bt.indicators.SimpleMovingAverage(self.datas[0], period=self.params.maPeriod)
# 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
# 监控订单情况
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
elif order.issell():
self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
(order.executed.price,
order.executed.value,
order.executed.comm))
# 记录当前bar的状态,并复制给自身属性bar_executed
self.bar_executed = len(self)
# 如果下单的情况在另外状态
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
self.log('Order Canceled/Margin/Rejected')
# Write down: no pending order
# 将订单状态重置为None
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))
# 每个bar都会执行一次,回测的每个日期都会执行一次
def next(self):
# print('===均线值',self.ma[0])
# print('===收盘价',self.datas[0].close[0])
# print('===有订单在提交',self.order)
# 如果订单在提交或者允许的情况下,直接返回
if self.order:
print('===有正在执行的订单', self.order)
return
# 空仓
if not self.position.size:
if self.datas[0].close[0] >= self.ma[0]:
self.order = self.buy(size=200)
print(f"====买入>>>买入价{self.datas[0].close[0]} > 均线价{self.ma[0]} 买入后仓位={self.getposition().size}")
else:
if self.datas[0].close[0] < self.ma[0]:
self.order = self.sell(size=200)
print(f"====卖出>>>买出价{self.datas[0].close[0]} < 均线价{self.ma[0]} 买入后仓位={self.getposition().size}")
if __name__ == '__main__':
# code = '000001.SZ'
code = '600519.SH'
data = get_data(code, '20160101', '20200101')
print(data.head())
# print(data)
'''第一步:数据加载 dataname:数据来源, fromdate:开始时间,enddate:结束时间(date格式)'''
fromDate = datetime.date(2016, 1, 1)
toDate = datetime.date(2020, 1, 1)
data = bt.feeds.PandasData(dataname=data, fromdate=fromDate, todate=toDate) # 加载数据
'''第三步: 策略设置'''
# 创建大脑
cerebro = bt.Cerebro()
# 添加策略
cerebro.addstrategy(MyStrategy)
# 将数据加入回测系统
cerebro.adddata(data)
# 设置经纪人
startCash = 1000000
cerebro.broker.setcash(startCash)
cerebro.broker.setcommission(0.0002)
# 运行大脑
startDate = fromDate.strftime('%Y-%m-%d')
endDate = toDate.strftime('%Y-%m-%d')
print(f'初始资金:{round(startCash, 2)}\n回测时间:{startDate}到{endDate}:')
cerebro.run()
endCash = cerebro.broker.getvalue()
print(f'剩余总资金:{round(endCash, 2)}')
netIncome = round(endCash - startCash, 2)
print(f'净收益:{netIncome}')
'''第四步,可视化'''
# cerebro.plot(volume=False)
cerebro.plot()
打印结果:
2019-12-18, BUY EXECUTED, Price: 1174.00, Cost: 234800.00, Comm 46.96
====卖出>>>买出价1157.4 < 均线价1157.716 买入后仓位=200
2019-12-20, SELL EXECUTED, Price: 1162.18, Cost: 234800.00, Comm 46.49
2019-12-20, OPERATION PROFIT, GROSS -2364.00, NET -2457.45
====买入>>>买入价1163.0 > 均线价1148.6955 买入后仓位=0
2019-12-30, BUY EXECUTED, Price: 1170.20, Cost: 234040.00, Comm 46.81
剩余总资金:1116412.06
净收益:116412.06图表展示:
总结
先了解backtrader框架的基本组成部分,根据github文档例子,自己先写一遍代码,并跑通看看回测结果和图表视图,让自己能更快的认识backtrader,其中代码部分只需要认清执行的步骤顺序和代码结构方式就可以,具体的代码后面再逐一分析。