Documentation

Install. Run on your data. Decide.

Part one: AlphaEngine API. Part two: platform surfaces for the research record.

Part one · The operating system

Start here

Three commands. The first works with the wifi off and no account; the second needs a key; the third needs your own model key. Each rung is useful without the one above it.

pip install alphaengine
alphaengine demo                                  # the whole offline half
alphaengine run screen_universe --data prices.csv # your own data
alphaengine                                       # a session; ask in plain English
Nothing here is a trial, and nothing expires.

A stop is a result and exits 0.When a workflow refuses — the series is too short, the universe could not be measured — that is the system working, not a failure. A non-zero exit would make every CI pipeline treat an honest refusal as a broken build, which is precisely the pressure that gets honesty controls switched off.

Every command

The terminal has the same list: alphaengine commands prints it grouped, and alphaengine commands run expands one with its flags and examples.

Get started

demoRun the built-in example offline. No account, no data of your own.
workflowsWhat the server offers, what each needs, and which reproduce.
commands [verb]The directory, or one command in full.
key [which]Enter a credential, or see which rungs are unlocked. Session only.

Do the work

run <workflow>Run one workflow exactly as written. Scripted and reproducible.
<anything else>Ask in plain English. Your model picks and drives. Session only.
statusThe current run, and what is loaded. Session only.

Bring your data

--data FILEA local CSV: wide, long, or a single series.
--universe NAMEA universe you registered here, with the closes you stored.
--project MODULEA Python module. The only door that can carry a simulator.

Session

logoutRemove stored credentials from this machine.
versionPrint the version.
helpThe short list. Session only.
quitLeave the session.

Scripted or exploratory, and the run says which. run <workflow> reaches the same place twice over the same data. A plain-English question lets a model choose each step from what the server permits, so two runs may differ. Both are legitimate; presenting one as the other is not.

The eight questions

Eight workflows. They are not eight features — they are the eight questions a desk actually asks, in the order it asks them: can I trust this data, what is worth a look, does this signal carry information, is it real, where does it break, is it new or my book again, how much, and is it still inside the lines.

A ninth would have to name a question none of these answers. The admission test is stricter than that: a workflow has to close on an artifact that is an INPUT to somebody’s next verb. A run whose output is only prose about itself does no work.

Can I trust this data?

alphaengine run diagnose_data --universe sp500

Profiles the panel before anything is computed on it: how much history each name has, where the gaps are, which series end early, and how many spikes look like splits rather than moves. Reports; repairs nothing. A gap stays a gap, because filling one invents observations nobody supplied and makes every coverage figure downstream a lie.

What is worth a look?

alphaengine run screen_universe --universe sp500 --input rank_by=rsi

Ranks your universe and returns a bounded shortlist. If most of the universe could not be measured it refuses rather than ranking the survivors, because the names that drop out are the young, the illiquid and the recently listed — so the bias has a direction and it flatters.

Does this signal carry information?

alphaengine run evaluate_signal --project research.factor

The information coefficient through time, the spread between quantiles, and how fast the edge decays. The gate that matters here runs the other way: an implausibly HIGH IC stops the run, because nothing real ranks a wide cross-section that well and a leak does it effortlessly. A fabricated edge that ships is worse than any overfit one, and this is the cheapest place in the system to catch it.

Is it real, once you count the tries?

alphaengine run validate_study --project research.momentum

Sweeps your grid, then deflates the result for the number of configurations actually tried. Most results that look good do not survive this, which is the reason to run it. A result that holds at exactly one point in its parameter space is a fitted result; the workflow refuses it rather than dressing the problem up as a smaller number.

Where does it break?

alphaengine run stress_study --data returns.csv --input turnover=0.2

Subperiod stability, the drawdown anatomy, and the cost ladder — what execution costs the edge, and the basis-point level at which the strategy stops being one. Supply no turnover and the ladder is not drawn and says so, rather than reporting a cost of zero.

Is it new, or my book again?

alphaengine run check_overlap --data returns.csv

