1use std::collections::HashMap;
23use std::sync::{OnceLock, 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().expect("registry write lock poisoned");
61 map.insert(name.to_ascii_lowercase(), factory);
62 }
63
64 pub fn list(&self) -> Vec<String> {
68 let map = self.entries.read().expect("registry read lock poisoned");
69 map.keys().cloned().collect()
70 }
71
72 pub fn get(&self, name: &str) -> Option<IndicatorFactory> {
76 let map = self.entries.read().expect("registry read lock poisoned");
77 map.get(&name.to_ascii_lowercase()).copied()
78 }
79
80 pub fn create(
89 &self,
90 name: &str,
91 params: &HashMap<String, String>,
92 ) -> Result<Box<dyn Indicator>, IndicatorError> {
93 let factory = self
94 .get(name)
95 .ok_or_else(|| IndicatorError::UnknownIndicator {
96 name: name.to_string(),
97 })?;
98 factory(params)
99 }
100
101 pub fn contains(&self, name: &str) -> bool {
105 self.get(name).is_some()
106 }
107}
108
109pub static REGISTRY: OnceLock<IndicatorRegistry> = OnceLock::new();
118
119pub fn registry() -> &'static IndicatorRegistry {
121 REGISTRY.get_or_init(|| {
122 let reg = IndicatorRegistry {
123 entries: RwLock::new(HashMap::new()),
124 };
125 crate::trend::register_all(®);
127 crate::momentum::register_all(®);
128 crate::volatility::register_all(®);
129 crate::volume::register_all(®);
130 crate::signal::register_all(®);
131 crate::regime::register_all(®);
132 reg
133 })
134}
135
136pub fn param_usize<S: ::std::hash::BuildHasher>(
142 params: &HashMap<String, String, S>,
143 key: &str,
144 default: usize,
145) -> Result<usize, IndicatorError> {
146 match params.get(key) {
147 None => Ok(default),
148 Some(s) => s
149 .parse::<usize>()
150 .map_err(|_| IndicatorError::InvalidParameter {
151 name: key.to_string(),
152 value: s.parse::<f64>().unwrap_or(f64::NAN),
153 }),
154 }
155}
156
157pub fn param_f64<S: ::std::hash::BuildHasher>(
159 params: &HashMap<String, String, S>,
160 key: &str,
161 default: f64,
162) -> Result<f64, IndicatorError> {
163 match params.get(key) {
164 None => Ok(default),
165 Some(s) => s
166 .parse::<f64>()
167 .map_err(|_| IndicatorError::InvalidParameter {
168 name: key.to_string(),
169 value: f64::NAN,
170 }),
171 }
172}
173
174pub fn param_str<'a, S: ::std::hash::BuildHasher>(
176 params: &'a HashMap<String, String, S>,
177 key: &str,
178 default: &'a str,
179) -> &'a str {
180 params.get(key).map_or(default, String::as_str)
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186
187 fn dummy_factory(_p: &HashMap<String, String>) -> Result<Box<dyn Indicator>, IndicatorError> {
188 Err(IndicatorError::UnknownIndicator {
190 name: "dummy".into(),
191 })
192 }
193
194 #[test]
195 fn registry_register_and_list() {
196 let reg = IndicatorRegistry {
197 entries: RwLock::new(HashMap::new()),
198 };
199 reg.register("sma", dummy_factory);
200 reg.register("ema", dummy_factory);
201 let mut names = reg.list();
202 names.sort();
203 assert_eq!(names, vec!["ema", "sma"]);
204 }
205
206 #[test]
207 fn registry_unknown_returns_error() {
208 let reg = IndicatorRegistry {
209 entries: RwLock::new(HashMap::new()),
210 };
211 let err = reg
212 .create("no_such_indicator", &HashMap::new())
213 .unwrap_err();
214 assert!(matches!(err, IndicatorError::UnknownIndicator { .. }));
215 }
216
217 #[test]
218 fn param_usize_default() {
219 let params = HashMap::new();
220 assert_eq!(param_usize(¶ms, "period", 14).unwrap(), 14);
221 }
222
223 #[test]
224 fn param_usize_override() {
225 let params = [("period".to_string(), "20".to_string())].into();
226 assert_eq!(param_usize(¶ms, "period", 14).unwrap(), 20);
227 }
228
229 #[test]
230 fn param_usize_bad_value() {
231 let params = [("period".to_string(), "abc".to_string())].into();
232 assert!(param_usize(¶ms, "period", 14).is_err());
233 }
234}