Metadata-Version: 2.5
Name: alphaengine
Version: 0.7.0
Summary: Validated research tooling for investment strategies: deflation, overfitting detection, and honest trial counts.
Project-URL: Homepage, https://github.com/quantOSC/alphaengine
Project-URL: Documentation, https://github.com/quantOSC/alphaengine#readme
Project-URL: Source, https://github.com/quantOSC/alphaengine
Project-URL: Issues, https://github.com/quantOSC/alphaengine/issues
Author: QuantOS
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: backtesting,deflated-sharpe,overfitting,quantitative-finance,research
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Office/Business :: Financial :: Investment
Classifier: Topic :: Scientific/Engineering :: Mathematics
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: numpy>=1.24
Requires-Dist: scipy>=1.10
Provides-Extra: agents
Requires-Dist: anthropic>=0.40; extra == 'agents'
Requires-Dist: openai>=1.0; extra == 'agents'
Provides-Extra: connectors
Requires-Dist: httpx>=0.27; extra == 'connectors'
Requires-Dist: pyarrow>=15.0; extra == 'connectors'
Provides-Extra: dev
Requires-Dist: mypy==2.3.0; extra == 'dev'
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff==0.16.1; extra == 'dev'
Requires-Dist: statsmodels>=0.14; extra == 'dev'
Provides-Extra: factors
Requires-Dist: statsmodels>=0.14; extra == 'factors'
Description-Content-Type: text/markdown

<p align="center">
  <img src="docs/assets/banner.png" alt="AlphaEngine: the research loop, on your machine" width="100%">
</p>

<h1 align="center">AlphaEngine</h1>

<p align="center">
  <strong>The research loop, on your machine.</strong><br>
  Ask what is worth looking at, whether it holds up, how much to hold,<br>
  and whether anything has crossed a line. Your data never leaves.
</p>

<p align="center">
  <a href="https://pypi.org/project/alphaengine/"><img src="https://img.shields.io/pypi/v/alphaengine.svg?style=flat-square&color=1B7A7A" alt="PyPI"></a>
  <a href="https://pypi.org/project/alphaengine/"><img src="https://img.shields.io/pypi/pyversions/alphaengine.svg?style=flat-square" alt="Python 3.10+"></a>
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-0B1220?style=flat-square" alt="Apache 2.0"></a>
  <a href="https://github.com/quantOSC/alphaengine/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/quantOSC/alphaengine/ci.yml?branch=main&style=flat-square&label=CI" alt="CI"></a>
  <img src="https://img.shields.io/badge/deps-numpy%20%2B%20scipy-C4893A?style=flat-square" alt="Two dependencies: numpy and scipy">
</p>

<p align="center">
  <img src="docs/assets/mark.png" alt="AlphaEngine mark" width="96">
</p>

```bash
pip install alphaengine
alphaengine demo          # the whole offline half, no account, no data of your own
```

<p align="center">
  <img src="docs/assets/session.png" alt="The session canvas: a living parameter surface, then demo, login, load" width="92%">
</p>

The session is the product. Sign in, load something, then ask:

```bash
alphaengine
❯ login
❯ load prices.csv
❯ screen
```

---

## What it answers

Eight questions, in the order a research week actually asks them. The command
IS the question; only two of them need code of yours.

| Ask | Command | Workflow | Needs |
|---|---|---|---|
| Can I trust this data? | `diagnose` | `diagnose_data` | prices |
| What is worth a look? | `screen` | `screen_universe` | prices |
| Does this signal carry information? | `signal` | `evaluate_signal` | a signal panel + prices |
| Is this real, once you count the tries? | `validate` | `validate_study` | prices + your backtest |
| Where does it break? | `stress` | `stress_study` | a return series |
| Is it new, or my book again? | `overlap` | `check_overlap` | the candidate + the book |
| How much should I hold? | `size` | `size_position` | a return series |
| Still inside the lines? | `monitor` | `monitor_sleeve` | a return series |

```bash
alphaengine diagnose --data prices.csv     # before anything runs on it
alphaengine screen --universe sp500
alphaengine size --data returns.csv
```

