The generator library

Table of Contents

1. Summary

ores.analytics.quant is the library that holds the price and short-rate processes the synthetic producers step, and the arrival laws that decide when a producer ticks. Its two process interfaces are i_stochastic_process in ores.analytics.quant/domain/i_stochastic_process.hpp and i_yield_curve_process beside it, and the arrival laws are values in ores.analytics.quant/domain/arrival_law.hpp. Its entry point for the processes is process_factory in ores.analytics.quant/service/process_factory.hpp, which has one method for each family: make_process and make_yield_curve_process. make_arrival_law builds an arrival law.

2. Detail

2.1. The interface

i_stochastic_process has two methods. next() advances the process by one step and returns the new price, and current() reads the price without advancing. The process owns its own state, which is the current price, the engine and the parameters, and it is not thread-safe.

i_yield_curve_process extends that interface with one method. discount_factor(ticks_ahead) answers, from the current state, what zero-coupon bond price the model implies at that distance in ticks. Every instrument a caller derives from one state is evaluated through that one call, which is what makes a published curve a slice of a single curve rather than a set of separately noised quotes. Mapping a calendar tenor to a tick count is the caller's job, not the process's.

The library sits outside ores.marketdata.synthetic on purpose, and the header records why. A calibration service, ores.marketdata or ores.marketdata.synthetic can all instantiate a process without linking the synthetic service binary. The library also carries no logging dependency, so a caller that wants to diagnose an unknown process name has to log around the call rather than inside it.

2.2. The factory

process_factory::make_process takes the process type string, the component means, standard deviations and weights, an initial price, a seed and a tick length in years. It has three branches.

  • "arithmetic" builds arithmetic_gaussian_mixture_model_process.
  • "ornstein_uhlenbeck" builds ornstein_uhlenbeck_process.
  • "geometric" and every unrecognised string build gaussian_mixture_model_process.

There is no error for an unknown name. The geometric engine is the silent fallback, which is why the FX generation config's process_type column is a plain checked string rather than a foreign key.

Before any branch it calls ores::analytics::quant::domain::validate_process_parameters. That function is the single rule set, shared with the Qt client, so a parameter set the dialog rejects is rejected again in the service.

2.3. The Ornstein-Uhlenbeck branch repurposes the mixture columns

The FX config stores a mixture, and an Ornstein-Uhlenbeck process is a single regime rather than a mixture. The factory therefore reads the component rows as scalars. weights.front() is kappa, the reversion speed. stdevs.front() is sigma, the volatility. initial_price doubles as theta, the long-run mean, so a fresh process reverts toward its own starting level. means is unused.

A reader who edits an FX config's components while its process_type is "ornstein_uhlenbeck" is therefore editing a reversion speed in a field labelled as a weight.

2.4. The arrival law is a third kind

Everything above answers what the next price is. When the price arrives is a different question with a different answer, and it does not fit i_stochastic_process, because next() there returns a level. An arrival law is a value that gives the gap to the next tick in the market's operational time. The scheduler, which lives in this library, draws each series' gaps from its law, and a periodic law gives a fixed period. The value process in turn has next(elapsed), so a step can be longer or shorter than one nominal tick. The design, its parameters and its limits are in Tick arrival. The mathematics is in Hawkes Process and Poisson Process.

2.5. The RNG design

Every process owns a std::mt19937 engine built from the seed passed to its constructor, and the constructor's default is 42. There is no global engine and no shared mutable state, so concurrent feeds do not contend.

The mixture weight normalisation is delegated to the standard library. The component weights are handed to a std::discrete_distribution<int>, which normalises them itself, so no code rewrites the stored gmm_component rows.

2.6. The seeding rule

The two producers seed differently, and the difference is deliberate in one case and not recorded in the other.

  • The FX path draws one std::uint32_t from a function-local static std::random_device and writes it to the log at info level as "SYNTHETIC SEED" with the source name. See The FX spot producer.
  • The curve path passes the literal 42. See The IR curve producer.
  • The simulate and preview handlers take a caller seed and add the path index to it, so several paths in one request are different streams and a repeated request reproduces the same paths.

2.7. The facts the code shape does not show

A zero standard deviation is a legal input and both mixture processes handle it directly by drawing the component's mean, because std::normal_distribution asserts on a zero standard deviation in a debug build and would abort the process. validate_process_parameters rejects a negative standard deviation but permits a zero one.

make_yield_curve_process is a second factory entry point on the same class. It is not on any producer's path, it constructs only three of the five catalogued short-rate engines, and it has no caller in the tree. See The yield curve process type catalogue.

3. See also

Emacs 29.3 (Org mode 9.6.15)