sharpe-gate
Everyone computes a Sharpe ratio. Nobody stores the honest trial count behind
it. Without that count N, every multiple-testing statistic — the Deflated
Sharpe Ratio above all — is decoration: it deflates by however many trials you
remembered running, not by how many you actually ran across every session,
notebook, and parameter sweep.
This package is the machinery that keeps N honest, and the gates that spend
it:
- Trial registry (
sharpe_gate.registry) — every backtest persists one append-only row keyed by a code-agnosticconfig_hash, plus its daily PnL/exposure stream. SQLite (WAL) — safe for concurrent writers. Studies are pre-registered (declared trial count, kill threshold) before the search; you cannot record a trial without one. - Canonical dataset fingerprints (
sharpe_gate.fingerprint) — the pooling key fingerprints the data, never the hypothesis, and is validated structurally at the write boundary. A spelling drift or a hypothesis suffix cannot carve a private, smaller-N pool out of shared data. Venues ship as prefixes;register_source("bybit")adds your own without turning the validation off. - Overfit gate (
sharpe_gate.overfit) — DSR (Bailey & López de Prado) on the registry-resolved pool, PSR fallback for N=1, expected-max-Sharpe under H0, PBO via CSCV on purged+embargoed folds, and a shuffled-label held-out AUC floor for ML overlays. The entry point takes atrial_id, not a bare float — an unrecorded Sharpe cannot reach the gate. Kurtosis is Pearson (normal = 3.0) throughout; passing excess kurtosis raises rather than quietly inflating the result. - minBTL (
sharpe_gate.min_btl) — the cheapest kill: is the sample even long enough to support the hurdle Sharpe givenN? Pure arithmetic, runs before any backtest.
None of this contains alpha. It is machinery against self-deception — the part that tells you the unpleasant truth before you pay for it.
Install
pip install sharpe-gate
Quick start
from sharpe_gate import register_study, record_trial, overfit_screen
study = register_study(
study_id="my_screen_v1",
hypothesis_id="H42",
generator="grid",
dataset_keys=["binance_btc_perp_4h"],
statistic="sharpe",
n_declared=24,
kill_threshold=0.95,
)
trial_id = record_trial(study, config={"lookback": 20}, stream=(daily_pnl, in_position))
result = overfit_screen(trial_id) # -> OverfitResult(probability, method="dsr"|"psr", ...)
The store location resolves in order: explicit store_dir= argument →
SHARPE_GATE_DIR env var (HL_REGISTRY_DIR is honored as a legacy alias)
→ sharpe_gate.registry.DEFAULT_STORE_DIR (settable by a host repo) →
./data/registry under the current working directory.
What it does not do
It contains no alpha, no strategies, and no data. It never asks the network, holds no keys, and cannot place an order. The registry is a local SQLite file plus Parquet stream sidecars.
Development
git clone https://github.com/bond-labs-dev/sharpe-gate
cd sharpe-gate
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pytest -q
See CONTRIBUTING.md for the checks CI runs and the invariants a change must not break.
Status
Alpha. The API is stable in practice but not promised across 0.x.
License
MIT — see LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters