use crate::domain::expiry::{ActiveExpiry, RollingPlanner};
use crate::domain::factors::{FactorRow, FactorTape, build_chain};
use crate::infrastructure::{DEFAULT_MAX_CACHED_SNAPSHOT_CONTRACTS, DEFAULT_MAX_CACHED_SNAPSHOTS};
use crate::session::SimulationParametersV2;
use crate::utils::ChainError;
use chrono::{DateTime, Utc};
use optionstratlib::ExpirationDate;
use optionstratlib::chains::chain::OptionChain;
use positive::Positive;
use std::collections::HashMap;
use std::time::Instant;
use tracing::{debug, instrument};
use uuid::Uuid;
#[derive(Debug, Clone)]
pub(crate) struct ExpiryChain {
pub(crate) expires_at: DateTime<Utc>,
pub(crate) days_to_expiration: Positive,
pub(crate) labels: Vec<String>,
pub(crate) chain: OptionChain,
}
impl PartialEq for ExpiryChain {
fn eq(&self, other: &Self) -> bool {
self.expires_at == other.expires_at
&& self.days_to_expiration == other.days_to_expiration
&& self.labels == other.labels
&& self.chain.underlying_price == other.chain.underlying_price
&& self.chain.symbol == other.chain.symbol
&& self.chain.options == other.chain.options
}
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct SeriesSnapshot {
pub(crate) step: usize,
pub(crate) simulated_at: DateTime<Utc>,
pub(crate) spot: Positive,
pub(crate) base_volatility: Positive,
pub(crate) chains: Vec<ExpiryChain>,
}
impl SeriesSnapshot {
#[must_use]
#[cfg_attr(
not(test),
expect(
dead_code,
reason = "the by-expiration lookup the tests use; the DTO layer walks \
`chains` in order instead"
)
)]
pub(crate) fn chain_at(&self, expires_at: DateTime<Utc>) -> Option<&ExpiryChain> {
self.chains
.iter()
.find(|chain| chain.expires_at == expires_at)
}
#[cfg_attr(
not(test),
expect(
dead_code,
reason = "the per-rule view the inventory tests assert on; nothing served \
needs it, because a chain carries its own labels"
)
)]
pub(crate) fn chains_for(&self, rule_id: &str) -> impl Iterator<Item = &ExpiryChain> {
self.chains
.iter()
.filter(move |chain| chain.labels.iter().any(|label| label == rule_id))
}
}
#[must_use]
fn snapshot_contracts(snapshot: &SeriesSnapshot) -> usize {
snapshot
.chains
.iter()
.map(|chain| chain.chain.options.len())
.sum()
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct SeriesBuilder<'a> {
parameters: &'a SimulationParametersV2,
tape: &'a FactorTape,
}
impl<'a> SeriesBuilder<'a> {
pub(crate) fn new(
parameters: &'a SimulationParametersV2,
tape: &'a FactorTape,
) -> Result<Self, ChainError> {
parameters.validate()?;
Ok(Self { parameters, tape })
}
#[instrument(skip(self), level = "debug")]
pub(crate) fn snapshot(&self, step: usize) -> Result<SeriesSnapshot, ChainError> {
let row = self.tape.row(step).ok_or_else(|| {
ChainError::NotFound(format!(
"step {step} is past the end of a {}-step simulation",
self.tape.len()
))
})?;
let planner = RollingPlanner::new(&self.parameters.schedule);
let active = planner.active_at(row.simulated_at)?;
let mut chains = Vec::with_capacity(active.len());
for expiry in &active {
chains.push(self.build_expiry_chain(row, expiry)?);
}
debug!(
step,
chains = chains.len(),
"Built a rolling multi-expiration snapshot"
);
Ok(SeriesSnapshot {
step: row.step,
simulated_at: row.simulated_at,
spot: row.spot,
base_volatility: row.base_volatility,
chains,
})
}
fn build_expiry_chain(
&self,
row: &FactorRow,
expiry: &ActiveExpiry,
) -> Result<ExpiryChain, ChainError> {
let days = expiry.days_to_expiration(row.simulated_at)?;
let days_to_expiration = Positive::new_decimal(days).map_err(|e| {
ChainError::Internal(format!(
"days to expiration {days} for {} is not a valid Positive: {e}",
expiry.expires_at
))
})?;
let chain = build_chain(
self.parameters,
row.spot,
row.base_volatility,
ExpirationDate::Days(days_to_expiration),
)?;
Ok(ExpiryChain {
expires_at: expiry.expires_at,
days_to_expiration,
labels: expiry.labels.clone(),
chain,
})
}
}
struct CacheEntry {
snapshot: SeriesSnapshot,
last_access: Instant,
contracts: usize,
}
pub(crate) struct SnapshotCache {
entries: HashMap<(Uuid, usize), CacheEntry>,
capacity: usize,
contract_budget: usize,
resident_contracts: usize,
}
impl Default for SnapshotCache {
fn default() -> Self {
Self::new()
}
}
impl SnapshotCache {
#[must_use]
pub(crate) fn new() -> Self {
Self::with_bounds(
DEFAULT_MAX_CACHED_SNAPSHOTS,
DEFAULT_MAX_CACHED_SNAPSHOT_CONTRACTS,
)
}
#[must_use]
pub(crate) fn with_bounds(capacity: usize, contract_budget: usize) -> Self {
Self {
entries: HashMap::new(),
capacity: capacity.max(1),
contract_budget: contract_budget.max(1),
resident_contracts: 0,
}
}
#[must_use]
#[cfg_attr(
not(test),
expect(
dead_code,
reason = "the budget is enforced internally; this reports it"
)
)]
pub(crate) fn contracts(&self) -> usize {
self.resident_contracts
}
#[must_use]
pub(crate) fn len(&self) -> usize {
self.entries.len()
}
#[must_use]
#[cfg_attr(
not(test),
expect(
dead_code,
reason = "clippy's len_without_is_empty requires this alongside `len`"
)
)]
pub(crate) fn is_empty(&self) -> bool {
self.entries.is_empty()
}
#[must_use]
#[cfg_attr(
not(test),
expect(
dead_code,
reason = "the configured bound, asserted by the tests that pin the \
zero-capacity floor"
)
)]
pub(crate) fn capacity(&self) -> usize {
self.capacity
}
pub(crate) fn get(&mut self, simulation: Uuid, step: usize) -> Option<&SeriesSnapshot> {
let entry = self.entries.get_mut(&(simulation, step))?;
entry.last_access = Instant::now();
Some(&entry.snapshot)
}
pub(crate) fn insert(&mut self, simulation: Uuid, snapshot: SeriesSnapshot) {
let key = (simulation, snapshot.step);
self.remove_entry(&key);
let incoming = snapshot_contracts(&snapshot);
if incoming > self.contract_budget {
debug!(
step = snapshot.step,
contracts = incoming,
budget = self.contract_budget,
"The snapshot is larger than the cache budget; serving it without caching"
);
return;
}
debug_assert!(
self.capacity >= 1,
"capacity is floored at one on construction"
);
self.evict_to(self.capacity - 1);
self.evict_to_contracts(self.contract_budget - incoming);
self.resident_contracts = self.resident_contracts.saturating_add(incoming);
self.entries.insert(
key,
CacheEntry {
snapshot,
last_access: Instant::now(),
contracts: incoming,
},
);
}
fn remove_entry(&mut self, key: &(Uuid, usize)) {
if let Some(entry) = self.entries.remove(key) {
self.resident_contracts = self.resident_contracts.saturating_sub(entry.contracts);
}
}
pub(crate) fn evict_simulation(&mut self, simulation: Uuid) -> usize {
let victims: Vec<(Uuid, usize)> = self
.entries
.keys()
.filter(|(id, _)| *id == simulation)
.copied()
.collect();
for key in &victims {
self.remove_entry(key);
}
victims.len()
}
fn evict_to_contracts(&mut self, max: usize) {
while self.resident_contracts > max {
match self.least_recently_used() {
Some(key) => self.remove_entry(&key),
None => break,
}
}
}
fn least_recently_used(&self) -> Option<(Uuid, usize)> {
self.entries
.iter()
.min_by_key(|(key, entry)| (entry.last_access, **key))
.map(|(key, _)| *key)
}
fn evict_to(&mut self, max: usize) {
while self.entries.len() > max {
match self.least_recently_used() {
Some(key) => self.remove_entry(&key),
None => break,
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::api::rest::models::{ApiTimeFrame, ApiWalkType};
use crate::api::rest::requests_v2::CreateSimulationRequest;
use crate::session::{ExpiryRule, ExpiryRuleKind};
use chrono::{TimeZone, Weekday};
fn reference_schedules() -> Vec<ExpiryRule> {
vec![
rule("zero_dte", ExpiryRuleKind::Daily, 1),
rule(
"weeklies",
ExpiryRuleKind::weekly([Weekday::Mon, Weekday::Wed, Weekday::Fri]),
3,
),
rule(
"monthlies",
ExpiryRuleKind::Monthly {
weekday: Weekday::Fri,
},
12,
),
]
}
pub(super) fn monthly_rule() -> ExpiryRule {
rule(
"monthlies",
ExpiryRuleKind::Monthly {
weekday: Weekday::Fri,
},
12,
)
}
pub(super) fn zero_dte_rule() -> ExpiryRule {
rule("zero_dte", ExpiryRuleKind::Daily, 1)
}
pub(super) fn parameters_with_rules(schedules: Vec<ExpiryRule>) -> SimulationParametersV2 {
parameters(request(2, schedules))
}
fn rule(id: &str, kind: ExpiryRuleKind, count: usize) -> ExpiryRule {
match ExpiryRule::new(id, kind, count) {
Ok(rule) => rule,
Err(error) => panic!("the test rule must be valid: {error}"),
}
}
fn request(steps: usize, schedules: Vec<ExpiryRule>) -> CreateSimulationRequest {
let start_at = match Utc.with_ymd_and_hms(2026, 1, 5, 14, 30, 0).single() {
Some(instant) => instant,
None => panic!("the test instant must be valid"),
};
CreateSimulationRequest {
symbol: "SPX".to_string(),
steps,
start_at: Some(start_at),
step_interval_seconds: Some(86_400),
timezone: "America/New_York".to_string(),
calendar: None,
expiration_time: "17:00".to_string(),
schedules,
initial_price: 5000.0,
volatility: 0.18,
risk_free_rate: 0.04,
dividend_yield: 0.0,
method: ApiWalkType::Brownian {
dt: 1.0 / 252.0,
drift: 0.0,
volatility: 0.18,
},
time_frame: ApiTimeFrame::Day,
chain_size: Some(3),
strike_interval: Some(25.0),
skew_slope: None,
smile_curve: None,
spread: Some(0.02),
seed: Some(42),
}
}
pub(super) fn test_parameters() -> SimulationParametersV2 {
parameters(request(2, reference_schedules()))
}
pub(super) fn test_tape(parameters: &SimulationParametersV2) -> FactorTape {
tape(parameters)
}
pub(super) fn test_parameters_with_seed(seed: u64) -> SimulationParametersV2 {
let mut request = request(2, reference_schedules());
request.seed = Some(seed);
parameters(request)
}
fn parameters(request: CreateSimulationRequest) -> SimulationParametersV2 {
match SimulationParametersV2::try_from(request) {
Ok(parameters) => parameters,
Err(error) => panic!("the request must convert: {error}"),
}
}
pub(super) fn builder<'a>(
parameters: &'a SimulationParametersV2,
tape: &'a FactorTape,
) -> SeriesBuilder<'a> {
match SeriesBuilder::new(parameters, tape) {
Ok(builder) => builder,
Err(error) => panic!("the reference parameters must validate: {error}"),
}
}
fn tape(parameters: &SimulationParametersV2) -> FactorTape {
match FactorTape::build(parameters, ¶meters.method) {
Ok(tape) => tape,
Err(error) => panic!("the tape must build: {error}"),
}
}
fn snapshot(
parameters: &SimulationParametersV2,
tape: &FactorTape,
step: usize,
) -> SeriesSnapshot {
match builder(parameters, tape).snapshot(step) {
Ok(snapshot) => snapshot,
Err(error) => panic!("the snapshot at step {step} must build: {error}"),
}
}
fn atm_call_mid(chain: &ExpiryChain) -> Positive {
let atm = match chain.chain.atm_option_data() {
Ok(data) => data,
Err(error) => panic!("the chain must have an ATM strike: {error}"),
};
match atm.call_middle {
Some(mid) => mid,
None => panic!("the ATM call must have a mid price"),
}
}
#[test]
fn test_reference_configuration_satisfies_every_rule_at_every_step() {
let parameters = parameters(request(12, reference_schedules()));
let tape = tape(¶meters);
for step in 0..tape.len() {
let snapshot = snapshot(¶meters, &tape, step);
for (rule_id, expected) in [("zero_dte", 1), ("weeklies", 3), ("monthlies", 12)] {
assert_eq!(
snapshot.chains_for(rule_id).count(),
expected,
"step {step} lost inventory for {rule_id}"
);
}
assert!(
snapshot.chains.len() <= 16,
"step {step} priced {} chains, more than the sixteen rule slots",
snapshot.chains.len()
);
}
}
#[test]
fn test_coincident_expirations_are_priced_once_with_both_labels() {
let parameters = parameters(request(1, reference_schedules()));
let tape = tape(¶meters);
let snapshot = snapshot(¶meters, &tape, 0);
assert_eq!(snapshot.chains.len(), 15);
let first = match snapshot.chains.first() {
Some(chain) => chain,
None => panic!("the snapshot must carry chains"),
};
assert_eq!(
first.labels,
vec!["weeklies".to_string(), "zero_dte".to_string()]
);
}
#[test]
fn test_chains_are_chronological_and_unique() {
let parameters = parameters(request(3, reference_schedules()));
let tape = tape(¶meters);
let snapshot = snapshot(¶meters, &tape, 0);
assert!(!snapshot.chains.is_empty());
for pair in snapshot.chains.windows(2) {
assert!(
pair[0].expires_at < pair[1].expires_at,
"chains must be strictly increasing in expiration"
);
}
}
#[test]
fn test_chains_share_the_step_state_and_differ_in_expiration() {
let parameters = parameters(request(5, reference_schedules()));
let tape = tape(¶meters);
let snapshot = snapshot(¶meters, &tape, 2);
let row = match tape.row(2) {
Some(row) => row,
None => panic!("the tape must have a row at step 2"),
};
assert_eq!(snapshot.spot, row.spot);
assert_eq!(snapshot.base_volatility, row.base_volatility);
assert_eq!(snapshot.simulated_at, row.simulated_at);
let mut seen: Vec<DateTime<Utc>> = Vec::new();
for chain in &snapshot.chains {
assert!(chain.days_to_expiration > Positive::ZERO);
assert!(!seen.contains(&chain.expires_at));
seen.push(chain.expires_at);
}
}
#[test]
fn test_an_expired_chain_is_never_emitted() {
let parameters = parameters(request(20, reference_schedules()));
let tape = tape(¶meters);
for step in 0..tape.len() {
let snapshot = snapshot(¶meters, &tape, step);
for chain in &snapshot.chains {
assert!(
chain.expires_at > snapshot.simulated_at,
"step {step} emitted an expired chain"
);
}
}
}
#[test]
fn test_crossing_the_cutoff_replaces_the_expiry_without_a_gap() {
let mut crossing = request(4, vec![rule("zero_dte", ExpiryRuleKind::Daily, 1)]);
crossing.step_interval_seconds = Some(43_200);
let parameters = parameters(crossing);
let tape = tape(¶meters);
let mut expirations = Vec::new();
for step in 0..tape.len() {
let snapshot = snapshot(¶meters, &tape, step);
assert_eq!(
snapshot.chains.len(),
1,
"step {step} must always carry exactly one 0DTE"
);
match snapshot.chains.first() {
Some(chain) => expirations.push(chain.expires_at),
None => panic!("step {step} lost its rolling expiration"),
}
}
let distinct: std::collections::BTreeSet<DateTime<Utc>> =
expirations.iter().copied().collect();
assert!(
distinct.len() > 1,
"the 0DTE must roll at least once over two simulated days"
);
}
#[test]
fn test_a_long_horizon_builds_every_step() {
let parameters = parameters(request(90, reference_schedules()));
let tape = tape(¶meters);
for step in [0, 1, 29, 45, 60, 89] {
let snapshot = snapshot(¶meters, &tape, step);
assert_eq!(snapshot.chains_for("monthlies").count(), 12);
assert_eq!(snapshot.chains_for("weeklies").count(), 3);
}
}
#[test]
fn test_a_step_past_the_tape_is_not_found() {
let parameters = parameters(request(3, reference_schedules()));
let tape = tape(¶meters);
match builder(¶meters, &tape).snapshot(3) {
Err(ChainError::NotFound(message)) => assert!(message.contains("past the end")),
other => panic!("expected NotFound, got {other:?}"),
}
}
#[test]
fn test_higher_base_volatility_raises_the_atm_premium() {
let low = parameters(request(1, vec![rule("zero_dte", ExpiryRuleKind::Daily, 1)]));
let mut high_request = request(1, vec![rule("zero_dte", ExpiryRuleKind::Daily, 1)]);
high_request.volatility = 0.40;
high_request.method = ApiWalkType::Brownian {
dt: 1.0 / 252.0,
drift: 0.0,
volatility: 0.40,
};
let high = parameters(high_request);
let low_tape = tape(&low);
let high_tape = tape(&high);
let low_snapshot = snapshot(&low, &low_tape, 0);
let high_snapshot = snapshot(&high, &high_tape, 0);
let low_chain = match low_snapshot.chains.first() {
Some(chain) => chain,
None => panic!("the snapshot must carry a chain"),
};
let high_chain = match high_snapshot.chains.first() {
Some(chain) => chain,
None => panic!("the snapshot must carry a chain"),
};
assert_eq!(
low_chain.expires_at, high_chain.expires_at,
"the comparison is only meaningful at the same expiration"
);
assert!(
atm_call_mid(high_chain) > atm_call_mid(low_chain),
"a higher base volatility must raise the ATM premium"
);
}
#[test]
fn test_a_nearer_expiration_carries_less_extrinsic_value() {
let parameters = parameters(request(1, reference_schedules()));
let tape = tape(¶meters);
let snapshot = snapshot(¶meters, &tape, 0);
let nearest = match snapshot.chains.first() {
Some(chain) => chain,
None => panic!("the snapshot must carry chains"),
};
let farthest = match snapshot.chains.last() {
Some(chain) => chain,
None => panic!("the snapshot must carry chains"),
};
assert!(nearest.days_to_expiration < farthest.days_to_expiration);
assert!(
atm_call_mid(nearest) < atm_call_mid(farthest),
"the nearer expiration must carry less extrinsic value"
);
}
#[test]
fn test_skew_and_smile_shape_the_per_strike_volatility() {
let mut shaped = request(1, vec![rule("zero_dte", ExpiryRuleKind::Daily, 1)]);
shaped.chain_size = Some(9);
shaped.skew_slope = Some(-0.3);
shaped.smile_curve = Some(0.5);
let parameters = parameters(shaped);
let tape = tape(¶meters);
let snapshot = snapshot(¶meters, &tape, 0);
let chain = match snapshot.chains.first() {
Some(chain) => chain,
None => panic!("the snapshot must carry a chain"),
};
let volatilities: Vec<Positive> = chain
.chain
.iter()
.map(|data| data.implied_volatility)
.collect();
assert!(
volatilities.len() > 2,
"the ladder must expose per-strike volatilities"
);
let distinct: std::collections::BTreeSet<String> =
volatilities.iter().map(ToString::to_string).collect();
assert!(
distinct.len() > 1,
"skew and smile must vary the volatility across strikes"
);
}
#[test]
fn test_rebuilding_a_step_yields_an_identical_snapshot() {
let parameters = parameters(request(4, reference_schedules()));
let tape = tape(¶meters);
let builder = builder(¶meters, &tape);
match (builder.snapshot(2), builder.snapshot(2)) {
(Ok(first), Ok(second)) => assert_eq!(first, second),
(first, second) => panic!("both builds must succeed: {first:?} {second:?}"),
}
}
#[test]
fn test_same_seed_produces_an_identical_snapshot_tape() {
let first_parameters = parameters(request(6, reference_schedules()));
let second_parameters = parameters(request(6, reference_schedules()));
let first_tape = tape(&first_parameters);
let second_tape = tape(&second_parameters);
for step in 0..first_tape.len() {
assert_eq!(
snapshot(&first_parameters, &first_tape, step),
snapshot(&second_parameters, &second_tape, step),
"step {step} diverged under the same seed"
);
}
}
#[test]
fn test_a_different_seed_produces_a_different_snapshot_tape() {
let baseline = parameters(request(6, reference_schedules()));
let mut other_request = request(6, reference_schedules());
other_request.seed = Some(43);
let other = parameters(other_request);
let baseline_tape = tape(&baseline);
let other_tape = tape(&other);
let baseline_last = snapshot(&baseline, &baseline_tape, 5);
let other_last = snapshot(&other, &other_tape, 5);
assert_ne!(baseline_last, other_last);
let baseline_expiries: Vec<DateTime<Utc>> =
baseline_last.chains.iter().map(|c| c.expires_at).collect();
let other_expiries: Vec<DateTime<Utc>> =
other_last.chains.iter().map(|c| c.expires_at).collect();
assert_eq!(baseline_expiries, other_expiries);
assert_ne!(baseline_last.spot, other_last.spot);
}
#[test]
fn test_a_chain_can_be_found_by_its_expiration() {
let parameters = parameters(request(1, reference_schedules()));
let tape = tape(¶meters);
let snapshot = snapshot(¶meters, &tape, 0);
let target = match snapshot.chains.first() {
Some(chain) => chain.expires_at,
None => panic!("the snapshot must carry chains"),
};
match snapshot.chain_at(target) {
Some(found) => assert_eq!(found.expires_at, target),
None => panic!("the chain must be findable by its expiration"),
}
assert!(
snapshot
.chain_at(target - chrono::Duration::days(3650))
.is_none()
);
}
fn entry_bounded_cache(capacity: usize) -> SnapshotCache {
SnapshotCache::with_bounds(capacity, usize::MAX)
}
fn cached_snapshot(step: usize) -> SeriesSnapshot {
let simulated_at = match Utc.with_ymd_and_hms(2026, 1, 5, 14, 30, 0).single() {
Some(instant) => instant,
None => panic!("the test instant must be valid"),
};
SeriesSnapshot {
step,
simulated_at,
spot: Positive::ONE,
base_volatility: Positive::ONE,
chains: Vec::new(),
}
}
#[test]
fn test_the_cache_returns_what_it_stored() {
let mut cache = entry_bounded_cache(4);
let simulation = Uuid::new_v4();
assert!(cache.is_empty());
cache.insert(simulation, cached_snapshot(0));
assert_eq!(cache.len(), 1);
match cache.get(simulation, 0) {
Some(snapshot) => assert_eq!(snapshot.step, 0),
None => panic!("the cache must return what it stored"),
}
assert!(cache.get(simulation, 1).is_none());
}
#[test]
fn test_the_cache_evicts_the_least_recently_accessed_entry() {
let mut cache = entry_bounded_cache(2);
let simulation = Uuid::new_v4();
cache.insert(simulation, cached_snapshot(0));
cache.insert(simulation, cached_snapshot(1));
assert!(cache.get(simulation, 0).is_some());
cache.insert(simulation, cached_snapshot(2));
assert_eq!(cache.len(), 2, "the cache must respect its bound");
assert!(
cache.get(simulation, 0).is_some(),
"the touched entry stays"
);
assert!(cache.get(simulation, 1).is_none(), "the idle entry goes");
assert!(cache.get(simulation, 2).is_some());
}
#[test]
fn test_the_cache_evicts_on_the_contract_budget() {
let parameters = test_parameters();
let tape = test_tape(¶meters);
let priced = |step: usize| snapshot(¶meters, &tape, step);
let first = priced(0);
let second = priced(1);
let budget = snapshot_contracts(&first) + snapshot_contracts(&second);
assert!(budget > 0, "the fixture must price something");
let mut cache = SnapshotCache::with_bounds(10, budget);
let simulation = Uuid::new_v4();
cache.insert(simulation, first);
cache.insert(simulation, second);
assert_eq!(cache.len(), 2, "both fit within the budget");
let other = Uuid::new_v4();
cache.insert(other, priced(0));
assert!(
cache.len() < 3,
"the entry bound of ten cannot be what stopped it"
);
assert!(
cache.contracts() <= budget,
"the cache holds {} contracts, above its budget of {budget}",
cache.contracts()
);
assert!(cache.get(other, 0).is_some(), "the newest stayed");
}
#[test]
fn test_a_snapshot_larger_than_the_budget_is_not_cached() {
let parameters = test_parameters();
let tape = test_tape(¶meters);
let weight = snapshot_contracts(&snapshot(¶meters, &tape, 0));
let mut cache = SnapshotCache::with_bounds(10, weight / 2);
let simulation = Uuid::new_v4();
let other = Uuid::new_v4();
cache.insert(other, snapshot(¶meters, &tape, 1));
let resident = cache.len();
cache.insert(simulation, snapshot(¶meters, &tape, 0));
assert!(
cache.get(simulation, 0).is_none(),
"an entry that cannot fit the budget must not be admitted"
);
assert_eq!(
cache.len(),
resident,
"and it must not evict the entries that do fit"
);
assert!(
cache.contracts() <= weight / 2,
"the cache holds {} contracts, above its budget",
cache.contracts()
);
}
#[test]
fn test_a_simulations_entries_can_be_evicted_together() {
let mut cache = entry_bounded_cache(8);
let first = Uuid::new_v4();
let second = Uuid::new_v4();
cache.insert(first, cached_snapshot(0));
cache.insert(first, cached_snapshot(1));
cache.insert(second, cached_snapshot(0));
assert_eq!(cache.evict_simulation(first), 2);
assert_eq!(cache.len(), 1);
assert!(cache.get(first, 0).is_none());
assert!(cache.get(second, 0).is_some());
assert_eq!(cache.evict_simulation(first), 0);
}
#[test]
fn test_reinserting_a_key_replaces_the_entry() {
let mut cache = entry_bounded_cache(4);
let simulation = Uuid::new_v4();
cache.insert(simulation, cached_snapshot(7));
cache.insert(simulation, cached_snapshot(7));
assert_eq!(cache.len(), 1);
}
#[test]
fn test_a_zero_capacity_is_raised_to_one() {
let mut cache = entry_bounded_cache(0);
let simulation = Uuid::new_v4();
assert_eq!(cache.capacity(), 1);
cache.insert(simulation, cached_snapshot(0));
assert_eq!(cache.len(), 1);
}
#[test]
fn test_eviction_is_unobservable() {
let parameters = parameters(request(3, reference_schedules()));
let tape = tape(¶meters);
let simulation = Uuid::new_v4();
let mut cache = entry_bounded_cache(1);
let original = snapshot(¶meters, &tape, 1);
cache.insert(simulation, original.clone());
cache.insert(simulation, snapshot(¶meters, &tape, 2));
assert!(cache.get(simulation, 1).is_none());
assert_eq!(snapshot(¶meters, &tape, 1), original);
}
}
#[cfg(test)]
mod reproducibility_tests {
use super::tests::*;
use super::*;
fn surfaced_content(snapshot: &SeriesSnapshot) -> String {
let mut rendered = format!(
"step={} at={} spot={} iv={}",
snapshot.step, snapshot.simulated_at, snapshot.spot, snapshot.base_volatility
);
for chain in &snapshot.chains {
rendered.push_str(&format!(
"\n expires={} dte={} labels={:?}",
chain.expires_at, chain.days_to_expiration, chain.labels
));
for data in chain.chain.iter() {
rendered.push_str(&format!(
"\n k={} iv={} cb={:?} ca={:?} cm={:?} pb={:?} pa={:?} pm={:?} dc={:?} dp={:?} g={:?}",
data.strike_price,
data.implied_volatility,
data.call_bid,
data.call_ask,
data.call_middle,
data.put_bid,
data.put_ask,
data.put_middle,
data.delta_call,
data.delta_put,
data.gamma,
));
}
}
rendered
}
#[test]
fn test_adding_a_rule_leaves_the_other_chains_untouched() {
let baseline = parameters_with_rules(vec![monthly_rule()]);
let extended = parameters_with_rules(vec![monthly_rule(), zero_dte_rule()]);
let baseline_tape = test_tape(&baseline);
let extended_tape = test_tape(&extended);
let baseline_snapshot = match builder(&baseline, &baseline_tape).snapshot(1) {
Ok(snapshot) => snapshot,
Err(error) => panic!("the baseline snapshot must build: {error}"),
};
let extended_snapshot = match builder(&extended, &extended_tape).snapshot(1) {
Ok(snapshot) => snapshot,
Err(error) => panic!("the extended snapshot must build: {error}"),
};
assert_eq!(
baseline_snapshot.spot, extended_snapshot.spot,
"the price path must not depend on the schedule"
);
let monthlies = |snapshot: &SeriesSnapshot| -> Vec<ExpiryChain> {
snapshot
.chains
.iter()
.filter(|chain| chain.labels.iter().any(|label| label == "monthlies"))
.cloned()
.collect()
};
let before = monthlies(&baseline_snapshot);
let after = monthlies(&extended_snapshot);
assert!(
!before.is_empty(),
"the baseline must price a monthly chain"
);
assert_eq!(
before, after,
"adding a 0DTE rule must not change the monthly chains"
);
}
#[test]
fn test_same_seed_reproduces_every_surfaced_value() {
let first = test_parameters();
let second = test_parameters();
let first_tape = test_tape(&first);
let second_tape = test_tape(&second);
for step in 0..first_tape.len() {
let left = match builder(&first, &first_tape).snapshot(step) {
Ok(snapshot) => snapshot,
Err(error) => panic!("the snapshot must build: {error}"),
};
let right = match builder(&second, &second_tape).snapshot(step) {
Ok(snapshot) => snapshot,
Err(error) => panic!("the snapshot must build: {error}"),
};
assert_eq!(
surfaced_content(&left),
surfaced_content(&right),
"step {step} diverged under the same seed"
);
}
}
#[test]
fn test_rebuilding_reproduces_every_surfaced_value() {
let parameters = test_parameters();
let tape = test_tape(¶meters);
let builder = builder(¶meters, &tape);
match (builder.snapshot(1), builder.snapshot(1)) {
(Ok(first), Ok(second)) => {
assert_eq!(surfaced_content(&first), surfaced_content(&second));
}
(first, second) => panic!("both builds must succeed: {first:?} {second:?}"),
}
}
#[test]
fn test_a_different_seed_changes_the_surfaced_market_state() {
let baseline = test_parameters();
let other = test_parameters_with_seed(43);
let baseline_tape = test_tape(&baseline);
let other_tape = test_tape(&other);
let left = match builder(&baseline, &baseline_tape).snapshot(1) {
Ok(snapshot) => snapshot,
Err(error) => panic!("the snapshot must build: {error}"),
};
let right = match builder(&other, &other_tape).snapshot(1) {
Ok(snapshot) => snapshot,
Err(error) => panic!("the snapshot must build: {error}"),
};
assert_ne!(surfaced_content(&left), surfaced_content(&right));
let left_expiries: Vec<DateTime<Utc>> = left.chains.iter().map(|c| c.expires_at).collect();
let right_expiries: Vec<DateTime<Utc>> =
right.chains.iter().map(|c| c.expires_at).collect();
assert_eq!(
left_expiries, right_expiries,
"expirations come from the schedule, not the seed"
);
}
}