Metadata-Version: 2.4
Name: mktlib-scan
Version: 0.1.1
Classifier: Development Status :: 4 - Beta
Classifier: Programming Language :: Rust
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Topic :: Office/Business :: Financial :: Investment
Requires-Dist: polars>=1.0
Requires-Dist: pytest>=8 ; extra == 'dev'
Requires-Dist: maturin>=1.7 ; extra == 'dev'
Requires-Dist: pyright ; extra == 'dev'
Provides-Extra: dev
License-File: LICENSE
Summary: Compiled entry/exit chain resolver for mktlib's backtest engine.
Keywords: polars,backtest,finance,rust
Author-email: Matt Buck <matt@mblance.com>
License-Expression: Apache-2.0
Requires-Python: >=3.12
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Homepage, https://github.com/mattbuck85/mktlib-scan
Project-URL: Issues, https://github.com/mattbuck85/mktlib-scan/issues
Project-URL: Repository, https://github.com/mattbuck85/mktlib-scan

# mktlib-scan

Compiled entry/exit chain resolver for [mktlib](https://github.com/mattbuck85/polars-mktlib)'s
backtest engine.

```bash
pip install mktlib[fast]
```

That is the whole user-facing story. This package has no API you are meant to
call — mktlib discovers it and dispatches to it automatically. Installing it
changes performance and nothing else.

## What it does

mktlib resolves which entry signals actually open a position by walking the
entry/exit chain forward. In pure Python that walk is fast, but *getting the data
to it* is not: `Series.to_list()` boxes one Python object per element.

Measured in mktlib at 500k bars, before this package existed:

| stage | time | share of a full `run()` |
|-|-|-|
| full `run()` | 129.9 ms | — |
| chain resolution | 108.9 ms | 85.8% |
| ↳ polars evaluation | 0.4 ms | 0.3% |
| ↳ `to_list()` boxing | **71.0 ms** | **54.7%** |
| ↳ the scan loop itself | 35.7 ms | 27.5% |

Two-thirds of the cost was marshalling, not computing. So this package takes
`polars.Series` directly and never builds a Python list of floats. An FFI that
accepted lists would re-pay that and capture almost nothing — which is the single
most important thing to know if you ever change this boundary.

## Correctness

`mktlib/backtest/_scan.py` remains the reference implementation and the fallback.
This crate must agree with it **bit for bit**, and that is enforced rather than
asserted:

- mktlib runs its full equivalence corpus and all of its frozen golden baselines
  against *both* backends. The baselines were frozen before this package existed,
  so any divergence fails.
- The kernel is a separate crate with no pyo3 and no polars, unit-tested against
  the rules that are easy to get wrong — entry-wins-on-a-shared-bar, session
  forcing, NaN-as-null, strictness folding, and the pair/general tie-break.
- `nextafter` is pinned bit-for-bit against a table generated from CPython,
  including the signed zeros, where a sign-propagating implementation diverges.

Bit-identity is achievable here rather than aspirational: the outputs are indices
and booleans, and the only float operations are comparisons and `nextafter`. No
accumulation, no reassociation, nothing for an optimizer to reorder.

The semantic contract both implementations satisfy is in [CONTRACT.md](CONTRACT.md).

## Layout

| path | what |
|-|-|
| `kernel/` | the scan, over slices. No pyo3, no polars, no dependencies. |
| `py/` | pyo3-polars shim. Series in, indices out. All marshalling, no semantics. |
| `python/mktlib_scan/` | the importable package and its type stubs |

## License

Apache-2.0

