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 {
212 self.x.days_left().unwrap_or_else(|e| {
216 tracing::warn!("get_graph_x_in_days_left: days_left failed: {e}; using 0");
217 Positive::ZERO
218 })
219 }
220
221 pub fn get_graph_y_value(&self) -> Result<Positive, SimulationError> {
237 self.y.positive()
238 }
239
240 pub fn get_y_step(&self) -> &Ystep<Y> {
251 &self.y
252 }
253
254 pub fn get_x_step(&self) -> &Xstep<X> {
261 &self.x
262 }
263
264 pub fn get_value(&self) -> &Y {
273 self.y.value()
274 }
275
276 pub fn get_index(&self) -> &X {
286 self.x.step_size_in_time()
287 }
288
289 pub fn get_positive_value(&self) -> Result<Positive, SimulationError> {
305 self.y.positive()
306 }
307}
308
309impl<X, Y> Display for Step<X, Y>
310where
311 X: Copy + TryInto<Positive> + AddAssign + Display,
312 Y: TryInto<Positive> + Display + Clone,
313{
314 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
315 writeln!(f, "Step {{ x: {}, y: {} }}", self.x, self.y)
316 }
317}
318
319impl<X, Y> Serialize for Step<X, Y>
320where
321 X: Copy + TryInto<Positive> + AddAssign + Display + Serialize,
322 Y: TryInto<Positive> + Display + Serialize + Clone,
323{
324 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
325 where
326 S: Serializer,
327 {
328 let mut state = serializer.serialize_struct("Step", 2)?;
329 state.serialize_field("x", &self.x)?;
330 state.serialize_field("y", &self.y)?;
331 state.end()
332 }
333}
334
335#[cfg(test)]
336mod tests {
337 use super::*;
338 use optionstratlib_core::pos_or_panic;
339
340 #[derive(Debug, Copy, Clone, PartialEq)]
342 struct TestValue(u32);
343
344 impl From<TestValue> for Positive {
345 fn from(val: TestValue) -> Self {
346 Positive::new(val.0 as f64).unwrap()
347 }
348 }
349
350 impl AddAssign for TestValue {
351 fn add_assign(&mut self, other: Self) {
352 self.0 += other.0;
353 }
354 }
355
356 impl Display for TestValue {
357 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
358 write!(f, "{}", self.0)
359 }
360 }
361
362 #[test]
363 fn test_step_new() {
364 let value = TestValue(5);
366 let time_unit = TimeFrame::Day;
367 let datetime = ExpirationDate::Days(pos_or_panic!(30.0));
368
369 let step = Xstep::new(value, time_unit, datetime);
370
371 assert_eq!(*step.index(), 0);
372 assert_eq!(step.step_size_in_time(), &TestValue(5));
373 assert_eq!(*step.time_unit(), TimeFrame::Day);
374 assert_eq!(*step.datetime(), ExpirationDate::Days(pos_or_panic!(30.0)));
375 }
376
377 #[cfg(feature = "clock")]
378 #[test]
379 fn test_step_new_with_datetime_is_normalized_to_days() {
380 use chrono::{Duration, Utc};
384
385 let value = TestValue(5);
386 let time_unit = TimeFrame::Day;
387 let dt = Utc::now() + Duration::days(30);
388 let datetime = ExpirationDate::DateTime(dt);
389
390 let step = Xstep::new(value, time_unit, datetime);
391 match step.datetime() {
392 ExpirationDate::Days(_) => {}
393 other => panic!("expected ExpirationDate::Days, got {other:?}"),
394 }
395 }
396
397 #[test]
398 fn test_step_next() {
399 let value = TestValue(5);
403 let time_unit = TimeFrame::Day;
404 let datetime = ExpirationDate::Days(pos_or_panic!(30.0));
405
406 let step = Xstep::new(value, time_unit, datetime);
407 let next_step = step.next().unwrap();
408
409 assert_eq!(*step.index(), 0);
410 assert_eq!(step.step_size_in_time(), &TestValue(5));
411 assert_eq!(*step.time_unit(), TimeFrame::Day);
412 assert_eq!(*step.datetime(), ExpirationDate::Days(pos_or_panic!(30.0)));
413
414 assert_eq!(*next_step.index(), 1);
415 assert_eq!(next_step.step_size_in_time(), &TestValue(5));
416 assert_eq!(*next_step.time_unit(), TimeFrame::Day);
417 assert_eq!(
418 *next_step.datetime(),
419 ExpirationDate::Days(pos_or_panic!(25.0))
420 );
421 }
422
423 #[test]
424 fn test_step_next_with_weeks() {
425 let value = TestValue(2);
429 let time_unit = TimeFrame::Week;
430 let datetime = ExpirationDate::Days(pos_or_panic!(30.0));
431
432 let step = Xstep::new(value, time_unit, datetime);
433 let next_step = step.next().unwrap();
434
435 assert_eq!(*step.index(), 0);
436 assert_eq!(step.step_size_in_time(), &TestValue(2));
437 assert_eq!(*step.time_unit(), TimeFrame::Week);
438 assert_eq!(*step.datetime(), ExpirationDate::Days(pos_or_panic!(30.0)));
439
440 assert_eq!(*next_step.index(), 1);
441 assert_eq!(next_step.step_size_in_time(), &TestValue(2));
442 assert_eq!(*next_step.time_unit(), TimeFrame::Week);
443 assert_eq!(
444 *next_step.datetime(),
445 ExpirationDate::Days(pos_or_panic!(16.0))
446 );
447 }
448
449 #[test]
450 fn test_step_previous() {
451 let value = TestValue(5);
455 let time_unit = TimeFrame::Day;
456 let datetime = ExpirationDate::Days(pos_or_panic!(30.0));
457
458 let step = Xstep::new(value, time_unit, datetime);
459 let prev_step = step.previous().unwrap();
460
461 assert_eq!(*step.index(), 0);
462 assert_eq!(step.step_size_in_time(), &TestValue(5));
463 assert_eq!(*step.time_unit(), TimeFrame::Day);
464 assert_eq!(*step.datetime(), ExpirationDate::Days(pos_or_panic!(30.0)));
465
466 assert_eq!(*prev_step.index(), -1);
467 assert_eq!(prev_step.step_size_in_time(), &TestValue(5));
468 assert_eq!(*prev_step.time_unit(), TimeFrame::Day);
469 assert_eq!(
470 *prev_step.datetime(),
471 ExpirationDate::Days(pos_or_panic!(35.0))
472 );
473 }
474
475 #[test]
476 fn test_step_previouse_with_months() {
477 let value = TestValue(3);
482 let time_unit = TimeFrame::Month;
483 let datetime = ExpirationDate::Days(pos_or_panic!(30.0));
484
485 let step = Xstep::new(value, time_unit, datetime);
486 let prev_step = step.previous().unwrap();
487
488 assert_eq!(*step.index(), 0);
489 assert_eq!(step.step_size_in_time(), &TestValue(3));
490 assert_eq!(*step.time_unit(), TimeFrame::Month);
491 assert_eq!(*step.datetime(), ExpirationDate::Days(pos_or_panic!(30.0)));
492
493 assert_eq!(*prev_step.index(), -1);
494 assert_eq!(prev_step.step_size_in_time(), &TestValue(3));
495 assert_eq!(*prev_step.time_unit(), TimeFrame::Month);
496 assert_eq!(
497 *prev_step.datetime(),
498 ExpirationDate::Days(pos_or_panic!(121.25))
499 );
500 }
501
502 #[test]
503 fn test_multiple_steps() {
504 let value = TestValue(10);
506 let time_unit = TimeFrame::Day;
507 let datetime = ExpirationDate::Days(Positive::HUNDRED);
508
509 let step = Xstep::new(value, time_unit, datetime);
510 let step1 = step.next().unwrap();
511 let step2 = step1.next().unwrap();
512 let step3 = step2.next().unwrap();
513
514 assert_eq!(*step1.index(), 1);
515 assert_eq!(*step1.datetime(), ExpirationDate::Days(pos_or_panic!(90.0)));
516 assert_eq!(*step2.index(), 2);
517 assert_eq!(*step2.datetime(), ExpirationDate::Days(pos_or_panic!(80.0)));
518 assert_eq!(*step3.index(), 3);
519 assert_eq!(*step3.datetime(), ExpirationDate::Days(pos_or_panic!(70.0)));
520 }
521
522 #[test]
523 fn test_forward_and_backward() {
524 let value = TestValue(5);
526 let time_unit = TimeFrame::Day;
527 let datetime = ExpirationDate::Days(pos_or_panic!(50.0));
528
529 let original = Xstep::new(value, time_unit, datetime);
530 let forward = original.next().unwrap();
531 let back_to_original = forward.previous().unwrap();
532
533 assert_eq!(*original.index(), 0);
534 assert_eq!(*forward.index(), 1);
535 assert_eq!(*back_to_original.index(), 0);
536 assert_eq!(
537 *original.datetime(),
538 ExpirationDate::Days(pos_or_panic!(50.0))
539 );
540 assert_eq!(
541 *forward.datetime(),
542 ExpirationDate::Days(pos_or_panic!(45.0))
543 );
544 assert_eq!(
545 *back_to_original.datetime(),
546 ExpirationDate::Days(pos_or_panic!(50.0))
547 );
548 }
549}
550
551#[cfg(test)]
552mod tests_positive {
553 use super::*;
554 use optionstratlib_core::pos_or_panic;
555
556 #[test]
557 fn test_step_new() {
558 let value = pos_or_panic!(5.0);
560 let time_unit = TimeFrame::Day;
561 let datetime = ExpirationDate::Days(pos_or_panic!(30.0));
562
563 let step = Xstep::new(value, time_unit, datetime);
564
565 assert_eq!(*step.index(), 0);
566 assert_eq!(*step.step_size_in_time(), pos_or_panic!(5.0));
567 assert_eq!(*step.time_unit(), TimeFrame::Day);
568 assert_eq!(*step.datetime(), ExpirationDate::Days(pos_or_panic!(30.0)));
569 }
570
571 #[cfg(feature = "clock")]
572 #[test]
573 fn test_step_new_with_datetime_is_normalized_to_days() {
574 use chrono::{Duration, Utc};
577
578 let value = pos_or_panic!(5.0);
579 let time_unit = TimeFrame::Day;
580 let dt = Utc::now() + Duration::days(30);
581 let datetime = ExpirationDate::DateTime(dt);
582
583 let step = Xstep::new(value, time_unit, datetime);
584 match step.datetime() {
585 ExpirationDate::Days(_) => {}
586 other => panic!("expected ExpirationDate::Days, got {other:?}"),
587 }
588 }
589
590 #[test]
591 fn test_step_next() {
592 let value = pos_or_panic!(5.0);
596 let time_unit = TimeFrame::Day;
597 let datetime = ExpirationDate::Days(pos_or_panic!(30.0));
598
599 let step = Xstep::new(value, time_unit, datetime);
600 let next_step = step.next().unwrap();
601
602 assert_eq!(*next_step.index(), 1);
603 assert_eq!(*next_step.step_size_in_time(), pos_or_panic!(5.0));
604 assert_eq!(*next_step.time_unit(), TimeFrame::Day);
605 assert_eq!(
606 *next_step.datetime(),
607 ExpirationDate::Days(pos_or_panic!(25.0))
608 );
609 }
610
611 #[test]
612 fn test_step_next_with_weeks() {
613 let value = Positive::TWO;
617 let time_unit = TimeFrame::Week;
618 let datetime = ExpirationDate::Days(pos_or_panic!(30.0));
619
620 let step = Xstep::new(value, time_unit, datetime);
621 let next_step = step.next().unwrap();
622
623 assert_eq!(*next_step.index(), 1);
624 assert_eq!(*next_step.step_size_in_time(), Positive::TWO);
625 assert_eq!(*next_step.time_unit(), TimeFrame::Week);
626 assert_eq!(
627 *next_step.datetime(),
628 ExpirationDate::Days(pos_or_panic!(16.0))
629 );
630 }
631
632 #[test]
633 fn test_step_previous() {
634 let value = pos_or_panic!(5.0);
638 let time_unit = TimeFrame::Day;
639 let datetime = ExpirationDate::Days(pos_or_panic!(30.0));
640
641 let step = Xstep::new(value, time_unit, datetime);
642 let prev_step = step.previous().unwrap();
643
644 assert_eq!(*prev_step.index(), -1);
645 assert_eq!(*prev_step.step_size_in_time(), pos_or_panic!(5.0));
646 assert_eq!(*prev_step.time_unit(), TimeFrame::Day);
647 assert_eq!(
648 *prev_step.datetime(),
649 ExpirationDate::Days(pos_or_panic!(35.0))
650 );
651 }
652
653 #[test]
654 fn test_step_previouse_with_months() {
655 let value = pos_or_panic!(3.0);
660 let time_unit = TimeFrame::Month;
661 let datetime = ExpirationDate::Days(pos_or_panic!(30.0));
662
663 let step = Xstep::new(value, time_unit, datetime);
664 let prev_step = step.previous().unwrap();
665
666 assert_eq!(*prev_step.index(), -1);
667 assert_eq!(*prev_step.step_size_in_time(), pos_or_panic!(3.0));
668 assert_eq!(*prev_step.time_unit(), TimeFrame::Month);
669 assert_eq!(
670 *prev_step.datetime(),
671 ExpirationDate::Days(pos_or_panic!(121.25))
672 );
673 }
674
675 #[test]
676 fn test_multiple_steps() {
677 let value = Positive::TEN;
679 let time_unit = TimeFrame::Day;
680 let datetime = ExpirationDate::Days(Positive::HUNDRED);
681
682 let step = Xstep::new(value, time_unit, datetime);
683 let step1 = step.next().unwrap();
684 let step2 = step1.next().unwrap();
685 let step3 = step2.next().unwrap();
686
687 assert_eq!(*step1.index(), 1);
688 assert_eq!(*step1.datetime(), ExpirationDate::Days(pos_or_panic!(90.0)));
689 assert_eq!(*step2.index(), 2);
690 assert_eq!(*step2.datetime(), ExpirationDate::Days(pos_or_panic!(80.0)));
691 assert_eq!(*step3.index(), 3);
692 assert_eq!(*step3.datetime(), ExpirationDate::Days(pos_or_panic!(70.0)));
693 }
694
695 #[test]
696 fn test_forward_and_backward() {
697 let value = pos_or_panic!(5.0);
699 let time_unit = TimeFrame::Day;
700 let datetime = ExpirationDate::Days(pos_or_panic!(50.0));
701
702 let original = Xstep::new(value, time_unit, datetime);
703 let forward = original.next().unwrap();
704 let back_to_original = forward.previous().unwrap();
705
706 assert_eq!(*original.index(), 0);
707 assert_eq!(*forward.index(), 1);
708 assert_eq!(*back_to_original.index(), 0);
709 assert_eq!(
710 *original.datetime(),
711 ExpirationDate::Days(pos_or_panic!(50.0))
712 );
713 assert_eq!(
714 *forward.datetime(),
715 ExpirationDate::Days(pos_or_panic!(45.0))
716 );
717 assert_eq!(
718 *back_to_original.datetime(),
719 ExpirationDate::Days(pos_or_panic!(50.0))
720 );
721 }
722}
723
724#[cfg(test)]
725mod tests_step {
726 use super::*;
727 use optionstratlib_core::pos_or_panic;
728
729 #[derive(Debug, Copy, Clone, PartialEq)]
731 struct TestValue(u32);
732
733 impl From<TestValue> for Positive {
734 fn from(val: TestValue) -> Self {
735 Positive::new(val.0 as f64).unwrap()
736 }
737 }
738
739 impl AddAssign for TestValue {
740 fn add_assign(&mut self, other: Self) {
741 self.0 += other.0;
742 }
743 }
744
745 impl Display for TestValue {
746 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
747 write!(f, "{}", self.0)
748 }
749 }
750
751 #[test]
752 fn test_step_new() {
753 let x_value = TestValue(5);
755 let time_unit = TimeFrame::Day;
756 let datetime = ExpirationDate::Days(pos_or_panic!(30.0));
757 let y_value = 42.5;
758
759 let step = Step::new(x_value, time_unit, datetime, y_value);
760
761 assert_eq!(*step.x.index(), 0);
763 assert_eq!(step.x.step_size_in_time(), &TestValue(5));
764 assert_eq!(*step.x.time_unit(), TimeFrame::Day);
765 assert_eq!(
766 *step.x.datetime(),
767 ExpirationDate::Days(pos_or_panic!(30.0))
768 );
769
770 assert_eq!(*step.y.index(), 0);
772 assert_eq!(*step.y.value(), 42.5);
773
774 let result_next = step.next(100.0);
775 assert!(result_next.is_ok());
776 let next = result_next.unwrap();
777
778 assert!(
779 next.to_string()
780 .contains("Step { x: Xstep { index: 1, value: 5, time_unit: Day, datetime:")
781 );
782
783 let previous_next = step.previous(100.0);
784 assert!(previous_next.is_ok());
785 let previous = previous_next.unwrap();
786
787 assert!(
788 previous
789 .to_string()
790 .contains("Step { x: Xstep { index: -1, value: 5, time_unit: Day, datetime:")
791 );
792
793 assert_eq!(next.get_graph_x_value().unwrap(), Decimal::ONE);
794 assert_eq!(previous.get_graph_x_value().unwrap(), Decimal::NEGATIVE_ONE);
795 }
796
797 #[test]
798 fn test_step_next() {
799 let x_value = TestValue(5);
801 let time_unit = TimeFrame::Day;
802 let datetime = ExpirationDate::Days(pos_or_panic!(30.0));
803 let y_value = 42.5;
804
805 let step = Step::new(x_value, time_unit, datetime, y_value);
806
807 let new_y_value = 45.0;
809 let next_step = step.next(new_y_value).unwrap();
810
811 assert_eq!(*step.x.index(), 0);
813 assert_eq!(step.x.step_size_in_time(), &TestValue(5));
814 assert_eq!(*step.x.time_unit(), TimeFrame::Day);
815 assert_eq!(
816 *step.x.datetime(),
817 ExpirationDate::Days(pos_or_panic!(30.0))
818 );
819
820 assert_eq!(*step.y.index(), 0);
822 assert_eq!(*step.y.value(), 42.5);
823
824 assert_eq!(*next_step.x.index(), 1);
825 assert_eq!(next_step.x.step_size_in_time(), &TestValue(5));
826 assert_eq!(*next_step.x.time_unit(), TimeFrame::Day);
827 assert_eq!(
828 *next_step.x.datetime(),
829 ExpirationDate::Days(pos_or_panic!(25.0))
830 );
831
832 assert_eq!(*next_step.y.index(), 1);
834 assert_eq!(*next_step.y.value(), 45.0);
835 }
836
837 #[test]
838 fn test_step_previous() {
839 let x_value = TestValue(5);
841 let time_unit = TimeFrame::Day;
842 let datetime = ExpirationDate::Days(pos_or_panic!(30.0));
843 let y_value = 42.5;
844
845 let step = Step::new(x_value, time_unit, datetime, y_value);
846
847 let new_y_value = 38.0;
849 let prev_step = step.previous(new_y_value).unwrap();
850
851 assert_eq!(*step.x.index(), 0);
853 assert_eq!(step.x.step_size_in_time(), &TestValue(5));
854 assert_eq!(*step.x.time_unit(), TimeFrame::Day);
855 assert_eq!(
856 *step.x.datetime(),
857 ExpirationDate::Days(pos_or_panic!(30.0))
858 );
859
860 assert_eq!(*prev_step.y.value(), 38.0);
862 }
863
864 #[test]
865 fn test_step_with_timeframe_conversion() {
866 let x_value = TestValue(2);
868 let time_unit = TimeFrame::Week;
869 let datetime = ExpirationDate::Days(pos_or_panic!(30.0));
870 let y_value = 100.0;
871
872 let step = Step::new(x_value, time_unit, datetime, y_value);
873
874 let next_y = 105.0;
876 let next_step = step.next(next_y).unwrap();
877
878 assert_eq!(
879 *next_step.x.datetime(),
880 ExpirationDate::Days(pos_or_panic!(16.0))
881 );
882 assert_eq!(*next_step.y.value(), 105.0);
883
884 let prev_y = 95.0;
886 let prev_step = step.previous(prev_y).unwrap();
887
888 assert_eq!(
889 *prev_step.x.datetime(),
890 ExpirationDate::Days(pos_or_panic!(44.0))
891 );
892 assert_eq!(*prev_step.y.value(), 95.0);
893 }
894
895 #[test]
896 fn test_step_chain() {
897 let x_value = TestValue(10);
899 let time_unit = TimeFrame::Day;
900 let datetime = ExpirationDate::Days(Positive::HUNDRED);
901 let y_value = 50.0;
902
903 let initial = Step::new(x_value, time_unit, datetime, y_value);
904
905 let step1 = initial.next(55.0).unwrap();
907 let step2 = step1.next(60.0).unwrap();
908 let step3 = step2.next(65.0).unwrap();
909
910 assert_eq!(*step1.x.index(), 1);
912 assert_eq!(
913 *step1.x.datetime(),
914 ExpirationDate::Days(pos_or_panic!(90.0))
915 );
916 assert_eq!(*step1.y.index(), 1);
917 assert_eq!(*step1.y.value(), 55.0);
918 assert_eq!(*step2.x.index(), 2);
919 assert_eq!(
920 *step2.x.datetime(),
921 ExpirationDate::Days(pos_or_panic!(80.0))
922 );
923 assert_eq!(*step2.y.index(), 2);
924 assert_eq!(*step2.y.value(), 60.0);
925 assert_eq!(*step3.x.index(), 3);
926 assert_eq!(
927 *step3.x.datetime(),
928 ExpirationDate::Days(pos_or_panic!(70.0))
929 );
930 assert_eq!(*step3.y.index(), 3);
931 assert_eq!(*step3.y.value(), 65.0);
932 }
933
934 #[test]
935 fn test_with_positive_type() {
936 let x_value = pos_or_panic!(5.0);
938 let time_unit = TimeFrame::Day;
939 let datetime = ExpirationDate::Days(pos_or_panic!(30.0));
940 let y_value = pos_or_panic!(50.0);
941
942 let step = Step::new(x_value, time_unit, datetime, y_value);
943
944 let next_step = step.next(pos_or_panic!(55.0)).unwrap();
945
946 assert_eq!(
947 *next_step.x.datetime(),
948 ExpirationDate::Days(pos_or_panic!(25.0))
949 );
950 assert_eq!(*next_step.y.value(), pos_or_panic!(55.0));
951 }
952
953 #[test]
954 fn test_with_zero_days() {
955 let x_value = pos_or_panic!(5.0);
957 let time_unit = TimeFrame::Day;
958 let datetime = ExpirationDate::Days(Positive::ZERO);
959 let y_value = pos_or_panic!(50.0);
960 let step = Step::new(x_value, time_unit, datetime, y_value);
961 let result = step.next(pos_or_panic!(55.0));
962 assert!(result.is_err());
963 let step_x = step.get_x_step();
964 assert_eq!(*step_x.index(), 0);
965 assert_eq!(*step_x.step_size_in_time(), pos_or_panic!(5.0));
966 let result = step_x.next();
967 assert!(result.is_err());
968 }
969
970 #[test]
971 fn next_ok_increments_indices_and_builds_self() {
972 let x_value = pos_or_panic!(5.0);
973 let time_unit = TimeFrame::Day;
974 let datetime = ExpirationDate::Days(Positive::TWO);
975 let y_value = pos_or_panic!(50.0);
976 let step = Step::new(x_value, time_unit, datetime, y_value);
977
978 let new_y = pos_or_panic!(55.0);
979 let next = step.next(new_y).unwrap();
980
981 assert_eq!(next.get_value(), &new_y);
982 assert_eq!(*next.x.index(), 1);
983 assert_eq!(*next.y.index(), step.y.index() + 1);
984 }
985}
986
987#[cfg(test)]
988mod tests_step_serialization {
989 use super::*;
990
991 #[cfg(feature = "clock")]
992 use chrono::{TimeZone, Utc};
993 use optionstratlib_core::pos_or_panic;
994 use serde_json::{self, Value};
995
996 fn create_test_step() -> Step<f64, f64> {
998 let x_value = 1.5;
999 let time_unit = TimeFrame::Day;
1000 let expiration_date = ExpirationDate::Days(pos_or_panic!(30.0));
1001 let y_value = 100.0;
1002
1003 Step::new(x_value, time_unit, expiration_date, y_value)
1004 }
1005
1006 #[test]
1007 fn test_step_serialization_with_days() {
1008 let step = create_test_step();
1009
1010 let serialized = serde_json::to_string(&step).unwrap();
1012
1013 let json_value: Value = serde_json::from_str(&serialized).unwrap();
1015
1016 assert!(json_value.is_object());
1018 assert!(json_value.get("x").is_some());
1019 assert!(json_value.get("y").is_some());
1020
1021 let x = &json_value["x"];
1023 assert_eq!(x["index"], 0);
1024 assert_eq!(x["step_size_in_time"], "1.5");
1025
1026 let y = &json_value["y"];
1028 assert_eq!(y["index"], 0);
1029 assert_eq!(y["value"], "100");
1030 }
1031
1032 #[cfg(feature = "clock")]
1033 #[test]
1034 fn test_step_with_datetime_is_normalized() {
1035 let x_value = 2.5;
1038 let time_unit = TimeFrame::Hour;
1039 let expiration_date =
1040 ExpirationDate::DateTime(Utc.with_ymd_and_hms(2024, 12, 31, 23, 59, 59).unwrap());
1041 let y_value = 200.0;
1042
1043 let step = Step::new(x_value, time_unit, expiration_date, y_value);
1044 match step.x.datetime() {
1045 ExpirationDate::Days(_) => {}
1046 other => panic!("expected ExpirationDate::Days, got {other:?}"),
1047 }
1048 }
1049
1050 #[test]
1051 fn test_step_serialize() {
1052 let x_value = pos_or_panic!(5.0);
1053 let time_unit = TimeFrame::Day;
1054 let datetime = ExpirationDate::Days(pos_or_panic!(30.0));
1055 let y_value = 42.5;
1056 let step = Step::new(x_value, time_unit, datetime, y_value);
1057
1058 let serialized = serde_json::to_string(&step).unwrap();
1060 assert_eq!(
1061 serialized,
1062 r#"{"x":{"index":0,"step_size_in_time":"5","time_unit":"Day","datetime":{"days":30.0}},"y":{"index":0,"value":"42.5"}}"#
1063 );
1064 }
1065
1066 #[test]
1067 fn test_step_pretty_serialization() {
1068 let step = create_test_step();
1069
1070 let serialized = serde_json::to_string_pretty(&step).unwrap();
1072
1073 assert!(serialized.contains("{\n"));
1075 assert!(serialized.contains(" \"x\": {"));
1076 assert!(serialized.contains(" \"y\": {"));
1077
1078 let deserialized: Value = serde_json::from_str(&serialized).unwrap();
1080 assert_eq!(deserialized["x"]["step_size_in_time"], "1.5");
1081 assert_eq!(deserialized["y"]["value"], "100");
1082 }
1083}