Backtests
Use the Test button (Running code) in the QuantCraft IDE to open Run backtest. There you set symbols, price data, optional fundamentals, the simulation window, starting balance and fill costs, and (if your script uses them) input parameters for a single run. The engine invokes your strategy lifecycle callbacks. Results appear in Test results; OHLCV explains bar shapes.
At the top of the modal body you can Load or Save to reuse a run preset (.qcs). Everything else is grouped into up to three tabs so you can work through the form before you start the backtest.
Run backtest modal tabs
| Tab | What you set |
|---|---|
| Script | Shown only when the script declares params.NAME inputs. Parameter values, plus Optimize (brute-force grid or genetic search). See Input parameters and Optimization. |
| Symbol & data | Price feed (connected broker feed or QuantCraft B2), Symbol source, Asset class, Bar timeframe (broker feed) / Timeframe (B2), Additional timeframes (optional) for rt.get_bar, Min bars (optional) (broker feed), Uses fundamentals (select if your strategy requires fundamental data), Include Fama-French factors (global), and Include calendar data (dividends, splits, earnings dates) (Premium / free-data override; on by default). |
| Simulation | Start / end date (default: the last month), warmup bars (default 45), starting balance (default 100,000), commission and slippage models. See Account model. |
Clock / primary symbol: There is no primary-symbol control in the modal. With Selected symbols, the bar clock uses a SYMBOL constant in your script (if that ticker is in the loaded list), otherwise the first loaded symbol. With Screener there is no clock symbol at all — the symbol set changes as the run goes, so read each ticker's history from its own bar.close[i] offsets. See Strategy lifecycle.
Timer / on_timer: The Run backtest modal does not set a timer interval. on_timer is for forward / bulk / chart-forward runs that expose a timer control — not IDE backtests.
Price source (broker feed vs. B2)
On the Symbol & data tab, choose the price feed:
- Connected broker feed — intraday and daily-style timeframes are available (requires a connected broker account with credentials configured in the app).
- QuantCraft B2 — on a free plan, only daily (
d), weekly (w), and monthly (m) OHLCV timeframes are available; Premium unlocks intraday and extended B2 timeframes. You must be signed in to QuantCraft so a session token can be sent for B2 OHLCV (and for fundamentals when enabled).
The choice is saved in run presets (.qcs) as ohlcvSource ("alpaca" or "b2").
Bar timeframe and asset class (broker feed only)
When the price source is the connected broker feed, you can set:
| Field | Purpose |
|---|---|
| Bar timeframe | Candle resolution for the run — e.g. 1 Day, 1 Hour, 4 Hour, 1 Week, and several minute bars (1 / 5 / 15 / 30 min). |
| Asset class | US equity or Crypto — drives which symbol list you search and how the broker fetches data. |
| Min bars (optional) | If set, after the date range is applied the engine keeps only the N most recent bars. Leave empty to use the full range implied by your start and end dates. |
When the price source is B2, you only choose Timeframe: Daily, Weekly, or Monthly on a free plan; Premium adds intraday and extended B2 timeframes (see the B2 codes in OHLCV and bar data).
Additional timeframes (optional)
Use Additional timeframes on Symbol & data to load extra bar sizes alongside your primary timeframe. In strategy code, read them with rt.get_bar(Timeframe.X, shift) — see OHLCV and bar data and Strategy lifecycle.
- Multi-select any extra timeframes your strategy needs (e.g. daily when your primary bar is 5-minute).
- The primary timeframe is also available via
rt.get_barwithout listing it under Additional timeframes. - Returns
Noneif that timeframe has no buffer for the run, on a Screener source, or when data is unavailable. - In a backtest
get_baronly ever sees bars up to the current step, and always reads the clock symbol's bars — for another ticker's history use that ticker'sbar.close[i]offsets. - Settings can be saved in run presets (.qcs) under
secondaryTimeframes.
Symbols
Symbol source
Symbol source on the Symbol & data tab decides which tickers the backtest trades:
| Source | What it does |
|---|---|
| Selected symbols | Trades the fixed list you pick, plus anything you add with Paste tickers. |
| Screener | Re-runs a screen as the backtest goes and trades whatever matched at that point in the simulation. |
With Screener selected, you configure the screen itself:
| Setting | What it does |
|---|---|
| Screener source | Screener preset or Python script. |
| Sort column / Direction | How preset matches are ranked before the limit is applied. |
| Rank direction | For scripts — which end of your rank() score to take first. |
| Limit | How many symbols to trade per rebalance. 0 takes every match. |
| Refresh interval (days) | How often the screen re-runs, in simulated calendar days. Scripts require 7 days or more. |
| Keep tickers with an open position | Keeps a symbol tradable after it stops matching, so an open position can still be managed. |
Limits for a screener-driven backtest: it needs explicit start and end dates; additional timeframes are not supported; a Python script screen needs a Refresh interval of 7 days or more; and Debug Test doesn't support a Screener source (it stops with a message asking you to switch to Selected symbols). With fundamentals on, every screened symbol gets its fundamentals automatically. Results include a Screener Output tab beside Logs, showing every rebalance as a collapsible date with the symbols traded for that window. See Screener for building screens and for point-in-time and survivorship details.
Selected symbols
On Symbol & data, choose one or more tickers for the run:
- With the connected broker feed, the list comes from your broker's asset universe (equity vs. crypto follows Asset class). You can search and multi-select.
- With B2, the list comes from the same catalog as fundamentals paths on the server.
Rules:
- At least one symbol is required.
- Symbols are normalized to uppercase for the run.
- Valid characters: letters, digits,
.and-(typical for exchange suffixes likeBRK.BorRHM.XETRA).
The first symbol in your selection order is the default clock symbol unless your strategy sets a top-level SYMBOL = "..." that matches a loaded ticker. The modal has no separate primary-symbol control.
Fundamentals (optional)
On Symbol & data, check Uses fundamentals (select if your strategy requires fundamental data) when your strategy reads the fundamentals argument in on_bar / on_tick (backtest), or in on_timer on a forward run, or uses fundamentals-aware logic.
The checkbox is shown when fundamentals features are turned on for your build, but is disabled (with a Premium hint) unless you have Premium or free-data access is enabled. When fundamentals are on:
- You must be signed in (QuantCraft token is sent to load B2 fundamentals files).
- Pick one or more fundamentals bundles from the multi-select (paths like
fundamentals/AAPL.json.gz). The dialog shows the normalized path under each selection.
Important: OHLCV symbols and fundamentals symbols are independent. You can backtest RNMBY via the broker feed while loading RHM.XETRA fundamentals — the UI reminds you that in that case you must pass the correct ticker into fundamentals.current(..., symbol="RHM.XETRA") (or equivalent) in code.
When multiple fundamentals bundles are selected, the engine builds a per-path map. The primary fundamentals bundle used for bar-aligned snapshots defaults to the traded symbol (or the alphabetically first bundle); in multi-symbol and Screener runs always pass symbol= on fundamentals.current / get_data_with_offset, since the primary may not be the ticker you're on.
Fama-French factors (optional)
On Symbol & data, tick Include Fama-French factors (global) when your strategy reads global monthly factor returns via fama_french.qc. See Fama-French factors for the full API.
When factors are on:
- You need Premium (or free-data access when enabled) and must be signed in.
- The checkbox is on by default when the data gate allows it. Uncheck to skip factor data for that run.
- OHLCV symbols, fundamentals bundles, and Fama-French factors are independent — enable any combination you need.
- There is no
factors=callback argument — importquantcraft.fama_frenchand readfama_french.qcinside callbacks (on_init,on_bar,on_tick, …).
Calendar data and corporate actions (optional)
On Symbol & data, Include calendar data adds dividend history, split history, earnings dates, and SEC filings to your backtest. It is on by default, and your choice is remembered for the next run.
Why this matters
Backtest price bars are unadjusted. Without calendar data, a 4-for-1 split appears as a 75 % overnight price collapse and your simulated position collapses with it. Dividends also never arrive, which quietly understates the total return of any long-held position.
Sub-options
Two sub-options control how actions are applied to open positions during the simulation:
| Sub-option | What it does |
|---|---|
| Adjust positions for stock splits | On the split's effective date, share count is multiplied by the ratio and cost basis divided by it. Stop and target prices are rescaled too. Because qty × entry_price is unchanged, your equity curve stays continuous across the split. |
| Credit dividends to cash | On the ex-date, qty × amount is added to cash for longs and subtracted for shorts (shorts owe the dividend to the lender). This flows into equity and every return metric but not into realized_pnl. |
Both sub-options are on by default when calendar data is enabled.
Note on ex-date estimation: EDGAR does not publish ex-dividend dates. Unless exchange data was available, the ex-date is estimated from the declaration date (accurate to about ±7 days) and is marked
est.in Test Results. Estimated dividends are credited by default; to skip them, setdividendDatePolicytoexact_only(oroff) in a .qcs preset — the modal has no control for it. Filter ond.estimatedin code if your strategy is sensitive to the exact date.
Screener universes
Calendar data works with a screener symbol source. Calendars load once the screen has resolved the full ticker list; symbols matched but with no price bars cost no calendar request. A wide screen may include symbols with no calendar on the server — the run log reports the count rather than failing.
Where you see it
- Output panel — one
[calendar]line per corporate action applied during the run, plus a summary line at the end. - Test Results — Split and Dividend rows appear inline in the closed-trades table (then titled Closed trades & events), showing the ratio or per-share amount and cash impact.
See Calendar data for the full BacktestCalendar API and the calendar callback argument.
Simulation — dates, balance, and fill costs
All of the following live on the Simulation tab:
| Field | Purpose |
|---|---|
| Start date | Required (defaults to about a month ago). First UTC calendar day included in the execution window (YYYY-MM-DD). |
| End date | Required. Last UTC calendar day included. Must be on or after the start date. |
| Warmup bars (indicators) | Extra bars loaded before the start date (same timeframe as your bars) so moving averages and similar indicators have history. Callbacks still run only between start and end dates. Default is 45; enter 0 to disable extra warmup. |
| Starting balance | Required. Initial cash for the simulated PaperAccount (default 100,000). |
| Commission / slippage | How fills are adjusted for trading costs — see below and Account model. |
The Run backtest modal does not configure a timer interval.
on_timeris only available on forward runs (chart forward test, bulk forward / QuantCloud) whentimerIntervalMsis set. See Strategy lifecycle.
Optional default stop-loss / take-profit / risk sizing can still appear in .qcs presets when present, but the Run backtest modal does not currently expose SL/TP / risk inputs. Configure those in code or via presets if your helpers support them — see Account model and Run presets (.qcs).
Commission and slippage
| Setting | Purpose |
|---|---|
| Commission model | Per share (default), per trade, or zero |
| Commission cost | Cost per share or flat fee per order ($) — default $0.001 per share |
| Minimum trade cost | Floor per order when using per-share commission ($) — default $0 |
| Slippage model | Volume share (default), fixed spread, or zero |
| Volume limit / Price impact | Volume-share slippage parameters (volume_limit also shrinks the quantity on opens) |
| Spread | Total spread per share for fixed-spread slippage ($) |
Simulation settings can be saved in run presets (.qcs).
Script parameters (single run)
If your strategy imports from quantcraft.inputs import params (the older quantcraft.inputs_module / ide.inputs_module paths are still matched for existing scripts) and references params.NAME, the dialog shows a Script tab with one row per discovered name: Parameter, Type (int / float / str / bool), and Value.
Fill every row before you start — values are required for each declared parameter. They are sent on the wire as strings and coerced by the engine before your file loads, so params.PERIOD (for example) is already the correct Python type when your module executes.
On the same tab you can enable Optimize for grid or genetic search — see Optimization. Leave Optimize unchecked for a normal single backtest.
What happens when you start the run
The app validates the form (dates, symbols, balance, fundamentals if enabled, and script parameter types). On success it starts one backtest with the assembled configuration; progress and logs appear in the Output panel, and structured results open in Test Results.
Quick reference
| Topic | Where it’s set |
|---|---|
| Broker feed vs. B2 prices | Symbol & data → price feed (ohlcvSource in .qcs). |
| Timeframe | Bar timeframe (broker feed) or Timeframe (B2: d / w / m on a free plan; more with Premium). |
| Additional timeframes | Additional timeframes multi-select — for rt.get_bar(Timeframe.X, shift). |
| Asset class | US equity / Crypto (broker feed only). |
| Truncate to last N bars | Min bars (broker feed only, optional). |
| What to trade | Symbol source: Selected symbols (multi-select) or Screener (fundamentals-driven screen — see Screener). |
| Clock symbol | Script SYMBOL if loaded; else first symbol (no modal control); none on a Screener source. |
| Financial statements | Uses fundamentals + fundamentals multi-select (automatic on a Screener source); requires sign-in. |
| Global factor returns | Include Fama-French factors (global) — see Fama-French factors. |
| Dividends, splits, earnings dates | Include calendar data — on by default; see Calendar data. |
| Calendar window | Start date / End date on Simulation (required). |
| Indicator history before start | Warmup bars on Simulation. |
| Starting cash | Starting balance on Simulation. |
| Commission / slippage | Simulation tab — see Account model. |
on_timer | Not in Run backtest — forward runs with timerIntervalMs (Strategy lifecycle). |
on_tick | Backtest: four synthetic OHLC ticks per bar (no real tick history). Forward runs: every broker price update — see Strategy lifecycle. |
| Tunables in code | Script tab (params.*), single values per run (or Optimize). |
Tips
- If broker feed symbols never load, open Trade once or confirm your broker connection; for B2, confirm you’re signed in and the symbol catalog loaded.
- Warmup only extends loaded history — it does not add trading days outside your date range.
- Do not expect
on_timerin the IDE backtest — there is no timer control on that modal. Useon_bar/on_tickinstead. Timers apply on forward runs whentimerIntervalMsis configured. on_tickin backtest uses synthetic OHLC from each bar (four calls per bar) — not real tick data. Test live tick logic on forward runs (Strategy lifecycle).- Match fundamentals bundles to the tickers you actually read in code when OHLCV and fundamentals tickers differ.
- Use Min bars sparingly — it is easy to accidentally shorten a run more than you intended.
