3、策略回测框架:回测的核心概念与Backtrader实战
各位同学好,我是你们的讲师。今天咱们聊聊回测框架。说实话,我见过太多人把回测当成「过家家」——跑个曲线,看个收益,就觉得稳了。结果实盘一上,亏得亲妈都不认识。
为什么会这样?因为回测里藏着太多坑。滑点、手续费、资金管理,任何一个没处理好,你的「完美策略」都可能变成灾难。今天我就把这些核心概念掰开揉碎,再带大家上手Backtrader这个利器。
核心观点:回测不是验证策略「能不能赚钱」,而是验证策略「在真实市场中会不会死」。滑点、手续费、资金管理,就是决定生死的三个关卡。
3.1 回测的核心概念
3.1.1 滑点(Slippage)
滑点说白了,就是你下单的价格和实际成交价格之间的差价。我刚开始做回测时,天真地以为「想买就能买到,想卖就能卖掉」。直到有一次,我在回测里年化收益80%,实盘一跑直接腰斩。查了半天,罪魁祸首就是滑点。
滑点产生的原因主要有三个:
- 市场流动性不足:你挂100手买单,但卖单只有50手,剩下的只能追高
- 交易延迟:从你点击「下单」到交易所收到指令,这几十毫秒里价格可能已经变了
- 大单冲击:你的订单本身就会影响市场价格,尤其是小市值股票
在回测中,我们通常用固定滑点或百分比滑点来模拟。我个人习惯用万分之三到万分之五,这个范围比较贴近A股实际情况。
避坑指南:我曾经见过有人把滑点设成0.0001%,回测曲线漂亮得像艺术品。结果实盘第一天就亏了2%。记住:滑点宁可高估,不要低估。
3.1.2 手续费(Commission)
手续费这块,很多人觉得「不就是万几嘛,影响不大」。你想想看,高频策略一天交易几十次,手续费累积起来可能吃掉你一半的利润。
常见的费用包括:
- 佣金:券商收取的交易费用,A股一般是万2.5到万3
- 印花税:卖出时收取,目前是千分之一
- 过户费:沪深两市都有,万0.2左右
- 平台费:有些量化平台会额外收取
在Backtrader里,我们可以这样设置手续费:
import backtrader as bt
cerebro = bt.Cerebro()
# 设置佣金为万分之三
cerebro.broker.setcommission(commission=0.0003)
# 也可以按固定金额,比如每笔5元
cerebro.broker.setcommission(commission=5.0, mult=1.0)
注意:不同品种的手续费差异很大。期货是万分之几,但还有保证金占用;ETF是万分之零点几;期权更复杂。一定要根据实际交易品种来设置。
3.1.3 资金管理(Money Management)
资金管理,说白了就是「每次该下多少注」。我见过最惨的案例:一个策略回测收益300%,实盘时全仓干进去,结果连续三次止损,本金直接腰斩。
常见的资金管理方法:
- 固定仓位:每次固定买入100股或1手,简单粗暴
- 固定比例:每次用总资金的固定比例(比如10%)开仓
- 凯利公式:根据胜率和盈亏比计算最优仓位
- 风险平价:让每个品种的风险贡献相等
我个人比较推荐固定比例法,尤其是新手。比如每次只用总资金的2%去冒险,就算连续亏10次,也只亏20%,还有翻盘的机会。
3.2 Backtrader框架入门
Backtrader是我用得最多的回测框架。为什么选它?因为它是纯Python,扩展性强,社区活跃。说白了,你想要的它基本都有,没有的也能自己写。
3.2.1 核心组件
Backtrader有五个核心组件,我画了个图帮你理解:
简单解释一下:
- Cerebro:整个框架的大脑,负责协调所有组件
- Data Feed:喂给策略的数据,比如日K线、分钟线
- Strategy:你的交易逻辑,什么时候买、什么时候卖
- Broker:模拟真实券商,处理订单、计算资金
- Analyzer/Observer:分析回测结果,监控运行过程
3.2.2 快速上手
来看一个最简单的例子,实现一个「金叉买入,死叉卖出」的策略:
import backtrader as bt
import backtrader.indicators as btind
class GoldenCrossStrategy(bt.Strategy):
params = (
('fast_period', 10),
('slow_period', 30),
)
def __init__(self):
# 计算快线和慢线
self.fast_ma = btind.SMA(self.data.close, period=self.params.fast_period)
self.slow_ma = btind.SMA(self.data.close, period=self.params.slow_period)
# 金叉信号
self.crossover = btind.CrossOver(self.fast_ma, self.slow_ma)
def next(self):
# 没有持仓时,金叉买入
if not self.position:
if self.crossover > 0:
self.buy()
# 有持仓时,死叉卖出
elif self.crossover < 0:
self.sell()
# 创建大脑
cerebro = bt.Cerebro()
# 添加数据
data = bt.feeds.YahooFinanceData(dataname='AAPL', fromdate='2020-01-01', todate='2023-12-31')
cerebro.adddata(data)
# 添加策略
cerebro.addstrategy(GoldenCrossStrategy)
# 设置初始资金
cerebro.broker.setcash(100000.0)
# 运行回测
results = cerebro.run()
# 画图
cerebro.plot()
小技巧:刚开始用Backtrader时,建议先用内置的YahooFinance数据测试。等熟悉了再换成自己的数据源。我当年就是直接上自己的数据,结果格式不对折腾了一整天。
3.3 自定义策略编写
内置策略不够用?那就自己写。Backtrader的策略编写其实就三个核心方法:
- __init__:初始化指标和变量
- next:每根K线都会调用一次,在这里写交易逻辑
- notify_order:订单状态变化时触发,用于处理成交、撤单等
来看一个更复杂的例子,加入止损和仓位管理:
class MyStrategy(bt.Strategy):
params = (
('rsi_period', 14),
('rsi_upper', 70),
('rsi_lower', 30),
('risk_per_trade', 0.02), # 每次冒险2%的资金
)
def __init__(self):
self.rsi = btind.RSI(self.data.close, period=self.params.rsi_period)
self.order = None # 跟踪当前订单
def next(self):
# 如果有未完成的订单,跳过
if self.order:
return
# 计算仓位大小
size = int(self.broker.getcash() * self.params.risk_per_trade / self.data.close[0])
# 超卖区域买入
if not self.position and self.rsi < self.params.rsi_lower:
self.order = self.buy(size=size)
# 超买区域卖出
elif self.position and self.rsi > self.params.rsi_upper:
self.order = self.sell(size=size)
def notify_order(self, order):
if order.status in [order.Completed, order.Canceled, order.Margin]:
self.order = None
注意:在next方法里,一定要处理订单状态。我曾经犯过一个错误:连续下单导致资金不足,回测结果完全乱套。记住:每次下单前检查是否有未完成的订单。
3.4 回测结果评估指标
回测跑完了,怎么判断策略好不好?光看收益率曲线可不行。我见过太多「漂亮曲线」背后藏着巨大的风险。
3.4.1 夏普比率(Sharpe Ratio)
夏普比率衡量的是「每承担一单位风险,能获得多少超额收益」。说白了,就是你的策略值不值得冒险。
计算公式:
夏普比率 = (策略收益率 - 无风险利率) / 策略波动率
一般来说:
- 夏普比率 > 1:还不错
- 夏普比率 > 2:很好
- 夏普比率 > 3:非常优秀(但要小心过拟合)
在Backtrader里计算夏普比率:
cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpe')
results = cerebro.run()
sharpe = results[0].analyzers.sharpe.get_analysis()
print(f"夏普比率: {sharpe['sharperatio']:.2f}")
3.4.2 最大回撤(Max Drawdown)
最大回撤,就是策略从最高点跌到最低点的最大幅度。这个指标比收益率更重要。为什么?因为回撤超过30%,大部分人就扛不住了,会手动止损,然后完美错过后面的反弹。
我个人的经验:
- 回撤 < 10%:很稳健
- 回撤 10%-20%:可以接受
- 回撤 20%-30%:需要警惕
- 回撤 > 30%:建议重新审视策略
cerebro.addanalyzer(bt.analyzers.DrawDown, _name='drawdown')
results = cerebro.run()
dd = results[0].analyzers.drawdown.get_analysis()
print(f"最大回撤: {dd['max']['drawdown']:.2f}%")
3.4.3 年化收益(Annualized Return)
年化收益是把总收益换算成一年的收益率。注意,不是简单的总收益除以年数,而是用复利计算。
举个例子:你回测了3年,总收益50%,那年化收益不是50%/3=16.67%,而是:
年化收益 = (1 + 0.5)^(1/3) - 1 = 14.47%
在Backtrader里:
cerebro.addanalyzer(bt.analyzers.AnnualReturn, _name='annual')
results = cerebro.run()
annual = results[0].analyzers.annual.get_analysis()
print(f"年化收益: {annual['annual_return']:.2f}%")
3.4.4 综合评估表
我把这几个指标整理成了一张表,方便你对照:
| 指标 | 含义 | 优秀值 | 警戒值 |
|---|---|---|---|
| 夏普比率 | 风险调整后收益 | > 2.0 | < 1.0 |
| 最大回撤 | 最大亏损幅度 | < 10% | > 20% |
| 年化收益 | 复利年化收益率 | > 20% | < 10% |
| 胜率 | 盈利交易占比 | > 60% | < 40% |
| 盈亏比 | 平均盈利/平均亏损 | > 2.0 | < 1.5 |
重要提醒:不要只看单一指标。一个策略可能夏普比率很高,但最大回撤也很大。要综合评估,尤其是要看看资金曲线是否平滑。我见过最坑的策略:夏普比率2.5,但最大回撤40%,这种策略实盘根本拿不住。
好了,这一章的内容就到这里。回测框架是量化交易的基石,滑点、手续费、资金管理这三个概念一定要吃透。Backtrader是个好工具,但工具再好,也得看你怎么用。下一章我们会深入讲数据获取和清洗,那是另一个容易踩坑的地方。