A backtest in Research is a recipe: a complete, declarative description of a strategy. The recipe says
what to open, how many, when, only if what holds, when to get out, what to do while the position
is open and at what cost; the engine applies it to every day of the period and
books the result.
The Backtest page has two full-screen views. Compose is the recipe; Results is what the last
run produced (see Results & archive). A third view, Study, explores
parameters (see Parameter study). The switch at the top moves between them.
The compose view
On the left, the Plan reads the recipe one step per line, in the order of a trade's story:
Structure: the legs, under the name the Anatomy gives their shape (Iron Condor · 4 legs).
With: capital, contracts, margin, open positions.
When: period, frequency, entry time and re-entry.
Only if: the entry conditions (VIX, gap, premium, indicators…). Every session when there are none.
Exit: target, stop and every exit rule.
Manage: what happens while the position is open: profit actions, timed actions, leg groups, Tactics.
Costs: commissions, slippage and execution rules.
On the right, the Structure card holds the Anatomy and the leg rows. On a phone and on a tablet
the Structure card comes first, since the legs are the first thing you set, and the plan follows it.
Values are tokens
Every value in the plan is a token with a dotted underline. Click it and its settings open next to the
plan (on a phone, as a dialog): the same fields described below, one step at a time. The step whose
settings are open is highlighted. Esc or a click outside closes them; clicking Structure takes you
to the legs.
Numbers you change often (capital, contracts, target, stop, a single entry time) also change without
opening anything: drag the token sideways, or focus it and use the arrow keys (Shift for a bigger
step).
Adding and removing
Only what the recipe contains takes space in the plan. + Add a condition (and + Add timing,
+ Add an exit rule, + Add management) opens a list with a search box:
the options you added most recently and most often come first, remembered in this browser;
the search reaches every option of the backtest, including the settings of the other steps, by the
start of any word of its name or description;
Show all opens a window with every option, step by step, each with a one-line description and
whether it is already in the plan;
/, pressed outside a field, opens Add a condition.
What you add opens at once, ready to fill. Remove at the bottom of its settings takes it out of the
recipe; its values are kept, so adding it back brings them back.
Status and running
A dot next to a step number appears only when there is something to say:
Red
A value the run can't accept: an entry time outside 09:32–15:59, or specific dates none of which can be read. Blocks the run.
Amber
Something required is missing (no legs, no entry time, an empty list of specific dates). Blocks the run.
Hollow
Something is in the plan without values, so it would have no effect on the run (a filter with no bounds, leg groups with fewer than two groups in use). It does not block; its token reads no values and the line under the step counts them.
The line under a step says what is wrong; clicking it opens the settings that fix it. A minimum above
its maximum (VIX, gap, premium, RSI…) doesn't color the step: a note under the fields says so. Some of
these the server refuses when you run (RSI, VIX9D ÷ VIX, the floating window), with the reason next to
the run button; the others simply let no entry through.
At the bottom of the plan (on a phone, a sticky bar) is Run backtest, also on ⌘↵ /
Ctrl+Enter. Above it, what is still missing, as a link to the field. It runs even when the recipe is
identical to the run on screen: you get a new entry in the run history with the same numbers.
Three more controls live here:
Back to results returns to the last run without running; your edits are kept.
Reset parameters puts every field back to its starting value, after confirmation. It does not
touch the run on screen or the run history.
Draft. Edits you haven't run are saved in the browser. When you come back, a banner says that
unrun changes were restored, with Discard changes to drop them.
Starting points
The recipe starts empty: no template is imposed. Only the test bench assumptions are pre-filled
($100,000 capital, 1 contract, $2 commissions, per-position slippage of $0.10). To start faster:
the Structure button in the Structure card offers 46 templates in nine families (single leg,
verticals, volatility, iron, butterflies, condors, ratio, ladders, lizards and combos), one family at
a time on tabs, each with the silhouette of its payoff, and a search across all families. A template only fills the legs; every delta, distance and quantity stays editable. If legs
are already there, you're asked before they're replaced;
Ask Veeman composes the recipe from a sentence such as "inverse iron condor 20 delta,
20-point wings, 200% stop, VIX overnight up 0%". It fills the plan and never runs it: every field
it set is listed with the words it came from and the step it landed in, and a click opens that field. See Veeman AI.
The Research bar of the Overview does the same: describe an idea there and the composer opens
with it;
Trade log, next to Compose (or a CSV dropped on the bar), rebuilds the whole recipe from a
backtest's trade log and checks it on the engine: see Rebuilding from a trade log;
New backtest in the results view reopens the recipe with the parameters of the run on screen,
so the usual cycle is: change one thing, run, compare;
Define can send a structure here (it starts from the same test bench
assumptions as any new recipe), and a strategy in the Analysis library that came from a backtest
can be reopened with Reopen in Research, which runs its recipe again straight away.
Every run, saved backtest or handed-over recipe reopens in the composer field by field. If a recipe
contains something the composer has no control for (a leg with an absolute strike, for example), the
leg reopens with a default criterion (delta 10) and a notice above the recipe tells you, rather than
silently showing a structure different from the one that produced the numbers.
Rebuilding from a trade log
A strategy built on another backtester, or a Veeman backtest whose recipe you no longer have, can be
rebuilt from its trade log. Drop the CSV on the Veeman AI bar (or pick it with Trade log):
VEEMAN reads every trade, deduces the recipe that produced them, runs that recipe on the engine and
compares the two, trade by trade. The recipe lands in the plan as a new strategy, named after the
file's strategy; nothing runs on its own, and Undo puts the form back.
The deduction is done by the program, not by a language model: each field comes from a measured fact
of the file, and the report under the bar shows it next to the value ("picks the file's strike on 262
of 270 trades"). When an AI provider is configured, Veeman AI
adds a short summary on top.
What it reads
The standard trade log layout, the one VEEMAN exports from a backtest and imports in Analysis: one row
per trade, with the legs of each trade in a Legs column (see
Import formats). One strategy at a time: a file
with several names in its Strategy column asks which one to rebuild. At least 10 readable trades are
needed, on SPX options expiring the same day (0DTE). The file is read in memory and not stored.
How each field is deduced
Structure
The legs that most trades share (side, type and ratio, by strike). Trades with different legs are listed and left out of the deduction, but they still count in the check.
Strikes
For every trade, the option chain at its entry minute, read as the engine reads it. For each leg and each criterion (delta, premium, % OTM, points from the underlying, offset from another leg) VEEMAN works out which values would make the engine pick the file's strike, with the engine's own rules for ties and for legs without a bid. The criterion and value that explain the most trades win; at equal coverage the rounder value wins (delta in whole points, premium on the nickel), and the report gives the range of values that pick the same strikes.
Entry
One entry time means a fixed time. Different times on different days mean a floating window from the first time seen; the file only says the window ends at the last entry or later, so the end is set on the next round minute and checked on the engine. Several entries on the same day mean extra fixed times, or a re-entry after an exit.
Premium
With a floating window, a minimum shows up as a cluster of trades opened at exactly that premium and none below: the window was waiting for it.
Exits
Each position of the file is marked minute by minute with the engine's marking. A rule's level must be reached at the minute the file exits for that reason, and not before on any trade that closed otherwise: the level that satisfies the most trades is the one set. This covers position delta, the stop on the structure or per leg (in % of the premium or in $), the profit target (with two hits), the underlying move, the VIX move, the exit time and minutes in trade. Exits marked Economic event give the close before economic events: the minutes from the exit to that day's release, and the releases they come before. The stop slippage comes from the file's closing cost against the mark at the exit minute.
Costs and size
Commissions per contract per leg, the position slippage (combo price against the sum of its legs), contracts, and the starting capital from the account balance after each trade minus the P/L so far.
Filters
A trade log only shows the days a strategy traded. VEEMAN runs the recipe without filters to see where it would have entered, then looks for the conditions that separate those days from the file's: gap, overnight and intraday VIX moves, intraday move, VIX level, VIX9D/VIX, RSI, price against a moving average, weekdays, economic events and, with a fixed time, the premium. They're read with the same values and the same directional rules as the engine's filters. An economic event rule (Skip CPI, Only FOMC, on the event's session or the one before or after) is stricter: the release dates are known, there's no threshold to estimate, so it's proposed only if the file trades at most one of the sessions it would rule out, only for events whose dates are known over the whole period of the file, and only if chance can't explain it: with about a hundred event rules tried, a file that trades half of the days skips every quarter end of two years now and then by luck, so an exact test has to put the pattern below 1% for the whole search. Rules found one by one are written as a trader would: Skip FOMC, CPI (event day). The days the file trades and the days to rule out weigh half each, so a filter that drops almost every day never wins; a filter has to explain enough days to earn its place, together they must keep most of the days the file trades (a real filter keeps the strategy's own trades), at most four are kept, and dates are never blacklisted to cover days nothing explains.
The check on the engine
The rebuilt recipe runs over the file's period, and each trade is compared with the file's on the same
day. A trade is identical when the entry minute, the strikes (with side and ratio) and the exit
match, and the P/L is within $1. Otherwise the report counts it under its first difference (entry
minute, strikes, exit, P/L), plus the days only in the file and only on the engine; Show the
days lists them, file against engine. Where a value is ambiguous (the end of the window, a threshold
the other platform reads slightly differently), the plausible alternatives are run too, and the recipe
that reproduces the most trades is the one you get. A threshold that fits another platform's data
(a VIX move of 1.13%, a stop of $19) is also tried at the round value a trader would type (1%,
$20), and kept when it reproduces as many trades. Trades after the end of the dataset aren't checked,
and the report says how many.
Logs from another backtester
A log from another platform won't match 100% even with the right recipe: that platform computes deltas
and premiums with its own model and data, opens the VIX at a slightly different moment, and may fill on
quotes VEEMAN treats as unexecutable. Those differences show up as different strikes at the same
minute and entry minute differences around a threshold, while the recipe itself comes back whole.
On a log exported from VEEMAN the rebuilt recipe reproduces every trade.
What it doesn't rebuild yet
Trailing stops, partial closes, custom signals, technical-indicator and ratio exits, sizing that
follows the account, positions open at the same time, multi-day trades and anything the engine can't
run. The report says which trades closed by a rule it didn't rebuild, and those exits will differ.
Structure
Each leg is a row: Sell / Buy, Call / Put, quantity, strike criterion and its value. Rows can
be duplicated or removed. A structure needs at least one leg.
Strike criteria
The engine chooses each leg's strike every day, at the entry minute, on the chain quoted at that
minute: the strike closest to the criterion. If the exact strike isn't quoted that minute, the nearest
quoted one is used (on a tie, the lower), so a wing can come out a few points wider or narrower. A leg
you sell is chosen only among strikes with a bid, because without a buyer the sale can't be executed;
a leg you buy, among strikes with an offer. The criterion is the rule it applies:
Delta
The quoted strike whose absolute delta is closest to the target. Typed in points: 10 means a delta of 0.10.
Percentage (OTM)
The strike closest to spot × (1 ± %), always out of the money: below spot for puts, above for calls.
Fixed Premium
The strike whose mid price is closest to the target premium, typed in $ like a broker quote ($1.20, worth $120 per SPX contract: see premium in dollars). On a tie, the strike nearest the money: cheap far strikes often share the same distance ($0.10/$0.15 and $0.05/$0.10 are both $0.025 from $0.10), and at the same price the nearest one protects the most.
Strike Offset
A signed distance in points from a reference: another leg (strike = that leg's strike + offset, the usual way to place wings) or the underlying (strike = spot + offset). The sign gives the direction: −25 is 25 points below. An offset from spot may be in the money; that's a legitimate choice.
A leg used as a reference must have its own criterion (delta, % OTM or premium): an offset leg can't
point to another offset leg. If no leg qualifies, the reference list says so.
Each leg also has an expiry selector (days to expiration; today only 0DTE) and an optional
leg group (A–F) used by leg groups.
Round strikes (with its Multiple, default 25) restricts every leg to strikes that are multiples
of that number (with 25: xx00, xx25, xx50, xx75). Those strikes are usually the most liquid, but the
chosen strike can drift from the leg's criterion.
Anatomy
Next to the legs, the Anatomy panel draws the structure as you build it: a schematic expiry
payoff in points from spot, computed with Black-Scholes at constant volatility (no skew). It reports
credit or debit, the kind of risk (defined, open to the upside, to the downside, on both
sides) and the wing widths, which are exact. Each strike is a token: drag it (or focus it and use
← →) to move the strike, press Enter to jump to its row.
A shape, not a price
The Anatomy shows the shape of the structure, not its numbers: the engine picks the real strikes
every day from the quoted chain. Changing a leg's criterion (say, from delta to offset) never moves
its strike on the schematic: it only changes the rule the engine will apply.
With
The sizing of the test bench: how much capital, how many contracts, how many positions at once.
Starting capital ($)
The account the equity curve starts from. It drives the equity-based statistics (CAGR, drawdown %, Sharpe) and, with Margin %, the number of contracts. Default $100,000.
Max contracts
Contracts per position. With fixed sizing it is the size of every trade; with Margin % it is the cap.
Margin %
Empty = fixed contracts. With a percentage, contracts = ⌊ starting capital × % ÷ margin of the trade ⌋, capped at Max contracts. The capital is static (no compounding). If the budget doesn't cover one contract, the entry is skipped.
Ignore margin requirements
Bypasses margin sizing and uses the fixed Max contracts.
Max positions
How many positions may be open at the same time (relevant with several entry times or re-entries). Empty = no limit. At the limit the new entry is skipped.
Close the oldest trades at the limit
At the limit, closes the oldest open position at the new entry's minute instead of skipping the new one, but only if the new entry actually opens (one refused by its filters leaves the open position alone). A position closed this way doesn't re-enter.
The margin of a trade is its maximum loss at expiration, per contract: the worst P&L of the
payoff over every strike and beyond both ends. It exists only for defined-risk structures. If the
structure sells more calls than it buys, the loss has no upper limit. If it sells more puts than it buys,
the loss is limited (the index can't fall below zero), but a broker margins uncovered puts with its own
rules, which the backtest doesn't model. In both cases margin sizing doesn't apply: the run uses the fixed
contracts and says so in the warnings.
When
Period
Start date and End date, or a preset: YTD, 30D, 90D, 1Y, 2Y, SPX daily
(from 2022-05-16, when SPX started listing an expiration every weekday). Empty fields mean the full
history of the dataset. A date outside the data, or two swapped dates, is pulled back to the nearest
valid bound and the field is rewritten.
Entry time and days
Entry time (ET)
The minute the position opens. It must be between 09:32 and 15:59 ET.
+ Add time
Additional entry times. Each time opens its own position on the qualifying days: three times = up to three positions a day.
Floating time
Replaces the fixed time with a window (Entry from / Entry until). The engine enters at the first minute of the window where every time-dependent filter passes and a position can be built; if no minute qualifies, there is no trade that day. Both times are needed: with only one, the window is ignored and the run uses the fixed entry time (a note under the fields says so).
Frequency
Daily (Mon–Fri); Weekly with the weekdays to trade (no day selected = every day); Monthly with the occurrence and the weekday (for example, the third Friday); Specific dates as a comma-separated ISO list. Dates that are invalid or outside the data are listed as ignored; with no valid date the backtest can't run (an empty list would otherwise mean every day).
Custom signals are paused
The Signals module is paused, so the recipe doesn't offer signal sets as entry source. A backtest saved
earlier with a custom signal set, as entry source or as a signal exit, keeps it: reopened, it still
shows the set and runs exactly as before.
Indicator signal
Added from + Add timing, by switching the entry source of Entry time to Indicator signal, or
with Trade every signal when a custom indicator in Only if already decides the entry. Instead of
a clock time, one of your custom indicators opens the positions. The signal is
a row like the ones in Only if:
An event that fires
On an event output (a plotshape, plotchar or alertcondition), fires: each bar where it fires is a signal, at the minute that bar closes (a 5-minute bar of 10:10 closes, and signals, at 10:15).
A condition
On any output: ≥, ≤ a value or another output, crosses, or is on / turns on for a 1/0 output, read the same way as in Only if. The signal is the minute the condition starts to hold: two hours above 70 are one signal, not 120. A condition that already holds when the session (or the signal window) opens is a signal at that first minute. A crossing holds only at its minute.
Then how it trades:
First signal of the day
One position a day, at the first minute the signal and the Only if filters agree.
Every signal
Each new signal opens a position. Three settings shape it:
At most, a day
At most this many positions a day, re-entries included. Empty: no limit.
With a position open
A signal that comes while a position is open: Skip it (one position at a time, the default), Open another up to Max open positions at the same time, or Close the oldest to make room (the new one is tried first: if its filters reject it, the oldest stays). It's the same limit as Max open positions in With, where the hint says so.
Re-enter while the signal holds
For a condition that lasts (≥, ≤, is on, is off). After an exit, the position opens again from the next minute if the signal still holds, instead of waiting for a new signal. After an exit on a condition (a custom indicator exit, an indicator exit) it waits until that condition no longer holds, as the re-entry does. It never reopens at or after the close, or past the time of an Early exit: the new position would close a minute later. Off by default: after a stop or a target, the next position waits for a new signal.
Signals from / Until
Optional: only the signals inside these times are traded. Empty: the whole session, until 13:00 on half-days. Whole session clears both.
Exit when the signal stops holding
For a condition that lasts: adds the opposite rule to the custom indicator exit in one click (is on becomes is off, ≥ 70 becomes ≤ 70: at exactly 70 both hold). Once it's there, the window says the position exits there.
Every signal opens at most one position (more only with Re-enter while the signal holds). If the
Only if filters reject a lasting signal at its first minute, the entry is tried again at each next
minute while it holds, like a floating window; a crossing or an event is lost if its minute is rejected.
The Only if filters apply at the minute of each attempt. An opening range breakout
filter rejects the signals that come before the range ends.
It doesn't combine with the floating time window or the automatic re-entry: the signal has its own window
and its own re-entry.
Signals at or before 9:30, or at or after the close, aren't traded. A daily bar closes after the
session, so to act the next day on a daily indicator, use a ≥ or ≤ condition: all day long it
reads yesterday's bar.
A day without signals is counted in the funnel as no indicator signal that day; skipped signals are
counted in the warnings.
Examples: every time RSI crosses below 10; every time Pullback fires, at most 3 a day; in while
SPX is above its 200-day average, out when it goes below: a lasting signal (Below the average is off),
Exit when the signal stops holding, and Re-enter while the signal holds if a stop shouldn't end
the day.
Re-entry
Added from + Add timing.
Re-enter after exit
Reopens the same structure the same day after an exit: on which exits (Profit target, Stop loss, which includes the per-leg stop, Time exit; none selected = any), delay in minutes, don't re-enter if exited before / after a time, max re-entries per day. Time exit is an exit after Minutes in trade: after an Early exit at a clock time there is never a re-entry, since the new position would close a minute later. After an exit on a market condition (indicator, custom indicator, short/long or VIX9D/VIX ratio, delta, tested short) the re-entry waits until that condition no longer holds, read at the minute of each attempt with what is known then, never the minute after. So it doesn't reopen a position every few minutes, with or without a cooldown on that rule.
Only if
The conditions a session must meet for the entry to go through, added from + Add a condition.
Each condition has its own fields. A condition in the plan without values is inert: its token reads
no values and it has no effect. Filters that read the day (VIX, gap) decide whether the day is tradable;
filters that read the minute (intraday move, VIX at entry, ORB, indicators, premium, ratio) decide
whether a given entry attempt goes through. None of them looks at data after the decision.
VIX filter
Three blocks. Level at entry: Min / Max VIX at the entry minute. Overnight move: VIX at the open versus the previous close, up or down. Intraday move: VIX at the entry minute versus the open, up or down, plus Min / Max VIX9D ÷ VIX at the entry minute. Moves in % or VIX points. The open is the session's first VIX print: on the days it comes after your first entry minute (about one in seven at 09:32), the two moves wait for it, so a window enters a minute or two later and an entry at a fixed time before it doesn't open.
Opening gap filter
The index open versus the previous close: Min / Max gap up and down, in % or points.
Intraday move
The index at the entry minute versus the day's open: Min / Max move up and down, in % or points.
Opening range breakout
Opening range end (ET), Breakout condition (High or Low, High only, Low only, No breakout) and Use intraday extremes. The range must end before the first entry time.
Min/max entry premium
The net premium of one structure at entry, in $, as a broker quotes the combo and as the trade log shows it, plus its direction (credit or debit). Bounds are magnitudes. Max $16, Debit means: if the structure costs more than $16.00 to open ($1,600 per SPX contract), don't enter. The bound applies to one structure whatever the number of contracts, and is compared with the entry fill (mids plus slippage, see fills), commissions excluded. Under the fields the bounds are read back per contract, with the underlying and its multiplier: Per SPX contract (×100): up to $1,600.
Technical indicators
Min / Max RSI (14), Entry SMA (price above or below an N-day average) and Entry EMA (price above or below an N-minute average).
Custom indicator
Conditions on the outputs of your own indicators, written in Pine Script. See Custom indicators below.
Economic events
Skip the sessions of scheduled economic releases, trade only on them, or pause entries just around the release time. See Economic events below.
Blackout days
Dates (ISO, comma-separated) on which no new initial entry is opened. Useful to exclude days with anomalous data.
Entry short/long ratio
Premium of the short legs ÷ premium of the long legs at mid, at entry: Min / Max. Without long legs the ratio is infinite.
The exact semantics of each filter (how directional bounds combine, which VIX value is read when, how
the indicators are computed) are in How the engine trades.
Custom indicators
Your own indicators, saved in Indicators, can decide entries and exits.
Add Custom indicator from + Add a condition (in Only if) or + Add an exit rule (in Exit),
then Add an indicator from your library. Use in backtest in Indicators does the same in one
click: it opens the composer with the indicator already in Only if. The library lists all your
indicators; one that doesn't compile, or has no output that can reach a backtest, is disabled with the
reason. A backtest reads up to 4 indicators, with up to 4 conditions in Only if and in each exit rule,
and up to 4 exit rules.
Conditions. Each condition compares an output with a value or with another output (of the same
indicator or of another one):
≥, ≤
The state of the indicator's last closed bar. RSI ≤ 30 holds for as long as the last closed bar is at or below 30.
crosses above, crosses below, crosses
The minute the bar that crosses closes: RSI crosses above 70 holds only at that minute.
is on, is off, turns on, turns off
For outputs that are 1 or 0: an event (plotshape, plotchar), a background band (bgcolor), an alert (alertcondition). Is on = the last closed bar has it; turns on = the minute the first bar with it closes. An event drawn with location.absolute (whose value is the price where it's drawn) reads the same way: on where it's drawn, off elsewhere.
A daily bar closes after the session (16:01), so a crossing between a daily indicator and a number, or
another daily indicator, can't happen during the trading day: the plan doesn't offer it and refuses it
if it's there. Use ≥ or ≤ to read yesterday's bar, or compare the daily value with an intraday output
(the 5-minute close crosses above the daily average is fine: one side moves during the day).
In Only if every condition must hold (and). With a floating window, the position opens at the
first minute the conditions hold: that is how to enter the first time the indicator says so. In
Exit, conditions form rules: a rule holds when all its conditions hold, and the position exits at
the first minute any rule holds (or).
The entry window. When a custom indicator enters Only if and Entry time has no time yet, the
composer turns on the floating window for the whole session, 09:32 to 15:59, and says so under the
fields. The indicator decides the minute; narrow the window only if you need one. On half-days the
engine opens nothing after the 13:00 close, so the same window ends there. With an
opening range breakout filter the window starts the minute after the range ends: before
then the range isn't known yet. What you already set stays:
a fixed time (or extra times): the conditions are read at that minute only;
a window: it stays as it is; with only one end set, the other end is filled with the edge of the session.
This trades the first signal of the day. To open a position at every signal, Trade every signal
(in the Entry time window, or next to the note in Custom indicator) turns the first condition into
the indicator signal of When; the other conditions stay in Only if as filters,
and a window you chose becomes the signal window.
While the window is still the one the composer set, it follows the opening range end if you change it.
If you remove the custom indicator from Only if before touching the window, the window is cleared and
the plan asks for an entry time again: it was set for the indicator, and without it, with no other
condition that reads the minute, it would open every day at 09:32. Once you change the window, it's
yours and stays.
What a condition reads. At the decision minute every condition reads the indicator's last closed
bar: a bar's value is known from the minute it closes, and never before. A daily indicator reads
yesterday's bar all day long; a 5-minute indicator at 10:31 reads the bar that opened at 10:25 and
closed at 10:30. A value the script leaves empty (na) makes the condition false. Display-only outputs
(they look ahead, depend on where the data ends, or read a skipped drawing) are never offered, see
Outputs.
Chart and inputs. Under the conditions, each indicator shows its chart and its inputs. The chart
starts as the one you saved it with, and you can change its symbol and timeframe for this backtest (from
1 minute to daily: on weekly, monthly or multi-day charts a backtest couldn't tell which sessions the
indicator covers). Numbers, option lists and on/off inputs can be changed for this backtest without
touching the saved indicator, numbers within the range the script declares (minval, maxval);
sources, texts, times and colors stay as in the script.
Warm-up and data. Veeman computes the indicator once on the whole history of its data, then reads
it. Until its warm-up is over (the start of the data), entries wait and the funnel counts them as
custom indicator still warming up. A session the indicator's data doesn't cover isn't traded (funnel:
no indicator data that day): that also counts the data of every symbol the script reads through
request.security(), so a VIX that stops a day before SPX never gives yesterday's VIX for today. The
warnings give how many days, where the data starts or ends, and up to three of them. If the script stops
with a runtime error, the warnings say on which bar, even when it's before the backtest period: from
that bar on its outputs are empty, and its conditions never hold.
Versions. The backtest runs the version you picked. If the indicator is saved again, the plan
marks it and offers Use v…: running with the old pick is refused, so another script never runs in
its place. Each run embeds the script, its inputs, its chart and the compiler version: a saved backtest,
reopened or brought up to date later, runs the same script even if the indicator changes or is deleted
(the composer shows it as from the run, and offers the library's newer version when there is one).
Economic events
Added from + Add a condition, in the Calendar group next to Blackout. A rule says what to do on
the sessions of chosen releases:
Skip / Only
Skip: no new entry on the sessions the rule hits. Only: entries only on those sessions. Several Only rules add up (a session passes if any of them hits it); a Skip rule removes sessions even from an Only set.
Events
Grouped as Fed (FOMC decision, FOMC minutes), Inflation (CPI, PPI, PCE), Jobs (jobs report, JOLTS, jobless claims), Growth (GDP, retail sales, ISM manufacturing, ISM services) and Market (monthly opex, quad witching, VIX expiration, month end, quarter end, half day). Hovering an event shows its usual release time (ET) and its next date.
Sessions
Day before, Event day, Day after, one or more. They count trading sessions, not calendar days: the day before a Monday release is the previous Friday.
Around the release
With Skip, instead of whole sessions: no entries from N minutes before to N minutes after the release time, on the event's day, both ends included (0 to 390 minutes each, not both zero). Only releases have a time: market events (opex, month end…) can't take a window, and stay out of it if already picked. Under the minutes the window is read back on each release's usual time: FOMC (14:00 ET): no entries 13:30–14:30. A release before the open matters only if the window reaches past 09:30: CPI at 08:30 with 60 minutes after ends at the open and never applies.
For example Skip FOMC (event day, day after) stays out of the Fed's decision day and the following
session; Only CPI tests a strategy on CPI mornings alone; Skip FOMC (±30 min around the release)
keeps trading the Fed's day but opens nothing between 13:30 and 14:30. A window doesn't remove the
session: entries before and after it go ahead, and a re-entry that falls inside it waits for it to end.
Different sessions for different events are separate rules (Add a rule, up to six).
The window counts, on the recipe's period, how many sessions each rule hits (or, around the
release, on how many it pauses entries) and how many pass in total, with the same notes the run will give: a rule that hits no session, a period that goes beyond
the dates known for an event, or an Only set too small to judge a strategy (fewer than 30 sessions).
Before adding a rule, check whether it pays: Results → More → Economic events compares the P&L of
the run on each event's sessions with the other sessions, with a verdict that accounts for chance (see
the results).
The recipe stores the rule, never the dates. Every run resolves it on the current calendar, which
updates by itself from the official sources (see the economic event calendar):
a backtest run again after new data, or brought up to date with Update data, follows the new
releases without touching the recipe. How a release maps to a session, and why unscheduled events
never count, is in How the engine trades.
Exit
Without any exit rule a position is held to settlement: at the close it is valued at intrinsic
value with the index's closing print, with no closing commission or slippage.
Target and stop
Target and Stop are always in the plan, reading off when they are zero. The stop is one: on
the whole structure, or on the legs you pick with Per leg, the switch next to its level (searching
per leg finds it and opens the stop). The fill rules at the end of this list appear in the settings of
the target and of the stop once a level is set; the two stop rules apply wherever the stop is measured.
Profit target
In % of premium (50 = close when the open P&L reaches half the entry premium) or in $, like a broker quote ($5 = close when the open P&L reaches $5.00, i.e. $500 per SPX contract).
Stop loss
In % of premium (200 = close when the open loss reaches twice the premium) or in $, like a broker quote: $20 means 20.00, so the position closes when it loses $2,000 per SPX contract. Under the fields, a level in $ is read back per contract: Per SPX contract (×100): stop −$2,000. A stop larger than the most the position can lose (on a debit structure, anything above 100% of the debit) practically never fires: the warnings say so.
Per leg (per-leg stop loss)
The switch next to the stop's level. On, the same level is measured on each watched leg instead of the whole structure. The leg is the trigger, not what closes: when a watched leg's loss reaches the level, the whole position closes, every leg at that minute (with leg groups, the group of that leg). In % of that leg's own premium (200 = the leg loses twice what it was sold or bought for: a put sold at $2.00 stops out when buying it back costs $6.00) or in $, like a broker quote (the leg's loss times its quantity: $20 = $2,000 per SPX contract). Watched legs appear under the switch: watch one leg or several. Each watched leg is checked on its own, and the first to reach the level closes the position. The short legs are watched by default; click a leg to add or remove it, and at least one stays watched. On an iron condor the default watches both short legs: click the short call to watch only the short put, or click a wing to watch it too. The plan reads the legs after the level: Stop 200% on the short put, on each short leg, on each of legs 01, 02. Example: sell a 15-delta put and buy one 25 points lower for margin, watch the sold put: when it hits its stop, both legs close. With Per leg on the structure has no stop of its own: there is one stop, measured on the legs. A bought leg can lose at most 100% of its premium, so above 100% the stop never fires on it: the form and the warnings say so.
Cap profits at the target
On by default. A target exit books exactly the target, not the overshoot of the minute it was hit (like a limit order resting at the target).
Two consecutive touches at the target
On by default. The target fires only when the P&L stays at or above it for two minutes in a row, filtering one-minute spikes of the quotes.
Cap the stop-out at the stop amount
Off by default. A stop exit books at most the stop amount, not the overshoot of the minute. With Per leg, the leg that hit its stop books at its level and the other legs at their fill.
Ignore single-bar stop violation
Off by default. The stop fires only when the P&L stays at or below it for two minutes in a row. With Per leg, the same leg must stay at or below its level two minutes in a row.
The percentage always applies to the premium in magnitude: on a debit structure 50% means a profit
of half the debit paid, and a 100% stop means losing the whole debit.
Note
Backtests saved before the stop became one could have both a stop on the structure and a per-leg stop.
From the archive they run exactly as saved. Reopened in the composer they keep the per-leg stop, and a
note at the top of the composer says which structure stop a new run from there leaves out.
Exit rules
Added from + Add an exit rule. Each closes the whole position at the first minute it is true, or
the first minute after its cooldown.
Early exit
Close at a fixed time (ET). The Early exit type selector next to it is greyed out with a soon badge: with same-day expirations there is only one kind.
Minutes in trade (MIT)
Exit after N minutes from entry.
Close before economic events
In the Calendar group. Pick releases that come out during market hours (FOMC decision and FOMC minutes at 14:00, JOLTS, ISM manufacturing and ISM services at 10:00) and the minutes before the release, 1 to 120. On those sessions the position closes N minutes before the release (FOMC: closes at 13:55, released at 14:00 ET), and no new entry opens until the release is out: from the minute after it the strategy runs as usual, and a re-entry waits for it. Like the other exit rules it acts on the main position; the adjustments of a Tactic keep their own exits. Releases before the open (CPI, the jobs report at 08:30) usually find nothing open, so they aren't offered; a recipe that names one closes only on the rare days it comes out during market hours (PCE has a few at 10:00). The window counts the sessions of the recipe's period it would close early on. Like the entry rule, the recipe stores the rule, never the dates.
Underlying move
Exit if the index rises or falls by a threshold from the entry price, in % or points.
Position delta
Exit when the position delta of one structure goes below or above a level, typed in points (−30 = −0.30). Long legs add their delta, short legs subtract it, times the leg quantity: a short put at 20 counts +20, a short call at 20 counts −20, an iron condor starts near 0. The check runs every minute from the minute after entry, so a level the position is already beyond at entry closes it at once. The warnings flag both that case and a level the legs can never reach. The lower level must be below the upper one. With leg groups, each group reads only its own legs.
Tested short
Exit when the index touches the strike of any short leg, with an offset in points: positive = exit before contact (a buffer above a short put, below a short call), negative = exit past the strike. Set 0 to exit exactly at the strike.
Trailing stop
Follows the peak of the open P&L and exits when the profit gives back the trail width: in % of the initial premium (Fixed) or of the current premium, i.e. the cost to close, which tightens as the premium decays (Recalculated). Activate only after a profit of (% of premium) arms it later; empty = armed from entry.
VIX exit
Exit if VIX or VIX9D rises or falls by a threshold from its value at the entry minute (% or points), or when VIX9D ÷ VIX goes below or above a level.
Indicator exit
Exit when RSI ≤ or ≥ a level, or when the price is above or below an N-day SMA or an N-minute EMA. Same definitions as the entry.
Custom indicator
Exit when one of your indicators says so: rules combine with or, the conditions inside a rule with and (Add rule, Add condition). Checked every minute of the trade on the indicator's last closed bar, as in Custom indicators.
Short/long ratio exit
Exit when the short ÷ long premium ratio, at mid, minute by minute, goes to or below / to or above a level.
Cooldown
Every exit that fires on a condition has a Cooldown switch at the bottom of its settings: target,
stop (also per leg and for leg groups), trailing stop, underlying move, position delta, tested short,
VIX exit (moves and VIX9D ÷ VIX), indicator exit, custom indicator exit (one cooldown for all its
rules), short/long ratio exit, exit signals and profit actions (one cooldown for all of them). Switch it on and type the minutes, 1 to 390. Each exit has
its own: a stop with a 5-minute cooldown and a VIX exit with 1 minute can sit in the same recipe.
What it does
The first N minutes of the trade, starting with the entry minute, the rule isn't watched. After that it's watched as usual, measured from the entry: the VIX, the index and the premium at the entry minute stay the reference. Without a cooldown the entry minute is already watched: entry at 10:03, first possible exit at 10:04, the first option price after the entry. With 1 minute the 10:03 minute is skipped, the rule is watched from 10:04 and can fire from 10:05, the price that closes the 10:04 minute. Under the switch the composer reads it back with your entry time.
A level already crossed
If the target, the stop, a watched leg or a profit action is already past its level when the cooldown ends, no order was waiting for it: the position closes at that minute's price, never at the level. The cap options apply only to a level crossed after the cooldown.
Trailing stop
Starts at the end of its cooldown: the peak, and the Activate only after a profit of threshold, count from there.
Two touches and single bar
Both minutes must come after the cooldown.
Time exits
Early exit, minutes in trade, the close before economic events and timed actions have no cooldown: they are already a time.
The plan shows the cooldown next to the rule's values (1-min cooldown), and in Rewind the exit's
cause says when a rule was already true as its cooldown ended. A study can vary the cooldown's
minutes like any other exit field.
Manage
What happens while the position is open, added from + Add management.
Profit actions
Partial exits at profit levels. Each action has a level (% of premium or $, the quote: $5 = $500 per SPX contract) and,
when the open P&L reaches it, can close a percentage of the initial contracts and/or move the
stop to a P&L level (0 = breakeven, positive = locked-in profit, negative = a tighter maximum loss).
The remaining contracts go back to the main target and stop. Percentages round down to whole contracts:
an action that can't close at least one contract only moves the stop, and the warnings say so.
Timed actions
Timed actions
At a clock time or after N minutes in the trade: close a percentage of the initial contracts and/or move the stop (P&L level, 0 = breakeven) and/or set a new target. Levels are in % of the initial premium or in $, the quote ($5 = $500 per SPX contract).
Leg groups
With groups, the position enters as one trade and then each group is managed on its own: its own
Target % and Stop % (of the group's premium; empty = the recipe's target and stop: a
percentage then applies to the group's premium, a level in $ stays as it is) and its own
exits in the trade log. All other exit conditions apply to every group; a per-leg stop closes the group
of the leg that hit it. Assign legs to groups in the
structure; at least two groups are needed (legs without a group count as one).
Tactics
Tactics
Tactics the engine executes mechanically on every trade: when a watched series reaches a threshold it opens an extra structure, without confirmation, with its own exits. The window lists the Tactics armed for this backtest (Edit, Disarm); Manage opens the same library and builder as Rewind (Arm, Edit, Delete, New Tactic). Up to six can be armed, each with its own ladder. The plan flags an armed Tactic edited since you armed it (Use this version), deleted, or that works in Rewind only, and the run waits for it. Details in Tactics in the backtest.
Premium in dollars and the contract multiplier
Everything the recipe measures per contract is typed and read in $, the way a broker quotes it
and the trade log shows it: the min/max entry premium, the target and stop in $, the per-leg stop, the
levels of profit and timed actions, the premium of a Fixed Premium leg and the per-position slippage.
$16 is a combo quoted at 16.00.
A contract is worth the quote times the contract multiplier of the underlying, and under each
field the level is read back per contract with both: Per SPX contract (×100): up to $1,600. The
multiplier comes from the underlying of the recipe, never from a fixed number: it is 100 on SPX
and on every underlying in the data plan (SPY, QQQ, NDX, RUT, IWM, VIX options and XSP), and it
differs on futures options (50 on ES, 20 on NQ) and on European index options. The engine, saved
recipes and the API keep the per-contract dollars; the quote is how they are typed and read.
Costs & assumptions
Commissions & fees
Open and Close, in $ per contract per leg (a leg of quantity 2 pays twice). Default $2 each, a realistic all-in figure for SPX (broker commission plus exchange and regulatory fees). Settlement pays no closing commission.
Slippage · Per position ($)
The default model. Each leg is priced at its mid rounded to 0.05, and slippage is a fixed amount on the quote of the whole position: $0.10 = $10 per SPX contract, whatever the number of legs (per leg group, if there are groups). Separate values for Entry, Exit and Stop loss (empty stop = same as exit; it also applies to per-leg stop and trailing-stop exits). Paid at entry and on early exits, never at expiration.
Slippage · Per leg (fraction of the half-spread)
The alternative model. Each leg fills at mid ± a fraction of the half-spread: 0 = at the mid, 0.5 = halfway between mid and bid/ask, 1 = at the bid or ask. The price is rounded to the exchange tick (0.05 below $3, 0.10 from $3), against the trader when exactly halfway, as a real fill would be. Separate fractions for entry and exit.
Ignore wide bid/ask spreads
Skips entries where a leg's spread exceeds 100% of its mid (anomalous markets, or strikes with no real market). A one-tick spread is never wide.
Close open trades at the end of the test
On 0DTE every position closes the same day, so this switch has no effect today; it matters once multi-day expirations exist.
Turning off Commissions or Slippage sets them to zero for the run. How each cost enters the
P&L, step by step, is in Fills and costs.
Running
Run backtest sends the recipe to the server, which validates it and runs it (a few seconds for a
full history). Validation errors are shown in plain language next to the run button. If a run
doesn't answer within two minutes you get an explicit timeout: try again, or narrow the period.
Results land in the results view; identical recipes on the same data are
served from a cache.