AI股票小助手06-Backtrader 量化回测

by 雪隐_上班了 from juejin.cn/user/143341...

欢迎分享与聚合,全文转载就不必了,尊重版权,圈子就这么大,若急用可联系授权。

1. 为什么要量化回测?(别只靠玄学)

在搞技术前,先回答一个灵魂拷问:你这交易策略,到底是真牛逼,还是你觉得它牛逼?

1.1 避免"马后炮"综合症

你是不是经常这样:看到一只股票涨了20%,一拍大腿:"我早就知道会涨!" 然后开始编十个理由,像极了考试后对答案------永远觉得自己选对了。

量化回测就是你的时光机 。它在每一个历史时间点上,只用当时已经发生的数据做决策,绝不偷看未来。没有未来函数,没有作弊,跟高考一样公平。

1.2 用数据说话,而不是靠直觉

一个交易想法,如果不经过几百只股票、几年数据的毒打,那就只是**"我觉得"**。量化回测能让你:

  • 几百只股票、好几年的行情里验证策略------相当于请历史当考官
  • 看到胜率、夏普比率、最大回撤等硬核指标------这些数字比你的第六感靠谱100倍
  • 区分策略是真能赚钱 ,还是刚好踩了狗屎运

1.3 参数调优?让电脑替你熬夜

手工交易时,想测试一下均线用10天还是20天好?你得画图、算账、掉头发。量化回测一秒跑完所有组合:

  • 快速比较不同参数下的表现,像选手机套餐一样简单
  • 发现策略在哪些市场环境下会扑街(这就是传说中的过拟合检测
  • 自动加上最大回撤止损等风控规则------亏到一定比例,策略自己喊停

1.4 从"我想赚钱"到"我知道策略能不能赚钱"

量化回测帮你回答一个终极问题:

在过去 X 年里,这个策略在 YY% 的市场环境下,有没有稳定盈利过?

如果一个策略在过去5年、各种熊市牛市里一直在亏钱,你还指望它未来帮你买房?别做梦了。


2. Backtrader 简介------回测界的瑞士军刀

Backtrader 是一个纯 Python 写的回测框架,界面简洁,设计模块化,就像乐高。相比其他回测工具,它有几个优点:

  • 设计相当直观 :核心概念叫 Cerebro(大脑),你往里面塞数据、策略、经纪商,它就能跑起来------跟指挥你的宠物狗去捡球一样自然
  • 策略就是一个类 :继承 bt.Strategy,在 __init__ 里定义指标,在 next() 里写买卖逻辑。会写类就会写策略
  • 内置一大堆分析器:夏普比率、最大回撤、交易统计......开箱即食,不用自己造轮子
  • 直接喂 Pandas DataFrame:不需要格式转换,DataFrame 塞进去就能跑,懒人福音

3. 核心概念------大脑、饲料、经纪人,还有你

3.1 Cerebro(大脑)------总指挥官

Cerebro 是 Backtrader 的总控,负责:

  • 加载数据源(你的 K 线饲料)
  • 添加策略(你的交易想法)
  • 设置经纪商(模拟券商,收你佣金)
  • 运行回测(点一下,开始跑)
  • 收集结果(赔了赚了,一目了然)
python 复制代码
cerebro = bt.Cerebro()
cerebro.addstrategy(MyStrategy)       # 把你的策略加进去
cerebro.adddata(bt.feeds.PandasData(dataname=df))  # 喂数据
cerebro.broker.setcash(1000000)       # 给你100万虚拟启动资金(羡慕吧)
cerebro.broker.setcommission(commission=0.0002) # 佣金万二,比某些真券商还便宜
cerebro.addsizer(bt.sizers.PercentSizer, percents=95)  # 每次用95%的资金梭哈(保守派可以改小)

results = cerebro.run()  # 3、2、1,发射!

3.2 Data Feed(数据源)------K 线的自助餐

Backtrader 支持 N 种数据源,最常用的就是 PandasData,直接把 pandas DataFrame 往里扔:

python 复制代码
cerebro.adddata(bt.feeds.PandasData(dataname=df))

DataFrame 的 index 必须是 datetime ,列名要叫 openhighlowclosevolume(大小写不敏感)。不按规矩来,它就不理你。

3.3 Strategy(策略)------你的摇钱树脚本

策略类继承 bt.Strategy,核心方法就四个:

方法 作用 吐个槽
__init__ 初始化指标,只执行一次 像买车,只买一次
next 每个 K 线执行一次 像开车,每根 K 线踩一脚
notify_order 订单状态变了就回调 "报告,你的挂单成交了!"
notify_trade 交易变了就回调 "报告,你的买卖亏了/赚了!"

关键属性:

  • self.data.close:收盘价序列(别拿它当数组用,它是特殊对象)
  • self.data.close[0]:最新收盘价(0表示当前,-1表示上一个)
  • self.position:当前持仓数量(亏光了就是0)
  • self.buy() / self.close():买入 / 卖出(别点反了)

3.4 Broker(经纪商)------雁过拔毛的中间商

经纪商模拟真实交易所,负责:

  • 记录你还有多少现金,持了多少股
  • 每次交易扣佣金(万恶的中间商)
  • 执行你的买卖订单(不滑点,但现实会滑)
python 复制代码
cerebro.broker.setcash(1000000)               # 初始资金100万(虚拟的,别激动)
cerebro.broker.setcommission(commission=0.0002)  # 佣金万分之二
cerebro.addsizer(bt.sizers.PercentSizer, percents=95)  # 每次拿95%的钱去买,留5%吃手续费

3.5 Analyzer(分析器)------赛后技术统计

回测跑完了,到底赚了没?赚了多少?亏了多惨?交给分析器:

python 复制代码
cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name="sharpe", riskfreerate=0.02)
cerebro.addanalyzer(bt.analyzers.DrawDown, _name="drawdown")
cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name="trades")

