$npx -y skills add lzwme/finance-quant-skills --skill backtraderBacktrader 开源量化回测框架,支持多数据源、多策略、多周期回测与实盘交易,纯Python实现。当用户需要开发量化策略、进行回测分析、编写交易逻辑、回测参数优化,或提及 backtrader、量化回测框架时使用。若用户仅需数据获取而无回测需求,引导使用 baostock/akshare/tushare 等数据 Skill。
| 1 | # Backtrader(开源量化回测框架) |
| 2 | |
| 3 | [Backtrader](https://github.com/mementum/backtrader) 是一个开源 Python 量化回测框架。采用事件驱动架构,核心组件包括:**Cerebro**(引擎)、**Strategy**(策略类)、**Data Feed**(数据源)、**Broker**(经纪商)、**Indicator**(内置 100+ 技术指标)、**Analyzer**(绩效分析)。纯 Python 实现,无外部依赖,适合离线研究。 |
| 4 | |
| 5 | > 官方文档:https://www.backtrader.com/docu/ |
| 6 | |
| 7 | ## 安装 |
| 8 | |
| 9 | ```bash |
| 10 | pip install backtrader |
| 11 | # 如需绘图 |
| 12 | pip install backtrader[plotting] |
| 13 | # 或 |
| 14 | pip install matplotlib |
| 15 | ``` |
| 16 | |
| 17 | ## 最简示例 |
| 18 | |
| 19 | ```python |
| 20 | import backtrader as bt |
| 21 | |
| 22 | class MyStrategy(bt.Strategy): |
| 23 | """简单均线策略""" |
| 24 | params = (('period', 20),) # 策略参数:均线周期 |
| 25 | |
| 26 | def __init__(self): |
| 27 | # 初始化指标(在__init__中定义,自动计算) |
| 28 | self.sma = bt.indicators.SimpleMovingAverage(self.data.close, period=self.params.period) |
| 29 | |
| 30 | def next(self): |
| 31 | # 每根K线触发一次,在此编写交易逻辑 |
| 32 | if self.data.close[0] > self.sma[0]: |
| 33 | if not self.position: # 无持仓则买入 |
| 34 | self.buy() |
| 35 | elif self.data.close[0] < self.sma[0]: |
| 36 | if self.position: # 有持仓则卖出 |
| 37 | self.sell() |
| 38 | |
| 39 | # 创建引擎 |
| 40 | cerebro = bt.Cerebro() |
| 41 | cerebro.addstrategy(MyStrategy) |
| 42 | |
| 43 | # 加载数据(Yahoo CSV格式) |
| 44 | data = bt.feeds.YahooFinanceCSVData(dataname='stock_data.csv') |
| 45 | cerebro.adddata(data) |
| 46 | |
| 47 | # 设置初始资金 |
| 48 | cerebro.broker.setcash(100000.0) |
| 49 | # 设置手续费 |
| 50 | cerebro.broker.setcommission(commission=0.001) |
| 51 | |
| 52 | # 运行回测 |
| 53 | print(f'初始资金: {cerebro.broker.getvalue():.2f}') |
| 54 | cerebro.run() |
| 55 | print(f'最终资金: {cerebro.broker.getvalue():.2f}') |
| 56 | |
| 57 | # 绘制结果 |
| 58 | cerebro.plot() |
| 59 | ``` |
| 60 | |
| 61 | |
| 62 | ## 数据源 |
| 63 | |
| 64 | ### 从Pandas DataFrame加载 |
| 65 | |
| 66 | ```python |
| 67 | import backtrader as bt |
| 68 | import pandas as pd |
| 69 | |
| 70 | # 从CSV读取数据 |
| 71 | df = pd.read_csv('stock_data.csv', parse_dates=['date'], index_col='date') |
| 72 | # DataFrame必须包含列: open, high, low, close, volume(小写列名) |
| 73 | |
| 74 | data = bt.feeds.PandasData(dataname=df) |
| 75 | cerebro.adddata(data) |
| 76 | ``` |
| 77 | |
| 78 | ### 从CSV文件加载 |
| 79 | |
| 80 | ```python |
| 81 | # 通用CSV格式 |
| 82 | data = bt.feeds.GenericCSVData( |
| 83 | dataname='stock_data.csv', |
| 84 | dtformat='%Y-%m-%d', # 日期格式 |
| 85 | datetime=0, # 日期列索引 |
| 86 | open=1, # 开盘价列索引 |
| 87 | high=2, # 最高价列索引 |
| 88 | low=3, # 最低价列索引 |
| 89 | close=4, # 收盘价列索引 |
| 90 | volume=5, # 成交量列索引 |
| 91 | openinterest=-1 # 持仓量列索引(-1表示无此列) |
| 92 | ) |
| 93 | cerebro.adddata(data) |
| 94 | ``` |
| 95 | |
| 96 | ### 多股票 / 多周期 |
| 97 | |
| 98 | ```python |
| 99 | # 加载多只股票数据 |
| 100 | data1 = bt.feeds.PandasData(dataname=df1, name='stock1') |
| 101 | data2 = bt.feeds.PandasData(dataname=df2, name='stock2') |
| 102 | cerebro.adddata(data1) |
| 103 | cerebro.adddata(data2) |
| 104 | |
| 105 | # 在策略中访问多只股票 |
| 106 | class MultiStockStrategy(bt.Strategy): |
| 107 | def __init__(self): |
| 108 | # self.datas[0]是第一只股票,self.datas[1]是第二只 |
| 109 | self.sma1 = bt.indicators.SMA(self.datas[0].close, period=20) |
| 110 | self.sma2 = bt.indicators.SMA(self.datas[1].close, period=20) |
| 111 | |
| 112 | def next(self): |
| 113 | for i, d in enumerate(self.datas): |
| 114 | print(f'{d._name}: close={d.close[0]:.2f}') |
| 115 | ``` |
| 116 | |
| 117 | ### 数据重采样(分钟线转日线) |
| 118 | |
| 119 | ```python |
| 120 | # 加载分钟数据 |
| 121 | data_min = bt.feeds.GenericCSVData(dataname='1min_data.csv', timeframe=bt.TimeFrame.Minutes) |
| 122 | cerebro.adddata(data_min) |
| 123 | |
| 124 | # 重采样为日线 |
| 125 | cerebro.resampledata(data_min, timeframe=bt.TimeFrame.Days) |
| 126 | ``` |
| 127 | |
| 128 | |
| 129 | |
| 130 | ## 策略类详解 |
| 131 | |
| 132 | ### 策略参数 |
| 133 | |
| 134 | ```python |
| 135 | class MyStrategy(bt.Strategy): |
| 136 | # 定义可调参数(元组格式) |
| 137 | params = ( |
| 138 | ('fast_period', 5), # 快速均线周期 |
| 139 | ('slow_period', 20), # 慢速均线周期 |
| 140 | ('stake', 100), # 每次交易手数 |
| 141 | ) |
| 142 | |
| 143 | def __init__(self): |
| 144 | self.fast_ma = bt.indicators.SMA(period=self.p.fast_period) |
| 145 | self.slow_ma = bt.indicators.SMA(period=self.p.slow_period) |
| 146 | # self.p 是 self.params 的简写 |
| 147 | |
| 148 | def next(self): |
| 149 | if self.fast_ma[0] > self.slow_ma[0]: |
| 150 | self.buy(size=self.p.stake) |
| 151 | |
| 152 | # 参数可在运行时覆盖 |
| 153 | cerebro.addstrategy(MyStrategy, fast_period=10, slow_period=30) |
| 154 | ``` |
| 155 | |
| 156 | ### 交易方法 |
| 157 | |
| 158 | ```python |
| 159 | class MyStrategy(bt.Strategy): |
| 160 | def next(self): |
| 161 | # 按数量买入 |
| 162 | self.buy(size=100) # 买入100股 |
| 163 | self.sell(size=100) # 卖出100股 |
| 164 | |
| 165 | # 调整到目标仓位 |
| 166 | self.order_target_size(target=500) # 调整持仓为500股 |
| 167 | self.order_target_value(target=50000) # 调整持仓为5万元市值 |
| 168 | self.order_target_percent(target=0.5) # 调整持仓为总资产的50% |
| 169 | |
| 170 | # 限价单 |
| 171 | self.buy(size=100, price=10.5, exectype=bt.Order.Limit) |
| 172 | # 止损单 |
| 173 | self.sell(size=100, price=9.0, exectype=bt.Order.Stop) |
| 174 | # 止损限价单 |
| 175 | self.buy(size=100, price=10.5, pricelimit=10.8, exectype=bt.Order.StopLimit) |
| 176 | |
| 177 | # 撤单 |
| 178 | order = self.buy(size=100) |
| 179 | self.cancel(order) |
| 180 | |
| 181 | # 对其他股票下单 |
| 182 | self.buy(data=self.datas[1], size=200) # 买入第二只股票 |
| 183 | ``` |
| 184 | |
| 185 | ### 订单通知回调 |
| 186 | |
| 187 | ```python |
| 188 | class MyStrategy(bt.Strategy): |
| 189 | def notify_order(self, order): |
| 190 | """订单状态变化时触发""" |
| 191 | if order.status in [order.Submitted, order.Accepted]: |
| 192 | return # 订单已提交/已接受,等待执行 |
| 193 | |
| 194 | if order.status in [order.Completed]: |
| 195 | if order.isbuy(): |
| 196 | print(f'买入执行: 价格={order.executed.price:.2f}, ' |