**The refusals are the point.** A screen over a universe that mostly could not
be measured is refused as a survivorship artifact rather than ranked. A size on
a record shorter than its own minimum track length is refused outright, because
a small position is still a claim and the caveat does not travel with it into a
book. A monitor with no stated tolerances reports `unchecked` — never a green
light. A stop exits 0: "this did not clear the bar" is the system working.

Or say it in plain English and let your own model pick:

```bash
alphaengine
❯ which of my names are overbought on RSI?
```

That second path is EXPLORATORY: the model chooses a workflow and then chooses
each step from what the server permits. Two runs of the same question may
differ, and the run says so. `run <workflow>` is SCRIPTED and reproducible.
Both are legitimate; presenting one as the other is not.

---

## The three rungs

Each is useful without the one above it. `login` lights the next one.

| Rung | What you need | What you get |
|---|---|---|
| **The maths** | nothing | Every statistic, offline, forever. No account. |
| **Workflows** | a QuantOS `ae_live_` key (`login`) | The loop end to end, with the record. |
| **Ask anything** | your OWN model key (`login anthropic`) | Plain English in. Runs under your account, not ours. |

Nothing here stores a model key: it is read from your environment at call time
and handed to the provider's own client. There is no field to put one in.

```mermaid
flowchart LR
  A["demo / import<br/>offline maths"] --> B["login<br/>QuantOS key"]
  B --> C["login anthropic<br/>your model"]
  A -.->|"no account"| D["DSR · PBO · ICIR · HRP"]
  B -.->|"ae_live_"| E["screen · validate · runs"]
  C -.->|"BYOK"| F["plain English in"]
```

---

## Getting your data in

One verb, three shapes. Nothing is ever fetched on your behalf.

```bash
load prices.csv              # a local CSV
load sp500                   # registered in the portal, with the closes you stored
load research.momentum       # a module of yours (the only door that can carry a simulator)
```

From a shell the same doors are flags, for scripts:

```bash
alphaengine screen --data prices.csv
alphaengine screen --universe sp500
alphaengine validate --project research.momentum
```

`--data` reads three shapes, decided by the header and nothing else:

```
wide     date,AAPL,MSFT,NVDA      one column per name
long     date,symbol,close        one row per name per day
series   date,close               a single series, or one bare column of numbers
```

A file it cannot place raises and names all three rather than guessing. A loader
that guesses eventually guesses wrong on somebody's book, and the failure is not
an error: it is a screen that ranked returns as prices and produced a shortlist
nobody can tell is wrong.

`--project` is the only door that can carry a **simulator**, because a simulator
is code. That is why `validate_study` needs it — and `evaluate_signal` needs it
for the scores it measures — while the rest run on prices alone.

`--universe` brings the closes you stored with it in the portal. That is your own
upload decrypted back to your own account, not us fetching market data, and the
distinction is the whole of the data boundary below.

<p align="center">
  <img src="docs/assets/data_boundary.png" alt="Your machine holds prices and notebooks; only figures cross to the QuantOS record" width="92%">
</p>

---

## What's new in 0.7.0

The daily modelling morning, and the overnight book, without a third
dependency. Existing goldens (deflated Sharpe, PBO, performance, screen) are
byte-identical. New figures are a public contract from this release.

| You have | You get | Module |
|---|---|---|
| A raw factor panel | Cross-sectional rank, z-score, winsorize, neutralize | `core.panel` |
| A signal and prices | ICIR, Newey-West t-stat, Fama-MacBeth λ, quantile book with one-way turnover | `core.signals`, `core.cross_section` |
| A return panel | EWMA / Ledoit-Wolf / Marchenko-Pastur covariance, HRP, risk parity, vol target | `core.covariance`, `core.allocate` |

The session is slimmer too: `login` lights a rung, `load` is the one data verb,
and boot paints the parameter surface this tool actually judges rather than a
command encyclopedia.

```python
from alphaengine.core import cs_zscore, signal_icir, hrp_weights, fama_macbeth

z = cs_zscore(factor_panel)          # skipped names are counted, not dropped
ic = signal_icir(signal, prices)     # Spearman ICIR; Pearson is opt-in
fm = fama_macbeth(signal, prices)    # λ_mean, t-stat, Newey-West
w = hrp_weights(cov, names=names)    # no matrix inverse; weights sum to one
```

