Python量化交易:Zipline与Backtrader回测实战
各位朋友,大家好!今天我们来聊聊Python量化交易中两个非常重要的回测框架:Zipline和Backtrader。回测是量化交易策略开发过程中至关重要的一环,它能帮助我们评估策略在历史数据上的表现,从而指导我们优化策略,避免盲目投入实盘交易。
1. 回测的重要性与基本流程
在开始深入了解Zipline和Backtrader之前,我们先来明确一下回测的重要性。
为什么需要回测?
- 验证策略有效性: 回测能够模拟策略在历史市场中的表现,检验策略是否能够产生预期的收益。
- 风险评估: 通过回测,我们可以了解策略的最大回撤、波动率等风险指标,评估策略的风险承受能力。
- 参数优化: 回测可以帮助我们找到策略的最优参数组合,提高策略的收益率。
- 避免过拟合: 回测可以帮助我们检测策略是否存在过拟合现象,即策略在历史数据上表现很好,但在未来市场中表现不佳。
回测的基本流程:
- 数据准备: 收集历史交易数据,包括股票代码、时间、开盘价、最高价、最低价、收盘价、成交量等。
- 策略编写: 根据交易逻辑编写交易策略的代码。
- 回测引擎配置: 配置回测引擎的参数,包括起始资金、交易手续费、滑点等。
- 运行回测: 运行回测引擎,模拟策略在历史数据上的交易过程。
- 结果分析: 分析回测结果,包括收益率、最大回撤、夏普比率等指标,评估策略的性能。
2. Zipline:事件驱动的回测框架
Zipline 是由 Quantopian 开发的一个开源的回测框架,它以事件驱动的方式模拟交易过程。Zipline 的特点是易于使用、性能高效、支持自定义数据源和交易规则。
2.1 Zipline 的基本概念
-
Algorithm: 算法是 Zipline 的核心,它包含了策略的交易逻辑。算法定义了
initialize()
、handle_data()
和before_trading_start()
三个重要的函数。initialize(context)
: 在回测开始时执行一次,用于初始化算法的参数和状态。context
对象用于存储算法的全局变量。handle_data(context, data)
: 在每个交易日执行一次,用于处理当前时刻的市场数据,并根据策略逻辑进行交易。data
对象包含了当前时刻的市场数据。before_trading_start(context, data)
: 在每个交易日开盘前执行一次,用于进行一些准备工作,例如计算移动平均线等。
-
DataPortal: 数据门户负责提供历史数据给算法。Zipline 支持多种数据源,例如 CSV 文件、数据库等。
-
Order: 订单是算法发出的交易指令。Zipline 支持多种订单类型,例如市价单、限价单等。
-
Portfolio: 投资组合记录了算法持有的资产和现金。
-
PerfTracker: 性能跟踪器负责记录回测过程中的各种指标,例如收益率、最大回撤等。
2.2 Zipline 的安装与配置
Zipline 的安装相对复杂,因为它依赖于许多 Python 包。建议使用 Anaconda 环境来安装 Zipline。
conda create -n zipline python=3.8
conda activate zipline
conda install -c conda-forge zipline
安装完成后,需要下载一些示例数据:
zipline ingest
2.3 Zipline 代码示例:均线策略
下面是一个简单的均线策略的 Zipline 代码示例:
import zipline
from zipline.api import order_target, record, symbol
from zipline.utils.events import date_rules, time_rules
import pandas as pd
def initialize(context):
"""
初始化策略
"""
context.i = 0
context.asset = symbol('AAPL')
context.sma_length = 20
# 按月运行 before_trading_start 函数
schedule_function(
rebalance,
date_rules.month_start(),
time_rules.market_open(),
)
# 每天记录价格和移动平均线
schedule_function(
record_vars,
date_rules.every_day(),
time_rules.market_close(),
)
def handle_data(context, data):
"""
处理每个交易日的数据
"""
context.i += 1
if context.i < context.sma_length:
return
sma = data.history(context.asset, 'price', bar_count=context.sma_length, frequency="1d").mean()
if data.current(context.asset, 'price') > sma:
order_target(context.asset, 100)
elif data.current(context.asset, 'price') < sma:
order_target(context.asset, 0)
record(AAPL=data.current(context.asset, 'price'), SMA=sma)
def rebalance(context, data):
"""
每月初重新平衡仓位
"""
pass # 在这个例子中,我们不做任何操作
def record_vars(context, data):
"""
记录变量
"""
pass # 这个例子中,handle_data 已经进行记录了
代码解释:
initialize(context)
函数初始化了策略的参数,包括股票代码AAPL
和移动平均线的长度20
。同时,使用schedule_function
函数安排了rebalance
和record_vars
函数的执行时间。handle_data(context, data)
函数是策略的核心,它在每个交易日执行。该函数首先计算过去 20 天的移动平均线,然后根据当前价格与移动平均线的关系来决定是否买入或卖出股票。order_target(context.asset, 100)
函数表示将AAPL
的仓位调整到 100 股。record(AAPL=data.current(context.asset, 'price'), SMA=sma)
函数用于记录当前价格和移动平均线。
2.4 运行 Zipline 回测
要运行 Zipline 回测,需要创建一个 Python 脚本,并调用 zipline.run_algorithm()
函数。
import pandas as pd
from zipline import run_algorithm
from zipline.api import symbol
from datetime import datetime
import pytz
from moving_average_cross import initialize, handle_data # 导入你的策略文件
start = pd.Timestamp('2017-01-01', tz='utc')
end = pd.Timestamp('2018-01-01', tz='utc')
# 准备数据,这里需要创建一个 CSV 文件,包含 AAPL 的历史数据
# 数据需要包含 date, open, high, low, close, volume, adj_close 列
# 并将数据保存到 'AAPL.csv' 文件中
# 创建一个 DataFrame,用于存储数据
data = pd.read_csv('AAPL.csv', index_col='date', parse_dates=True)
data.index = data.index.tz_localize('UTC') # 设置时区
# 创建一个 Panel,用于存储数据
# panel = {
# 'AAPL': data
# }
# panel = pd.Panel(panel)
def load_data():
# 加载数据
data = pd.read_csv('AAPL.csv', index_col='date', parse_dates=True)
data.index = data.index.tz_localize('UTC') # 设置时区
# 将数据转换为 Zipline 期望的格式
return data
def analyze(context, perf):
import matplotlib.pyplot as plt
fig = plt.figure()
ax1 = fig.add_subplot(111)
perf.portfolio_value.plot(ax=ax1)
ax1.set_ylabel('Portfolio value (USD)')
plt.show()
if __name__ == '__main__':
data = load_data()
results = run_algorithm(
start=start,
end=end,
initialize=initialize,
handle_data=handle_data,
capital_base=10000,
data_frequency='daily',
data=data
# ,bundle_name = None # 如果你使用了 `zipline ingest` 则需要注释掉这行
)
print(results)
# 运行分析函数
analyze(None, results)
运行上面的脚本,即可得到回测结果。 你需要将上面的 moving_average_cross.py
替换成你保存策略的文件名。
2.5 Zipline 的优缺点
优点:
- 事件驱动: Zipline 以事件驱动的方式模拟交易过程,能够更真实地反映市场情况。
- 易于使用: Zipline 提供了简单的 API,方便用户编写和运行回测。
- 性能高效: Zipline 使用 Cython 编写,性能非常高效。
- 支持自定义数据源和交易规则: Zipline 允许用户自定义数据源和交易规则,满足不同的需求。
缺点:
- 安装复杂: Zipline 的安装相对复杂,因为它依赖于许多 Python 包。
- 数据格式要求严格: Zipline 对数据格式有严格的要求,需要将数据转换为特定的格式才能使用。
- 文档不够完善: Zipline 的文档不够完善,学习曲线较陡峭。
- 不再积极维护: Zipline 项目的维护力度已经减弱,社区活跃度不高。
3. Backtrader:灵活的面向对象回测框架
Backtrader 是一个灵活的 Python 回测框架,它以面向对象的方式构建交易策略。Backtrader 的特点是易于扩展、支持多种数据源和交易规则、提供丰富的分析工具。
3.1 Backtrader 的基本概念
-
Strategy: 策略是 Backtrader 的核心,它包含了策略的交易逻辑。策略定义了
__init__()
、next()
和notify_order()
等重要的方法。__init__(self)
: 策略的构造函数,用于初始化策略的参数和状态。next(self)
: 在每个交易日执行一次,用于处理当前时刻的市场数据,并根据策略逻辑进行交易。notify_order(self, order)
: 当订单状态发生变化时执行,例如订单被接受、拒绝、成交等。
-
Data Feed: 数据源负责提供历史数据给策略。Backtrader 支持多种数据源,例如 CSV 文件、数据库、Yahoo Finance 等。
-
Order: 订单是策略发出的交易指令。Backtrader 支持多种订单类型,例如市价单、限价单等。
-
Broker: 经纪人负责处理订单,并模拟交易过程。
-
Analyzer: 分析器负责计算回测过程中的各种指标,例如收益率、最大回撤等。
3.2 Backtrader 的安装
Backtrader 的安装非常简单,只需要使用 pip 命令即可:
pip install backtrader
3.3 Backtrader 代码示例:均线策略
下面是一个简单的均线策略的 Backtrader 代码示例:
import backtrader as bt
class MovingAverageStrategy(bt.Strategy):
params = (
('period', 20), # 移动平均线周期
('printlog', True),
)
def log(self, txt, dt=None, doprint=False):
''' Logging function for this strategy'''
if self.params.printlog or doprint:
dt = dt or self.datas[0].datetime.date(0)
print('%s, %s' % (dt.isoformat(), txt))
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.period)
# Backtrader 自带的平均真实波幅ATR指标
self.atr = bt.indicators.ATR(self.datas[0], period=14)
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
# 检查订单是否完成
# 注意: broker 可能会拒绝订单
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
else: # Sell
self.log(
'SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
(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('Order Canceled/Margin/Rejected')
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))
def next(self):
# 记录收盘价
self.log('Close, %.2f' % self.dataclose[0])
# 检查是否有未完成的订单
if self.order:
return
# 检查是否已经在市场中
if not self.position:
# 当前价格大于移动平均线,则买入
if self.dataclose[0] > self.sma[0]:
# 买入
self.log('BUY CREATE, %.2f' % self.dataclose[0])
# 跟踪订单避免重复
self.order = self.buy()
else:
# 当前价格小于移动平均线,则卖出
if self.dataclose[0] < self.sma[0]:
# 卖出
self.log('SELL CREATE, %.2f' % self.dataclose[0])
# 跟踪订单避免重复
self.order = self.sell()
代码解释:
MovingAverageStrategy(bt.Strategy)
定义了一个继承自bt.Strategy
的策略类。__init__(self)
函数初始化了策略的参数,包括移动平均线的周期period
。next(self)
函数是策略的核心,它在每个交易日执行。该函数首先判断当前是否有未完成的订单,然后判断是否已经在市场中。如果不在市场中,且当前价格大于移动平均线,则买入股票。如果在市场中,且当前价格小于移动平均线,则卖出股票。self.buy()
和self.sell()
函数用于发出买入和卖出订单。
3.4 运行 Backtrader 回测
要运行 Backtrader 回测,需要创建一个 Cerebro
对象,并添加策略、数据源和分析器。
import backtrader as bt
import datetime
import pandas as pd
from moving_average_strategy import MovingAverageStrategy # 导入你的策略文件
if __name__ == '__main__':
# 创建 Cerebro 引擎
cerebro = bt.Cerebro()
# 添加策略
cerebro.addstrategy(MovingAverageStrategy)
# 加载数据
# data = bt.feeds.YahooFinanceCSVData(
# dataname='AAPL.csv',
# fromdate=datetime.datetime(2017, 1, 1),
# todate=datetime.datetime(2018, 1, 1),
# reverse=False)
# 用 PandasData 加载数据
df = pd.read_csv('AAPL.csv', index_col='date', parse_dates=True)
data = bt.feeds.PandasData(dataname=df, fromdate=datetime.datetime(2017, 1, 1), todate=datetime.datetime(2018, 1, 1))
cerebro.adddata(data)
# 设置初始资金
cerebro.broker.setcash(100000.0)
# 设置手续费
cerebro.broker.setcommission(commission=0.001)
# 添加分析器
cerebro.addanalyzer(bt.analyzers.Returns, _name='returns')
cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpe')
cerebro.addanalyzer(bt.analyzers.DrawDown, _name='drawdown')
# 打印初始资金
print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())
# 运行回测
results = cerebro.run()
# 打印最终资金
print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
# 打印分析结果
print('Returns:', results[0].analyzers.returns.get_analysis())
print('Sharpe Ratio:', results[0].analyzers.sharpe.get_analysis())
print('DrawDown:', results[0].analyzers.drawdown.get_analysis())
# 绘制回测结果
cerebro.plot()
代码解释:
cerebro = bt.Cerebro()
创建了一个Cerebro
对象,它是 Backtrader 的核心引擎。cerebro.addstrategy(MovingAverageStrategy)
将策略添加到引擎中。cerebro.adddata(data)
将数据源添加到引擎中。 这里使用了PandasData
避免了数据格式的问题。cerebro.broker.setcash(100000.0)
设置初始资金为 100000.0。cerebro.broker.setcommission(commission=0.001)
设置手续费为 0.001。cerebro.addanalyzer(bt.analyzers.Returns, _name='returns')
添加收益率分析器。cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpe')
添加夏普比率分析器。cerebro.addanalyzer(bt.analyzers.DrawDown, _name='drawdown')
添加回撤分析器。cerebro.run()
运行回测。cerebro.plot()
绘制回测结果。
运行上面的脚本,即可得到回测结果,包括收益率、夏普比率、最大回撤等指标,以及回测曲线。 需要将上面的 moving_average_strategy.py
替换成你保存策略的文件名。
3.5 Backtrader 的优缺点
优点:
- 灵活易扩展: Backtrader 以面向对象的方式构建交易策略,易于扩展和定制。
- 支持多种数据源和交易规则: Backtrader 支持多种数据源和交易规则,满足不同的需求。
- 提供丰富的分析工具: Backtrader 提供了丰富的分析工具,方便用户评估策略的性能。
- 文档完善: Backtrader 的文档非常完善,学习曲线较平缓。
- 社区活跃: Backtrader 的社区非常活跃,用户可以获得及时的支持。
缺点:
- 性能相对较低: 相比 Zipline,Backtrader 的性能相对较低,尤其是在处理大量数据时。
- 代码量相对较多: 相比 Zipline,Backtrader 的代码量相对较多,需要编写更多的代码才能实现相同的功能。
4. Zipline 与 Backtrader 的比较
特性 | Zipline | Backtrader |
---|---|---|
编程范式 | 事件驱动 | 面向对象 |
易用性 | 相对简单,API 简洁 | 灵活,但需要编写更多代码 |
性能 | 高效,使用 Cython 编写 | 相对较低 |
数据源 | 支持多种数据源,但需要转换为特定格式 | 支持多种数据源,例如 CSV、数据库、Yahoo Finance 等 |
分析工具 | 相对较少 | 丰富,提供多种分析器 |
文档 | 不够完善,学习曲线较陡峭 | 完善,学习曲线较平缓 |
社区 | 活跃度不高 | 活跃 |
维护 | 维护力度减弱 | 积极维护 |
安装 | 相对复杂 | 简单 |
5. 如何选择合适的回测框架
选择合适的回测框架取决于你的具体需求和偏好。
- 如果你追求性能,并且对数据格式有较强的控制力,可以选择 Zipline。
- 如果你需要灵活易扩展的框架,并且希望使用面向对象的方式构建交易策略,可以选择 Backtrader。
- 如果你的策略比较简单,并且对性能要求不高,可以选择 Backtrader。
- 如果你的策略比较复杂,并且需要处理大量数据,可以选择 Zipline。
6. 回测的注意事项
- 数据质量: 回测结果的准确性取决于数据的质量。需要确保数据是准确、完整和可靠的。
- 手续费和滑点: 回测时需要考虑手续费和滑点的影响。手续费是指交易时需要支付的费用,滑点是指实际成交价格与预期价格之间的差异。
- 流动性: 回测时需要考虑市场的流动性。如果市场流动性不足,可能会导致订单无法成交或成交价格不利。
- 过拟合: 回测时需要注意避免过拟合。过拟合是指策略在历史数据上表现很好,但在未来市场中表现不佳。
- 样本外测试: 为了验证策略的有效性,需要在样本外数据上进行测试。样本外数据是指没有用于训练策略的数据。
7. 结束语:回测是量化交易成功的基石
回测是量化交易策略开发过程中不可或缺的一环。通过回测,我们可以验证策略的有效性,评估策略的风险,并优化策略的参数。Zipline 和 Backtrader 是两个非常流行的 Python 回测框架,它们各有优缺点,可以根据自己的需求选择合适的框架。希望今天的分享能够帮助大家更好地理解和使用 Zipline 和 Backtrader,开发出更加优秀的量化交易策略。
回测框架选择因需求而异,回测过程需谨慎对待。
量化交易成功的基石是回测,希望今天的内容能帮助大家。