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 #[inline]
71 fn update(&mut self, input: f64) -> Option<f64> {
72 let v = self.inner.update(input)?.fama;
73 self.last_value = Some(v);
74 Some(v)
75 }
76
77 fn reset(&mut self) {
78 self.inner.reset();
79 self.last_value = None;
80 }
81
82 #[inline]
83 fn warmup_period(&self) -> usize {
84 self.inner.warmup_period()
85 }
86
87 #[inline]
88 fn is_ready(&self) -> bool {
89 self.last_value.is_some()
90 }
91
92 #[inline]
93 fn name(&self) -> &'static str {
94 "FAMA"
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101 use crate::error::Error;
102 use crate::traits::BatchExt;
103
104 #[test]
105 fn rejects_invalid_limits() {
106 assert!(matches!(
107 Fama::new(0.0, 0.05),
108 Err(Error::InvalidPeriod { .. })
109 ));
110 assert!(matches!(
111 Fama::new(0.05, 0.5),
112 Err(Error::InvalidPeriod { .. })
113 ));
114 }
115
116 #[test]
117 fn new_with_valid_limits_constructs_via_mama() {
118 let mut fama = Fama::new(0.5, 0.05).expect("valid Mama limits");
122 assert_eq!(fama.limits(), (0.5, 0.05));
123 for i in 0..60 {
124 fama.update(100.0 + (f64::from(i) * 0.3).sin() * 5.0);
125 }
126 assert!(fama.value().is_some());
127 }
128
129 #[test]
130 fn accessors_and_metadata() {
131 let mut fama = Fama::classic();
132 assert_eq!(fama.limits(), (0.5, 0.05));
133 assert_eq!(fama.warmup_period(), 33);
134 assert_eq!(fama.name(), "FAMA");
135 assert!(!fama.is_ready());
136 for i in 0..60 {
137 fama.update(100.0 + (f64::from(i) * 0.3).sin() * 5.0);
138 }
139 assert!(fama.is_ready());
140 assert!(fama.value().is_some());
141 }
142
143 #[test]
144 fn batch_equals_streaming() {
145 let prices: Vec<f64> = (0..120)
146 .map(|i| 100.0 + (f64::from(i) * 0.25).cos() * 5.0)
147 .collect();
148 let mut a = Fama::classic();
149 let mut b = Fama::classic();
150 let batch = a.batch(&prices);
151 let streamed: Vec<_> = prices.iter().map(|p| b.update(*p)).collect();
152 assert_eq!(batch, streamed);
153 }
154
155 #[test]
156 fn ignores_non_finite_input() {
157 let mut fama = Fama::classic();
158 let prices: Vec<f64> = (0..100)
159 .map(|i| 100.0 + (f64::from(i) * 0.3).sin() * 5.0)
160 .collect();
161 fama.batch(&prices);
162 let before = fama.value();
163 assert!(before.is_some());
164 assert_eq!(fama.update(f64::NAN), None);
165 }
166
167 #[test]
168 fn reset_clears_state() {
169 let mut fama = Fama::classic();
170 let prices: Vec<f64> = (0..100)
171 .map(|i| 100.0 + (f64::from(i) * 0.3).sin() * 5.0)
172 .collect();
173 fama.batch(&prices);
174 assert!(fama.is_ready());
175 fama.reset();
176 assert!(!fama.is_ready());
177 }
178}