Correlation and beta of the candidate to what the book already holds — and, since 2026-08-08, the correlation THROUGH TIME rather than only on average. An idea that averages 0.2 to the book and runs at 0.9 in every drawdown is the book again exactly when being the book again costs money, and the average says the opposite.

How much?

alphaengine run size_position --data returns.csv --input risk_budget=0.01

A target weight, never a share count. If the track record is too short to tell the result from luck it refuses to size it at all — a small position is still a claim there is something there, and the caveat does not follow the number into a book.

Has anything crossed a line?

alphaengine run monitor_sleeve --data returns.csv   --input tolerances='{"max_drawdown_pct": 12}'

Measures a live sleeve against the tolerances you gave it, and reports every limit’s margin rather than only the ones that broke. A monitor does not stop on a breach — every other workflow stops when a gate refuses, but withholding a monitor’s artifact on a breach withholds it exactly when it matters. The status is four-way — ok, breached, undetermined, unchecked— because “nothing crossed” and “nobody was watching” must never read the same.

Install

Two runtime dependencies, numpy and scipy, both already present in a research environment. Importing the package makes no network call and needs no account.

pip install alphaengine

# factor decomposition and cointegration need statsmodels
pip install 'alphaengine[factors]'

Everything in part one works on a laptop with the wifi off, forever, at no cost. It is not a trial and nothing in it expires. See access for where the desk line falls and why.

Your first sweep

The library does not backtest anything. It runs your backtest once per combination in a grid, then measures the result against the size of the search that produced it.

from alphaengine import sweep

r = sweep(
    my_backtest,                                    # YOUR function
    {"fast": [5, 10, 20], "slow": [50, 100, 200]},  # the search
    data=prices,                                    # passed through untouched
)

r.n_trials
# 9

That number is why the API is shaped this way. The correction that makes a Sharpe ratio honest needs to know how many variants you tested. Ask a person for it and you get the number that flatters them, not from dishonesty, but because nobody counts what they threw away. Running the grid makes the count len(grid), so the question never has to be asked.

n_trials is a property with no setter. There is no argument anywhere in this package that lets you tell it how many things you tried.

Reading the surface

Before any verdict, look at the neighbourhood. A result sitting on a broad plateau is robust. A single spike surrounded by failures is a result fitted to its own parameters.

r.surface()
# {
#   'shape': 'knife_edge',
#   'n_ok': 9,
#   'n_failed': 0,
#   'best_sharpe': 1.9143,
#   'median_sharpe': 0.221,
#   'share_within_20pct_of_best': 0.1111,
#   'plateau_centre': None,
#   'reading': 'One configuration performs and its neighbours do not. '
#              'Usually a fitted result.',
# }
shape
plateau, ridge or knife_edge (API literal). Share of the grid within 20% of the best result: ≥30% plateau, ≥10% ridge, else knife_edge.
share_within_20pct_of_best
The number behind the shape, so you are free to disagree with the thresholds.
plateau_centre
Per parameter, where the robust region sits and how wide it is. The actionable half: it tells you where to re-centre, not merely that your number was flattered.
reading
The shape in one sentence. The same string every time, so it can be quoted.

Reading the verdict

The verdict deflates the best result for the search that actually produced it, and reports what it needed in order to say so.

v = r.verdict()

v["n_trials"]                 # 9
v["n_trials_source"]          # 'derived_from_grid'
v["deflated_sharpe"]          # 0.3106
v["verdict"]                  # 'inconclusive'
v["data_hash"]                # a content hash of what it ran on
v["min_track_record_length"]  # how long this record must run to beat luck

n_trials_source travels with the figure on purpose. A count that was asserted and a count that was counted are different claims, and every deflated number downstream rests on which one it is. When this package produces the figure the source is always derived_from_grid, because there is no other way to produce it here.

Probability of backtest overfitting is reported alongside, and is deliberately not the gate:

v["selection"]
# {
#   'question': 'was the choice among configurations informative?',
#   'not_a_verdict_on_the_edge': True,
#   'pbo': 0.48, ...
# }

