📑 本文目录
数据质量ETF复权除权除息DuckDB量化数据清洗跳空检测数据工程515880512480

ETF 暴跌 65% 却无人恐慌:515880/512480 除权跳变识别与 DuckDB 复权检测算法


TL;DR 当你的 ETF 涨跌幅榜上,前两名都是 “-65%“,先别急着写”半导体崩盘、通信暴跌”的复盘——它们大概率是除权跳变。2026 年 7 月,515880 通信 ETF 单日 -52.06%、512480 半导体 ETF 单日 -54.77%,两者把 20 日跌幅榜拉到 -65%,但复权后的真实 20 日收益分别是 -26.91%-22.84%。跌幅被夸大了近三倍——虽然确实在跌,但远没有”腰斩”那么恐怖。本文记录这个坑的全貌,并给出一份可直接粘贴运行的 DuckDB 复权清洗 SQL。

一、引子:一张”惊悚”的跌幅榜

2026 年 7 月 30 日,我在做每周 ETF 筛选时,顺手按 20 日累计涨跌幅排了个序。跌幅榜前两名跳出来:

代码20 日涨跌幅最新净值20 日前净值
512480 半导体ETF-65.10%1.0272.943
515880 通信ETF-64.96%0.6021.718
515000-26.11%1.2341.670
588000-22.45%1.7762.290

按照惯例,一只宽基/行业 ETF 二十天跌 65%,是足以上财经头条的极端事件。可奇怪的是,雪球、东方财富股吧、各类研报里,没有一个人在讨论”半导体 ETF 崩了”或”通信板块腰斩”。这本身就违反常识——A 股市场对单日跌停都极度敏感,更别说一个月腰斩。

唯一合理的解释是:这个跌幅是假的。 它不是市场行为,而是 ETF 的份额变动(分红、份额拆分/合并)造成的价格机械跳变——也就是俗称的”除权跳变”。如果我直接把这张表喂给任何一个动量因子、轮动策略、风险预算模型,结果都会是一场灾难:策略会判定半导体 ETF “趋势崩坏”而清仓,判定通信 ETF “极度超跌”而抄底,两个动作都是错的。

这个坑比想象中常见。本文就用这两个真实案例,把”假跌幅”的全貌讲清楚,并给出一套可复用的 DuckDB 检测与复权算法。

二、定位:单日 -52% 发生在什么时候

第一步永远是回到日线,把那根”断崖”找出来。在 DuckDB 里拉 515880 通信 ETF 整个 2026 年的日线,按”相对前一日收盘的跳空幅度”排序,最大的单日下跌一目了然:

515880 通信ETF 2026 年最大单日跌幅 TOP3:
2026-07-06 : close 0.757, prev 1.579  → -52.06%
2026-07-28 : close 0.600, prev 0.667  → -10.04%  (真实行情下跌)
2026-07-17 : close 0.659, prev 0.732  → -9.97%   (真实行情下跌)

关键证据藏在第一行:2026-07-06,515880 从 1.579 元直接跳到 0.757 元,单日 -52.06%。A 股 ETF 单日涨跌幅限制是 ±10%(少数跨境 ETF 例外),无论如何不可能出现 -52% 的真实成交。再看跳变前后的 K 线:

日期开盘最高最低收盘
2026-07-031.5601.6331.5601.579
2026-07-060.7980.8000.7430.757
2026-07-070.7500.7730.7420.761

注意 7 月 6 日的开盘价(0.798)就已经在 0.75 附近,而不是从前一日收盘 1.579 慢慢跌下来。整个价格中枢在一夜之间下移了 52%,随后几天在 0.75~0.81 区间正常波动。这是教科书级的除权跳变特征:跳变发生在开盘集合竞价,跳变前后各自的波动结构是连续的,只有跳变那一根 K 线是断裂的。

512480 半导体 ETF 是同样的剧本,日期还更早两天:

512480 半导体ETF 2026 年最大单日跌幅 TOP3:
2026-07-03 : close 1.331, prev 2.943  → -54.77%
2026-07-17 : close 1.070, prev 1.159  → -7.68%
2026-07-28 : close 1.041, prev 1.124  → -7.38%

