wickra_core/indicators/
fama.rs1use crate::error::Result;
4use crate::indicators::mama::Mama;
5use crate::traits::Indicator;
6
7#[derive(Debug, Clone)]
29pub struct Fama {
30 inner: Mama,
31 last_value: Option<f64>,
32}
33
34impl Fama {
35 pub fn new(fast_limit: f64, slow_limit: f64) -> Result<Self> {
41 Ok(Self {
42 inner: Mama::new(fast_limit, slow_limit)?,
43 last_value: None,
44 })
45 }
46
47 pub fn classic() -> Self {
49 Self {
50 inner: Mama::classic(),
51 last_value: None,
52 }
53 }
54
55 pub const fn limits(&self) -> (f64, f64) {
57 self.inner.limits()
58 }
59
60 pub const fn value(&self) -> Option<f64> {
62 self.last_value
63 }
64}
65
66impl Indicator for Fama {
67 type Input = f64;
68 type Output = f64;
69
70 fn update(&mut self, input: f64) -> Option<f64> {
71 let v = self.inner.update(input)?.fama;
72 self.last_value = Some(v);
73 Some(v)
74 }
75
76 fn reset(&mut self) {
77 self.inner.reset();
78 self.last_value = None;
79 }
80
81 fn warmup_period(&self) -> usize {
82 self.inner.warmup_period()
83 }
84
85 fn is_ready(&self) -> bool {
86 self.last_value.is_some()
87 }
88
89 fn name(&self) -> &'static str {
90 "FAMA"
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97 use crate::error::Error;
98 use crate::traits::BatchExt;
99
100 #[test]
101 fn rejects_invalid_limits() {
102 assert!(matches!(
103 Fama::new(0.0, 0.05),
104 Err(Error::InvalidPeriod { .. })
105 ));
106 assert!(matches!(
107 Fama::new(0.05, 0.5),
108 Err(Error::InvalidPeriod { .. })
109 ));
110 }
111
112 #[test]
113 fn new_with_valid_limits_constructs_via_mama() {
114 let mut fama = Fama::new(0.5, 0.05).expect("valid Mama limits");
118 assert_eq!(fama.limits(), (0.5, 0.05));
119 for i in 0..60 {
120 fama.update(100.0 + (f64::from(i) * 0.3).sin() * 5.0);
121 }
122 assert!(fama.value().is_some());
123 }
124
125 #[test]
126 fn accessors_and_metadata() {
127 let mut fama = Fama::classic();
128 assert_eq!(fama.limits(), (0.5, 0.05));
129 assert_eq!(fama.warmup_period(), 33);
130 assert_eq!(fama.name(), "FAMA");
131 assert!(!fama.is_ready());
132 for i in 0..60 {
133 fama.update(100.0 + (f64::from(i) * 0.3).sin() * 5.0);
134 }
135 assert!(fama.is_ready());
136 assert!(fama.value().is_some());
137 }
138
139 #[test]
140 fn batch_equals_streaming() {
141 let prices: Vec<f64> = (0..120)
142 .map(|i| 100.0 + (f64::from(i) * 0.25).cos() * 5.0)
143 .collect();
144 let mut a = Fama::classic();
145 let mut b = Fama::classic();
146 let batch = a.batch(&prices);
147 let streamed: Vec<_> = prices.iter().map(|p| b.update(*p)).collect();
148 assert_eq!(batch, streamed);
149 }
150
151 #[test]
152 fn ignores_non_finite_input() {
153 let mut fama = Fama::classic();
154 let prices: Vec<f64> = (0..100)
155 .map(|i| 100.0 + (f64::from(i) * 0.3).sin() * 5.0)
156 .collect();
157 fama.batch(&prices);
158 let before = fama.value();
159 assert!(before.is_some());
160 assert_eq!(fama.update(f64::NAN), before);
161 }
162
163 #[test]
164 fn reset_clears_state() {
165 let mut fama = Fama::classic();
166 let prices: Vec<f64> = (0..100)
167 .map(|i| 100.0 + (f64::from(i) * 0.3).sin() * 5.0)
168 .collect();
169 fama.batch(&prices);
170 assert!(fama.is_ready());
171 fama.reset();
172 assert!(!fama.is_ready());
173 }
174}