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": "2019-01-01T00:00:00",
  "End": "2021-12-31T00:00:00",
  "Cash": 10000.0000,
  "Symbol": "SPX",
  "Structure": {
    "Name": "ShortStrangle",
    "Expirations": [
      {
        "Name": "160dte",
        "DTE": "160",
        "Min": 140,
        "Max": 190
      }
    ],
    "Legs": [
      {
        "Name": "short_call",
        "Qty": "-1",
        "ExpirationName": "160dte",
        "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
        },
        "OptionType": "Call"
      },
      {
        "Name": "short_put",
        "Qty": "-1",
        "ExpirationName": "160dte",
        "StrikeSelector": {
          "Min": 5,
          "Max": 15,
          "BidPrice": null,
          "AskPrice": null,
          "MidPrice": null,
          "Delta": "leg_short_call_delta",
          "Gamma": null,
          "Theta": null,
          "Vega": null,
          "WVega": null,
          "Rho": null,
          "IV": null,
          "Statement": null
        },
        "OptionType": "Put"
      }
    ]
  },
  "Entry": {
    "Schedule": {
      "AfterMarketOpenMinutes": null,
      "BeforeMarketCloseMinutes": 30,
      "Every": "day"
    },
    "Conditions": [],
    "VarDefines": {
      "initial_theta": "pos_theta"
    },
    "AbortConditions": [
      "pos_theta < 20"
    ],
    "ReentryDays": 1,
    "Concurrency": {
      "MaxPositionsInFlight": 2,
      "EntryShiftDays": 3
    }
  },
  "Adjustment": {
    "Schedule": {
      "AfterMarketOpenMinutes": null,
      "BeforeMarketCloseMinutes": 30,
      "Every": "day"
    },
    "ConditionalAdjustments": {
      "pos_delta > 5": {
        "MoveLegAdjustment": {
          "LegName": "short_put",
          "StrikeSelector": {
            "Min": null,
            "Max": null,
            "BidPrice": null,
            "AskPrice": null,
            "MidPrice": null,
            "Delta": "pos_delta - leg_short_put_delta",
            "Gamma": null,
            "Theta": null,
            "Vega": null,
            "WVega": null,
            "Rho": null,
            "IV": null,
            "Statement": null
          }
        }
      },
      "pos_delta < -5": {
        "MoveLegAdjustment": {
          "LegName": "short_call",
          "StrikeSelector": {
            "Min": null,
            "Max": null,
            "BidPrice": null,
            "AskPrice": null,
            "MidPrice": null,
            "Delta": "pos_delta - leg_short_call_delta",
            "Gamma": null,
            "Theta": null,
            "Vega": null,
            "WVega": null,
            "Rho": null,
            "IV": null,
            "Statement": 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
      }
    }
  },
  "SimSettings": {
    "FillModel": "AtMidPrice",
    "SlippageAmt": 0,
    "Commission": {
      "OptionFee": 1.5
    }
  },
  "MesoSimVersion": null
}

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.
It is important to note that the margin is not calculated nor enforced. You must understand your strategy’s margin requirements by considering your account’s margin capability, your broker’s margin rules, and the structure you are planning to trade.

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)
  • 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": "my90DayExpiration",
     "DTE": "90",
     "Min": 50,
     "Max": null
  },
  {
     "Name": "160dte",
     "DTE": "160",
     "Min": 140,
     "Max": 190
  }
]

The above snippet defines two expirations with unique names: my90DayExpiration and 160dte. 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 90dte 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. 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".

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": "160dte",
     "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
     },
     "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.

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, 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".


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": [],
 "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.

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:
List with an arbitrary number of days included from the work week

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


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 find exits opportunistically using intraday data 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.

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.

Now, let’s talk about the meat of the detail adjustment. 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_put",
          "StrikeSelector": {
            "Delta": "pos_delta - leg_short_put_delta"
        }
      }
   },
    "delta < -5": {
       "MoveLegAdjustment": {
         "LegName": "short_call",
         "StrikeSelector": {
            "Delta": "pos_delta - leg_short_call_delta"
          }
     }
    }
 },
 "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_put leg.
   - When the structure delta moves below -5, we move the short_call leg.

Currently, MesoSim supports one type of adjustment, the MoveLegAdjustment, which is moving a certain leg to a new strike. Later we might consider additional ways to adjust the structure.


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).

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.


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

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.

List of available indicators:

APO(fastPeriod, slowPeriod)
Absolute Price Oscillator

BBANDS(timePeriod, devUp, devDown)
Bollinger Bands

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

MAMA(fastLimit, slowLimit)
MESA Adaptive Moving Average

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

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

Comments