Completeness-aware release control for staggered-arrival cross-sectional data.
Audit registry · Pledge · Papers · Result schema
Rebuilt from as-filed SEC EDGAR filings, on observed filing dates, 7 of 14
standard fundamental signals are contaminated by incomplete-cross-section
leakage — measured, reproducibly, in the companion registry.
This tool measures each signal's susceptibility before release and withholds
only the signals that need it. The screen costs one fit_trailing call per
signal.
The shipped fixed-seed demo, where the right answer is planted: naive early
release carries a systematic bias of −0.386 on the strong-leak signal; the gate
routes it to the deadline (bias exactly 0.0) while releasing the two benign
signals at 36–39% completeness. tests/test_reproduces_paper.py pins these
numbers; tools/make_readme_chart.py redraws
this figure from the live demo.
You need this if:
- you build same-period cross-sectional signals — industry-adjusted ratios, cross-sectional ranks, peer medians — on entities that report on their own schedule;
- you rebuild panels from as-filed sources (EDGAR
companyfacts, raw filings) instead of using a vendor's curated release; - you own a feature-store pipeline where an as-of join reads whatever has arrived by t;
- you want a per-signal susceptibility number published next to every released signal, the way a standard error is.
Not for you if you are hunting look-ahead bugs in a backtest engine, or your data has no arrival times — that is a different failure mode (scope statement).
No telemetry — structurally, not merely by default: no module in this package imports a transport, and a test enforces that over every module.
pip install pit-release-gateRequires Python 3.10+ (numpy, pandas, scipy).
import numpy as np
from pit_release_gate import SusceptibilityGate, ReleaseController, make_group
rng = np.random.default_rng(0)
# 1. Fit the susceptibility gate on prior COMPLETED periods (honest estimation:
# never on the period being gated — its cross-section is still incomplete).
train = [make_group(c_a=0.3, c_x=0.7, rng=rng) for _ in range(10)]
gate = SusceptibilityGate(threshold=0.10)
rho = gate.fit_trailing(train)
# 2. Gate a fresh, live period with the frozen estimate.
controller = ReleaseController(gate=gate)
live = make_group(c_a=0.3, c_x=0.7, rng=rng)
decision = controller.run_until_release(live, policy="gated")
print(f"rho_hat={rho:+.3f} -> {decision.action} "
f"at completeness {decision.completeness:.0%} ({decision.policy})")To gate your own data, build an AsOfDataStore from your design matrix, signal
values, and per-entity filing-arrival times, then call
ReleaseController.decide(store, t) at each evaluation time — it returns
WITHHOLD, REWEIGHT_RELEASE, or RELEASE plus the released values.
One long table — one row per (entity, period) — and one call. The table can be a pandas DataFrame, a polars DataFrame, a pyarrow Table, or a dict of arrays: columns are read by duck typing, so the screen itself depends on no dataframe library.
from pit_release_gate import screen_dataframe
record = screen_dataframe(
panel, # columns: period, arrival, value, size
value=["accruals", "roa"], # one signal or several
trailing_k=5, # fit on 5 prior COMPLETED periods, then freeze
)
for s in record["signals"]:
print(s["name"], s["verdict"], s["periods_flagged"], "/", s["periods_screened"])Or without writing any Python:
pit-release-gate --csv panel.csv --value accruals --value roa --export results.jsonscreened panel.csv: 2 signal(s), trailing_k=5, threshold=0.1
signal periods flagged mean rho max |rho| phi_req verdict
accruals 7 7 -0.8721 0.8841 1.000 susceptible
roa 7 0 +0.0268 0.0564 0.377 benign
totals: 1 benign, 1 susceptible, 14 signal-cycles screened
This is the same frozen protocol the published audits
use: the estimate applied to a period is never fitted on that period, and the
first trailing_k periods are used for fitting only. The output is a
pit-screen-results v1.0 record — the file a screened with badge should
point at.
One caveat, stated up front. A verdict fires if any single screened period
crosses the threshold, so it inherits that period's sampling noise
(roughly 1 / sqrt(trailing_k x entities_per_period)). On a small panel,
a reading just over the threshold may be noise. Establish your noise floor
first — screen a signal you expect to be unexposed, or shuffle arrival order
within periods and re-screen — before treating a marginal verdict as a finding.
| guarantee | as-of join / bitemporal store | purged & embargoed CV | pit-release-gate |
|---|---|---|---|
| No value was read before it was available | ✅ | — | assumed as input (bring your arrival times) |
| Train and test don't overlap through time | — | ✅ | — |
| The set of entities present at t was not selected on the disturbance | ❌ | ❌ | ✅ measured per signal (ρ̂), gated per signal |
A point-in-time-correct join over an incomplete cross-section is a correct join over a biased sample. Statement of need has the full argument; the as-of join methodology page has the measured demonstration.
Five public components, all importable from the top-level pit_release_gate package:
| component | what it does |
|---|---|
AsOfDataStore |
Holds one period's as-filed records for a cross-sectional group: design matrix, signal values, and a filing-arrival time per entity. |
CompletenessMonitor |
Reports the arrived fraction at an evaluation time, plus a composition-shift gauge for the arrived subset. |
SusceptibilityGate |
Estimates ρ̂, the partial correlation between filing latency and the complete-cross-section residual given observables. fit_trailing enforces the honest-estimation contract: prior completed periods only. |
PropensityReweighter |
Inverse-filing-propensity weights. Included to make a negative result executable: reweighting on observables corrects composition, but cannot remove selection on the disturbance. |
ReleaseController |
Maps |ρ̂| to a required completeness φ_req = min(1, φ_min + κ·|ρ̂|) and returns WITHHOLD / REWEIGHT_RELEASE / RELEASE at each evaluation time. |
make_group, run_demo and demo (simulate.py)
generate and run the known-ground-truth worked example described below.
The package ships a self-contained worked example with a planted leakage strength, so the right answer is known exactly and no licensed data is needed:
pit-release-gate # or: python -m pit_release_gateReal output, abridged to the signal the gate exists for:
Strong-leak (c_a=1.0, c_x=0.7) rho_trailing=-0.868 (fitted ex ante) (SUSCEPTIBLE -> wait)
policy comp% [95%CI] flip% [95%CI] biasB(signed) [CI] route
naive 35 ± 0.0 63.6 ± 2.8 -0.386 ±0.037 naive
deadline 100 ± 0.0 0.0 ± 0.0 +0.000 ±0.000 deadline
gated 100 ± 0.0 0.0 ± 0.0 +0.000 ±0.000 gated(phi_req=1.00) <-- gated
It compares five release policies (naive, threshold, reweight, deadline,
gated) on four signal types. Headline behavior:
| signal | susceptibility ρ̂ | gated releases at | gated bias |
|---|---|---|---|
| Clean | ≈ +0.005 (benign) | 36% completeness | ≈ 0 |
| Composition (selection on observables only) | ≈ −0.036 (benign) | 39% completeness | ≈ 0 |
| Mild leak | ≈ −0.53 | 88% completeness | −0.099 (naive: −0.319) |
| Strong leak | ≈ −0.87 | 100% (deadline) | exactly 0.0 (naive: −0.386) |
A sensitivity sweep of the policy slope κ shows the timeliness–bias dial:
κ = 0.5 → release at 59% completeness (bias −0.229); κ = 1.0 → 83% (−0.118);
κ = 2.0 → 100% (bias exactly 0). The demo is deterministic (fixed seed), and
tests/test_reproduces_paper.py asserts these numbers.
When the entities of a cross-section report on staggered dates — companies filing financial statements are the canonical case — any same-period cross-sectional signal computed before the last filer arrives is estimated from an incomplete, and possibly selectively incomplete, cross-section. If filing timing depends on the very disturbance the signal measures, releasing early produces a systematic bias (incomplete-cross-section leakage), while a blanket wait-for-the-deadline rule removes the bias at a timeliness cost paid by every signal, biased or not.
Point-in-time discipline in ML pipelines currently rests on tooling that answers one question: was this value readable at time t? Feature-store as-of joins, bitemporal and vintage-aware storage, and purged or embargoed cross-validation all enforce read-time correctness, and they do it well.
None of them answers a second question: given that every value read was legitimately
readable, was the set of entities that had reported by t a selected sample? An
as-of join over an incomplete cross-section is a correct join over a biased sample.
The two failures need different remedies — the first is fixed by timestamp hygiene,
the second only by waiting or by an explicit correction. Researchers building
cross-sectional signals on staggered-arrival panels have had no routine, per-signal
screen for the second. pit-release-gate is that screen, plus the release controller
that acts on it: one fit_trailing call per signal, so reporting a susceptibility
estimate alongside a released signal costs about as much as reporting a standard
error.
pit-release-gate --export results.jsonwrites a pit-screen-results v1.0 record: per screened signal, how many periods
were screened, how many the measure flagged, mean and max ρ̂, the required
completeness that was assigned, and the verdict (benign / susceptible), plus
the five settings that produced those verdicts. It is the file a screened with
badge should point at. The format is a standalone versioned interchange spec —
docs/results-schema.md — that any other tool is free
to emit or consume.
--export is fully offline: it writes a local file and makes no network
call. Records carry summary statistics only — never input rows, file paths,
usernames, hostnames, or any environment detail beyond the tool version — and no
clock is read while building one, so the same screen always exports byte-identical
bytes.
Where the record goes afterwards is entirely your business — commit it next to
your badge, publish it, or keep it. This tool does not send it anywhere. There is
no background thread, no atexit hook, no anonymous usage counter, and nothing
to opt out of.
Programmatic use, for screens on your own data:
from pit_release_gate import build_results, screen_config, summarize_signal, validate_results
sig = summarize_signal("accruals", rhos=[...], phi_reqs=[...], rho_threshold=0.10)
record = build_results([sig], screen_config(0.10, 0.35, 1.0, trailing_k=8, min_entities=6))
assert validate_results(record) == []The method and its evaluation are developed in three public papers:
- Correct-by-Construction Factor Computation: A Verifiably Point-in-Time Engine for Tradeable Signals — doi:10.6084/m9.figshare.32952482
- Measuring Incomplete-Cross-Section Leakage: A Matched Placebo, a Susceptibility Screen, and Evidence from Taiwan and US As-Filed Data — doi:10.6084/m9.figshare.33061955
- Susceptibility-Graded Release Control: Preventing Incomplete-Cross-Section Leakage in Financial Machine-Learning Pipelines without a Blanket Timeliness Penalty — doi:10.6084/m9.figshare.33158615
This package is the reference implementation of paper 3's release controller; its demo reproduces the paper's controlled experiment.
If you have run the susceptibility screen on your own data — whatever the result — you are welcome to say so:
[](https://github.com/MaxWellApexLab/pit-release-gate)The badge reads screened with, not passed — it states that the screen was run, the same way a formatter badge states that the formatter was run. A benign result and a susceptible result are equally worth badging; the second one arguably more, because it means the screen found something and your pipeline now waits for it.
Make it point at something. A badge is worth reading only if there is evidence behind
it. Commit your screen output — pit-release-gate --export results.json produces exactly
that file: which signals came out benign, which came out susceptible, and the required
completeness each was assigned — and link the badge at it rather
than at this repo. A worked example is the OSAP screen in the
PIT audit registry.
Related: the PIT Hygiene pledge is a broader, tool-neutral statement about how a staggered-arrival pipeline is built; this badge is the narrower statement that this particular screen was run.
See CITATION.cff. If you use this software, please cite paper 3:
@article{wu2026releasecontrol,
title = {Susceptibility-Graded Release Control: Preventing Incomplete-Cross-Section
Leakage in Financial Machine-Learning Pipelines without a Blanket
Timeliness Penalty},
author = {Wu, Kuan-Ta and Wu, Kuan-I},
year = {2026},
doi = {10.6084/m9.figshare.33158615}
}- Report a bug or request a feature: open an issue at github.com/MaxWellApexLab/pit-release-gate/issues.
- Contribute: see CONTRIBUTING.md for the development setup, test requirements, and pull-request process.
- Get help: open an issue with a minimal reproducing example, or email maxwellapexlab@proton.me.
MIT — see LICENSE.
Patent pending: this software implements techniques described in pending U.S. patent applications. The MIT license above governs use of this code.
Max Well Apex LLC — maxwellapexlab@proton.me
