Price Master: the Aria payoff language and deterministic GPU Monte Carlo (Sibelius)

First, a name collision to clear up. There are two things called Aria in this family. The one in gpu-backtest is a strategy DSL — signals and when ... -> buy/sell rules. The one here, in Sibelius, is a contract / payoff description language. Same naming instinct, different jobs. Sibelius is the sell-side core behind Price Master.

Aria, the payoff language

It’s a combinator-based contract language in the SPJ/Peyton-Jones/Eber “Composing Contracts” lineage (the same idea behind Bloomberg’s DLIB/BLAN, built on LexiFi’s MLFi algebra). You describe a payoff out of primitives and the engine prices it. The combinator set, from AriaSpec.md:

Combinator Description
zero / one(ccy) null contract / pay 1 unit of ccy now
give(c) flip to the counterparty view
and / or both contracts / holder takes the better one
scale(e, c) scale payments by expression e
when(d, c) / until(d, c) acquire on / active until date d
anytime(d1, d2, c) American exercise in [d1, d2]
on(dates, c) Bermudan exercise on specific dates
cond(b, c1, c2) branch on a boolean
barrier(dir, level, asset, hit, miss) up_in / up_out / down_in / down_out

What a script looks like

Scripts are .aria files. Comments are --, payoffs are built from let bindings and the combinators above, and |> when(date) pins a cashflow to a settlement date. Here’s a plain FX call (TestData/payoffs/fx_call.aria):

-- FX call: payoff = notional * max(USDKRW(T) - K, 0)
contract FXCall {
    let fx = fxrate("USDKRW")
    let K = 1300.0
    let notional = 1000.0
    notional * max(fx - K, 0.0) * one(KRW) |> when(2026-01-01)
}

The point of a payoff language is the exotics, where var + := carry path state across observation dates. A trimmed Korean autocall (ELS) — the running minimum pmin, a tf “already terminated” flag, and a smooth_step barrier at each semi-annual date (korean_autocall_3y.aria, periods 3–6 elided):

contract KoreanAutocall3Y {
    let basket = min(spot("KOSPI2") / 280.0, spot("SAMSUNG") / 100.0)

    var pmin = 1000000.0          -- running minimum of the basket
    var tf   = 0.0                -- terminated-flag, 0..1
    pmin := min(pmin, basket)

    let d1 = when(2025-06-01, one(KRW))
    let d2 = when(2025-12-01, one(KRW))

    -- Period 1: autocall barrier at 90%
    let t1  = smooth_step(0.9 - pmin, 10.0, 0.0)
    let et1 = min(1.0 - tf, t1)
    tf   := tf * (1.0 - d1) + min(1.0, tf + et1) * d1
    pmin := pmin * (1.0 - d1) + 1000000.0 * d1

    -- Period 2: barrier steps down to 90% -> 85% -> ... over the schedule
    let t2  = smooth_step(0.9 - pmin, 10.0, 0.0)
    let et2 = min(1.0 - tf, t2)
    tf   := tf * (1.0 - d2) + min(1.0, tf + et2) * d2

    -- Coupons on early termination (5% per period), accreting
      0.05 * et1 * d1
    + 0.10 * et2 * d2
    -- ... periods 3-6 ...
    -- Knock-in put at maturity if never autocalled and basket < 60%
    + (1.0 - tf) * min(basket - 0.60, 0.0) * d2
}

That’s the whole pitch: the same file describes a vanilla and a path-dependent autocall, and the engine prices either one. The smooth_step instead of a hard indicator is deliberate — it keeps the adjoint Greeks well-behaved through the barrier. Other scripts in TestData/payoffs/ cover snowballs, TARNs, cliquets, range accruals, quantos, CDS, and dual-leg IR swaps.

A contract compiles to flat bytecode and is evaluated either across Monte Carlo paths (SIMD, antithetic, QMC) or via a PDE solver (1D/2D, American via tridiagonal + projection). Models behind it: Black-Scholes, Heston, Dupire local vol, rough vol. Greeks come out by adjoint algorithmic differentiation. XVA (CVA/KVA) and SIMM counterparty risk sit on top.

The unglamorous GPU work

The recent commits aren’t features — they’re the determinism plumbing that makes a GPU pricer trustworthy. CUDA Monte Carlo had an RNG-state writeback race; fixing it meant a deterministic reduction and a portable RNG mode so the CPU and GPU paths produce the same numbers. CI now keeps dual goldens split by platform and auto-enables CUDA so the build matches the goldens it’s checked against. If your GPU pricer can’t reproduce its own outputs, none of the headline throughput matters.

Deep hedging — what exists, what doesn’t

DEEPHEDGE_TODO.md is honest about the gap between the demo and a portfolio-grade hedger:

Surface Status
RLHedgeTrainer::trainPolicy — REINFORCE, short call, BS dynamics, single asset
evaluatePolicy — score vs BS-delta over N paths
Multi-asset variant (AriaMultiAssetHedge.h)
AriaDeepHedgeTrain / Evaluate JSON endpoints
Train on an arbitrary Aria contract
Train on a portfolio of contracts
Per-trade notionals, fixings, multi-currency netting
Production reward (mean-CVaR, exponential utility)
GPU training

So: a working single-instrument RL hedger with JSON train/evaluate endpoints, and a clear list of what portfolio-level hedging still needs. There’s also a dnn-plugin plan for a stable C ABI with memory-domain awareness, to keep the model boundary clean.

Numbers

MC throughput ~579M path-steps/sec at 65,536 paths × 252 steps (x86-64, 32 threads, GCC -O2 auto-vectorized); PDE1D European under 5 ms on a 501×365 grid (larger grids scale up — ~50 ms at 801×365). Self-benchmarked — the GPU MC numbers are currently transfer-bound, which is exactly why the determinism work came before any speedup claim.

The buy-side counterpart — order flow, microstructure, market-making quoters — is Paganini / Flow Master.

Written on May 13, 2026