跑完之后,通过 strat.analyzers.sharpe.get_analysis() 提取,跟查考试成绩一样。


4. 数据加载------把 K 线喂给 Backtrader

4.1 数据来源:偷懒用 HTTP API

回测需要历史 K 线数据。我们直接用项目里的 005_QMT_server 提供的 HTTP API,省得自己去找数据(懒人必备)。

端点 说明
GET /api/history?code=600519&period=1d&start=2024-01-01&end=2025-12-31 获取贵州茅台的历史日K线

返回字段:dateopenhighlowclosevolumeamount。够用了,又不是做高频。

4.2 数据获取函数------用 requests 优雅地白嫖

python 复制代码
API_BASE = "http://127.0.0.1:8080"

def load_data(stock_code, start_date=None, end_date=None, count=500):
    """从 005 QMT API 获取 K 线数据(别把服务打挂了)"""
    url = f"{API_BASE}/api/history"
    params = {
        "code": stock_code,
        "period": "1d",
        "start": start_date or "",
        "end": end_date or "",
    }
    resp = requests.get(url, params=params, timeout=30)
    resp.raise_for_status()  # 出错了就直接报错,不藏着掖着
    data = resp.json()
    records = data.get("records", [])
    df = pd.DataFrame(records)
    df["date"] = pd.to_datetime(df["date"])
    df.set_index("date", inplace=True)
    df.sort_index(inplace=True)
    if count > 0 and len(df) > count:
        df = df.tail(count)  # 只取最近 N 根,免得电脑冒烟
    return df

4.3 DataFrame 格式要求------别让 Backtrader 闹脾气

PandasData 对 DataFrame 的格式有洁癖,必须长这样:

字段 说明 备注
index (datetime) 日期时间 必须是 datetime 类型,字符串它不认
open 开盘价 别填成收盘价
high 最高价 别小于 low
low 最低价 别大于 high
close 收盘价 最重要的列
volume 成交量 整数就行,不要求单位

5. 五种基础策略------从捡硬币到追涨杀跌

这里给你准备了五个经典策略,从"幼儿园级别"到"小学毕业"。你可以像吃自助餐一样挑着用。

5.1 双均线策略(DoubleMAStrategy)------金叉买,死叉卖,简单粗暴

原理:短期均线上穿长期均线就买,下穿就卖。没有比这更经典的了。

