ZVT 项目研究站 · 数据避坑

ZVT 下载成功后,为什么还不能直接拿来回测?

数据在库里,不等于数据能直接用。A 股有六类结构性问题会静默污染结论:停牌与零成交、涨跌停不可成交、ST 与退市、成分股变动、缺失与重复记录、复权口径。这一页把每类问题拆成「现象 → 怎么发现 → 怎么处理 → ZVT 是否代劳」,你可以当成取数后的验收清单。

问题类型:6 类框架代劳:均否(取数能力,非清洗能力)依据源码与 README(2026-09-18)

取数之后、回测之前的六道关

停牌行数缺口
涨跌停不可成交
ST / 退市幸存者偏差
成分变动股票池穿越
缺失 / 重复口径污染
复权口径量级错配
按 A 股研究常见缺陷整理的示意图(非官方图);每一项的处理方式都需要使用者在查询层或策略层自行实现。
Overview

六类问题总表:现象、后果、处理、ZVT 是否代劳

先看这张表建立全局观,下面几节再逐个展开。

问题在数据里的现象对回测的后果处理方式ZVT 代劳?
停牌与零成交该标的在交易日历上「缺行」,或成交量为 0把停牌日当交易日,持仓价格凭空延续按交易日历补行并标记停牌;停牌期间不允许成交
涨跌停价格等于涨跌停价、成交极稀薄在买不到的价格上买入,收益虚高用前收盘价与涨跌停规则判断可成交性
ST 与退市当前清单里没有已退市标的幸存者偏差,长期收益被高估保留退市记录或用历史成分股;标记 ST 期间
成分股变动指数/板块成分只有当前快照股票池穿越,等于提前知道谁被纳入按历史时点取成分(框架有指数成分表,需自己按时间取)部分(提供数据,不自动按时间切)
缺失与重复同一天多行、或关键字段为空统计口径被重复行放大entity_id+timestamp 去重;空值单独处理
复权口径默认表与后复权表价格量级不同因子与成交价错配,长期收益失真全流程锁定一个口径(见「K 线与复权」页)部分(提供口径,需你选对)
为什么框架不代劳:ZVT 的定位是「统一取数与研究框架」,它把数据取下来、按统一 schema 存好;至于这条数据在回测里能不能成交、要不要剔除,取决于你的策略假设。把清洗逻辑写在你的研究代码里,而不是指望框架——这也是为什么「数据质量」必须单独成页。
Suspension

ZVT 数据里的停牌与零成交:先用行数对不上把它找出来

停牌不会报错,只会让某一列悄悄少几行。发现它的标准动作是「和交易日历对齐」。

  1. 取交易日历作为基准

    框架里有交易日历相关的表(聚宽一侧提供 jq_trade_day_recorder)。没有日历表时,至少用指数日线(如沪深 300)的日期序列当作全市场交易日基准。

  2. 按标的比对该有的行数

    df = Stock1dHfqKdata.query_data(code='000338', index='timestamp')
    missing = set(calendar_dates) - set(df.index)   # 疑似停牌日

    说明:这一步不依赖任何框架特性,纯集合差。预期输出:缺失日期列表——通常就是停牌日。

  3. 在策略里显式禁止停牌成交

    拿到缺失日期后,在选股与成交判断里先剔除这些日期:停牌期间既不能买也不能卖。不处理的话,回测会假设你在停牌日以某价格成交。

  4. 零成交也要当停牌处理

    部分数据在停牌日仍有行但成交量为 0。判断条件建议加一条:volume > 0。两条一起用,能覆盖绝大多数情形。

Limit up/down

涨跌停:ZVT 给得出价格,但成交要靠你自己判断

涨停买不到、跌停卖不掉——这是 ZVT 回测里最常见的「看着能赚」的来源。

情形为什么不可成交怎么发现处理方式注意点
一字涨停开盘即封板,全天只有封单开盘价=收盘价=最高价=最低价,且等于涨停价当天不可买入,信号顺延或作废顺延到下一个可成交日的价格可能已经更高
盘中涨停封板期间挂单排队最高价触及涨停价保守做法:触及涨停即视为不可买入严格做法要看封单量与成交笔数,成本高
跌停卖出同样排不上队最低价触及跌停价跌停日不可卖出,止损会失效止损回测必须处理这一点,否则回撤被低估
ST 股涨跌幅限制不同不同板块涨跌停幅度不一样(如 ST、科创板、创业板)按标的类型与所属板块区分阈值按板块维护不同的涨跌停比例统一按 ±10% 判断会出错
新股上市初期上市首日与之后的涨跌幅规则不同结合上市日期判断把上市初期单独处理或直接排除新股上市初期的数据特征与常态差异大
提示:涨跌停判断需要前收盘价与板块规则,这两样都能在你的本地库里查到(前收盘价可用行情表推算)。ZVT 不会替你判断,但数据是齐的。
Survivorship