---

## Command reference

`alphaengine commands` prints this directory in the terminal, and
`alphaengine commands run` expands any one of them.

<!-- COMMANDS:START -->
<!-- Generated by scripts/gen_docs.py from src/alphaengine/commands.py.
     Do not edit by hand; run `python scripts/gen_docs.py --write`. -->

### Get started

| Command | Does | Where |
|---|---|---|
| `demo` | run the built-in example offline, with no account and no data | shell + session |
| `runs [--limit N]` | your own week: what ran, what it decided, what it filed | shell + session |
| `gaps` | what your record says is UNANSWERED, and what closes each one | shell + session |
| `tonight [--budget N]` | what would run unattended tonight, without running any of it | shell + session |
| `workflows` | what the server offers, what each needs, and which reproduce | shell + session |
| `login [quantos \| anthropic \| openai \| gemini \| groq \| azure \| openrouter \| gateway]` | sign in, or login anthropic for a model key | shell + session |
| `key [quantos \| anthropic \| openai \| gemini \| groq \| azure \| openrouter \| gateway]` | same as login: enter a credential, or see which rungs are lit | session |
| `commands [verb]` | this directory, or one command in full | shell + session |
| `models` | which model providers this machine can actually use | shell + session |
| `model [<provider[:name]>]` | pin the model for this session, or show the pin | session |
| `trace [run_id]` | local model/run events for a run, hashed prompts only | shell + session |

### Do the work

| Command | Does | Where |
|---|---|---|
| `diagnose` | can I trust this data? | shell + session |
| `screen` | what is worth a look? | shell + session |
| `signal` | does this signal carry information? | shell + session |
| `validate` | is this real? | shell + session |
| `stress` | where does it break? | shell + session |
| `overlap` | is it new, or my book again? | shell + session |
| `size` | how much? | shell + session |
| `monitor` | still inside the lines? | shell + session |
| `run <workflow>` | run one workflow exactly as written | shell + session |
| `<anything else>` | ask in plain English; your model picks a workflow and drives it | session |
| `status` | the current run, and what is loaded | session |

### Bring your data

| Command | Does | Where |
|---|---|---|
| `book [<name> \| status]` | show or load sleeves on the multi-strategy book | session |
| `load <file \| module \| universe>` | a CSV, a project module, or a portal universe | session |
| `universe <name>` | same as load: a universe registered in the portal | session |
| `data <file>` | same as load: a local CSV or parquet | session |
| `project <module>` | same as load: a module with data and backtest_fn | session |

### Session

| Command | Does | Where |
|---|---|---|
| `logout` | remove stored credentials from this machine | shell + session |
| `version` | print the version | shell |
| `help` | the short list: demo, login, load, then a question | session |
| `quit` | leave the session | session |

### Data flags

| Flag | What it takes |
|---|---|
| `--data FILE` | a local CSV. Wide (date,AAPL,MSFT), long (date,symbol,close), or one series. |
| `--universe NAME` | a universe you registered in the portal, with the closes you stored. |
| `--project MODULE` | a Python module. The only door that can carry a simulator. |

### Flags

| Flag | Does |
|---|---|
| `--url URL` | workflow server (default $QUANTOS_API_URL or the public API) |
| `--key KEY` | portal-issued ae_live_ key (default $QUANTOS_API_KEY) |
| `--project MODULE` | a module exposing `data` and `backtest_fn` |
| `--data FILE` | a local CSV: wide, long, or a single series |
| `--universe NAME` | a universe registered in the portal, with its stored closes |
| `--symbol TICKER` | one name out of a loaded universe; its closes become the return series |
| `--label TEXT` | what to call the artifact this run produces |
| `--input K=V` | a workflow input; repeatable |
| `--quiet` | only the result, no step narration |
| `--stream` | print the answer once the citation guard has passed |
| `--limit N` | how many rows to show (default 25) |
| `--budget N` | how many runs a night is worth (default 3) |