2026-07-03,512480 从 2.943 元跳到 1.331 元,单日 -54.77%。这只 ETF 的除权日比 515880 早了三个交易日,所以两只 ETF 的”假跌幅”在 20 日窗口里是错开计入的,更增加了识别难度——如果你只看某一天的榜单,可能只中招一只。

三、根因:ETF 除权 ≠ 行情下跌

ETF 的”除权”和股票的除权原理一致,但触发场景更多。对 ETF 而言,价格(二级市场收盘价)机械下调主要有三类原因:

  1. 现金分红除息:ETF 把成分股分红累积后以现金形式派发给持有人,除息日价格按每份派现金额等额下调。单次分红通常只下调几个百分点,不会造成 -52% 这种极端跳变。

  2. 份额拆分(Split,“折分”):基金管理人把一份 ETF 拆成多份(例如 1 拆 2、1 拆 3),份额数翻倍、单位价格等比例下降,持有人总资产不变。这正是 515880 和 512480 此次跳变的主因。512480 在上交所公告里的标题就是《国联安基金管理有限公司关于国联安中证全指半导体产品与设备交易型开放式指数证券投资基金实施基金份额……》——“基金份额”字样直接点明了这是份额变动。

  3. 份额合并(Reverse Split,“合分”):与拆分相反,把多份合成一份,价格等比例上升,常见于”低价保壳”的跨境 ETF。

为什么管理人要搞份额拆分?通常是因为单位净值涨得比较高(比如净值到 2~3 元),为了降低单手交易门槛、提升二级市场流动性,把单位价格”折”回 1 元附近。这是纯粹的产品维护操作,和成分股、和行业基本面、和市场情绪没有任何关系

理解这一点至关重要:除权跳变是一个”记账事件”,不是一个”行情事件”。 它出现在价格序列里,但它不反映任何资产的供需变化。任何把它当作真实收益的下游计算——涨跌幅、动量、波动率、最大回撤、相关性——都会被污染。

四、量化复权:跳变前后的真实收益

如果跳变只是”记账”,那把跳变”抹平”之后,515880 和 512480 这 20 天到底涨没涨?答案是:两只都在跌,但跌幅远没有 -65% 那么夸张。

复权的核心思路是:跳变当日的收益率视为无效,用跳变前后两段价格序列各自的真实波动拼出”连续价格”。具体地,对跳变当日(即相对前日涨跌幅绝对值大于 30% 的异常日),把它的日收益率从累计收益里剔除,相当于用一个”中性 1.0”替代,再对整段区间做累计乘积。这等价于前复权——把跳变前的历史价格乘以除权因子(除权后收盘价除以除权前收盘价)向下贴齐。

对两只 ETF 2026-07-01 至 2026-07-29 区间(约 20 个有效交易日)分别计算:

代码原始 20 日收益跳变事件复权后真实 20 日收益
515880 通信ETF-64.96%2026-07-06 单日 -52.06%-26.91%
512480 半导体ETF-65.10%2026-07-03 单日 -54.77%-22.84%

这个对比表是整篇文章最该记住的一组数字:

  • 515880:原始算出来 -65%,复权后是 -27%。通信 ETF 这 20 天确实在调整,但调整幅度是被夸大了近 2.4 倍的”较大下跌”,远谈不上腰斩。一个动量策略如果看到 -65% 会无脑清仓,看到 -27% 可能只是降仓或观望,决策完全不同。

  • 512480:原始算出来 -65%,复权后是 -22.84%——它也在跌,但同样被夸大了近三倍。这里要特别纠正一个常见的误判:不能因为”复权后就不算崩盘了”就反过来说”它其实在涨”。512480 复权后仍然是负收益,只是从”骇人听闻的 -65%“还原成了”正常的 -23%“。一个基于涨跌幅排名的轮动策略,如果用未复权数据,会把这只 ETF 误判成”趋势彻底崩坏”而踢出候选;用复权数据,则会正确识别为”短期回调但趋势未破”,持仓决策完全不同。

