Metadata-Version: 2.4
Name: backtest-bias
Version: 0.1.0
Summary: Checks whether your backtest data is lying to you: survivorship, dead-name coverage, integrity gates for financial price panels.
Author: Ayan Jain
License: MIT
Project-URL: Homepage, https://github.com/Finance-broski/backtest-bias
Keywords: backtesting,survivorship-bias,quant,data-quality,point-in-time
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Office/Business :: Financial :: Investment
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pandas>=1.5
Requires-Dist: numpy>=1.23
Dynamic: license-file

# backtest-bias

**Checks whether your backtest data is lying to you.**

Most backtests don't fail loudly. They flatter you quietly, because the data underneath them is
missing the stocks that died. This library tests your price panel for that — in one line — and
tells you roughly what it costs when it finds it.

## The measured numbers this library is built on

These are not estimates. I measured them on real Indian market data and published the write-ups:

- **23%** of the top-500 Indian stocks (as of 2015) are invisible to yfinance today — delisted,
  merged, suspended. Any backtest built on it runs on survivors only.
- Survivor-only universes inflated equal-weight returns by **+0.8 to +2.5 pp/yr** depending on
  universe vintage — same market, same method, 3x difference. Anyone quoting one number is guessing.
- On the most widely used Kaggle NSE dataset, index-membership look-ahead added **+10%** terminal
  wealth cap-weighted and **+43%** equal-weighted over 2010-2021. The bias depends on construction.
- Comparable mid/large-cap universes lose **10-30%** of their names over multi-year windows.
  If your panel lost zero, your panel is the problem.

## Install

```
pip install backtest-bias
```

## 30 seconds to a verdict

```python
import pandas as pd
from backtest_bias import check_survivorship

prices = pd.read_csv("my_panel.csv")   # wide (date x symbols) or long (date/symbol/close)
report = check_survivorship(prices)
print(report.summary())
```

```
survivorship check: 412 symbols over 9.2y, 0 died in-window (0%)
verdict: SEVERE - 412 names over 9.2y with zero deaths is the survivor-only signature;
comparable universes lose 10%-30% of names over such windows
expect EW returns inflated roughly +0.8-2.5 pp/yr vs an honest universe (measured,
vintage-dependent; see backtest_bias.REFERENCES)
```

## What v0.1 ships

| function | what it answers |
|---|---|
| `check_survivorship(prices)` | does my universe contain the stocks that died, or only the winners? Full report with severity and a measured bias estimate |
| `dead_name_ratio(prices)` | one number: what fraction of my names end before the panel does. `0.0` = pure survivor panel |
| `assert_integrity(prices)` | CI gate: raise if the panel smells survivor-only, so a silent re-download of bad data fails your pipeline instead of flattering your backtest |

Input handling is forgiving: wide panels, long frames, sniffed column names, NaN-padded
histories. Anything the library cannot judge honestly, it raises instead of guessing.

## Roadmap

- **v0.2** — look-ahead / point-in-time violations: fundamentals dated by period instead of
  announcement, index membership applied backwards, same-bar signal fills
- **v0.3** — rename-continuity and corporate-action gap detection

## Who

I'm [Ayan Jain](https://www.linkedin.com/in/ayanjain259). I build point-in-time Indian
equity data and audit backtests and datasets for bias — the measured numbers above come from
those audits. If you want this class of check run on your own backtest by a person instead of a
library, that's my [Bias Check](https://forms.gle/sAvosfHnitCBm9FD7): fixed price, 48h, written
verdict.

MIT licensed. Issues and war stories welcome — especially datasets that fooled you.
