Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Script Language

Scripts are small imperative programs. Each event holds one script; statements are separated by ;, blocks use { }, and #-style comments are not supported (keep comments in the surrounding Rust or JSON). The grammar is defined in src/scripting/parsing/lexer.rs and parser.rs.

Statements

x = 0.035;                 # assignment
x += 1; x -= 1; x *= 2; x /= 2;
acc pays amount on "2027-06-09" in "USD";
if cond { ... } else { ... }
for i in range(1, 3) { ... }
for s in [1, 2, 3] { ... }

Identifiers are case-sensitive and must not collide with the reserved words if else and or not for true false pays on in or with the built-in function names below. Every variable that is read must have been assigned in the same or an earlier event; variables persist across events on the same path.

Literals and operators

CategorySyntax
Numbers0.035, 10000000, 1e-4
Booleanstrue, false
Strings"2027-06-09", "USD", "Actual360" (dates, currencies, day counters, index names)
Arithmetic+ - * /, ** (power), unary +/-
Comparison== != < <= > >=
Logicand, or, not
Arrays[1, 2, 3], range(1, 3), vals[0], vals.append(x), vals.mean(), vals.std()

Comparisons produce booleans that can only be used in if conditions or combined with and/or/not; they cannot be assigned to numeric variables. Use fif (below) when you need a differentiable numeric indicator.

Built-in functions

FunctionMeaning
exp(x), ln(x), pow(x, y)Elementary functions
min(a, b, ...), max(a, b, ...)Variadic (2 to 100 arguments) min/max, differentiable almost everywhere
cvg("start", "end", "DayCounter")Year fraction between two dates; day counter names follow the DayCounter enum: Actual360, Actual365, Thirty360, Thirty360US, ActualActual, Business252
fif(x, a, b, eps)Smoothed “functional if”: returns a where x > eps/2, b where x < -eps/2, and interpolates linearly in between (a call spread of width eps), so the derivative with respect to x is finite
range(a, b)Integer range a..b for for loops

Market data access

Market observations are resolved on the event date unless a date argument is given. Each call becomes an entry in that event’s SimulationDataRequest; the market model must be able to serve it.

ExpressionRequest createdNotes
RateIndex("SOFR", "start", "end")ForwardRateRequestSimple forward rate of MarketIndex::SOFR for the period; on the fixing date this is the realised rate
Df("2027-06-09")DiscountRequest on the engine’s local discount indexDiscount factor from the event date to the given date
Df("2027-06-09", "TermSOFR3m")DiscountRequest on a named curve
Spot("AAPL")SpotRequest for MarketIndex::Equity("AAPL")Equity spot on the event date
Spot("USD", "CLP")FxRequestPrice of one USD in CLP
Spot("USD", "CLP", "2024-12-31")FxRequest with explicit observation date

Index names are parsed with MarketIndex::from_str, currencies with Currency::try_from, so the spelling must match the enum variants (SOFR, TermSOFR3m, ICP, ESTR, …).

Payments

acc pays <amount> [on "<date>"] [in "<CCY>"];
  • acc is the accumulator variable; the engine adds the discounted, numeraire-deflated value of the payment to it. Never multiply the amount by a discount factor yourself.
  • on defaults to the event date; in defaults to the engine’s local currency. Payments in another currency are converted with the simulated FX rate on the payment date.
  • Every pays statement gets a payment id during indexing. ScriptedProduct turns each id into one ContingentClaim, and evaluate_with_cashflows reports each id’s expected amount and present value.
  • pays may also appear inside an expression, e.g. call = pays max(Spot("CLP", "USD") - 900.0, 0);, which assigns the discounted payment to call.
  • An EventStream must contain at least one event, and a ScriptedProduct must contain at least one pays expression with no payment before the reference date.

Conditionals and smoothing

if Spot("AAPL") <= trigger {
    deal pays 100000 on "2027-09-09";
} else {
    deal pays 0 on "2027-09-09";
}

A plain if is a discontinuous function of Spot("AAPL"), so its pathwise derivative is zero almost everywhere and infinite at the barrier. The FuzzyEvaluator (visitors/fuzzyevaluator.rs) first lets IfConditionTransform rewrite every comparison into the canonical form \((\text{lhs}-\text{rhs}) > 0\), then replaces the hard branch selection by a truth degree \(d_t\in[0,1]\) computed with a call spread of width \(\varepsilon\):

\[ d_t(x) = \begin{cases} 0 & x < -\varepsilon/2 \ \dfrac{x + \varepsilon/2}{\varepsilon} & |x| \le \varepsilon/2 \ 1 & x > \varepsilon/2 \end{cases}, \qquad \text{result} = d_t\cdot\text{then} + (1-d_t)\cdot\text{else}. \]

Equality tests use a butterfly instead of a call spread, and and/or/not are implemented as products and complements of truth degrees. The width is chosen per comparison: with automatic scaling (the default in ScriptEngine) it is

\[ \varepsilon = \max\bigl(0.02\cdot\max(|\text{lhs}|,|\text{rhs}|),\;10^{-8}\bigr) \]

(constants AUTO_SMOOTHING_RELATIVE_WIDTH and AUTO_SMOOTHING_MIN_WIDTH), so a rate barrier at 4% is smoothed over roughly 8 bp while an equity barrier at 150 is smoothed over 3 price units. FuzzyEvaluator::with_eps overrides the fallback width used when auto scaling is off. Nested ifs are supported; the IfProcessor pass computes the maximum nesting depth and pre-allocates one variable snapshot per level so both branches can be evaluated and blended.

Use fif when a single expression is more readable than an if block, for example a digital coupon paying 5% when SOFR fixes above 4%, smoothed over 10 bp:

coupon = fif(RateIndex("SOFR", "2027-03-09", "2027-06-09") - 0.04, 0.05, 0.0, 0.001);

Worked payoffs

Capped floating coupon (per accrual period event):

a1 = cvg("2027-03-09", "2027-06-09", "Actual360");
r1 = RateIndex("SOFR", "2027-03-09", "2027-06-09");
deal pays 10000000 * (0.0385 - min(r1, 0.06)) * a1 on "2027-06-09";

European equity put settled in cash:

payoff pays 10000 * max(strike - Spot("AAPL"), 0) / strike on "2027-09-09";

Fixed-rate note (single event, explicit currency):

note = 0; note pays 1052500 on "2028-09-09" in "USD";

Errors

Parsing and evaluation return ScriptingError:

VariantRaised when
InvalidSyntax(String)Grammar violation or reserved-word misuse; the message includes line and column
UnexpectedToken(String)Token sequence does not match the expected production
InvalidToken(String)Lexer could not classify a character sequence
ParsingError(ParseFloatError)Malformed numeric literal
EvaluationError(String)Runtime failure, e.g. the requested result variable is not defined
NotFoundError(String)A market response slot was missing
InvalidOperation(String)Structural rule broken, e.g. no events, unordered events, payment before the reference date
QuantSupport(QSError)Wrapped library error (date parsing, unknown index or currency, …)