这就是”假跌幅”的真实杀伤力:它不是让你少赚一点,而是让模型基于一个被放大了近三倍的虚假信号做决策。而且因为跳变是合法的、数据源本身没 bug,这种错误极其隐蔽——数据看起来”完整、连续、有据可查”,只有复权校验才能发现。值得庆幸的是,两只 ETF 复权后都是”真实下跌但幅度合理”,并没有出现”复权前暴跌、复权后暴涨”这种极端反转——但即便如此,-65% 和 -23% 对策略的含义已经是天壤之别。

4.1 附:除权日前后原始 K 线(供读者自行核验)

为方便读者复核,下面贴出两只 ETF 除权日前后的原始日线数据(直接从 DuckDB 查得,未做任何修改)。可以清楚看到跳变当日的价格断裂、以及跳变前后各自波动的连续性。

515880 通信ETF(除权日 2026-07-06):

日期开盘最高最低收盘单日涨幅
2026-07-011.7961.8181.6941.718-4.34%
2026-07-021.6501.6601.5621.576-8.27%
2026-07-031.5601.6331.5601.579+0.19%
2026-07-060.7980.8000.7430.757-52.06%(除权跳变)
2026-07-070.7500.7730.7420.761+0.53%
2026-07-080.7690.7850.7470.757-0.53%
2026-07-090.7680.8070.7500.807+6.61%

注意 7/06 当天开盘价 0.798 元已经落在 0.75 附近,而不是从前一日收盘 1.579 元慢慢跌下来——价格中枢在集合竞价阶段就完成了下移,这是除权跳变最典型的特征。

512480 半导体ETF(除权日 2026-07-03):

日期开盘最高最低收盘单日涨幅
2026-06-302.9093.0112.8383.005+3.30%
2026-07-013.0053.1132.9002.943-2.06%
2026-07-022.9432.9432.9432.9430.00%
2026-07-031.3221.3831.3031.331-54.77%(除权跳变)
2026-07-061.3601.3671.2771.334+0.23%
2026-07-071.3101.3651.2991.336+0.15%
2026-07-081.3421.3891.2901.339+0.22%

跳变前(6/307/02)三天价格稳定在 2.9433.005 元,跳变后(7/067/08)三天稳定在 1.3311.339 元——两个平台之间的”断层”就是除权因子 0.4523(1.331/2.943)。复权计算只需把跳变前的价格乘以这个因子,序列立刻连续。

五、检测算法:>11% 跳空检测 SQL 模板

既然跳变是污染源,那第一道防线就是自动检测。这里给一份可直接在 DuckDB(或任何兼容窗口函数的 SQL 引擎)上运行的检测查询。核心逻辑:单日相对前收盘的涨跌幅绝对值超过阈值,且无法用当日成交区间解释的,判定为除权跳变

阈值怎么定?A 股股票正常涨跌停是 ±10%,科创板/创业板是 ±20%,跨境 ETF 有时波动更大。ETF 的份额拆分/合并通常一次性造成 ±30% 以上的跳变,而即便是 20cm 板的单日真实涨跌也到不了 30%。所以一个稳妥的经验阈值是 |gap| > 11%(覆盖股票 10cm 板留 1% 缓冲),对极端跳变(拆分/合并)再用 |gap| > 30% 作为强信号。下面这份 SQL 用 11% 阈值,能在保留真实 20cm 涨跌停的同时,把除权跳变精准抓出来:

-- ETF 除权跳变检测(11% 阈值,DuckDB 可直接运行)
-- 输入:etf_daily (code, date, close),按 code-date 排序
-- 输出:每一行是一个被判定为除权跳变的 (code, date),附跳变幅度与前后收盘价
WITH with_prev AS (
  SELECT
    code, date, close,
    LAG(close) OVER (PARTITION BY code ORDER BY date) AS prev_close
  FROM etf_daily
),
gaps AS (
  SELECT
    code, date, close, prev_close,
    (close / prev_close - 1) * 100 AS gap_pct
  FROM with_prev
  WHERE prev_close IS NOT NULL AND prev_close > 0
)
SELECT
  code,
  date       AS ex_date,
  prev_close AS close_before,
  close      AS close_after,
  ROUND(gap_pct, 2) AS gap_pct,
  CASE
    WHEN gap_pct < -30 THEN 'SPLIT_OR_DIVIDEND_DOWN'   -- 强信号:份额拆分/分红
    WHEN gap_pct >  30 THEN 'REVERSE_SPLIT_UP'         -- 强信号:份额合并
    ELSE 'POSSIBLE_MINOR_ADJ'                          -- 弱信号:小额分红等
  END AS gap_type
