Skip to content

Replay fixed orders and price rules

Use prepared replay when you already have one instrument's historical prices and market-order decisions, or a simple TradeScript price rule. It skips observations that cannot change a decision or fill, while retaining Atom's normal next-open execution, cash checks, costs and position reports. It is a local research API, not a broker connection or a production strategy runner.

For indicators, arbitrary Python callbacks, portfolios, protection or advanced instruments, keep the ordinary event backtest. Nothing is silently translated to a simpler financial model.

Supply the observations and decisions

Pass a Polars DataFrame with timestamp, symbol, open, high, low, close and volume. An optional timeframe column labels the actual supplied interval; without it the label is 1m, as with Bar.from_polars. Labels do not resample data. Timestamps must be strictly increasing UTC datetimes; naive datetimes mean UTC. Supply exactly one nonempty symbol and one timeframe. Each timestamp must identify a completed observation; the data owner must not label future aggregate OHLC as already available at an earlier instant. Prices must be positive and finite, high/low must enclose open/close, and volume must be finite and nonnegative. Nulls, duplicates, unsorted data, extra metadata columns and mixed instruments refuse admission; the API does not repair them. Select the required columns explicitly from enriched Oracle/SharkSigma output.

This complete example buys after the first observation and sells after the third:

from datetime import datetime, timedelta, timezone
import polars as pl
from atom.research import ReplayOrder, prepare_replay

prices = pl.DataFrame({
    "timestamp": [datetime(2026, 1, 1, tzinfo=timezone.utc) + timedelta(minutes=i)
                  for i in range(4)],
    "symbol": ["A"] * 4,
    "open": [100., 102., 105., 104.],
    "high": [101., 104., 106., 105.],
    "low": [99., 101., 103., 102.],
    "close": [100., 103., 104., 104.],
    "volume": [1000.] * 4,
})
plan = prepare_replay(prices, [ReplayOrder(0, "BUY", 1), ReplayOrder(2, "SELL", 1)])
result = plan.run(starting_capital=10_000, commission_bps=0)
assert result.portfolio.cash == 10_002
assert result.portfolio.total_value == 10_002
assert result.wiring.closed_trades[0].bars_held == 2

bar_index is a zero-based decision index, not a fill index. The buy fills at the second open, 102, and the sell at the fourth open, 104. Decisions on the last observation remain pending; there is no automatic final liquidation. Orders at the same index retain their supplied order. qty is a positive whole share count; zero, fractional and nonfinite quantities refuse. These are cash equity orders in USD, regardless of ticker spelling. A symbol does not declare Forex, crypto, futures, options, margin or currency-conversion terms. Those workflows need the ordinary qualified engine/host path. Prepared sells can reduce, reverse or open a short under the simulator's existing cash model; this does not simulate borrow availability, financing or margin.

starting_capital defaults to USD 100,000. commission_bps defaults to 1 per fill, matching the event engine's conservative default; zero explicitly opts out. slippage_bps defaults to zero, is nonnegative and must be below 10,000. It increases buys and decreases sells at the next actual open. These two numeric settings select Atom's existing BPS models, not new cost formulas. Cash admission includes fees. A rejected buy stays rejected; later supplied intents still run. Inspect rejected_orders, rejected_sample and pending orders rather than assuming every decision filled.

Preparation owns a detached copy. Editing the caller's frame, a returned feed Bar or a previous result's portfolio cannot alter a later run. Every plan.run() starts a fresh account; reuse a plan for repeated cost/capital comparisons. A nonempty bounded list/tuple of ordinary Bar objects is also accepted, but owning them separately costs more than copying one frame. Dense snapshot limits still apply. Do not mutate inputs concurrently during preparation.

Replay a qualified TradeScript rule

Use the same prices with this source; no new language version or syntax is needed:

from tradescript.replay import prepare_replay

source = """//@version=1
symbol: A
run: each bar
size: 1 share per instrument
entry:
    long when: close < 101
exit:
    long when: close > 103
"""
plan = prepare_replay(prices, source)
result = plan.run(starting_capital=10_000, commission_bps=0)
assert result.portfolio.cash == 10_002

The admitted subset is one literal symbol, run: each bar, fixed shares per instrument, plain long entries and full conditional long exits. Conditions use raw OHLCV names, constant nonnegative offsets such as close[1], numeric literals, comparisons, and, or and not. Automatic or explicit warmup still applies; offsets count original observations, not selected callbacks. Every cold observation runs normally, preserving missing-history diagnostics. If warmup exceeds the supplied history, entries stay unready; no additional observations are fetched or allocated to satisfy it. A candidate means a rule may act, not that a trade has been preapproved. The original generated callback still checks readiness, holdings, quantities and admission. Repeated true conditions remain candidates even if an earlier buy was rejected.

Definitions, arithmetic, provider/indicator calls, position predicates, multiple symbols, market/time schedules, portfolio sizing, short rules, scaling, options, stops and risk directives are not admitted by this API. They remain available through the normal language/runtime where supported. Integer input values and literals beyond magnitude 2**53 also refuse this subset, avoiding Polars/Python mixed-numeric comparison ambiguity. Refusals identify <replay>:line:column; use the ordinary runner rather than changing the rule to obtain admission. Prepared intents must themselves be causal: Atom cannot prove that an external decision producer did not inspect future prices.

Choose and measure a backend

backend="auto" uses native scheduling when the compatible wheel is installed, otherwise the complete event loop. backend="native" requires the binary and refuses if missing; backend="event" is the full-loop reference over the same owned input. Unsupported scripts refuse preparation on all backends; auto does not secretly broaden the language subset. Source users can use event; wheel users need no Rust compiler.

Inspect result.wiring.execution_profile: replay_scheduler is rust or event, event_bars counts executed callback rows, and event_driver remains python. bars_processed counts the original observations. Bar indices, timestamps, observed history, open-position peaks/troughs and trade holding lengths retain the original axis. No per-bar callback/progress, custom model, persistence, live execution, equity-curve or analytics API is provided here.

Measure preparation, first run and repeated runs separately. Savings depend on how many observations cannot act; always-true rules may save nothing. Compare identical inputs, costs and execution reports before comparing runtimes. Prepared price-rule timings do not establish indicator or live-host parity. This is native scheduling, not a claim that arbitrary TradeScript or a complete backtest runs entirely in Rust.