ores.analytics.quant

Table of Contents

1. Diagram

ores.analytics.quant component diagram

Figure 1: ores.analytics.quant

2. Summary

Dependency-light quantitative math library. One consumer is the Cross Rates Matrix (CRM): a two-phase engine that builds and validates a driver/derived spanning-tree topology over currency pairs (throwing a readable error at build time if the input admits more than one path between two currencies), then serves continuously-updating derived rates with staleness propagation. Deliberately has no database, messaging, or refdata coupling – everything domain-specific (currency codes, spot days, short-term rates) is supplied by the caller as plain parameters, so the library is consumable standalone and fully unit-testable in isolation.

A second, unrelated area covers stochastic price processes: pluggable i_stochastic_process implementations (geometric and arithmetic Gaussian mixture engines, an Ornstein-Uhlenbeck mean-reverting engine), a process_factory that builds one from a process-type string plus raw parameters, and process_parameter_validation as the single source of truth for "are these parameters good?" shared between server-side construction and any UI that wants a friendly error before submitting. This code originated in ores.synthetic, which mixed the maths with synthetic-data-generation orchestration; it moved here so the maths is reusable (e.g. by a future calibration service) and independently unit-testable without pulling in a service binary.

A third family answers a different question: when does the next tick arrive. domain::i_arrival_process has one method, next_gap() in seconds, and three implementations: periodic_arrival_process (equal gaps), poisson_arrival_process (exponential gaps) and hawkes_arrival_process (self-exciting gaps). The same factory builds them with make_arrival_process, and tick_clock turns one into the stop-responsive loop every producer runs on. The arrival rules live beside the others in domain::validate_arrival_process_parameters.

3. Inputs

  • domain::ccy_pair_input – raw currency-pair quotes (base/quote codes, driver flag) fed to service::topology_builder::build.
  • Pivot currency code and the list of required "major" currencies.
  • domain::driver_quote – a single tick on a driver edge, fed to service::rate_engine::update.
  • domain::staleness_policy – caller-supplied max age for a derived rate to be considered fresh.
  • Raw process parameters (means, stdevs, weights, initial_price) plus a process_type string ("geometric" / "arithmetic" / "ornstein_uhlenbeck"), fed to service::process_factory::make_process and domain::validate_process_parameters.

4. Outputs

  • domain::crm_topology – the immutable, validated spanning tree.
  • domain::topology_build_error – thrown with one topology_error per offending pair when the input cannot form a valid tree.
  • domain::derived_rate – a rate (direct or triangulated) with its rate_status (fresh/stale/unavailable) and the oldest contributing driver's timestamp.
  • domain::i_stochastic_process – the pure interface every price process implements (next() advances one step, current() reads without advancing).
  • domain::process_parameter_validation_result – ok/error-message pair from validating raw process parameters before construction.
  • domain::i_arrival_process – the pure interface an arrival process implements (next_gap() draws the wait to the next arrival, in seconds).
  • domain::validate_arrival_process_parameters – the arrival rules (mode, positive rate, branching ratio below one, positive decay), returning the same ok/error-message pair as the value-process rules.

5. Entry points

  • service::topology_builder::build(pairs, pivot_code, required_majors)
  • service::rate_engine::update(driver_quote)
  • service::rate_engine::rate(base_code, quote_code) / rates(pairs)
  • service::recenter(source_engine, aggregation_code, policy, now) – point-in-time star-shaping around an aggregation currency for risk, preserving every current rate's value.
  • service::process_factory::make_process(process_type, means, stdevs, weights, initial_price, seed) – builds a gaussian_mixture_model_process (geometric/multiplicative), arithmetic_gaussian_mixture_model_process (additive/Bachelier), or ornstein_uhlenbeck_process (mean-reverting) depending on process_type; unrecognised values fall back to geometric.
  • domain::validate_process_parameters(process_type, means, stdevs, weights, initial_price)

6. Boundary

Quant holds its own types and knows no stored spelling. Its engines take enums and small value types, such as domain::date_period and math::fx_quote_convention, so its tests need no other component. The component that owns a record owns its mapping into quant and out of it: reference data reads its codes in ores.refdata.core/mapping/quant_codes.hpp and its FX option conventions in ores.refdata.core/mapping/fx_option_convention_mapping.hpp, and market data maps its observations into engine inputs and the results back. A code is read once, when a row is loaded, so no engine loop sees a string.

7. Dependencies

  • Boost::graph / Boost::boost – topology construction (boost::disjoint_sets for incremental cycle detection, BFS for parent/path assignment).
  • immer (immer::atom<rate_snapshot>, immer::vector<vertex_state>) – lock-free, structural-sharing immutable snapshots for the runtime rate engine.
  • Deliberately not linked: ores.database, ores.service, NATS, ores.refdata, ores.logging (the stochastic-process code carries no logging dependency either – callers that need diagnostics log around the call).

8. Build dependencies

What the shared profile cannot know, declared here rather than hand-edited into the generated build file. Each block is spliced into its generated file after the profile's baseline.

9. See also

find_package(Immer CONFIG REQUIRED)

find_package(magic_enum CONFIG REQUIRED)

find_package(Eigen3 CONFIG REQUIRED)

target_link_libraries(${lib_target_name}
    PUBLIC
        Boost::boost
        Boost::graph
    PRIVATE
        immer
        magic_enum::magic_enum
    PUBLIC
        # Eigen types appear in public headers (the process params
        # structs); on a shared build a PRIVATE link would hide its
        # include directories from consumers.
        Eigen3::Eigen)
target_link_libraries(${tests_target_name}
    PRIVATE
        ${lib_target_name}
        Catch2::Catch2
        Threads::Threads)

Emacs 29.3 (Org mode 9.6.15)