pit-adjuster
Point-in-time fixed-basis back-adjustment engine for daily price history: rebuild prices so that any day reads exactly what that day could have known — plus drift detection for vendors that silently switch adjustment conventions. Python 3.11+, zero dependencies, Windows / Linux / macOS.
Status: v0.1 — alpha. The adjustment math is battle-tested inside a production research pipeline, but this standalone package is new: expect the CLI and schema to shift before v1.0.
Why this exists
A-share (and most equity) history arrives from vendors in current-vintage adjusted form. Two silent dangers:
- The convention itself is not point-in-time. Prices you see today embed every adjustment event that ever happened — including events that were announced after a historical date. A backtest that uses them reads the future.
- Vendors switch conventions silently. One day your data source starts serving forward-adjusted prices where it served back-adjusted prices yesterday. Nothing in the CSV changes shape; every historical signal silently changes value.
pit-adjuster rebuilds history from two ingredients — current-vintage
forward-adjusted (qfq) bars plus a point-in-time corporate-action archive
— into a fixed-basis back-adjusted (hfq) chain where each day's price depends
only on events whose ex-date is on or before that day. Then it checks: did
the rebuild invert the vendor chain correctly, and does the vendor chain
still agree with live raw prices today?
Philosophy
Price history must be reversible. A research pipeline that cannot prove its
prices were knowable in the past is not doing backtesting — it is doing
wishful thinking. pit-adjuster treats look-ahead freedom as a
verifiable property, not a style preference:
- PIT principle — every price, factor, and calibration depends only on information available at that historical point. See Kelly et al., "Scaling Point-in-Time Language Models" (NBER w35247) and Look-Ahead-Bench (arXiv:2601.13770) for why the whole industry is converging on this.
- Look-ahead bias is measurable — Daniel, Sornette & Wohrmann (2008), "Look-Ahead Benchmark Bias in Portfolio Performance Evaluation" (arXiv:0810.1922) quantify how ex-post benchmark construction inflates performance. A vendor that silently swaps adjustment conventions is doing exactly this, inside your price column.
- Formal ground — Fonseca (2026), "Look-Ahead-Freedom as Temporal Non-Interference" (arXiv:2607.04958) proves look-ahead-freedom is undecidable in general (Π⁰₁-hard when availability depends on data values), but admits a linear-time decidable type-effect system on the value-independent fragment — windowing, resampling, joins, PIT and vintage reads.
Honest boundary: this package implements verifiable checks for the value-independent fragment of the problem (factor chains, ex-date ordering, snapshot equivalence, chain inversion). For the general value-dependent case we fall back to heuristic guards and say so explicitly — verifiability is claimed only where the theory allows it.
Quick start
# install from PyPI (once published)
pip install pit-adjuster
# or run without installing anything:
# PYTHONPATH=src python -m pit_adjuster --help
# try it on synthetic data (builds a fake qfq history + action archive,
# rebuilds to hfq, runs invert-check and drift-check)
python examples/demo.py
Rebuild your own history:
padj rebuild \
--bars bars.json --actions actions.json \
--as-of 2026-08-11 --code 600000 --out hfq.json
padj invert-check --bars hfq.json --actions actions.json --as-of 2026-08-11
padj drift-check --bars hfq.json --actions actions.json \
--as-of 2026-08-11 --live live_closes.json
padj rebuild is the workhorse: it inverts the vendor qfq chain back to raw
prices, then re-applies only events whose ex-date is on or before each bar
date (fixed basis at the archive coverage start). Raw open/close are kept
alongside adjusted prices so execution-level work can map back to nominal
prices.
Commands
| Command | What it does |
|---|---|
rebuild |
Rebuild bars to fixed-basis hfq: open/high/low/close adjusted, raw_open/raw_close nominal, adj_factor cumulative multiplier, volume normalized to shares |
invert-check |
Ex-date continuity sanity check: is raw_{ex-1} × factor_e ≈ raw_ex? Informational — real ex-dates carry overnight returns, so violations can be false positives |
drift-check |
Static forward-adjustment detection. Compares inverted raw closes against live raw closes; divergence above tolerance is authoritative — a vendor chain that no longer matches the archive |
snapshot-equivalence |
Compare two rebuilt outputs (e.g. old and new pipeline versions) date-by-date within tolerance — the "did anything change?" gate |
version |
Print version |
Global flags: --help on every subcommand; JSON outputs via --out where
supported; everything else prints a human-readable summary.
Data model
Bars — a JSON list of daily bars, each with at least date (ISO) and
close; open/high/low/volume/amount/turnover are preserved through the
rebuild:
{"date": "2026-06-12", "open": 95.0, "high": 96.0, "low": 94.5, "close": 95.5, "volume": 1234500}
Actions — a point-in-time corporate-action archive, one record per
event, with ex_date, adjustment_factor and available_at:
{"ex_date": "2026-06-15", "adjustment_factor": 0.95, "available_at": "2026-06-14T18:00:00", "action_type": "cash_dividend_stock_distribution"}
Invalid records (missing ex-date, non-positive or non-finite factor) are
dropped; only events with ex_date <= as_of_date participate. The schema
lives in schema/corporate-action.schema.json.
Adjustment math
Standard A-share factor math (as documented by exchange reference-price rules):
factor_e = (prior_close - cash) / (prior_close * (1 + bonus + transfer))
qfq_t = raw_t * prod_{e: ex_date_e > t} factor_e
hfq_t = raw_t * prod_{e: ex_date_e <= t} (1 / factor_e)
rebuild inverts the vendor qfq chain back to raw prices, then applies the
hfq chain with a fixed basis at the archive coverage start. Key property
(under test): hfq and qfq yield identical adjusted returns for the same
factor chain, while hfq additionally guarantees that a price at time t is
untouched by events with ex-date after t.
Volume normalization follows the A-share convention: most codes store volume
in lots (×100 to shares); STAR-market codes (688/689 prefixes) store native
shares. Both are parameterizable — see --volume-to-shares and the
native_share_prefixes argument in rebuild_bars.
Verification model
pit-adjuster never trusts its inputs:
invert-check— factor continuity at ex-dates (sanity, false-positive tolerant)drift-check— inverted raws vs live raws (authoritative divergence detection; this is the "static forward-adjustment detector" — if a vendor swaps conventions, this fires)snapshot-equivalence— before/after equivalence of two rebuilds, the reproducibility gate for pipeline migrations
Every check is read-only. Nothing here trades, prices, or decides.
Development
python -m pip install -e . pytest
python -m pytest
CI runs the full test suite on Ubuntu, Windows and macOS with Python 3.11 and 3.12. Issues are handled on weekends; pull requests are welcome.
Related work
- Daniel, Sornette & Wohrmann (2008) — look-ahead benchmark bias, quantified
- Fonseca (2026) — look-ahead-freedom as temporal non-interference (the verifiability boundary)
- Point-in-Time Backtesting: A Formal Bias Taxonomy (Mathematics 2026, 14(12):2182)
- Kelly et al., Scaling Point-in-Time Language Models (NBER w35247)
- Look-Ahead-Bench (arXiv:2601.13770) — measuring look-ahead bias in PIT LLMs
License
MIT
Metadata
Release files for pit-adjuster 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pit_adjuster-0.1.0.tar.gz | 19.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pit_adjuster-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 34.1 kB
Release files / pit_adjuster-0.1.0.tar.gz
| Download URL | pit_adjuster-0.1.0.tar.gz |
|---|---|
| Size | 19.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f832db4e76148d7f16e598244d1777452107feda6c6fdb3f4f536d816cc1cf8f
|
|
BLAKE2b-256 checksum How to use checksums |
dfb5f694b14642118ff39e33bed5698209880a55408cb3091ffd56648f7af5dd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.15
|
Release files / pit_adjuster-0.1.0-py3-none-any.whl
| Download URL | pit_adjuster-0.1.0-py3-none-any.whl |
|---|---|
| Size | 14.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
dea2af9ff2ae23d528ca1f9fed8f1734113c5213922ade13b1c6a889ed101831
|
|
BLAKE2b-256 checksum How to use checksums |
b789cb05e296965a8feb823c0feec5777540aeaafa16b6428b868306acac330f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.15
|