barter/statistic/metric/drawdown/
max.rs1use crate::statistic::metric::drawdown::Drawdown;
2use derive_more::Constructor;
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, PartialEq, PartialOrd, Default, Deserialize, Serialize, Constructor)]
13pub struct MaxDrawdown(pub Drawdown);
14
15#[derive(Debug, Clone, PartialEq, PartialOrd, Default, Deserialize, Serialize, Constructor)]
17pub struct MaxDrawdownGenerator {
18 pub max: Option<MaxDrawdown>,
19}
20
21impl MaxDrawdownGenerator {
22 pub fn init(drawdown: Drawdown) -> Self {
24 Self {
25 max: Some(MaxDrawdown(drawdown)),
26 }
27 }
28
29 pub fn update(&mut self, next_drawdown: &Drawdown) {
32 let max = match self.max.take() {
33 Some(current) => {
34 if next_drawdown.value.abs() > current.0.value.abs() {
35 MaxDrawdown(next_drawdown.clone())
36 } else {
37 current
38 }
39 }
40 None => MaxDrawdown(next_drawdown.clone()),
41 };
42
43 self.max = Some(max);
44 }
45
46 pub fn generate(&self) -> Option<MaxDrawdown> {
48 self.max.clone()
49 }
50}
51
52#[cfg(test)]
53mod tests {
54 use super::*;
55 use crate::test_utils::time_plus_days;
56 use chrono::{DateTime, Utc};
57 use rust_decimal_macros::dec;
58
59 #[test]
60 fn test_max_drawdown_generator_update() {
61 struct TestCase {
62 input: Drawdown,
63 expected_state: MaxDrawdownGenerator,
64 expected_output: Option<MaxDrawdown>,
65 }
66
67 let base_time = DateTime::<Utc>::MIN_UTC;
68
69 let mut generator = MaxDrawdownGenerator::default();
70
71 let cases = vec![
72 TestCase {
74 input: Drawdown {
75 value: dec!(-0.227272727272727273), time_start: base_time,
77 time_end: time_plus_days(base_time, 2),
78 },
79 expected_state: MaxDrawdownGenerator {
80 max: Some(MaxDrawdown::new(Drawdown {
81 value: dec!(-0.227272727272727273),
82 time_start: base_time,
83 time_end: time_plus_days(base_time, 2),
84 })),
85 },
86 expected_output: Some(MaxDrawdown::new(Drawdown {
87 value: dec!(-0.227272727272727273),
88 time_start: base_time,
89 time_end: time_plus_days(base_time, 2),
90 })),
91 },
92 TestCase {
94 input: Drawdown {
95 value: dec!(-0.55), time_start: base_time,
97 time_end: time_plus_days(base_time, 3),
98 },
99 expected_state: MaxDrawdownGenerator {
100 max: Some(MaxDrawdown::new(Drawdown {
101 value: dec!(-0.55),
102 time_start: base_time,
103 time_end: time_plus_days(base_time, 3),
104 })),
105 },
106 expected_output: Some(MaxDrawdown::new(Drawdown {
107 value: dec!(-0.55),
108 time_start: base_time,
109 time_end: time_plus_days(base_time, 3),
110 })),
111 },
112 TestCase {
114 input: Drawdown {
115 value: dec!(-0.033333333333333333), time_start: base_time,
117 time_end: time_plus_days(base_time, 3),
118 },
119 expected_state: MaxDrawdownGenerator {
120 max: Some(MaxDrawdown::new(Drawdown {
121 value: dec!(-0.55),
122 time_start: base_time,
123 time_end: time_plus_days(base_time, 3),
124 })),
125 },
126 expected_output: Some(MaxDrawdown::new(Drawdown {
127 value: dec!(-0.55),
128 time_start: base_time,
129 time_end: time_plus_days(base_time, 3),
130 })),
131 },
132 TestCase {
134 input: Drawdown {
135 value: dec!(-0.99999), time_start: base_time,
137 time_end: time_plus_days(base_time, 3),
138 },
139 expected_state: MaxDrawdownGenerator {
140 max: Some(MaxDrawdown::new(Drawdown {
141 value: dec!(-0.99999),
142 time_start: base_time,
143 time_end: time_plus_days(base_time, 3),
144 })),
145 },
146 expected_output: Some(MaxDrawdown::new(Drawdown {
147 value: dec!(-0.99999),
148 time_start: base_time,
149 time_end: time_plus_days(base_time, 3),
150 })),
151 },
152 ];
153
154 for (index, test) in cases.into_iter().enumerate() {
155 generator.update(&test.input);
156
157 assert_eq!(
159 generator, test.expected_state,
160 "TC{index} generator state failed"
161 );
162 assert_eq!(
163 generator.generate(),
164 test.expected_output,
165 "TC{index} generated output failed"
166 );
167 }
168 }
169}