Prepare indicators for a fixed backtest¶
Use this option when you already have the complete input dataset and want to avoid recalculating the same indicator history at every decision. It applies to Python strategies using Atom's injected indicators and to TradeScript. It does not fetch, resample, forward-fill or correct Oracle/SharkSigma data.
atom run strategy.py --data prices.csv --immutable-history --precompute-indicators
tradescript run strategy.strat --data prices.csv --market us --immutable-history --precompute-indicators
The Python equivalent is
Engine(strategy_module=strategy, bars=bars).start(immutable_history=True, precompute_indicators=True).
Both options default to False. Preparation without immutable history is an
error before the strategy starts. Live/paper, custom clients/feeds, persistence
and resume retain the ordinary path; the snapshot admission rules
still apply. No extra compiler or installation step is needed.
Keep the same strategy¶
For example, supply one-minute SPY OHLCV observations for this TradeScript rule:
//@version=1
symbol: SPY
run: each bar
size: 1 share per instrument
entry:
long when: close > ema(20) and macd_hist(close) > 0
exit:
long when: close < ema(20)
EMA20 is a price; MACD histogram is the distance between its MACD and signal lines. An entry requires both comparisons. The exit only needs price below EMA20. Preparation changes how the calculations are reused, not those rules. If a decision queues one share and the next observed open is 102, the base fill price remains 102, with your configured slippage and fees applied normally. There are no fills on unobserved prices and no skipped decision callbacks.
The qualified provider calculates a vector over the privately owned input once
per admitted input/parameter combination. At observation 30, your strategy sees
30 outputs, never output 31. [0] is the current reading and [1] the previous
one; keeping this result does not advance it on the next callback. Warmup and
missing-input masks are retained. A hole invalidates the consuming window;
inception-dependent calculations do not restart their seed after that hole.
Public result reads work as before; the prepared private numeric buffer is
read-only. Trusted Python still must not inspect private storage or close over
future input. This is not a security sandbox.
Coverage and fallback¶
This is a shared provider path, not replacement implementations of SMA or RSI. Qualification covers the 56 exposed TAP series/component contracts, including rolling, recursive, cumulative, OHLCV and named multi-line outputs. Formulas remain in TAP. Current preparation requires the tested TAP 8.3.0 implementation and its actual wrapper; a compatible later patch still runs normally until its preparation semantics are qualified.
- Plain and symbol-bound OHLCV series can prepare qualified outputs. Symbols are input identifiers, not a special SPY list; each symbol/field/parameter combination has separate storage.
- Explicit TradeScript base-source reads such as
$SPY.ema(close, 20)and$SPY.atr(bars, 14)use the same preparation contract. Replace SPY with any supplied symbol; there is no instrument-name allowlist. A missing SPY row remains a hole even while another symbol has a current bar. The first qualified panel materialization preserves its original column types and metadata; later reads slice only the observed prefix. - Inception-dependent
barscalculations, such as ATR, OBV and cumulative VWAP, can prepare on complete, single-timeframe symbol streams. - Rolling panel calculations reuse current values and the previous 63 offsets.
Deep indexing,
.seriesand length retain the normal bounded history through a lazy calculation pinned to that observation, even when read after a later tick. - Sparse/mixed-timeframe panels, nullable integers needing dtype promotion,
derived/custom sources, chained results, direct
tap.atomimports, Alpha and unqualified providers keep their existing paths. Nested calls may still benefit from preparation of their ordinary raw-series input.
Native higher-timeframe snapshots select published rows by an availability
index; they are never resampled from the execution feed. Finite, single-symbol
snapshots can use the same qualified provider preparation, bounded by their
published native-row count, not the number of base bars. For example,
$SPY.1H.sma(close, 3) needs three published hourly observations; ten minute
callbacks between publications still see the same hourly result. A changed
source revision gets its own snapshot and retained prior results never advance.
Sparse or otherwise inadmissible native inputs retain ordinary observed-prefix
evaluation. All source families share the run's output limits. Additional
native input copies are capped at 64 snapshots and 2,000,000 rows in total;
exceeding either cap falls back without eviction or loss of observations.
The first observed prefix (the latest 64 values for an already bounded panel) is compared exactly with ordinary evaluation before retention. Tests compare early, late and missing-data prefixes. The per-run limit is 64 attempted combinations and 2,000,000 retained numeric values (about 16 MB of output buffers, excluding input and temporary provider allocations). Additional combinations fall back; they do not evict a retained result or allocate an unbounded cache. Dynamic periods and large universes can therefore receive less acceleration.
Prepared rolling series use the provider's full-input arithmetic. The older snapshot-only tail optimization can differ by a last floating-point bit, so a threshold exactly on that boundary can differ. Compare against ordinary full history and inspect actual fills, not only approximate numeric equality. No claim of universal trade equivalence follows from a finite fixture set.
Check what ran¶
result.execution_profile["indicators"] reports preparation_requested,
prepared_calls (distinct retained combinations) and prepared_values.
Zero retained combinations means normal evaluation, not a missing calculation.
The event driver remains Python and all modes use the same financial kernel.
Measure the complete run including snapshot copying and lazy preparation:
POLARS_MAX_THREADS=2 OPENBLAS_NUM_THREADS=1 .venv/bin/python scripts/benchmark_engines.py 250 3 --indicators --workload trend --immutable-history --precompute-indicators --scale-factor 10 --records-dir .context/trend-records
The benchmark also offers baseline, flow and panel workloads. Compare the
same input and complete financial-record hashes without the two flags as well.
Research modes distinguish this full event timer from
prepared-order scheduling, other libraries and production-live throughput.
Arbitrary mutable history still requires rereading corrections; provider-owned
incremental state is a separate future integration, not implemented by this option.