For the latest documentation, please visit the new sites: MesoSim docs at docs.mesosim.io and MesoLive docs at docs.mesolive.io.

Job Definition Reference

High Level Overview

The job definition consists of six well-defined sections:

Top level fields

Variables setting generic backtest-related parameters, such as:
Start Date, End Date, Initial Cash, etc.

Structure

Defines the combination of legs to be traded.

Entry

This section defines everything related to entry:
Schedule, conditions, and variables to be recorded.

Adjustment

Optional field that defines how adjustments should be made if the strategy requires it. Filling this field enables the user to keep the structure balanced over various market conditions.

Exit

Sets the exit criteria using Profit Target, Stop Loss, Conditional Statements, or Max Days in trade.

SimSettings

Simulator-related settings where the order execution mode, slippage, initial cash, and commission can be defined.


Full Job Definition

To better understand the upcoming sections, we provide a complete backtest job definition for reference. This is a synthetic example to provide an overview of all variables.

{
  "Name": "generate",
  "TemplateName": "[FULL]",
  "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",
          "Gamma": null,
          "Theta": null,
          "Vega": null,
          "WVega": null,
          "Rho": null,
          "IV": null,
          "Statement": 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",
          "Gamma": null,
          "Theta": null,
          "Vega": null,
          "WVega": null,
          "Rho": null,
          "IV": null,
          "Statement": 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)",
            "Gamma": null,
            "Theta": null,
            "Vega": null,
            "WVega": null,
            "Rho": null,
            "IV": null,
            "Statement": 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)",
            "Gamma": null,
            "Theta": null,
            "Vega": null,
            "WVega": null,
            "Rho": null,
            "IV": null,
            "Statement": 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": [
      "ema_10 < ema_5",
      "initial_theta > pos_theta * 4"
    ]
  },
  "Indicators": {
    "Standard": {
      "ema_5": {
        "Instrument": null,
        "Type": "EMA",
        "Param1": "5",
        "Param2": null,
        "Param3": null
      },
      "ema_10": {
        "Instrument": null,
        "Type": "EMA",
        "Param1": "10",
        "Param2": null,
        "Param3": null
      }
    }
  },
  "ExternalData": null,
  "SimSettings": {
    "FillModel": "AtMidPrice",
    "SlippageAmt": 0,
    "Margin": {
      "Model": "RegT",
      "HouseMultiplier": null,
      "RegTMode": "CBOEPermissive"
    },
    "Commission": {
      "CommissionModel": "FixedFee",
      "OptionFee": 1.5,
      "DeribitCommissionSettings": null
    },
    "PositionMonitor": {
      "TraceCollectionInterval": "Hourly"
    }
  },
  "MesoSimVersion": "__VERSION__"
}


Top level fields

The following top-level fields control the backtest run-related settings:

Name
The user-provided name of the backtest. If set to GENERATE, the simulator will create a random, memorable name for the run, similar to YawningFish or GrimCat.

TemplateName
Name of the template potentially used for this run. Note that there is no strict relation between the existing templates and the name provided here. That is, nonexistent template names can also be provided.

Start
A field specifying the first date and time of the simulation.
Format: YYYY-mm-ddTHH:MM:ss

End
Date field specifying the last date and time of the simulation.
Format: YYYY-mm-ddTHH:MM:ss

Cash
The amount of cash at the beginning of the simulation.
It can be considered as “Planned Capital” for the trades.

Symbol
Specifies the Underlying of the trade. Please refer to the “Site/Service Status” page in the portal to obtain what symbols are available.

MesoSimVersion
Field used to store the execution engine’s version. This field is automatically updated on the backtest run to fill the simulator’s version.


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 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: date 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
  BTCUSD: BTCD, BTCW, BTCM, BTCQ
  ETHUSD: ETHD, ETHW, ETHM, ETHQ

Note - Behavioural change: 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. 

Crypto note:
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",
       "Gamma": null,
       "Theta": null,
       "Vega": null,
       "WVega": null,
       "Rho": null,
       "IV": null,
       "Statement": 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 the Expirations section.

OptionType: Defines the option type to be traded. Either Put or Call.

StrikeSelector: Defines how strikes should be selected for the given leg. Currently, strikes can be selected based on type, greeks, IV, or using Statement selector. Given that we must end up selecting exactly one strike for a leg, this section must define exactly one selector.

