Run & evaluate
Preview & Backtest
VeriRun Lab separates “show me what my strategy does” from “tell me whether it holds up”. Previews answer the first question with a short run over a few sessions on the same conservative execution a backtest uses; backtests answer the second with that execution over the full window plus the statistics and the scorecard. A preview is honest per session and faster only because it does less. Knowing which question each one answers is the key to reading both correctly.
Two run modes, one fill model
| Preview | Backtest | |
|---|---|---|
| Purpose | A teaching surface: see the engine’s own orders, fills and trades, and your strategy’s annotations, on a chart — over a few sessions. | Research-grade evidence: a graded result under pessimistic assumptions. |
| Scope | Bounded — a few sessions (the platform’s current default, 3 as shipped, unless you say otherwise; its current ceiling, 5 as shipped). | The full window declared by the strategy's spec. |
| Fills | The same conservative venue as a backtest (since 2026-09-23): a limit order needs price to trade through it, fees are charged, a triggered stop entry fills worse than its trigger. No statistics, no scorecard, no performance report. | Conservative: a limit order needs price to trade through it (someone actually traded past your price); maker orders are post-only; per-contract fees are charged; a triggered stop entry fills slightly worse than its trigger price. |
| Unlocks after | Tests green. | Tests green and the version explicitly approved. |
| Cost | Free — previews never consume credits. | Shows a credit estimate before launch. |
The app never lets you forget which one you're looking at. Every preview page carries a banner naming its venue — “This preview ran on the realistic engine (conservative venue) — the same fills as a backtest over N sessions; no statistics”. A preview stored from the retired four-callback form keeps the one line its old pipeline wrote (a fast first pass chose which stretches of each day the engine saw, and the chart’s indicator lines are that pass’s); an engine-native strategy’s preview and a built-in template’s carry no such line. Every backtest result carries its method banner: “Conservative fills (trade-through required, maker post-only, per-contract fees). Research result — probabilities, not promises.”
What changed on 2026-09-23. Previews used to run on an idealized fast simulator (a limit filled the moment price touched it) with an optional “realistic check” of one session, a rehearsal of the backtest’s benchmark stage and a table of detected-but-rejected setups. All of that is gone: a preview now runs the engine itself, so a strategy that previews well cannot backtest to nothing because of a fill-model gap. What you lose until the new lane lands: the rejected-setups table (a preview shows what the venue did, never a setup it did not see), the per-setup debug trace and the per-trade chart receipt. The order shapes a backtest cannot host are refused by the engine itself, in the preview.
Engine-native strategies. For a strategy written against the
simulated exchange directly, preview and backtest are ONE lane: a preview is the same
run over its last few sessions, a backtest the whole window, and an optimizer trial
the same again with an early stop — the same fills, the same records, no fast
pass choosing what the venue sees, and no note line to carry: nothing is inherited
from the old pipeline. A preview whose window touches a held-out range is refused by
name at launch. The launch box for such a strategy shows its own settings — the
hours it trades, what happens at each session's end, how the simulated exchange fills
you, and every advanced exchange, account and risk option — each marked with
who decided it: Platform default, From your strategy (your code's
RUN_OPTIONS) or Your choice (see
Launching a run).
Built-in templates (since 2026-10-22). A strategy built from one of the two built-in templates runs on that same lane: its preview, backtest, study and screen are real runs of the template’s own strategy on the simulated exchange, with the same records and the same graded basis. The spec’s hours, its close and its fill model are the run’s From your strategy settings, which a launch may change for that run (Your choice). The Fills tab names each setup that did not become a normal trade and why (no opening range formed, a stop too wide to size, an account rule, an entry window that closed). The template’s declared stop slippage is recorded with the run’s costs and priced by its cost grid, not taken off each fill. The quick significance check stays what it was: the template’s own setup detector over the window’s sessions, free. A template run made before that date came from the old two-pass pipeline and keeps its labels; a template run still queued when the change shipped did not run and says so — launch it again from the strategy version.
Optional: the realistic execution venue (diagnostic only)
Code-path backtests can opt into a third venue at launch: Realistic. Resting orders wait behind the displayed queue at their price and fill only as real prints consume it (possibly partially), and every order carries a fixed 150 ms decision→exchange delay — so it shows what queue position and latency would actually do to your entries. It is a diagnostics surface: a realistic run gets no scorecard grade (grades always come from the conservative tier), and the result pages label it “Diagnostic tier — not graded” everywhere.
Why the pessimism?
Optimistic fills are the classic way backtests flatter you: in real markets, the limit orders that fill effortlessly are disproportionately the ones you'll wish hadn't. Requiring trade-through, charging fees and filling stop entries with slippage means that when a strategy still looks good, the edge is less likely to be an artifact of the simulator. The gap between the two fill models is itself a diagnostic — see the fill-realism gap.
Launching a run
- Press Preview or Backtest in the workspace header (a strategy built from a template: Run preview or Run backtest on its version page). If the button is disabled, its tooltip names the reason (tests gate, missing version, missing approval).
- Name the run, if you like. Above the steps, the Name (optional) box takes up to 200 characters. The name is shown wherever the run is listed or picked — its tab, the run history, the Data Lens — and you can rename it there any time. Leave it blank and the run is shown by its strategy.
-
Work through the launch dialog's steps.
For a code strategy the dialog has up to five numbered steps:
1 · What runs — the instrument: your strategy's own
INSTRUMENTline is the default, and you can pick any other market (markets with data are listed first); 2 · When — the window (presets, start and end dates, the calendar; a preset fills the start and end dates and the calendar with the dates it covers, so you can adjust either end from there), the hours it trades (Regular hours, Full exchange session or Your own window) and what happens at each session's end (Flatten at session end, Flatten at a time I set or Carry into the next session); 3 · How the simulated exchange fills you — the fill model (only the conservative one is graded; any other is labelled diagnostic); 4 · Your strategy’s settings — your settings class's fields (a blank keeps your code's default; a required field must be given); 5 · Advanced exchange, account and risk options — closed by default, but its header still says what is in effect inside; open it for a search and a Changed only filter. Every setting shows where its value comes from — Platform default, From your strategy or Your choice (Picked for you when the dialog chose the only market with data for you) — and an ⓘ that explains what it does, with an example and the line that sets it in your code. Undo puts a setting back to your strategy's value, or else the platform default. For a template strategy, the window and data slice come from its versioned spec and are shown read-only, and the hours, the close and the fill model start from the spec (From your strategy) — e.g. “Window: 2026-01-05 → 2026-03-27” with a coverage check; a preview chooses its Preview length (up to the platform's current cap, 5 sessions as shipped — the slider stops at it) and an optional Tick limit. If part of the window has no data on the platform, the dialog says so before anything runs. - The coverage check is per instrument. It asks the same question the runner enforces — is there data for this strategy’s instrument over this window — and the verdict names it, e.g. “2026-01-05 → 2026-03-27 · 58 sessions · full NQ.v.0 coverage”. A day only counts once its data is downloaded and built into the engine catalog. If the window has no data for that instrument, the readiness bar says so before you can launch — in the same words the platform would refuse with, “This window has no data for this instrument” — names the span that does have data, and its Use ‹market›’s available range button jumps to it in one click — so a green preview and a failed run cannot disagree. A screener sweep, which runs once per instrument, requires the window to be covered for every instrument you selected. A strategy spec’s launch dialog asks the same question for every instrument the spec names, and the run stages exactly those instruments from the same catalog — a spec that names an instrument the platform has no data for is refused before it runs, on every path.
- A strategy that reads several markets. When your strategy reads more than one market (see Writing strategies), the dialog's 1 · What runs step shows an Other market row for each further market under the Instrument — the market your orders go on — each with its chip (From your strategy or Your choice), an ⓘ and Undo. A strategy that declares its markets opens on them (change which, not how many); one that names none offers Add a market, up to the platform's limit — at the limit the button goes and the dialog says “This platform allows up to 2 markets per run.” The coverage line then counts the sessions every market holds: a session one market lacks is left out of the run, never filled in, and the finished run says so (“Ran on 97 of 131 sessions — NQ.v.0 has no data on 34 …”). A preview's tick limit applies to each market's data on its own. The preview's chart and its bar-by-bar Replay draw the other markets on their own panes under the price, with a Swap button that puts one on top.
- Read the readiness bar. The bar at the bottom of the dialog says the run in one sentence — the market, the window, the sessions, the hours, the close and the fills — and shows the Estimated cost (credits): approximate compute minutes and credits, with an honest caveat that cost bases are estimates (see Data & credits for how to read it). If anything stops the launch, the bar names the problem and why it matters, and its Fix button takes you to the field; + N more to fix says when there is more than one. A template strategy's backtest also needs its brief approved: the approval box sits just above the bar, and Approve brief & launch approves it (with your authenticator code) and launches in one step.
- Press Launch (or Ctrl+Enter / ⌘+Enter). Launch looks dimmed until the run is ready; pressing it then sends nothing and shows you what is missing. A launch the platform refuses says why, in the dialog, with the fix — or, for a backtest that needs more memory than one run can have, with Report this (what that means). Once launched, the run is queued; its status chip moves through queued → running → succeeded (or failed / cancelled / timed out). You can leave the page — every run lands in the run library.
Why a run sits in queued
Platform background work — market-data downloads, catalog and report builds, the nightly passes — runs on its own worker, so it can never take the slot your test run, preview or backtest is waiting for. And queued runs are ordered round-robin between accounts: everybody's first queued run goes before anybody's second, so one busy account cannot push yours to the back. Your own runs keep the order you launched them in.
What genuinely does make a run wait:
- Memory. A backtest reserves more RAM than a preview, and both cannot be in flight at once on the same host. The run's event log says waiting for memory on the worker when this is the reason — or a catalog data build is running on this server when the platform is rebuilding its market-data catalog; either way the run starts on its own.
- Your own lane. One backtest at a time per account; a second starts when the first finishes.
When a backtest is interrupted: Resume
A backtest that failed for a platform reason can be resumed from where it stopped for 48 hours. The platform's reasons are: the server stopped under the run, an unexpected platform error, a wait for memory that ran past its limit before the backtest ran, a memory limit in a step after the backtest itself had finished, and a timeout once at least one contract segment of the backtest was saved. The run's card then says The work this run finished is saved. with the time it is kept until, the run library marks it resumable until that time, and Resume puts the same run back in the queue as its next attempt — it continues after the finished backtest, from the last completed contract segment, or with the market data already prepared, as the card says. Cancelling a resume that is still waiting in the queue withdraws it; the saved work stays.
When the platform itself interrupts a backtest — a deploy, a restart of the machine — it restarts the run on its own, once by default, and the run simply shows queued again. A restarted run's performance report says Interrupted for <duration>, restarted as attempt N under its elapsed time, which spans the interruption when the backtest itself was continued across attempts.
Never resumable: a failure in your strategy's own code, the backtest's own memory limit, a Stop, and a resumed backtest that ran out of time again without finishing a further segment — resuming once more would only repeat the same work, so the fixes are a larger time budget from an administrator or a shorter window. The saved work is cleared when its time runs out, to free disk space, or when the market data was rebuilt or the platform was updated since; the card says which.
Execution settings, parameters, and re-runs
A code strategy launches with the run options above — the hours it trades, what happens at each session's end, how the exchange fills it, and the advanced drawer — and with its own settings: the dialog shows your settings class's fields with their defaults, and only the values you change travel with the run, checked when you launch. Everything is recorded in the run's manifest with where it came from. The retired four-callback form's own launch controls (an execution venue and execution settings) are gone with it; a run stored with them keeps showing what it used.
Code-path preview and backtest detail pages have Re-run with changes: it reopens the launch dialog prefilled with that run's name, window, parameters, venue, and execution settings, and the new run records which run it was seeded from.
Reading the chart
Preview results open a per-session chart with a tab per symbol and date. One rule places everything on it: a marker sits on the candle that was open at the moment it happened (an entry filled at 09:35:03 sits on the 09:35 candle), and a value computed at a bar's close sits on that bar — so what you see is never a candle ahead of what the strategy could have known. The visual vocabulary:
Markers
- Blue arrows — entries; the arrow points up for longs, down for shorts.
- Green markers — winning exits; red markers — losing exits.
- Amber markers — signals (a setup was detected).
- Gray markers — rejected setups on a backtest chart: the pattern fired but no order ever filled or the setup was refused; the marker's text is the reject reason. A preview chart (since 2026-09-23) draws the engine's fills only — it has no reject markers until the new simulation lane lands.
Lines
- Selecting a trade draws its entry (blue solid), stop (red dotted) and target (green dotted) price lines.
- Level lines show market structure your strategy used: point of control (amber), value-area high/low (dashed), opening-range high/low (blue dashed), swept levels (sparse dots), and VWAP (amber).
- Levels your strategy plots itself (
plot_level(self, name, price), from the platform'splatform_sdk.plot) draw as labelled segments from the bar they were plotted on, at every timeframe. A level that stood for less than one bar at the timeframe you picked is not drawn — stretching it would misstate how long it stood — and the chart says how many were left out (“2 levels too brief to show at 5m”); pick a finer timeframe to see them. - Your own drawings —
plotlines,plot_levellevels andmarkmarkers — draw here too, on preview and backtest charts alike, each with the label you wrote. This is where you verify the code sees the market the way you meant. A level or a marker you bind to one trade (plot_level(self, name, price, scope="trade", trade=order.client_order_id), any order of that trade) appears on that trade's per-trade page and in its trade view only, never on a neighbouring trade's; the session chart shows them all. One that names an order no trade filled stays on the session chart, and the run's events say how many there were. A run stored from the retired four-callback form keeps the levels and zones that form drew.
Panes
- Volume — per-bar volume histogram.
- Delta — per-bar buy-minus-sell volume, green/red by sign.
- CVD — cumulative volume delta as a line.
Timeframes
Every trading chart — a preview or backtest session, a run’s execution window and its replay, the journal trade chart and Session Replay — offers 5s, 15s, 30s, 1m, 5m, 15m, 30m and 1h candles. A full session opens at 1m; the first time you pick 5s, 15s or 30s the chart fetches that session’s 5-second bars (the toolbar says Loading 5 s bars… for a moment, and if they cannot be loaded the 1m chart stays, with the reason and a Retry button). Candles, the volume, delta and CVD panes, entries, exits, stops, targets, levels and markers line up at every timeframe exactly as they do at 1m. A run on the full exchange session charts the whole Globex session, 18:00 ET the evening before to 17:00 ET; a regular-hours run charts 09:30–16:00. A position held overnight shows its entry on the first day and its exit on the next, and dragging past either edge of the chart loads the neighbouring session. Runs stored before 16 October 2026 kept 1-minute bars only, so their 5s, 15s and 30s stay greyed out — re-run one to get them.
Your declared indicators, drawn
The engine charts draw the indicators the strategy actually declared, so the code and the picture agree at a glance. Overlays that belong on price sit on the price pane — VWAP variants, moving averages and their bands — while oscillators like RSI and MACD get their own panes below, and level studies (pivots, prior-day and overnight levels, the opening range) render as lines. This works the same for hand-written and AI-authored strategies: anything the builder or coding agent writes is fully backtestable and its declared indicators are drawn just like your own.
The chart fills the space it's given. Expand grows it to fill the pane and it reflows responsively as the window resizes, and Download saves the current chart as a self-contained file you can keep or share as-is.
The orders, fills & trades blotter
Below the chart, Orders, fills & trades lists the engine’s own rows for the selected session in three tabs: Trades (time, side, outcome — win / loss / open — and a detail line with the realized entry and exit, the stop and target, and the net), Orders (every order the venue worked, with its type, status, quantity and price) and Fills (each fill’s price, quantity, liquidity side and commission). Clicking a trade navigates the chart to it. A preview stored before 2026-09-23 keeps its chart but no longer renders its setups table.
The debug trace and reject reasons
Reject reasons are a backtest surface today: the backtest's own tables and charts name every setup the pattern fired that never traded, with the machine-readable reason below. A preview run since 2026-09-23 stores no per-setup debug trace and no reject list — it shows what the venue did (orders, fills, trades) and what did not fill is in its funnel; the trace returns with the new simulation lane. A preview stored before that date keeps the trace it stored.
An engine-native strategy (one class on the engine’s own strategy base) has no
per-setup trace, and its print output is not kept anywhere (its
self.log lines are kept since 2026-10-28 — its strategy log, below).
What it leaves for debugging besides is its drawings —
mark(self, "why", bar=bar, kind="reject") markers carrying a short note
(up to 120 characters), plot(...) lines and plot_level(...)
levels from platform_sdk.plot — drawn on the run's charts and in the
Replay and kept with the run. A session no trade touched has no chart, so its drawings
are only in the run's stored drawings file, which an agent can read.
Its debug trace (since 2026-10-26): with
from platform_sdk import trace, a call such as
trace(self, "skip short", bar=bar, side="short", es_high=es) from any
handler keeps one line — the bar's close (or the strategy clock's time), the
session, the note and the values. The run page's Watch panel lists
the lines of every session, traded or not, and an agent reads the same lines as the
run's trace.csv table. Each session keeps its first lines up to a limit
the platform sets (200 by default) and counts the rest: the run says how many lines
it did not keep, and on which day. Warm-up sessions and optimizer trials keep no
trace, and print output is still not kept.
Its strategy log (since 2026-10-28): the lines it writes with
self.log.info(...) (or .warning(...), .error(...)
— info and above) show in the same Watch panel, in a
“Strategy log” section beside the trace: the time, the level and the
message of each line, session by session, and an agent reads the same lines as the
run’s strategy_log.csv table. Only the strategy’s own lines
are shown — not the platform’s, not the engine’s start and stop
lines, not warm-up sessions. Each session shows its first lines up to a limit the
platform sets (200 by default) and counts the rest, and the run says how many it did
not keep, and on which day. A trace line carries values you can sort and filter; a
log line carries text.
Reject reasons you'll commonly see, and what they mean:
| Reason | Meaning |
|---|---|
no_profile | The level or profile the setup needed was never built, so no order could rest. |
no_rth_window | The level existed, but there was no valid regular-trading-hours window left for an order to rest in. |
no_fill_timeout | An order rested but expired without filling — price never came to it (or, in a backtest, never traded through it). |
rejected_geometry | The setup's price geometry was invalid — e.g. a stop and target arranged so the trade could never make sense. |
rejected_sizing | Your own risk rules refused the trade (stop too wide, size would exceed limits). |
Rejects are information, not noise
A strategy that detects 40 setups and fills 6 is telling you something important
about itself. If most rejects are no_fill_timeout, your entries may
be too passive for the pattern; if they're rejected_sizing, your
stops may be structurally too wide for your risk rules. Read the distribution,
not just the trades.
Backtest results at a glance
A finished backtest opens the results viewer with tabs: Overview, Equity (the run's stored results first — net P&L, max drawdown, Sharpe, trades, the recovery factor — a Daily / Weekly / Monthly / Yearly switch with the typical, best and worst period, then the equity curve in dollars with an optional buy-and-hold or flat-cash benchmark line, the underwater (drawdown) curve, the five deepest drawdowns and the bars of the chosen period, and the stored performance tearsheet last — the equity tab and its benchmark), Trades, Fills (what the simulated exchange did with every order, and for a built-in template each setup that did not become a normal trade; an old-form plugin run shows its idealized-versus-conservative diagnostics), Gates (the scorecard — covered on the next page), Regimes, Sensitivity, Stress, Walk-forward and Provenance (the exact data slice, code version and parameters behind every number).
Days and streaks, and the weekday-and-hour table
The Overview’s Summary — per symbol grid adds two blocks under its main rows, stored when the backtest is graded: Days (Day win %, Green days, Red days, Breakeven days, Flat days (no trades), Best day, Worst day, Avg green day, Avg red day, Expectancy per day ($)) and Streaks and trades (Longest green-day streak, Longest red-day streak, Day streak at the end, Most wins in a row, Most losses in a row, Payoff ratio, Avg hold time, Largest win, Largest loss).
A day is one exchange session, and a trade counts on the session it closed in. Day win % is green days divided by days with trades; flat days are shown on their own, and a breakeven or flat day breaks neither day streak. A trade that nets exactly $0 counts as a loss, as in the win rate (the journal keeps its own definitions). Below the grid, By weekday and hour lists the trades by the weekday of the session they were entered in (a Sunday-evening entry belongs to Monday) and the hour of the entry in New York time, with each bucket’s net and its winning and losing trades; the export menu’s CSV · weekday × hour downloads it, and the compare page shows both blocks side by side. A run graded before these existed shows a short note instead.