pub mod cfd_swap;
pub mod fx_rollover;
use std::{
fmt::{Debug, Display},
rc::Rc,
};
use ahash::AHashMap;
pub use cfd_swap::{CfdSwapModule, CfdSwapRate};
pub use fx_rollover::FXRolloverInterestModule;
use indexmap::IndexMap;
use nautilus_common::cache::Cache;
use nautilus_core::UnixNanos;
use nautilus_execution::matching_engine::OrderMatchingEngine;
use nautilus_model::{
data::Data,
identifiers::{InstrumentId, Venue},
instruments::InstrumentAny,
types::{Currency, Money},
};
#[derive(Debug)]
pub struct ExchangeContext<'a> {
pub venue: Venue,
pub base_currency: Option<Currency>,
pub instruments: &'a AHashMap<InstrumentId, InstrumentAny>,
pub matching_engines: &'a IndexMap<InstrumentId, OrderMatchingEngine>,
pub cache: &'a Cache,
}
#[derive(Debug, Clone)]
pub enum SimulationModuleAny {
CfdSwap(CfdSwapModule),
FXRolloverInterest(FXRolloverInterestModule),
#[cfg(feature = "python")]
Python(crate::python::modules::PythonSimulationModule),
}
impl SimulationModule for SimulationModuleAny {
fn pre_process(&self, data: &Data) -> anyhow::Result<()> {
match self {
Self::CfdSwap(module) => module.pre_process(data),
Self::FXRolloverInterest(module) => module.pre_process(data),
#[cfg(feature = "python")]
Self::Python(module) => module.pre_process(data),
}
}
fn process(
&self,
ts_now: UnixNanos,
ctx: &ExchangeContext,
) -> anyhow::Result<SimulationModuleResult> {
match self {
Self::CfdSwap(module) => module.process(ts_now, ctx),
Self::FXRolloverInterest(module) => module.process(ts_now, ctx),
#[cfg(feature = "python")]
Self::Python(module) => module.process(ts_now, ctx),
}
}
fn acknowledge(&self, outcomes: &[AccountAdjustmentOutcome]) -> anyhow::Result<()> {
match self {
Self::CfdSwap(module) => module.acknowledge(outcomes),
Self::FXRolloverInterest(module) => module.acknowledge(outcomes),
#[cfg(feature = "python")]
Self::Python(module) => module.acknowledge(outcomes),
}
}
fn log_diagnostics(&self) -> anyhow::Result<()> {
match self {
Self::CfdSwap(module) => module.log_diagnostics(),
Self::FXRolloverInterest(module) => module.log_diagnostics(),
#[cfg(feature = "python")]
Self::Python(module) => module.log_diagnostics(),
}
}
fn reset(&self) -> anyhow::Result<()> {
match self {
Self::CfdSwap(module) => module.reset(),
Self::FXRolloverInterest(module) => module.reset(),
#[cfg(feature = "python")]
Self::Python(module) => module.reset(),
}
}
}
#[derive(Clone)]
pub struct SimulationModuleHandle(Rc<dyn SimulationModule>);
impl SimulationModuleHandle {
#[must_use]
pub fn new<T>(module: T) -> Self
where
T: SimulationModule + 'static,
{
Self(Rc::new(module))
}
#[must_use]
pub fn from_rc(module: Rc<dyn SimulationModule>) -> Self {
Self(module)
}
}
impl Debug for SimulationModuleHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple(stringify!(SimulationModuleHandle))
.field(&"<dyn SimulationModule>")
.finish()
}
}
impl SimulationModule for SimulationModuleHandle {
fn pre_process(&self, data: &Data) -> anyhow::Result<()> {
self.0.pre_process(data)
}
fn process(
&self,
ts_now: UnixNanos,
ctx: &ExchangeContext,
) -> anyhow::Result<SimulationModuleResult> {
self.0.process(ts_now, ctx)
}
fn acknowledge(&self, outcomes: &[AccountAdjustmentOutcome]) -> anyhow::Result<()> {
self.0.acknowledge(outcomes)
}
fn log_diagnostics(&self) -> anyhow::Result<()> {
self.0.log_diagnostics()
}
fn reset(&self) -> anyhow::Result<()> {
self.0.reset()
}
}
impl From<SimulationModuleAny> for SimulationModuleHandle {
fn from(module: SimulationModuleAny) -> Self {
Self::new(module)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SimulationModuleResult {
NotReady,
Completed(Vec<Money>),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AccountAdjustmentError {
TotalOverflow(Currency),
FreeBalanceOverflow(Currency),
MissingBalance(Currency),
MissingAccount(Venue),
AccountStateGeneration(String),
}
impl Display for AccountAdjustmentError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::TotalOverflow(currency) => {
write!(
f,
"Cannot adjust account: {currency} total exceeds Money bounds"
)
}
Self::FreeBalanceOverflow(currency) => write!(
f,
"Cannot adjust account: {currency} free balance exceeds Money bounds"
),
Self::MissingBalance(currency) => {
write!(
f,
"Cannot adjust account: no balance for currency {currency}"
)
}
Self::MissingAccount(venue) => {
write!(f, "Cannot adjust account: no account for venue {venue}")
}
Self::AccountStateGeneration(error) => {
write!(
f,
"Cannot adjust account: failed to generate account state: {error}"
)
}
}
}
}
impl std::error::Error for AccountAdjustmentError {}
impl AccountAdjustmentError {
pub(crate) const fn is_retryable(&self) -> bool {
matches!(
self,
Self::TotalOverflow(_) | Self::FreeBalanceOverflow(_) | Self::AccountStateGeneration(_)
)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AccountAdjustmentOutcome {
Applied,
Failed(AccountAdjustmentError),
}
pub trait SimulationModule {
fn pre_process(&self, data: &Data) -> anyhow::Result<()>;
fn process(
&self,
ts_now: UnixNanos,
ctx: &ExchangeContext,
) -> anyhow::Result<SimulationModuleResult>;
fn acknowledge(&self, outcomes: &[AccountAdjustmentOutcome]) -> anyhow::Result<()>;
fn log_diagnostics(&self) -> anyhow::Result<()>;
fn reset(&self) -> anyhow::Result<()>;
}
#[cfg(test)]
mod tests {
use std::{cell::Cell, rc::Rc};
use rstest::rstest;
use super::*;
#[derive(Debug)]
struct CountingModule {
resets: Rc<Cell<u32>>,
}
impl SimulationModule for CountingModule {
fn pre_process(&self, _data: &Data) -> anyhow::Result<()> {
Ok(())
}
fn process(
&self,
_ts_now: UnixNanos,
_ctx: &ExchangeContext,
) -> anyhow::Result<SimulationModuleResult> {
Ok(SimulationModuleResult::NotReady)
}
fn acknowledge(&self, _outcomes: &[AccountAdjustmentOutcome]) -> anyhow::Result<()> {
Ok(())
}
fn log_diagnostics(&self) -> anyhow::Result<()> {
Ok(())
}
fn reset(&self) -> anyhow::Result<()> {
self.resets.set(self.resets.get() + 1);
Ok(())
}
}
#[rstest]
fn simulation_module_handle_from_rc_clones_shared_module() {
let resets = Rc::new(Cell::new(0));
let module: Rc<dyn SimulationModule> = Rc::new(CountingModule {
resets: resets.clone(),
});
let handle = SimulationModuleHandle::from_rc(module);
let cloned = handle.clone();
handle.reset().unwrap();
cloned.reset().unwrap();
assert_eq!(resets.get(), 2);
assert_eq!(
format!("{handle:?}"),
"SimulationModuleHandle(\"<dyn SimulationModule>\")"
);
}
}