use crate::error::PricingError;
use crate::pricing::Profit;
use crate::simulation::WalkParams;
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};
#[derive(Debug, Clone, Default)]
pub struct RandomWalk<X, Y>
where
X: Copy + TryInto<Positive> + AddAssign + Display,
Y: TryInto<Positive> + Display + Clone,
{
title: String,
steps: Vec<Step<X, Y>>,
}
impl<X, Y> RandomWalk<X, Y>
where
X: Copy + TryInto<Positive> + AddAssign + Display,
Y: TryInto<Positive> + Display + Clone,
{
pub fn new<F>(title: String, params: &WalkParams<X, Y>, generator: F) -> Self
where
F: FnOnce(&WalkParams<X, Y>) -> Vec<Step<X, Y>>,
X: Copy + TryInto<Positive> + AddAssign + Display,
Y: TryInto<Positive> + Display + Clone,
{
let steps = generator(params);
Self { title, steps }
}
pub fn get_title(&self) -> &str {
&self.title
}
pub fn set_title(&mut self, title: String) {
self.title = title;
}
pub fn get_steps(&self) -> Vec<&Step<X, Y>> {
self.steps.iter().collect::<Vec<&Step<X, Y>>>()
}
pub fn get_step(&self, index: usize) -> &Step<X, Y> {
&self.steps[index]
}
pub fn get_step_mut(&mut self, index: usize) -> &mut Step<X, Y> {
&mut self.steps[index]
}
pub fn first(&self) -> Option<&Step<X, Y>> {
self.steps.first()
}
pub fn last(&self) -> Option<&Step<X, Y>> {
self.steps.last()
}
}
impl<X, Y> Len for RandomWalk<X, Y>
where
X: Copy + TryInto<Positive> + AddAssign + Display,
Y: TryInto<Positive> + Display + Clone,
{
fn len(&self) -> usize {
self.steps.len()
}
fn is_empty(&self) -> bool {
self.steps.is_empty()
}
}
impl<X, Y> Index<usize> for RandomWalk<X, Y>
where
X: Copy + TryInto<Positive> + AddAssign + Display,
Y: TryInto<Positive> + Display + Clone,
{
type Output = Step<X, Y>;
fn index(&self, index: usize) -> &Self::Output {
&self.steps[index]
}
}
impl<X, Y> IndexMut<usize> for RandomWalk<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.steps[index]
}
}
impl<X, Y> Display for RandomWalk<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, "RandomWalk Title: {}, Steps: ", self.title)?;
for step in &self.steps {
write!(f, "\t{step}")?;
}
Ok(())
}
}
impl<X, Y> Profit for RandomWalk<X, Y>
where
X: AddAssign + Copy + Display + TryInto<Positive>,
Y: TryInto<Positive> + Display + Clone,
{
fn calculate_profit_at(&self, _price: &Positive) -> Result<Decimal, PricingError> {
Err(PricingError::other(
"Profit calculation not implemented for RandomWalk",
))
}
}
impl<X, Y> BasicAble for RandomWalk<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 RandomWalk<X, Y>
where
X: Copy + TryInto<Positive> + AddAssign + Display,
Y: TryInto<Positive> + Display + Clone,
{
fn graph_data(&self) -> GraphData {
let steps = self.get_steps();
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();
GraphData::Series(Series2D {
x,
y,
name: self.get_title().to_string(),
mode: TraceMode::Lines,
line_color: Some("#1f77b4".to_string()),
line_width: Some(2.0),
})
}
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: false,
color_scheme: ColorScheme::Default,
line_style: Default::default(),
legend: None,
}
}
}
#[cfg(test)]
mod tests_random_walk {
use super::*;
use crate::ExpirationDate;
use crate::error::SimulationError;
use crate::simulation::WalkParams;
use crate::simulation::WalkType;
use crate::simulation::WalkTypeAble;
use crate::simulation::steps::Step;
use crate::utils::TimeFrame;
use num_traits::ToPrimitive;
use rust_decimal::Decimal;
use positive::pos_or_panic;
use std::fmt::Display;
use std::ops::AddAssign;
struct TestWalker {}
impl<X, Y> WalkTypeAble<X, Y> for TestWalker
where
X: Copy + TryInto<Positive> + AddAssign + Display,
Y: TryInto<Positive> + Display + Clone,
{
fn brownian(&self, params: &WalkParams<X, Y>) -> Result<Vec<Positive>, SimulationError> {
let mut values = Vec::new();
let init_value: Positive = params.ystep_as_positive()?;
values.push(init_value);
for i in 1..params.size {
values.push(pos_or_panic!(
init_value.value().to_f64().unwrap() + i as f64
));
}
Ok(values)
}
}
fn create_test_params<X, Y>(
size: usize,
x_value: X,
y_value: Y,
walk_type: WalkType,
) -> WalkParams<X, Y>
where
X: Copy + TryInto<Positive> + AddAssign + Display,
Y: TryInto<Positive> + Display + Clone,
{
let init_step = Step::new(
x_value,
TimeFrame::Day,
ExpirationDate::Days(pos_or_panic!(30.0)),
y_value,
);
WalkParams {
size,
init_step,
walk_type,
walker: Box::new(TestWalker {}),
}
}
fn generate_test_steps<X, Y>(params: &WalkParams<X, Y>) -> Vec<Step<X, Y>>
where
X: Copy + TryInto<Positive> + AddAssign + Display,
Y: TryInto<Positive> + Display + Clone,
{
let mut steps = Vec::new();
steps.push(params.init_step.clone());
let test_walker = TestWalker {};
let values = test_walker.brownian(params).unwrap();
let mut current_step = params.init_step.clone();
for _value in values.iter().skip(1) {
let new_y_value = current_step.y.value();
let next_step = current_step.next(new_y_value.clone()).unwrap();
steps.push(next_step.clone());
current_step = next_step;
}
steps
}
#[test]
fn test_random_walk_creation() {
let params = create_test_params(
5,
1.0,
100.0,
WalkType::Brownian {
dt: Positive::ONE,
drift: Decimal::ZERO,
volatility: pos_or_panic!(0.2),
},
);
let title = "Test Random Walk".to_string();
let walk = RandomWalk::new(title.clone(), ¶ms, generate_test_steps);
assert_eq!(walk.get_title(), title);
assert_eq!(walk.len(), 5);
assert!(!walk.is_empty());
}
#[test]
fn test_random_walk_empty() {
let params = create_test_params(
0,
1.0,
100.0,
WalkType::Brownian {
dt: Positive::ONE,
drift: Decimal::ZERO,
volatility: pos_or_panic!(0.2),
},
);
let title = "Empty Walk".to_string();
let walk = RandomWalk::new(title.clone(), ¶ms, |_| Vec::new());
assert_eq!(walk.get_title(), title);
assert_eq!(walk.len(), 0);
assert!(walk.is_empty());
assert!(walk.first().is_none());
assert!(walk.last().is_none());
}
#[test]
fn test_random_walk_title_update() {
let params = create_test_params(
5,
1.0,
100.0,
WalkType::Brownian {
dt: Positive::ONE,
drift: Decimal::ZERO,
volatility: pos_or_panic!(0.2),
},
);
let title = "Initial Title".to_string();
let mut walk = RandomWalk::new(title, ¶ms, generate_test_steps);
let new_title = "Updated Title".to_string();
walk.set_title(new_title.clone());
assert_eq!(walk.get_title(), new_title);
}
#[test]
fn test_random_walk_first_last() {
let params = create_test_params(
5,
1.0,
100.0,
WalkType::Brownian {
dt: Positive::ONE,
drift: Decimal::ZERO,
volatility: pos_or_panic!(0.2),
},
);
let walk = RandomWalk::new("Test Walk".to_string(), ¶ms, generate_test_steps);
let first = walk.first().unwrap();
let last = walk.last().unwrap();
assert_eq!(*first.x.index(), 0);
assert_eq!(*last.x.index(), 4); }
#[test]
fn test_random_walk_get_steps() {
let params = create_test_params(
5,
1.0,
100.0,
WalkType::Brownian {
dt: Positive::ONE,
drift: Decimal::ZERO,
volatility: pos_or_panic!(0.2),
},
);
let walk = RandomWalk::new("Test Walk".to_string(), ¶ms, generate_test_steps);
let steps = walk.get_steps();
assert_eq!(steps.len(), 5);
for (i, step) in steps.iter().enumerate() {
assert_eq!(*step.x.index(), i as i32);
}
}
#[test]
fn test_random_walk_get_step() {
let params = create_test_params(
5,
1.0,
100.0,
WalkType::Brownian {
dt: Positive::ONE,
drift: Decimal::ZERO,
volatility: pos_or_panic!(0.2),
},
);
let walk = RandomWalk::new("Test Walk".to_string(), ¶ms, generate_test_steps);
let step_0 = walk.get_step(0);
let step_3 = walk.get_step(3);
assert_eq!(*step_0.x.index(), 0);
assert_eq!(*step_3.x.index(), 3);
}
#[test]
#[should_panic(expected = "index out of bounds")]
fn test_random_walk_get_step_out_of_bounds() {
let params = create_test_params(
5,
1.0,
100.0,
WalkType::Brownian {
dt: Positive::ONE,
drift: Decimal::ZERO,
volatility: pos_or_panic!(0.2),
},
);
let walk = RandomWalk::new("Test Walk".to_string(), ¶ms, generate_test_steps);
let _step = walk.get_step(10);
}
#[test]
fn test_random_walk_get_step_mut() {
let params = create_test_params(
5,
1.0,
100.0,
WalkType::Brownian {
dt: Positive::ONE,
drift: Decimal::ZERO,
volatility: pos_or_panic!(0.2),
},
);
let mut walk = RandomWalk::new("Test Walk".to_string(), ¶ms, generate_test_steps);
let step_2 = walk.get_step_mut(2);
assert_eq!(*step_2.x.index(), 2);
let new_y_value = *step_2.y.value();
let new_step = step_2.clone();
*step_2 = new_step.next(new_y_value * 2.0).unwrap();
assert_eq!(*walk.get_step(2).x.index(), 3);
}
#[test]
fn test_random_walk_index_operator() {
let params = create_test_params(
5,
1.0,
100.0,
WalkType::Brownian {
dt: Positive::ONE,
drift: Decimal::ZERO,
volatility: pos_or_panic!(0.2),
},
);
let walk = RandomWalk::new("Test Walk".to_string(), ¶ms, generate_test_steps);
let step_1 = &walk[1];
assert_eq!(*step_1.x.index(), 1);
assert_eq!(*walk.get_step(3).x.index(), *walk[3].x.index());
}
#[test]
fn test_random_walk_index_mut_operator() {
let params = create_test_params(
5,
1.0,
100.0,
WalkType::Brownian {
dt: Positive::ONE,
drift: Decimal::ZERO,
volatility: pos_or_panic!(0.2),
},
);
let mut walk = RandomWalk::new("Test Walk".to_string(), ¶ms, generate_test_steps);
let initial_index = *walk[2].x.index();
let new_y_value = *walk[2].y.value();
let new_step = walk[2].clone();
walk[2] = new_step.next(new_y_value * 2.0).unwrap();
assert_ne!(*walk[2].x.index(), initial_index);
}
#[test]
fn test_random_walk_display() {
let params = create_test_params(
3,
1.0,
100.0,
WalkType::Brownian {
dt: Positive::ONE,
drift: Decimal::ZERO,
volatility: pos_or_panic!(0.2),
},
);
let walk = RandomWalk::new("Display Test".to_string(), ¶ms, generate_test_steps);
let display_output = format!("{walk}");
assert!(display_output.contains("Display Test"));
}
#[test]
fn test_random_walk_graph_implementation() {
let params = create_test_params(
5,
1.0,
100.0,
WalkType::Brownian {
dt: Positive::ONE,
drift: Decimal::ZERO,
volatility: pos_or_panic!(0.2),
},
);
let walk = RandomWalk::new("Graph Test".to_string(), ¶ms, generate_test_steps);
assert_eq!(walk.get_title(), "Graph Test");
}
#[test]
fn test_with_different_types() {
#[derive(Debug, Copy, Clone, PartialEq)]
struct TestX(f64);
impl Display for TestX {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl AddAssign for TestX {
fn add_assign(&mut self, other: Self) {
self.0 += other.0;
}
}
impl From<TestX> for Positive {
fn from(val: TestX) -> Self {
pos_or_panic!(val.0)
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
struct TestY(f64);
impl Display for TestY {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<TestY> for Positive {
fn from(val: TestY) -> Self {
pos_or_panic!(val.0)
}
}
let params = create_test_params(
3,
TestX(1.0),
TestY(100.0),
WalkType::Brownian {
dt: Positive::ONE,
drift: Decimal::ZERO,
volatility: pos_or_panic!(0.2),
},
);
let generator = |params: &WalkParams<TestX, TestY>| {
let mut steps = Vec::new();
steps.push(params.init_step.clone());
let mut current_step = params.init_step.clone();
for i in 1..params.size {
let next_step = current_step.next(TestY((100.0 + i as f64) * 1.1)).unwrap();
steps.push(next_step.clone());
current_step = next_step;
}
steps
};
let walk = RandomWalk::new("Custom Types Test".to_string(), ¶ms, generator);
assert_eq!(walk.len(), 3);
assert_eq!(*walk[0].y.value(), TestY(100.0));
}
}