FROM gaps
WHERE ABS(gap_pct) > 11
ORDER BY ABS(gap_pct) DESC;

把这份 SQL 跑在我们这个 2026 年的数据上,515880 和 512480 的那两根跳变 K 线会直接出现在结果集最前面,gap_type 标记为 SPLIT_OR_DIVIDEND_DOWN。一份合格的数据管道,应该在 ETL 写入之后、策略读取之前,每天自动跑一次这个检测,把命中的 (code, date) 写进一张 etf_corporate_actions(公司行动)表,供下游复权使用。

六、复权回填:前复权与后复权的 SQL 实现

检测只是第一步,真正的工程是把除权跳变从价格序列里”抹平”,生成一条连续可比的复权价。复权分两种:

  • 前复权(forward-adjusted):以最新价格为基准,把历史价格按除权因子反向调整。适合看”当前价格相对历史的真实涨跌”,是最常用的策略回测基准。
  • 后复权(backward-adjusted):以最早价格为基准,把最新价格按除权因子正向调整。适合看”持有至今的累计真实收益”,第一个价格就是历史真实价格。

复权因子(adj_factor)定义为:跳变日之后每一天的累计调整系数。对一次从 close_beforeclose_after 的跳变,单次调整系数 k = close_before / close_after(>1 表示价格下调、需要把跳变前的历史价格乘以 k 向下贴齐)。多次除权则把每次的 k 累乘。下面是前复权的完整 SQL(DuckDB 窗口函数实现,无需 Python):

-- 步骤 1:识别每次除权点及其单次调整系数 k
WITH split_events AS (
  SELECT code, date AS ex_date,
         prev_close AS close_before,
         close      AS close_after,
         prev_close / close AS k           -- 单次调整系数
  FROM (
    SELECT code, date, close,
           LAG(close) OVER (PARTITION BY code ORDER BY date) AS prev_close
    FROM etf_daily
  ) t
  WHERE prev_close IS NOT NULL
    AND ABS(close/prev_close - 1) * 100 > 11   -- 11% 跳空阈值
),
-- 步骤 2:为每一行 (code, date) 计算"在该日期之前发生的所有除权"的累计调整系数
adj AS (
  SELECT
    d.code, d.date, d.close AS raw_close,
    COALESCE(
      (SELECT EXP(SUM(LN(e.k))) FROM split_events e
       WHERE e.code = d.code AND e.ex_date > d.date),
      1.0
    ) AS adj_factor
  FROM etf_daily d
)
-- 步骤 3:前复权价 = 原始价 × adj_factor
SELECT
  code, date,
  ROUND(raw_close, 4)         AS close_raw,
  ROUND(adj_factor, 6)        AS adj_factor,
  ROUND(raw_close * adj_factor, 4) AS close_forward_adj   -- 前复权价
FROM adj
ORDER BY code, date;

逻辑要点:

  1. split_events 找出所有除权点,并计算每次的单次系数 k = close_before / close_after
  2. 子查询 adj 对每一行,把”在该日期之后发生的所有除权”的 k 累乘起来,作为这一行的累计 adj_factor。注意方向:前复权是把历史价格向现在贴齐,所以累计的是”未来发生的除权”。
  3. 前复权价 = 原始价 × adj_factor。在最新日期,adj_factor 恒为 1.0(没有任何后续除权要调整),所以前复权序列的最后一个值就是当前真实价格,这正是它适合做策略基准的原因。

把这套 SQL 跑完后,515880 和 512480 的前复权序列会变成一条平滑连续的曲线,2026-07-06 那根 -52% 的断崖会消失,前复权后的 20 日累计收益就是前面给出的 -26.91%-22.84%

一个落地建议:把前复权结果物化成一张 etf_daily_adj 表,每天 ETL 时增量更新(只有新发生除权的 ETF 需要重算整段历史,其余直接复制),策略层只读 etf_daily_adj,永远不碰原始 etf_daily。这样从架构上就杜绝了”忘了复权”的可能。

