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::simulate serves 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 is projects/ores.synthetic/service/src/ir_curve_preview_handler.hpp. The per-kind entries both read are in projects/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.hpp and projects/ores.synthetic/api/include/ores.synthetic.api/messaging/simulate_paths_common.hpp. The first two are generated from projects/ores.synthetic/modeling/; the last is hand-written, because a clamp is not a wire field.

Emacs 29.3 (Org mode 9.6.15)