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.