【小白必看】均值回归如何找超跌股

用户头像sh_***416jmt75L
2026-09-05 发布

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

均值回归策略最核心的问题不是“跌了以后会不会涨”,而是先把“什么叫超跌”定义清楚,再用历史样本统计反弹概率。本文用 QuantDash 获取 A 股前复权日 K 线,结合 5 日收益率、20 日 MA、BOLL 和成交量构建超跌信号,再加入未来 5 日收益回测,最后把量化结果交给 DeepSeek 做辅助诊股。

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

我发现很多新手做量化有一个误区:

把 80% 的时间花在找数据,20% 的时间研究策略。

这其实挺可惜。

比如你想验证一个非常简单的问题:

“A 股连续大跌以后,未来 5 天是不是更容易反弹?”

策略逻辑本身非常简单。

但你真开始做,会马上遇到:

股票池从哪里来?
↓
历史 K 线怎么批量拿?
↓
复权怎么办?
↓
不同股票数据格式怎么办?
↓
数据缺失怎么办?
↓
5000 多只股票怎么循环?
↓
最后怎么统一计算?

数据工程直接把策略研究的兴趣磨没了。

所以我的思路一直比较简单:

数据接口标准化,策略逻辑自己写。

QuantDash 的 Python SDK 可以直接安装:

pip install quantdash

K 线支持日、周、月等周期,分钟数据也提供 1m、5m、15m、30m、60m 等粒度;同时可以直接输出 Pandas DataFrame。


二、解决方案对比:做 A 股均值回归到底差在哪里?

对比维度 传统/竞品方案 QuantDash 解决方案
A 股股票池 经常需要自己维护 CN_Stock标的池
历史 K 线 接口、格式需要适配 qd.klines.get()
批量数据 自己设计批量下载 qd.klines.batch()
Python 数据格式 经常需要转换 直接to_dataframe=True
复权 可能需要自行处理 adjust参数直接选择
多市场 不同市场需要不同处理 A 股、美股、港股统一代码体系
分钟数据 数据源之间接口差异大 官方 SDK 提供统一 K 线接口

QuantDash 官方网站目前明确定位为面向策略研发和实盘交易的数据 API,并提供历史/实时行情、日内分时、五档盘口和多市场数据。


三、Python 代码实战:从“超跌”到“反弹胜率”

这一版我把策略稍微升级一点。

我们不单纯看跌幅,而是使用:

5日跌幅
+
20日MA
+
20日BOLL
+
成交量

构造一个简单的超跌因子。


1. 初始化 QuantDash

# pip install quantdash

from quantdash import QuantDash
import pandas as pd

# 推荐:
# export QUANTDASH_API_KEY="your_api_key"

qd = QuantDash()

如果不想使用环境变量,也可以:

qd = QuantDash(api_key="your_api_key")

官方 SDK 两种初始化方式都支持。


2. 获取 A 股股票池

quotes = qd.quotes.get(
    universes=["CN_Stock"],
    to_dataframe=True
)

symbols = (
    quotes["symbol"]
    .dropna()
    .drop_duplicates()
    .tolist()
)

print(f"A股股票数量:{len(symbols)}")

如果只是第一次测试,我建议先跑:

symbols = symbols[:100]

确认逻辑没问题以后,再扩大样本。

这一步非常重要。

别上来就拿全市场跑。


3. 批量获取历史 K 线

dfs = qd.klines.batch(
    symbols,
    period="1d",
    count=300,
    adjust="forward",
    to_dataframe=True,
    show_progress=True
)

这里使用前复权比例方式。

原因很简单:

我们后面需要比较:

买入价格
vs
未来卖出价格

官方文档对不同复权方式有明确区分,其中 forward 是前复权比例方式,适合研究收益率变化;如果你研究绝对价差,则可以进一步比较加法复权方式。


4. 构造均值回归指标

def build_signal(df):
    df = df.copy()

    df = df.sort_values("trade_date")
    df = df.reset_index(drop=True)

    # 5日价格变化
    df["ret_5d"] = df["close"].pct_change(5)

    # 20日均线
    df["ma20"] = df["close"].rolling(20).mean()

    # 20日标准差
    df["std20"] = df["close"].rolling(20).std()

    # BOLL
    df["boll_upper"] = df["ma20"] + 2 * df["std20"]
    df["boll_lower"] = df["ma20"] - 2 * df["std20"]

    # 20日平均成交量
    df["volume_ma20"] = df["volume"].rolling(20).mean()

    # 超跌信号
    df["oversold"] = (
        (df["ret_5d"] <= -0.10) &
        (df["close"] < df["boll_lower"]) &
        (df["volume"] >= df["volume_ma20"])
    )

    return df

这里加成交量,是为了把:

“单纯价格下跌”

和:

“价格明显下跌,同时成交量放大”

区分开。

但注意:

成交量放大不等于一定反弹。

它只是一个额外条件。


5. 回测未来 5 日反弹

