backtest_run (or the REST API receives POST /api/runs with kind: "backtest"), Ithaca loads the referenced StrategySpec, fetches an aligned price panel, and runs a multi-asset accounting kernel. The result is a deterministic equity curve, a stats block, and a risk attestation — all reproducible to the bit.
Pipeline
Fetch price panel
An aligned multi-asset price panel is fetched from the configured data source (e.g. Polygon). The panel is a single numpy array of shape
(T, N, K) — time × names × fields (open, high, low, close, volume). All names are aligned to the same trading calendar; missing bars are forward-filled then flagged.Run accounting kernel
The kernel computes signal → weights → returns with no lookahead: weights computed at time
t-1 are applied to returns at time t. This is the single most important invariant in the engine.Emit artifacts
The 8-file artifact bundle is written and content-hashed. See Artifacts.
No-lookback invariant
weights[t-1] are applied to return[t]. The kernel never reads a bar at time t to decide the weight at time t. Violating this would be a critical bug and is covered by the test suite.t cannot influence the weight applied to day t’s return.
Return values
A successfulbacktest_run returns:
| Field | Type | Description |
|---|---|---|
equityCurve | number[] | Daily equity values, starting at 1.0. |
stats | object | Performance + risk statistics (below). |
finalWeights | object | Last-bar per-name weights, keyed by symbol. |
riskAttestation | object | Confirmation that risk guardrails held. |
stats block
| Field | Type | Description |
|---|---|---|
sharpe | float | Annualized Sharpe ratio (rf = 0). |
sortino | float | Annualized Sortino ratio. |
cagr | float | Compound annual growth rate. |
totalReturn | float | Total return over the window. |
vol | float | Annualized volatility. |
maxDD | float | Maximum drawdown (positive number). |
winRate | float | Fraction of days with positive return. |
turnover | float | Average daily turnover. |
var95 | float | 95% Value-at-Risk (daily). |
cvar95 | float | 95% Conditional VaR (daily). |
riskAttestation
violations lists the bars and the breach. A non-empty violations array does not fail the run, but it is surfaced to the human reviewer.
Job lifecycle
A backtest run moves through a state machine:draft | Spec is attached; run not yet enqueued. |
| queued | Run is in the work queue waiting for a worker lease. |
| running | A worker has leased the run and is executing the kernel. |
| succeeded | Kernel completed; artifacts written. |
| failed | Kernel raised an error. Error captured in the trace. |
| timeout | Run exceeded its tier’s wall-clock budget. |
| cancelled | A human or the system cancelled the run. |
Transitions are emitted as run_transition trace events and streamed over SSE. See Run endpoints for the REST API and Trace events for the event contract.
Determinism
A backtest is reproducible if and only if these three things are held constant:seed— the RNG seed from the spec (default42).- Pure numpy — the kernel uses only deterministic numpy operations. No
random.random(), no GPU non-determinism, no floating-point reordering across runs. - Fixed window — the same price panel (same symbols, same calendar, same bar range).
environment_lock.json that pins the numpy version and panel hash so a future re-run can be verified.
Related
- StrategySpec — the declarative input
- Artifacts — the 8-file output bundle
- Approval workflow — promoting a backtest to paper trading
- Run endpoints — REST API for creating and inspecting runs