Python 量化入门
回测框架入门
backtrader / vectorbt 入门,回测一个真实策略
上一篇我们手撸了一个双均线回测,代码已经有点乱了。如果策略再复杂一点(多周期、多品种、止损止盈),手撸会越来越难维护。这时候就需要专业的回测框架。
主流框架对比
| 框架 | 特点 | 适合 |
|---|---|---|
| backtrader | 事件驱动,功能全面,社区大 | 中长期策略、新手入门 |
| vectorbt | 向量化,速度极快 | 大规模参数扫描、因子研究 |
| zipline | Quantopian 出品 | 已停止维护,不推荐 |
| rqalpha | 米筐出品,A 股友好 | A 股策略 |
新手建议从 backtrader 开始,资料多、API 直观。
backtrader 入门
安装
bashuv pip install backtrader
核心概念
backtrader 用面向对象的方式组织代码,几个核心概念:
- Cerebro(大脑)— 整个回测的引擎
- Strategy(策略)— 你的交易逻辑
- Data Feed(数据源)— 喂给策略的行情
- Broker(券商)— 模拟成交、收手续费
- Analyzer(分析器)— 计算绩效指标
第一个 backtrader 策略
pythonimport backtrader as bt import akshare as ak import pandas as pd # 1. 定义策略 class DualMAStrategy(bt.Strategy): params = ( ('fast', 5), ('slow', 20), ) def __init__(self): self.ma_fast = bt.indicators.SMA(self.data.close, period=self.params.fast) self.ma_slow = bt.indicators.SMA(self.data.close, period=self.params.slow) self.crossover = bt.indicators.CrossOver(self.ma_fast, self.ma_slow) def next(self): if not self.position: # 空仓 if self.crossover > 0: # 金叉 self.buy() elif self.crossover < 0: # 死叉 self.close() def log(self, txt): dt = self.data.datetime.date(0) print(f'{dt} {txt}') def notify_order(self, order): if order.status == order.Completed: self.log(f'{"BUY" if order.isbuy() else "SELL"} @ {order.executed.price:.2f}') # 2. 准备数据 df = ak.stock_us_daily(symbol="AAPL", adjust="qfq") df['date'] = pd.to_datetime(df['date']) df = df[df['date'] >= '2023-01-01'].set_index('date') df = df.rename(columns={ 'open':'open', 'high':'high', 'low':'low', 'close':'close', 'volume':'volume' }) # 3. 创建 Cerebro cerebro = bt.Cerebro() # 4. 添加策略 cerebro.addstrategy(DualMAStrategy, fast=5, slow=20) # 5. 添加数据 data = bt.feeds.PandasData(dataname=df) cerebro.adddata(data) # 6. 设置初始资金、手续费 cerebro.broker.setcash(100000) cerebro.broker.setcommission(commission=0.0003) # 7. 运行 print(f'初始资金: {cerebro.broker.getvalue():.2f}') cerebro.run() print(f'最终资金: {cerebro.broker.getvalue():.2f}') # 8. 画图 cerebro.plot(style='candlestick')
跑起来你会看到每次开平仓的日志,最后输出最终账户净值,并自动画出 K 线 + 信号 + 净值曲线。
加入分析器
backtrader 自带很多分析器:
pythoncerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpe') cerebro.addanalyzer(bt.analyzers.DrawDown, _name='dd') cerebro.addanalyzer(bt.analyzers.Returns, _name='returns') cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name='trades') results = cerebro.run() strat = results[0] print(f'Sharpe Ratio: {strat.analyzers.sharpe.get_analysis()["sharperatio"]:.2f}') print(f'Max Drawdown: {strat.analyzers.dd.get_analysis()["max"]["drawdown"]:.2f}%') print(f'Total Return: {strat.analyzers.returns.get_analysis()["rtot"]:.2%}') trades = strat.analyzers.trades.get_analysis() print(f'交易次数: {trades.total.total}') print(f'盈利次数: {trades.won.total}') print(f'亏损次数: {trades.lost.total}')
加入仓位管理
实战中很少全仓买入。常见做法是按账户资金的固定比例下单:
pythonclass DualMAStrategy(bt.Strategy): def next(self): if not self.position: if self.crossover > 0: # 用账户 30% 的资金买入 size = int(self.broker.getcash() * 0.3 / self.data.close[0]) self.buy(size=size) elif self.crossover < 0: self.close()
或者用 backtrader 内置的 sizer:
pythoncerebro.addsizer(bt.sizers.PercentSizer, percents=30)
加入止损止盈
pythonclass DualMAWithStop(bt.Strategy): params = ( ('fast', 5), ('slow', 20), ('stop_loss', 0.05), # 5% 止损 ('take_profit', 0.10), # 10% 止盈 ) def __init__(self): self.ma_fast = bt.indicators.SMA(self.data.close, period=self.params.fast) self.ma_slow = bt.indicators.SMA(self.data.close, period=self.params.slow) self.crossover = bt.indicators.CrossOver(self.ma_fast, self.ma_slow) self.entry_price = None def next(self): if not self.position: if self.crossover > 0: self.buy() self.entry_price = self.data.close[0] else: current = self.data.close[0] ret = (current - self.entry_price) / self.entry_price if ret <= -self.params.stop_loss: self.close() # 止损 elif ret >= self.params.take_profit: self.close() # 止盈 elif self.crossover < 0: self.close() # 死叉
多品种回测
pythonsymbols = ['AAPL', 'MSFT', 'GOOGL'] cerebro = bt.Cerebro() cerebro.addstrategy(DualMAStrategy) for symbol in symbols: df = ak.stock_us_daily(symbol=symbol, adjust="qfq") df['date'] = pd.to_datetime(df['date']) df = df[df['date'] >= '2023-01-01'].set_index('date') data = bt.feeds.PandasData(dataname=df, name=symbol) cerebro.adddata(data) cerebro.broker.setcash(300000) cerebro.broker.setcommission(commission=0.0003) cerebro.run()
策略代码会自动对每个品种独立运行。
vectorbt:向量化回测
vectorbt 用纯 numpy 向量化运算,速度比 backtrader 快几十倍,适合参数扫描和因子研究。
安装
bashuv pip install vectorbt
一行代码回测双均线
pythonimport vectorbt as vbt import yfinance as yf # 拉取数据 price = yf.download('AAPL', start='2023-01-01', end='2024-12-31')['Close'] # 计算均线 fast_ma = vbt.MA.run(price, 5) slow_ma = vbt.MA.run(price, 20) # 生成信号 entries = fast_ma.ma_crossed_above(slow_ma) exits = fast_ma.ma_crossed_below(slow_ma) # 回测 pf = vbt.Portfolio.from_signals( price, entries, exits, init_cash=100000, fees=0.0003, ) # 查看结果 print(pf.stats()) pf.plot().show()
pf.stats() 会输出几十个统计指标,比 backtrader 完整。
参数扫描
vectorbt 的杀手锏是一次跑几百组参数:
pythonimport numpy as np # 5 个快线 × 5 个慢线 = 25 组参数 fast_periods = [5, 10, 15, 20, 25] slow_periods = [30, 50, 60, 100, 200] fast_ma = vbt.MA.run(price, window=fast_periods, short_name='fast') slow_ma = vbt.MA.run(price, window=slow_periods, short_name='slow') entries = fast_ma.ma_crossed_above(slow_ma) exits = fast_ma.ma_crossed_below(slow_ma) pf = vbt.Portfolio.from_signals(price, entries, exits, fees=0.0003) # 找出夏普最高的参数组合 print(pf.sharpe_ratio().sort_values(ascending=False).head())
不到 1 秒钟就能跑完。
框架选择建议
| 场景 | 推荐 |
|---|---|
| 学习量化交易、写第一个策略 | backtrader |
| 复杂的事件驱动逻辑 | backtrader |
| 大规模参数优化 | vectorbt |
| 快速验证想法 | vectorbt |
| 实盘策略部署 | backtrader(社区有实盘对接方案) |
我自己的做法:研究阶段用 vectorbt 快速试错,定型后用 backtrader 写正式代码。
小结
你已经会:
- ✅ backtrader 基础用法
- ✅ 加入分析器、仓位管理、止损止盈
- ✅ 多品种回测
- ✅ vectorbt 向量化回测
- ✅ 参数扫描
下一篇 风险与绩效指标,我们详细搞懂回测报告里那些指标到底意味着什么。
📌 避坑提示 别一上来就用复杂框架。如果一个策略你手撸都说不清楚收益从哪来,框架只会让你更迷茫。先理解原理,再用框架。
本页导读