Strategy Definition Reference
Updated for v3 · Migration Guide
This page explains every part of a strategy definition: how to describe your structure, control entry/adjustment/exit behavior, bring in external data, and configure simulator/core settings.
Coming from an older version? See the Migration Guide for v1→v2 and v2→v3 changes. Many fields accept expressions evaluated by the Script Engine (/about-the-simulator/scriptengine); time windows and anchors are described in Timing (/about-the-simulator/timing); valuation tools are covered in Options Valuation (/about-the-simulator/options-valuation).
The strategy definition consists of these sections:
- Top level fields:
Strategy metadata and symbol selection. - Backtest:
Backtest run parameters: Name, Start, End, Cash. - Structure:
Defines the combination of legs to be traded. - Entry:
Schedule, entry conditions, and variables to record at entry. - Adjustments:
Optional – defines how adjustments should be made during the trade lifecycle. - Exit:
Exit criteria using Profit Target, Stop Loss, conditions, or Max Days in Trade. - Settings:
Simulator and core controls: fill model, commission, slippage, position monitoring, plus core constraints like margin and leg/expiration selection.
Full Strategy Definition
To better understand the upcoming sections, we provide a complete backtest strategy definition for reference. This is a synthetic example to provide an overview of all fields.
Complete Strategy Definition
{
"StrategyName": "[FULL]",
"Description": "",
"Backtest": {
"Name": "generate",
"Start": "2021-01-01T00:00:00",
"End": "2021-12-31T00:00:00",
"Cash": 10000.0000
},
"Symbol": "SPX",
"Structure": {
"Name": "ShortStrangle",
"Expirations": [
{
"Name": "exp",
"DTE": "160",
"Min": 140,
"Max": 190,
"Roots": {
"Include": [
"SPXW",
"SPX"
],
"Exclude": null
}
}
],
"Legs": [
{
"Name": "short_call",
"Qty": "-1",
"ExpirationName": "exp",
"StrikeSelector": {
"Min": 5,
"Max": 15,
"BidPrice": null,
"AskPrice": null,
"MidPrice": null,
"Delta": "10",
"StrikePrice": null,
"Complex": null
},
"OptionType": "Call"
},
{
"Name": "short_put",
"Qty": "-1",
"ExpirationName": "exp",
"StrikeSelector": {
"Min": 5,
"Max": 15,
"BidPrice": null,
"AskPrice": null,
"MidPrice": null,
"Delta": "-1 * leg_short_call_delta",
"StrikePrice": null,
"Complex": null
},
"OptionType": "Put"
}
]
},
"Entry": {
"Schedule": {
"AfterMarketOpenMinutes": null,
"BeforeMarketCloseMinutes": 30,
"Every": "day"
},
"Conditions": [],
"VarDefines": {
"initial_theta": "pos_theta"
},
"AbortConditions": [
"pos_theta < 20"
],
"QtyMultiplier": "1",
"ReentryDays": 1,
"Concurrency": {
"MaxPositionsInFlight": 2,
"EntryShiftDays": 3
}
},
"Adjustment": {
"Schedule": {
"AfterMarketOpenMinutes": null,
"BeforeMarketCloseMinutes": 30,
"Every": "day"
},
"ConditionalAdjustments": {
"pos_delta > 5": {
"MoveLegAdjustment": {
"LegName": "short_call",
"ExpirationName": null,
"Expirations": null,
"StrikeSelector": {
"Min": null,
"Max": null,
"BidPrice": null,
"AskPrice": null,
"MidPrice": null,
"Delta": "(-1 * leg_short_put_delta) / abs(leg_short_call_qty)",
"StrikePrice": null,
"Complex": null
}
},
"RemoveLegAdjustment": null,
"AddLegsAdjustment": null
},
"pos_delta < -5": {
"MoveLegAdjustment": {
"LegName": "short_put",
"ExpirationName": null,
"Expirations": null,
"StrikeSelector": {
"Min": null,
"Max": null,
"BidPrice": null,
"AskPrice": null,
"MidPrice": null,
"Delta": "(-1 * leg_short_call_delta) / abs(leg_short_put_qty)",
"StrikePrice": null,
"Complex": null
}
},
"RemoveLegAdjustment": null,
"AddLegsAdjustment": null
}
},
"MaxAdjustmentCount": 5
},
"Exit": {
"Schedule": {
"AfterMarketOpenMinutes": null,
"BeforeMarketCloseMinutes": 30,
"Every": "day"
},
"MaxDaysInTrade": 90,
"ProfitTarget": "pos_theta * 160 * 0.5",
"StopLoss": "pos_theta * 160 * 0.5 * 3",
"Conditions": [
"initial_theta > pos_theta * 4"
]
},
"ExternalData": null,
"Settings": {
"Sim": {
"FillModel": "AtMidPrice",
"SlippageAmt": 0,
"Commission": {
"CommissionModel": "FixedFee",
"OptionFee": 1.5,
"DeribitCommissionSettings": null
},
"PositionMonitor": {
"TraceCollectionInterval": "Hourly"
},
"TearsheetGeneration": "On"
},
"Core": {
"LegSelectionConstraint": "UniqueInPosition",
"ExpirationSelectionConstraint": "AbortOnReuse",
"Margin": {
"Model": "RegT",
"HouseMultiplier": null,
"RegTMode": "CBOEPermissive",
"PMConfig": null
}
},
"User": null
},
"MesoSimVersion": "__VERSION__"
}
Sections
Top level fields
The following top-level fields and sections control the job:
- StrategyName:
Optional metadata field (formerly TemplateName in v2). Can reflect which template the strategy originated from. - Description:
Optional description of the strategy. - Symbol:
Specifies the underlying of the trade. - MesoSimVersion:
Execution engine’s version. Automatically populated.
Backtest
Controls the backtest run itself:
- Name:
User-provided name of the backtest. If set togenerate, a memorable name is created. - Start:
First date and time of the simulation. Format:YYYY-mm-ddTHH:MM:ss - End:
Last date and time of the simulation. Format:YYYY-mm-ddTHH:MM:ss - Cash:
Initial cash (expression). Must evaluate to a number greater than 0.
Expressions in this section
| Field | Type | When evaluated | Notes |
|---|---|---|---|
Backtest.Cash | number | At validation and job start | Must be > 0; expression allowed |
Structure
This section defines the combination of option contracts that are traded together to create a structure.
Each Option Contract is uniquely defined by its:
- Underlying instrument (such as SPX, GLD, or BTCUSD)
- Expiration (e.g., 2022-05-18)
- Type: Put or Call
- Strike (e.g., 3500)
Currently, MesoSim doesn't support structures created for multiple Underlyings; hence the underlying instrument can be defined top-level via the Symbol parameter.
Expirations
Expiration selection is made dynamically during options trading. Similarly, during backtesting, the traded expirations are dynamically selected at a given simulation time. MesoSim specifies expiries by adding calendar days to the current simulation time. That is if we started our simulation back in 2008. January 2, and we specify that we are planning to trade options 30 days out (DTE: days till expiration), then option contracts will be selected that expire around 2008 February.
The Structure.Expirations define a list of expirations that are used during trading. At least one should be provided, but multiple expiries are also supported:
"Expirations": [
{
"Name": "front",
"DTE": "90",
"Min": 50,
"Max": null
},
{
"Name": "back",
"DTE": "expiration_front_dte + 60",
"Min": 140,
"Max": 190
}
]
The above snippet defines two expirations with unique names: front and back.
Later, during leg definitions, these names will be used to refer to expiries defined in this section
(Structure.Legs.ExpirationName references Structure.Expirations).
It is a good practice to keep things simple and expressive; hence front is considered a good name.
The Name field is mandatory for every Expiration.
The DTE field defines how many days out should an option contract be selected. As simulation time passes and entry is considered, the DTE statement is evaluated (by the Lua Script Engine) to find an option contract to trade.
Note that expiry selection using DTE is not strict: the closest expiry will be chosen for the given DTE.
Referring to other leg's DTE field is possible via the expiration_NAME_dte variable.
As defined above, the back DTE will be calculated once the front 's exact DTE is found by adding 60 days to it.
The DTE field is mandatory for every Expiration.
The Min and Max are optional fields and are used to create a subset of the available expirations at any given time.
Using these fields, one can avoid choosing expiries that are either too far out or too close to the current simulation time.
Defining a narrow range will result in less (or zero) trades than a loose range.
As Min and Max are both optional, they can be turned off by setting them to null.
The Roots field enables users to filter (include or exclude) specific OCC Option Symbols, such as SPXW, SPX,
or - in early days - SPXPM, SZP, etc.
The Include field doubles as a Priority List:
The order of the listed symbols is taken into account during Expiration selection.
For example, when the following root filter is specified:
"Roots": {
"Include": [
"SPXW", "SPX"
],
"Exclude": null
}
Then, in case of multiple matching Expirations at the given DTE, the first item in the Include list will be chosen: SPXW.
When no Include has been specified, the Roots are ordered in the following manner:
- SPX: SPXW, SPX, lexicographic order of the rest
- GLD: single root: GLD
- RUT: RUTW, RUT, lexicographic order of the rest
- VIX: VIX, lexicographic order of the rest
- BTCUSD: BTCD, BTCW, BTCM, BTCQ
- ETHUSD: ETHD, ETHW, ETHM, ETHQ
Prior to version 2.4, when multiple matching expirations were present for the given DTE, the order of the selected expirations was set at data ingestion time.
Deribit does not officially assign Root to the contracts yet still relies on different expiration types when calculating Fees. MesoSim fills the gap and creates Root similarly as it is present in Index Options. The last character of the Root Symbol denotes the expiration type:
- BTCD / ETHD: Daily
- BTCW / ETHW: Weekly
- BTCM / ETHM: Monthly
- BTCQ / ETHQ: Quarterly
Legs
The section “Legs” defines option contracts to trade as part of the structure.
Each leg has its unique Name, associated expiration (ExpirationName), option type (Type), target quantity (Qty), and a strike selector (StrikeSelector):
"Legs": [
{
"Name": "short_call",
"Qty": "-1",
"ExpirationName": "front",
"StrikeSelector": {
"Min": 5,
"Max": 15,
"BidPrice": null,
"AskPrice": null,
"MidPrice": null,
"Delta": "10",
"StrikePrice": null,
"Complex": null
},
"OptionType": "Call"
}
]
-
Name:
The unique name of the leg. Later, this name will be used when adjustments are made to the structure. Additionally, it makes job inspection and debugging easier. -
Qty:
Defines the number of contracts to be traded. If negative, a short position is taken. Crypto note: In case of Equity Index Options whole numbers are allowed, while in case of Crypto Options fractional shares (such as 0.2) can be specified. -
ExpirationName:
Reference back to the expiration defined in theExpirationssection. -
OptionType:
Defines the option type to be traded. EitherPutorCall. -
StrikeSelector:
Defines how strikes are selected. Exactly one selector must be defined per leg. -
Min / Max:
Optional constraints referring to the selector’s metric (price or delta). Set tonullto disable. -
BidPrice / AskPrice / MidPrice:
Select the strike closest to the specified quote-based value. For example, to hedge using 33% of expected premium:BidPrice=(initial_theta * 60 * 0.33) / 100 -
Delta:
Select the strike closest to the specified absolute delta (x 100) (v2 to v3 Migration → Strike selector notes). -
StrikePrice:
Directly target a strike by expression (FAQ → Choose a Leg certain points away).Example – StrikePrice selector to offset another leg by 25 points:
"StrikePrice": "leg_short_put_strike + 25" -
Complex:
The complex strike selector iterates through all the contracts within the given expiration and chooses the strike that best aligns with the specified criteria."Complex": {
"Statement": "leg_long_strike",
"Target": "underlying_price + 20",
"Constraints": [
"leg_long_strike > underlying_price"
]
}The processing begins with walking through all contracts within the specified expiration and the
Constraintsare evaluated (if they present). If all Constraints evaluate to true (or no if constraints are specified) then theStatementis calculated and stored in the inclusion list. Once all contracts are processed, theTargetstatement is evaluated. Finally, the contract that is closest to the Target is selected from the inclusion list.The Complex StrikeSelector snippet shown above selects contracts that are 20 points higher than the At The Money strike, while ensuring that the chosen contract will always have a strike price higher than the current price of the underlying.
Complex Strike Selector NoteThis selector iterates over all contracts it can be used to create spreads dynamically and balance more complex structures (such as BWBs) based on custom criteria. For more information please refer to the FAQ/Finding spreads page.
Expressions in this section
| Field | Type | When evaluated | Notes |
|---|---|---|---|
Structure.Expirations[].DTE | number | During structure resolution | Target DTE per alias; must be ≥ 0 |
Structure.Expirations[].Min / Max | number | During structure resolution | Optional DTE constraints; non‑negative; Min ≤ Max |
Structure.Legs[].Qty | number | Entry and AddLegs adjustment | Ceil for index/equity options |
Structure.Legs[].LegGroupId | number | Entry and adjustments | Optional group id for live order grouping |
| `Structure.Legs[].StrikeSelector.[BidPrice | AskPrice | MidPrice | Delta |
Structure.Legs[].StrikeSelector.Min / Max | number | When selecting strikes | Optional constraints for strike selection |
Entry
The Entry section is used to specify when and how entries are made. Specifying the schedule and the number of days between two entries is compulsory.
"Entry": {
"Schedule": {
"AfterMarketOpenMinutes": null,
"BeforeMarketCloseMinutes": 30,
"Every": "day"
},
"Conditions": [],
"QtyMultiplier": "1"
"VarDefines": {
"initial_theta": "pos_theta"
},
"ReentryDays": 1
},
Schedule
Defines the time and frequency when entry is considered. As exact timing (such as 14:10) is problematic due to early closes in exchanges,
we have taken the route to specify the timing using relative times from Open (AfterMarketOpenMinutes) or Close (BeforeMarketCloseMinutes).
This approach has no problem with early closes. Schedules operate on 5‑minute increments; avoid targeting exact minutes and prefer ranges. Use Timing Module functions to parameterize these windows.
Crypto Options (on Deribit) trade 24x7; hence there is no official close. In order to match the strategy definition with Equity Index Options, MesoSim considers UTC 00:00 as the Open and Close time for Crypto exchange.
To avoid conflict and ambiguity on entry, either AfterMarketOpenMinutes or BeforeMarketCloseMinutes should be specified.
The Every field defines the run frequency when entry is attempted.
Valid values for this field:
day: For strategies running once a daymon,tue,wed,thu,fri,sat,sun:List with an arbitrary number of days included from the week.5min: Intra-day mode, the Entry is considered every 5 minutes
In case of Equity Index Options only workdays are allowed, while in case of Crypto Instrument Saturday and Sunday is also available.
-
Try to enter every day, 30 minutes before close:
"Schedule": {
"AfterMarketOpenMinutes": null,
"BeforeMarketCloseMinutes": 30,
"Every": "day"
} -
Try to enter 30 minutes after open every Mon, Wed, Fri:
"Schedule": {
"AfterMarketOpenMinutes": 30,
"BeforeMarketCloseMinutes": null,
"Every": "mon,wed,fri"
}
Conditions
This section specifies a list of statements, any of which need to become true to enter the position. This field can filter trades based on the variables available via the Script Engine.
For example, using conditions, it becomes possible to enter only on down days:
"Conditions": [
"underlying_price < underlying_today_open"
]
When multiple conditions are specified, a position is taken when any of the statements become true.
Expressions in this section
| Field | Type | When evaluated | Notes |
|---|---|---|---|
Entry.Schedule.AfterMarketOpenMinutes | number | On Entry schedule | Expression allowed; timing functions permitted |
Entry.Schedule.BeforeMarketCloseMinutes | number | On Entry schedule | Expression allowed; timing functions permitted |
Entry.Conditions[] | boolean | On Entry schedule | Any true triggers entry attempt |
Entry.AbortConditions[] | boolean | Immediately after entry signal | Checked before placing orders; any true aborts entry |
Entry.VarDefines{} | number | After successful entry | Records variables for later use (e.g., initial_theta) |
Entry.QtyMultiplier | number | At order sizing time | Multiplies leg quantities (post‑selector) |
Entry.ReentryDays | number | After position exit | Controls re‑entry cadence |
Entry.Concurrency.MaxPositionsInFlight | number | On Entry schedule | Governs maximum concurrent positions |
Entry.Concurrency.EntryShiftDays | number | On Entry schedule | Staggers entries across days |
When Conditions are evaluated, the legs are not selected. Therefore all the Greeks are set to 0. See AbortConditions to filter based on the Structures Greeks.
Variable Definitions
The VarDefines section enables the user to capture the state during entry.
Then at later stages (Adjustment and Exit), these variables become available in the Conditions section.
A practical example is to capture the whole structure’s Theta at initiation, then later compare it with the point in time Theta exits the position:
"VarDefines": {
"initial_theta": "pos_theta"
}
AbortConditions
This section specifies a list of statements; when any of which evaluates to true, the entry is aborted. This field is evaluated after the leg selection is complete. Therefore, it can filter trades based on the initial state of the structure to be taken.
For example, using AbortConditions, it becomes possible to enter only when a reasonable amount of Theta is gained:
"AbortConditions": [
"pos_theta < 40"
]
When multiple conditions are specified, the entry is aborted when any of the statements become true.
This field is introduced in version 1.2.1
QtyMultiplier
Quantity Multiplier can be used to dynamically adjust the Leg's quantity. It is evaluated after all the Legs are determined, therefore all the leg and position-based variables can be used in the statement.
This field can be used to scale the structure based on the NAV, dynamically.
For more details please see the Set Quantity Dynamically FAQ entry.
Concurrency
The Entry.Concurrency section contains the settings for the parallel positions in flight. The way of concurrency is controlled using two variables:
MaxPositionsInFlight:
Defines how many parallel positions should be taken at the maximum. The number of parallel positions can be less than this if the entry conditions do not enable position entry.EntryShiftDays:
This variable defines how many days should be kept between two entries.
"Entry": {
...
"Concurrency": {
"MaxPositionsInFlight": 4,
"EntryShiftDays": 3
}
}
Exit
Exit rules define the conditions when trades are exited. Just like Entry rules, they are mandatory in each backtest configuration. Currently, it is not possible to leg out from trade; at exit, the whole structure is liquidated.
Schedule
Exit schedules are defined the same way as Entry schedules, as described in the Entry schedule section.
Additional to the schedule specification described in the Entry selection, it is possible to run the algorithm in intraday mode and find
exits opportunistically by specifying a 5min value for the Every field:
"Schedule": {
"AfterMarketOpenMinutes": 30,
"BeforeMarketCloseMinutes": null,
"Every": "5min"
}
Maximum days in trade
Trades will be held for this many days unless other conditions (Profit Target, Stop Loss, Conditions) cause an early exit.
Profit target
The desired profit target where a trade should be exited. This field takes an expression, which allows describing complex scenarios. As an example:
"ProfitTarget": "pos_theta * 160 * 0.5"
Defines a profit target as the projected total theta obtained by holding to the position for 160 days multiplied by a 50% discount factor.
In Deribit, the Theta is represented in Dollars, while the options trade in their respective Crypto Currency (BTC or ETH). Therefore, when calculating expected profit based on theta, the pos_theta should be divided by the underlying price to arrive at the respective cryptocurrency:
"ProfitTarget": "pos_theta/underlying_price * 160 * 0.5"
Stop loss
When the loss of our overall structure reaches the value defined by the stop loss expression, an early exit will be performed. It is a common practice to set the StopLoss to a multiplier of the Profit Target:
"StopLoss": "pos_theta * 160 * 0.5 * 3"
Conditions
Exit conditions are defined similarly to Entry conditions. Let’s say we want to exit when the theta potential of the position degrades to 25%. This could be achieved by defining a variable at entry, then using that variable in the exit condition:
"Entry": {
...
"VarDefines": {
"initial_theta": "theta"
},
"Exit": {
...
"Conditions": [
"initial_theta > pos_theta * 4"
]
}
When comparing timing values (e.g., leg_*_dte, days_in_trade), avoid exact equality checks because these are decimals; prefer tolerant comparisons (for example, leg_long_put_dte <= 1) or cast to integer (int(leg_long_put_dte) == 1).
Expressions in this section
| Field | Type | When evaluated | Notes |
|---|---|---|---|
Exit.Schedule.AfterMarketOpenMinutes | number | On Exit schedule | Expression allowed; timing functions permitted |
Exit.Schedule.BeforeMarketCloseMinutes | number | On Exit schedule | Expression allowed; timing functions permitted |
Exit.MaxDaysInTrade | number | On Exit schedule and lifecycle checks | Must be > 0 |
Exit.ProfitTarget | number | On Exit schedule and lifecycle checks | Exit threshold (profit) |
Exit.StopLoss | number | On Exit schedule and lifecycle checks | Exit threshold (loss) |
Exit.Conditions[] | boolean | On Exit schedule | Additional exit conditions |
Exit.VarDefines{} | number | After exit events | Record variables during exit |
Adjustment
With the optional Adjustment section, keeping an open position balanced based on the criteria defined via the ConditionalAdjustments field is possible.
Similar to the Entry and Exit sections, a Schedule must also be provided for the Adjustment.
Please refer to the Entry and Exit sections’ Schedule for further details on specifying this field.
The MaxAdjustmentCount field controls what the maximum allowed adjustment count is.
Every adjustment increases a counter. If the counter reaches the value specified in the MaxAdjustmentCount field,
the following adjustment will result in position liquidation.
The following snippet contains two conditional adjustments. Please note that not all the fields of the StrikeSelector are shown. For a complete reference on StrikeSelector, please refer to the Structure part of this reference.
"Adjustment": {
"Schedule": {
"BeforeMarketCloseMinutes": 30,
"Every": "day"
},
"ConditionalAdjustments": {
"pos_delta > 5": {
"MoveLegAdjustment": {
"LegName": "short_call",
"StrikeSelector": {
"Delta": "(-1 * leg_short_put_delta) / abs(leg_short_call_qty)"
}
}
},
"pos_delta < -5": {
"MoveLegAdjustment": {
"LegName": "short_put",
"StrikeSelector": {
"Delta": "(-1 * leg_short_call_delta) / abs(leg_short_put_qty)"
}
}
}
},
"MaxAdjustmentCount": 5
},
In the above example, we create two Conditional Adjustments.
The ConditionalAdjustments section is a JSON Map (aka. dictionary), which maps keys
(such as the pos_delta < -5 statement) to values (such as MoveLegAdjustment structure).
In MesoSim, the keys of this map are statements executed by the Script Engine. The statements must evaluate to bool (true or false) to signal the simulator if the adjustment should be activated or not. In the above example, we have two entries (key-value pairs) in the map:
- When the structure delta moves beyond 5, we move the
short_callleg. - When the structure delta moves below -5, we move the
short_putleg.
The Conditional Statements in Conditional Adjustments are alphabetically ordered and evaluated before execution.
This behavior enables moving (or removing) multiple legs in a predictable manner.
The [SPX-MultiLegAdjustment] built-in template shows how to leverage Lua Comments to predictably move multiple legs
in the user-defined order.
MoveLegAdjustment
During the process of leg adjustment, we look for a new strike for the given leg (specified by LegName) to bring the whole structure back to 0 delta. In the case of the first adjustment, this is achieved by evaluating the statement:
pos_delta - leg_short_put_delta
where pos_delta is the whole structure’s actual delta and log_short_put_delta is the put leg’s current delta.
How does this bring the structure back to 0 delta? It’s easiest to see via a small example:
Consider that leg_short_put_delta=4 and leg_short_call_delta=2
Then the overall pos_delta=4+2=6.
If we consider that we will be liquidating our short_put leg and opening a new position, then the new position’s target delta must equal:
pos_delta-leg_short_put_delta = 6 - 4 = 2
Which is precisely the delta of the short call. Why bother creating a formula if we could have just written leg_short_call_delta?
Well, this is a pedagogical example that shows how to calculate it dynamically. The method outlined here works even if multiple legs are considered (for instance, in the case of an Iron Condor strategy).
Move Leg Adjustment enables the user to move the leg vertically (by moving the strikes), horizontally (by moving the expiration), or both.
The mandatory StrikeSelector is used to specify the new strike, while the optional Expirations and ExpirationName can move the leg in time.
For example:
"ConditionalAdjustments": {
"pos_delta > 5": {
"MoveLegAdjustment": {
"LegName": "short_call",
"StrikeSelector": {
"Delta": "(-1 * leg_short_put_delta) / abs(leg_short_call_qty)"
},
"ExpirationName": "exp2",
"Expirations": [
{
"Name": "exp2",
"DTE": "100",
"Min": 80,
"Max": 120,
"Roots": null
}
]
},
"RemoveLegAdjustment": null,
"AddLegsAdjustment": null
}
}
If you are unsure about your adjustment, it is best to check the result by looking at the Greeks chart or validating the variables through the Events viewer.
RemoveLegAdjustment
The RemoveLeg Adjustment simply exits the specified leg once the condition is met, realizing any profits or losses that occurred during the trade:
"RemoveLegAdjustment": {
"LegName": "shorts"
}
If the leg to be closed happens to be the last leg of the position, then, at leg close the whole position will be closed, and a new position will be considered.
RemoveLegAdjustment can be combined with MoveLegAdjustment. If the two are coupled together, then first, the RemoveLeg action will be taken, then the MoveLeg will be executed. This setup enables balancing the structure after the leg is removed.
AddLegsAdjustment
AddLegsAdjustment enables the user to add one or multiple legs.
Add Legs Adjustment contains a set of Legs and optional Expirations and AbortConditions fields,
so that it's functionality is matching the Position Entry.
"AddLegsAdjustment": {
"Legs": [
{
"Name": "long_put",
"Qty": "1",
"ExpirationName": "exp2",
"StrikeSelector": {
"StrikePrice": "leg_short_put_strike + 25"
},
"OptionType": "Put"
}
],
"Expirations":[
{
"Name": "exp2",
"DTE": "100",
"Min": 90,
"Max": 110,
"Roots": null
}
],
"AbortConditions": [
"leg_long_put_price > short_put_initial_price -- wait until long_put is cheaper than short_put"
]
}
All the AddLegAdjustment fields are matching the previously introduced described top level fields:
- Legs: matches Structure.Legs
- Expirations: matches Structure.Expirations
- AbortConditions: matches Entry.AbortConditions
Being able to add and remove legs during execution enables the user to create complex algorithms where the system have all the means to react to changing account or position condition.
Using this feature, one can add a Put Debit Spread, Put Credit Spread, or even a Calendar when necessary.
Add Legs Adjustment can be combined with MoveLegsAdjustment and RemoveLegAdjustment. In case when all the adjustments are included to a ConditionalAdjustment then the execution order will be as follows:
- Remove Leg Adjustment
- Move Leg Adjustment
- Add Leg Adjustment
The following built-in templates demonstrate the Add Legs Adjustment functionality:
- SPX-AddLegAdjustment
- SPX-AddPDSAdjustment
For additional patterns to construct and balance multi‑leg structures dynamically, see Finding Spreads.
Expressions in this section
| Field | Type | When evaluated | Notes |
|---|---|---|---|
Adjustment.Schedule.AfterMarketOpenMinutes | number | On Adjustment schedule | Expression allowed; timing functions permitted |
Adjustment.Schedule.BeforeMarketCloseMinutes | number | On Adjustment schedule | Expression allowed; timing functions permitted |
Adjustment.ConditionalAdjustments{ condition } | boolean | On Adjustment schedule | Key is a condition; true triggers adjustments |
MoveLegAdjustment.StrikeSelector.[…] | number | When selecting strikes | Same semantics as Structure.Legs[].StrikeSelector |
AddLegsAdjustment.Expirations[].(DTE/Min/Max) | number | During adjustment expiration resolution | Same semantics as Structure.Expirations |
AddLegsAdjustment.Legs[].(Qty/StrikeSelector) | number | When selecting and sizing legs | Same semantics as Structure.Legs |
AddLegsAdjustment.AbortConditions[] | boolean | During adjustment | Abort add‑legs when any is true |
*.VarDefines{} | number | During adjustment | Update or record variables |
Adjustment.MaxAdjustmentCount | number | Validation and runtime | Must be ≥ 0 |
Indicators
Indicators were removed in v3 (see v2 to v3 Migration). Use External CSV Data to bring your own variables into the simulation.
External CSV Data
Load data from the CSV file and make it available to the backtest.
The ExternalData.CsvUrl allows users to bring their data and use it in the backtest.
The CSV file columns will be available as variables in the backtest throughout the execution.
Requirements:
- The CSV file must have a header row with the names of the columns/variables.
- The first column must be named
dateordatetimeand can contain date or datetime values - The rest of the columns must have unique alphabetic names that do not conflict with the simulator's internal variables
- The CSV file must be smaller than 2 MB
- The CSV file must be publicly accessible either via Github Gist or Google Sheets's "Publish to the web" feature
- Github Gist is preferred over Google Sheets because it is faster to load
If the first column contains only a date (no time), then the data is assumed to be sampled at EOD. Therefore, the values set for the day will be available the next day.
If the first column contains a date and time, then the values will be usable after the given date and time is passed. These safety measures ensure that no Lookahead Bias is introduced to the simulation.
The system caches the CSV file for 1 minute between validations and downloads to avoid excessive requests. After changing the content of the uploaded file, you need to wait for the caching to expire to see new values.
Further references:
- The
[SPX-ExternalData-Csv]template demonstrates the usage of this feature - FAQs: Use External Data and Share External Data
Settings
Simulation and core configuration are defined in the Settings object.
The user's preferences have a Settings Page to set the majority of the Settings fields and will be applied to any templated run initiated by the web interface.
Settings definition:
"Settings": {
"Sim": {
"FillModel": "AtMidPrice",
"SlippageAmt": 0,
"Commission": {
"CommissionModel": "FixedFee",
"OptionFee": 1.5
},
"PositionMonitor": {
"TraceCollectionInterval": "Hourly"
},
"TearsheetGeneration": "On"
},
"Core": {
"LegSelectionConstraint": "UniqueInPosition",
"ExpirationSelectionConstraint": "AbortOnReuse",
"Margin": {
"Model": "RegT",
"HouseMultiplier": null,
"RegTMode": "CBOEPermissive"
}
}
},
Margin (Core)
Margin controls the margin calculation used during the simulation.
The Reg-T margin model enables the calculation and capturing of the margin requirement of complex options positions
based on CBOE's Margin Manual. The margin requirement for each position is calculated in every simulation step and
made accessible to the user through the pos_margin variable. The sum of all position margins is used to calculate
the account margin, which is provided by the acc_margin variable.
The PM-Like margin model tries to approximate brokerages' Portfolio Margin mode by projecting the Risk Graph's T+0 line to the user-specified boundaries (haircuts). The Portfolio Margin calculation is a highly complex subject and brokerages do not fully disclose their calculations, therefore the calculated margin using this mode is only an approximation.
- Settings.Core.Margin.Model:
When set to "None", no margin calculation is performed. "RegT" uses CBOE’s rules. "PMLike" approximates portfolio margin via T+0 projections. - Settings.Core.Margin.HouseMultiplier:
Multiplies margin requirement to model broker house overlays. - Settings.Core.Margin.RegTMode:
CBOE calculation mode: Vanilla or Permissive. - Settings.Core.Margin.PMConfig:
Upper/lower bounds used by PM-like approximation:
"PMConfig": {
"LowerBoundPct": 10,
"UpperBoundPct": 10
}
Please refer to the Margin Report page to see how the captured results can be interpreted.
Commission:
In case of Index and Equity Options (SPX, RUT, VIX, GLD) and the FixedFee Commission Model is suggested.
With the FixedFee model each contract traded will be charged with a commission specified in the OptionFee field.
In the case of Crypto Options (BTCUSD and ETHUSD) the Deribit commission model can be used:
"Settings": {
"Sim": {
"FillModel": "AtMidPrice",
"SlippageAmt": 0,
"Commission": {
"CommissionModel": "Deribit",
"OptionFee": null,
"DeribitCommissionSettings": {
"MakerFeePctPerContract": 0.03,
"TakerFeePctPerContract": 0.03,
"MaxFeePctPerContract": 12.5,
"DeliveryFeePctPerContract": 0.015,
"WaiveBuySellComboOneSide": true,
"WaiveDailyOptionsDeliveryFees": true
}
}
},
"Core": {
"LegSelectionConstraint": "UniqueInPosition"
}
}
The defaults of this commission setting reflect Deribit's Fees at the time of writing (2023-03-15), but the user is free to change it. The Maker/Taker fees, Max Fee and Delivery fees are described in Deribit's website in detail.
The WaiveBuySellComboOneSide parameter models the discounted commission structure, where the cheapest part of the combo order (which has both long and short legs) is waived. This can be turned off as the user might simulate complex structures, which Deribit doesn't recognize.
Similarly, WaiveDailyOptionsDeliveryFees parameter controls that Fees are waived when Daily Options are kept until expiration. This can be switched off to future-proof the commission as these discount might be not offered by Deribit in the future.
FillModel (Sim)
The FillModel parameter controls how fills are calculated for every entry, exit and NAV calculation:
AtBidAskprovides a pessimistic approach to fillsAtMidPriceis filling in the mid-point of the Bid-Ask spread.
Fills close to MidPrice with some slippage are often achievable in liquid markets, such as SPX. Slippage is the exact amount applied per contract if specified.
Settings.Core.LegSelectionConstraint
LegSelectionConstraint controls how contracts are chosen for each leg of a position.
When set to FullyUnique (default), then each leg of every position must be unique.
This holds true for position initiation and adjustment as well. In this behavior, if the
StrikeSelector selects a contract that is already in use by another position, then the next closest
contract is selected.
The UniqueInPosition is less restrictive than FullyUnique:Contracts for legs can be shared across
multiple positions, but within a position, each leg must remain unique. This restriction holds true for entry
and adjustments as well.
The None option enables the least restrictive mode of operation:
it allows strike sharing within and across positions. When enabled, entries and adjustments can re-use strikes
from existing legs (that is: two legs can end up in the same contract).
Please note that when an offsetting position is made, it is not closing the affected leg, but the two legs are tracked as separate entities.
UniqueInPosition is introduced in Version 2.6, while None is introduced in Version 2.7.
Prior versions always used (the now default) FullyUnique.
PositionMonitor
The trace collection interval can be specified via the PositionMonitor settings:
"PositionMonitor": {
"TraceCollectionInterval": "Hourly"
}
Valid values for TraceCollectionInterval:
- Off : Fastest
- Daily: Fast
- Hourly : Most resource intensive
Please refer to the Backtest Position Monitor article for a complete description of the subject.
Expressions in this section
| Field | Type | When evaluated | Notes |
|---|---|---|---|
Settings.Sim.SlippageAmt | number | Validation and job start | Must be ≥ 0; expression allowed |
Settings.Sim.Commission.OptionFee | number | Validation and job start | Must be ≥ 0 when provided; expression allowed |
Settings.Core.Margin.HouseMultiplier | number | Validation and job start | Numeric parameter; scales margin requirement |
Settings.Core.Margin.PMConfig.LowerBoundPct | number | Validation and job start | Numeric parameter; PM-like lower bound (%) |
Settings.Core.Margin.PMConfig.UpperBoundPct | number | Validation and job start | Numeric parameter; PM-like upper bound (%) |
Expressions Quick Reference
Many fields accept Script Engine expressions. This table summarizes where expressions go, what type they must return, and when they are evaluated.
| Field (path) | Expected type | When evaluated | Notes |
|---|---|---|---|
Backtest.Cash | number | At validation and job start | Must be > 0; expression allowed |
Entry.Schedule.AfterMarketOpenMinutes | number | On Entry schedule | Expression allowed; use timing to parameterize windows |
Entry.Schedule.BeforeMarketCloseMinutes | number | On Entry schedule | Expression allowed; use timing to parameterize windows |
Entry.Conditions[] | boolean | On Entry schedule | If any condition is true, an entry is attempted |
Entry.AbortConditions[] | boolean | Immediately after entry signal | Checked before placing orders; if any true, the entry is aborted |
Entry.VarDefines{} | number | After successful entry | Records variables for later use (e.g., initial_theta) |
Entry.QtyMultiplier | number | At order sizing time | Multiplies leg quantities (post‑selector) |
Entry.ReentryDays | number | After position exit | Controls re‑entry cadence |
Entry.Concurrency.MaxPositionsInFlight | number | On Entry schedule | Governs maximum concurrent positions |
Entry.Concurrency.EntryShiftDays | number | On Entry schedule | Staggers entries across days |
Structure.Expirations[].DTE | number | During structure resolution | Controls target DTE for alias |
Structure.Expirations[].Min / Max | number | During structure resolution | Optional constraints for DTE selection |
Structure.Legs[].Qty | number | Entry and adjustments (add‑legs) | Leg quantity expression |
| `Structure.Legs[].StrikeSelector.[BidPrice | AskPrice | MidPrice | Delta |
Structure.Legs[].StrikeSelector.Min / Max | number | When selecting strikes | Optional constraints for strike selection |
Adjustment.ConditionalAdjustments{ key } | boolean | On Adjustment schedule | Each key is a condition; true triggers the associated adjustments |
Adjustment.*.VarDefines{} | number | During adjustment execution | Update or record variables during lifecycle |
Exit.Schedule.AfterMarketOpenMinutes | number | On Exit schedule | Expression allowed |
Exit.Schedule.BeforeMarketCloseMinutes | number | On Exit schedule | Expression allowed |
Exit.MaxDaysInTrade | number | On Exit schedule and lifecycle checks | Maximum days in trade before forced exit |
Exit.ProfitTarget | number | On Exit schedule and lifecycle checks | Exit threshold (profit) |
Exit.StopLoss | number | On Exit schedule and lifecycle checks | Exit threshold (loss) |
Exit.Conditions[] | boolean | On Exit schedule | Additional exit conditions |
Note: For timing variables that are now decimals, prefer tolerant comparisons or casting. See v2 to v3 Migration → Timing variables are now decimals