### Examples

```bash
alphaengine diagnose --universe <name>
alphaengine screen --universe <name>
alphaengine signal
alphaengine validate
alphaengine stress
alphaengine overlap
alphaengine size
alphaengine monitor
alphaengine demo
alphaengine runs
alphaengine runs --limit 50
alphaengine gaps
alphaengine tonight
alphaengine tonight --budget 5
alphaengine workflows
alphaengine login
alphaengine commands
alphaengine run screen_universe --universe sp500
alphaengine run size_position --data returns.csv
alphaengine run validate_study --project research.momentum
alphaengine models
alphaengine trace
alphaengine logout
alphaengine version
```

<!-- COMMANDS:END -->

## The library, without the CLI

The commands above are a wrapper. Everything they compute is importable, runs
offline, and needs no account:

```python
from alphaengine import sweep

r = sweep(backtest_fn, {"fast": [5, 10, 20], "slow": [50, 100, 200]}, data=prices)

r.surface()  # is the result a broad plateau or a single lucky configuration?
r.verdict()  # deflated for the 9 trials that were actually run
r.save()  # study.json, on your disk
```

The trial count is **derived from the grid that ran**, never asserted. Omit it
and it is recorded as `not_recorded` rather than defaulting to a flattering 1,
and an unrecorded denominator cannot reach an `edge` verdict at all.

## Writing your own `backtest_fn`

Two rules, both easy to get wrong the first time, and the reason the example
above exists to copy:

**Return a bare 1-D return series.** Not a dict, not a stats object — the
per-period returns themselves. `sweep` does `np.asarray(list(raw))`, so a dict
of results iterates its *keys* and fails on the first string.

**Return the same length for every combination.** PBO splits the trial matrix
into time blocks and compares configurations within each block, which only means
anything if they line up in time. Ragged output is refused rather than truncated,
because silently trimming produces a confident number over series that do not
correspond. In practice: pick a warm-up long enough for the slowest window in
your grid and start every configuration there.

```python
WARMUP = 200  # covers the slowest `slow` in the grid


def backtest_fn(*, data, fast, slow):
    close = data["close"]
    return [
        (close[i + 1] - close[i]) / close[i] * (1 if sma(close, fast, i) > sma(close, slow, i) else 0)
        for i in range(WARMUP, len(close) - 1)
    ]
```

`data` is whatever you want it to be — a DataFrame, a dict of series, an array.
The package never inspects it and it never leaves your machine.

## What it does

**Runs your parameter grid.** `sweep()` calls your backtest function once per
combination. It does not backtest anything itself, so the engine you already
trust stays the engine you trust.

**Counts the trials for you.** The statistics that correct a Sharpe ratio for
multiple testing need to know how many variants were tested. That number is
almost never recorded, because nobody counts what they discarded. Running the
grid makes it `len(grid)`, so it never has to be asked for or asserted.

**Refuses to flatter an unrecorded count.** Since 0.2.0, omitting `n_trials`
means `not_recorded` — not `1`. The trial count comes back `null`,
`n_trials_source` travels beside it, and **a verdict of `edge` is unreachable
without a recorded denominator.** A deflated Sharpe is a ratio; deflating by a
denominator nobody wrote down does not produce a weaker claim, it produces a
claim about nothing.

**Shows you the neighbourhood.** The output is whether your result sits on a
broad plateau or a knife edge, and where the robust region is centred. A single
spike surrounded by failures is a result fitted to its own parameters.

**Produces a portable study.** A JSON artifact holding what was tried, what came
back, and a content hash of the data it ran on. Readable in a text editor,
diffable, and versioned so it still parses in two years.

## What is in it

| Module | Contents |
|---|---|
| `alphaengine.core` | deflated Sharpe, PSR, PBO via CSCV, CPCV, minimum track record length, performance and risk |
| `alphaengine.core.panel` | cross-sectional rank, z-score, winsorize, neutralize |
| `alphaengine.core.signals` | IC, ICIR, quantile returns, decay |
| `alphaengine.core.cross_section` | Fama-MacBeth, quantile book with turnover |
| `alphaengine.core.covariance` | EWMA, Ledoit-Wolf, Marchenko-Pastur denoise, detone |
| `alphaengine.core.allocate` | HRP, risk parity, vol target |
| `alphaengine.sweep` | the grid runner and the sensitivity surface |
| `alphaengine.study` | the study artifact and its schema |
| `alphaengine.client` | the workflow client and the step executor |
| `alphaengine.cli` | the `alphaengine` terminal entry point |