ZVT 股票池里的 ST、退市与动态成分:最容易让长期收益变好看的偏差

把这两件事放在一起讲,因为它们造成的是同一种错误:你在用「后来才知道的信息」筛标的——而这正是 ZVT 不会替你拦住的那个错误。

退市股去哪了

如果你取的是「当前 A 股清单」,那么期间内退市的标的根本不在表里。这些标的当年往往是跌得最惨的一批,把它们排除,等于把亏损从样本里删掉——长期策略的收益会被系统性高估。

ST 要不要排除

排除 ST 是一种常见的、也需要明说的假设:它确实降低了踩雷概率,但同时也排除了「重组翻身」这一类标的。无论选哪种做法,都要在研究报告里写明规则与生效时点,而不是事后挑一个好看的版本。

股票池要带时间戳

动态股票池的正确形态是「每个时点各一份名单」:按调仓日重建池子,而不是用一份静态名单跑到底。框架提供了标的清单、指数成分、板块成分这类原料,但按时间切片要你自己做。

自查问法:「我的股票池是哪一天生成的?那一天它能包含哪些标的?」——如果答不上来,长期回测结论就先别信。
Hygiene

缺失、重复与更新时间:取数后的三分钟体检

这三项检查成本极低,但能拦住一大批「数据脏」导致的问题。建议做成脚本,每次 ZVT 写库后自动跑一遍。

检查项怎么写合格线不合格时怎么办
行数是否等于交易日数与交易日历取交集比较(停牌日需排除)缺行只出现在停牌日补写缺失区间,或确认是否停牌
是否有重复记录entity_id + timestamp 分组计数,看是否有 count>1全为 1provider 找出来源,去重并固定单一来源
关键字段空值率对价格、成交量、财务关键列算 isnull().mean()非停牌日价格不应为空空值多的列不要直接用于因子
时间范围是否符合预期min / max 时间戳与标的上市日期、你的需求区间一致补齐区间;确认是否上市前数据
最近更新时间看最大 timestamp 与当天日期差差 1 个交易日以内重新跑增量更新;检查数据源是否失效
跨源一致性同一标的两家 provider 的收盘价做差差异在可解释范围内固定一家为基准,差异写进研究记录
FAQ

A 股数据避坑常见问题

前半部分回答数据层事实,后半部分是通用研究规范,框架不提供现成功能。

ZVT 会自动处理停牌和涨跌停吗?

不会。它负责把数据取下来并按统一 schema 存好;停牌日往往表现为「缺行」,涨跌停日则是有行但不可成交。这两件事都需要你在查询与策略层显式处理。

怎么判断一只股票当天是不是停牌?

最可靠的做法是与交易日历比对:交易日历有、行情表没有的那几天基本就是停牌。另一个补充条件是成交量为 0。两者结合足够覆盖绝大多数情况。

退市股票还能取到历史数据吗?

取决于数据源与取数方式。如果标的还在源的历史接口里,用 code 直接取通常可以拿到上市期间的行情;但如果你的股票池来自「当前清单」,这些标的就不会出现在池子里。这是两件事,别混起来。

ST 股应该剔除吗?

没有标准答案,但必须有明确规则并按规则执行。常见做法是:调仓日处于 ST 状态的标的排除,且规则在回测开始前固定。事后修改规则会让结果不可信。

同一天出现两条记录怎么处理?

先按 provider 拆开,确认是不是两家源各写了一条;如果是同一源重复,按 entity_id+timestamp 去重并保留最新写入。治本的做法是固定一个 provider 作为研究基准。

数据库多久更新一次比较合适?

日频研究每天收盘后增量更新一次即可(框架是增量写入,重复运行不会重复拉取)。关键是加一个「最大时间戳距今超过 N 个交易日」的告警,而不是靠人记得跑。

这些检查有没有现成脚本?

ZVT 没有内置数据体检工具。本站建议把上面六项做成一个小脚本,每次写库后自动跑一遍——这也是把「数据能用」和「数据在库里」区分开的最省力方式。

数据干净了,最后一步是把成交假设补齐

信号、手续费、滑点、可成交性与仓位,这五件事决定了回测结果能不能当研究结论用。