Timing
MesoSim's timing module lets a strategy respond to the trading calendar: enter near the start of a month, measure time in a position, or act before an options expiration. Use timing expressions in Entry, Adjustment, and Exit.
A schedule determines when MesoSim checks a rule. A timing condition determines whether the rule is satisfied at that check. Configure both in the AI Job Editor; see Schedules for the supported cadence and session offsets.
Practical example
Allow entry during the first three trading sessions of the month, then make the position eligible to exit after five calendar days:
{
"Entry": {
"Conditions": [
"timing.trading_days_after(this_month, first) < 3"
],
"VarDefines": {
"entry_minutes_after_open": "timing.minutes_after_open"
}
},
"Exit": {
"Conditions": [
"timing.days_in_trade >= 5"
],
"VarDefines": {
"exit_days_in_trade": "timing.days_in_trade"
}
}
}
Merge these fields into your strategy, keeping its schedules, structure, sizing, and other required settings. The entry window allows a trade when the other entry requirements are also met. The exit condition is evaluated on the exit schedule; weekends or the chosen check time can mean the actual exit occurs later than five calendar days after entry.
Separate Entry.Conditions items are OR alternatives. If a timing window must also satisfy an existing filter, combine them with and in one expression, preserving any existing parentheses. See Entry conditions.
Variables
Read these values as properties, without parentheses: timing.day_of_week, for example. Calendar functions take arguments and use parentheses. See Script Engine for expression syntax and variable availability.
| Name | Type | Description |
|---|---|---|
timing.day_of_week | int | Current weekday: 0 = Sunday through 6 = Saturday |
timing.year | int | Current year in exchange time |
timing.month | int | Current month, 1–12, in exchange time |
timing.day | int | Current day of the month in exchange time |
timing.minutes_after_open | int | Minutes since market open, rounded down to the backtest's market-data interval |
timing.minutes_before_close | int | Minutes until market close, rounded down to the backtest's market-data interval |
timing.days_in_trade | decimal | Elapsed calendar days since this position's entry, including fractional days; requires a position |
Use comparisons that allow fractional values, such as timing.days_in_trade >= 5, rather than relying on a scheduled check to land exactly on a threshold.
Functions
| Signature | Description |
|---|---|
timing.trading_days_until(anchor, [boundary]) -> decimal | Trading days from now until the selected anchor boundary |
timing.trading_days_after(anchor, [boundary]) -> decimal | Trading days elapsed since the selected anchor boundary, including fractional sessions |
timing.calendar_days_until(anchor, [boundary]) -> decimal | Calendar days from now until the selected anchor boundary |
timing.calendar_days_after(anchor, [boundary]) -> decimal | Calendar days elapsed since the selected anchor boundary, including fractional days |
timing.trading_days_in(anchorA, [anchorB], [a_boundary], [b_boundary]) -> decimal | Number of trading sessions in a period or between the selected boundaries |
timing.calendar_days_in(anchorA, [anchorB], [a_boundary], [b_boundary]) -> decimal | Calendar-day length of a period or the interval between selected boundaries |
Square brackets mark optional arguments; omit the brackets when writing an expression. For period anchors, boundary defaults to first. Both *_days_after functions return 0 until the selected boundary is reached; both *_days_until functions return 0 once it is reached.
With one period anchor, *_days_in measures the whole period. With two anchors, it measures from the first boundary of anchorA to the last boundary of anchorB unless you supply other boundaries. For example, timing.calendar_days_in(this_month, next_month) covers both months; timing.calendar_days_in(this_month, next_month, first, first) covers only the current month.
Calendar days and trading days
Calendar days include weekends and holidays. For example, at September 10 at noon in exchange time:
timing.calendar_days_after(this_month, first)is9.5: elapsed days since September 1 at midnight.timing.calendar_days_after(this_month, last)is0: the end of the current month has not been reached.timing.calendar_days_after(last_month, last)is9.5: elapsed days since the end of August.
Trading days follow the instrument's sessions. For equity and index options, weekends and exchange holidays do not add trading days. Fractions are measured against the session's actual length, including early closes. At 10:30 ET on Tuesday, October 8, 2024, timing.trading_days_after(this_week, first) is approximately 1.153846: one complete Monday session plus one hour of Tuesday's 6.5-hour session.
Anchors and boundaries
Anchors are relative to the current simulation date. Write the names directly, without quotes.
| Period | Current | Next | Previous |
|---|---|---|---|
| Week, starting Monday | this_week | next_week | last_week |
| Calendar month | this_month | next_month | last_month |
| Calendar quarter | this_quarter | next_quarter | last_quarter |
| Calendar year | this_year | next_year | last_year |
The boundary selects a reference point within the chosen period:
| Boundary | Calendar functions | Trading functions |
|---|---|---|
first | Start of the period at midnight | Opening time of the first trading session in the period |
last | Start of the following period at midnight | Opening time of the first trading session after the period |
For example, last for a calendar month means midnight at the start of the next month. With *_days_after, this boundary is the point from which elapsed time is measured, and the endpoint is always now.
Event anchors
| Anchor | Event |
|---|---|
monthly_opex | Current month's standard monthly options expiration, normally the third Friday, adjusted for exchange holidays |
vix_settlement | Current month's VIX settlement event |
triple_witching | Quarterly expiration anchor; use in March, June, September, or December |
quad_witching | Quarterly expiration anchor; use in March, June, September, or December |
Events select a single timestamp, so they do not need a first or last boundary. These event anchors use the opening-time reference, normally 09:30 ET, rather than the session close.
An event anchor does not advance to the next occurrence after its date passes. For example, after this month's OPEX reference time, timing.trading_days_until(monthly_opex) is 0. Include a positive lower bound if a rule should apply only before the event.
To restrict a rule to quarterly expiration months, combine it with (timing.month == 3 or timing.month == 6 or timing.month == 9 or timing.month == 12).
More timing rules
Use these expressions in the relevant condition field and choose a schedule that checks during the intended window.
| Intent | Expression |
|---|---|
| Enter on Mondays | timing.day_of_week == 1 |
| Enter when at most one trading day remains before next month | timing.trading_days_until(next_month, first) <= 1 |
| Act within one trading day before the current month's OPEX, while it is still ahead | timing.trading_days_until(monthly_opex) > 0 and timing.trading_days_until(monthly_opex) <= 1 |
| Allow an adjustment only after two calendar days in the position | timing.days_in_trade >= 2 |
Check a timing rule
- Set the schedule. Choose session offsets and a cadence supported by the selected market-data resolution. A condition does not add extra evaluation times.
- Check the intended dates. Test a short period covering a month boundary, holiday, or expiration relevant to your rule.
- Inspect the result. Use the Events Viewer to confirm entry, adjustment, and exit times. Capture a needed timing value with VarDefines to inspect it alongside the events.
For help composing timing expressions, use the AI Job Editor or AI Agents. For full strategies with analysis, visit the Deltaray blog.