Skip to main content

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.

adjustment chain python

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:

  1. 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.
  2. 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

Project family

Part of Foolproof Labs — a toolchain against self-deception in quantitative research:

License

MIT

Metadata

Release files for pit-adjuster 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pit-adjuster 0.1.1
File Size Uploaded
pit_adjuster-0.1.1.tar.gz 20.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pit-adjuster 0.1.1
File Interpreter ABI Platform
pit_adjuster-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 34.9 kB

Release files / pit_adjuster-0.1.1.tar.gz

Download URL pit_adjuster-0.1.1.tar.gz
Size 20.0 kB
Tags Source
SHA-256 checksum
How to use checksums
53875a6ad0bf4f2e300a51fe9684ef5df25376791dd1f28a795cd99ceeb0be20
BLAKE2b-256 checksum
How to use checksums
72ddeea6a1605ea078a5e718e5000416f84ac7e1c04f4f3a58c4d7b71541e252
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.1-py3-none-any.whl

Download URL pit_adjuster-0.1.1-py3-none-any.whl
Size 14.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d5c305093c5c570973b33bfdfa81d4e218b110f45a6529674d67481e1418616b
BLAKE2b-256 checksum
How to use checksums
c6dd70b44c469b4f582dcdc89a03199529484d623856340adbfa097feb1492a4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.15

Release history Release notifications | RSS feed

0.1.5

2 release files

0.1.3

2 release files

0.1.2

2 release files

This release

0.1.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page