七、避坑清单:5 类常见的”假信号”

除权跳变只是”价格序列里出现不真实波动”的一类,实战中还有几种同源的坑,值得一并列入数据清洗的检查表:

  1. ETF 份额拆分/合并(本文主角):单日 ±30% 以上的机械跳变,用 >11% 跳空检测 + 公告核验识别。⚠️ 注意跨境 ETF(如 513050、159605)有时份额变动幅度更大,且可能合并小额分红一起除权。

  2. 股票分红送转除权:股票的每 10 股送/转/派会造成除权日价格跳变,幅度通常几个点到 20%+。和 ETF 不同,股票除权在 A 股是高频事件,任何横截面动量因子都必须基于复权价,否则每个分红季都会出现一批”假跌停”。

  3. 指数成分调整 / 退市重新上市:指数 ETF 跟踪的成分股调整、个股退市后重新上市(如 ST 摘帽复牌),会造成跟踪误差骤变,但通常不直接体现在 ETF 价格跳变上,而是体现在 ETF 相对基准的跟踪偏离上。这类需要监控折溢价率而非价格。

  4. 净值与价格的四舍五入:ETF 二级市场价格和基金净值(NAV)是两个序列,分红日可能出现”价格除息了但净值还没更新”或反向的情况,造成短暂的折溢价假象。做套利策略时务必用同一时点的价格与净值。

  5. 停牌补跌 / 复牌跳空:个股长期停牌期间市场已大幅波动,复牌当日出现”补偿性”跳空,这是真实行情而非除权。区分方法是:除权跳变在开盘集合竞价就完成、当日振幅正常;停牌补跌往往当日振幅巨大、成交放量。阈值检测会把两者都抓出来,需要用振幅/成交量做二次过滤。

一个稳健的量化数据管道,应该对上述 5 类分别建表记录(公司行动表、停复牌表、成分调整表),并在所有策略读取层默认挂上复权与过滤逻辑。默认复权、默认跳过异常跳空,把”忘记处理”变成”必须显式关闭”,是数据治理的第一原则。

八、结论:复权是量化数据管道的”必修课”

回到开头那张”惊悚”的跌幅榜。515880 和 512480 的 -65%,本质上是一个数据工程问题,而不是一个市场问题。它暴露的不是”半导体崩盘”,而是”这个量化库的 ETF 价格序列没有做复权”。一旦补上复权这道工序,两个”暴跌”标的立刻还原成 -27% 和 -23% 的”较大但正常的回调”——市场叙事完全改变,策略动作完全改变。

这件事的教训有三层:

  • 数据层:任何价格序列在进入策略前,必须经过除权检测与复权回填。前复权是策略回测的事实标准,后复权是收益核算的事实标准。把复权物化成独立的 _adj 表,让策略层读不到原始价。

  • 流程层:每日 ETL 写入后、策略读取前,自动跑一次跳变检测,命中即告警并写入公司行动表。一份没有复权的 ETF 涨跌幅榜,比没有数据更危险——因为它会给出一个高度自信但完全错误的结论。

  • 认知层:当某个数字看起来违反常识(单日 -52%、20 日 -65%、无人讨论的”崩盘”),第一步永远是怀疑数据,而不是怀疑市场。市场极少在不被任何人讨论的情况下崩盘;数据却经常在我们不注意的时候悄悄跳变。

最后一句话送给所有写过 ETF 策略的人:你的跌幅榜前两名,大概率不是最该清仓的,而是最该复权的。


数据声明 本文数据来自本地 DuckDB 量化库(/mnt/c/Users/Administrator/clawd/data/quant/quant_v2.duckdb,只读直查),ETF 日线截至 2026-07-29。512480 的份额变动依据上海证券交易所公告《国联安基金管理有限公司关于……实施基金份额……》核验。复权后真实收益为作者基于跳空检测算法自行计算,仅供方法论演示,不构成任何投资建议。文中 SQL 模板可在 DuckDB 0.10+ 直接运行。

延伸阅读

💬 评论