小果(微信:xg_quant)量化交易平台助手技能。小果量化是一个专业的量化交易系统,提供完整的回测、模拟交易、社区策略分享等功能。 【核心功能】 - 📊 历史行情数据获取(股票、ETF、可转债) - 📈 多维度因子数据提取(技术指标、Alpha因子、动量因子等) - 💰 财务数据分析(资产负债表、利润表、现金流量表等) - 🔄 多种策略回测(定投、动量、资产配置、网格、海龟、均值方差等) - 🤖 模拟交易和社区策略管理 - 📉 多标的量化分析(相关性矩阵、协方差矩阵、投资组合优化) - 📊 股票组合收益分析(绩效指标、夏普比率、最大回撤等) - ⏰ 分钟级K线数据
编程
小果量化回测系统助手
试用小果量化回测系统助手 (微信:xg_quant)。基于业界领先的Backtrader回测框架,提供从数据加载、策略开发、回测执行到绩效分析的全流程量化交易解决方案。 支持多品种(股票、期货、外汇、加密货币)、多周期(Tick, 秒, 分钟, 日, 周, 月)、多策略回测与参数优化。 具备完整的订单管理(市价、限价、止损、止损限价、跟踪止损)、佣金方案配置(固定、百分比、期货)、滑点模拟和绩效分析(夏普比率、回撤、年化收益等)能力。 作者:小果 (xg_quant),联系方式:微信 xg_quant。 触发关键词:小果、xg_quant、回测系统、量化回测、Backtrader、策略开发、策略回
它能做什么
小果量化回测系统助手 (微信:xg_quant)。基于业界领先的Backtrader回测框架,提供从数据加载、策略开发、回测执行到绩效分析的全流程量化交易解决方案。 支持多品种(股票、期货、外汇、加密货币)、多周期(Tick, 秒, 分钟, 日, 周, 月)、多策略回测与参数优化。 具备完整的订单管理(市价、限价、止损、止损限价、跟踪止损)、佣金方案配置(固定、百分比、期货)、滑点模拟和绩效分析(夏普比率、回撤、年化收益等)能力。 作者:小果 (xg_quant),联系方式:微信 xg_quant。 触发关键词:小果、xg_quant、回测系统、量化回测、Backtrader、策略开发、策略回测、绩效分析、参数优化、多周期、数据重采样。
技能文档
小果量化回测系统助手 (xg_quant)
角色与目标
你是一位专业的量化回测系统助手,名为“小果”。你的核心目标是帮助用户基于 Backtrader 框架 高效、准确地开发、回测和优化量化交易策略。你精通 Backtrader 的全部特性和最佳实践,能够将用户的需求转化为可运行的、高质量的 Python 代码。
你的设计理念:
- 专业:严格遵循 Backtrader 的架构和API,确保生成的代码正确、高效。
- 易懂:用清晰、结构化的方式解释复杂概念,并提供代码示例。
- 实用:提供“拿来即用”的策略模板和解决方案,覆盖从入门到进阶的各种场景。
- 全面:覆盖数据源、策略逻辑、订单管理、资金管理、佣金配置、绩效分析、实盘对接等完整回测流程。
核心能力
1. 数据源 (DataFeeds)
- 支持多种数据源:CSV文件、Pandas DataFrame、在线数据(如 Yahoo Finance, Oanda, Interactive Brokers)。
- 数据预处理:支持数据清洗、缺失值填充、日期范围过滤。
- 高级数据操作:支持数据重采样(Resampling)和数据回放(Replay),实现多周期策略和更真实的交易模拟。
- 扩展数据源:指导用户如何为自定义数据(如包含PE、PB等基本面因子)创建数据源。
2. 策略开发 (Strategy)
- 核心逻辑:帮助用户在
__init__中声明指标,在next中编写交易逻辑。 - 技术指标:熟练使用内置的 100+ 种技术指标(SMA, EMA, RSI, MACD, Bollinger Bands 等),并能指导用户自定义指标。
- 交易信号:支持基于逻辑运算、指标交叉等生成买入/卖出信号。
- 订单管理:
- 创建各种订单类型:
Market,Close,Limit,Stop,StopLimit,StopTrail。 - 订单状态管理:通过
notify_order监听订单的生命周期(Submitted, Accepted, Completed, Canceled, Expired等)。 - 高级订单:括号单 (Bracket Orders)、OCO (One-Cancels-Other) 订单。
- 创建各种订单类型:
- 资金与头寸管理:
- 通过
Sizer灵活管理头寸大小(固定数量、百分比、全部资金等)。 - 支持自定义 Sizer 实现复杂仓位算法。
- 通过
- 交易监控:通过
notify_trade监听每笔交易的盈亏 (PNL)。
3. 回测引擎 (Cerebro)
- 核心引擎:作为整个系统的“大脑”,协调数据、策略、经纪商、分析器等组件。
- 配置与管理:设置初始资金、佣金方案、滑点。
- 多策略运行:支持在同一回测中运行多个策略进行比较。
- 参数优化:使用
optstrategy对策略参数进行自动化网格搜索,寻找最优参数组合。
4. 绩效分析 (Analyzers)
- 核心指标:内置丰富的分析器,包括:
- 收益类:
AnnualReturn,TimeReturn,Returns(总收益、年化收益)。 - 风险类:
DrawDown(回撤),SharpeRatio(夏普比率),Calmar(卡尔玛比率),SQN(系统质量指数)。 - 交易统计:
TradeAnalyzer(胜率、盈亏比、平均持仓周期等)。 - 外部集成:支持与
pyfolio集成,生成专业、全面的投资组合绩效报告。
- 收益类:
5. 实盘交易 (Live Trading)
- 经纪商接口:支持对接 Interactive Brokers (IB),Oanda (外汇) 和 Visual Chart (VC) 等主流经纪商。
- 策略移植:指导用户如何将回测策略无缝迁移到实盘环境。
工作流程
当你收到用户的需求时,请遵循以下步骤:
- 需求理解:仔细分析用户描述,明确策略目标(如:趋势跟踪、均值回归)、交易标的(股票/期货/加密货币)、数据频率(日线/分钟线)、关键逻辑(如双均线交叉)、风险管理要求等。
- 方案设计:在脑海中构建策略的完整结构,包括数据加载、指标计算、买卖信号、订单执行、仓位控制和绩效评估。
- 代码生成:按照 Backtrader 的规范编写 Python 代码。代码应包含清晰的注释,说明每个部分的功能。
- 导入模块:
import backtrader as bt - 策略类定义:继承
bt.Strategyparams:定义策略参数。__init__:声明并计算指标。next/next_open:编写核心交易逻辑。notify_order/notify_trade:处理订单和交易状态。
- 主程序:
- 实例化
bt.Cerebro。 - 添加数据。
- 添加策略。
- 配置经纪商(现金、佣金)。
- 运行回测:
cerebro.run() - 输出分析结果和/或绘图:
cerebro.plot()
- 实例化
- 导入模块:
- 解释与说明:为用户解释代码的关键部分,特别是逻辑难点和Backtrader特有的概念(如
self.data.close[0]的含义)。 - 提供建议:根据策略逻辑,主动提供改进建议,如参数优化范围、风险控制思路或不同数据频率的影响。
约束与规范
- API准确性:提供的所有代码必须严格符合Backtrader的API规范。例如,使用
self.buy()/self.sell()下单,使用self.data.close[0]访问当前收盘价。 - 概念正确性:准确解释 Backtrader 的核心概念,如 Lines、Index 0、min period 等。
- 代码可运行性:生成的代码应尽可能完整,可以直接复制到Python环境中运行(可能需要根据用户的实际数据路径进行微小调整)。
- 最佳实践:推荐的写法应遵循社区最佳实践,例如在
__init__中声明指标,在next中处理逻辑,以实现性能最优。 - 命名规范:鼓励用户在添加数据时使用
name参数,以便在多数据源策略中清晰管理。
代码模板与示例
1. 基础策略模板
这是最常用的策略模板,包含完整的结构和注释。
"""
小果量化回测系统 - 极简模板
作者: xg_quant
功能: 双均线策略回测模板
"""
import backtrader as bt
import backtrader.indicators as btind
import yfinance as yf
from datetime import datetime
# ==================== 1. 定义策略 ====================
class DoubleMAStrategy(bt.Strategy):
"""双均线交叉策略:金叉买入,死叉卖出"""
# 策略参数
params = (
('fast', 5), # 快速均线周期
('slow', 20), # 慢速均线周期
)
def __init__(self):
# 计算两条均线
self.fast_ma = btind.SMA(self.data.close, period=self.params.fast)
self.slow_ma = btind.SMA(self.data.close, period=self.params.slow)
# 交叉信号:1=金叉,-1=死叉
self.crossover = btind.CrossOver(self.fast_ma, self.slow_ma)
# 订单状态标记
self.order = None
def log(self, txt):
"""日志输出"""
dt = self.datas[0].datetime.date(0)
print(f'{dt}, {txt}')
def notify_order(self, order):
"""订单状态通知"""
if order.status in [order.Submitted, order.Accepted]:
return
if order.status == order.Completed:
if order.isbuy():
self.log(f'买入成交 @ {order.executed.price:.2f}')
else:
self.log(f'卖出成交 @ {order.executed.price:.2f}')
self.order = None
def next(self):
"""核心交易逻辑(每个Bar调用一次)"""
# 如果有未完成订单,不执行新操作
if self.order:
return
# 金叉买入
if self.crossover[0] == 1 and not self.position:
self.log(f'金叉信号,准备买入 @ {self.data.close[0]:.2f}')
self.order = self.buy()
# 死叉卖出
elif self.crossover[0] == -1 and self.position:
self.log(f'死叉信号,准备卖出 @ {self.data.close[0]:.2f}')
self.order = self.sell()
# ==================== 2. 主程序 ====================
def main():
# 2.1 创建回测引擎
cerebro = bt.Cerebro()
# 2.2 添加策略
cerebro.addstrategy(DoubleMAStrategy, fast=5, slow=20)
# 2.3 加载数据(从Yahoo Finance)
print("正在下载数据...")
df = yf.download(
'AAPL', # 股票代码
start='2020-01-01', # 开始日期
end='2024-12-31', # 结束日期
progress=False # 关闭进度条
)
data = bt.feeds.PandasData(dataname=df)
cerebro.adddata(data)
print(f"数据加载完成,共 {len(df)} 个交易日\n")
# 2.4 配置经纪商
cerebro.broker.setcash(100000.0) # 初始资金 10万
cerebro.broker.setcommission(commission=0.001) # 佣金 0.1%
# 2.5 添加分析器
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')
# 2.6 运行回测
print("开始回测...")
print(f"初始资金: {cerebro.broker.getvalue():.2f}\n")
results = cerebro.run()
strat = results[0]
# 2.7 输出绩效报告
print("\n" + "="*50)
print("📊 回测绩效报告")
print("="*50)
# 资金变化
print(f"最终资金: {cerebro.broker.getvalue():.2f}")
profit = cerebro.broker.getvalue() - 100000
print(f"总盈亏: {profit:+.2f} ({profit/100000*100:+.2f}%)")
# 收益率
returns = strat.analyzers.returns.get_analysis()
print(f"年化收益率: {returns.get('rnorm100', 0):.2f}%")
# 夏普比率
sharpe = strat.analyzers.sharpe.get_analysis()
print(f"夏普比率: {sharpe.get('sharperatio', 0):.4f}")
# 最大回撤
drawdown = strat.analyzers.drawdown.get_analysis()
print(f"最大回撤: {drawdown.max.drawdown:.2f}%")
# 交易统计
trades = strat.analyzers.trades.get_analysis()
total_trades = trades.get('total', {}).get('total', 0)
if total_trades > 0:
win_rate = trades.get('won', {}).get('total', 0) / total_trades * 100
print(f"交易次数: {total_trades}")
print(f"胜率: {win_rate:.2f}%")
# 平均盈利/亏损
avg_win = trades.get('won', {}).get('pnl', {}).get('average', 0)
avg_loss = trades.get('lost', {}).get('pnl', {}).get('average', 0)
print(f"平均盈利: {avg_win:.2f}")
print(f"平均亏损: {avg_loss:.2f}")
if avg_loss != 0:
print(f"盈亏比: {abs(avg_win/avg_loss):.2f}")
print("="*50)
# 2.8 绘制图表
cerebro.plot(style='candlestick', figsize=(16, 10))
if __name__ == '__main__':
main()
##########################################
import backtrader as bt
import backtrader.indicators as btind
# 1. 定义策略类,继承自bt.Strategy
class MyStrategy(bt.Strategy):
# 1.1 定义策略参数
params = (
('ma_period', 15), # 移动平均线周期
('printlog', True), # 是否打印日志
)
# 1.2 日志函数
def log(self, txt, dt=None, doprint=False):
if self.params.printlog or doprint:
dt = dt or self.datas[0].datetime.date(0)
print(f'{dt.isoformat()}, {txt}')
# 1.3 策略初始化
def __init__(self):
# 获取收盘价序列的引用,方便使用
self.dataclose = self.datas[0].close
# 计算移动平均线
self.sma = btind.SimpleMovingAverage(self.datas[0], period=self.params.ma_period)
# 订单状态变量
self.order = None
# 1.4 订单状态通知
def notify_order(self, order):
if order.status in [order.Submitted, order.Accepted]:
# 订单已提交/接受,等待执行
return
if order.status in [order.Completed]:
if order.isbuy():
self.log(f'买入执行, 价格: {order.executed.price:.2f}, 成本: {order.executed.value:.2f}, 佣金: {order.executed.comm:.2f}')
else:
self.log(f'卖出执行, 价格: {order.executed.price:.2f}, 成本: {order.executed.value:.2f}, 佣金: {order.executed.comm:.2f}')
self.bar_executed = len(self) # 记录成交时的Bar索引
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
self.log('订单取消/保证金不足/被拒绝')
# 订单处理完毕,重置订单变量,允许新订单
self.order = None
# 1.5 交易通知
def notify_trade(self, trade):
if not trade.isclosed:
return
self.log(f'交易利润, 毛利润: {trade.pnl:.2f}, 净利润: {trade.pnlcomm:.2f}')
# 1.6 策略核心逻辑
def next(self):
# 如果有待处理订单,不执行新操作
if self.order:
return
# 如果没有持仓
if not self.position:
# 买入条件:收盘价上穿移动平均线
if self.dataclose[0] > self.sma[0] and self.dataclose[-1] <= self.sma[-1]:
self.log(f'创建买入订单, 收盘价: {self.dataclose[0]:.2f}')
self.order = self.buy() # 使用默认Sizer计算头寸
else:
# 卖出条件:收盘价下穿移动平均线
if self.dataclose[0] < self.sma[0] and self.dataclose[-1] >= self.sma[-1]:
self.log(f'创建卖出订单, 收盘价: {self.dataclose[0]:.2f}')
self.order = self.sell()
# 2. 主程序
if __name__ == '__main__':
# 2.1 实例化Cerebro引擎
cerebro = bt.Cerebro()
# 2.2 添加策略
cerebro.addstrategy(MyStrategy, ma_period=20)
# 2.3 加载数据 (示例:从CSV加载)
data = bt.feeds.GenericCSVData(
dataname='your_data_file.csv', # 替换为你的文件路径
dtformat='%Y-%m-%d', # 日期格式
timeframe=bt.TimeFrame.Days
)
cerebro.adddata(data)
# 2.4 配置经纪商
cerebro.broker.setcash(100000.0) # 初始资金
cerebro.broker.setcommission(commission=0.001) # 佣金:0.1%
# 2.5 添加分析器
cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpe')
cerebro.addanalyzer(bt.analyzers.DrawDown, _name='drawdown')
cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name='trades')
# 2.6 运行回测
print(f'初始资金: {cerebro.broker.getvalue():.2f}')
results = cerebro.run()
strat = results[0] # 获取策略实例
# 2.7 输出分析结果
print(f'最终资金: {cerebro.broker.getvalue():.2f}')
print(f'夏普比率: {strat.analyzers.sharpe.get_analysis()["sharperatio"]:.4f}')
print(f'最大回撤: {strat.analyzers.drawdown.get_analysis()["max"]["drawdown"]:.2f}%')
# 2.8 绘图
cerebro.plot(style='candlestick') # 可选 style='bar', 'line'
#################################################
多数据/多周期策略片段
展示如何在同一策略中使用日线和周线数据。
python
# 假设已经通过 cerebro.adddata 和 cerebro.resampledata 添加了数据
class MultiTimeframeStrategy(bt.Strategy):
def __init__(self):
# self.data0 是日线数据
self.sma_daily = bt.indicators.SMA(self.data0, period=20)
# self.data1 是周线数据 (通过重采样得到)
self.sma_weekly = bt.indicators.SMA(self.data1, period=5)
def next(self):
# 检查周线趋势:周线收盘价高于周线均线
weekly_trend_up = self.data1.close[0] > self.sma_weekly[0]
# 日线买入信号:日线收盘价上穿日线均线
daily_buy_signal = self.data0.close[0] > self.sma_daily[0] and self.data0.close[-1] <= self.sma_daily[-1]
if weekly_trend_up and daily_buy_signal and not self.position:
self.buy()
3. 使用分析器进行绩效评估
在回测后获取并打印交易统计信息。
python
# ... 回测运行后 ...
strat = results[0]
trade_analysis = strat.analyzers.trades.get_analysis()
print("=" * 30)
print("交易统计")
print("=" * 30)
print(f"总交易次数: {trade_analysis.total.total}")
print(f"胜率: {trade_analysis.won.total / trade_analysis.total.total if trade_analysis.total.total > 0 else 0:.2%}")
print(f"平均盈利: {trade_analysis.won.pnl.average:.2f}")
print(f"平均亏损: {trade_analysis.lost.pnl.average:.2f}")
print(f"最大盈利: {trade_analysis.won.pnl.max:.2f}")
print(f"最大亏损: {trade_analysis.lost.pnl.min:.2f}")
4. 参数优化示例
python
# 在添加策略时,使用 optstrategy 替代 addstrategy
cerebro.optstrategy(
MyStrategy,
ma_period=range(10, 31) # 优化 ma_period 从 10 到 30
)
# 运行优化(注意:这会消耗较多计算资源)
optimized_results = cerebro.run()
# 优化结果是一个列表的列表,每个内部列表包含一次运行的策略实例
# 可以遍历并输出每次运行的参数和绩效
for run_strategies in optimized_results:
for strat in run_strategies:
print(f"参数: ma_period={strat.params.ma_period}, 最终资金: {strat.broker.getvalue():.2f}")
常见问题解答 (FAQ)
问:self.datas[0] 和 self.data 有什么区别?
答:self.datas 是一个列表,存储了系统中所有数据源。self.datas[0] 是第一个数据源。self.data 是 self.datas[0] 的别名,用于简化代码。
问:self.data.close[0] 中的 [0] 是什么意思?
答:在Backtrader中,[0] 表示当前时刻(即最新的Bar)的值。[-1] 表示前一个Bar的值,[-2] 表示再前一个,以此类推。
问:订单在 next 中创建后,为什么有时不会立即执行?
答:订单创建后,会经过经纪商(Broker)的处理。经纪商会在下一个Bar(或根据订单类型)进行撮合。因此,订单的执行是异步的,需要通过 notify_order 来获知其最终状态。
问:如何设置不同品种的佣金?
答:可以使用 cerebro.broker.setcommission() 方法。对于期货,需要设置 margin 和 mult 参数。对于不同品种,可以通过 name 参数来区分,例如:
python
cerebro.broker.setcommission(commission=0.001, name='AAPL') # 为AAPL设置0.1%佣金
cerebro.broker.setcommission(commission=2.0, margin=2000, mult=10, name='ES') # 为ES期货设置固定佣金
总结
作为“小果量化回测系统助手”,你的使命是降低量化交易的门槛,帮助用户将他们的交易思想快速转化为可验证、可优化的代码。借助 Backtrader 的强大功能和你的专业知识,你将使用户的回测工作事半功倍
1 1什么是 Backtrader.txt ##################################################
文件路径: 1什么是 Backtrader.txt
================================================================================
Backtrader 是一个基于 Python 实现功能强大且开源的量化回测交易框架。Backtrader 有着完整的基础设施,支持编写可重用组件,如策略、指标和分析器。
Backtrader 支持多品种多周期多策略的回测。相对于其他专门用于策略回测的框架,Backtrader 不仅仅是可以回测交易策略,还可以连接 Broker 实盘交易。
有哪些特性呢?#
稍微展开说说 Backtrader 的特性吧。
丰富的技术指标
Backtrader 提供超过 122 种内置指标,包括多种移动平均线(SMA、EMA 等)和经典指标(MACD、随机指标、RSI 等)。
如果内置指标不能满足需求,Backtrader 还集成了其他技术指标库,如 ta-lib 库。而且,我们也可以自定义技术指标,Backtrader 也提供了自定义指标的能力。
支持多种数据源
Backtrader 支持多种数据源类型,如常见的一些数据源:
CSV 数据文件;
Pandas 数据源,可以借助 Pandas 的强大功能连接各种不同的数据源;
YahooFinance,这个好像已经不能用了,不过借助 yfinance 包和 pandas 可轻易集成;
其他的一些公开数据源。
除此以外,Backtrader 支持自定义数据格式,如希望实现股票选择策略,可自定义数据格式,加入财务数据和因子,如 PE、PB、ROI 等指标。
Backtrader 支持同时添加多个 DataFeed 数据源,如多个时间框架数据源实现多周期策略,多标的数据源实现组合交易策略,或是直接进行数据的重采样和重放功能,实现真实交易环境的模拟。
灵活的策略实现
Backtrader 支持灵活的策略开发,如在策略初始化阶段,Backtrader 支持预热计算,预热完成才进入交易逻辑部分。
Backtrader 支持并行运行多个策略,实现多策略组合。其内置的 Broker 支持多种订单类型,如市价单、限价单、止损单等,支持多空交易,自定义佣金方案和信用利息,提供针对期货类工具的连续现金调整,支持基金模式和自定义滑点策略。
它还提供多种订单生成方法,并支持事件通知机制,包括新数据、订单、交易和计时器等等。
性能分析与绘图
Backtrader 内置多个性能分析器,包括时间收益、交易分析器、夏普比率、VWR 和 SQN 等,帮助用户全面评估策略表现。Backtrader 支持通过一行代码绘图(安装 Matplotlib),用户可自定义绘图,评估策略表现。
架构设计#
Backtrader 本身是一个模块拆分的非常优秀的框架,解耦复用方面做的非常优秀。
如下所示的一系列组件:
DataFeed:负责加载行情数据,包括历史数据和实时数据,是策略运行的基础输入。
Cerebro:Backtrader 的核心引擎,用于统一管理数据、策略、分析器、经纪商等组件,并负责执行回测或实盘。
Indicator:用于计算常见的技术指标(如均线、RSI、布林带等),也可以自定义指标逻辑,为策略提供信号依据。
Strategy:定义具体的交易规则和执行逻辑,包括开仓、平仓、止损止盈等,是系统的决策核心。
Broker:模拟或连接真实交易账户,负责订单撮合、资金管理和手续费处理。
Analyzer:对策略的回测结果进行统计与评估,如收益率、最大回撤、夏普比率等。
Observer:用于跟踪策略运行过程并输出可视化结果,如资金曲线、买卖点、回撤变化等。
如下是各个组件之间的关系架构图:
Backtrader 的核心 Cerebro 调度整个回测流程。
首先,由 DataFeed 提供行情数据,Cerebro 将数据传递给 Strategy。策略读取数据并调用 Indicator 计算技术信号,据此生成买卖决策并交由 Broker 执行交易。交易完成后,Broker 更新账户与持仓,并将结果反馈给 Cerebro。
随后,Analyzer 分析策略绩效(如收益、回撤),Observer 记录并可视化资金曲线与交易点。Cerebro 不断循环此流程,直到数据结束,最终汇总输出策略表现。
整个系统形成从“数据→决策→执行→反馈→分析”的闭环结构。
实盘交易#
Backtrader 的交易逻辑和经纪商 Broker 的操作是逐事件驱动,这让其可非常容易实现实盘交易。
我们在回测时,指标计算尽可能通过向量化计算,预加载源数据提升计算速度。而实盘模式下,可在仅事件模式下运行,无需预加载数据。
Backtrader 支持与多种 Broker 经纪商实时交易,包括 Interactive Brokers、Oanda v1 和 VisualChart。此外,还支持第三方经纪商如 Alpaca、Oanda v2 和 ccxt 的集成。其中,ccxt 是加密货币实盘交易的 Python 库。
加密货币的程序交易账号相对更加容易获取,后续或许我会用 ccxt 演示 backtrader 的实盘交易加密货币。
2 2安装指南.txt ##################################################
文件路径: 2安装指南.txt
================================================================================
本文是 Backtrader 量化交易框架的安装指南。我们将详细介绍多种安装方式,并涵盖版本兼容性、功能依赖和验证安装的完整流程。
环境与兼容性#
Backtrader 官方支持 Python 3.2-3.7,但实际测试表明,它在 Python 3.10 乃至最新的 3.13 版本上都能稳定运行。
虽然官方主要基于 Python 2.7 进行开发,并在 3.4 环境下测试,但当前版本对新版 Python 的兼容性表现良好。如需使用绘图功能,请确保安装 Matplotlib 1.4.1 或更高版本。
安装方式#
最便捷的方式是通过 PyPI 进行安装。只需执行以下命令即可完成基础安装:
pip install backtrader
如果需要使用绘图功能,建议安装包含 Matplotlib 的完整版本:
pip install "backtrader[plotting]"
这条命令会自动安装 Matplotlib 及其所有依赖项,省去手动配置的麻烦。
验证安装效果#
为了确认安装成功,我们可以运行一个简单的测试策略。以下是完整的测试代码示例:
import yfinance as yf
import backtrader as bt
from datetime import datetime
class BuyHold(bt.Strategy):
def next(self):
if not self.position:
self.buy()
cerebro = bt.Cerebro()
cerebro.addstrategy(BuyHold)
raw_data = yf.download(
"AAPL", start="2020-01-01", end="2021-01-01", multi_level_index=False
)
apple_data = bt.feeds.PandasData(dataname=raw_data)
cerebro.adddata(apple_data)
cerebro.broker.setcash(1e8)
print(f"初始资金: {cerebro.broker.getcash()}")
cerebro.run()
print(f"最终资金: {cerebro.broker.getcash()}")
cerebro.plot()
运行前请确保已安装 yfinance 库:
pip install yfinance
执行脚本:
python buyhold.py
如果能看到资金变化并显示交易图表,说明安装完全成功。
源码安装方式#
对于希望深入研究的用户,可以从 GitHub 获取最新源码:
git clone https://github.com:mementum/backtrader src
cp -r src/backtrader project_directory/
cd project_directory
export PYTHONPATH=`pwd`
这种方式虽然需要手动安装 Matplotlib,但便于调试和阅读核心代码,特别适合需要进行深度定制的用户。
额外提示#
如果追求极致的回测性能,可以尝试在 PyPy3 环境下运行 Backtrader,不过需要注意其绘图功能支持相对有限。无论选择哪种安装方式,都建议通过测试代码验证各项功能是否正常。
3 3快速开始.txt ##################################################
文件路径: 3快速开始.txt
================================================================================
本文是一份 Backtrader 的快速入门指南,将通过一个完整的示例,带你从零构建一个交易系统,希望在此过程中掌握 Backtrader 的基础使用。
主要内容如下:
初始设置:配置 Backtrader,实例化 Cerebro 准备运行环境;
账户资金:Cerebro 配置初始资金、手续费与滑点;
配置数据:加载历史行情数据(CSV 或 Pandas)并定义时间周期;
基础概念:在开始编写策略前,先了解 Line 和索引 0 的概念;
演示策略:编写基础策略类,实现最简单的买入逻辑;
开始交易:运行策略,完成第一轮回测;
卖出操作:添加卖出逻辑,支持止盈止损与仓位管理;
交易监控:通过 notify_order 和 notify_trade 实时跟踪成交情况;
参数定义:为策略添加可调参数,为后续优化做准备;
技术指标:引入常用指标(SMA、EMA、RSI 等),完善信号判断;
可视化: 绘制回测结果图,展示每笔交易盈亏与指标变化;
策略优化:利用 optstrategy 功能自动化优化参数,比较收益表现;
4 4初始配置.txt ##################################################
文件路径: 4初始配置.txt
================================================================================
在写复杂策略前,先搭好运行环境很重要。Backtrader 的核心是 Cerebro,它负责管理数据、策略和资金,就像系统的大脑。
如下所示是最简单的初始配置代码:
import backtrader as bt
if __name__ == '__main__':
cerebro = bt.Cerebro()
print(f'初始资金: {cerebro.broker.getvalue()}')
cerebro.run()
print(f'最终资金: {cerebro.broker.getvalue()})
首先导入了 backtrader 模块并命名为 bt。
import backtrader as bt
并基于 bt.Cerebro 实例化了 Cerebro 引擎。
cerebro = bt.Cerebro()
通过 cerebro.broker.getvalue() 获取并打印了初始的组合价值。
print(f'初始资金: {cerebro.broker.getvalue()}')
接着运行 cerebro.run() 迭代数据模拟交易。完成后,再次打印最终的持仓组合价值
cerebro.run()
print(f'最终资金: {cerebro.broker.getvalue()})
输出如下:
初始资金: 10000.00
最终资金: 10000.00
这个基础设置是构建复杂交易策略的基础。
这一简单的示例中,Cerebro 引擎在后台创建了一个 broker 实例,并自动分配了一些初始资金。这种 broker 实例化是 backtrader 的常规特性,旨在简化用户操作。
如未明确设置 broker,系统会使用默认 broker,默认初始资金通常是 10,000 货币单位。
接下来的章节,会对 broker 进行更对的配置,继续添加数据源(DataFeed)、策略(Strategy)、指标(Indicator)等,逐步完善这个交易系统。
5 5账户配置.txt ##################################################
文件路径: 5账户配置.txt
================================================================================
本指南将介绍 Backtrader 量化回测框架中的基本配置方法,包括设置初始账户资金、配置交易佣金等核心参数。这些基础配置是进行准确回测的前提,能够帮助您模拟真实的交易环境。
设置初始账户资金#
上节中,账户资金使是默认值 10,000 货币单位。当然,这个默认值是可以更改的,通过 cerebro.broker 的 setcash 方法即可。
cerebro.broker.setcash(100000.0)
完整示例#
import backtrader as bt
def main():
cerebro = bt.Cerebro()
cerebro.broker.setcash(100000.0)
print(f'初始资金: {cerebro.broker.getvalue()}')
cerebro.run()
print(f'最终资金: {cerebro.broker.getvalue()}')
if __name__ == '__main__':
main()
输出:
初始资金: 100000.00
最终资金: 100000.00
设置交易费率(佣金)#
在回测中,交易成本是影响最终收益的重要因素。Backtrader 允许您灵活配置各种佣金方案。
按比例设置佣金#
对于股票等按交易金额比例收取佣金的品种,可以使用 setcommission 方法:
import backtrader as bt
def main():
cerebro = bt.Cerebro()
cerebro.broker.setcash(100000.0)
# 设置交易佣金为千分之三(0.3%)
cerebro.broker.setcommission(commission=0.003)
print(f'初始资金: {cerebro.broker.getvalue():.2f}')
cerebro.run()
print(f'最终资金: {cerebro.broker.getvalue():.2f}')
if __name__ == '__main__':
main()
佣金计算说明#
commission=0.003 表示每笔交易收取交易金额的 0.3% 作为佣金
买入和卖出时都会按比例收取
佣金在交易执行时自动从账户中扣除
在实际回测中,佣金会直接影响最终的账户价值。
设置滑点规则#
Backtrader 也提供了简单的滑点模拟功能。
最常用的是按比例设置,使用名为 set slippage percent 的方法。
如设置一个千分之一,也就是 0.1% 的滑点:
cerebro.broker.set_slippage_perc(0.001)
它的计算规则是这样的:
买入订单时,实际成交价会比你预期的价格高一点,公式:预期价格 乘以 1 + 滑点比例。
卖出订单时,实际成交价会比你预期的价格低一点,公式:预期价格 乘以 1 - 滑点比例。
如当执行是市价买入时,价格是 1000,如果滑点是 0.1%,则买入价就是 1001。
Backtrader 也支持设置固定值的滑点,通过 cerebro.broker.set_slippage_fixed 方法。
如设置固定滑点为 1。
cerebro.broker.set_slippage_fixed(1)
如当执行是市价买入时,价格是 1000,如果滑点是 1,则买入价就是 1001。当执行是市价买入时,价格是 2000,如果滑点是 1,则买入价就是 2001。
最后#
本文介绍了 Backtrader 的账户资金、佣金和滑点的配置,下一节正式开始介绍数据的配置。
6 6配置数据.txt ##################################################
文件路径: 6配置数据.txt
================================================================================
本文具体讲讲如何在 Backtrader 中配置数据源,也就是 DataFeed。
我会先演示如何给 Backtrader 添加 CSV 数据源。然后,再介绍 Backtrader 中最常用的两种 DataFeed 的使用。
添加数据#
首先,来演示给 Backtrader 添加数据源吧。
给 Backtrader 加载数据源的过程可分为两个步骤,首先是创建 DataFeed,然后就是将 DataFeed 加载到 cerebro。
data = bt.feeds.{DataFeedClass}
cerebro.adddata(data)
Backtrader 提供了种类繁多的 DataFeedClass,可用于对接处理不同的数据源,如 CSV 文件、Pandas DataFrame 或者其他自定义的数据类。
下面来个具体的案例,先用 CSV 数据文件作为演示案例。
先从 Backtrader 代码仓库中下载了 Oracle 从 1995 年到 2014 的行情数据文件orcl-1995-2014.csv。
开始编写代码吧。
首先是读取 csv 数据文件创建 DataFeed。Backtrader 内置的 bt.feeds.GenericCSVData 类来读取 CSV 数据文件。
具体操作代码如下:
data = bt.feeds.GenericCSVData(
dataname="./orcl-1995-2014.txt",
dtformat="%Y-%m-%d",
)
指定数据文件的路径和设置日期格式。
GenericCSVData 能适应各种格式的 CSV 文件,是一个通用的读取 CSV 行情数据的 DataFeed 类。
Datafeed 创建好后,接下来的关键一步,即通过 cerebro.adddata(data) 把数据加载到回测引擎中。
cerebro.adddata(data)
这个脚本的代码:
import backtrader as bt
def main():
cerebro = bt.Cerebro()
data = bt.feeds.GenericCSVData(
dataname="./orcl-1995-2014.txt", dtformat="%Y-%m-%d",
)
cerebro.adddata(data)
cerebro.run()
cerebro.plot()
if __name__ == "__main__":
main()
运行这个代码,现在就能看到输出的图表中是有价格行情了。
创建数据#
前面是通过从远程下载 CSV 文件,通过 Backtrader 的 GenericCSVData 读取 CSV 文件创建 DataFeed。Backtrader 还支持数据获取方式。
这里就再介绍一种更常用的 DataFeed 数据创建方式:基于 Pandas 的 DataFrame 创建 DataFeed。
依赖 pandas 的灵活性,这种方式能极大扩展你的数据渠道,无论是本地文件,如 CSV 文件,还是关系型数据,如 CSV,或是远程 API 下载,如 yfinance、tushare、akshare 等,都能搞定。
好,我们直接来看代码。
现在通过 yfinance 下载行情数据,而不是手动下载 CSV 文件再加载。
下载和加载 BTC-USD 从 2024-01-01 到 2025-11-10 的行情数据
cerebro = bt.Cerebro()
df = yf.download("BTC-USD", start="2024-01-01", end='2025-11-10', multi_level_index=False)
data = bt.feeds.PandasData(dataname=df)
cerebro.adddata(data)
cerebro.run()
cerebro.plot(style='bar')
注,记得使用最新的 yfinance 要设置 multi_level_index=False,否则可能得到多级索引的数据,导致加载失败。
输出绘图如下所示:
如上所示,我们先用 yfinance 获取函数下载指定品种和时间段的数据,得到一个 DataFrame。然后传递给 PandasData 就可创建出满足 Backtrader 的数据了。
实际上 PandasData 对传入的数据是有一定要求的。
索引是时间,数据列要满足 open,high,low,close,volume等。当然,数据列名是大写,如 Open、High、Low、Close 也是可以的。PandasData 内部会自动转化为小写。
准备工作做完后,个 DataFrame 就可用 bt.feeds.PandasData 来包装了。然后通过 cerebro.adddata() 把它添加到回测系统中。
好了,现在我们的回测系统里已经有了数据。
最后说明#
Backtrader 的数据能力是非常强大,本文只是简单的介绍创建和添加数据。除此,Backtrader 还提供了其他丰富多样的 DataFeed 类。
大部分情况,本文的 GenericCSVData 和 PandasData 已经基本能满足大部分的情况。如果有自定义数据列的需求,Backtrader 也是只是自定义数据的。
添加数据部分,除了 cerebro.adddata 直接将数据添加到回测系统,还可以重放、重采样数据,模拟实际交易环境,或者多周期交易。
下一节,我们学习如何在 Backtrader 中编写我们的交易策略。
7 7第一个策略.txt ##################################################
文件路径: 7第一个策略.txt
================================================================================
本节我们将学习如何开发策略。
第一个策略不涉及交易,我们将通过它打印每一天(bar)的 “收盘价(Close)” 。
策略类(Strategy)继承自 bt.Strategy。
class TestStrategy(bt.Strategy):
def __init__(self):
pass
def next(self):
pass
它最重要的两个方法是 __init__(策略初始化)和 next(每个 OHLC,即bar,执行一次该方法)。
class TestStrategy(bt.Strategy):
def log(self, txt, dt=None):
dt = dt or self.datas[0].datetime.date(0)
print('%s, %s' % (dt.isoformat(), txt))
def __init__(self):
self.dataclose = self.datas[0].close
def next(self):
self.log('Close, %.2f' % self.dataclose[0])
如上代码中的 self.datas[0].close 访问的就是 cerebro.broker.adddata 方法添加的第一个数据源 DataFeed。
数据列表(self.datas)是一个标准的Python列表,如添加多个数据源,数据按插入顺序存储。列表中的第一个数据项self.datas[0]是默认交易数据,用于同步所有策略元素(它作为系统时钟)。
self.datas 的列表元素 data 底层类是 DataSeries,它有别名访问众所周知的 OHLC(开盘、高、低、收盘)每日值。
open = data.open
low = data.low
high = data.high
close = data.close
为了便于使用,我们将其赋值到 self.dataclose,这将简化打印逻辑的创建。
self.dataclose = self.datas[0].close
接着,在 next 方法中打印 self.dataclose[0] 最新收盘价即可。策略的 next 方法会在每个新的bar上调用,使用系统时钟(即self.datas[0])作为参考。
def next(self):
self.log('Close, %.2f' % self.dataclose[0])
我们有了策略类 TestStrategy,还要通过 cerebro.addstrategy 将其添加交易系统中。
cerebro.addstrategy(TestStrategy)
完整示例#
import datetime # For datetime objects
import os.path # To manage paths
import sys # To find out the script name (in argv[0])
import backtrader as bt
class TestStrategy(bt.Strategy):
def log(self, txt, dt=None):
dt = dt or self.datas[0].datetime.date(0)
print('%s, %s' % (dt.isoformat(), txt))
def __init__(self):
self.dataclose = self.datas[0].close
def next(self):
self.log('Close, %.2f' % self.dataclose[0])
if __name__ == '__main__':
cerebro = bt.Cerebro()
cerebro.addstrategy(TestStrategy)
modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
datapath = os.path.join(modpath, './orcl-1995-2014.txt')
data = bt.feeds.YahooFinanceCSVData(
dataname=datapath,
fromdate=datetime.datetime(2000, 1, 1),
todate=datetime.datetime(2000, 12, 31),
reverse=False)
cerebro.adddata(data)
cerebro.broker.setcash(100000.0)
print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())
cerebro.run()
print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
输出:
Starting Portfolio Value: 100000.00
2000-01-03, Close, 27.85
2000-01-04, Close, 25.39
2000-01-05, Close, 24.05
...
...
...
2000-12-26, Close, 29.17
2000-12-27, Close, 28.94
2000-12-28, Close, 29.29
2000-12-29, Close, 27.41
Final Portfolio Value: 100000.00
8 8基础概念.txt ##################################################
文件路径: 8基础概念.txt
================================================================================
基础概念#
在正式动手前,我们需要先理解两个极其关键的概念:Line(线) 和 索引 0(Index 0)。
Line#
在 Backtrader 的世界中,几乎一切都由 线(Line) 构成。无论是价格数据、指标还是策略内部变量,它们都以 Line 的形式存在。
你可以把 Line 理解为一条随时间变化的数据序列,就像价格走势图上的那条曲线。一条 Line 是由一系列点(即时间序列数据)构成的,例如收盘价随时间变化的轨迹。
对于一个典型的行情数据源(DataFeed),每天包含以下几个关键数据点:
开盘价(Open)
最高价(High)
最低价(Low)
收盘价(Close)
成交量(Volume)
未平仓量(OpenInterest)
沿着时间轴看,这些数据点各自形成一条独立的“线”:如所有的收盘价构成了 Close Line,所有的开盘价构成了 Open Line。
例如,沿时间轴上的“开盘价”形成了一条线(Line)。因此,一个数据源通常包含6条线。如果再考虑“日期时间” (DateTime)(作为单个点的实际参考),就可以得到7条线(Line)。
因此,一个完整的数据源通常包含 6 条线。如果再加上用于标识时间的 DateTime,那么一共就是 7 条线。
在指标(Indicator)中,这一概念同样适用。如简单移动平均线(SMA) 会根据收盘价计算一个平均值序列,这个序列同样是一条 Line。再如布林带、RSI、MACD 等指标,都会在内部生成若干条 Line,分别表示不同的计算结果。
在 Backtrader 中,Line 是一切的基础结构单位。
数据 → Line
指标 → Line 的运算结果
策略 → 对多条 Line 的逻辑组合与判断
索引0#
理解 Line 之后,另一个必须掌握的核心概念是 索引(Index)。在 Python 中,索引 [0] 通常表示第一个元素,而 [-1] 表示最后一个元素。但 Backtrader 中,索引的语义稍有不同:
索引 0([0])表示当前时刻的值
索引 -1([-1])表示上一个时间点的值
索引 -2([-2])表示再上一个时间点的值
以此类推。
例如,假设在策略初始化阶段创建了一个简单移动平均线(SMA):
self.sma = bt.indicators.SimpleMovingAverage(self.data.close, period=15)
当前的 SMA 值是:
current_value = self.sma[0]
上一个时间点的 SMA 值:
previous_value = self.sma[-1]
继续往前推:
two_bars_ago = self.sma[-2]
这样,我们就可方便地比较过去几个时间点,判断趋势变化、触发信号等。
# 收盘价刚刚从下方突破均线,则买入
if self.data.close[0] > self.sma[0] and self.data.close[-1] <= self.sma[-1]:
self.buy()
这段逻辑就是一个典型的 “均线突破买入信号”。通过索引操作,我们无需关心当前是哪一天或第几根K线,只需直接比较“现在”和“过去”的数值即可。
Line 和索引的应用#
理解了 Line 和 索引 0,你就掌握了 Backtrader 的“语言”。当编写指标或策略逻辑时,就会经常看到类似这样的写法:
self.rsi = bt.indicators.RSI(self.data.close)
RSI 指标生成了一条新的 Line。
if self.rsi[0] > 70:
self.sell()
我们通过 rsi[0] 访问当前 RSI 值,当 RSI 超过 70 时,执行卖出操作。
无论是移动平均、布林带、RSI,还是你的自定义指标,最终都是 Line + 索引访问 的形式。
9 9开始交易.txt ##################################################
文件路径: 9开始交易.txt
================================================================================
本节,我们将演示一个简单的策略,让策略执行交易操作,如果出现连续两个交易日下跌即 - 买入!买入!买入!
我们将基于上节的策略类 TestStrategy 继续开发,策略逻辑部分要在 next 方法中实现。
class TestStrategy(bt.Strategy):
def log(self, txt, dt=None):
dt = dt or self.datas[0].datetime.date(0)
print('%s, %s' % (dt.isoformat(), txt))
def __init__(self):
self.dataclose = self.datas[0].close
def next(self):
self.log('Close, %.2f' % self.dataclose[0])
首先是买入条件,如何判断两日连续下跌呢?
简单而言,就是 close[0] < close[-1] 和 close[-1] < close[-2],即当前收盘价小于昨日收盘价,昨日收盘价小于大前日收盘价。
self.dataclose[0] < self.dataclose[-1] and self.dataclose[-1] < self.dataclose[-2]
而买入操作使用 self.buy 皆可。
self.buy()
默认情况下,如果存在多个数据源,self.buy 买入的是第一个数据资产。
完整示例#
import datetime # For datetime objects
import os.path # To manage paths
import sys # To find out the script name (in argv[0])
import backtrader as bt
class TestStrategy(bt.Strategy):
def log(self, txt, dt=None):
dt = dt or self.datas[0].datetime.date(0)
print('%s, %s' % (dt.isoformat(), txt))
def __init__(self):
self.dataclose = self.datas[0].close
def next(self):
self.log('Close, %.2f' % self.dataclose[0])
if self.dataclose[0] < self.dataclose[-1]:
if self.dataclose[-1] < self.dataclose[-2]:
self.log('BUY CREATE, %.2f' % self.dataclose[0])
self.buy()
if __name__ == '__main__':
cerebro = bt.Cerebro()
cerebro.addstrategy(TestStrategy)
modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
datapath = os.path.join(modpath, './orcl-1995-2014.txt')
data = bt.feeds.YahooFinanceCSVData(
dataname=datapath,
fromdate=datetime.datetime(2000, 1, 1),
todate=datetime.datetime(2000, 12, 31),
reverse=False)
cerebro.adddata(data)
cerebro.broker.setcash(100000.0)
print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())
cerebro.run()
print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
输出:
Starting Portfolio Value: 100000.00
2000-01-03, Close, 27.85
2000-01-04, Close, 25.39
2000-01-05, Close, 24.05
2000-01-05, BUY CREATE, 24.05
2000-01-06, Close, 22.63
2000-01-06, BUY CREATE, 22.63
2000-01-07, Close, 24.37
...
...
...
2000-12-20, BUY CREATE, 26.88
2000-12-21, Close, 27.82
2000-12-22, Close, 30.06
2000-12-26, Close, 29.17
2000-12-27, Close, 28.94
2000-12-27, BUY CREATE, 28.94
2000-12-28, Close, 29.29
2000-12-29, Close, 27.41
Final Portfolio Value: 99725.08
多个“买入”创建订单被发出,我们的组合价值减少了。显然缺少了一些重要的内容,订单已创建,但尚未确定其执行时间和价格。
下个示例将监听订单状态通知来完善这个过程。
10 10卖出操作.txt ##################################################
文件路径: 10卖出操作.txt
================================================================================
了解如何进入市场(多头,self.buy)后,还要一个“退出概念”,以及了解策略是否在市场中。
本节将演示入场后如何退出市场。
退出逻辑很简单,入场持有 5 根 bar 后(在第 6 根 bar 上)退出,无论是盈利还是亏损。另外,为了简化逻辑,仅在未入场时允许买入订单,即如果有持仓或者进行中的订单都不可买入。
逻辑逻辑#
要完成这个逻辑,要能确认订单成交时间、成交所在位置、当前是否有进行中的订单以及是否有持仓。
订单状态#
订单确认状态通过 Strategy 的订单状态变化 notify_order 方法监听。
def notify_order(self, order):
print(order.status)
订单状态有 Accepted(已接受)、Submitted(已提交)、Completed(已成交)、Margin(保证金不足)、Rejected(已拒绝)。
订单类中除了状态,还包括订单的其他信息,如执行价格 - order.executed.price,已执行价值 - order.executed.value,订单手续费 - order.executed.comm。
更多订单的信息,可查看 订单- Orders。
成交位置#
策略对象提供了对默认数据源位置的访问,通过 len(self) 即可获得。
len(self)
next 方法没有提供 “bar索引”,因此很难理解何时经过了 5 根bar,但调用对象的 len 方法,它会告诉你线的长度。
我们只需记下订单完成时的长度,并查看当前长度是否距离其5根bar。
def notify_order(self, order):
if order.status in [order.Completed]:
self.bar_executed = len(self)
def next(self):
# 其他代码
if len(self) >= (self.bar_executed + 5):
# 卖出退场
此处无 “时间” 或 “时间框架” 含义,仅仅是 bar 的数量。bar可以代表1分钟、1小时、1天、1周或任何其他时间周期。尽管我们知道数据源是每日的,但策略不对其做任何假设。
进行中的订单#
是否有进行中的订单如何判断?
我们调用买入和卖出方法会返回创建的(尚未执行的)订单。我们可记录创建后的订单,待最终确认后(即非提交中 Submitted 和订单被接受 Accepted)将其清空。而我们只要在每次交易前,检查是否有进行中的订单即可。
def notify_order(self, order):
if order.status in [order.Submitted, order.Accepted]:
return
self.order = None
def next(self):
# 是否有进行中的订单
if self.order:
return
# 如果满足买入
self.order = self.buy()
# 如果满足退出
self.order = self.sell()
是否有持仓#
是否有持仓的判断比较简单,通过 self.position 即可判断。
if not self.position:
# 入场逻辑
else:
# 出场逻辑
完整示例#
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import datetime # For datetime objects
import os.path # To manage paths
import sys # To find out the script name (in argv[0])
import backtrader as bt
class TestStrategy(bt.Strategy):
def log(self, txt, dt=None):
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
def notify_order(self, order):
if order.status in [order.Submitted, order.Accepted]:
return
if order.status in [order.Completed]:
if order.isbuy():
self.log('BUY EXECUTED, %.2f' % order.executed.price)
elif order.issell():
self.log('SELL EXECUTED, %.2f' % order.executed.price)
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 next(self):
self.log('Close, %.2f' % self.dataclose[0])
if self.order:
return
if not self.position:
if self.dataclose[0] < self.dataclose[-1]:
if self.dataclose[-1] < self.dataclose[-2]:
self.log('BUY CREATE, %.2f' % self.dataclose[0])
self.order = self.buy()
else:
if len(self) >= (self.bar_executed + 5):
self.log('SELL CREATE, %.2f' % self.dataclose[0])
self.order = self.sell()
if __name__ == '__main__':
cerebro = bt.Cerebro()
cerebro.addstrategy(TestStrategy)
modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
datapath = os.path.join(modpath, '../../datas/orcl-1995-2014.txt')
data = bt.feeds.YahooFinanceCSVData(
dataname=datapath,
fromdate=datetime.datetime(2000, 1, 1),
todate=datetime.datetime(2000, 12, 31),
reverse=False)
cerebro.adddata(data)
cerebro.broker.setcash(100000.0)
print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())
cerebro.run()
print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
输出:
Starting Portfolio Value: 100000.00
2000-01-03, Close, 27.85
2000-01-04, Close, 25.39
2000-01-05, Close, 24.05
2000-01-05, BUY CREATE, 24.05
2000-01-06, BUY EXECUTED, 23.61
2000-01-06, Close, 22.63
2000-01-07, Close, 24.37
2000-01-10, Close, 27.29
2000-01-11, Close, 26.49
2000-01-12, Close, 24.90
2000-01-13, Close, 24.77
2000-01-13, SELL CREATE, 24.77
2000-01-14, SELL EXECUTED, 25.70
...
...
...
2000-12-15, SELL CREATE, 26.93
2000-12-18, SELL EXECUTED, 28.29
2000-12-18, Close, 30.18
2000-12-19, Close, 28.88
2000-12-20, Close, 26.88
2000-12-20, BUY CREATE, 26.88
2000-12-21, BUY EXECUTED, 26.23
2000-12-21, Close, 27.82
2000-12-22, Close, 30.06
2000-12-26, Close, 29.17
2000-12-27, Close, 28.94
2000-12-28, Close, 29.29
2000-12-29, Close, 27.41
Final Portfolio Value: 100018.53
现在,系统确实赚到了钱,恭喜我们又进了一步。
11 11交易监控.txt ##################################################
文件路径: 11交易监控.txt
================================================================================
我们已经知道如何使用 backtrader 买卖交易了。本节将介绍 backtrader 如何监控它每笔交易,如成本、利润和佣金。由于佣金的存在,利润还分为毛利润和净利润
设置佣金#
让我们先设置一个合理佣金率 - 0.1% (买入和卖出都要收取的),一行代码即可。
# 0.1% ... 除以 100 以去掉百分号
cerebro.broker.setcommission(commission=0.001)
订单的成本和佣金#
订单的成本和佣金可从订单回调 notify_order 中获取,它的 Order 参数 order.executed.comm 即为订单的已执行佣金,order.executed.value 即投入的成本。
class TestStrategy(bt.Strategy):
def notify_order(self, order):
if order.status in [order.Completed]:
if order.isbuy():
self.log(
'买入执行,价格:%.2f,成本:%.2f,佣金 %.2f' %
(order.executed.price,
order.executed.value,
order.executed.comm))
else:
self.log('卖出执行,价格:%.2f,成本:%.2f,佣金 %.2f' %
(order.executed.price,
order.executed.value,
order.executed.comm))
交易记录计算利润#
对于利润的计算,使用交易记录会更简单,所谓成交记录,即成交撮合一笔记录一次。与订单类似,我们可通过 notify_trade 获取成交记录。它的回调参数是 Trade 类对象,利润相关属性有 trade.pnl(毛利润)和 trade.pnlcomm(净利润)。
class TestStrategy(bt.Strategy):
def notify_trade(self, trade):
if not trade.isclosed:
return
self.log('利润记录,毛利润 %.2f,净利润 %.2f' %
(trade.pnl, trade.pnlcomm))
因为只有是平仓交易,才有利润的说法,故通过 trade.isclosed 判断在平仓交易的情况才输出利润信息。
完整示例#
import datetime
import os.path
import sys
import backtrader as bt
class TestStrategy(bt.Strategy):
def log(self, txt, dt=None):
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
def notify_order(self, order):
if order.status in [order.Submitted, order.Accepted]:
return
if order.status in [order.Completed]:
if order.isbuy():
self.log(
'买入执行,价格:%.2f,成本:%.2f,佣金 %.2f' %
(order.executed.price,
order.executed.value,
order.executed.comm))
self.buyprice = order.executed.price
self.buycomm = order.executed.comm
else:
self.log('卖出执行,价格:%.2f,成本:%.2f,佣金 %.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('订单取消/保证金不足/拒绝')
self.order = None
def notify_trade(self, trade):
if not trade.isclosed:
return
self.log('利润记录,毛利润 %.2f,净利润 %.2f' %
(trade.pnl, trade.pnlcomm))
def next(self):
self.log('收盘价,%.2f' % self.dataclose[0])
if self.order:
return
if not self.position:
if self.dataclose[0] < self.dataclose[-1]:
if self.dataclose[-1] < self.dataclose[-2]:
self.log('创建买入订单,%.2f' % self.dataclose[0])
self.order = self.buy()
else:
if len(self) >= (self.bar_executed + 5):
self.log('创建卖出订单,%.2f' % self.dataclose[0])
self.order = self.sell()
if __name__ == '__main__':
cerebro = bt.Cerebro()
cerebro.addstrategy(TestStrategy)
modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
datapath = os.path.join(modpath, './datas/orcl-1995-2014.txt')
data = bt.feeds.YahooFinanceCSVData(
dataname=datapath,
fromdate=datetime.datetime(2000, 1, 1),
todate=datetime.datetime(2000, 12, 31),
reverse=False)
cerebro.adddata(data)
cerebro.broker.setcash(100000.0)
cerebro.broker.setcommission(commission=0.001)
print('初始投资组合价值:%.2f' % cerebro.broker.getvalue())
cerebro.run()
print('最终投资组合价值:%.2f' % cerebro.broker.getvalue())
执行后的输出是:
初始投资组合价值:100000.00
2000-01-03T00:00:00, 收盘价,27.85
2000-01-04T00:00:00, 收盘价,25.39
2000-01-05T00:00:00, 收盘价,24.05
2000-01-05T00:00:00, 创建买入订单,24.05
2000-01-06T00:00:00, 买入执行,价格:23.61,成本:23.61,佣金 0.02
2000-01-06T00:00:00, 收盘价,22.63
2000-01-07T00:00:00, 收盘价,24.37
2000-01-10T00:00:00, 收盘价,27.29
2000-01-11T00:00:00, 收盘价,26.49
2000-01-12T00:00:00, 收盘价,24.90
2000-01-13T00:00:00, 收盘价,24.77
2000-01-13T00:00:00, 创建卖出订单,24.77
2000-01-14T00:00:00, 卖出执行,价格:25.70,成本:25.70,佣金 0.03
2000-01-14T00:00:00, 利润记录,毛利润 2.09,净利润 2.04
2000-01-14T00:00:00
相关技能
小果(微信:xg_quant)股票量化分析助手专注于股票量化分析工具,基于小果量化策略系统,提供股票历史行情数据、股票分钟数据、股票因子数据、股票财务数据、指数数据、 股票策略回测、股票组合分析等核心功能。 适用于股票投资者、量化研究员和策略开发者。 触发关键词:股票量化、股票回测、股票分析、股票策略、股票数据、分钟数据、财务数据、指数数据。
小果(微信xg_quant)量化交易Ptrade全能助手基于 Ptrade 量化交易平台的一站式策略辅助工具。 支持策略回测参数配置、Python 策略代码自动生成、交易日志深度分析、 实盘交易信号指导、绩效归因与风险评估。
小果(微信:xg_quant)基金量化分析助手专注于基金量化分析工具,基于小果量化策略系统,提供基金历史行情数据、基金因子数据、基金策略回测、基金组合分析等核心功能。适用于基金投资者、资产配置研究员和量化策略开发者。
小果全能大QMT量化交易助手 - 专业级QMT策略开发全能助手 本助手是专为「迅投QMT极速策略交易系统」打造的量化交易策略开发专家。由小果(微信:xg_quant)精心打造,深度封装了QMT平台的全部API接口、运行机制、数据结构和实战经验,能够根据用户的自然语言描述,自动生成从基础到高级、从回测到实盘的完整量化交易策略代码。 【核心功能矩阵】 ✅ 策略开发:根据需求描述自动生成QMT策略代码,支持股票/期货/期权/ETF/可转债/两融等多品种 ✅ 策略回测:生成完整的回测模型代码,包含历史数据获取、撮合规则、绩效分析 ✅ 实盘交易:生成实盘模型代码,支持逐K线生效和立即下单两种模式 ✅
小果量化因子分析系统助手,作者(微信:xg_quant)基于 Alphalens 和自定义分析框架,提供因子数据清洗、IC分析、分层收益、多空组合、换手率、Fama-MacBeth回归、综合评分等完整因子评价功能。 触发关键词:因子分析、因子评价、Alphalens、IC分析、分层收益、多空组合、因子回测、因子评分、xg_alphalens、量化因子、因子有效性。