def backtest(df):
    df = build_signal(df)

    trades = []

    for i in range(len(df) - 5):

        if not df.loc[i, "oversold"]:
            continue

        # T日收盘产生信号
        # T+1日开盘买入
        entry = df.loc[i + 1, "open"]

        # T+5日收盘卖出
        exit_price = df.loc[i + 5, "close"]

        if pd.isna(entry) or pd.isna(exit_price):
            continue

        ret = exit_price / entry - 1

        trades.append({
            "signal_date": df.loc[i, "trade_date"],
            "entry_price": entry,
            "exit_price": exit_price,
            "return_5d": ret,
            "win": ret > 0
        })

    return pd.DataFrame(trades)

这里的逻辑我建议大家重点看:

entry = df.loc[i + 1, "open"]

而不是:

entry = df.loc[i, "close"]

因为前者更符合:

今天收盘发现信号 → 下一交易日才执行

这个思路。


6. 汇总所有股票

results = []

for symbol, df in dfs.items():

    if df.empty:
        continue

    trades = backtest(df)

    if trades.empty:
        continue

    trades["symbol"] = symbol
    results.append(trades)

if not results:
    print("没有产生超跌信号。")
else:
    results = pd.concat(results, ignore_index=True)

    print(f"总信号数:{len(results)}")

    print(
        f"反弹胜率:"
        f"{results['win'].mean():.2%}"
    )

    print(
        f"平均5日收益:"
        f"{results['return_5d'].mean():.2%}"
    )

    print(
        f"中位数5日收益:"
        f"{results['return_5d'].median():.2%}"
    )

我特别建议大家同时看:

胜率
平均收益
中位数收益

因为单看胜率很容易被误导。


四、进一步升级:MACD / KDJ + DeepSeek AI 诊股

做到这里,其实已经可以继续玩了。

例如把超跌池进一步过滤:

5日跌幅 ≤ -10%
+
BOLL 下轨
+
MACD 是否出现底部拐点
+
KDJ 是否处于低位
+
成交量变化

不过这里我建议分两层。

第一层:机器负责筛选

例如:

5000+ A股
↓
5日跌幅筛选
↓
BOLL筛选
↓
成交量筛选
↓
MACD/KDJ筛选
↓
剩下几十只

第二层:AI 负责解释

把最终候选股的数据整理成:

股票:
600519.SH

5日收益:
-11.8%

20日均线:
xxx

当前价格:
xxx

BOLL下轨:
xxx

成交量/20日均量:
1.62

MACD:
xxx

KDJ:
xxx

然后让 DeepSeek 做辅助分析,例如:

“根据给出的历史价格指标,分析这只股票当前属于技术性超跌、趋势性破位还是可能出现均值回归。请分别列出支持反弹和不支持反弹的证据,不要直接给出买卖建议。”

这种方式比直接问:

“DeepSeek,明天什么股票涨?”

靠谱得多。

AI 是分析器,不应该替代回测。


五、交易员避坑指南

避坑 1:不要把“超跌”当成“便宜”

这是均值回归最大的坑。

股票跌 20%:

可能是错杀

也可能是:

基本面发生永久变化

所以:

价格超跌 ≠ 股票便宜。

均值回归策略最怕接飞刀。


避坑 2:参数不要调到“历史最优”

比如你回测:

5日跌幅 ≤ -11.37%
BOLL = 2.13倍标准差
持有 = 4天

结果特别漂亮。

别高兴太早。

很可能只是:

过拟合。

我更喜欢:

-8%
-10%
-12%

分别测试。

再看不同年份、不同市场环境下结果是否依然稳定。


避坑 3:一定要做样本外验证

比较靠谱的思路:

2019-2023
训练 / 参数研究

2024-2025
样本外验证

2026
模拟跟踪

不要把整个历史区间拿来“调参”,最后再宣布:

“策略胜率 70%。”

这在量化里没有太大意义。


六、常见问题解答

Q1:A 股均值回归策略用什么数据 API 比较方便?

A:如果主要使用 Python,QuantDash 提供官方 Python SDK,可以通过 pip install quantdash 安装;K 线接口支持 to_dataframe=True,可以直接和 Pandas 对接,也支持批量 K 线获取。

Q2:QuantDash 能不能批量获取 A 股股票历史 K 线?

A:可以。官方 SDK 提供 qd.klines.batch(),可以传入股票代码列表并批量获取日 K 或其他支持的 K 线周期;A 股股票池则可以通过 CN_Stock 获取。

Q3:为什么做超跌策略推荐使用前复权?

A:主要是为了让历史价格变化更适合连续收益率研究。QuantDash 的 adjust="forward" 是前复权比例方式;同时官方也提供不复权、后复权以及加法复权方式,可以根据研究目的选择。


🔗 相关资源与延伸阅读

🚀 QuantDash 官网:
QuantDash

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

⭐ GitHub 官方仓库:
QuantDash GitHub

💡 API Key 管理:
QuantDash API Key

官方 GitHub 仓库还提供 quickstart.py、测试和示例工作流,适合第一次接触 QuantDash 的朋友直接照着跑。

评论