python 复制代码
class DoubleMAStrategy(bt.Strategy):
    params = (("fast", 10), ("slow", 30))

    def __init__(self):
        self.ma_fast = bt.indicators.SMA(self.data.close, period=self.p.fast)
        self.ma_slow = bt.indicators.SMA(self.data.close, period=self.p.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()

参数说明

  • fast=10:快速均线周期(10日,短线敏感)
  • slow=30:慢速均线周期(30日,长线稳重)

吐槽:这个策略在趋势行情里很爽,在震荡行情里会被来回打脸。打脸打多了就习惯了。

5.2 MACD 策略(MACDStrategy)------别人恐惧我贪婪,别人贪婪我......看 MACD

原理:MACD 线与信号线金叉买入,死叉卖出。高级一点的均线策略。

python 复制代码
class MACDStrategy(bt.Strategy):
    def __init__(self):
        self.macd = bt.indicators.MACD(
            self.data.close, period_me1=12, period_me2=26, period_signal=9
        )
        self.crossover = bt.indicators.CrossOver(self.macd.macd, self.macd.signal)

    def next(self):
        if not self.position:
            if self.crossover > 0:
                self.buy()
        elif self.crossover < 0:
            self.close()

MACD 参数

  • period_me1=12:快速 EMA(12 日)
  • period_me2=26:慢速 EMA(26 日)
  • period_signal=9:信号线(9 日)

吐槽:MACD 几乎人手一个,但用它赚到钱的人......嗯,你猜。

5.3 RSI 策略(RSIStrategy)------跌多了买,涨多了卖,像个老股民

原理:RSI 低于 30 算超卖,买!高于 70 算超买,卖!典型的均值回归思维。

python 复制代码
class RSIStrategy(bt.Strategy):
    params = ("period", 14), ("upper", 70), ("lower", 30)

    def __init__(self):
        self.rsi = bt.indicators.RSI(self.data.close, period=self.p.period)

    def next(self):
        if not self.position:
            if self.rsi < self.p.lower:
                self.buy()
        elif self.rsi > self.p.upper:
            self.close()

参数说明

  • period=14:RSI 周期(传统值 14)
  • lower=30:超卖线(低于 30 就捡便宜)
  • upper=70:超买线(高于 70 就止盈)

吐槽:这个策略在震荡市里如鱼得水,但在单边牛市中你会卖飞,在单边熊市中你会抄底抄在半山腰。人生总是有舍有得。

5.4 布林带策略(BBandsStrategy)------下轨买,上轨卖,像根皮筋

原理:价格碰到下轨(超卖)就买,碰到上轨(超买)就卖。也是均值回归。

python 复制代码
class BBandsStrategy(bt.Strategy):
    params = (("period", 20), ("devfactor", 2.0))

    def __init__(self):
        self.bbands = bt.indicators.BollingerBands(
            self.data.close, period=self.p.period, devfactor=self.p.devfactor
        )

    def next(self):
        if not self.position:
            if self.data.close < self.bbands.bot:
                self.buy()
        elif self.data.close > self.bbands.top:
            self.close()

参数说明

  • period=20:中轨均线周期(20 日)
  • devfactor=2.0:标准差倍数(2 倍,传统值)

布林带三轨

  • bbands.top:上轨 = 中轨 + 2σ
  • bbands.mid:中轨 = 20 日均线
  • bbands.bot:下轨 = 中轨 - 2σ

吐槽:布林带在震荡行情里很好用,但如果遇到趋势突破,价格会沿着上轨一直涨,你卖飞后会想打自己。所以很多人在突破上轨时反而追买------那是另一套玩法了。

5.5 动量策略(MomentumStrategy)------追涨杀跌,简单直接

原理:动量 > 0(价格比 N 天前高)就买,动量 < 0 就卖。典型的趋势跟踪。

python 复制代码
class MomentumStrategy(bt.Strategy):
    params = (("period", 10),)

    def __init__(self):
        self.mom = bt.indicators.Momentum(self.data.close, period=self.p.period)

    def next(self):
        if not self.position:
            if self.mom > 0:
                self.buy()
        elif self.mom < 0:
            self.close()

参数说明

  • period=10:动量计算周期(10 日)

动量计算:当前价格 - N 日前价格 > 0 即为正。

吐槽:这个策略在牛市里舒服得不要不要的,但在震荡市里会频繁止损------今天买,明天卖,像极了韭菜的日常。

5.6 策略注册表------点菜一样选策略

所有策略放到一个字典里,方便你按名字调用:

python 复制代码
STRATEGIES = {
    "double_ma": DoubleMAStrategy,
    "macd": MACDStrategy,
    "rsi": RSIStrategy,
    "bbands": BBandsStrategy,
    "momentum": MomentumStrategy,
}

用的时候:

python 复制代码
strat_cls = STRATEGIES.get(strategy_name, DoubleMAStrategy)
cerebro.addstrategy(strat_cls)

6. 策略轮动(ADX 市场 Regime 切换)------打不过就加入,市场变你也变

单一策略就像一把锤子,看什么都像钉子。然而市场有时是趋势,有时是震荡------你需要一个工具箱

6.1 核心思想------看菜吃饭,量体裁衣

市场状态 ADX 值 应该用的策略 策略类型
趋势市 ADX > 25 MACD 趋势跟踪(顺势而为)
震荡市 ADX ≤ 25 RSI 均值回归(高抛低吸)

ADX 就是市场的"狂躁指数":值越高,趋势越猛;值越低,市场在打酱油。

6.2 RotationStrategy 实现------一个策略统治所有市场

python 复制代码
class RotationStrategy(bt.Strategy):
    params = (
        ("adx_period", 14),
        ("adx_threshold", 25),
        ("rsi_period", 14),
        ("rsi_lower", 35),
        ("rsi_upper", 65),
        ("max_drawdown_stop", 0.08),
        ("grace_bars", 5),
        ("position_pct", 0.50),
    )

参数说明(别怕,一个一个来):

  • adx_period=14:ADX 计算周期(14 天,传统值)
  • adx_threshold=25:趋势与震荡的分界线(ADX > 25 算趋势)
  • rsi_lower=35 / rsi_upper=65:放宽的 RSI 阈值(相比基础版的 30/70,更温和一点)
  • max_drawdown_stop=0.08:最大回撤止损 8% ------ 亏 8% 就强制离场
  • grace_bars=5:买入后前 5 天不检查回撤(防止刚买就遇到小波动误触)
  • position_pct=0.50:每次最多用 50% 的资金(不梭哈,活得久)

6.3 ADX 指标------市场的"情绪温度计"

ADX(Average Directional Index)只告诉你趋势强不强,不告诉你方向。

  • ADX 上升 → 趋势在加强(无论涨跌)
  • ADX 下降 → 市场在震荡
python 复制代码
self.adx = bt.indicators.ADX(period=self.p.adx_threshold)

6.4 最大回撤止损------保住你的韭菜根

next() 里实时监控账户总值的回撤:

python 复制代码
current_value = self.broker.getvalue()
if current_value > self.max_value:
    self.max_value = current_value

if self.bars_held >= self.p.grace_bars:
    drawdown = (self.max_value - current_value) / self.max_value
    if drawdown >= self.p.max_drawdown_stop:
        self.stopped = True
        if self.position:
            self.close()

逻辑:记录持仓期间的最高市值,过了保护期后,如果从最高点回撤达到 8%,立刻清仓并停止后续交易。这叫"断臂求生",总比全亏完强

6.5 完整交易逻辑------人话版

python 复制代码
def next(self):
    # 1. 更新最高市值
    current_value = self.broker.getvalue()
    if current_value > self.max_value:
        self.max_value = current_value

    # 2. 检查是否需要止损
    if self.stopped:
        return
    if self.bars_held >= self.p.grace_bars:
        drawdown = (self.max_value - current_value) / self.max_value
        if drawdown >= self.p.max_drawdown_stop:
            self.stopped = True
            if self.position:
                self.close()
            return

    # 3. 判断当前市场状态
    adx_val = self.adx[0]
    trend_mode = adx_val > self.p.adx_threshold

    # 4. 如果空仓:根据市场状态选择开仓策略
    if not self.position:
        self.bars_held = 0
        if trend_mode:
            # 趋势市 → 用 MACD 金叉买入
            if self.crossover_macd > 0:
                size = int((self.broker.getvalue() * self.p.position_pct) / self.data.close[0])
                if size > 0:
                    self.buy(size=size)
        else:
            # 震荡市 → 用 RSI 超卖买入
            if self.rsi < self.p.rsi_lower:
                size = int((self.broker.getvalue() * self.p.position_pct) / self.data.close[0])
                if size > 0:
                    self.buy(size=size)
    # 5. 如果持仓:根据市场状态决定平仓
    else:
        self.bars_held += 1
        if trend_mode:
            # 趋势市 → MACD 死叉卖出
            if self.crossover_macd < 0:
                self.close()
        else:
            # 震荡市 → RSI 超买卖出
            if self.rsi > self.p.rsi_upper:
                self.close()

7. 运行回测与结果解读------是骡子是马拉出来遛遛

7.1 运行单策略回测

打开终端,输入一行命令:

bash 复制代码
python scripts/run_backtest.py 600519 2024-01-01 2025-12-31 macd

格式:股票代码 开始日期 结束日期 策略名

可选策略:double_mamacdrsibbandsmomentum

7.2 运行策略轮动回测

bash 复制代码
python scripts/run_rotation_backtest.py 600519 2022-01-01 2025-12-31 --max-dd 0.08 --pos-pct 0.50

可选参数:

  • --max-dd:最大回撤止损比例,默认 0.08(8%)
  • --pos-pct:仓位上限,默认 0.50(50%)

7.3 输出结果示例------看看你是赚是亏

json 复制代码
{
  "stock_code": "600519",
  "strategy": "macd",
  "start_date": "2024-01-02",
  "end_date": "2025-12-31",
  "trading_days": 489,
  "initial_cash": 1000000,
  "final_value": 1250000.00,
  "total_return": 0.25,
  "annual_return": 0.1175,
  "max_drawdown": -0.082,
  "sharpe_ratio": 1.35,
  "total_trades": 12,
  "win_rate": 0.6667
}

7.4 关键指标解读------这些数字代表什么意思?

指标 说明 怎样算好?
total_return 总收益率(25% 就是说 100 万变 125 万) 越高越好,但别贪心
annual_return 年化收益率(把总收益换算到每年) 比银行定期高就算及格
max_drawdown 最大回撤(历史上最多亏了多少) -8% 比 -30% 舒服多了
sharpe_ratio 夏普比率(每承担一单位风险赚多少超额收益) >1 优秀,>2 牛逼
total_trades 总交易次数 太多可能手续费爆炸
win_rate 胜率(赚钱的交易占比) 高胜率不一定赚钱,还要看盈亏比
stopped_early 是否触发了止损 触发了说明风控起了作用,好事

7.5 回测框架流程图------一张图看懂全过程

scss 复制代码
run_backtest()
├── load_data()         → 从 API 白嫖 K 线数据
├── Cerebro 配置
│   ├── addstrategy()   → 把你选好的策略塞进去
│   ├── adddata()       → 把数据塞进去
│   ├── broker.setcash() → 给你虚拟资金(随便写,反正不是真的)
│   ├── broker.setcommission() → 让中间商赚点差价(佣金)
│   ├── addsizer()      → 决定每次下注多大
│   └── addanalyzer()   → 装好赛后统计工具
├── cerebro.run()       → 点火,开跑!
└── 提取分析结果       → 输出 JSON,高兴或悲伤

附录:策略参数一览(方便你抄作业)

策略 参数 默认值 说明
DoubleMAStrategy fast 10 快速均线周期(越短越敏感)
slow 30 慢速均线周期(越长越迟钝)
MACDStrategy period_me1 12 快速 EMA 周期
period_me2 26 慢速 EMA 周期
period_signal 9 信号线周期
RSIStrategy period 14 RSI 周期
upper 70 超买线(卖点)
lower 30 超卖线(买点)
BBandsStrategy period 20 布林带中轨周期
devfactor 2.0 标准差倍数(轨道宽度)
MomentumStrategy period 10 动量周期
RotationStrategy adx_period 14 ADX 周期
adx_threshold 25 趋势分界线(>25 算趋势)
rsi_lower 35 震荡市买入 RSI 阈值(更宽松)
rsi_upper 65 震荡市卖出 RSI 阈值
max_drawdown_stop 0.08 最大回撤止损线(8%)
grace_bars 5 买入后保护期(5 根 K 线)
position_pct 0.50 单次最大仓位(50%)

写在最后

这篇文章如果对你有帮助,请点赞 + 评论 ,让我知道你没有被 K 线折磨死。

如果有哪里写得不够风趣,欢迎吐槽------毕竟,亏钱的路上有你,我就没那么孤独了。😄

Happy backtesting! 愿你回测暴富,实盘不亏。

相关推荐
水木流年追梦1 小时前
大模型入门-大模型优化方法13- MTP 多 token 输出、DCA 双块注意力
人工智能·分布式·算法·正则表达式·prompt
蓝桉~MLGT1 小时前
语音陪伴助手
人工智能·语音识别
数据皮皮侠1 小时前
全国消协智慧 315 平台投诉信息数据库
大数据·人工智能·算法·百度·制造
设计师小聂!1 小时前
Java异常处理
java·开发语言·后端·编辑器·idea
ihuyigui1 小时前
国际商超零售短信接口
大数据·前端·后端·架构·零售
SimonKing1 小时前
实用,DynamicTP进阶之数据采集与告警
java·后端·程序员
ting94520001 小时前
Fundraisly 融资定向 AI 智能体全栈技术深度剖析
人工智能·架构
Aqoo1 小时前
AI抢工作这笔账终于有人认真算了
人工智能·openai
路人甲3261 小时前
SONIC: Supersizing Motion Tracking for Natural Humanoid Whole-Body Control
人工智能·深度学习·计算机视觉·机器人·具身智能