1use crate::simulation::WalkParams;
8use crate::simulation::steps::Step;
9use optionstratlib_core::model::Positive;
10use optionstratlib_core::utils::Len;
11use optionstratlib_pricing::error::PricingError;
12use optionstratlib_pricing::pricing::Profit;
13use rust_decimal::Decimal;
14use std::fmt::Display;
15use std::ops::{AddAssign, Index, IndexMut};
16
17#[derive(Debug, Clone, Default)]
34pub struct RandomWalk<X, Y>
35where
36 X: Copy + TryInto<Positive> + AddAssign + Display,
37 Y: TryInto<Positive> + Display + Clone,
38{
39 title: String,
41
42 steps: Vec<Step<X, Y>>,
44}
45
46impl<X, Y> RandomWalk<X, Y>
47where
48 X: Copy + TryInto<Positive> + AddAssign + Display,
49 Y: TryInto<Positive> + Display + Clone,
50{
51 pub fn new<F, E>(title: String, params: &WalkParams<X, Y>, generator: F) -> Result<Self, E>
75 where
76 F: FnOnce(&WalkParams<X, Y>) -> Result<Vec<Step<X, Y>>, E>,
77 X: Copy + TryInto<Positive> + AddAssign + Display,
78 Y: TryInto<Positive> + Display + Clone,
79 {
80 let steps = generator(params)?;
81 Ok(Self { title, steps })
82 }
83
84 #[must_use]
90 pub fn get_title(&self) -> &str {
91 &self.title
92 }
93
94 pub fn set_title(&mut self, title: String) {
100 self.title = title;
101 }
102
103 #[must_use]
109 pub fn get_steps(&self) -> Vec<&Step<X, Y>> {
110 self.steps.iter().collect::<Vec<&Step<X, Y>>>()
111 }
112
113 #[must_use]
129 pub fn get_step(&self, index: usize) -> Option<&Step<X, Y>> {
130 self.steps.get(index)
131 }
132
133 pub fn get_step_mut(&mut self, index: usize) -> Option<&mut Step<X, Y>> {
147 self.steps.get_mut(index)
148 }
149
150 #[must_use]
157 pub fn first(&self) -> Option<&Step<X, Y>> {
158 self.steps.first()
159 }
160
161 #[must_use]
168 pub fn last(&self) -> Option<&Step<X, Y>> {
169 self.steps.last()
170 }
171}
172
173impl<X, Y> Len for RandomWalk<X, Y>
187where
188 X: Copy + TryInto<Positive> + AddAssign + Display,
189 Y: TryInto<Positive> + Display + Clone,
190{
191 fn len(&self) -> usize {
197 self.steps.len()
198 }
199
200 fn is_empty(&self) -> bool {
206 self.steps.is_empty()
207 }
208}
209
210impl<X, Y> Index<usize> for RandomWalk<X, Y>
220where
221 X: Copy + TryInto<Positive> + AddAssign + Display,
222 Y: TryInto<Positive> + Display + Clone,
223{
224 type Output = Step<X, Y>;
226
227 #[allow(clippy::indexing_slicing)]
243 fn index(&self, index: usize) -> &Self::Output {
244 &self.steps[index]
248 }
249}
250
251impl<X, Y> IndexMut<usize> for RandomWalk<X, Y>
261where
262 X: Copy + TryInto<Positive> + AddAssign + Display,
263 Y: TryInto<Positive> + Display + Clone,
264{
265 #[allow(clippy::indexing_slicing)]
281 fn index_mut(&mut self, index: usize) -> &mut Self::Output {
282 &mut self.steps[index]
286 }
287}
288
289impl<X, Y> Display for RandomWalk<X, Y>
290where
291 X: Copy + TryInto<Positive> + AddAssign + Display,
292 Y: TryInto<Positive> + Display + Clone,
293{
294 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
295 writeln!(f, "RandomWalk Title: {}, Steps: ", self.title)?;
296 for step in &self.steps {
297 write!(f, "\t{step}")?;
298 }
299 Ok(())
300 }
301}
302
303impl<X, Y> Profit for RandomWalk<X, Y>
304where
305 X: AddAssign + Copy + Display + TryInto<Positive>,
306 Y: TryInto<Positive> + Display + Clone,
307{
308 fn calculate_profit_at(&self, _price: &Positive) -> Result<Decimal, PricingError> {
309 Err(PricingError::other(
310 "Profit calculation not implemented for RandomWalk",
311 ))
312 }
313}
314
315#[cfg(test)]
316#[allow(irrefutable_let_patterns)]
317mod tests_random_walk {
318 use super::*;
319 use optionstratlib_core::model::ExpirationDate;
320
321 use crate::error::SimulationError;
322
323 use crate::simulation::WalkParams;
324 use crate::simulation::WalkType;
325 use crate::simulation::WalkTypeAble;
326 use crate::simulation::steps::Step;
327 use optionstratlib_core::utils::TimeFrame;
328
329 use num_traits::ToPrimitive;
330 use rust_decimal::Decimal;
331
332 use optionstratlib_core::pos_or_panic;
333 use std::convert::Infallible;
334 use std::fmt::Display;
335 use std::ops::AddAssign;
336
337 #[derive(Clone)]
339 struct TestWalker {}
340
341 impl<X, Y> WalkTypeAble<X, Y> for TestWalker
342 where
343 X: Copy + TryInto<Positive> + AddAssign + Display,
344 Y: TryInto<Positive> + Display + Clone,
345 {
346 fn brownian(&self, params: &WalkParams<X, Y>) -> Result<Vec<Positive>, SimulationError> {
348 let mut values = Vec::new();
349 let init_value: Positive = params.ystep_as_positive()?;
350 values.push(init_value);
351
352 for i in 1..params.size {
354 values.push(pos_or_panic!(
355 init_value.value().to_f64().unwrap() + i as f64
356 ));
357 }
358
359 Ok(values)
360 }
361 }
362
363 fn create_test_params<X, Y>(
365 size: usize,
366 x_value: X,
367 y_value: Y,
368 walk_type: WalkType,
369 ) -> WalkParams<X, Y>
370 where
371 X: Copy + TryInto<Positive> + AddAssign + Display,
372 Y: TryInto<Positive> + Display + Clone,
373 {
374 let init_step = Step::new(
375 x_value,
376 TimeFrame::Day,
377 ExpirationDate::Days(pos_or_panic!(30.0)),
378 y_value,
379 );
380
381 WalkParams {
382 size,
383 init_step,
384 walk_type,
385 walker: Box::new(TestWalker {}),
386 seed: None,
387 }
388 }
389
390 fn generate_test_steps<X, Y>(params: &WalkParams<X, Y>) -> Result<Vec<Step<X, Y>>, Infallible>
392 where
393 X: Copy + TryInto<Positive> + AddAssign + Display,
394 Y: TryInto<Positive> + Display + Clone,
395 {
396 let mut steps = Vec::new();
397 steps.push(params.init_step.clone());
398
399 let test_walker = TestWalker {};
400 let values = test_walker
401 .brownian(params)
402 .expect("test brownian generator");
403
404 let mut current_step = params.init_step.clone();
405
406 for _value in values.iter().skip(1) {
408 let new_y_value = current_step.y.value();
410
411 let next_step = current_step
413 .next(new_y_value.clone())
414 .expect("test step.next");
415 steps.push(next_step.clone());
416
417 current_step = next_step;
418 }
419
420 Ok(steps)
421 }
422
423 #[test]
424 fn test_random_walk_creation() {
425 let params = create_test_params(
426 5,
427 1.0,
428 100.0,
429 WalkType::Brownian {
430 dt: Positive::ONE,
431 drift: Decimal::ZERO,
432 volatility: pos_or_panic!(0.2),
433 },
434 );
435
436 let title = "Test Random Walk".to_string();
437 let Ok(walk) = RandomWalk::new(title.clone(), ¶ms, generate_test_steps) else {
438 unreachable!()
439 };
440
441 assert_eq!(walk.get_title(), title);
442 assert_eq!(walk.len(), 5);
443 assert!(!walk.is_empty());
444 }
445
446 #[test]
447 fn test_random_walk_empty() {
448 let params = create_test_params(
449 0,
450 1.0,
451 100.0,
452 WalkType::Brownian {
453 dt: Positive::ONE,
454 drift: Decimal::ZERO,
455 volatility: pos_or_panic!(0.2),
456 },
457 );
458
459 let title = "Empty Walk".to_string();
460 let Ok(walk) = RandomWalk::new(title.clone(), ¶ms, |_| Ok::<_, Infallible>(Vec::new()))
461 else {
462 unreachable!()
463 };
464
465 assert_eq!(walk.get_title(), title);
466 assert_eq!(walk.len(), 0);
467 assert!(walk.is_empty());
468 assert!(walk.first().is_none());
469 assert!(walk.last().is_none());
470 }
471
472 #[test]
473 fn test_random_walk_title_update() {
474 let params = create_test_params(
475 5,
476 1.0,
477 100.0,
478 WalkType::Brownian {
479 dt: Positive::ONE,
480 drift: Decimal::ZERO,
481 volatility: pos_or_panic!(0.2),
482 },
483 );
484
485 let title = "Initial Title".to_string();
486 let Ok(mut walk) = RandomWalk::new(title, ¶ms, generate_test_steps) else {
487 unreachable!()
488 };
489
490 let new_title = "Updated Title".to_string();
491 walk.set_title(new_title.clone());
492
493 assert_eq!(walk.get_title(), new_title);
494 }
495
496 #[test]
497 fn test_random_walk_first_last() {
498 let params = create_test_params(
499 5,
500 1.0,
501 100.0,
502 WalkType::Brownian {
503 dt: Positive::ONE,
504 drift: Decimal::ZERO,
505 volatility: pos_or_panic!(0.2),
506 },
507 );
508
509 let Ok(walk) = RandomWalk::new("Test Walk".to_string(), ¶ms, generate_test_steps)
510 else {
511 unreachable!()
512 };
513
514 let first = walk.first().unwrap();
515 let last = walk.last().unwrap();
516
517 assert_eq!(*first.x.index(), 0);
518 assert_eq!(*last.x.index(), 4); }
520
521 #[test]
522 fn test_random_walk_get_steps() {
523 let params = create_test_params(
524 5,
525 1.0,
526 100.0,
527 WalkType::Brownian {
528 dt: Positive::ONE,
529 drift: Decimal::ZERO,
530 volatility: pos_or_panic!(0.2),
531 },
532 );
533
534 let Ok(walk) = RandomWalk::new("Test Walk".to_string(), ¶ms, generate_test_steps)
535 else {
536 unreachable!()
537 };
538
539 let steps = walk.get_steps();
540 assert_eq!(steps.len(), 5);
541
542 for (i, step) in steps.iter().enumerate() {
544 assert_eq!(*step.x.index(), i as i32);
545 }
546 }
547
548 #[test]
549 fn test_random_walk_get_step() {
550 let params = create_test_params(
551 5,
552 1.0,
553 100.0,
554 WalkType::Brownian {
555 dt: Positive::ONE,
556 drift: Decimal::ZERO,
557 volatility: pos_or_panic!(0.2),
558 },
559 );
560
561 let Ok(walk) = RandomWalk::new("Test Walk".to_string(), ¶ms, generate_test_steps)
562 else {
563 unreachable!()
564 };
565
566 let step_0 = walk.get_step(0).unwrap();
567 let step_3 = walk.get_step(3).unwrap();
568
569 assert_eq!(*step_0.x.index(), 0);
570 assert_eq!(*step_3.x.index(), 3);
571 }
572
573 #[test]
577 fn test_random_walk_get_step_out_of_bounds() {
578 let params = create_test_params(
579 5,
580 1.0,
581 100.0,
582 WalkType::Brownian {
583 dt: Positive::ONE,
584 drift: Decimal::ZERO,
585 volatility: pos_or_panic!(0.2),
586 },
587 );
588
589 let Ok(mut walk) = RandomWalk::new("Test Walk".to_string(), ¶ms, generate_test_steps)
590 else {
591 unreachable!()
592 };
593
594 assert!(walk.get_step(10).is_none());
595 assert!(walk.get_step_mut(10).is_none());
596 }
597
598 #[test]
599 fn test_random_walk_get_step_mut() {
600 let params = create_test_params(
601 5,
602 1.0,
603 100.0,
604 WalkType::Brownian {
605 dt: Positive::ONE,
606 drift: Decimal::ZERO,
607 volatility: pos_or_panic!(0.2),
608 },
609 );
610
611 let Ok(mut walk) = RandomWalk::new("Test Walk".to_string(), ¶ms, generate_test_steps)
612 else {
613 unreachable!()
614 };
615
616 let step_2 = walk.get_step_mut(2).unwrap();
618 assert_eq!(*step_2.x.index(), 2);
619
620 let new_y_value = *step_2.y.value();
622 let new_step = step_2.clone();
623 *step_2 = new_step.next(new_y_value * 2.0).unwrap();
624
625 assert_eq!(*walk.get_step(2).unwrap().x.index(), 3);
627 }
628
629 #[test]
630 fn test_random_walk_index_operator() {
631 let params = create_test_params(
632 5,
633 1.0,
634 100.0,
635 WalkType::Brownian {
636 dt: Positive::ONE,
637 drift: Decimal::ZERO,
638 volatility: pos_or_panic!(0.2),
639 },
640 );
641
642 let Ok(walk) = RandomWalk::new("Test Walk".to_string(), ¶ms, generate_test_steps)
643 else {
644 unreachable!()
645 };
646
647 let step_1 = &walk[1];
649 assert_eq!(*step_1.x.index(), 1);
650
651 assert_eq!(*walk.get_step(3).unwrap().x.index(), *walk[3].x.index());
653 }
654
655 #[test]
656 fn test_random_walk_index_mut_operator() {
657 let params = create_test_params(
658 5,
659 1.0,
660 100.0,
661 WalkType::Brownian {
662 dt: Positive::ONE,
663 drift: Decimal::ZERO,
664 volatility: pos_or_panic!(0.2),
665 },
666 );
667
668 let Ok(mut walk) = RandomWalk::new("Test Walk".to_string(), ¶ms, generate_test_steps)
669 else {
670 unreachable!()
671 };
672
673 let initial_index = *walk[2].x.index();
675
676 let new_y_value = *walk[2].y.value();
678 let new_step = walk[2].clone();
679 walk[2] = new_step.next(new_y_value * 2.0).unwrap();
680
681 assert_ne!(*walk[2].x.index(), initial_index);
683 }
684
685 #[test]
686 fn test_random_walk_new_propagates_generator_error() {
687 #[derive(Debug, PartialEq)]
690 struct FakeError(&'static str);
691
692 let params = create_test_params(
693 3,
694 1.0_f64,
695 100.0_f64,
696 WalkType::Brownian {
697 dt: Positive::ONE,
698 drift: Decimal::ZERO,
699 volatility: pos_or_panic!(0.2),
700 },
701 );
702
703 let result: Result<RandomWalk<f64, f64>, FakeError> =
704 RandomWalk::new("err".to_string(), ¶ms, |_| Err(FakeError("boom")));
705
706 match result {
707 Err(FakeError(msg)) => assert_eq!(msg, "boom"),
708 Ok(_) => panic!("expected generator error to propagate"),
709 }
710 }
711
712 #[test]
713 fn test_random_walk_display() {
714 let params = create_test_params(
715 3,
716 1.0,
717 100.0,
718 WalkType::Brownian {
719 dt: Positive::ONE,
720 drift: Decimal::ZERO,
721 volatility: pos_or_panic!(0.2),
722 },
723 );
724
725 let Ok(walk) = RandomWalk::new("Display Test".to_string(), ¶ms, generate_test_steps)
726 else {
727 unreachable!()
728 };
729
730 let display_output = format!("{walk}");
732 assert!(display_output.contains("Display Test"));
733 }
734
735 #[test]
736 fn test_random_walk_graph_implementation() {
737 let params = create_test_params(
738 5,
739 1.0,
740 100.0,
741 WalkType::Brownian {
742 dt: Positive::ONE,
743 drift: Decimal::ZERO,
744 volatility: pos_or_panic!(0.2),
745 },
746 );
747
748 let Ok(walk) = RandomWalk::new("Graph Test".to_string(), ¶ms, generate_test_steps)
749 else {
750 unreachable!()
751 };
752
753 assert_eq!(walk.get_title(), "Graph Test");
755 }
756
757 #[test]
758 fn test_with_different_types() {
759 #[derive(Debug, Copy, Clone, PartialEq)]
761 struct TestX(f64);
762
763 impl Display for TestX {
764 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
765 write!(f, "{}", self.0)
766 }
767 }
768
769 impl AddAssign for TestX {
770 fn add_assign(&mut self, other: Self) {
771 self.0 += other.0;
772 }
773 }
774
775 impl From<TestX> for Positive {
776 fn from(val: TestX) -> Self {
777 pos_or_panic!(val.0)
778 }
779 }
780
781 #[derive(Debug, Copy, Clone, PartialEq)]
782 struct TestY(f64);
783
784 impl Display for TestY {
785 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
786 write!(f, "{}", self.0)
787 }
788 }
789
790 impl From<TestY> for Positive {
791 fn from(val: TestY) -> Self {
792 pos_or_panic!(val.0)
793 }
794 }
795
796 let params = create_test_params(
798 3,
799 TestX(1.0),
800 TestY(100.0),
801 WalkType::Brownian {
802 dt: Positive::ONE,
803 drift: Decimal::ZERO,
804 volatility: pos_or_panic!(0.2),
805 },
806 );
807
808 let generator = |params: &WalkParams<TestX, TestY>| -> Result<_, Infallible> {
810 let mut steps = Vec::new();
811 steps.push(params.init_step.clone());
812
813 let mut current_step = params.init_step.clone();
814 for i in 1..params.size {
815 let next_step = current_step
816 .next(TestY((100.0 + i as f64) * 1.1))
817 .expect("test step.next");
818 steps.push(next_step.clone());
819 current_step = next_step;
820 }
821
822 Ok(steps)
823 };
824
825 let Ok(walk) = RandomWalk::new("Custom Types Test".to_string(), ¶ms, generator) else {
826 unreachable!()
827 };
828
829 assert_eq!(walk.len(), 3);
830 assert_eq!(*walk[0].y.value(), TestY(100.0));
831 }
832}