Preview and simulate
Table of Contents
1. Summary
Three operations let an editor see what a feed would produce before it runs.
synthetic.v1.ops.simulate_fx_spot_paths returns sample FX spot paths,
synthetic.v1.ops.simulate_ir_curve_paths returns sample short rate paths, and
synthetic.v1.ops.preview_ir_curve_shape returns the curve a set of process
parameters and template rows implies. None of the three persists a row or
publishes a tick. They exist so a change to a config is judged before it is
saved, and they are the read side of the surface the Market Simulator drives.
2. Detail
2.1. What each operation returns
simulate_fx_spot_paths_request carries the GMM means, stdevs and weights,
a process type, an initial price, a tick count, a path count and a seed. The
response carries paths, one series of prices per path, each num_ticks
long.
simulate_ir_curve_paths_request is the same shape for a short rate. It
carries named parameters instead of a GMM mixture, and the response carries
one series of short rate values per path.
preview_ir_curve_shape_request carries a process type, named parameters,
a fixed leg payment frequency code and the Curve Template rows as edited.
Those rows are deliberately not the persisted ir_curve_template_entry rows,
because an edited row has no id or party yet. The response carries one
preview_ir_curve_shape_point per entry, sorted by sequence_index, each
with the entry's tenors and a rate. The persisted ladder is
The IR curve template and its parameters.
2.2. Why a preview reads and a run writes
The three handlers are stateless. simulate_handler and
ir_curve_preview_handler build a real process, step it, and reply. Nothing
reaches the database and nothing is published on NATS.
A running generator is the opposite. The simulator publishes its ticks from inside the generator, and The ingest loop and the one pipeline maps them, writes the observation and delivers it on the per-party stream. The tick type itself is The market tick. The divide matters because a preview is asked for a batch and answers it in one reply, while the synthetic runner owns the generator and keeps publishing while demand holds it.
2.3. How the counts are bounded
simulate_paths_common.hpp declares the bounds once:
max_simulate_num_ticks = 5000, max_simulate_num_paths = 50 and
max_preview_curve_entries = 50. They are service-side clamps rather than wire
fields, so no request carries them, and the shared run_simulate_paths()
applies the first two with std::clamp before generating anything, so a UI bug
cannot ask for a runaway batch. A screen that advertises a limit reads the same
header, which keeps the service limit and the widget limit one number.
ir_curve_preview_handler rejects an empty entry list and a list above
max_preview_curve_entries.
2.4. Where each read gets its numbers
The FX simulate builds its process through
ores::analytics::quant::service::process_factory::make_process, using the
request's own mixture, initial price and seed. Path i uses seed + i, so
one seed gives a reproducible batch and changing the seed reseeds it. The
mixture is the same one the gmm_component rows hold, stated in
The GMM component.
The IR simulate rebuilds the {definition, value} shape the mapping layer
consumes, from the request's named parameters, and calls
map_parameters_to_yield_curve_process. The definitions are made up on the
fly because the request carries parameter names rather than catalogue ids.
That helper is make_preview_process, in
projects/ores.synthetic/service/src/ir_curve_preview_process.hpp, and the
curve shape preview calls the same one.
The curve shape preview builds a refdata context from the
RATES_SPOT_FORWARD tenor convention, resolves the template rows with
resolve(), and prices each row through the shared price_ir_curve_entry()
at the process's current state, with no ticking. The seed is fixed at 42, so
the same parameters and template always give the same shape.
2.5. Authorisation
Each handler authenticates the request and then checks a permission. The
permission is the kind's own config_permission, read from its entry in
feed_kind_registry; the FX simulate uses
synthetic::fx_spot_generation_configs:read and the two IR operations use
synthetic::ir_curve_generation_configs:read, but no handler holds either
literal. A caller who can read a config can preview it, and no separate
operation permission exists.
2.6. Facts the code shape does not show
- The curve shape preview has no FX equivalent. FX has one scalar spot and not a tenor grid, so there is no ladder to price.
- The preview prices each row the same way
The IR curve producer does,
through
price_ir_curve_entry(). A published curve and its preview therefore cannot drift apart. - The IR preview path applies no minimum or maximum to a parameter value. The handler re-materialises the definitions without bounds, so only the UI spin boxes enforce a range on this path.
- All three operations are registered in
projects/ores.synthetic/service/src/registrar.cpp, which is the one list of the service's subjects. - One
simulate_handler::simulateserves both simulate subjects. It takes its kind string from the registration, and the kind's entry supplies the permission, the request decoder, the process maker and the reply writer. Only the envelope, the clamp, the per-path seeding and the stepping are shared. - The two simulate subjects stay separate even though one handler serves them, because the project carries one subject per concrete type and never a payload that carries its own type tag; see Polymorphic types over NATS.
3. See also
- Market Data Architecture — the structure note that orders this cluster, and where to read this page in it.
- Synthetic data generation screens, the two editors that call these operations.
- The generator library, the process interface and factory both simulations build on.
- The simulate handler is
projects/ores.synthetic/service/src/simulate_handler.hpp, and the curve shape preview isprojects/ores.synthetic/service/src/ir_curve_preview_handler.hpp. The per-kind entries both read are inprojects/ores.synthetic/service/src/feed_kind_registry.hpp. - The wire shapes and their bounds are
projects/ores.synthetic/api/include/ores.synthetic.api/messaging/simulate_fx_spot_paths_protocol.hpp,projects/ores.synthetic/api/include/ores.synthetic.api/messaging/ir_curve_operations_protocol.hppandprojects/ores.synthetic/api/include/ores.synthetic.api/messaging/simulate_paths_common.hpp. The first two are generated fromprojects/ores.synthetic/modeling/; the last is hand-written, because a clamp is not a wire field.