The FX spot producer

Table of Contents

1. Summary

ores::marketdata::synthetic::fx_spot_generator is the generator that turns an fx_spot_generation_config into vendor ticks for the simulator adapter. It is declared in ores.marketdata.synthetic.api/generators/fx_spot_generator.hpp. It implements i_generator, and its kind() returns the string constant fx_spot_generator_kind, spelled "fx_spot".

2. Detail

2.1. How the generator is built

make_fx_spot_feed is the builder the factory calls with a persisted config, its component rows, its fixing items and its calibration policy. It does four things.

  1. It rejects an empty component set with std::invalid_argument, because a mixture with no components has no process to build.
  2. It resolves the initial price through resolve_fx_start. Under "fixed" it takes gmm_initial_price. Under "fixing" it reads the newest fixing of the first candidate that has one: the config's own items when it names them, or the items the class policy derives from the series that cover the pair. The spread of that item's fixings over the window scales the mixture. With no fixing it keeps gmm_initial_price, or refuses to start when the policy says so.
  3. It draws one seed and logs it.
  4. It calls process_factory::make_process with the config's process_type and the means, standard deviations and weights taken from the component rows in order.

The publish subject is not built here. The adapter publishes on its ingress subject, and the tick names the vendor instrument that the config serves. See The adapter subjects.

2.2. The tick clock

start() runs the loop on the calling thread, and the caller supplies a dedicated std::thread. The period is one hour divided by ticks_per_hour. The sleep is taken in slices of one hundred milliseconds rather than in one block, so a stop() arriving during a long period is seen promptly and the join does not wait out the period.

The clock lives in ores.analytics.quant and takes its gaps from an arrival process. A periodic one gives the equal period described here. A Poisson or Hawkes one gives random and bursty gaps. See Tick arrival.

stop_flag_ is deliberately not reset at the top of start(). A stop() that lands between the thread spawn and the first loop test would otherwise be clobbered, and the loop and its join would never finish.

2.3. One tick

Each pass through the loop advances the process once with next() and builds one adapter tick.

  • The tick names the vendor instrument: the provider, the id scheme, the external id and the field.
  • value is the price as decimal text, produced by std::format("{}", ...) on the double the process returned.
  • vendor_time is the simulator's clock at the tick. The tick carries no source name; the service knows the source from the adapter.

2.4. Identity and conflict

The generator reports the one vendor instrument it serves in vendor_instruments(): a provider, an id scheme, an external id and a field. Two FX generators conflict when they serve the same vendor instrument. Nothing else about the config enters the conflict rule. The oresmd datum of the quote is not the generator's concern. The service finds it through the mapping.

2.5. The facts the code shape does not show

A publish failure is logged and the tick is dropped. The loop thread has no caller to receive an exception, and an uncaught throw would terminate the whole service process, taking every other feed and NATS handler with it. The next tick simply tries again.

The seed comes from a function-local static std::random_device rather than a fresh device per call, and it is written to the log at info level as "SYNTHETIC SEED" together with the source name. The static keeps the OS entropy pool from being re-seeded between rapid successive calls, which on some platforms returns equal values. The log line is the only record of the seed a running feed used.

publish_count() counts ticks published.

3. See also

Emacs 29.3 (Org mode 9.6.15)