使用手册
功能在哪 · 是什么 · 怎么调出来一、软件定位与适用场景
量化研究 / 交易框架(Python / 云 / 券商 API),用于数据、回测、实盘。本文只讲软件功能与界面含义,不涉及任何交易方向建议。
二、使用前准备
- 准备 Python 环境或平台账号
- 订阅数据源
- 配置 API 密钥
- 准备策略代码
三、界面结构总览
手机端通常分为三个主区域:
- 行情页:看品种价格、分时、K 线、盘口
- 交易页:下单、持仓、挂单、资金
- 账户页:账户信息、安全设置、业务办理、通知
四、数据页字段含义
- 标的 / 周期 / 复权方式
- 字段:OHLCV、因子
- 起止范围与频率
独到见解:复权方式选错,回测结论全盘反转。
五、回测页要素
- 起止时间 / 初始资金
- 手续费与滑点设置
- 绩效指标:夏普、最大回撤
- 参数优化区间
六、实盘页常用功能
- API 连接状态
- 订单管理
- 风控阈值
- 日志与告警
- 部署(云 / 本地)
七、它是什么:回测框架,不是"赚钱系统"
1、准确定位
| 它是 | 它不是 |
|---|---|
| 一个回测与策略开发框架 | 不是自带策略的软件 |
| 执行引擎:驱动策略在历史数据上逐根 K 线运行 | 不是数据源(数据要你自己喂) |
| 记账器:模拟资金、手续费、持仓 | 不是行情终端 |
| 一个可以安装到你自己机器上的 Python 库 | 不是托管服务 |
2、一个关键的前提认知
Backtrader 能做的事:
给你一套标准机制,让策略逻辑可以被执行、被记账、被统计
Backtrader 不能做的事:
告诉你该买什么
保证回测结果在未来有效
替你判断数据好不好
这对我意味着什么: 很多人把「学会框架」当成「学会量化」。实际上框架解决的是执行,策略解决的是判断。这两件事的差距,比想象中大得多。
八、安装与环境准备
1、安装
pip install backtrader
如需绘图功能,通常需要额外安装绘图相关依赖:
pip install matplotlib
2、建议的项目结构
my_backtest/
├── data/
│ └── sample.csv # 历史数据
├── strategies/
│ └── my_strategy.py # 策略逻辑
├── run.py # 主运行脚本
└── utils/
└── loaders.py # 数据加载辅助
为什么建议分开: 随着策略变多,把所有东西塞进一个文件会让「改一行代码就跑不动」的风险大增。分离数据与策略是最基本的工程习惯。
3、环境注意事项
- 强烈建议使用虚拟环境(venv / conda 均可)
- 锁定依赖版本,避免升级后 API 不兼容导致旧脚本失效
- Python 版本与依赖的兼容性以官方说明为准
操作教程
一步一步 · 跟着做完一件事九、五个核心概念(理解这五个,就能用起来)
| 概念 | 作用 | 一句话理解 |
|---|---|---|
| Cerebro | 总控制器/引擎 | 大脑,负责把数据、策略、经纪商装配起来并运行 |
| Strategy | 策略 | 你的交易逻辑写在里面 |
| Data Feed | 数据源 | 被喂进去的历史数据 |
| Broker | 经纪商模拟 | 负责资金、手续费、撮合、持仓记账 |
| Analyzer | 分析器 | 跑完之后算绩效指标 |
此外还有两个常用配件:
| 配件 | 作用 |
|---|---|
| Indicator | 技术指标(内置一大堆,也可自定义) |
| Sizer | 仓位管理,决定每次买多少 |
1、一次完整回测的标准流程
① 创建 Cerebro 实例
② 准备数据(Data Feed)
③ 把数据加进 Cerebro
④ 把策略加进 Cerebro
⑤ 设置资金、手续费、滑点
⑥ (可选)添加分析器
⑦ 运行
⑧ 查看结果与绘图
十、第一个完整例子:双均线交叉
下面这段代码是 Backtrader 的最小可运行范式,务必逐行理解:
import backtrader as bt
## ---------- 1. 定义策略 ----------
class SmaCross(bt.Strategy):
# 可调参数
params = (
('pfast', 10), # 快线周期
('pslow', 30), # 慢线周期
)
def __init__(self):
# 指标通常在 __init__ 中定义
self.sma_fast = bt.indicators.SMA(self.data.close, period=self.p.pfast)
self.sma_slow = bt.indicators.SMA(self.data.close, period=self.p.pslow)
# 交叉信号
self.crossover = bt.indicators.CrossOver(self.sma_fast, self.sma_slow)
def next(self):
# 每根 K 线都会调用 next()
if not self.position: # 当前空仓
if self.crossover > 0: # 金叉
self.buy() # 买入
else: # 已持仓
if self.crossover < 0: # 死叉
self.close() # 平仓
## ---------- 2. 组装并运行 ----------
cerebro = bt.Cerebro()
data = bt.feeds.GenericCSVData(
dataname='data/sample.csv',
dtformat='%Y-%m-%d',
timeframe=bt.TimeFrame.Days,
datetime=0, open=1, high=2, low=3, close=4, volume=5, openinterest=-1,
)
cerebro.adddata(data) # 加数据
cerebro.addstrategy(SmaCross) # 加策略
cerebro.broker.setcash(100000.0) # 初始资金
cerebro.broker.setcommission(commission=0.001) # 手续费
print('起始资金: %.2f' % cerebro.broker.getvalue())
cerebro.run()
print('结束资金: %.2f' % cerebro.broker.getvalue())
cerebro.plot() # 绘图
1、这段代码在做什么
| 环节 | 发生了什么 |
|---|---|
__init__ | 声明指标。框架会在这里一次性创建指标对象 |
next | 每根 K 线调用一次,你的买卖判断写在这里 |
self.buy() | 发出买单,由 Broker 模拟成交 |
self.position | 当前持仓,空仓时为空 |
cerebro.run() | 开始逐根 K 线推进,直到数据结束 |
2、__init__ 与 next 的区别极其重要
__init__:整个回测只执行一次,用于声明指标与预计算
next :每根 K 线执行一次,用于写逻辑
常见错误:把指标计算写在 next 里
→ 每根 K 线重复计算,极慢,且容易出错
这对我意味着什么: 这是新手最容易写错的地方。指标声明放 __init__,交易判断放 next。 这个习惯会让你的代码高效且不易出错。
十一、Lines:Backtrader 的数据抽象
1、什么是 line
在 Backtrader 里,所有时间序列都叫 line。收盘价是一条 line,均线是一条 line,MACD 也是一条 line。
访问方式:
self.data.close # 收盘价这条 line(当前对象)
self.data.close[0] # 当前这根 K 线的值
self.data.close[-1] # 上一根 K 线的值
self.data.close[-2] # 上两根
索引是「时间倒序」的:0 = 当下,-1 = 上一根。
这一点与 Python 列表的正向索引习惯相反,是新手最常犯的错误之一。
2、访问多条数据
self.data.close[0] # 当前收盘价
self.data.open[0] # 当前开盘价
self.data.high[0] # 当前最高价
self.data.volume[0] # 当前成交量
3、为什么会存在「预热期」
一条 30 日均线,需要有 30 根数据才能算出第一个有效值。
第 1 根 K 线:SMA(30) 无值
第 30 根 K 线:SMA(30) 开始有值
在这之前,next() 仍会被调用,但指标还没有有效值。
这对我意味着什么: 回测开始时策略不会立刻交易,这不是 bug,是数学必然。如果你的策略在开头几根 K 线就产生信号,要检查是否误读了无效值。
十二、数据接入:三种常见方式
1、CSV 文件
data = bt.feeds.GenericCSVData(
dataname='data.csv',
dtformat='%Y-%m-%d',
timeframe=bt.TimeFrame.Days,
datetime=0, open=1, high=2, low=3,
close=4, volume=5, openinterest=-1,
)
cerebro.adddata(data)
参数说明:
datetime/open/high/low/close/volume:这些字段在 CSV 中的列序号openinterest=-1:表示没有持仓量列dtformat:日期格式,必须与文件完全一致
2、Pandas DataFrame
import pandas as pd
import backtrader as bt
df = pd.read_csv('data.csv', index_col='date', parse_dates=True)
data = bt.feeds.PandasData(dataname=df)
cerebro.adddata(data)
注意: DataFrame 的索引通常应为日期时间索引,列名需符合框架约定或另行做映射。
3、多个数据源
cerebro.adddata(data_a, name='AAA')
cerebro.adddata(data_b, name='BBB')
## 在策略中按序号访问
self.datas[0] # AAA
self.datas[1] # BBB
self.getdatabyname('BBB') # 按名称访问
适用场景: 跨品种配对、跨市场联动、多时间框架分析。
4、数据质量决定回测上限
| 检查项 | 为什么重要 |
|---|---|
| 是否存在缺失日期 | 缺数据会让指标错算 |
| 是否有重复行 | 成交量会被重复计入 |
| 是否做过复权处理 | 未复权会让除权日产生假信号 |
| 时间戳是否对齐 | 多数据源时必须对齐 |
| 是否有异常极值 | 脏数据会引发假信号 |
这对我意味着什么: 再漂亮的策略,喂进去脏数据就是 garbage out。在怀疑策略之前,先怀疑数据。
十三、指标:内置与自定义
1、内置指标
self.sma = bt.indicators.SMA(self.data.close, period=20)
self.ema = bt.indicators.EMA(self.data.close, period=20)
self.macd = bt.indicators.MACD(self.data.close)
self.rsi = bt.indicators.RSI(self.data.close, period=14)
self.atr = bt.indicators.ATR(self.data)
self.boll = bt.indicators.BollingerBands(self.data.close)
2、自定义指标
class DoubleSMA(bt.Indicator):
lines = ('result',) # 声明输出线
params = (('period', 20),)
def __init__(self):
sma1 = bt.indicators.SMA(self.data.close, period=self.p.period)
sma2 = bt.indicators.SMA(sma1, period=self.p.period)
self.lines.result = sma2 # 赋值给自己声明的线
## 使用
self.myind = DoubleSMA(self.data.close, period=15)
3、关键提醒:不要用「未来」的计算方式
错误:看到后一根 K 线的数据来决定前一根的操作
正确:只用当前及之前可获得的信息
自查方法:
在 next() 里,只能使用 [0] 及负索引(过去)的值。
如果出现正索引(如 [1] 表示未来),那就是未来函数。
这对我意味着什么: 「偷看未来」是回测作假的头号方式,而且经常是无意的。一个看似无害的写法就可能引入未来函数。回测越漂亮,越要怀疑。
十四、下单、成交与持仓
1、基本下单
self.buy() # 买入
self.sell() # 卖出(含建立空头)
self.close() # 平掉当前持仓
self.order_target_percent(target=0.5) # 调整到目标仓位比例
2、订单通知:把成交状况打印出来
def notify_order(self, order):
if order.status in [order.Submitted, order.Accepted]:
return # 已提交/已接受,暂不处理
if order.status == order.Completed:
if order.isbuy():
print('买入成交 价格%.2f 数量%.0f' % (order.executed.price,
order.executed.size))
elif order.issell():
print('卖出成交 价格%.2f' % order.executed.price)
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
print('订单未完成,原因:', order.getstatusname())
这对我意味着什么: 「发出去买指令」和「真的买到了」是两件事。Margin(资金不足)、Rejected(被拒)都是可能出现的正常状态,必须在代码里处理,而不是假设一定成交。
3、资金与持仓查询
self.broker.getcash() # 可用现金
self.broker.getvalue() # 账户总权益
self.position.size # 当前持仓数量(正为多,负为空)
self.position.price # 持仓均价
十五、把成本算进去:回测真实性的分水岭
1、设置手续费
cerebro.broker.setcommission(commission=0.001) # 按比例,此处为千分之一示例
2、设置滑点
cerebro.broker.set_slippage_perc(perc=0.0005) # 按百分比滑点
## cerebro.broker.set_slippage_fixed(fixed=0.02)
3、为什么这一步不能省
对成本量级的示意性理解(非具体品种费率):
某策略日均做 2 个往返,一个月约 20 个交易日
→ 每年约 480 个往返
若每个往返的滑点 + 手续费合计约为成交金额的 0.1%
→ 年度累计成本约 48%(相对单笔全额周转计)
即便实际数值远小于这个示意,频繁交易的成本也极其可观。
若策略本身毛收益不高,这些成本完全可以吃掉全部利润甚至转负。
这对我意味着什么: 不设手续费和滑点的回测,结果系统性偏乐观。 一个策略在零成本时赚 20%,加上真实成本后可能亏损——这是量化最常见的"希望破灭"时刻。
4、仓位管理
cerebro.addsizer(bt.sizers.FixedSize, stake=100) # 固定 100 股/手
cerebro.addsizer(bt.sizers.PercentSizer, percents=20) # 每次用约 20% 资金
也可以在策略内自行决定数量:
size = int((self.broker.getcash() * 0.2) / self.data.close[0])
self.buy(size=size)
十六、绩效评估:用 Analyzer
1、添加分析器
cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpe')
cerebro.addanalyzer(bt.analyzers.DrawDown, _name='drawdown')
cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name='trades')
cerebro.addanalyzer(bt.analyzers.Returns, _name='returns')
results = cerebro.run()
strat = results[0]
print(strat.analyzers.sharpe.get_analysis())
print(strat.analyzers.drawdown.get_analysis())
print(strat.analyzers.trades.get_analysis())
2、三个最值得看的指标
| 指标 | 回答什么 |
|---|---|
| 最大回撤 DrawDown | 最惨的时候亏了多少(先看这个) |
| 夏普比率 SharpeRatio | 每承担一单位波动换来多少超额收益 |
| 交易分析 TradeAnalyzer | 胜率、盈亏比、连续亏损笔数等 |
这对我意味着什么: 新手盯着「总收益率」,专业看「回撤」和「夏普」。一个回撤 60% 的高收益策略,实盘上大概率撑不到它兑现的那天——资金曲线会在中途被击穿,或者人的心理先崩了。
十七、参数优化:威力与陷阱
1、基本用法
cerebro.optstrategy(SmaCross, pfast=range(5, 20, 1), pslow=range(20, 60, 5))
results = cerebro.run() # 每组参数跑一次
2、优化的三个陷阱
| 陷阱 | 说明 |
|---|---|
| 过拟合 | 参数记住了历史,换了数据就失效 |
| 参数越多越容易拟合 | 参数空间越大,越容易找到"完美"组合 |
| 未做样本外检验 | 只在调参所用的数据上好看,是自我欺骗 |
3、正确做法
① 把数据分成两段:样本内 / 样本外
② 只在样本内调参
③ 用样本外检验结果是否站得住
④ 检查参数敏感性:微调参数,结果是否剧烈变化
- 剧烈变化 = 高过拟合风险
⑤ 加倍成本再看是否仍有正收益
关键认知:回测的作用是"证伪",不是"证明"。 正确用法是全力想办法让策略亏掉,如果怎么都亏不掉,再谈实盘。
十八、多周期处理:Resample 与 Replay
1、Resample(降频)
把细粒度数据合并成粗粒度,例如把分钟线合成日线:
cerebro.resampledata(
data,
timeframe=bt.TimeFrame.Days,
compression=1,
)
2、Replay(回放)
把一根粗粒度 K 线内的细粒度数据逐步回放出来,用于更精细的成交模拟:
cerebro.replaydata(
data,
timeframe=bt.TimeFrame.Days,
)
3、两者的区别
| Resample | Replay | |
|---|---|---|
| 粒度 | 合成后只有一根 Bar | 一根 Bar 期间多次触发 next |
| 用途 | 多周期分析 | 精细成交模拟 |
| 速度 | 快 | 慢 |
这对我意味着什么: 用日线数据回测出来的信号,在分钟级实盘中可能因为盘中路径不同而产生完全不同的结果。回测粒度决定结论的适用范围。
十九、绘图
cerebro.plot()
常用调整依赖绘图后端与样式设置,具体参数以版本文档为准。
绘图的价值在于目视检查:你会发现很多问题(比如信号堆积在某个时段、长期空仓、成交集中在异常价格),看图往往比看数字更快发现问题。
二十、从回测到实盘:边界要清楚
1、框架自带的实盘连接能力(概念层面)
开源 Backtrader 包含对部分对接方式的支持(例如面向某些经纪商的 Store / Data Feed / Broker 组件)。具体支持哪些、如何配置,以官方文档为准。
2、回测与实盘的差距清单
| 差距 | 回测 | 实盘 |
|---|---|---|
| 成交假设 | 按规则成交 | 排队、部分成交、拒单 |
| 延迟 | 零延迟 | 网络 + 柜台 + 撮合延迟 |
| 流动性影响 | 不影响价格 | 大单有冲击成本 |
| 数据 | 历史静态 | 实时可能有脏数据 |
| 连续性 | 一次跑完 | 需处理断线与重启 |
| 成本 | 固定假设 | 费率可能变化 |
3、转实盘的检查清单
- 是否已在样本外数据上验证
- 是否已建模手续费与滑点
- 是否已处理资金不足/拒单状态
- 是否有明确的仓位上限
- 是否有超时/断线处理
- 是否有日志
- 是否已小资金试运行
- 是否知道什么情况下应该停掉它
二十一、常见坑清单
| 坑 | 表现 | 处理 |
|---|---|---|
| 索引方向搞反 | 用 [1] 表示过去 | 记住 [0] 当前,[-1] 过去 |
| 指标写在 next 里 | 极慢、结果异常 | 移到 __init__ |
| 忘记设手续费滑点 | 回测异常漂亮 | 必须设置 |
| 数据未排序 | 回测顺序错乱 | 检查时间升序 |
| 数据有缺失 | 指标跳变 | 补齐或检查数据源 |
| 忽视预热期 | 开头信号怪异 | 检查指标是否已有值 |
| 参数过拟合 | 样本外崩塌 | 分段验证 |
| 用日线结论做分钟交易 | 实盘不匹配 | 回测粒度对齐实盘 |
| 未来函数 | 回测完美 | 只用 [0] 及负索引 |
| 不处理拒单 | 实盘仓位错乱 | 实现 notify_order |
二十二、注意事项
- 框架不产生盈利能力。 它只负责执行与记账,策略有效性完全取决于使用者。
- 回测的作用是证伪,不是证明。 漂亮曲线应当引起怀疑,而非信心。
- 不设成本的回测没有意义。 手续费与滑点必须建模。
- 回测粒度必须与实盘一致。 用日线结论指导分钟级交易是常见错误。
- 适时承认失败。 需要反复微调参数才能成立的策略,通常是拟合出来的。
- 建议使用版本锁定与可复现环境。 把框架版本、依赖清单与随机种子一同记录,避免因版本升级导致回测结果不可复现。
- 先用模拟账户跑满一个完整周期。 至少覆盖一段震荡与一段趋势行情,再考虑是否进入实盘。
- 关于「Pro」: 如前所述,开源主干没有名为 Pro 的官方发行版。若使用第三方版本,请以该版本官方资料为准,并对其声称的能力保持审慎。
- 本文仅在介绍框架操作,不构成投资建议,不推荐任何产品。
金融产品有风险,交易决策请自行判断,据此操作风险自负。
💬 用户评价 (0)
立即登录