我回测了 A 股 10 年的"追涨停"策略,结果可能和你想的不一样
A 股散户圈里有一种玩法叫"打板"——今天某只股票涨停了,明天开盘就买进去,赌它继续涨。
打板选手有一套自己的逻辑:涨停说明有资金强势介入,次日惯性上冲的概率不低,如果能接力涨停就赚 10%,不能涨停就止损出来。听起来赔率不错。
但"听起来"和"跑数据"是两回事。
这篇文章用 AlphaFeed 拉 A 股历史数据,做一个严格的统计回测:涨停之后的第二天、第三天、第五天,平均涨跌是多少?胜率多高?如果严格执行"涨停次日买入"策略,长期下来是赚是亏?
先说结论:追涨停长期来看大概率亏钱,但细节比你以为的复杂。
1. 找出历史上所有涨停的日子
A 股涨停幅度一般是 10%(创业板/科创板 20%),但实际因为四舍五入,涨幅在 9.5%–10.5% 之间就可以算涨停。
我们先选一组活跃股,拉长期 K 线,找出所有涨停日:
import pandas as pd
from alphafeed import AlphaFeed
af = AlphaFeed()
symbols = [
"000001.SZ", "600519.SH", "000858.SZ", "601318.SH",
"002594.SZ", "300750.SZ", "600036.SH", "000333.SZ",
"601012.SH", "600276.SH", "000568.SZ", "601888.SH",
"002415.SZ", "300059.SZ", "600809.SH", "000651.SZ",
"002304.SZ", "300124.SZ", "002475.SZ", "600887.SH",
]
klines = af.klines.batch(
symbols, period="1d", count=5000,
adjust="none", # 用不复权数据,否则涨跌幅会被复权扭曲
to_dataframe=True, show_progress=True,
)
all_limit_ups = []
for sym, df in klines.items():
df = df.sort_values("trade_date").reset_index(drop=True)
df["ret"] = df["close"].pct_change()
for i in range(1, len(df)):
if df["ret"].iloc[i] >= 0.095: # 涨停(含误差)
all_limit_ups.append({
"symbol": sym,
"date": df["trade_date"].iloc[i],
"close": df["close"].iloc[i],
"idx": i,
})
print(f"共找到 {len(all_limit_ups)} 次涨停")
print(f"覆盖 {len(klines)} 只标的")
2. 涨停次日表现:平均能赚多少?
核心问题:今天涨停,明天买进去,一天能赚多少?
import pandas as pd
import numpy as np
from alphafeed import AlphaFeed
af = AlphaFeed()
symbols = [
"000001.SZ", "600519.SH", "000858.SZ", "601318.SH",
"002594.SZ", "300750.SZ", "600036.SH", "000333.SZ",
"601012.SH", "600276.SH", "000568.SZ", "601888.SH",
"002415.SZ", "300059.SZ", "600809.SH", "000651.SZ",
]
klines = af.klines.batch(
symbols, period="1d", count=5000,
adjust="none", to_dataframe=True, show_progress=True,
)
results = []
for sym, df in klines.items():
df = df.sort_values("trade_date").reset_index(drop=True)
df["ret"] = df["close"].pct_change()
for i in range(1, len(df) - 5):
if df["ret"].iloc[i] < 0.095:
continue
# 涨停次日买入(用次日开盘价)
if i + 1 >= len(df):
continue
buy_price = df["open"].iloc[i + 1]
if buy_price <= 0:
continue
# 次日收益(用次日收盘价)
ret_1d = df["close"].iloc[i + 1] / buy_price - 1
# 第3日收益
ret_2d = df["close"].iloc[i + 2] / buy_price - 1 if i + 2 < len(df) else None
# 第5日收益
ret_5d = df["close"].iloc[i + 5] / buy_price - 1 if i + 5 < len(df) else None
# 是否连板(次日也涨停)
next_ret = df["ret"].iloc[i + 1]
is_连板 = next_ret >= 0.095
results.append({
"symbol": sym,
"date": df["trade_date"].iloc[i],
"ret_1d": ret_1d,
"ret_2d": ret_2d,
"ret_5d": ret_5d,
"is_连板": is_连板,
"open_pct": (buy_price / df["close"].iloc[i] - 1), # 次日开盘溢价
})
rdf = pd.DataFrame(results)
print(f"=== 涨停次日表现统计({len(rdf)} 次涨停样本)===")
print()
for label, col in [("持有1天", "ret_1d"), ("持有2天", "ret_2d"), ("持有5天", "ret_5d")]:
valid = rdf[col].dropna()
print(f" {label}:")
print(f" 平均收益: {valid.mean():+.2%}")
print(f" 中位数: {valid.median():+.2%}")
print(f" 胜率: {(valid > 0).mean():.1%}")
print(f" 最大赚: {valid.max():+.2%}")
print(f" 最大亏: {valid.min():+.2%}")
print()
你可能会看到的结果
在多数统计中,涨停次日的平均收益接近 0 或微负,但中位数通常为负。这意味着:
- 少数大赚的案例拉高了平均值(连板翻倍的那些)
- 多数情况是小亏:高开低走、开盘冲高后回落
- 胜率大概在 40%–50% 之间——连抛硬币都不如
3. 次日开盘溢价:买入成本被抬高了
涨停次日最大的问题不是"涨不涨",而是你买不到好价格。
因为昨天涨停了,今天大家都想买,开盘价往往直接高开 3%–5%。你以为自己在追涨停,其实你在高位接盘:
print("=== 涨停次日开盘溢价分布 ===")
print(f" 平均开盘溢价: {rdf['open_pct'].mean():+.2%}")
print(f" 中位数溢价: {rdf['open_pct'].median():+.2%}")
print()
bins = [(-1, -0.02), (-0.02, 0), (0, 0.02), (0.02, 0.05), (0.05, 0.10), (0.10, 1)]
labels = ["低开>2%", "低开0-2%", "高开0-2%", "高开2-5%", "高开5-10%", "高开>10%"]
for (lo, hi), label in zip(bins, labels):
pct = ((rdf["open_pct"] >= lo) & (rdf["open_pct"] < hi)).mean()
print(f" {label}: {pct:.1%}")
如果次日平均高开 3%,那你买入的一瞬间已经"亏了 3%"。即使当天收涨 2%,你的实际收益是 -1%。
这就是追涨停最大的隐性成本:你的买入价不是昨天的涨停价,而是今天被抬高后的开盘价。
4. 连板概率:买到第二个涨停有多难
打板选手追求的最大收益来源是"连板"——买进去之后第二天继续涨停。我们来看看概率:
连板率 = rdf["is_连板"].mean()
print(f"=== 连板概率 ===")
print(f" 涨停后次日再涨停的概率: {连板率:.1%}")
print(f" 也就是说,每 {1/连板率:.0f} 次涨停里,大约有 1 次连板")
print()
# 连板 vs 不连板的收益差异
连板组 = rdf[rdf["is_连板"]]
非连板组 = rdf[~rdf["is_连板"]]
print(f" 连板组 (n={len(连板组)}):")
print(f" 次日收益: {连板组['ret_1d'].mean():+.2%}")
print(f" 非连板组 (n={len(非连板组)}):")
print(f" 次日收益: {非连板组['ret_1d'].mean():+.2%}")
一般来说,连板概率在 10%–20% 之间。这意味着你打 10 次板,可能只有 1–2 次吃到连板的肉,其余 8 次都在亏开盘溢价和高开低走的差价。
5. 什么样的涨停更值得追
不是所有涨停都一样。我们可以按几个维度拆分,看看哪种涨停次日表现更好:
import pandas as pd
import numpy as np
from alphafeed import AlphaFeed
af = AlphaFeed()
# 假设已经有 rdf(上面的结果 DataFrame)
# 维度1: 成交额大小
# 需要在构建 results 时加入成交额字段,这里补充
symbols = list(set(rdf["symbol"].tolist()))
klines = af.klines.batch(
symbols, period="1d", count=5000,
adjust="none", to_dataframe=True,
)
enriched = []
for _, row in rdf.iterrows():
sym = row["symbol"]
if sym not in klines:
continue
kdf = klines[sym]
kdf = kdf.sort_values("trade_date").reset_index(drop=True)
match = kdf[kdf["trade_date"] == row["date"]]
if match.empty:
continue
amount = match["amount"].values[0]
volume = match["volume"].values[0]
prev_amount = kdf[kdf["trade_date"] < row["date"]]["amount"].tail(20).mean()
vol_ratio = amount / prev_amount if prev_amount > 0 else 1
enriched.append({
**row.to_dict(),
"amount": amount,
"vol_ratio": vol_ratio,
})
edf = pd.DataFrame(enriched)
print("=== 按成交额分组的涨停次日表现 ===")
edf["amount_group"] = pd.qcut(edf["amount"], q=3, labels=["低成交额", "中成交额", "高成交额"])
for group in ["低成交额", "中成交额", "高成交额"]:
g = edf[edf["amount_group"] == group]
print(f" {group} (n={len(g)}): 次日均值{g['ret_1d'].mean():+.2%} 胜率{(g['ret_1d']>0).mean():.0%}")
print()
print("=== 按量比分组的涨停次日表现 ===")
edf["vol_group"] = pd.cut(edf["vol_ratio"], bins=[0, 1.5, 3, 100], labels=["量比<1.5", "量比1.5-3", "量比>3"])
for group in ["量比<1.5", "量比1.5-3", "量比>3"]:
g = edf[edf["vol_group"] == group]
if len(g) < 5:
continue
print(f" {group} (n={len(g)}): 次日均值{g['ret_1d'].mean():+.2%} 胜率{(g['ret_1d']>0).mean():.0%}")
通常的发现是:
| 涨停类型 | 次日表现 | 解读 |
|---|---|---|
| 缩量涨停(量比低) | 相对较好 | 筹码锁定好,抛压小 |
| 放巨量涨停(量比>3) | 相对较差 | 换手大,可能是拉高出货 |
| 高成交额涨停 | 不确定 | 大盘股涨停少见,统计上有参考价值 |
| 低成交额涨停 | 波动大 | 小盘股弹性高但风险也高 |
6. 止损纪律:不止损会怎样
打板选手最重要的规则之一是"错了就砍"。不止损的后果有多严重?我们来对比:
import pandas as pd
import numpy as np
# 模拟三种执行方式
strategies = {
"无止损(持有5天)": rdf["ret_5d"].dropna(),
"止损-3%": rdf["ret_1d"].apply(lambda x: x if x > -0.03 else -0.03),
"止损-5%": rdf["ret_1d"].apply(lambda x: x if x > -0.05 else -0.05),
}
print("=== 止损 vs 不止损 ===")
for name, rets in strategies.items():
equity = (1 + rets).cumprod()
total_ret = equity.iloc[-1] - 1
max_dd = (equity / equity.cummax() - 1).min()
print(f" {name}:")
print(f" 累计收益: {total_ret:+.2%}")
print(f" 最大回撤: {max_dd:.2%}")
print(f" 平均单次: {rets.mean():+.2%}")
print()
不止损的结果通常非常难看——因为那些大亏的 case(涨停次日直接跌 5%–8%)会把利润全部吃掉。严格止损会改善结果,但可能从"大亏"变成"小亏"。
7. 累计净值曲线:长期跑下来什么样
把每一次"涨停次日买入、收盘卖出"的收益串成净值曲线:
import pandas as pd
import numpy as np
rdf_sorted = rdf.sort_values("date").reset_index(drop=True)
equity = [1.0]
for _, row in rdf_sorted.iterrows():
ret = row["ret_1d"]
if pd.isna(ret):
continue
cost = 0.0015 # 单边手续费+印花税
net_ret = ret - cost * 2
equity.append(equity[-1] * (1 + net_ret))
equity_series = pd.Series(equity)
total_return = equity_series.iloc[-1] - 1
max_dd = (equity_series / equity_series.cummax() - 1).min()
print(f"=== 追涨停策略长期表现 ===")
print(f" 交易次数: {len(rdf_sorted)}")
print(f" 累计收益: {total_return:+.2%}")
print(f" 最大回撤: {max_dd:.2%}")
print(f" 最终净值: {equity_series.iloc[-1]:.4f}")
print()
if total_return < 0:
print(f" 结论: 无差别追涨停,长期是亏钱的")
else:
print(f" 结论: 有正收益,但需要看夏普和回撤是否可接受")
加上交易成本之后,结果会更加难看。每次交易都要付万 3 佣金 + 千 1 印花税,打板交易频率高,成本累积很快。
8. 这意味着什么
回测结果通常指向几个结论:
1. 无差别追涨停是负期望的。
平均次日收益在扣除成本后大概率为负。涨停不是免费的"涨 10%"——你的买入成本被次日高开抬高了,而高开之后回落的概率比继续涨的概率大。
2. 少数人能赚钱,因为他们不是无差别追。
真正的打板高手会筛选:首板 vs 二板、题材强度、板块联动、分时走势、封单量。他们追的是特定条件下的涨停,而不是看到涨停就冲。
3. 追涨停赚钱的本质不是"涨停好",而是"选股好"。
如果你能选出明天涨停的票,你当然赚钱。但这和"追涨停"是两回事。追涨停是在涨停已经发生之后买入,这时候信息已经被价格消化了。
4. 打板的收益分布是极端的。
少数连板收益很大,多数普通涨停次日是亏的。这意味着你必须交易很多次才能碰到几次大赚,但过程中的亏损会消耗你的本金和心态。
9. 如果你还是想研究打板
把上面的分析框架改成你自己的筛选条件,看看特定条件下的涨停是否表现更好:
# 示例:只看"首板"(前一天不是涨停的涨停)
首板 = rdf[rdf["open_pct"] < 0.095] # 次日不是一字板,说明不是连板后的
print(f"首板次日表现 (n={len(首板)}):")
print(f" 平均收益: {首板['ret_1d'].mean():+.2%}")
print(f" 胜率: {(首板['ret_1d'] > 0).mean():.0%}")
你也可以加入更多筛选维度:
- 板块里同时涨停的个数(题材强度)
- 涨停时间(早盘涨停 vs 尾盘涨停)
- 涨停前的走势(底部涨停 vs 高位涨停)
- 市值区间(小盘涨停 vs 大盘涨停)
每加一个条件,样本量就会减少,但如果剩下的样本显示出稳定的正期望,那可能是一个值得深入研究的方向。
10. 你可以用这个方法验证任何"民间策略"
这篇文章的价值不只是"打板赚不赚钱"这一个结论,而是给了你一套验证方法:
提出假设 → 定义规则 → 拉历史数据 → 统计收益/胜率/回撤 → 下结论
AlphaFeed 可以拉数千根 K 线,支持不复权模式(对涨停分析很重要),批量获取几十只票的数据。有了数据,任何策略假设都可以用同样的方法验证。
下次有人跟你说"这种票明天一定涨",你不用争论——打开终端,跑一下数据。
结语
追涨停是 A 股散户圈最有争议的话题之一。有人靠它赚到过钱(他们的故事你都听过),有人靠它亏到清仓(他们的故事你没听过)。
数据告诉我们的是:无差别追涨停的长期期望收益为负。 涨停次日高开低走的概率、交易成本的累积、连板极低的概率,这些因素加在一起,让"看到涨停就追"变成一个负期望的游戏。
但这不意味着所有涨停都不值得关注。如果你有能力在涨停中进一步筛选——通过成交量特征、板块联动、市场情绪、分时结构——那有可能找到正期望的子集。只是,这已经不是"追涨停",而是"选股"了。
参考文献:
- AlphaFeed 官网:https://alphafeed.org/
- Python SDK 快速开始:https://docs.alphafeed.org/zh-Hans/sdk/python-quickstart

