use crate::simulation::WalkParams;
use crate::simulation::randomwalk::RandomWalk;
use crate::simulation::steps::Step;
use optionstratlib_core::model::Options;
use optionstratlib_core::model::Positive;
use optionstratlib_core::utils::{Len, deterministic_rng};
use optionstratlib_pricing::error::PricingError;
use optionstratlib_pricing::pricing::Profit;
use optionstratlib_pricing::pricing::monte_carlo::price_option_monte_carlo;
use optionstratlib_pricing::pricing::unified::MonteCarloPricer;
use rand::RngExt;
use rayon::prelude::*;
use rust_decimal::Decimal;
use std::fmt::Display;
use std::ops::{AddAssign, Index, IndexMut};
use tracing::warn;
pub const PARALLEL_MIN_WALKS: usize = 3;
const PARALLEL_ROUND_WALKS: usize = 1024;
#[derive(Debug, Clone)]
pub struct Simulator<X, Y>
where
X: Copy + TryInto<Positive> + AddAssign + Display,
Y: TryInto<Positive> + Display + Clone,
{
title: String,
random_walks: Vec<RandomWalk<X, Y>>,
}
impl<X, Y> Simulator<X, Y>
where
X: Copy + TryInto<Positive> + AddAssign + Display,
Y: TryInto<Positive> + Display + Clone,
{
pub fn new<F, E>(
title: String,
size: usize,
params: &WalkParams<X, Y>,
generator: F,
) -> Result<Self, E>
where
F: Fn(&WalkParams<X, Y>) -> Result<Vec<Step<X, Y>>, E> + Clone + Send + Sync,
E: Send,
X: Copy + TryInto<Positive> + AddAssign + Display + Send + Sync,
Y: TryInto<Positive> + Display + Clone + Send + Sync,
{
let mut random_walks = Vec::new();
if let Err(e) = random_walks.try_reserve_exact(size) {
warn!(
size,
error = %e,
"Simulator::new: cannot reserve the walks up front; growing on demand"
);
}
let mut seeds = params.seed.map(deterministic_rng);
if size < PARALLEL_MIN_WALKS {
let mut walk_params = params.clone();
for i in 0..size {
if let Some(seeds) = seeds.as_mut() {
walk_params.seed = Some(seeds.random::<u64>());
}
let walk_title = format!("{title}_{i}");
random_walks.push(RandomWalk::new(
walk_title,
&walk_params,
generator.clone(),
)?);
}
} else {
let mut indices = 0..size;
loop {
let jobs: Vec<(usize, Option<u64>)> = indices
.by_ref()
.take(PARALLEL_ROUND_WALKS)
.map(|i| (i, seeds.as_mut().map(|seeds| seeds.random::<u64>())))
.collect();
if jobs.is_empty() {
break;
}
let built: Vec<Result<RandomWalk<X, Y>, E>> = jobs
.into_par_iter()
.map_init(
|| params.clone(),
|walk_params, (i, seed)| {
if seed.is_some() {
walk_params.seed = seed;
}
RandomWalk::new(format!("{title}_{i}"), walk_params, generator.clone())
},
)
.collect();
for walk in built {
random_walks.push(walk?);
}
}
}
Ok(Self {
title,
random_walks,
})
}
#[must_use]
pub fn get_title(&self) -> &str {
&self.title
}
pub fn set_title(&mut self, title: String) {
self.title = title;
}
#[must_use]
pub fn get_random_walks(&self) -> Vec<&RandomWalk<X, Y>> {
self.random_walks.iter().collect::<Vec<&RandomWalk<X, Y>>>()
}
#[must_use]
pub fn get_random_walk(&self, index: usize) -> Option<&RandomWalk<X, Y>> {
self.random_walks.get(index)
}
pub fn get_random_walk_mut(&mut self, index: usize) -> Option<&mut RandomWalk<X, Y>> {
self.random_walks.get_mut(index)
}
#[must_use]
pub fn first(&self) -> Option<&RandomWalk<X, Y>> {
self.random_walks.first()
}
#[must_use]
pub fn last(&self) -> Option<&RandomWalk<X, Y>> {
self.random_walks.last()
}
#[must_use]
pub fn get_steps(&self) -> Vec<Vec<&Step<X, Y>>> {
self.into_iter().map(|step| step.get_steps()).collect()
}
#[must_use]
pub fn get_last_steps(&self) -> Vec<&Step<X, Y>> {
self.into_iter().filter_map(|step| step.last()).collect()
}
#[must_use]
pub fn get_last_values(&self) -> Vec<&Step<X, Y>> {
self.into_iter().filter_map(|step| step.last()).collect()
}
#[must_use]
pub fn get_last_positive_values(&self) -> Vec<Positive> {
let last_values = self.get_last_values();
last_values
.iter()
.filter_map(|step| step.get_positive_value().ok())
.collect::<Vec<Positive>>()
}
pub fn get_mc_option_price(&self, option: &Options) -> Result<Positive, PricingError> {
let last_values = self.get_last_positive_values();
price_option_monte_carlo(option, &last_values)
}
}
impl MonteCarloPricer for Simulator<Positive, Positive> {
#[inline]
fn price_monte_carlo(&self, option: &Options) -> Result<Positive, PricingError> {
self.get_mc_option_price(option)
.map_err(|e| PricingError::simulation_error(&e.to_string()))
}
}
impl<X, Y> Len for Simulator<X, Y>
where
X: Copy + TryInto<Positive> + AddAssign + Display,
Y: TryInto<Positive> + Display + Clone,
{
fn len(&self) -> usize {
self.random_walks.len()
}
fn is_empty(&self) -> bool {
self.random_walks.is_empty()
}
}
impl<X, Y> Index<usize> for Simulator<X, Y>
where
X: Copy + TryInto<Positive> + AddAssign + Display,
Y: TryInto<Positive> + Display + Clone,
{
type Output = RandomWalk<X, Y>;
#[allow(clippy::indexing_slicing)]
fn index(&self, index: usize) -> &Self::Output {
&self.random_walks[index]
}
}
impl<X, Y> IndexMut<usize> for Simulator<X, Y>
where
X: Copy + TryInto<Positive> + AddAssign + Display,
Y: TryInto<Positive> + Display + Clone,
{
#[allow(clippy::indexing_slicing)]
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
&mut self.random_walks[index]
}
}
impl<X, Y> Display for Simulator<X, Y>
where
X: Copy + TryInto<Positive> + AddAssign + Display,
Y: TryInto<Positive> + Display + Clone,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "{}", self.title)?;
for random_walk in &self.random_walks {
writeln!(f, "\t{random_walk}")?;
}
Ok(())
}
}
impl<X, Y> Profit for Simulator<X, Y>
where
X: AddAssign + Copy + Display + TryInto<Positive>,
Y: Clone + Display + TryInto<Positive>,
{
fn calculate_profit_at(&self, _price: &Positive) -> Result<Decimal, PricingError> {
Err(PricingError::other(
"Profit calculation not implemented for Simulator",
))
}
}
impl<'a, X, Y> IntoIterator for &'a Simulator<X, Y>
where
X: Copy + TryInto<Positive> + AddAssign + Display,
Y: TryInto<Positive> + Display + Clone,
{
type Item = &'a RandomWalk<X, Y>;
type IntoIter = std::slice::Iter<'a, RandomWalk<X, Y>>;
fn into_iter(self) -> Self::IntoIter {
self.random_walks.iter()
}
}
#[cfg(test)]
#[allow(irrefutable_let_patterns)]
mod tests {
use super::*;
use crate::error::SimulationError;
use crate::simulation::generator_positive;
use crate::simulation::{
WalkParams, WalkType, WalkTypeAble,
steps::{Step, Xstep, Ystep},
};
use optionstratlib_core::model::ExpirationDate;
use optionstratlib_core::pos_or_panic;
use optionstratlib_core::utils::{TimeFrame, time::convert_time_frame};
use optionstratlib_pricing::pricing::OptionPricing;
use rust_decimal_macros::dec;
use std::convert::Infallible;
use tracing::{debug, info};
const SEED: u64 = 685;
#[derive(Clone)]
struct TestWalker;
impl TestWalker {
fn new() -> Self {
TestWalker {}
}
}
impl WalkTypeAble<Positive, Positive> for TestWalker {}
fn test_generator(
params: &WalkParams<Positive, Positive>,
) -> Result<Vec<Step<Positive, Positive>>, Infallible> {
Ok(vec![params.init_step.clone()])
}
#[test]
fn test_monte_carlo_pricer_matches_the_simulator_on_the_same_paths() {
use optionstratlib_core::model::types::{OptionStyle, OptionType, Side};
use optionstratlib_pricing::pricing::{GenericPricingEngine, price_option_with};
let prices: Vec<Positive> = (0..12)
.map(|i| pos_or_panic!(100.0 + f64::from(i) * 1.5))
.collect();
let params = WalkParams {
size: prices.len(),
init_step: Step::new(
Positive::ONE,
TimeFrame::Day,
ExpirationDate::Days(pos_or_panic!(30.0)),
Positive::HUNDRED,
),
walker: Box::new(TestWalker),
walk_type: WalkType::Historical {
timeframe: TimeFrame::Day,
prices,
symbol: None,
},
seed: None,
};
let simulator =
Simulator::new("MC parity".to_string(), 8, ¶ms, generator_positive).unwrap();
let option = Options {
option_type: OptionType::European,
side: Side::Long,
underlying_symbol: "TEST".to_string(),
strike_price: Positive::HUNDRED,
expiration_date: ExpirationDate::Days(pos_or_panic!(30.0)),
implied_volatility: pos_or_panic!(0.2),
quantity: Positive::ONE,
contract_size: Positive::ONE,
underlying_price: Positive::HUNDRED,
risk_free_rate: dec!(0.05),
option_style: OptionStyle::Call,
dividend_yield: Positive::ZERO,
exotic_params: None,
};
let expected = simulator.get_mc_option_price(&option).unwrap();
assert!(expected > Positive::ZERO);
assert_eq!(expected, simulator.price_monte_carlo(&option).unwrap());
let generic = GenericPricingEngine::MonteCarlo {
simulator: simulator.clone(),
};
assert_eq!(price_option_with(&option, &generic).unwrap(), expected);
let borrowed = GenericPricingEngine::MonteCarlo {
simulator: &simulator,
};
assert_eq!(price_option_with(&option, &borrowed).unwrap(), expected);
}
#[test]
fn test_monte_carlo_price_from_simulator_matches_black_scholes()
-> Result<(), Box<dyn std::error::Error>> {
use optionstratlib_core::assert_pos_relative_eq;
use optionstratlib_core::model::utils::create_sample_option;
use optionstratlib_core::model::{OptionStyle, Side};
use optionstratlib_pricing::pricing::monte_carlo::price_option_monte_carlo;
let walker = Box::new(TestWalker);
let initial_price = pos_or_panic!(1000.0);
let days = pos_or_panic!(365.0);
let volatility = pos_or_panic!(0.2);
let mut option = create_sample_option(
OptionStyle::Call,
Side::Long,
initial_price,
Positive::ONE,
initial_price,
volatility,
);
option.risk_free_rate = dec!(0.05);
option.dividend_yield = pos_or_panic!(0.02);
option.expiration_date = ExpirationDate::Days(days);
let init_step = Step {
x: Xstep::new(Positive::ONE, TimeFrame::Day, ExpirationDate::Days(days)),
y: Ystep::new(0, initial_price),
};
let dt = convert_time_frame(Positive::ONE, &TimeFrame::Day, &TimeFrame::Year)?;
let walk_params = WalkParams {
size: 365,
init_step,
walk_type: WalkType::Custom {
dt,
drift: dec!(0.02),
volatility,
vov: pos_or_panic!(0.01),
vol_speed: Default::default(),
vol_mean: pos_or_panic!(0.2),
},
walker,
seed: Some(SEED),
};
let Ok(simulator) = Simulator::new(
"Test Simulator".to_string(),
100,
&walk_params,
generator_positive,
) else {
panic!("simulator setup failed");
};
let get_last_positive_values = simulator.get_last_positive_values();
let result = price_option_monte_carlo(&option, &get_last_positive_values);
assert!(result.is_ok());
let bs = option.calculate_price_black_scholes().unwrap();
assert_pos_relative_eq!(
result.unwrap(),
Positive::new_decimal(bs).unwrap(),
pos_or_panic!(10.0)
);
Ok(())
}
#[test]
fn test_simulator_creation() -> Result<(), Box<dyn std::error::Error>> {
let walker = Box::new(TestWalker);
let initial_price = Positive::HUNDRED;
let init_step = Step {
x: Xstep::new(
Positive::ONE,
TimeFrame::Minute,
ExpirationDate::Days(pos_or_panic!(30.0)),
),
y: Ystep::new(0, initial_price),
};
let walk_params = WalkParams {
size: 5,
init_step,
walk_type: WalkType::GeometricBrownian {
dt: convert_time_frame(
Positive::ONE / pos_or_panic!(30.0),
&TimeFrame::Minute,
&TimeFrame::Day,
)?,
drift: dec!(0.0),
volatility: pos_or_panic!(0.2),
},
walker,
seed: Some(SEED),
};
let Ok(simulator) = Simulator::new(
"Test Simulator".to_string(),
5,
&walk_params,
test_generator,
) else {
unreachable!()
};
assert_eq!(simulator.get_title(), "Test Simulator");
assert_eq!(simulator.len(), 5);
assert!(!simulator.is_empty());
Ok(())
}
#[test]
fn test_simulator_title_methods() -> Result<(), Box<dyn std::error::Error>> {
let walker = Box::new(TestWalker);
let initial_price = Positive::HUNDRED;
let init_step = Step {
x: Xstep::new(
Positive::ONE,
TimeFrame::Minute,
ExpirationDate::Days(pos_or_panic!(30.0)),
),
y: Ystep::new(0, initial_price),
};
let walk_params = WalkParams {
size: 3,
init_step,
walk_type: WalkType::GeometricBrownian {
dt: convert_time_frame(
Positive::ONE / pos_or_panic!(30.0),
&TimeFrame::Minute,
&TimeFrame::Day,
)?,
drift: dec!(0.0),
volatility: pos_or_panic!(0.2),
},
walker,
seed: Some(SEED),
};
let Ok(mut simulator) = Simulator::new(
"Original Title".to_string(),
3,
&walk_params,
test_generator,
) else {
unreachable!()
};
assert_eq!(simulator.get_title(), "Original Title");
simulator.set_title("New Title".to_string());
assert_eq!(simulator.get_title(), "New Title");
Ok(())
}
#[test]
fn test_simulator_step_access() -> Result<(), Box<dyn std::error::Error>> {
let walker = Box::new(TestWalker);
let initial_price = Positive::HUNDRED;
let init_step = Step {
x: Xstep::new(
Positive::ONE,
TimeFrame::Minute,
ExpirationDate::Days(pos_or_panic!(30.0)),
),
y: Ystep::new(0, initial_price),
};
let walk_params = WalkParams {
size: 3,
init_step,
walk_type: WalkType::GeometricBrownian {
dt: convert_time_frame(
Positive::ONE / pos_or_panic!(30.0),
&TimeFrame::Minute,
&TimeFrame::Day,
)?,
drift: dec!(0.0),
volatility: pos_or_panic!(0.2),
},
walker,
seed: Some(SEED),
};
let Ok(simulator) = Simulator::new(
"Test Simulator".to_string(),
3,
&walk_params,
test_generator,
) else {
unreachable!()
};
let steps = simulator.get_random_walks();
assert_eq!(steps.len(), 3);
let step = simulator.get_random_walk(1).unwrap();
assert_eq!(step.get_title(), "Test Simulator_1");
assert!(simulator.get_random_walk(99).is_none());
assert!(simulator.first().is_some());
assert!(simulator.last().is_some());
assert_eq!(
simulator.first().expect("should be Ok").get_title(),
"Test Simulator_0"
);
assert_eq!(
simulator.last().expect("should be Ok").get_title(),
"Test Simulator_2"
);
Ok(())
}
#[test]
fn test_simulator_indexing() -> Result<(), Box<dyn std::error::Error>> {
let walker = Box::new(TestWalker);
let initial_price = Positive::HUNDRED;
let init_step = Step {
x: Xstep::new(
Positive::ONE,
TimeFrame::Minute,
ExpirationDate::Days(pos_or_panic!(30.0)),
),
y: Ystep::new(0, initial_price),
};
let walk_params = WalkParams {
size: 3,
init_step,
walk_type: WalkType::GeometricBrownian {
dt: convert_time_frame(
Positive::ONE / pos_or_panic!(30.0),
&TimeFrame::Minute,
&TimeFrame::Day,
)?,
drift: dec!(0.0),
volatility: pos_or_panic!(0.2),
},
walker,
seed: Some(SEED),
};
let Ok(mut simulator) = Simulator::new(
"Test Simulator".to_string(),
3,
&walk_params,
test_generator,
) else {
unreachable!()
};
assert_eq!(simulator[0].get_title(), "Test Simulator_0");
assert_eq!(simulator[1].get_title(), "Test Simulator_1");
assert_eq!(simulator[2].get_title(), "Test Simulator_2");
simulator[1].set_title("Modified Title".to_string());
assert_eq!(simulator[1].get_title(), "Modified Title");
Ok(())
}
#[test]
fn test_simulator_display() -> Result<(), Box<dyn std::error::Error>> {
let walker = Box::new(TestWalker);
let initial_price = Positive::HUNDRED;
let init_step = Step {
x: Xstep::new(
Positive::ONE,
TimeFrame::Minute,
ExpirationDate::Days(pos_or_panic!(30.0)),
),
y: Ystep::new(0, initial_price),
};
let walk_params = WalkParams {
size: 2,
init_step,
walk_type: WalkType::GeometricBrownian {
dt: convert_time_frame(
Positive::ONE / pos_or_panic!(30.0),
&TimeFrame::Minute,
&TimeFrame::Day,
)?,
drift: dec!(0.0),
volatility: pos_or_panic!(0.2),
},
walker,
seed: Some(SEED),
};
let Ok(simulator) =
Simulator::new("Display Test".to_string(), 2, &walk_params, test_generator)
else {
unreachable!()
};
let display_output = format!("{simulator}");
assert!(display_output.starts_with("Display Test"));
assert!(display_output.contains("Display Test_0"));
assert!(display_output.contains("Display Test_1"));
Ok(())
}
#[test]
fn test_simulator_empty() {
let simulator: Simulator<Positive, Positive> = Simulator {
title: "Empty Simulator".to_string(),
random_walks: Vec::new(),
};
assert_eq!(simulator.get_title(), "Empty Simulator");
assert_eq!(simulator.len(), 0);
assert!(simulator.is_empty());
assert!(simulator.first().is_none());
assert!(simulator.last().is_none());
}
#[test]
fn test_simulator_new_propagates_generator_error_short_circuits()
-> Result<(), Box<dyn std::error::Error>> {
use std::sync::atomic::{AtomicU32, Ordering};
let walker = Box::new(TestWalker);
let initial_price = Positive::HUNDRED;
let init_step = Step {
x: Xstep::new(
Positive::ONE,
TimeFrame::Minute,
ExpirationDate::Days(pos_or_panic!(30.0)),
),
y: Ystep::new(0, initial_price),
};
let walk_params = WalkParams {
size: 1,
init_step,
walk_type: WalkType::GeometricBrownian {
dt: convert_time_frame(
Positive::ONE / pos_or_panic!(30.0),
&TimeFrame::Minute,
&TimeFrame::Day,
)?,
drift: dec!(0.0),
volatility: pos_or_panic!(0.2),
},
walker,
seed: Some(SEED),
};
let calls = AtomicU32::new(0);
let size = PARALLEL_MIN_WALKS - 1;
let result: Result<Simulator<Positive, Positive>, &'static str> =
Simulator::new("Err Sim".to_string(), size, &walk_params, |p| {
let n = calls.fetch_add(1, Ordering::SeqCst);
if n == 0 {
Err("boom")
} else {
Ok(vec![p.init_step.clone()])
}
});
match result {
Err(msg) => assert_eq!(msg, "boom"),
Ok(_) => panic!("expected generator error to propagate"),
}
assert_eq!(calls.load(Ordering::SeqCst), 1);
Ok(())
}
#[test]
#[should_panic(expected = "index out of bounds")]
fn test_simulator_index_out_of_bounds() {
let walker = Box::new(TestWalker);
let initial_price = Positive::HUNDRED;
let init_step = Step {
x: Xstep::new(
Positive::ONE,
TimeFrame::Minute,
ExpirationDate::Days(pos_or_panic!(30.0)),
),
y: Ystep::new(0, initial_price),
};
let walk_params = WalkParams {
size: 3,
init_step,
walk_type: WalkType::GeometricBrownian {
dt: match convert_time_frame(
Positive::ONE / pos_or_panic!(30.0),
&TimeFrame::Minute,
&TimeFrame::Day,
) {
Ok(value) => value,
Err(error) => panic!("convert_time_frame failed: {error}"),
},
drift: dec!(0.0),
volatility: pos_or_panic!(0.2),
},
walker,
seed: Some(SEED),
};
let Ok(simulator) =
Simulator::new("Panic Test".to_string(), 3, &walk_params, test_generator)
else {
unreachable!()
};
let _ = simulator[3];
}
#[test]
fn test_full_simulation() -> Result<(), SimulationError> {
let simulator_size: usize = 5;
let n_steps = 10;
let initial_price = Positive::HUNDRED;
let std_dev = pos_or_panic!(20.0);
let walker = Box::new(TestWalker::new());
let days = Positive::TWO;
let walk_params = WalkParams {
size: n_steps,
init_step: Step {
x: Xstep::new(Positive::ONE, TimeFrame::Hour, ExpirationDate::Days(days)),
y: Ystep::new(0, initial_price),
},
walk_type: WalkType::GeometricBrownian {
dt: convert_time_frame(Positive::ONE / days, &TimeFrame::Hour, &TimeFrame::Day)?,
drift: dec!(0.0),
volatility: std_dev,
},
walker,
seed: Some(SEED),
};
assert_eq!(walk_params.size, n_steps);
assert_eq!(walk_params.init_step.get_value(), &Positive::HUNDRED);
assert_eq!(walk_params.y(), &Positive::HUNDRED);
let simulator = Simulator::new(
"Simulator".to_string(),
simulator_size,
&walk_params,
generator_positive,
)?;
debug!("Simulator: {}", simulator);
assert_eq!(simulator.get_title(), "Simulator");
assert_eq!(simulator.len(), simulator_size);
let random_walk = simulator[0].clone();
assert_eq!(random_walk.get_title(), "Simulator_0");
assert_eq!(random_walk.len(), n_steps);
let step = random_walk[0].clone();
assert_eq!(*step.get_index(), Positive::ONE);
let step_string = format!("{step}");
assert_eq!(step.to_string(), step_string);
let y_step = step.get_y_step();
assert_eq!(*y_step.index(), 0);
assert_eq!(*y_step.value(), Positive::HUNDRED);
let x_step = step.get_x_step();
assert_eq!(*x_step.index(), 0);
assert_eq!(*x_step.step_size_in_time(), Positive::ONE);
assert_eq!(x_step.time_unit(), &TimeFrame::Hour);
assert_eq!(x_step.days_left()?, Positive::TWO);
let next_step = step.next(pos_or_panic!(200.0)).expect("should be Ok");
assert_eq!(next_step.get_value(), &pos_or_panic!(200.0));
let next_step_string = format!("{next_step}");
assert_eq!(next_step.to_string(), next_step_string);
let previous_step = step.previous(pos_or_panic!(50.0))?;
assert_eq!(previous_step.get_value(), &pos_or_panic!(50.0));
let previous_step_string = format!("{previous_step}");
assert_eq!(previous_step.to_string(), previous_step_string);
let x_step = step.get_x_step();
let next_x_step = x_step.next().expect("should be Ok");
assert_eq!(*next_x_step.index(), 1);
assert_eq!(*next_x_step.step_size_in_time(), Positive::ONE);
let next_x_step_string = format!("{next_x_step}");
assert_eq!(next_x_step.to_string(), next_x_step_string);
let y_step = step.get_y_step();
assert_eq!(*y_step.index(), 0);
assert_eq!(*y_step.value(), Positive::HUNDRED);
assert_eq!(y_step.positive().unwrap(), Positive::HUNDRED);
let last_steps: Vec<&Step<Positive, Positive>> = simulator
.into_iter()
.map(|step| step.last().expect("should be Ok"))
.collect();
info!("Last Steps: {:?}", last_steps);
assert_eq!(last_steps.len(), simulator_size);
let last_values: Vec<&Positive> = simulator
.into_iter()
.map(|step| step.last().expect("should be Ok").get_value())
.collect();
info!("Last Values: {:?}", last_values);
assert_eq!(last_values.len(), simulator_size);
Ok(())
}
}
#[cfg(test)]
mod tests_new_panic_freedom {
use super::*;
use crate::simulation::{WalkType, WalkTypeAble};
use optionstratlib_core::model::ExpirationDate;
use optionstratlib_core::utils::TimeFrame;
use rust_decimal::Decimal;
#[derive(Debug, Clone)]
struct BareWalker;
impl WalkTypeAble<Positive, Positive> for BareWalker {}
#[test]
fn test_new_unreservable_size_reaches_the_generator() {
let params = WalkParams {
size: 3,
init_step: Step::new(
Positive::ONE,
TimeFrame::Day,
ExpirationDate::Days(Positive::HUNDRED),
Positive::HUNDRED,
),
walk_type: WalkType::Brownian {
dt: Positive::ONE,
drift: Decimal::ZERO,
volatility: Positive::ONE,
},
walker: Box::new(BareWalker),
seed: Some(788),
};
let result: Result<Simulator<Positive, Positive>, &'static str> =
Simulator::new("Huge".to_string(), usize::MAX, ¶ms, |_| Err("boom"));
assert!(matches!(result, Err("boom")));
}
}