📌 摘要 / 快速解答 (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 支持一下。