PBO answers a different question, and returns roughly 0.5 for genuinely near-tied top configurations even when the underlying edge is real. Treating it and the deflated Sharpe as two readings of the same thing would be a mistake, so the shape of the return says they are not.

The study file

A study is 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.

r.save("study.json")
# PosixPath('study.json')

from alphaengine import load
s = load("study.json")

s.schema_version   # load() refuses an unknown MAJOR version
s.data_hash        # a label can be changed to escape a history; an array cannot
s.n_trials_source  # 'derived_from_grid'

The version rule here is stricter than semver: a bump is required whenever a computed valuechanges, even when the signature is untouched. Once the numbers are published they are a contract, and somebody’s saved study has to reproduce.

A study written on a laptop with no account opens unchanged in the portal. That is the entire join between the two halves of this page.

Wiring your own backtest

Your function is called once per combination and must return a sequence of per-period returns. It is called as backtest_fn(data=data, **params) when data is given, and backtest_fn(**params) when it is not.

def my_backtest(*, data, fast, slow):
    fast_ma = data.close.rolling(fast).mean()
    slow_ma = data.close.rolling(slow).mean()
    signal = (fast_ma > slow_ma).shift(1)
    return (signal * data.close.pct_change()).dropna().to_numpy()

r = sweep(my_backtest, {"fast": [5, 10, 20], "slow": [50, 100, 200]}, data=px)
data
Passed through untouched. Only hashed, never inspected, never stored.
store_params
OFF by default. Hashes are always kept, so runs stay comparable without the parameter values leaving your machine. The grid is often bigger intellectual property than the return series.
on_error
'record' (the default) marks a failing combination and continues, because one bad corner of a grid should not lose the other ninety-nine results. 'raise' propagates.

The result holds the full trial matrix, which is what PBO needs and what a single-point run structurally cannot produce. That is the argument for running a grid rather than calling a calculator on one number.

What is in the core

Everything below is a pure function: no clock read, no environment read, no data-layer import. That is guaranteed by a test that scans the module tree without importing it.

from alphaengine.core import (
    deflated_sharpe,             # corrected for the size of the search
    probabilistic_sharpe_ratio,
    expected_max_sharpe,         # what the best of N looks like under no edge
    min_track_record_length,     # how long before it beats luck
    pbo_cscv,                    # probability of backtest overfitting
    cpcv_score,                  # combinatorial purged cross-validation
    performance_report,
    compute_var_cvar,
    run_backtest,
    score_backtest,
    technical_features,
)
VaR and CVaR
Always POSITIVE loss magnitudes, with CVaR at least VaR. Sign conventions are the most common source of a wrong risk number, so this one is fixed everywhere.
Calmar
Geometric. Annualised return compounds rather than averaging.
Sortino
The denominator is target semideviation over all N periods, not the standard deviation of the negative subset.

Factor decomposition and cointegration live behind the factors extra, so statsmodels is not a hard dependency of the core.

Whose data it is

The package makes no network call at all, so nothing here leaves your machine unless you send it. The platform is different and says so: it holds what it needs to do the job, because a monitor cannot run overnight against a browser tab and a study cannot be reproduced in two years against a series nobody kept. The question that matters is not whether we hold it, it is on what terms.

Yours
Encrypted at rest, scoped to your account, exportable in full, deletable in full.
Never pooled
One customer's data never sits in a store another can read, and no aggregate is derived across customers.
Never learned from
Nothing trains or tunes on it. Not a model, not a ranking, not a default.
Never redistributed
Vendor data stays under your licence and your entitlement, used on your behalf and never resold.
Your model
Never ingested. We do not take your alpha logic.
Your parameter grid
Hashed by default; values kept only if you ask for it.

The full line, item by item, is on the Trust page.

Part two · The portal

What the portal is for

Part one runs on one machine and answers one question: does this hold up? The portal exists for everything that happens after that answer has to reach a second person. Two pages do that work: Chain is the handshake, Work is every run. Inspect is a Work object. Connect stays on the rail. Floor and Desk are a corner popup, not a reserved column.

