1use crate::error::SimulationError;
7use crate::simulation::steps::{Xstep, Ystep};
8use num_traits::FromPrimitive;
9use optionstratlib_core::model::ExpirationDate;
10use optionstratlib_core::model::Positive;
11use optionstratlib_core::utils::TimeFrame;
12use rust_decimal::Decimal;
13use serde::ser::SerializeStruct;
14use serde::{Serialize, Serializer};
15use std::convert::TryInto;
16use std::fmt::{Display, Formatter};
17use std::ops::AddAssign;
18
19#[derive(Debug, Clone)]
50pub struct Step<X, Y>
51where
52 X: Copy + TryInto<Positive> + AddAssign + Display,
53 Y: TryInto<Positive> + Display + Clone,
54{
55 pub x: Xstep<X>,
57
58 pub y: Ystep<Y>,
60}
61
62impl<X, Y> Step<X, Y>
75where
76 X: Copy + TryInto<Positive> + AddAssign + Display,
77 Y: TryInto<Positive> + Display + Clone,
78{
79 pub fn new(x_value: X, time_unit: TimeFrame, datetime: ExpirationDate, y_value: Y) -> Self {
92 Self {
93 x: Xstep::new(x_value, time_unit, datetime),
94 y: Ystep::new(0, y_value),
95 }
96 }
97
98 pub fn next(&self, new_y_value: Y) -> Result<Self, SimulationError> {
118 let next_x = match self.x.next() {
119 Ok(x_step) => x_step,
120 Err(e) => {
121 return Err(SimulationError::step_error(&format!(
122 "Cannot generate next step. Expiration date is already reached: {e}"
123 )));
124 }
125 };
126 let next_index = self.y.index().checked_add(1).ok_or_else(|| {
127 SimulationError::step_error("step index overflowed advancing the walk")
128 })?;
129 Ok(Self {
130 x: next_x,
131 y: Ystep::new(next_index, new_y_value),
132 })
133 }
134
135 pub fn previous(&self, new_y_value: Y) -> Result<Self, SimulationError> {
155 let previous_x = match self.x.previous() {
156 Ok(x_step) => x_step,
157 Err(e) => {
158 return Err(SimulationError::step_error(&format!(
159 "Cannot generate previous step. Expiration date is already reached: {e}"
160 )));
161 }
162 };
163 let previous_index = self.y.index().checked_sub(1).ok_or_else(|| {
164 SimulationError::step_error("step index underflowed rewinding the walk")
165 })?;
166 Ok(Self {
167 x: previous_x,
168 y: Ystep::new(previous_index, new_y_value),
169 })
170 }
171
172 pub fn get_graph_x_value(&self) -> Result<Decimal, SimulationError> {
188 match Decimal::from_i32(*self.x.index()) {
189 Some(x) => Ok(x),
190 None => Err(SimulationError::IndexConversion),
191 }
192 }
193
194 pub fn get_graph_x_in_days_left(&self) -> Positive {
207 self.x.days_left().unwrap_or_else(|e| {
211 tracing::warn!("get_graph_x_in_days_left: days_left failed: {e}; using 0");
212 Positive::ZERO
213 })
214 }
215
216 pub fn get_graph_y_value(&self) -> Result<Positive, SimulationError> {
232 self.y.positive()
233 }
234
235 pub fn get_y_step(&self) -> &Ystep<Y> {
246 &self.y
247 }
248
249 pub fn get_x_step(&self) -> &Xstep<X> {
256 &self.x
257 }
258
259 pub fn get_value(&self) -> &Y {
268 self.y.value()
269 }
270
271 pub fn get_index(&self) -> &X {
281 self.x.step_size_in_time()
282 }
283
284 pub fn get_positive_value(&self) -> Result<Positive, SimulationError> {
300 self.y.positive()
301 }
302}
303
304impl<X, Y> Display for Step<X, Y>
305where
306 X: Copy + TryInto<Positive> + AddAssign + Display,
307 Y: TryInto<Positive> + Display + Clone,
308{
309 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
310 writeln!(f, "Step {{ x: {}, y: {} }}", self.x, self.y)
311 }
312}
313
314impl<X, Y> Serialize for Step<X, Y>
315where
316 X: Copy + TryInto<Positive> + AddAssign + Display + Serialize,
317 Y: TryInto<Positive> + Display + Serialize + Clone,
318{
319 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
320 where
321 S: Serializer,
322 {
323 let mut state = serializer.serialize_struct("Step", 2)?;
324 state.serialize_field("x", &self.x)?;
325 state.serialize_field("y", &self.y)?;
326 state.end()
327 }
328}
329
330#[cfg(test)]
331mod tests {
332 use super::*;
333 use optionstratlib_core::pos_or_panic;
334
335 #[derive(Debug, Copy, Clone, PartialEq)]
337 struct TestValue(u32);
338
339 impl From<TestValue> for Positive {
340 fn from(val: TestValue) -> Self {
341 Positive::new(val.0 as f64).unwrap()
342 }
343 }
344
345 impl AddAssign for TestValue {
346 fn add_assign(&mut self, other: Self) {
347 self.0 += other.0;
348 }
349 }
350
351 impl Display for TestValue {
352 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
353 write!(f, "{}", self.0)
354 }
355 }
356
357 #[test]
358 fn test_step_new() {
359 let value = TestValue(5);
361 let time_unit = TimeFrame::Day;
362 let datetime = ExpirationDate::Days(pos_or_panic!(30.0));
363
364 let step = Xstep::new(value, time_unit, datetime);
365
366 assert_eq!(*step.index(), 0);
367 assert_eq!(step.step_size_in_time(), &TestValue(5));
368 assert_eq!(*step.time_unit(), TimeFrame::Day);
369 assert_eq!(*step.datetime(), ExpirationDate::Days(pos_or_panic!(30.0)));
370 }
371
372 #[test]
373 fn test_step_new_with_datetime_is_normalized_to_days() {
374 use chrono::{Duration, Utc};
378
379 let value = TestValue(5);
380 let time_unit = TimeFrame::Day;
381 let dt = Utc::now() + Duration::days(30);
382 let datetime = ExpirationDate::DateTime(dt);
383
384 let step = Xstep::new(value, time_unit, datetime);
385 match step.datetime() {
386 ExpirationDate::Days(_) => {}
387 other => panic!("expected ExpirationDate::Days, got {other:?}"),
388 }
389 }
390
391 #[test]
392 fn test_step_next() {
393 let value = TestValue(5);
397 let time_unit = TimeFrame::Day;
398 let datetime = ExpirationDate::Days(pos_or_panic!(30.0));
399
400 let step = Xstep::new(value, time_unit, datetime);
401 let next_step = step.next().unwrap();
402
403 assert_eq!(*step.index(), 0);
404 assert_eq!(step.step_size_in_time(), &TestValue(5));
405 assert_eq!(*step.time_unit(), TimeFrame::Day);
406 assert_eq!(*step.datetime(), ExpirationDate::Days(pos_or_panic!(30.0)));
407
408 assert_eq!(*next_step.index(), 1);
409 assert_eq!(next_step.step_size_in_time(), &TestValue(5));
410 assert_eq!(*next_step.time_unit(), TimeFrame::Day);
411 assert_eq!(
412 *next_step.datetime(),
413 ExpirationDate::Days(pos_or_panic!(25.0))
414 );
415 }
416
417 #[test]
418 fn test_step_next_with_weeks() {
419 let value = TestValue(2);
423 let time_unit = TimeFrame::Week;
424 let datetime = ExpirationDate::Days(pos_or_panic!(30.0));
425
426 let step = Xstep::new(value, time_unit, datetime);
427 let next_step = step.next().unwrap();
428
429 assert_eq!(*step.index(), 0);
430 assert_eq!(step.step_size_in_time(), &TestValue(2));
431 assert_eq!(*step.time_unit(), TimeFrame::Week);
432 assert_eq!(*step.datetime(), ExpirationDate::Days(pos_or_panic!(30.0)));
433
434 assert_eq!(*next_step.index(), 1);
435 assert_eq!(next_step.step_size_in_time(), &TestValue(2));
436 assert_eq!(*next_step.time_unit(), TimeFrame::Week);
437 assert_eq!(
438 *next_step.datetime(),
439 ExpirationDate::Days(pos_or_panic!(16.0))
440 );
441 }
442
443 #[test]
444 fn test_step_previous() {
445 let value = TestValue(5);
449 let time_unit = TimeFrame::Day;
450 let datetime = ExpirationDate::Days(pos_or_panic!(30.0));
451
452 let step = Xstep::new(value, time_unit, datetime);
453 let prev_step = step.previous().unwrap();
454
455 assert_eq!(*step.index(), 0);
456 assert_eq!(step.step_size_in_time(), &TestValue(5));
457 assert_eq!(*step.time_unit(), TimeFrame::Day);
458 assert_eq!(*step.datetime(), ExpirationDate::Days(pos_or_panic!(30.0)));
459
460 assert_eq!(*prev_step.index(), -1);
461 assert_eq!(prev_step.step_size_in_time(), &TestValue(5));
462 assert_eq!(*prev_step.time_unit(), TimeFrame::Day);
463 assert_eq!(
464 *prev_step.datetime(),
465 ExpirationDate::Days(pos_or_panic!(35.0))
466 );
467 }
468
469 #[test]
470 fn test_step_previouse_with_months() {
471 let value = TestValue(3);
476 let time_unit = TimeFrame::Month;
477 let datetime = ExpirationDate::Days(pos_or_panic!(30.0));
478
479 let step = Xstep::new(value, time_unit, datetime);
480 let prev_step = step.previous().unwrap();
481
482 assert_eq!(*step.index(), 0);
483 assert_eq!(step.step_size_in_time(), &TestValue(3));
484 assert_eq!(*step.time_unit(), TimeFrame::Month);
485 assert_eq!(*step.datetime(), ExpirationDate::Days(pos_or_panic!(30.0)));
486
487 assert_eq!(*prev_step.index(), -1);
488 assert_eq!(prev_step.step_size_in_time(), &TestValue(3));
489 assert_eq!(*prev_step.time_unit(), TimeFrame::Month);
490 assert_eq!(
491 *prev_step.datetime(),
492 ExpirationDate::Days(pos_or_panic!(121.25))
493 );
494 }
495
496 #[test]
497 fn test_multiple_steps() {
498 let value = TestValue(10);
500 let time_unit = TimeFrame::Day;
501 let datetime = ExpirationDate::Days(Positive::HUNDRED);
502
503 let step = Xstep::new(value, time_unit, datetime);
504 let step1 = step.next().unwrap();
505 let step2 = step1.next().unwrap();
506 let step3 = step2.next().unwrap();
507
508 assert_eq!(*step1.index(), 1);
509 assert_eq!(*step1.datetime(), ExpirationDate::Days(pos_or_panic!(90.0)));
510 assert_eq!(*step2.index(), 2);
511 assert_eq!(*step2.datetime(), ExpirationDate::Days(pos_or_panic!(80.0)));
512 assert_eq!(*step3.index(), 3);
513 assert_eq!(*step3.datetime(), ExpirationDate::Days(pos_or_panic!(70.0)));
514 }
515
516 #[test]
517 fn test_forward_and_backward() {
518 let value = TestValue(5);
520 let time_unit = TimeFrame::Day;
521 let datetime = ExpirationDate::Days(pos_or_panic!(50.0));
522
523 let original = Xstep::new(value, time_unit, datetime);
524 let forward = original.next().unwrap();
525 let back_to_original = forward.previous().unwrap();
526
527 assert_eq!(*original.index(), 0);
528 assert_eq!(*forward.index(), 1);
529 assert_eq!(*back_to_original.index(), 0);
530 assert_eq!(
531 *original.datetime(),
532 ExpirationDate::Days(pos_or_panic!(50.0))
533 );
534 assert_eq!(
535 *forward.datetime(),
536 ExpirationDate::Days(pos_or_panic!(45.0))
537 );
538 assert_eq!(
539 *back_to_original.datetime(),
540 ExpirationDate::Days(pos_or_panic!(50.0))
541 );
542 }
543}
544
545#[cfg(test)]
546mod tests_positive {
547 use super::*;
548 use optionstratlib_core::pos_or_panic;
549
550 #[test]
551 fn test_step_new() {
552 let value = pos_or_panic!(5.0);
554 let time_unit = TimeFrame::Day;
555 let datetime = ExpirationDate::Days(pos_or_panic!(30.0));
556
557 let step = Xstep::new(value, time_unit, datetime);
558
559 assert_eq!(*step.index(), 0);
560 assert_eq!(*step.step_size_in_time(), pos_or_panic!(5.0));
561 assert_eq!(*step.time_unit(), TimeFrame::Day);
562 assert_eq!(*step.datetime(), ExpirationDate::Days(pos_or_panic!(30.0)));
563 }
564
565 #[test]
566 fn test_step_new_with_datetime_is_normalized_to_days() {
567 use chrono::{Duration, Utc};
570
571 let value = pos_or_panic!(5.0);
572 let time_unit = TimeFrame::Day;
573 let dt = Utc::now() + Duration::days(30);
574 let datetime = ExpirationDate::DateTime(dt);
575
576 let step = Xstep::new(value, time_unit, datetime);
577 match step.datetime() {
578 ExpirationDate::Days(_) => {}
579 other => panic!("expected ExpirationDate::Days, got {other:?}"),
580 }
581 }
582
583 #[test]
584 fn test_step_next() {
585 let value = pos_or_panic!(5.0);
589 let time_unit = TimeFrame::Day;
590 let datetime = ExpirationDate::Days(pos_or_panic!(30.0));
591
592 let step = Xstep::new(value, time_unit, datetime);
593 let next_step = step.next().unwrap();
594
595 assert_eq!(*next_step.index(), 1);
596 assert_eq!(*next_step.step_size_in_time(), pos_or_panic!(5.0));
597 assert_eq!(*next_step.time_unit(), TimeFrame::Day);
598 assert_eq!(
599 *next_step.datetime(),
600 ExpirationDate::Days(pos_or_panic!(25.0))
601 );
602 }
603
604 #[test]
605 fn test_step_next_with_weeks() {
606 let value = Positive::TWO;
610 let time_unit = TimeFrame::Week;
611 let datetime = ExpirationDate::Days(pos_or_panic!(30.0));
612
613 let step = Xstep::new(value, time_unit, datetime);
614 let next_step = step.next().unwrap();
615
616 assert_eq!(*next_step.index(), 1);
617 assert_eq!(*next_step.step_size_in_time(), Positive::TWO);
618 assert_eq!(*next_step.time_unit(), TimeFrame::Week);
619 assert_eq!(
620 *next_step.datetime(),
621 ExpirationDate::Days(pos_or_panic!(16.0))
622 );
623 }
624
625 #[test]
626 fn test_step_previous() {
627 let value = pos_or_panic!(5.0);
631 let time_unit = TimeFrame::Day;
632 let datetime = ExpirationDate::Days(pos_or_panic!(30.0));
633
634 let step = Xstep::new(value, time_unit, datetime);
635 let prev_step = step.previous().unwrap();
636
637 assert_eq!(*prev_step.index(), -1);
638 assert_eq!(*prev_step.step_size_in_time(), pos_or_panic!(5.0));
639 assert_eq!(*prev_step.time_unit(), TimeFrame::Day);
640 assert_eq!(
641 *prev_step.datetime(),
642 ExpirationDate::Days(pos_or_panic!(35.0))
643 );
644 }
645
646 #[test]
647 fn test_step_previouse_with_months() {
648 let value = pos_or_panic!(3.0);
653 let time_unit = TimeFrame::Month;
654 let datetime = ExpirationDate::Days(pos_or_panic!(30.0));
655
656 let step = Xstep::new(value, time_unit, datetime);
657 let prev_step = step.previous().unwrap();
658
659 assert_eq!(*prev_step.index(), -1);
660 assert_eq!(*prev_step.step_size_in_time(), pos_or_panic!(3.0));
661 assert_eq!(*prev_step.time_unit(), TimeFrame::Month);
662 assert_eq!(
663 *prev_step.datetime(),
664 ExpirationDate::Days(pos_or_panic!(121.25))
665 );
666 }
667
668 #[test]
669 fn test_multiple_steps() {
670 let value = Positive::TEN;
672 let time_unit = TimeFrame::Day;
673 let datetime = ExpirationDate::Days(Positive::HUNDRED);
674
675 let step = Xstep::new(value, time_unit, datetime);
676 let step1 = step.next().unwrap();
677 let step2 = step1.next().unwrap();
678 let step3 = step2.next().unwrap();
679
680 assert_eq!(*step1.index(), 1);
681 assert_eq!(*step1.datetime(), ExpirationDate::Days(pos_or_panic!(90.0)));
682 assert_eq!(*step2.index(), 2);
683 assert_eq!(*step2.datetime(), ExpirationDate::Days(pos_or_panic!(80.0)));
684 assert_eq!(*step3.index(), 3);
685 assert_eq!(*step3.datetime(), ExpirationDate::Days(pos_or_panic!(70.0)));
686 }
687
688 #[test]
689 fn test_forward_and_backward() {
690 let value = pos_or_panic!(5.0);
692 let time_unit = TimeFrame::Day;
693 let datetime = ExpirationDate::Days(pos_or_panic!(50.0));
694
695 let original = Xstep::new(value, time_unit, datetime);
696 let forward = original.next().unwrap();
697 let back_to_original = forward.previous().unwrap();
698
699 assert_eq!(*original.index(), 0);
700 assert_eq!(*forward.index(), 1);
701 assert_eq!(*back_to_original.index(), 0);
702 assert_eq!(
703 *original.datetime(),
704 ExpirationDate::Days(pos_or_panic!(50.0))
705 );
706 assert_eq!(
707 *forward.datetime(),
708 ExpirationDate::Days(pos_or_panic!(45.0))
709 );
710 assert_eq!(
711 *back_to_original.datetime(),
712 ExpirationDate::Days(pos_or_panic!(50.0))
713 );
714 }
715}
716
717#[cfg(test)]
718mod tests_step {
719 use super::*;
720 use optionstratlib_core::pos_or_panic;
721
722 #[derive(Debug, Copy, Clone, PartialEq)]
724 struct TestValue(u32);
725
726 impl From<TestValue> for Positive {
727 fn from(val: TestValue) -> Self {
728 Positive::new(val.0 as f64).unwrap()
729 }
730 }
731
732 impl AddAssign for TestValue {
733 fn add_assign(&mut self, other: Self) {
734 self.0 += other.0;
735 }
736 }
737
738 impl Display for TestValue {
739 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
740 write!(f, "{}", self.0)
741 }
742 }
743
744 #[test]
745 fn test_step_new() {
746 let x_value = TestValue(5);
748 let time_unit = TimeFrame::Day;
749 let datetime = ExpirationDate::Days(pos_or_panic!(30.0));
750 let y_value = 42.5;
751
752 let step = Step::new(x_value, time_unit, datetime, y_value);
753
754 assert_eq!(*step.x.index(), 0);
756 assert_eq!(step.x.step_size_in_time(), &TestValue(5));
757 assert_eq!(*step.x.time_unit(), TimeFrame::Day);
758 assert_eq!(
759 *step.x.datetime(),
760 ExpirationDate::Days(pos_or_panic!(30.0))
761 );
762
763 assert_eq!(*step.y.index(), 0);
765 assert_eq!(*step.y.value(), 42.5);
766
767 let result_next = step.next(100.0);
768 assert!(result_next.is_ok());
769 let next = result_next.unwrap();
770
771 assert!(
772 next.to_string()
773 .contains("Step { x: Xstep { index: 1, value: 5, time_unit: Day, datetime:")
774 );
775
776 let previous_next = step.previous(100.0);
777 assert!(previous_next.is_ok());
778 let previous = previous_next.unwrap();
779
780 assert!(
781 previous
782 .to_string()
783 .contains("Step { x: Xstep { index: -1, value: 5, time_unit: Day, datetime:")
784 );
785
786 assert_eq!(next.get_graph_x_value().unwrap(), Decimal::ONE);
787 assert_eq!(previous.get_graph_x_value().unwrap(), Decimal::NEGATIVE_ONE);
788 }
789
790 #[test]
791 fn test_step_next() {
792 let x_value = TestValue(5);
794 let time_unit = TimeFrame::Day;
795 let datetime = ExpirationDate::Days(pos_or_panic!(30.0));
796 let y_value = 42.5;
797
798 let step = Step::new(x_value, time_unit, datetime, y_value);
799
800 let new_y_value = 45.0;
802 let next_step = step.next(new_y_value).unwrap();
803
804 assert_eq!(*step.x.index(), 0);
806 assert_eq!(step.x.step_size_in_time(), &TestValue(5));
807 assert_eq!(*step.x.time_unit(), TimeFrame::Day);
808 assert_eq!(
809 *step.x.datetime(),
810 ExpirationDate::Days(pos_or_panic!(30.0))
811 );
812
813 assert_eq!(*step.y.index(), 0);
815 assert_eq!(*step.y.value(), 42.5);
816
817 assert_eq!(*next_step.x.index(), 1);
818 assert_eq!(next_step.x.step_size_in_time(), &TestValue(5));
819 assert_eq!(*next_step.x.time_unit(), TimeFrame::Day);
820 assert_eq!(
821 *next_step.x.datetime(),
822 ExpirationDate::Days(pos_or_panic!(25.0))
823 );
824
825 assert_eq!(*next_step.y.index(), 1);
827 assert_eq!(*next_step.y.value(), 45.0);
828 }
829
830 #[test]
831 fn test_step_previous() {
832 let x_value = TestValue(5);
834 let time_unit = TimeFrame::Day;
835 let datetime = ExpirationDate::Days(pos_or_panic!(30.0));
836 let y_value = 42.5;
837
838 let step = Step::new(x_value, time_unit, datetime, y_value);
839
840 let new_y_value = 38.0;
842 let prev_step = step.previous(new_y_value).unwrap();
843
844 assert_eq!(*step.x.index(), 0);
846 assert_eq!(step.x.step_size_in_time(), &TestValue(5));
847 assert_eq!(*step.x.time_unit(), TimeFrame::Day);
848 assert_eq!(
849 *step.x.datetime(),
850 ExpirationDate::Days(pos_or_panic!(30.0))
851 );
852
853 assert_eq!(*prev_step.y.value(), 38.0);
855 }
856
857 #[test]
858 fn test_step_with_timeframe_conversion() {
859 let x_value = TestValue(2);
861 let time_unit = TimeFrame::Week;
862 let datetime = ExpirationDate::Days(pos_or_panic!(30.0));
863 let y_value = 100.0;
864
865 let step = Step::new(x_value, time_unit, datetime, y_value);
866
867 let next_y = 105.0;
869 let next_step = step.next(next_y).unwrap();
870
871 assert_eq!(
872 *next_step.x.datetime(),
873 ExpirationDate::Days(pos_or_panic!(16.0))
874 );
875 assert_eq!(*next_step.y.value(), 105.0);
876
877 let prev_y = 95.0;
879 let prev_step = step.previous(prev_y).unwrap();
880
881 assert_eq!(
882 *prev_step.x.datetime(),
883 ExpirationDate::Days(pos_or_panic!(44.0))
884 );
885 assert_eq!(*prev_step.y.value(), 95.0);
886 }
887
888 #[test]
889 fn test_step_chain() {
890 let x_value = TestValue(10);
892 let time_unit = TimeFrame::Day;
893 let datetime = ExpirationDate::Days(Positive::HUNDRED);
894 let y_value = 50.0;
895
896 let initial = Step::new(x_value, time_unit, datetime, y_value);
897
898 let step1 = initial.next(55.0).unwrap();
900 let step2 = step1.next(60.0).unwrap();
901 let step3 = step2.next(65.0).unwrap();
902
903 assert_eq!(*step1.x.index(), 1);
905 assert_eq!(
906 *step1.x.datetime(),
907 ExpirationDate::Days(pos_or_panic!(90.0))
908 );
909 assert_eq!(*step1.y.index(), 1);
910 assert_eq!(*step1.y.value(), 55.0);
911 assert_eq!(*step2.x.index(), 2);
912 assert_eq!(
913 *step2.x.datetime(),
914 ExpirationDate::Days(pos_or_panic!(80.0))
915 );
916 assert_eq!(*step2.y.index(), 2);
917 assert_eq!(*step2.y.value(), 60.0);
918 assert_eq!(*step3.x.index(), 3);
919 assert_eq!(
920 *step3.x.datetime(),
921 ExpirationDate::Days(pos_or_panic!(70.0))
922 );
923 assert_eq!(*step3.y.index(), 3);
924 assert_eq!(*step3.y.value(), 65.0);
925 }
926
927 #[test]
928 fn test_with_positive_type() {
929 let x_value = pos_or_panic!(5.0);
931 let time_unit = TimeFrame::Day;
932 let datetime = ExpirationDate::Days(pos_or_panic!(30.0));
933 let y_value = pos_or_panic!(50.0);
934
935 let step = Step::new(x_value, time_unit, datetime, y_value);
936
937 let next_step = step.next(pos_or_panic!(55.0)).unwrap();
938
939 assert_eq!(
940 *next_step.x.datetime(),
941 ExpirationDate::Days(pos_or_panic!(25.0))
942 );
943 assert_eq!(*next_step.y.value(), pos_or_panic!(55.0));
944 }
945
946 #[test]
947 fn test_with_zero_days() {
948 let x_value = pos_or_panic!(5.0);
950 let time_unit = TimeFrame::Day;
951 let datetime = ExpirationDate::Days(Positive::ZERO);
952 let y_value = pos_or_panic!(50.0);
953 let step = Step::new(x_value, time_unit, datetime, y_value);
954 let result = step.next(pos_or_panic!(55.0));
955 assert!(result.is_err());
956 let step_x = step.get_x_step();
957 assert_eq!(*step_x.index(), 0);
958 assert_eq!(*step_x.step_size_in_time(), pos_or_panic!(5.0));
959 let result = step_x.next();
960 assert!(result.is_err());
961 }
962
963 #[test]
964 fn next_ok_increments_indices_and_builds_self() {
965 let x_value = pos_or_panic!(5.0);
966 let time_unit = TimeFrame::Day;
967 let datetime = ExpirationDate::Days(Positive::TWO);
968 let y_value = pos_or_panic!(50.0);
969 let step = Step::new(x_value, time_unit, datetime, y_value);
970
971 let new_y = pos_or_panic!(55.0);
972 let next = step.next(new_y).unwrap();
973
974 assert_eq!(next.get_value(), &new_y);
975 assert_eq!(*next.x.index(), 1);
976 assert_eq!(*next.y.index(), step.y.index() + 1);
977 }
978}
979
980#[cfg(test)]
981mod tests_step_serialization {
982 use super::*;
983
984 use chrono::{TimeZone, Utc};
985 use optionstratlib_core::pos_or_panic;
986 use serde_json::{self, Value};
987
988 fn create_test_step() -> Step<f64, f64> {
990 let x_value = 1.5;
991 let time_unit = TimeFrame::Day;
992 let expiration_date = ExpirationDate::Days(pos_or_panic!(30.0));
993 let y_value = 100.0;
994
995 Step::new(x_value, time_unit, expiration_date, y_value)
996 }
997
998 #[test]
999 fn test_step_serialization_with_days() {
1000 let step = create_test_step();
1001
1002 let serialized = serde_json::to_string(&step).unwrap();
1004
1005 let json_value: Value = serde_json::from_str(&serialized).unwrap();
1007
1008 assert!(json_value.is_object());
1010 assert!(json_value.get("x").is_some());
1011 assert!(json_value.get("y").is_some());
1012
1013 let x = &json_value["x"];
1015 assert_eq!(x["index"], 0);
1016 assert_eq!(x["step_size_in_time"], "1.5");
1017
1018 let y = &json_value["y"];
1020 assert_eq!(y["index"], 0);
1021 assert_eq!(y["value"], "100");
1022 }
1023
1024 #[test]
1025 fn test_step_with_datetime_is_normalized() {
1026 let x_value = 2.5;
1029 let time_unit = TimeFrame::Hour;
1030 let expiration_date =
1031 ExpirationDate::DateTime(Utc.with_ymd_and_hms(2024, 12, 31, 23, 59, 59).unwrap());
1032 let y_value = 200.0;
1033
1034 let step = Step::new(x_value, time_unit, expiration_date, y_value);
1035 match step.x.datetime() {
1036 ExpirationDate::Days(_) => {}
1037 other => panic!("expected ExpirationDate::Days, got {other:?}"),
1038 }
1039 }
1040
1041 #[test]
1042 fn test_step_serialize() {
1043 let x_value = pos_or_panic!(5.0);
1044 let time_unit = TimeFrame::Day;
1045 let datetime = ExpirationDate::Days(pos_or_panic!(30.0));
1046 let y_value = 42.5;
1047 let step = Step::new(x_value, time_unit, datetime, y_value);
1048
1049 let serialized = serde_json::to_string(&step).unwrap();
1051 assert_eq!(
1052 serialized,
1053 r#"{"x":{"index":0,"step_size_in_time":"5","time_unit":"Day","datetime":{"days":30.0}},"y":{"index":0,"value":"42.5"}}"#
1054 );
1055 }
1056
1057 #[test]
1058 fn test_step_pretty_serialization() {
1059 let step = create_test_step();
1060
1061 let serialized = serde_json::to_string_pretty(&step).unwrap();
1063
1064 assert!(serialized.contains("{\n"));
1066 assert!(serialized.contains(" \"x\": {"));
1067 assert!(serialized.contains(" \"y\": {"));
1068
1069 let deserialized: Value = serde_json::from_str(&serialized).unwrap();
1071 assert_eq!(deserialized["x"]["step_size_in_time"], "1.5");
1072 assert_eq!(deserialized["y"]["value"], "100");
1073 }
1074}