Min / Max:
The Min and Max are optional fields used to create a subset of the available strikes based on the StrikeSelector chosen.
They always refer to the rest of the parameters of the given StrikeSelector (Prices, greeks, or IV). As Min and Max are both optional, they can be turned off by setting them to null.


BidPrice / AskPrice / MidPrice: Select the strike based on the associated price for the option. As the name implies, BidPrice searches for contracts where the Bid quote is closest to the specified value, while AskPrice applies the same logic to the Ask side of the quote. MidPrice is taking the mid-point between Bid and Ask. This field is helpful for hedging scenarios where one would like to specify that x% of the generated income is used for hedging.

You can only use one of these variables (or one of the greek/IV options) at the same time and can combine it with the Min and Max fields if needed.
For example, using SPX, use 33% of the expected premium to buy 1 long put:
BidPrice=(initial_theta * 60 * 0.33) / 100

Delta / Gamma / Theta / Vega / WVega / Rho / IV: Select strikes closest to the specified greek or IV. The most commonly used field here would be Delta, as option structures are frequently specified using this greek. As the selector is a statement, it is possible to do dynamic hedging based on the rest of the structure.

You can use only one of these variables (or one of the price options) simultaneously and combine it with the Min and Max fields if needed.
The WVega selector is Weighted (or Modified) Vega as described in Nassim Taleb’s Dynamic Hedging book: a “simplified one-factor model using the variance of the volatilities broken up by maturities.” It is calculated using the following formula: sqrt(30/dte).

Statement: Select strikes by executing the statement. The use-case for this selector is to choose legs certain points away from another leg. For example, choosing the strike 25 points away from the short_put leg can be achieved by the following statement: "Statement"="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 Constraints are evaluated (if they present). If all Constraints evaluate to true (or no if constraints are specified) then the Statement is calculated and stored in the inclusion list.
Once all contracts are processed, the Target statement 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. 

Note: As this 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.

 

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.

Crypto Options (on Deribit) trade 24x7; hence there is no official close. In order to match the job 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 day
     - mon, tue, wed, thu, fri, sat, sun:
List with an arbitrary number of days included from the week.

Crypto note: In case of Equity Index Options only workdays are allowed, while in case of Crypto Instrument Saturday and Sunday is also available.

Examples:

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.
Note: 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.
Version information: 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.

Crypto Note: 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"
 ]
  }

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_call leg.
   - When the structure delta moves below -5, we move the short_put leg.

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] 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 - log_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": {
                "Statement": "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:

  1. Remove Leg Adjustment
  2. Move Leg Adjustment
  3. Add Leg Adjustment

The following built-in templates demonstrate the Add Legs Adjustment functionality:

  • SPX-AddLegAdjustment
  • SPX-AddPDSAdjustment

Indicators

In the Indicators section, the user can define technical analysis indicators (such as the Exponential Moving Average) to be used during simulation. The indicators are calculated for the given Instrument using the provided timing parameters. Currently, the only supported Instrument is the underlying index’s price.

The specified indicators are represented as standard variables in the ScriptEngine. Therefore they can be used in every place where a Statement is evaluated. For example

  • Entry.Conditions
  • Exit.Conditions
  • Adjustment’s conditions
  • StrikeSelector’s Statement
  • Expiry DTE Statement

The Indicators top-level field currently contains one entry: Standard.
This additional indirection is planned to add Machine Learning based indicators to the simulator later, which will have different specifications than the normal, standard indicators.

The Standard field is a JSON Map (aka Dictionary), which binds user-defined names to indicator specifications. The user-defined name will be used as the variable name in the ScriptEngine.

Example of two indicators specified: ema_5 and ema_10

  "Indicators": {
    "Standard": {
      "ema_5": {
            "Instrument": null,
            "Type": "EMA",
            "Param1": "5",
            "Param2": null,
            "Param3": null
      },
      "ema_10": {
            "Instrument": null,
            "Type": "EMA",
            "Param1": "10",
            "Param2": null,
            "Param3": null
      }
    }
  }

Each indicator definition has a

  • Type: defines which indicator to use. See the complete list below
  • Instrument: defines the data source (optional: the underlying price is used when null)
  • Param1, Param2, Param3: Parameters for the specified indicator. As different indicators have different parameters, many parameters should be filled as many are required by the specified indicator.

Once the indicator is specified, it can be used as a normal variable throughout the simulation. 

Most indicators (such as EMA(10) above) yield one value as a result. Such indicators' value is accessible by simply using the user-defined name (e.g. ema10).

