📑 本文目录
涨停板DuckDB量化分析创业板因子归因次日表现

一天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()

几个关键设计

  1. 涨停阈值:创业板/科创板用19.5%(20cm),主板用9.8%(10cm),统一用9.8%覆盖两者
  2. prev_close用窗口函数LAG(close) 按个股分区,避免用JOIN带来的性能开销
  3. 板块标识:通过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月),反而是底部特征。涨停板数量的趋势变化,比涨停板本身的收益预测价值大得多。


相关阅读

💬 评论