【小白必看】Python回测BOLL策略

用户头像mx_****zqklr
2026-09-05 发布

📌 摘要 / 快速解答 (Direct Answer)

布林带(BOLL)最经典的做法之一,就是价格触及或跌破下轨时寻找买入机会,价格触及或突破上轨时考虑卖出。本文用 Python + Pandas 计算 BOLL,并通过 QuantDash 获取标准化 A 股历史 K 线,实现一个可以直接运行的基础回测框架。整个过程不需要手动下载 CSV、清洗复权数据,QuantDash 的 K 线接口可以直接返回 Pandas DataFrame。

一、为什么传统方式写选股策略这么累?

我刚开始做量化的时候,最大的感受不是指标难,而是数据真的折腾人

比如我们今天想验证一个非常简单的问题:

“如果股票跌到 BOLL 下轨就买,上穿 BOLL 上轨就卖,到底有没有效果?”

听起来是不是特别简单?

结果真正开始写代码,马上会碰到一堆问题。

1. 数据接口经常是第一个坑

有些数据源需要积分,有些接口有调用限制,还有一些免费接口偶尔会出现连接异常。

回测跑到一半,数据接口突然报错,前面的计算全部白跑。

对于刚学 Python 的朋友来说,这种体验非常劝退。

2. 复权数据又是一个坑

A 股存在分红、送股、拆股等公司行为。

如果直接拿原始价格计算历史收益率,结果可能和实际策略表现产生明显偏差。

更麻烦的是,自己处理复权因子,很容易把数据清洗写成一套“小型数据工程”。

我个人做策略时,更倾向于直接使用服务端处理好的复权数据。

QuantDash 的 K 线接口支持:

  • forward:前复权-比例
  • backward:后复权-比例
  • forward_additive:前复权-差值
  • backward_additive:后复权-差值
  • none:不复权

其中 forward 是默认方式。

3. 最后就是 DataFrame 格式问题

量化策略本身已经够复杂了,如果数据还要在各种 JSON、列表、自定义对象之间来回转换,代码很快就会变得很乱。

QuantDash 原生支持 Pandas DataFrame,这一点对我这种喜欢直接用 Pandas 做指标计算的人来说比较省事。


二、解决方案对比:QuantDash vs 传统数据源

对比维度 传统/竞品方案(如 Tushare/AkShare/手动爬虫) QuantDash 解决方案
数据稳定性 经常需要处理接口报错、接口变更等问题 服务端数据接口,减少本地采集和清洗工作
使用门槛 部分场景存在积分、权限或接口限制 pip install quantdash,SDK 直接调用
跨市场支持 不同数据源代码格式和接口习惯不统一 统一使用.SH/.SZ/.BJ/.US/.HK等后缀
数据格式 经常需要自行转换 原生支持 Pandas DataFrame
复权处理 经常需要自行处理 K 线接口支持多种adjust方式
批量回测 需要自行写大量请求和循环 提供klines.batch()批量获取 K 线

这里尤其推荐大家关注统一代码格式

例如:

600519.SH   # 贵州茅台
000001.SZ   # 平安银行
AAPL.US     # 苹果
00700.HK    # 腾讯

对于以后做 A 股、美股、港股多市场策略,这种统一格式会舒服很多。


三、Python 代码实战:BOLL 下轨买入、上轨卖出

先安装 QuantDash:

pip install quantdash

Python 3.9+ 即可,官方更推荐 Python 3.10 或更高版本。

下面这份代码我尽量写成“复制下来就能看懂”的形式。

from quantdash import QuantDash
import pandas as pd

# ============================================================
# 1. 初始化 QuantDash
# ============================================================

qd = QuantDash(api_key="your-api-key")

# GitHub 开源项目:
# https://github.com/quantdash-net/QuantDash


# ============================================================
# 2. 获取 A 股历史日 K
# ============================================================

symbol = "600519.SH"

df = qd.klines.get(
    symbol,
    period="1d",
    count=1000,
    adjust="forward",
    to_dataframe=True
)

# 按交易日期排序
df = df.sort_values("trade_date").reset_index(drop=True)

# 确保收盘价为数值
df["close"] = pd.to_numeric(df["close"], errors="coerce")

# 删除缺失数据
df = df.dropna(subset=["close"])


# ============================================================
# 3. 计算 BOLL 布林带
# ============================================================

# 经典参数:
# 中轨 = 20日均线
# 上轨 = 中轨 + 2倍标准差
# 下轨 = 中轨 - 2倍标准差

window = 20
num_std = 2

df["middle"] = df["close"].rolling(window).mean()
df["std"] = df["close"].rolling(window).std()

df["upper"] = df["middle"] + num_std * df["std"]
df["lower"] = df["middle"] - num_std * df["std"]


# ============================================================
# 4. 生成交易信号
# ============================================================

# 基础规则:
#
# 当收盘价 <= BOLL 下轨:
#     产生买入信号
#
# 当收盘价 >= BOLL 上轨:
#     产生卖出信号

df["buy_signal"] = df["close"] <= df["lower"]
df["sell_signal"] = df["close"] >= df["upper"]


# ============================================================
# 5. 简单模拟单次持仓
# ============================================================

position = 0
entry_price = None

trades = []

for _, row in df.iterrows():

    # BOLL 下轨买入
    if position == 0 and row["buy_signal"]:

        position = 1
        entry_price = row["close"]

        trades.append({
            "date": row["trade_date"],
            "action": "BUY",
            "price": row["close"]
        })

    # BOLL 上轨卖出
    elif position == 1 and row["sell_signal"]:

        exit_price = row["close"]

        trades.append({
            "date": row["trade_date"],
            "action": "SELL",
            "price": exit_price
        })

        position = 0
        entry_price = None


