use crate::Options;
use crate::error::PricingError;
use crate::pricing::Profit;
use crate::pricing::monte_carlo::price_option_monte_carlo;
use crate::simulation::WalkParams;
use crate::simulation::randomwalk::RandomWalk;
use crate::simulation::steps::Step;
use crate::strategies::base::BasicAble;
use crate::utils::Len;
use crate::visualization::{ColorScheme, Graph, GraphConfig, GraphData, Series2D, TraceMode};
use positive::Positive;
use rust_decimal::Decimal;
use std::fmt::Display;
use std::ops::{AddAssign, Index, IndexMut};
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>(title: String, size: usize, params: &WalkParams<X, Y>, generator: F) -> Self
where
F: Fn(&WalkParams<X, Y>) -> Vec<Step<X, Y>> + Clone,
X: Copy + TryInto<Positive> + AddAssign + Display,
Y: TryInto<Positive> + Display + Clone,
{
let mut random_walks = Vec::new();
for i in 0..size {
let title = format!("{title}_{i}");
let random_walk = RandomWalk::new(title, params, &generator);
random_walks.push(random_walk);
}
Self {
title,
random_walks,
}
}
pub fn get_title(&self) -> &str {
&self.title
}
pub fn set_title(&mut self, title: String) {
self.title = title;
}
pub fn get_random_walks(&self) -> Vec<&RandomWalk<X, Y>> {
self.random_walks.iter().collect::<Vec<&RandomWalk<X, Y>>>()
}
pub fn get_random_walk(&self, index: usize) -> &RandomWalk<X, Y> {
&self.random_walks[index]
}
pub fn get_random_walk_mut(&mut self, index: usize) -> &mut RandomWalk<X, Y> {
&mut self.random_walks[index]
}
pub fn first(&self) -> Option<&RandomWalk<X, Y>> {
self.random_walks.first()
}
pub fn last(&self) -> Option<&RandomWalk<X, Y>> {
self.random_walks.last()
}
pub fn get_steps(&self) -> Vec<Vec<&Step<X, Y>>> {
self.into_iter().map(|step| step.get_steps()).collect()
}
pub fn get_last_steps(&self) -> Vec<&Step<X, Y>> {
self.into_iter().map(|step| step.last().unwrap()).collect()
}
pub fn get_last_values(&self) -> Vec<&Step<X, Y>> {
self.into_iter().map(|step| step.last().unwrap()).collect()
}
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<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>;
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,
{
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<X, Y> BasicAble for Simulator<X, Y>
where
X: AddAssign + Copy + Display + TryInto<Positive>,
Y: Clone + Display + TryInto<Positive>,
{
fn get_title(&self) -> String {
self.title.clone()
}
}
impl<X, Y> Graph for Simulator<X, Y>
where
X: Copy + TryInto<Positive> + AddAssign + Display,
Y: TryInto<Positive> + Display + Clone,
{
fn graph_data(&self) -> GraphData {
let mut series: Vec<Series2D> = Vec::new();
let random_walks = self.get_steps();
for (i, steps) in random_walks.iter().enumerate() {
let y: Vec<Decimal> = steps
.iter()
.map(|step| step.get_graph_y_value().unwrap_or(Positive::ZERO).to_dec())
.collect();
let x: Vec<Decimal> = steps
.iter()
.map(|step| -step.get_graph_x_in_days_left().to_dec())
.collect();
let title = format!("Sim_{i}");
series.push(Series2D {
x,
y,
name: title,
mode: TraceMode::Lines,
line_color: None,
line_width: Some(2.0),
});
}
GraphData::MultiSeries(series)
}
fn graph_config(&self) -> GraphConfig {
GraphConfig {
title: self.get_title().to_string(),
x_label: Some("Date".to_string()),
y_label: Some("Price".to_string()),
z_label: None,
width: 1600,
height: 900,
show_legend: true,
color_scheme: ColorScheme::HighContrast,
line_style: Default::default(),
legend: None,
}
}
}
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)]
mod tests {
use super::*;
use crate::ExpirationDate;
use crate::chains::generator_positive;
use crate::error::SimulationError;
use crate::simulation::{
WalkParams, WalkType, WalkTypeAble,
steps::{Step, Xstep, Ystep},
};
use crate::utils::{TimeFrame, time::convert_time_frame};
use positive::pos_or_panic;
use rust_decimal_macros::dec;
use tracing::{debug, info};
#[cfg(feature = "plotly")]
use {std::fs, std::path::Path};
struct TestWalker;
impl TestWalker {
fn new() -> Self {
TestWalker {}
}
}
impl WalkTypeAble<Positive, Positive> for TestWalker {}
fn test_generator(params: &WalkParams<Positive, Positive>) -> Vec<Step<Positive, Positive>> {
vec![params.init_step.clone()]
}
#[test]
fn test_simulator_creation() {
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,
};
let simulator = Simulator::new(
"Test Simulator".to_string(),
5,
&walk_params,
test_generator,
);
assert_eq!(simulator.get_title(), "Test Simulator");
assert_eq!(simulator.len(), 5);
assert!(!simulator.is_empty());
}
#[test]
fn test_simulator_title_methods() {
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,
};
let mut simulator = Simulator::new(
"Original Title".to_string(),
3,
&walk_params,
test_generator,
);
assert_eq!(simulator.get_title(), "Original Title");
simulator.set_title("New Title".to_string());
assert_eq!(simulator.get_title(), "New Title");
}
#[test]
fn test_simulator_step_access() {
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,
};
let simulator = Simulator::new(
"Test Simulator".to_string(),
3,
&walk_params,
test_generator,
);
let steps = simulator.get_random_walks();
assert_eq!(steps.len(), 3);
let step = simulator.get_random_walk(1);
assert_eq!(step.get_title(), "Test Simulator_1");
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"
);
}
#[test]
fn test_simulator_indexing() {
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,
};
let mut simulator = Simulator::new(
"Test Simulator".to_string(),
3,
&walk_params,
test_generator,
);
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");
}
#[test]
fn test_simulator_display() {
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,
};
let simulator = Simulator::new("Display Test".to_string(), 2, &walk_params, test_generator);
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"));
}
#[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]
#[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: 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,
};
let simulator = Simulator::new("Panic Test".to_string(), 3, &walk_params, test_generator);
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,
};
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);
#[cfg(feature = "plotly")]
{
let file_name = "Draws/Simulation/test_simulator.html".as_ref();
simulator.write_html(file_name)?;
if Path::new(file_name).exists() {
fs::remove_file(file_name)
.unwrap_or_else(|_| panic!("Failed to remove {}", file_name.to_str().unwrap()));
}
}
Ok(())
}
}