📑 本文目录
一天37只涨停7只20cm:我用DuckDB给A股涨停板画了张特征画像
一个数据钩子:37只涨停,医药霸屏
2026年7月13日(周一),A股收盘后我跑了一条DuckDB SQL,筛出当天所有涨停股。
结果让人意外——37只涨停,其中7只创业板20cm涨停。更引人注目的是行业分布:医药霸榜。万邦医药+20.01%、陇神戎发+20.00%、上海凯宝+15.81%,10cm阵营里还有九芝堂、哈药股份、天目药业、灵康药业、联环药业一连串医药股。
这不是偶发现象。我拉了近3个月(2026年4月至7月)的全部数据,20cm涨停板共出现367次,涉及超过200只个股。涨停板是A股最极端的日内事件,但市面上很少有文章用量化方法系统分析它的统计规律——散户搜”涨停板分析""涨停原因”是刚需,但搜索结果全是复盘帖和情绪贴,没有一篇是从数据出发的。
今天就用我自己的DuckDB数据库(quant_v2.duckdb,4500+只A股日线数据),给涨停板画一张完整的量化特征画像。
本文不是教你追涨停。恰恰相反——367次回测数据会告诉你,追涨停的期望收益到底值不值。
第一步:DuckDB涨停筛选SQL
核心SQL非常简洁,关键是用窗口函数计算前一日收盘价,再筛选涨幅:
import duckdb
con = duckdb.connect('quant_v2.duckdb', read_only=True)
# 筛选当日涨停股
limit_up_df = con.execute("""
WITH base AS (
SELECT code, date, close, amount,
LAG(close) OVER (PARTITION BY code ORDER BY date) AS prev_close
FROM stock_daily
WHERE date IN ('2026-07-10', '2026-07-13')
),
limit_up AS (
SELECT code, close, amount,
ROUND((close - prev_close) / prev_close * 100, 2) AS pct_change,
CASE
WHEN code LIKE '300%' OR code LIKE '301%' OR code LIKE '688%' THEN '20cm'
ELSE '10cm'
END AS board
FROM base
WHERE date = '2026-07-13'
AND prev_close IS NOT NULL
AND (close - prev_close) / prev_close >= 0.098
)
SELECT * FROM limit_up
ORDER BY pct_change DESC
""").fetchdf()
几个关键设计:
- 涨停阈值:创业板/科创板用19.5%(20cm),主板用9.8%(10cm),统一用9.8%覆盖两者
- prev_close用窗口函数:
LAG(close)按个股分区,避免用JOIN带来的性能开销 - 板块标识:通过ts_code前缀区分——
300/301开头是创业板,688开头是科创板,其余是主板
7月13日跑出来的结果:
| 板块 | 涨停数 | 平均涨幅 | 代表个股 |
|---|---|---|---|
| 20cm(创业板/科创板) | 7只 | +16.50% | 万邦医药+20.01%、陇神戎发+20.00%、日科化学+20.00% |
| 10cm(主板) | 30只 | +10.03% | 中国软件+9.99%、九丰能源+10.01%、中信重工+9.92% |
| 合计 | 37只 | — | — |
第二步:涨停板全景画像——行业、市值、成交额
行业聚类:医药+化工双主线
把37只涨停股通过 stock_industry_map 表映射到申万二级行业,出现了一个清晰的聚类:
| 行业(申万二级) | 涨停数 | 平均涨幅 | 代表个股 |
|---|---|---|---|
| 化学制药 | 5只 | +10.19% | 立方制药、联环药业、灵康药业、天目药业、哈药股份 |
| 中药 | 4只 | +13.96% | 陇神戎发+20%、上海凯宝+15.81%、九芝堂、天目药业 |
| 化学制品 | 3只 | +13.34% | 日科化学+20%、宿迁联盛、肯特催化 |
| 燃气 | 2只 | +10.03% | 水发燃气、九丰能源 |
| 塑料 | 2只 | +10.73% | 争光股份、沃顿科技 |
| 通用设备 | 2只 | +10.02% | 贵绳股份、晋亿实业 |
| 专用设备 | 2只 | +9.96% | 中信重工、亚联机械 |
医药(化学制药+中药+医药商业+生物制品)合计9只,占涨停总数的24%。如果加上化工链(化学制品+塑料),“医药+化工”合计14只,占比38%。
这个聚类不是随机的——7月初医药板块有创新药审批加速的政策催化,化学制药板块整体处于估值修复期。
成交额分布:中小盘为主
| 成交额区间 | 涨停股数量 | 占比 |
|---|---|---|
| 小于1亿 | 7只 | 19% |
| 1-5亿 | 14只 | 38% |
| 5-10亿 | 12只 | 32% |
| 10-20亿 | 3只 | 8% |
| 大于20亿 | 1只 | 3% |
67%的涨停股成交额在1-10亿之间——典型的中小盘活跃区间。成交额大于20亿的只有九丰能源(23.52亿),它是当天涨停股中成交最大的。成交额小于1亿的那7只,大多是ST股或低流动性标的(如国华退成交仅0.02亿),这类涨停往往缺乏参与价值。
第三步:涨停前兆特征——T-5日有什么信号?
涨停是突发事件还是蓄势已久?我拉了7只20cm涨停股在涨停前5个交易日(7月6日至7月10日)的走势:
| 个股 | T-5涨幅 | T-1涨幅 | 涨停日成交额/5日前成交额 |
|---|---|---|---|
| 万邦医药(301520) | +6.98% | +8.61% | 1.63倍 |
| 鑫宏业(301310) | -3.12% | +11.44% | 4.67倍 |
| 争光股份(301092) | -0.78% | +15.79% | 2.14倍 |
| 陇神戎发(300534) | 0% | +1.49% | 0.74倍 |
| 上海凯宝(300039) | +1.11% | +2.28% | 0.61倍 |
| 新天地(301277) | +0.21% | +1.65% | 0.42倍 |
| 日科化学(300214) | 0%(停牌) | 0%(停牌) | — |
两种截然不同的涨停前兆模式:
模式A:连涨加速型——万邦医药、鑫宏业、争光股份。涨停前1日已有较大涨幅(8%-16%),成交额在涨停日放大2-5倍。这类属于资金接力推升,涨停是上涨趋势的加速而非起点。
模式B:横盘突发型——陇神戎发、上海凯宝、新天地。涨停前5日几乎横盘(涨跌幅小于2%),成交额未明显放大,涨停日突然放量。这类属于消息驱动或游资突袭,事前几乎无法预判。
我把近3个月全部367次20cm涨停做了统计聚合:
| 前兆指标 | 均值 | 解读 |
|---|---|---|
| T-5日累计涨幅 | +2.33% | 涨停前5日整体偏强,但不显著 |
| T-5日上涨超5%的占比 | 33.7%(117/347) | 约三分之一属于”连涨加速型” |
| T-5日下跌的占比 | 46.1%(160/347) | 接近一半的涨停股前5日是跌的! |
核心发现:46%的20cm涨停板,前5日是下跌的。 涨停本质上是低预测性事件,不要试图用”前期涨了多少”来预判涨停。
第四步:历史回测——追涨停第二天到底赚不赚钱?
这是散户最关心的问题。我把2026年4月14日至7月13日的全部涨停板(次日有交易数据的)做了T+1表现统计:
20cm涨停板次日表现(367次样本)
| 指标 | 数值 |
|---|---|
| 样本量 | 367次(次日有数据364次) |
| 次日平均涨幅 | +1.93% |
| 次日中位数涨幅 | +1.13% |
| 上涨概率 | 57.1%(208涨 / 156跌) |
| 最大涨幅 | +20.21%(连续涨停) |
| 最大跌幅 | -30.81% |
| 连板概率(次日继续涨停) | 5.7%(21/367) |
10cm涨停板次日表现(4343次样本)
| 指标 | 数值 |
|---|---|
| 样本量 | 4343次(次日有数据4313次) |
| 次日平均涨幅 | +1.33% |
| 次日中位数涨幅 | +0.85% |
| 上涨概率 | 55.5%(2392涨 / 1921跌) |
| 最大涨幅 | +10.61% |
| 最大跌幅 | -21.82% |
| 连板概率 | 16.9%(736/4343) |
关键解读
1. 平均收益为正,但不可盲目乐观。
20cm涨停次日平均+1.93%,看起来不错。但中位数只有+1.13%,说明分布右偏——少数暴涨(连板)拉高了均值。如果去掉连板的21个样本,平均收益会降到+1.0%左右。
2. 上涨概率55-57%——比抛硬币略好。
57%的上涨概率意味着43%的概率次日亏钱。考虑到交易成本(印花税千1+佣金万5+滑点),实际胜率会更低。这不是一个高胜率策略。
3. 10cm连板概率(16.9%)远高于20cm(5.7%)。
这很反直觉——主板10cm涨停的次日连板率是创业板20cm的3倍。原因可能是:主板涨停封单更厚(10cm涨幅对应卖压更小),游资做连板的偏好集中在主板。如果你要打板做连板,主板的统计数据更有利。
4. 最大跌幅-30.81%是致命的。
20cm涨停次日最多能跌30.81%。这意味着如果你在涨停价买入,一天能亏掉三分之一。这种尾部风险是涨停策略最大的敌人——均值的+1.93%完全无法补偿这种极端亏损的风险。
第五步:月度涨停热度——6月是20cm爆发期
| 月份 | 20cm涨停次数 | 涉及个股数 | 日均涨停 |
|---|---|---|---|
| 2026年4月 | 49次 | 43只 | 约2.3次/日 |
| 2026年5月 | 93次 | 81只 | 约4.4次/日 |
| 2026年6月 | 168次 | 146只 | 约8.0次/日 |
| 2026年7月(至13日) | 57次 | 54只 | 约5.2次/日 |
6月是20cm涨停板的爆发期——168次涨停,是4月的3.4倍。这与6月市场情绪回暖、创业板走出底部反弹高度吻合。7月虽只过了半月,但日均5.2次仍处于高位。
这组数据揭示了一个规律:涨停板数量本身就是市场情绪的温度计。 当20cm日均涨停从2次飙到8次,往往意味着中小盘投机热度进入高位——反过来可以作为风控信号。
第六步:工程落地——每日涨停板自动扫描
基于以上分析,我写了一个每日涨停板扫描脚本,可以集成到cron中自动运行:
import duckdb
import json
from datetime import datetime
def scan_limit_up(con, trade_date, prev_date):
"""扫描指定日期的涨停板"""
df = con.execute(f"""
WITH base AS (
SELECT code, date, close, amount,
LAG(close) OVER (PARTITION BY code ORDER BY date) AS prev_close
FROM stock_daily
WHERE date IN ('{prev_date}', '{trade_date}')
)
SELECT code, close, amount,
ROUND((close - prev_close) / prev_close * 100, 2) AS pct_change,
CASE
WHEN code LIKE '300%' OR code LIKE '301%' OR code LIKE '688%'
THEN '20cm' ELSE '10cm'
END AS board
FROM base
WHERE date = '{trade_date}'
AND prev_close IS NOT NULL
AND (close - prev_close) / prev_close >= 0.098
ORDER BY pct_change DESC
""").fetchdf()
# 关联行业
if len(df) > 0:
df = df.merge(
con.execute("SELECT code, industry_sw2 FROM stock_industry_map").fetchdf(),
on='code', how='left'
)
return df
def analyze_limit_up_stats(con, lookback_days=90):
"""统计近N天涨停板次日表现"""
df = con.execute(f"""
WITH daily AS (
SELECT code, date, close,
LAG(close) OVER w AS prev_close,
LEAD(close) OVER w AS next_close
FROM stock_daily
WHERE date >= DATE '{trade_date}' - INTERVAL '{lookback_days}' day
WINDOW w AS (PARTITION BY code ORDER BY date)
)
SELECT
CASE
WHEN code LIKE '300%' OR code LIKE '301%' OR code LIKE '688%'
THEN '20cm' ELSE '10cm'
END AS board,
COUNT(*) AS total,
ROUND(AVG(
CASE WHEN next_close IS NOT NULL
THEN (next_close - close) / close * 100 END
), 2) AS avg_next_return,
SUM(CASE WHEN next_close > close THEN 1 ELSE 0 END) AS up_count,
COUNT(next_close) AS has_next
FROM daily
WHERE prev_close IS NOT NULL
AND (close - prev_close) / prev_close >= 0.098
GROUP BY board
""").fetchdf()
return df
# 使用示例
con = duckdb.connect('quant_v2.duckdb', read_only=True)
today_limit = scan_limit_up(con, '2026-07-13', '2026-07-10')
stats = analyze_limit_up_stats(con)
print(f"今日涨停:{len(today_limit)}只")
print(f"20cm:{len(today_limit[today_limit['board']=='20cm'])}只")
print(f"10cm:{len(today_limit[today_limit['board']=='10cm'])}只")
print(f"\n近3个月次日平均涨幅:")
print(stats.to_string(index=False))
把这段代码放进每日cron,早上8点自动跑一次,飞书推送涨停板清单+次日统计——比手动复盘效率高10倍。
结论:涨停板的三个量化真相
回到开篇的问题——涨停板的统计规律是什么?367次20cm + 4343次10cm的回测数据给出了三个清晰的答案:
真相一:涨停是低预测性事件。 46%的20cm涨停板前5日是下跌的,“连涨加速型”只占三分之一。不要试图用技术指标预判涨停,尤其是不要在涨停前一天基于”明天会涨停”的假设建仓。
真相二:追涨停的期望收益微薄。 20cm次日均值+1.93%看似可观,但中位数仅+1.13%,扣除交易成本后所剩无几。57%的上涨概率意味着你每追10次涨停,大约有4次次日亏钱。
真相三:尾部风险是致命的。 最大单日跌幅-30.81%(20cm)和-21.82%(10cm),一次就能抹掉数十次微薄盈利。如果用杠杆追涨停,破产只是时间问题。
正确的打开方式:与其追涨停,不如用涨停板数据做市场情绪温度计。当20cm日均涨停突破8次(如2026年6月),往往意味着投机过热;当降到2次以下(如2026年4月),反而是底部特征。涨停板数量的趋势变化,比涨停板本身的收益预测价值大得多。
相关阅读:
- DuckDB A股量化数据库搭建指南 — 本文使用的数据库架构详解
- DuckDB主力资金监控系统 — 资金面数据工程,可与涨停扫描联动
- Barra多因子选股模型 — 涨停是极端动量事件,多因子框架的另一面
- 动量vs均值回归:谁在A股有效? — 涨停板背后的动量因子分析
- 动态仓位管理:极端波动的风控框架 — 如果非要追涨停,至少做好风控