ZVT 项目研究站 · 点时数据

ZVT 点时数据与未来函数:六个时间概念,决定你的回测数字是真是假

「把数据取下来」和「用对数据」是两件事。回测里最常见的失真不是代码写错,而是用了当时还不存在的信息:拿今天才公布的财报去回测去年、拿现在的成分股去回测历史、用当天收盘价当作当天成交价。这一页把六个时间概念讲清楚,并给出 ZVT 里能真正落地的抓手。

核心抓手:report_date / get_recent_report_date成交侧:due_timestamp 与 happen_timestamp依据源码与 README(2026-09-18)

同一条财报数据的六个时间点

报告期末数据所属期间
公告日首次可被公众获得
因子计算日你在哪天算它
成交日due/happen 两个时间戳
按财务数据在回测中的真实流转顺序自绘(非官方图);四个环节的概念拆分与 ZVT 源码里的字段/参数一一对应。
Six times

六个时间概念:各自是什么、错了会怎样

下面这六个名词经常被混着用。它们在回测里的先后顺序是固定的,任何一次错位都会让结果偏乐观。

时间概念含义在 ZVT 里对应什么错位的后果怎么自查
报告期数据所属的会计期间(如 2024 年报、2025Q1)report_period(String 列,README 用 'year' 过滤)把不同期的数据混在一张横截面上打印该列所有唯一值,确认口径
公告日该期数据首次对外披露的日期report_date(DateTime 列)——是否等于公告日需自行核验用还没公布的数据做决策拿一家熟悉公司的公告日与库里日期对照
数据可获得日你的本地库真正能查到这条数据的日期(受抓取/入库时间影响)框架没有专门列;与 provider 写入时间相关回测时点早于入库时点 → 事实上用了未来的数据记录每次写库日期;对历史回测按公告日重排
因子计算日你在哪一天计算出这个因子值由你的代码决定(query 的时间范围)把未来数据算进因子在因子函数里打印实际用到的时间范围
信号日产生买卖决策的那一天策略里 on_time(timestamp)timestamp用当天收盘数据当天成交检查信号所用数据的最晚时间戳
成交日实际以什么价格成交trade_the_targets(due_timestamp=..., happen_timestamp=...)无手续费、无滑点、无 T+1核对两个时间戳是否被赋成同一天
为什么把它放在第一张表:只要这六个时间点在研究文档里写不清,后面所有绩效数字都无法复现——不是因为代码不严谨,而是因为「用了什么信息」这件事没有被记录。
Three classic bugs

三类典型未来函数与它们的自查方法

这三类在量化研究里反复出现,且共同点是——代码跑得通、曲线很好看。

类型典型写法为什么是未来函数怎么自查正确做法
财务数据穿越report_period == '2024' 的报告在 2024 年初就选股2024 年报要到 2025 年才公布把所用数据的可得日期与信号日期并排打印按公告日做 as-of 对齐(ZVT 里的锚点是 report_date,需先核验它等于公告日)
幸存者偏差 / 股票池穿越用「今天的股票清单」跑 2018 年的横截面当年的退市股、未上市股被排除,等于提前知道谁能活下来看股票池的来源时间:是不是查询了当前清单用历史成分(如指数成分股表)或在清单里保留退市记录
同价成交 / 收盘价穿越用当天收盘价产生信号并用同一天收盘价成交收盘价要收盘后才知道,实盘无法在这个价格成交检查 due_timestamphappen_timestamp 是否被赋成同一天信号用 T 日数据、成交放 T+1(框架用两个时间戳把这件事显式化)
一句话记忆法:问自己「在信号日那一天,这条信息我到底能不能看到?」——答案是否定的,就是未来函数。这条判断标准不需要任何框架知识。
What ZVT gives you

ZVT 里真正可用的四个抓手

下面是源码与 README 里能查到的具体机制——它们不是为「防止未来函数」设计的功能,但可以被你这样用。

  1. 两个时间字段(report_period / report_date)

    按源码,FinanceFactorreport_period = Column(String(32))report_date = Column(DateTime)。前者筛期间类别,后者做时间对齐。把「期间」与「日期」分开是这套设计里最值得利用的一点。

  2. get_recent_report_date(timestamp):给定时间点,取最近的报告日期

    这个函数存在于 src/zvt/api/portfolio.py,README 的示例策略里用它来判断「当前能看到的最近一期报告」。这正是 as-of 对齐需要的原语:回测时间点 → 当时可见的报告期

  3. due_timestamphappen_timestamp:把「信号时点」和「成交时点」拆开

    trade_the_targets(due_timestamp=..., happen_timestamp=...) 要求显式传两个时间。把 happen 设为 T+1,就在接口层面把「当天信号当天成交」这个漏洞堵住了。可用的另一个参数是 profit_threshold(官方示例里设为 None 表示不启用止盈)。

  4. provider 列:确认数据来自哪一家

    同一张表可能混写多个源,而不同源的更新时间与口径不同。做时间一致性检查时按 provider 拆开,能排除「同一指标两家源打架」造成的假信号。

