1use std::collections::HashMap;
23use std::sync::{OnceLock, PoisonError, RwLock};
24
25use crate::error::IndicatorError;
26use crate::indicator::Indicator;
27
28pub type IndicatorFactory =
35 fn(params: &HashMap<String, String>) -> Result<Box<dyn Indicator>, IndicatorError>;
36
37pub struct IndicatorRegistry {
44 entries: RwLock<HashMap<String, IndicatorFactory>>,
45}
46
47impl IndicatorRegistry {
48 pub fn new_uninit() -> Self {
49 Self {
52 entries: RwLock::new(HashMap::new()),
53 }
54 }
55
56 pub fn register(&self, name: &str, factory: IndicatorFactory) {
60 let mut map = self.entries.write().unwrap_or_else(PoisonError::into_inner);
63 map.insert(name.to_ascii_lowercase(), factory);
64 }
65
66 pub fn list(&self) -> Vec<String> {
70 let map = self.entries.read().unwrap_or_else(PoisonError::into_inner);
71 map.keys().cloned().collect()
72 }
73
74 pub fn get(&self, name: &str) -> Option<IndicatorFactory> {
78 let map = self.entries.read().unwrap_or_else(PoisonError::into_inner);
79 map.get(&name.to_ascii_lowercase()).copied()
80 }
81
82 pub fn create(
91 &self,
92 name: &str,
93 params: &HashMap<String, String>,
94 ) -> Result<Box<dyn Indicator>, IndicatorError> {
95 let factory = self
96 .get(name)
97 .ok_or_else(|| IndicatorError::UnknownIndicator {
98 name: name.to_string(),
99 })?;
100 factory(params)
101 }
102
103 pub fn contains(&self, name: &str) -> bool {
107 self.get(name).is_some()
108 }
109}
110
111pub static REGISTRY: OnceLock<IndicatorRegistry> = OnceLock::new();
120
121pub fn registry() -> &'static IndicatorRegistry {
123 REGISTRY.get_or_init(|| {
124 let reg = IndicatorRegistry {
125 entries: RwLock::new(HashMap::new()),
126 };
127 crate::trend::register_all(®);
129 crate::momentum::register_all(®);
130 crate::volatility::register_all(®);
131 crate::volume::register_all(®);
132 crate::signal::register_all(®);
133 crate::regime::register_all(®);
134 reg
135 })
136}
137
138pub fn param_usize<S: ::std::hash::BuildHasher>(
144 params: &HashMap<String, String, S>,
145 key: &str,
146 default: usize,
147) -> Result<usize, IndicatorError> {
148 match params.get(key) {
149 None => Ok(default),
150 Some(s) => s
151 .parse::<usize>()
152 .map_err(|_| IndicatorError::InvalidParameter {
153 name: key.to_string(),
154 value: s.parse::<f64>().unwrap_or(f64::NAN),
155 }),
156 }
157}
158
159pub fn param_f64<S: ::std::hash::BuildHasher>(
161 params: &HashMap<String, String, S>,
162 key: &str,
163 default: f64,
164) -> Result<f64, IndicatorError> {
165 match params.get(key) {
166 None => Ok(default),
167 Some(s) => s
168 .parse::<f64>()
169 .map_err(|_| IndicatorError::InvalidParameter {
170 name: key.to_string(),
171 value: f64::NAN,
172 }),
173 }
174}
175
176pub fn param_str<'a, S: ::std::hash::BuildHasher>(
178 params: &'a HashMap<String, String, S>,
179 key: &str,
180 default: &'a str,
181) -> &'a str {
182 params.get(key).map_or(default, String::as_str)
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 fn dummy_factory(_p: &HashMap<String, String>) -> Result<Box<dyn Indicator>, IndicatorError> {
190 Err(IndicatorError::UnknownIndicator {
192 name: "dummy".into(),
193 })
194 }
195
196 #[test]
197 fn registry_register_and_list() {
198 let reg = IndicatorRegistry {
199 entries: RwLock::new(HashMap::new()),
200 };
201 reg.register("sma", dummy_factory);
202 reg.register("ema", dummy_factory);
203 let mut names = reg.list();
204 names.sort();
205 assert_eq!(names, vec!["ema", "sma"]);
206 }
207
208 #[test]
209 fn registry_unknown_returns_error() {
210 let reg = IndicatorRegistry {
211 entries: RwLock::new(HashMap::new()),
212 };
213 let err = reg
214 .create("no_such_indicator", &HashMap::new())
215 .unwrap_err();
216 assert!(matches!(err, IndicatorError::UnknownIndicator { .. }));
217 }
218
219 #[test]
220 fn param_usize_default() {
221 let params = HashMap::new();
222 assert_eq!(param_usize(¶ms, "period", 14).unwrap(), 14);
223 }
224
225 #[test]
226 fn param_usize_override() {
227 let params = [("period".to_string(), "20".to_string())].into();
228 assert_eq!(param_usize(¶ms, "period", 14).unwrap(), 20);
229 }
230
231 #[test]
232 fn param_usize_bad_value() {
233 let params = [("period".to_string(), "abc".to_string())].into();
234 assert!(param_usize(¶ms, "period", 14).is_err());
235 }
236}