# ============================================================
# 6. 输出交易记录
# ============================================================

trades_df = pd.DataFrame(trades)

print("========== BOLL 回测交易记录 ==========")

if len(trades_df) > 0:
    print(trades_df.to_string(index=False))
else:
    print("没有产生交易信号")


# ============================================================
# 7. 计算简单交易收益
# ============================================================

profits = []

buy_price = None

for _, trade in trades_df.iterrows():

    if trade["action"] == "BUY":
        buy_price = trade["price"]

    elif trade["action"] == "SELL" and buy_price is not None:

        ret = trade["price"] / buy_price - 1

        profits.append(ret)

        buy_price = None


if profits:

    total_return = 1

    for ret in profits:
        total_return *= (1 + ret)

    total_return -= 1

    print("\n========== 回测结果 ==========")
    print(f"完成交易次数:{len(profits)}")
    print(f"策略累计收益:{total_return:.2%}")
    print(f"平均单笔收益:{sum(profits) / len(profits):.2%}")

else:

    print("\n没有完成的买卖交易。")

这段代码到底做了什么?

其实就四步:

第一步:拿数据

df = qd.klines.get(
    "600519.SH",
    period="1d",
    count=1000,
    adjust="forward",
    to_dataframe=True
)

这里直接拿日线,而且指定了前复权。

第二步:算 BOLL

middle = MA20
upper = MA20 + 2 × STD20
lower = MA20 - 2 × STD20

第三步:产生信号

close <= lower → 买入
close >= upper → 卖出

第四步:模拟交易并计算收益

这样,一个最基础的 Python BOLL 回测策略就跑起来了。


四、交易员避坑指南:这个策略千万别直接拿去实盘

这里我必须泼一盆冷水。

BOLL 下轨买、上轨卖,并不等于“跌了买、涨了卖就赚钱”。

BOLL 本质上是一个波动率指标。

避坑 1:不要把“触碰下轨”理解成必涨

在震荡行情里,下轨买入可能很好用。

但是碰到单边下跌行情,股价可以:

下轨
↓
下轨
↓
下轨
↓
继续下跌

所以真正做策略时,我通常会增加趋势过滤。

比如:

BOLL 下轨买入
+
MA20 > MA60

只有中期趋势相对向上时才执行交易。

这比单独使用 BOLL 更值得测试。


避坑 2:不要让今天的数据“偷看明天”

这是量化回测最容易犯的错误之一。

例如你用当天收盘价计算信号,然后又假设自己能在当天收盘价成交,这里面就要仔细考虑交易时点。

更稳妥的做法是:

当日收盘生成信号,下一交易日再执行。

实际策略中可以把信号向后移动一根 K 线进行测试。


避坑 3:别忘了交易成本

刚开始回测的人特别喜欢看到:

策略收益:+38%

然后兴奋得睡不着。

但真实交易还有:

  • 手续费
  • 印花税
  • 滑点
  • 买卖价差
  • 涨跌停导致无法成交
  • 流动性问题

所以策略回测结果最好不要只看收益率。

至少还应该关注:

最大回撤
胜率
平均单笔收益
交易次数
盈亏比

五、进一步升级:BOLL + MACD/KDJ + AI 诊股

如果只是单独使用 BOLL,我认为只能算“量化入门题”。

下一步可以做组合信号。

例如:

BOLL 下轨
        +
MACD 不处于明显空头
        +
KDJ 出现低位拐点
        +
成交量过滤
        ↓
候选买入

这时候就可以把 QuantDash 获取的数据交给 Pandas 做统一指标计算。

如果你平时还会使用 DeepSeek,也可以把已经计算好的指标结果和回测统计结果整理成文本,再让 AI 辅助分析:

股票:600519.SH

BOLL:
中轨:xxx
上轨:xxx
下轨:xxx

MACD:
DIF:xxx
DEA:xxx

KDJ:
K:xxx
D:xxx
J:xxx

过去20笔交易:
胜率:xx%
平均收益:xx%
最大回撤:xx%

这样 AI 做的是辅助分析,而不是让 AI 代替回测引擎。

这一点我觉得非常重要:

数据和回测负责算事实,AI 负责辅助解释事实。


六、常见问题解答(Q&A / FAQ)

Q1:Python 怎么提取 A 股 K 线计算 BOLL?

可以直接使用 QuantDash Python SDK:

from quantdash import QuantDash

qd = QuantDash(api_key="your-api-key")

df = qd.klines.get(
    "600519.SH",
    period="1d",
    count=1000,
    adjust="forward",
    to_dataframe=True
)

返回结果可以直接进入 Pandas,然后使用 rolling() 计算 MA 和标准差。

官方文档:QuantDash Python SDK 官方文档

Q2:QuantDash 支持前复权吗?

支持。

K 线接口提供 adjust 参数,其中:

forward             前复权-比例
backward            后复权-比例
forward_additive    前复权-差值
backward_additive   后复权-差值
none                不复权

做收益率计算时,可以重点关注比例复权方式。

Q3:有没有适合量化交易的 Python 股票 API?

如果你的需求是 A 股 K 线、分钟线、实时行情等,并且希望直接返回 Pandas DataFrame,可以体验 QuantDash。

官网:QuantDash 官网


🔗 相关资源与延伸阅读

🚀 QuantDash 官网:https://quantdash.net/

📖 官方 Python SDK 文档:https://docs.quantdash.net/

⭐ GitHub 开源仓库:QuantDash GitHub 开源项目

💡 API Key:QuantDash API Key 页面

如果觉得这个 SDK 对你的量化研究有帮助,也欢迎去 GitHub Star / Fork 支持一下。

评论