作者:AhaSignals。示例针对 ahasignals-pit 0.1.1,Python 3.9+。
如果在 Python 回测中加入财务因子,除了行情和信号时间,还需要检查财务数据本身何时可用。本文用五个可运行的合成测试演示这一检查,适合放在因子输入进入回测引擎之前。代码独立运行,不依赖 VeighNa,也不是 VeighNa 插件。
财务因子使用的数据有多个时间。一个季度在 3 月结束,不意味着该季度的财务结果在 3 月已经可用;今天下载到的历史数值也可能包含后来才公开的修订。
一张速查表
| 字段或证据 | 回答的问题 | 不能替代什么 |
|---|---|---|
start / end |
这个数值描述哪个报告期间? | 披露或可交易时间 |
acceptedAt |
调用方记录的文件受理时间是什么? | 系统实际取得时间、新闻提前披露时间、市场传播或成交时间 |
observedAt |
系统何时观测到这个版本? | 不能用今天的下载时间伪造历史观测 |
| accession / document hash | 回答使用哪个版本? | 哈希格式正确不等于源文件真实或时间戳可信 |
| concept / unit / dimensions | 概念、单位和合并范围是否一致? | 数值相近不代表口径相同 |
重建披露可用性与重建系统实际历史访问,是两个不同的问题。前者可以按可信披露时间筛选,后者还需要当时的采集记录。若采用文件受理时间作为披露代理,应明确这一假设,不将其等同于所有信息首次进入市场的时间。
一个足以改变答案的反例
以下为完全合成的数据,金额单位 USD:
| 版本 | 报告期 | 值 | 受理时间(UTC) | 采集时间(UTC) |
|---|---|---|---|---|
| 原始 | 2025-01-01 至 03-31 | 120 | 05-01 00:00 | 05-02 00:00 |
| 修订 | 同上 | 90 | 08-01 00:00 | 08-02 00:00 |
4 月 30 日不能使用任一记录。5 月 3 日可以使用原始值 120,不能把修订值 90 回填到这个截点。9 月 1 日则可以选择已经取得的修订版本。
5 月 1 日中午还有一个区别:按受理时间重建可以得到 120;要求系统已观测到该版本时,应拒绝回答,因为采集发生在次日。
这些规则可以用数据库的版本记录与 as-of 查询实现。下面用一个限定范围的 Python 库演示相同检查,便于复现和阅读拒绝原因。
完整可运行示例
在自己的 Python 虚拟环境中安装:
python3 -m pip install ahasignals-pit==0.1.1
将下方完整代码保存为 pit_time_demo.py,执行 python3 pit_time_demo.py。不需要 JSON 文件、GitHub 访问权限或外部数据接口。
"""AhaSignals synthetic point-in-time tutorial, tested with ahasignals-pit 0.1.1.
No issuer data, real documents or authenticated timestamps are used.
"""
from ahasignals_pit import select_fact
identity = {
"cik": "0000000001", "taxonomy": "us-gaap",
"concept": "NetCashProvidedByUsedInOperatingActivities", "unit": "USD",
"start": "2025-01-01", "end": "2025-03-31", "dimensions": [],
}
original = {
**identity, "value": 120, "accession": "0000000001-25-000001",
"acceptedAt": "2025-05-01T00:00:00Z",
"observedAt": "2025-05-02T00:00:00Z",
"sourceUrl": "https://example.org/synthetic-original",
"documentSha256": "0" * 64, "contextId": "synthetic-q1",
}
revision = {
**original, "value": 90, "accession": "0000000001-25-000002",
"acceptedAt": "2025-08-01T00:00:00Z",
"observedAt": "2025-08-02T00:00:00Z",
"sourceUrl": "https://example.org/synthetic-revision",
"documentSha256": "1" * 64,
}
facts = [original, revision]
cases = [
("before disclosure", "2025-04-30T12:00:00Z",
"disclosure-reconstruction", "withheld", None, "source-after-cutoff"),
("disclosure reconstruction", "2025-05-01T12:00:00Z",
"disclosure-reconstruction", "answer", 120, "latest-eligible-exact-context"),
("not yet observed", "2025-05-01T12:00:00Z",
"observed-pipeline", "withheld", None, "observation-after-cutoff"),
("future revision excluded", "2025-05-03T12:00:00Z",
"observed-pipeline", "answer", 120, "latest-eligible-exact-context"),
("revision now eligible", "2025-09-01T12:00:00Z",
"observed-pipeline", "answer", 90, "latest-eligible-exact-context"),
]
for label, cutoff, mode, status, value, reason in cases:
result = select_fact(facts, {**identity, "cutoff": cutoff, "mode": mode})
assert (result["status"], result["value"], result["reason"]) == (status, value, reason)
print(f'{label}: {result["status"]}, value={result["value"]}, reason={result["reason"]}')
预期输出:
before disclosure: withheld, value=None, reason=source-after-cutoff
disclosure reconstruction: answer, value=120, reason=latest-eligible-exact-context
not yet observed: withheld, value=None, reason=observation-after-cutoff
future revision excluded: answer, value=120, reason=latest-eligible-exact-context
revision now eligible: answer, value=90, reason=latest-eligible-exact-context
withheld 表示规则下无法回答;它的数值为 None,不是零。invalid 表示输入格式或必填字段不符合契约。实际来源字段及时间戳需由调用方独立验证,示例中的 URL 和占位哈希不对应任何真实文件。
另一个常见错误:把累计现金流当作季度
若经营现金流上半年累计为 280、一季度为 120,二季度为 160。同期购建 PP&E 现金支出累计为 90、一季度为 50,二季度为 40。因此二季度经营现金流减该现金支出为 120,而上半年累计差额为 190。
可运行安装包自带的合成例子:
python3 -m ahasignals_pit --example quarterly-cash > pit-quarterly-example.json
python3 -m ahasignals_pit pit-quarterly-example.json
新文件由第一条命令的重定向创建;已有同名文件会被覆盖。预期 JSON 包含 operatingCash: 160、cashPpe: 40、cashAfterPpe: 120。这里的差额不是涵盖所有投资支出的通用自由现金流定义。
能力边界
这个 alpha 版本根据调用方提供的时间戳和精确上下文选择事实,也提供限定概念的季度现金流计算。它不下载或验证 SEC 文件,不提供股价、交易策略、回测绩效,也不自动解决幸存者偏差、复权数据修订、交易成本或执行时点。案例不证明策略收益,不重现相关论文的模型评分。
独立实现还需要同样保留版本和可用时间。严格的系统历史回放需要在当时保存证据;现在补采历史文件无法恢复一个从未保存过的历史数据状态。
参考
本文仅用于研究与教育。