Checklist

回测前自查清单:八项,逐条打勾

把这份清单存进你的研究记录模板。任何一项答不上来,绩效数字就还不能对外说。

#检查项怎么查不合格的典型表现
1财务数据用的是公告日还是报告期get_recent_report_date 或按 report_date 加时间条件代码里出现 report_period == '2024' 却用在 2024 年内选股
2股票池是不是历史时点的查股票池来源表与其时间字段用「当前 A 股清单」回测 2018 年
3信号的成交时点是否晚于信息时点核对 due_timestamphappen_timestamp两个参数传了同一个 timestamp
4是否计入手续费、印花税与滑点看回测是否对这些成本建模零成本回测
5停牌与涨跌停是否被排除在可成交之外对照行情数据里停牌/一字板日期涨停当天按涨停价买入
6复权口径是否全流程一致检查数据表、因子、基准用的是不是同一口径因子用后复权、成交价用真实价
7样本内外的划分是否事先确定看研究记录里有没有时间切分规则调参调到样本外也好看为止
8绩效指标是否包含回撤与波动率至少报告年化、夏普、最大回撤、换手只报收益率
证据边界:本页对 ZVT 的具体描述均来自源码(domain/fundamental/finance.pyapi/portfolio.py)与 README 示例;清单里第 4–8 项属于通用回测规范,框架替你完成,需要你在策略与数据层自行实现。本站未在本机运行 ZVT,不提供任何绩效数字。
Code

把清单落成代码:三段可以直接抄的自查

清单不落到代码就会变成口号。下面三段都不依赖 ZVT 的框架特性,直接在你的研究脚本里跑。

  1. 检查一:股票池是不是「当时」的

    # 池子里的标的是哪天定下来的?
    pool = Stock.query_data(filters=[...], columns=['entity_id', 'list_date', 'end_date'])
    print(pool['list_date'].max(), pool['end_date'].notnull().sum())

    说明:如果清单里所有标的都是「至今仍在交易」,那它就带幸存者偏差。预期:能看出池子里是否保留了已退市记录(end_date 非空的条数)。

  2. 检查二:信号与成交的时间戳是否被拉开

    # 在 on_time 里打印两个时间,确认不是同一天
    self.logger.info(f"signal={timestamp} due={due_ts} happen={happen_ts}")

    说明:trade_the_targets(due_timestamp=..., happen_timestamp=...) 两个参数被赋成同一天,就是「当天信号当天成交」。预期:日志里出现至少一个交易日的间隔。

  3. 检查三:因子用到的最晚数据日期

    # 财务因子:把「实际用到的最晚可得日期」打出来
    latest = df.index.get_level_values('timestamp').max()
    print('factor uses data up to', latest, 'signal at', timestamp)

    说明:如果 latest 晚于信号日回到的全部日期,说明用到了未来数据。预期:latest <= timestamp。这条断言建议直接写成 assert,跑不过就中断回测。

为什么用 assert 而不是靠自觉:未来函数的特点是「结果变好但不会报错」。把它变成一条会失败的断言,比写在文档里有效得多。
FAQ

点时数据与未来函数常见问题

涉及框架机制的回答都给出源码位置;涉及通用规范的部分标明「非框架能力」。

什么是未来函数(前视偏差)?

在回测的某个时点使用了当时还不可能获得的信息。它不会让代码报错,只会让结果变好——所以必须靠流程和自查发现,不能靠调试。

ZVT 会自动帮我避免未来函数吗?

不会。框架提供的是原料与接口:两个时间字段、get_recent_report_date、以及把信号与成交时点分开的 due_timestamp / happen_timestamp。真正的 as-of 对齐逻辑要你自己写。

财报的 report_date 就是公告日吗?

官方文档没有定义,不能假设。请做一次对照实验:挑一家公司,把公开的年报公告日与库里的 report_date 并排打印。一致才能当公告日用;不一致就要另找公告日来源。

用当前股票清单回测历史,偏差有多大?

取决于期间内退市与上市的数量,无法给一个通用数字。但机制是明确的:这条清单已经见过「谁能活下来」,所以任何长期横截面回测都会偏乐观。正确做法是使用历史成分或保留退市标的。

日内策略也需要注意这些吗?

一样需要,而且更敏感:分钟级数据同样存在「信号时点 vs 可成交时点」的问题,且涨停、停牌在日内会造成完全不可成交的价格。ZVT 提供分钟级数据,但可成交性判断要自己做。

本站为什么反复强调这件事?

因为它是本站与「只介绍 ZVT 能做什么」的文章最大的区别:那些内容告诉你如何取数,这一页告诉你取到的数什么时候才能用。这一条做错,后面所有优化都是在拟合噪声。

时间轴理顺之后,去看数据本身还有什么坑

除了时间错位,A 股数据还有停牌、涨跌停、ST 退市、缺失与重复这些「不处理就会污染结论」的问题。