Skip to main content

nullbar

tests license: MIT python

Pre-registration and honest statistics for trading research.

Most backtesting tools help you find an edge. This library assumes you will fool yourself while trying — because in two years of production algorithmic trading, that is what happened to us, over and over, despite code review, 2,000+ tests, and genuine care. nullbar is the machinery that caught it, extracted into a standalone package.

The name is the two things it makes you commit to before you are allowed to believe a number: the null control and the pre-registered bar.

The one-day story this library made possible

On 2026-08-11 we designed a new ML model (a cross-sectional transformer over 379 crypto assets), pre-registered its evaluation — target, architecture, splits, and a three-condition pass bar, committed before training — trained it, and evaluated it once on a test window the training code could not read even by bug.

The model achieved 5× the rank correlation of every predecessor. It also failed all three bar conditions: its extra correlation lived in the middle of the ranking, and the only part of a ranking that can pay trading costs is the tail — which a four-line moving average already owned. Design to honest verdict: one day. Without this machinery, that same question had previously consumed months and produced numbers we later had to retract.

What's inside, and the production bug behind each piece

Module What it does The bug it exists because of
registration Freeze design + pass bar before results; one test look, enforced; verdicts graded fail-closed against the file on disk "One more epoch, one more threshold" after seeing test data
ledger Append-only trial count with per-trial metrics (no delete API) A t=2.68 celebrated, when a best-of-64 noise search clears 3.35 five percent of the time
stats Clustered t, PSR/DSR in strictly per-period units; dsr returns None, never 0, when the trial count or the spread is unknown Overlapping windows inflating results 1.9×; a gate that logged PSR=0.000 for months because annualized and per-period units were mixed
evaluate Block-clustered evaluation, hold baseline, and null controls graded against it Pipelines that "find" effects their own machinery created
fills Touch/through fill brackets for resting bids and asks Assumed fills overstated executed gross 1.3–1.5× — the entries that never fill are the best ones
leaklint Static lookahead lint (CLI) + prefix-replay check (sound one way: it cannot see a leak baked into a constant fitted outside the callable) A multi-timeframe resampling leak that fed +23h of future into features, survived two years and every review, and explained a deployed model's entire measured edge

The prefix-replay check deserves a sentence: recompute any feature on a data prefix and compare with the full-sample computation at the same rows. Any feature whose past changes when the future is appended is leaking, whatever its source looks like. This one test, run on day one, would have saved us a year.

Install

# from a clone (editable — edits are live):
git clone https://github.com/brunopereira81/nullbar && cd nullbar
pip3 install -e .

# run the examples:
python3 examples/01_full_workflow.py
python3 examples/02_catch_a_leak.py

# lint a tree for lookahead patterns:
python3 -m nullbar strategy/

Requires Python 3.10+; numpy and pandas (2.x and 3.x are separate CI legs). PyPI release pending — until then, install from the clone.

Renamed at v0.2.0: this package shipped its first version as prereg. The PyPI name prereg belongs to an unrelated project (an OSF-oriented pre-registration CLI). Nothing here is affiliated with it.

Quickstart

import nullbar

# 1. Register before you run
reg = nullbar.Registration(
    name="mean-reversion-24h",
    hypothesis="bottom-decile dist_ma168 mean-reverts over 24h",
    design={"hold_bars": 24, "entry_pct": 0.10, "cost_pct": 0.230},
    bar={"null_flat": "null control indistinguishable from holding",
         "t3": "clustered t >= 3.0 on 24h blocks",
         "beats_hold": "net beats unconditional exposure"},
)
reg.freeze("experiments/mr24.json")          # hashed; edits now visible

# 2. Count every variant you evaluate — with its Sharpe
ledger = nullbar.TrialLedger("experiments/trials.jsonl")
ledger.record("mr24", {"entry_pct": 0.10}, metrics={"sr": cell_sharpe})

# 3. Null control FIRST — against the hold baseline, not against zero
nv = nullbar.null_verdict(entry_mask, fwd_returns)        # nv["ok"] must hold
result = nullbar.block_cluster_eval(entry_mask, fwd_returns)

# 4. Price fills honestly
bracket = nullbar.fill_bracket(entry_mask, limit_px, low_px, fwd_returns)

# 5. Deflate by what you actually searched
d = nullbar.dsr(observed_sr, n=result["clusters"], n_trials=ledger.count(),
                sr_variance=ledger.sr_variance())

# 6. Spend the single test look, on the record
reg.spend_test_look("experiments/mr24.json", results=result)
print(reg.verdict({"null_flat": nv["ok"],
                   "t3": result["t"] >= 3.0,          # numpy bool: fine
                   "beats_hold": net > hold_net}))

What this library will not do

It will not find you an edge. Ours, measured with these exact tools across architecture, features, horizons, and training breadth, was zero net of costs — and we can prove it, which is the point. If your strategy survives this harness, you have something. If it doesn't, you found out for the price of compute instead of capital.

It is also tamper-evident, not tamper-proof: it grades the frozen file and binds the test-look stamp to that file's hash, but anyone with write access can delete both. It is built for a researcher keeping themselves honest. If a third party has to believe the record, commit it — or anchor it somewhere you don't control.

Docs & examples

Status

v0.4.0 — extracted 2026-08-12 from a live production system (Coinbase spot, TimescaleDB, 2,100+ tests), renamed and hardened 2026-08-18 across three independent audit passes, 27 distinct findings. The ones worth knowing: verdict() could grade a failing strategy as PASS, fill_bracket could overstate 9× on misaligned axes, clustered_t inflated t when a cluster held no finite observation, and the frozen bar could say something different from the code grading it. All fixed, each mutation-checked, in the CHANGELOG. API will move; the philosophy won't. MIT licensed — the statistics stay open, permanently.

Metadata

Release files for nullbar 0.4.0

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

Source distribution (sdist)

Source distribution for nullbar 0.4.0
File Size Uploaded
nullbar-0.4.0.tar.gz 59.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for nullbar 0.4.0
File Interpreter ABI Platform
nullbar-0.4.0-py3-none-any.whl Python 3 none any Details

Total release size: 90.8 kB

Release files / nullbar-0.4.0.tar.gz

Download URL nullbar-0.4.0.tar.gz
Size 59.2 kB
Tags Source
SHA-256 checksum
How to use checksums
6e219d0df80bcf04d4fb5ae7720a950695bd6d2b3c67642dd7583e696900abc9
BLAKE2b-256 checksum
How to use checksums
e9627a56d6f9cbda6d97082ed75f10179c8609e72c4f04fe528e16dcdb80b0f9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 18, 2026.

Transparency log

Release files / nullbar-0.4.0-py3-none-any.whl

Download URL nullbar-0.4.0-py3-none-any.whl
Size 31.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
573fadbf6f4ca52a5c44febbf407093b5e7a2830dcadddf56f31ce60f4a15f3e
BLAKE2b-256 checksum
How to use checksums
6a431e99f681971fba5ef1c2e9b38f21b786296fd59d36c42407c801106da54e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 18, 2026.

Transparency log

Release history Release notifications | RSS feed

0.7.1

2 release files

0.7.0

2 release files

0.6.0

2 release files

0.5.0

2 release files

This release

0.4.0 This release

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