Two runtime dependencies, numpy and scipy, both already present in a typical
research environment. `import alphaengine` makes no network call and needs no
account. Factor decomposition and cointegration testing need statsmodels and
are available as `pip install 'alphaengine[factors]'`.

## Getting a study to somebody else

`save()` writes to your disk and needs no account. When the work has to reach
the PM who will act on it, `report()` sends the study — and only the study.

```python
import os
from alphaengine import Study, sweep

os.environ["QUANTOS_API_KEY"] = "ae_live_..."  # created in the portal

r = sweep(backtest_fn, grid, data=prices)
r.save()  # yours, on your disk, always

Study.from_sweep(r, label="momentum, 9 configs").report()
```

What crosses is an explicit allowlist: the trial count and how it was obtained,
a content hash of the data, the verdict, the shape of the neighbourhood, the
performance figures. Your returns, your prices and your parameter grid stay on
the machine, and a guard keyed on length rather than field name refuses to send
anything series-shaped whatever it is called.

Reporting is the only part of this package that touches a network, so it is the
only part that is not imported until you call it. `import alphaengine` still
makes no network call.

## Where this sits in QuantOS

AlphaEngine is the open research layer of the [QuantOS](https://github.com/quantOSC)
platform. It is the piece that runs on your machine, against your data, and it
is complete on its own: everything above works offline and forever, at no cost.

The QuantOS platform builds on it. Studies produced here can be persisted to a
firm's record, referenced when an idea becomes a position, and assembled into
the reports that go to an investment committee or an allocator. The library
computes; the platform remembers and reports. The two halves are separated so
that the part touching your data has no reason to phone home.

## The methods

Everything in `core` comes from the published literature. Nothing here is a
proprietary formula, which is deliberate: a referee whose reasoning you cannot
inspect is not a referee.

**Deflated Sharpe Ratio, Probabilistic Sharpe Ratio, minimum track record length**
Bailey, D. H., and López de Prado, M. (2012). "The Sharpe Ratio Efficient
Frontier." *Journal of Risk* 15(2), 3 to 44.
Bailey, D. H., and López de Prado, M. (2014). "The Deflated Sharpe Ratio:
Correcting for Selection Bias, Backtest Overfitting, and Non-Normality."
*Journal of Portfolio Management* 40(5), 94 to 107.

**Probability of Backtest Overfitting via CSCV**
Bailey, D. H., Borwein, J., López de Prado, M., and Zhu, Q. J. (2017). "The
Probability of Backtest Overfitting." *Journal of Computational Finance* 20(4),
39 to 69.

**Combinatorial purged cross-validation**
López de Prado, M. (2018). *Advances in Financial Machine Learning.* Wiley,
chapters 7 and 12.

**Hierarchical Risk Parity**
López de Prado, M. (2016). "Building Diversified Portfolios that Outperform
Out of Sample." *Journal of Portfolio Management* 42(4), 59 to 69.

**Covariance shrinkage and spectral denoising**
Ledoit, O., and Wolf, M. (2004). "A Well-Conditioned Estimator for
Large-Dimensional Covariance Matrices." *Journal of Multivariate Analysis*
88(2), 365 to 411.
Laloux, L., Cizeau, P., Bouchaud, J.-P., and Potters, M. (1999). "Noise
Dressing of Financial Correlation Matrices." *Physical Review Letters* 83(7),
1467 to 1470.

**Multiple testing in asset pricing**
Harvey, C. R., Liu, Y., and Zhu, H. (2016). "... and the Cross-Section of
Expected Returns." *Review of Financial Studies* 29(1), 5 to 68.
Harvey, C. R., and Liu, Y. (2015). "Backtesting." *Journal of Portfolio
Management* 42(1), 13 to 28.

**Fama-MacBeth**
Fama, E. F., and MacBeth, J. D. (1973). "Risk, Return, and Equilibrium:
Empirical Tests." *Journal of Political Economy* 81(3), 607 to 636.

**Downside deviation**
Sortino, F. A., and Price, L. N. (1994). "Performance Measurement in a Downside
Risk Framework." *Journal of Investing* 3(3), 59 to 64.

**Factor regression standard errors** (in the `factors` extra)
Newey, W. K., and West, K. D. (1987). "A Simple, Positive Semi-Definite,
Heteroskedasticity and Autocorrelation Consistent Covariance Matrix."
*Econometrica* 55(3), 703 to 708.

**Unit root testing for cointegration** (in the `factors` extra)
Dickey, D. A., and Fuller, W. A. (1979). "Distribution of the Estimators for
Autoregressive Time Series with a Unit Root." *Journal of the American
Statistical Association* 74(366), 427 to 431.

## Reproducibility

The values these functions return are treated as a public contract. A study
written today has to reproduce in two years, so a change to a computed value is
a breaking change requiring a version bump even when the signature is unchanged.
While the leading digit is 0 the minor position carries that rule — 0.1 → 0.2 is
what a changed figure costs — so every 0.2.x release produces identical numbers.
CI fails if a pinned value moves.

## Installing: "command not found"

The console script lives in your venv's `Scripts/` (Windows) or `bin/` (POSIX),
which joins your PATH only while that venv is **activated**. A fresh install
followed by `alphaengine` therefore says *command not found*, which reads as a
broken install and is not one.

```powershell
.\.venv\Scripts\Activate.ps1     # Windows PowerShell
source .venv/bin/activate        # macOS / Linux
alphaengine
```

Or skip activation entirely — **`python -m alphaengine` always works**, because
the interpreter that can import the package can always run it:

```bash
.venv/Scripts/python.exe -m alphaengine      # Windows, unactivated
.venv/bin/python -m alphaengine              # POSIX, unactivated
```

This is the price of in-process data access, and it is the one cost we will not
engineer around: a launcher that worked from anywhere would have to run in its
own environment, which is exactly the isolation that makes `compute.*` unable to
see your DataFrames.

The run narrates itself, because a loop you cannot watch is a loop you cannot
trust. The server names an op, this machine executes it and hands back figures:

```
validate_study · <your workflow server>
  server →  compute.sweep
  local  ·  done
  server →  compute.deflated_sharpe
  local  ·  done
  server →  Stop: the surface is a ridge, not a plateau
```

`--project` names an ordinary module of yours exposing `data` and, if the
workflow sweeps, `backtest_fn`. Authenticate with a key from the portal:

```bash
export QUANTOS_API_KEY=ae_live_...     # QUANTOS_API_URL for self-hosted or VPC
```

**A stop exits 0.** "This did not clear the bar" is the system working, not a
broken build — a non-zero exit there would make every CI pipeline treat an
honest refusal as a failure, which is exactly the pressure that gets honesty
controls switched off. Only a run that could not execute a step exits non-zero.

> **Install this into the venv you do research in — not with `pipx` or
> `uv tool install`.** The compute steps execute in-process against your own
> DataFrames, so tool isolation, which is normally the right way to install a
> CLI, is the one thing that cannot work here. You cannot have isolation and
> in-process data access, and your data not moving is the point.

**No LLM dependency and no key field.** `AgentDriver` takes a callable, so you
bring your own model by passing a function. There is nowhere in this tool to put
a model key, ours or yours, and that is how "runs under your own account" is
satisfied structurally rather than promised.

## Development

```bash
python -m venv .venv && .venv/bin/pip install -e '.[dev,factors]'
```

CI runs five gates. Run all of them before pushing — `ruff check` and
`ruff format` are different tools with different opinions, and passing one
says nothing about the other:

```bash
python -m ruff check src tests
python -m ruff format --check src tests
python -m mypy src
python -m pytest
python -m pytest tests -m golden -q      # the frozen figures
```

## Licence

Apache-2.0. See [LICENSE](LICENSE).