A fund at this size has a specific failure, and all three seats named it independently when we researched them: the most valuable thing anyone produces is a spoken explanation, the decision record does not exist, and every review is a reconstruction assembled backwards from a profit and loss number everyone already knows. The portal’s only job is to make that record a byproduct of the work instead of a chore after it.

Nothing in the portal should ask you to retype something the system already knows. If a screen makes you re-enter a number that exists upstream, that screen is wrong and we would like to hear about it.

Chain: the handshake

Chain is who is waiting on whom. Incoming findings, asks sent out, work sent back, and the names the desk has already agreed. A row is an ask or a sealed run. Accept records a decision; it does not book a trade. Agents propose only.

Incoming
A sealed run waiting on the PM. Open it and inspect is the path, the surface, and the count.
Out
An ask the PM sent. The quant has not delivered yet.
Returned
Sent back with a reason. The floor still has work to do.
Names agreed
Compilation status when the night pass closed. Agreement is arithmetic across independent runs, not a model ranking.

Floor, Book, Record, Signal, Handoffs, Decisions, Reports and Calendar are retired addresses. They redirect here. The handshake is one page.

Work: the finding

Work is every OS ingest and every harness run, newest first. A row is the finding: the headline, how many trials, whether the count was derived, the shape of the surface, how many names were rejected. It is not a figure dump. Charts stay on inspect.

A study written on a laptop with no account lands here when it is handed across. The portal marks it from the OS and projects the same blocks a portal validate_studyalready draws: the winner’s path, the drawdown, the parameter surface, the configurations tried.

The list
The Work index is the finding, the trial count, the rejects, the chain hook. No blocks, no chart series.
Inspect
Opening a run is fat. Stop leads when the run refused. Then the figures. Then the modeling trace.
From the OS
Laptop sweep JSON is the same figures under different keys. The PM inspects the path a portal study already knows how to draw.

Floor and the eight questions

Floor is the quant’s popup, Desk the PM’s. Closed by default. Open is an overlay and a floating card; the working surface never yields a gutter to it. The eight-question table lives inside Floor so a model never chooses which workflow to run.

If your prices are on the account, all eight questions run here with no command and no local copy of anything. Ask in plain English; a table routes. The answer comes back as a run, with the same engine, the same gates and the same record as anything started from a terminal.

What runs in the portal
All eight, fed from a universe you uploaded on Connect, your own valuation snapshots, or your own filed rankings. Nothing is fetched on your behalf, ever.
What still runs on your machine
Validating a strategy YOU wrote. A simulator is code, and running your code on our servers is a different security posture than this product has. `alphaengine run validate_study --project <module>` stays the honest path for it.
The middle case
A study in the portal validates a BUILT-IN strategy family you pick by name — a crossover, cross-sectional momentum, mean reversion — with the same deflation and the same gates. The artifact records `strategy_source: builtin_declarative`, so a reader can never mistake one for a study of your own simulator.

A signal panel is refused rather than misaligned.If the universe was uploaded without its date column, scores dated to real days cannot be matched to positional closes — and the arithmetic would still return a confident number. It says so instead, and names the fix.

Inspect: charts and the trace

A run has its own page, and the id the terminal prints resolves to it. Work owns this object. Stop leads. Then the figures. Then the modeling trace. Then the written reading. Chain rides alongside when this run answers an ask. Quant delivers. PM decides.

The modeling is drawn, not just summarised. A Sharpe ratio and a deflated Sharpe are conclusions; what a reader has to judge is the work behind them, so inspect renders the mathematics the workflow actually did:

