finance_solution/stocks/ta/
advanced_ma.rs1use crate::stocks::ta::common::validate_series;
37use crate::stocks::ta::moving_average::EmaState;
38use crate::stocks::ta::ring::RingF64;
39use crate::util::error::{require_finite, FinanceError, FinanceResult};
40use crate::util::primitives::PeriodLength;
41
42#[derive(Clone, Debug)]
50pub struct RmaState {
51 period: usize,
52 alpha: f64,
53 seed: RingF64,
54 value: Option<f64>,
55}
56
57impl RmaState {
58 pub fn new(period: usize) -> FinanceResult<Self> {
59 let period = PeriodLength::new(period)?.get();
60 Ok(Self {
61 period,
62 alpha: 1.0 / period as f64,
63 seed: RingF64::with_capacity(period),
64 value: None,
65 })
66 }
67
68 pub fn from_history(period: usize, closes: &[f64]) -> FinanceResult<Self> {
69 let mut s = Self::new(period)?;
70 s.push_bars(closes)?;
71 Ok(s)
72 }
73
74 pub fn period(&self) -> usize {
75 self.period
76 }
77
78 pub fn reset(&mut self) {
79 self.seed.clear();
80 self.value = None;
81 }
82
83 pub fn push(&mut self, close: f64) -> FinanceResult<Option<f64>> {
84 require_finite("close", close)?;
85 if let Some(prev) = self.value {
86 let next = self.alpha * close + (1.0 - self.alpha) * prev;
87 self.value = Some(next);
88 return Ok(Some(next));
89 }
90 let _ = self.seed.push(close);
91 if self.seed.is_full() {
92 let seed = self.seed.sum() / self.period as f64;
93 self.value = Some(seed);
94 Ok(Some(seed))
95 } else {
96 Ok(None)
97 }
98 }
99
100 pub fn push_bars(&mut self, closes: &[f64]) -> FinanceResult<Vec<Option<f64>>> {
101 let mut out = Vec::with_capacity(closes.len());
102 for &c in closes {
103 out.push(self.push(c)?);
104 }
105 Ok(out)
106 }
107
108 pub fn last(&self) -> Option<f64> {
109 self.value
110 }
111}
112
113pub fn rma(closes: &[f64], period: usize) -> FinanceResult<Vec<Option<f64>>> {
115 validate_series("close", closes)?;
116 let mut st = RmaState::new(period)?;
117 st.push_bars(closes)
118}
119
120pub fn rma_last(closes: &[f64], period: usize) -> FinanceResult<Option<f64>> {
121 let mut st = RmaState::new(period)?;
122 for &c in closes {
123 st.push(c)?;
124 }
125 Ok(st.last())
126}
127
128#[derive(Clone, Debug)]
136pub struct DemaState {
137 e1: EmaState,
138 e2: EmaState,
139}
140
141impl DemaState {
142 pub fn new(period: usize) -> FinanceResult<Self> {
143 Ok(Self {
144 e1: EmaState::new(period)?,
145 e2: EmaState::new(period)?,
146 })
147 }
148
149 pub fn from_history(period: usize, closes: &[f64]) -> FinanceResult<Self> {
150 let mut s = Self::new(period)?;
151 s.push_bars(closes)?;
152 Ok(s)
153 }
154
155 pub fn period(&self) -> usize {
156 self.e1.period()
157 }
158
159 pub fn reset(&mut self) {
160 self.e1.reset();
161 self.e2.reset();
162 }
163
164 pub fn push(&mut self, close: f64) -> FinanceResult<Option<f64>> {
165 let e1 = match self.e1.push(close)? {
166 Some(v) => v,
167 None => return Ok(None),
168 };
169 match self.e2.push(e1)? {
170 Some(e2) => Ok(Some(2.0 * e1 - e2)),
171 None => Ok(None),
172 }
173 }
174
175 pub fn push_bars(&mut self, closes: &[f64]) -> FinanceResult<Vec<Option<f64>>> {
176 let mut out = Vec::with_capacity(closes.len());
177 for &c in closes {
178 out.push(self.push(c)?);
179 }
180 Ok(out)
181 }
182
183 pub fn last(&self) -> Option<f64> {
184 match (self.e1.last(), self.e2.last()) {
185 (Some(e1), Some(e2)) => Some(2.0 * e1 - e2),
186 _ => None,
187 }
188 }
189}
190
191pub fn dema(closes: &[f64], period: usize) -> FinanceResult<Vec<Option<f64>>> {
193 validate_series("close", closes)?;
194 let mut st = DemaState::new(period)?;
195 st.push_bars(closes)
196}
197
198pub fn dema_last(closes: &[f64], period: usize) -> FinanceResult<Option<f64>> {
199 let mut st = DemaState::new(period)?;
200 for &c in closes {
201 st.push(c)?;
202 }
203 Ok(st.last())
204}
205
206#[derive(Clone, Debug)]
214pub struct TemaState {
215 e1: EmaState,
216 e2: EmaState,
217 e3: EmaState,
218}
219
220impl TemaState {
221 pub fn new(period: usize) -> FinanceResult<Self> {
222 Ok(Self {
223 e1: EmaState::new(period)?,
224 e2: EmaState::new(period)?,
225 e3: EmaState::new(period)?,
226 })
227 }
228
229 pub fn from_history(period: usize, closes: &[f64]) -> FinanceResult<Self> {
230 let mut s = Self::new(period)?;
231 s.push_bars(closes)?;
232 Ok(s)
233 }
234
235 pub fn period(&self) -> usize {
236 self.e1.period()
237 }
238
239 pub fn reset(&mut self) {
240 self.e1.reset();
241 self.e2.reset();
242 self.e3.reset();
243 }
244
245 pub fn push(&mut self, close: f64) -> FinanceResult<Option<f64>> {
246 let e1 = match self.e1.push(close)? {
247 Some(v) => v,
248 None => return Ok(None),
249 };
250 let e2 = match self.e2.push(e1)? {
251 Some(v) => v,
252 None => return Ok(None),
253 };
254 match self.e3.push(e2)? {
255 Some(e3) => Ok(Some(3.0 * e1 - 3.0 * e2 + e3)),
256 None => Ok(None),
257 }
258 }
259
260 pub fn push_bars(&mut self, closes: &[f64]) -> FinanceResult<Vec<Option<f64>>> {
261 let mut out = Vec::with_capacity(closes.len());
262 for &c in closes {
263 out.push(self.push(c)?);
264 }
265 Ok(out)
266 }
267
268 pub fn last(&self) -> Option<f64> {
269 match (self.e1.last(), self.e2.last(), self.e3.last()) {
270 (Some(e1), Some(e2), Some(e3)) => Some(3.0 * e1 - 3.0 * e2 + e3),
271 _ => None,
272 }
273 }
274}
275
276pub fn tema(closes: &[f64], period: usize) -> FinanceResult<Vec<Option<f64>>> {
278 validate_series("close", closes)?;
279 let mut st = TemaState::new(period)?;
280 st.push_bars(closes)
281}
282
283pub fn tema_last(closes: &[f64], period: usize) -> FinanceResult<Option<f64>> {
284 let mut st = TemaState::new(period)?;
285 for &c in closes {
286 st.push(c)?;
287 }
288 Ok(st.last())
289}
290
291#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
300pub struct KamaParams {
301 pub period: usize,
303 pub fast: usize,
305 pub slow: usize,
307}
308
309impl KamaParams {
310 pub const fn new(period: usize, fast: usize, slow: usize) -> Self {
311 Self { period, fast, slow }
312 }
313
314 pub const fn standard() -> Self {
316 Self {
317 period: 10,
318 fast: 2,
319 slow: 30,
320 }
321 }
322}
323
324#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
325pub struct ValidatedKama {
326 params: KamaParams,
327}
328
329impl ValidatedKama {
330 pub fn new(params: KamaParams) -> FinanceResult<Self> {
331 PeriodLength::new(params.period)?;
332 PeriodLength::new(params.fast)?;
333 PeriodLength::new(params.slow)?;
334 if params.fast >= params.slow {
335 return Err(FinanceError::Unsolvable {
336 message: "KAMA requires fast < slow",
337 });
338 }
339 Ok(Self { params })
340 }
341
342 pub fn params(self) -> KamaParams {
343 self.params
344 }
345
346 pub fn compute(self, closes: &[f64]) -> FinanceResult<Vec<Option<f64>>> {
347 kama(closes, self.params)
348 }
349}
350
351#[derive(Clone, Debug)]
356pub struct KamaState {
357 params: KamaParams,
358 fast_sc: f64,
359 slow_sc: f64,
360 closes: RingF64,
362 kama: Option<f64>,
363 last: Option<f64>,
364}
365
366impl KamaState {
367 pub fn new(params: KamaParams) -> FinanceResult<Self> {
368 let _ = ValidatedKama::new(params)?;
369 let fast_sc = 2.0 / (params.fast as f64 + 1.0);
370 let slow_sc = 2.0 / (params.slow as f64 + 1.0);
371 Ok(Self {
372 params,
373 fast_sc,
374 slow_sc,
375 closes: RingF64::with_capacity(params.period + 1),
376 kama: None,
377 last: None,
378 })
379 }
380
381 pub fn from_history(params: KamaParams, closes: &[f64]) -> FinanceResult<Self> {
382 let mut s = Self::new(params)?;
383 s.push_bars(closes)?;
384 Ok(s)
385 }
386
387 pub fn params(&self) -> KamaParams {
388 self.params
389 }
390
391 pub fn reset(&mut self) {
392 self.closes.clear();
393 self.kama = None;
394 self.last = None;
395 }
396
397 pub fn push(&mut self, close: f64) -> FinanceResult<Option<f64>> {
398 require_finite("close", close)?;
399 let _ = self.closes.push(close);
400 if self.closes.len() < self.params.period + 1 {
402 self.last = None;
403 return Ok(None);
404 }
405 let mut ordered = Vec::with_capacity(self.params.period + 1);
406 self.closes.copy_ordered(&mut ordered);
407 let n = ordered.len();
408 let change = (ordered[n - 1] - ordered[0]).abs();
409 let mut volatility = 0.0;
410 for i in 1..n {
411 volatility += (ordered[i] - ordered[i - 1]).abs();
412 }
413 let er = if volatility > 0.0 {
414 change / volatility
415 } else {
416 0.0
417 };
418 let sc = (er * (self.fast_sc - self.slow_sc) + self.slow_sc).powi(2);
419 let kama = match self.kama {
420 None => close, Some(prev) => prev + sc * (close - prev),
422 };
423 self.kama = Some(kama);
424 self.last = Some(kama);
425 Ok(Some(kama))
426 }
427
428 pub fn push_bars(&mut self, closes: &[f64]) -> FinanceResult<Vec<Option<f64>>> {
429 validate_series("close", closes)?;
430 let mut out = Vec::with_capacity(closes.len());
431 for &c in closes {
432 out.push(self.push(c)?);
433 }
434 Ok(out)
435 }
436
437 pub fn last(&self) -> Option<f64> {
438 self.last
439 }
440}
441
442pub fn kama(closes: &[f64], params: KamaParams) -> FinanceResult<Vec<Option<f64>>> {
444 let mut st = KamaState::new(params)?;
445 st.push_bars(closes)
446}
447
448pub fn kama_last(closes: &[f64], params: KamaParams) -> FinanceResult<Option<f64>> {
449 let mut st = KamaState::new(params)?;
450 for &c in closes {
451 st.push(c)?;
452 }
453 Ok(st.last())
454}
455
456#[cfg(test)]
457mod tests {
458 use super::*;
459 use crate::stocks::ta::moving_average::ema;
460
461 #[test]
462 fn rma_matches_wilder_seed() {
463 let c: Vec<_> = (1..=20).map(|x| x as f64).collect();
464 let r = rma(&c, 5).unwrap();
465 assert!((r[4].unwrap() - 3.0).abs() < 1e-12);
467 assert!((r[5].unwrap() - 3.6).abs() < 1e-12);
469 }
470
471 #[test]
472 fn dema_related_to_ema() {
473 let c: Vec<_> = (1..=40).map(|x| x as f64).collect();
474 let d = dema(&c, 5).unwrap();
475 let e1 = ema(&c, 5).unwrap();
476 let first_e = e1.iter().position(|x| x.is_some()).unwrap();
478 let first_d = d.iter().position(|x| x.is_some()).unwrap();
479 assert!(first_d >= first_e);
480 assert!(d.last().unwrap().is_some());
481 }
482
483 #[test]
484 fn tema_runs() {
485 let c: Vec<_> = (1..=50).map(|x| 100.0 + x as f64 * 0.1).collect();
486 let t = tema(&c, 5).unwrap();
487 assert!(t.iter().filter(|x| x.is_some()).count() > 10);
488 }
489
490 #[test]
491 fn kama_standard_runs() {
492 let c: Vec<_> = (0..40).map(|i| 100.0 + (i as f64).sin() * 2.0).collect();
493 let k = kama(&c, KamaParams::standard()).unwrap();
494 assert!(k[9].is_none() || k[10].is_some()); assert!(k.last().unwrap().is_some());
496 }
497
498 #[test]
499 fn dema_tema_state_parity() {
500 let c: Vec<_> = (0..45).map(|i| 50.0 + i as f64 * 0.25).collect();
501 for (batch, mut st) in [(dema(&c, 8).unwrap(), DemaState::new(8).unwrap())] {
502 for i in 0..c.len() {
503 let o = st.push(c[i]).unwrap();
504 match (o, batch[i]) {
505 (None, None) => {}
506 (Some(a), Some(b)) => assert!((a - b).abs() < 1e-9),
507 other => panic!("{other:?}"),
508 }
509 }
510 }
511 let batch = tema(&c, 6).unwrap();
512 let mut st = TemaState::new(6).unwrap();
513 for i in 0..c.len() {
514 let o = st.push(c[i]).unwrap();
515 match (o, batch[i]) {
516 (None, None) => {}
517 (Some(a), Some(b)) => assert!((a - b).abs() < 1e-9),
518 other => panic!("{other:?}"),
519 }
520 }
521 let batch = rma(&c, 7).unwrap();
522 let mut st = RmaState::new(7).unwrap();
523 for i in 0..c.len() {
524 let o = st.push(c[i]).unwrap();
525 match (o, batch[i]) {
526 (None, None) => {}
527 (Some(a), Some(b)) => assert!((a - b).abs() < 1e-9),
528 other => panic!("{other:?}"),
529 }
530 }
531 let p = KamaParams::standard();
532 let batch = kama(&c, p).unwrap();
533 let mut st = KamaState::new(p).unwrap();
534 for i in 0..c.len() {
535 let o = st.push(c[i]).unwrap();
536 match (o, batch[i]) {
537 (None, None) => {}
538 (Some(a), Some(b)) => assert!((a - b).abs() < 1e-9),
539 other => panic!("{other:?}"),
540 }
541 }
542 }
543
544 #[test]
545 fn kama_fast_ge_slow_err() {
546 assert!(ValidatedKama::new(KamaParams::new(10, 30, 2)).is_err());
547 }
548}