Indicators, which produce multiple results (such as BBANDS, MAMA, MACD, STOCHRSI, etc) the indicator name will be taken as a prefix and the respective indicator value will be suffixed to a unique variable. For example, in case of BBANDS:

 "Indicators": {
    "Standard": {
      "bbands": {
        "Instrument": null,
        "Type": "BBANDS",
        "Param1": "20",
        "Param2": "1",
        "Param3": "1"
      }
    }
  }

The resulting variables will become:

  • bbands_lower
  • bbands_middle
  • bbands_upper

List of available indicators with their suffixes:

APO(fastPeriod, slowPeriod)
Absolute Price Oscillator

BBANDS(timePeriod, devUp, devDown)
Bollinger Bands
Returns: bbands_lower, bbands_middle, bbands_upper

CMO(period)
Chande Momentum Indicator

DEMA(period)
Double Exponential Moving Average

EMA(period)
Exponential Moving Average

KAMA(period)
Kaufman’s Adaptive Moving Average

MACD(fastPeriod, slowPeriod, signalPeriod)
Moving Average Convergence/Divergence
Returns: macd_macd, macd_signal, macd_hist

MAMA(fastLimit, slowLimit)
MESA Adaptive Moving Average
Returns: mama_mama, mama_fama

MOM(period)
Momentum Indicator

PPO(fastPeriod, slowPeriod)
Percentage Price Oscillator

ROC(period)
Rate Of Change

RSI(period)
Relative Strength Index

SMA(period)
Simple Moving Average

STOCHRSI(timePeriod, fastKPeriod, fastDPeriod)
Stochastic RSI
Returns: stochrsi_fast_k, stochrsi_fast_d

TEMA(period)
Triple Exponential Moving Average

TRIMA(period)
Triangular Moving Average

TRIX(period)
Triple Exponential Average

TSF(period)
Time Series Forecast

VAR(period)
Variance

WMA(period)
Weighted Moving Average

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 date or datetime and 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 1 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

Behavior:
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 30 seconds 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
  • Please see the External Data FAQ Article for more information.

SimSettings

The simulation-related settings, such as Fill Model, Slippage and commission, are located in the SimSettings object. 

Note: The user's preferences have a Settings Page to set the majority of the SimSettings fields and will be applied to any templated run initiated by the web interface.

SimSettings definition:

  "SimSettings": {
    "FillModel": "AtMidPrice",
    "SlippageAmt": 0,
    "Margin": {
      "Model": "RegT",
      "HouseMultiplier": null,
      "RegTMode": "CBOEPermissive"
    },
    "Commission": {
      "CommissionModel": "FixedFee",
      "OptionFee": 1.5
    },
    "PositionMonitor": {
      "TraceCollectionInterval": "Hourly"
    },
    "LegSelectionConstraint": "FullyUnique"
  },

Margin (new in version 2.8):
Margin field 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 approximate.

SimSettings.Margin.Model:
When SimSettings.Margin set to null or SimSettings.Margin.Model set to "None" , no margin calculation is performed, and the margin variables will not be populated. When set to "RegT" margin calculations are executed based on CBOE's Margin Manual. When set to "PMLike", then margin calculations are done using T+0 Risk Graph.

SimSettings.Margin.HouseMultiplier:
Reg-T Margin enables Brokers to increase the margin requirement for positions.
Additionally, sometimes PM requirements are increased by the brokerage.
The SimSettings.Margin.HouseMultiplier variable enables users to model the heightened margin requirements set by brokers by multiplying the margin calculation's result with the user-provided multiplier. The default value is 1.0.

SimSettings.Margin.RegTMode:
CBOE's Reg-T manual defines two calculation modes for complex options positions:
1) CBOEVanilla: The margin requirement is calculated by the predefined rules
2) CBOEPermissive: The vanilla margin calculation's result is reduced by the proceeds of the short sales of complex positions' legs whenever the Margin Manual allows.

Based on our experience, the brokers tend to use the Permissive mode with a House Multiplier of 1.2-1.5 based on their current risk assessment.

SimSettings.Margin.PMConfig:
This field enables setting the upper and lower bounds for the PM Approximation. The highest PnL drop within this range is used to determine the PM requirement:

"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 Equity Options (SPX, RUT) 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 DeribitFeeModel is can be used:

  "SimSettings": {
    "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
      }
    },
    "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.

The FeeModel parameter controls how fills are calculated for every entry, exit and NAV calculation:
 - AtBidAsk provides a pessimistic approach to fills
 - AtMidPrice is 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.

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.

Remark: 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.