量化回测教程
🧪 回测 = 用历史数据验证你的策略 —— 没有经过回测的策略都是"盲猜"。
🎯 为什么需要回测?
| 痛点 | 回测的作用 |
|---|---|
| "我以为这个策略能赚钱" | 历史数据验证 |
| "我以为均线金叉能涨" | 看胜率几何 |
| "我以为我比市场聪明" | 数据让你清醒 |
| "我以为回撤小" | 真实回测数据 |
📋 教程大纲
| 阶段 | 内容 | 时长 |
|---|---|---|
| 1. 环境搭建 | Python + Backtrader 安装 | 30 分钟 |
| 2. 第一个策略 | MA 金叉买入 | 1 小时 |
| 3. 回测结果分析 | 收益率 / 最大回撤 / 夏普 | 30 分钟 |
| 4. 优化策略 | 加入止损 / 仓位管理 | 2 小时 |
| 5. 完整策略 | 多因子 + 风险管理 | 半天 |
🛠️ 1. 环境搭建
1.1 安装 Python
bash
# 推荐 Python 3.10+
python --version
# 如果没装,去官网下载:https://www.python.org/downloads/
# 或用 Anaconda:https://www.anaconda.com/1.2 创建虚拟环境
bash
# macOS / Linux
python3 -m venv ~/backtest-env
source ~/backtest-env/bin/activate
# Windows
python -m venv C:\backtest-env
C:\backtest-env\Scripts\activate1.3 安装依赖
bash
pip install backtrader
pip install akshare # A 股数据源
pip install matplotlib # 画图
pip install pandas numpy # 数据处理
pip install jupyter # 可选:Jupyter Notebook1.4 安装 Ta-Lib(可选)
Ta-Lib 是技术分析库,Backtrader 支持它作为指标源。
bash
# macOS
brew install ta-lib
pip install TA-Lib
# Ubuntu
sudo apt-get install ta-lib
pip install TA-Lib
# Windows
# 1. 下载 wheel 文件:https://github.com/cgohlke/talib-build/releases
# 2. pip install TA_Lib‑0.4.xx‑cp3x‑cp3xm‑win_amd64.whl🎯 2. 第一个策略
2.1 完整代码
python
"""
策略:MA 金叉买入,死叉卖出
标的:贵州茅台(600519)
时间:2020-01-01 至 2024-12-31
"""
import backtrader as bt
import akshare as ak
import datetime
import pandas as pd
# ============ 1. 获取数据 ============
def get_data(symbol='600519', start='20200101', end='20241231'):
"""从 akshare 获取 A 股数据,转为 Backtrader 格式"""
df = ak.stock_zh_a_hist(
symbol=symbol,
period='daily',
start_date=start,
end_date=end,
adjust='qfq' # 前复权
)
# 标准化列名
df = df.rename(columns={
'日期': 'date',
'开盘': 'open',
'最高': 'high',
'最低': 'low',
'收盘': 'close',
'成交量': 'volume'
})
df['date'] = pd.to_datetime(df['date'])
df.set_index('date', inplace=True)
return df
# ============ 2. 定义策略 ============
class MaCrossStrategy(bt.Strategy):
"""MA 金叉死叉策略"""
# 参数
params = (
('fast_period', 5), # 短期均线周期
('slow_period', 20), # 长期均线周期
('printlog', True), # 是否打印日志
)
def __init__(self):
# 计算均线
self.fast_ma = bt.indicators.SMA(period=self.params.fast_period)
self.slow_ma = bt.indicators.SMA(period=self.params.slow_period)
# 金叉死叉信号
self.crossover = bt.indicators.CrossOver(self.fast_ma, self.slow_ma)
# 订单
self.order = None
def next(self):
# 有未完成订单,跳过
if self.order:
return
# 账户没仓位,金叉 → 买入
if not self.position:
if self.crossover > 0: # 金叉
self.order = self.buy()
if self.params.printlog:
self.log(f'金叉买入: 价格 {self.data.close[0]:.2f}')
# 账户有仓位,死叉 → 卖出
else:
if self.crossover < 0: # 死叉
self.order = self.sell()
if self.params.printlog:
self.log(f'死叉卖出: 价格 {self.data.close[0]:.2f}')
def log(self, txt, dt=None):
"""日志"""
dt = dt or self.datas[0].datetime.date(0)
print(f'[{dt}] {txt}')
def notify_order(self, order):
"""订单状态变化"""
if order.status in [order.Completed]:
if order.isbuy():
self.log(f'买入成交: 价 {order.executed.price:.2f} 手续费 {order.executed.comm:.2f}')
else:
self.log(f'卖出成交: 价 {order.executed.price:.2f} 手续费 {order.executed.comm:.2f}')
self.order = None
# ============ 3. 运行回测 ============
if __name__ == '__main__':
# 创建引擎
cerebro = bt.Cerebro()
# 添加策略
cerebro.addstrategy(MaCrossStrategy)
# 设置初始资金
cerebro.broker.setcash(1000000) # 100 万
# 设置手续费
cerebro.broker.setcommission(
commission=0.0003, # 佣金 0.03%
stamp_duty=0.001, # 印花税 0.1%(卖出)
stocklike=True
)
# 设置数据
df = get_data('600519', '20200101', '20241231')
data = bt.feeds.PandasData(dataname=df)
cerebro.adddata(data)
# 设置分析器
cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpe', riskfreerate=0.025)
cerebro.addanalyzer(bt.analyzers.DrawDown, _name='drawdown')
cerebro.addanalyzer(bt.analyzers.Returns, _name='returns')
# 打印初始状态
print('初始资金: %.2f' % cerebro.broker.getvalue())
# 运行
results = cerebro.run()
# 打印最终状态
print('最终资金: %.2f' % cerebro.broker.getvalue())
# 分析结果
strat = results[0]
print(f'夏普比率: {strat.analyzers.sharpe.get_analysis()["sharperatio"]:.2f}')
print(f'最大回撤: {strat.analyzers.drawdown.get_analysis().max.drawdown:.2f}%')
# 画图
cerebro.plot(style='candlestick')2.2 运行结果示例
初始资金: 1000000.00
[2020-04-15] 金叉买入: 价格 128.50
[2020-04-15] 买入成交: 价 128.50 手续费 38.55
[2020-09-22] 死叉卖出: 价格 175.30
[2020-09-22] 卖出成交: 价 175.30 手续费 17.53
...
[2024-12-30] 死叉卖出: 价格 1520.00
最终资金: 2350000.00
夏普比率: 1.85
最大回撤: -18.50%📊 3. 回测结果分析
3.1 关键指标
| 指标 | 含义 | 优秀值 |
|---|---|---|
| 年化收益 | 平均年化回报 | > 15% |
| 最大回撤 | 期间最大亏损 | < 20% |
| 夏普比率 | 单位风险收益 | > 1.5 |
| 卡玛比率 | 收益 / 回撤 | > 2.0 |
| 胜率 | 盈利笔数比例 | > 50% |
| 盈亏比 | 平均盈利 / 平均亏损 | > 2.0 |
| 交易次数 | 完整买卖次数 | 10-100 / 年 |
| 持仓时间 | 平均持仓天数 | 视策略 |
3.2 分析器(Analyzers)
python
# 添加更多分析器
cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpe', riskfreerate=0.025)
cerebro.addanalyzer(bt.analyzers.DrawDown, _name='drawdown')
cerebro.addanalyzer(bt.analyzers.Returns, _name='returns')
cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name='trades')
cerebro.addanalyzer(bt.analyzers.SQN, _name='sqn') # 系统质量
cerebro.addanalyzer(bt.analyzers.VWR, _name='vwr') # 波动率加权
# 运行后分析
results = cerebro.run()
strat = results[0]
# 获取详细分析
print('夏普:', strat.analyzers.sharpe.get_analysis())
print('回撤:', strat.analyzers.drawdown.get_analysis())
print('交易:', strat.analyzers.trades.get_analysis())🛠️ 4. 优化策略
4.1 加入止损
python
class MaCrossWithStop(bt.Strategy):
params = (
('fast_period', 5),
('slow_period', 20),
('stop_loss', 0.08), # 8% 止损
)
def __init__(self):
self.fast_ma = bt.indicators.SMA(period=self.params.fast_period)
self.slow_ma = bt.indicators.SMA(period=self.params.slow_period)
self.crossover = bt.indicators.CrossOver(self.fast_ma, self.slow_ma)
self.order = None
self.buy_price = None
def next(self):
if self.order:
return
if not self.position:
if self.crossover > 0:
self.order = self.buy()
self.buy_price = self.data.close[0]
else:
current_price = self.data.close[0]
# 止损
if self.buy_price and current_price < self.buy_price * (1 - self.params.stop_loss):
self.order = self.sell()
self.log(f'止损卖出: {current_price:.2f}')
# 死叉卖出
elif self.crossover < 0:
self.order = self.sell()
self.buy_price = None4.2 仓位管理
python
class PositionSizing(bt.Strategy):
"""固定金额仓位管理"""
def next(self):
if not self.position:
# 只用 20% 资金买入
size = int(self.broker.getcash() * 0.2 / self.data.close[0])
if size > 0:
self.buy(size=size)4.3 参数优化
python
# 用 Backtrader 的优化器
cerebro.optstrategy(
MaCrossStrategy,
fast_period=range(3, 15, 2), # 测试 3-15 步长 2
slow_period=range(15, 60, 5) # 测试 15-60 步长 5
)
# 运行优化
results = cerebro.run()
# 找最优参数
best_sharpe = -999
best_params = None
for result in results:
sharpe = result[0].analyzers.sharpe.get_analysis()['sharperatio']
if sharpe > best_sharpe:
best_sharpe = sharpe
best_params = result[0].params
print(f'最优参数: {best_params}, 夏普: {best_sharpe:.2f}')⚠️ 过拟合警告:参数优化很容易找到"历史最优"但在未来失效。建议用:
- 样本内 / 样本外分割:70% 数据优化,30% 数据验证
- Walk Forward:滚动窗口优化
- 多标的测试:用 10 只股票测试,避免单只股票过拟合
🧪 5. 完整策略示例
python
"""多因子选股 + 风险管理完整策略"""
class CompleteStrategy(bt.Strategy):
params = (
('fast_ma', 5),
('slow_ma', 20),
('rsi_period', 14),
('rsi_buy', 30), # RSI < 30 抄底
('rsi_sell', 70), # RSI > 70 减仓
('stop_loss', 0.10), # 10% 止损
('position_pct', 0.3), # 30% 仓位
)
def __init__(self):
# 指标
self.fast_ma = bt.indicators.SMA(period=self.params.fast_ma)
self.slow_ma = bt.indicators.SMA(period=self.params.slow_ma)
self.rsi = bt.indicators.RSI(period=self.params.rsi_period)
self.atr = bt.indicators.ATR(period=14) # 波动率
# 订单
self.order = None
self.buy_price = None
def next(self):
if self.order:
return
price = self.data.close[0]
if not self.position:
# 多重信号共振
ma_bullish = self.fast_ma[0] > self.slow_ma[0]
ma_crossover = self.fast_ma[-1] <= self.slow_ma[-1] and self.fast_ma[0] > self.slow_ma[0]
rsi_oversold = self.rsi[0] < self.params.rsi_buy
volatility_ok = self.atr[0] / price < 0.05 # 波动率 < 5%
if (ma_crossover or ma_bullish) and rsi_oversold and volatility_ok:
# 仓位管理:账户 30%
size = int(self.broker.getvalue() * self.params.position_pct / price)
if size > 0:
self.order = self.buy(size=size)
else:
# 持仓管理
if self.buy_price:
# 止损
if price < self.buy_price * (1 - self.params.stop_loss):
self.order = self.sell()
# RSI 超买卖出
elif self.rsi[0] > self.params.rsi_sell:
self.order = self.sell()
# 死叉卖出
elif self.fast_ma[0] < self.slow_ma[0]:
self.order = self.sell()📊 6. 回测结果判断
6.1 好策略的特征
| 指标 | 优秀策略 | 一般策略 |
|---|---|---|
| 年化收益 | > 20% | 10-20% |
| 最大回撤 | < 15% | 15-30% |
| 夏普比率 | > 1.5 | 0.8-1.5 |
| 胜率 | > 50% | 40-50% |
| 盈亏比 | > 2 | 1-2 |
6.2 危险信号
| 信号 | 含义 |
|---|---|
| 回撤 > 30% | 策略可能失效 |
| 夏普 < 0.5 | 风险收益比差 |
| 胜率 < 30% | 策略不适用 |
| 收益全靠 1-2 笔 | 不可持续 |
6.3 常见误区
| 误区 | 真相 |
|---|---|
| "回测好 = 实盘好" | 70% 回测好的策略实盘失效 |
| "参数越多越好" | 越简单越稳健 |
| "高收益 = 好策略" | 高收益往往伴随高回撤 |
| "夏普 2 = 完美" | 可能是过拟合 |
🛠️ 7. 进阶工具
7.1 qlib(微软 AI 量化平台)
bash
pip install pyqlib- 内置 Alpha 因子库
- AI 预测模型
- 适合机器学习量化
7.2 Zipline(Quantopian 开源)
bash
pip install zipline-reloaded- 老牌回测框架
- 美股数据支持
7.3 vnpy(国内最流行)
bash
pip install vnpy- 国内量化交易框架
- 支持实盘
- 适合专业量化
📊 实战建议
入门路径
第 1 月:
- 学习 Python 基础
- 跑通 Backtrader 第一个策略
第 2 月:
- 学习多因子
- 优化策略参数
第 3 月:
- 加入风险管理
- 模拟盘验证
第 4-6 月:
- 小资金实盘
- 持续迭代⚠️ 重要提醒
- 回测 ≠ 实盘:滑点、手续费、流动性都会影响
- 过拟合是最大风险:参数越多越容易过拟合
- 样本外测试:必须用未参与优化的数据测试
- 小资金起步:实盘先小资金验证
- 持续监控:策略会失效,需要定期回顾
📖 相关阅读
来源
- 证监会公开公告
- 上交所交易规则
- 深交所 / 北交所规则
- 中国证券业协会(SAC)、中国证券投资基金业协会(AMAC)公开资料
- 国家金融监督管理总局、人社部、财政部、税务总局公开资料
💡 数据截至 2026-08;具体规则以交易所/监管机构最新公告为准。