Parameter surface
Every configuration the sweep tried, as a grid, with the winner ringed. A cell that did not run is a hole, never a zero — a failed trial still counts in the denominator, and it must not read as a bad result.
The path and the drawdown
The winning configuration's own equity curve and where it fell. A Sharpe is believed with its path; the drawdown is the risk that number hides.
IC through time
For a signal evaluation: the information coefficient period by period, not just its mean. A mean IC of 0.03 built from a steady 0.03 and one built from +0.4 and −0.34 are different signals.
Correlation through time
For an overlap check: how the relationship to the book MOVED, plus the joint distribution behind it. A tight diagonal is the same bet twice; a cloud is genuinely new risk.
The cost descent
For a stress run: Sharpe against execution cost, and the basis-point level where the edge dies.
Every limit's margin
For a monitor: each reading as a share of its own limit, so limits in different units compare. A limit nobody could measure is absent rather than drawn at zero.

The curves are derived on your machine and bounded before they travel.What reaches the record is a summary of your backtest, computed where your data already is — never the data. The bound is 512 points, which is a couple of years of daily observations; a longer history is bucketed rather than shipped whole, and a payload larger than that is refused outright.

Below the figures is the step trace: every operation the engine executed in order, failures included. An OS study has no harness steps; inspect shows the three the package actually ran — alphaengine.sweep, compute.deflate, emit.study— with the figures that survived ingest. A run that stopped ends there rather than at its emit step. The missing tail is the record of where it refused.

The five objects

The whole product is five things that reference each other. Each points at the one below it by identity, never by a copy, so any figure can be traced back to the run that produced it.

Study
What was tried, how many ways, on what data. Produced by a sweep, not by typing. Frozen when published, and read as published rather than live.
Thesis
A study, plus what has to be true, what it is worth, what would falsify it, and the dates that settle it.
Position
A thesis, plus the size you chose, the limit that actually bound, your conviction at the time, and the fills against it.
Book
Positions rolled up to a sleeve, against the limits set for that sleeve, with every breach and what was decided about it.
Packet
All of the above for a period: frozen, countersigned, and exportable to somebody who was not in the room.

Two rules hold the chain together. Nothing is retyped at a handoff. And a figure that cannot carry the run that produced it does not travel.

Solo, Pod, Fund

Solo: one person, the whole loop

You are the quant and the PM. The handoffs still exist; they just happen six months later, when you have to explain to yourself why you are long that name. Solo makes the record for that conversation.

Runs persist as you work, with no save button to remember. Studies accumulate, including the ones that failed, so you stop rediscovering last quarter’s dead end. Chain still shows what is waiting on you. Work still opens the path.

Pod: quants and a PM

The crossing this tier exists for is quant to PM. A study is handed across with its provenance attached, and the PM gets the surface they actually need: what was tried this week, how many ways, on what data, what died, and whether the survivor holds up. That surface is Chain and inspect, not a slide.

The PM works in theses and positions. Sizing happens against the limits that apply, and the reason for the size is recorded at the moment of the decision, because whether a position is small from low conviction or from a bound limit are opposite facts about your process, and a return series cannot tell them apart afterwards. Desk drafts; it does not book.

What leaves is a ranked signal file: ticker, score, rank, target weight, dated and versioned. Your order management system turns that into orders. We never send a share count anywhere.

Fund: a CIO over several sleeves

A risk budget can only tighten. The budget a CIO sets and the mandate a PM already holds are merged on the server, taking the stricter of each limit, so a PM cannot decline a budget by not sending it, and a missing limit is silence rather than permission.

This tier is enforced but not yet a third seat on the rail. The budget merge, the allocation rules and the countersignature all run server-side and are covered by tests. The portal itself is two seats — PM and quant — sharing Chain and Work, while the quant-to-PM loop is the product. We would rather say that than show you a screenshot of a screen you cannot open.

Breaches get a disposition: cut, waive or hold, with a reason, a name and an expiry. A limit with no recorded decision behind it is the single thing an operational due diligence team asks to see and nobody can produce.

Periods close. The review packet, the letter commentary, the committee pack and the diligence responses assemble from evidence already collected, and a countersignature attaches to frozen content rather than to a pointer at something still editable.

This page describes what the objects mean and what each seat does with them, which is the half that does not move when a screen does. Request access or read the data boundary.