Simulation Module Lifecycle, State Sharing, and Failure Handling
Summary
This technical reference explains how simulation modules are configured and run within a backtesting exchange. It distinguishes declarative configuration, which accepts built-in modules and language bridges, from linked native configuration, which can hold any Rust trait implementation. It then describes the lifecycle: preprocessing before supported market-data items, ordered processing against a shared read-only snapshot after commands settle, and application of each completed adjustment batch followed by one acknowledgement per module.
The page specifies that hook failures place the exchange in an error state until all modules reset successfully, while diagnostic failures do not alter that state. It also explains Python context contents and native type extraction, including limits on name-based matching and cross-library trait-object compatibility. Built-in FX rollover and CFD swap modules use the batch acknowledgement flow; CFD swaps use signed per-instrument rates and may defer a batch when required pricing or exchange inputs are unavailable. This is implementation documentation, not evidence about strategy profitability. It notes that perpetual funding is handled by the exchange itself rather than through these modules.
Key ideas
- Cloning a module handle shares its module state, so isolated venues require distinct instances.
- Modules process in order against a read-only snapshot after timestamp commands have settled.
- Each completed adjustment batch is applied before its module receives one acknowledgement.
- Hook failures keep the exchange in an error state until all modules reset successfully.
- The built-in CFD swap module can defer adjustments when settlement or conversion inputs are missing.
Tags
Full text
# Simulation Modules # Simulation Modules This page describes simulation module configuration, lifecycle, and failure handling. The [behavioral model design](../behavioral_models.md) explains the enum and shared-handle representations used below. Simulation modules use the enum and handle forms at different configuration boundaries: | Boundary | Stored form | Accepted implementations | | ---------------------------------------------- | ------------------------ | ------------------------------------- | | Declarative `BacktestVenueConfig` | `SimulationModuleAny` | Built-ins and language bridges. | | `SimulatedVenueConfig` and `SimulatedExchange` | `SimulationModuleHandle` | Any linked Rust trait implementation. | :::warning[Shared module state] `SimulationModuleHandle` owns an `Rc<dyn SimulationModule>`, so cloning a handle shares the module and its state. Cloning a built-in enum value copies its state, while cloning a Python bridge retains the same Python object. Venues or runs that require isolated state therefore use distinct module instances, including distinct Python objects. ::: ## Lifecycle The exchange runs each module through this lifecycle: 1. `pre_process` runs before the exchange processes each supported market data item. 1. `process` runs for each module in order against the same read-only exchange snapshot after commands have settled for the timestamp. Processing stops at the first failure, and the exchange applies no adjustments from that timestamp. 1. For each completed result in order, the exchange applies its batch as ordered `Money` adjustments, then calls that module's `acknowledge` exactly once with the corresponding outcomes, including for an empty batch. ## Failure handling - Failures from `pre_process`, `process`, `acknowledge`, or `reset` leave the exchange in an error state until every module resets successfully. This prevents a failed acknowledgement from replaying adjustments that the account may already contain. - Diagnostic failures return to the engine with the module index and hook name without changing the exchange error state. ## Python modules The `process` hook for a Python `SimulationModule` subclass receives an owned `SimulationModuleContext` snapshot containing: - The venue. - The optional base currency. - The instruments. - The order books. - The open positions. The bridge does not expose mutable cache or matching-engine state. Python exceptions retain the hook name as they propagate through the exchange and `BacktestEngine.run`. ## Linked native types Linked native PyO3 types can register an extractor for their Python class. The extractor resolves an object for imperative `BacktestEngine.add_venue` configuration. Python configuration resolves modules as follows: | Configuration path | Accepted objects | Stored form | Native extractor behavior | | -------------------------- | ----------------------------------------------------- | ------------------------ | -------------------------- | | `BacktestEngine.add_venue` | Built-ins, linked native types, and Python subclasses | `SimulationModuleHandle` | Matches the exact type. | | `BacktestVenueConfig` | Built-ins and Python subclasses | `SimulationModuleAny` | Does not consult registry. | An unrelated class with the same name does not select a registered extractor. Extractor registration does not create a runtime ABI for trait objects across a `cdylib` boundary. ## Built-in modules The built-in FX rollover and CFD swap modules use the completed-batch acknowledgement flow. CFD swap rates are per-instrument signed daily `Decimal` fractions of settlement notional, with separate long and short values, a configurable UTC rollover time, and a configurable triple-roll weekday. For a single-currency account, the module converts the adjustment to the account base currency at the cached mid exchange rate. The CFD swap module defers the whole batch when any of these inputs is missing: - A matching engine. - A settlement price. - An exchange rate. The module logs one warning per booking date, instrument, and failure kind before quieter retries. Perpetual funding remains part of `SimulatedExchange` and is not a simulation module.
Shown in full with attribution under the source's licence. Licence: LGPL-3.0
This summary was written by Stratmill's research agent from the original; it is not a copy of the source.