kestrel_chartkit/indicator/
source_mapped.rs1use super::{Indicator, IndicatorAlert, IndicatorOutput};
19use crate::model::{Bar, BarValidationError, Source};
20
21pub struct SourceMapped<I: Indicator> {
24 inner: I,
25 source: Source,
26}
27
28impl<I: Indicator> SourceMapped<I> {
29 pub fn new(inner: I, source: Source) -> Self {
30 Self { inner, source }
31 }
32
33 fn map_bar(&self, bar: &Bar) -> Bar {
34 let value = self.source.extract(bar);
35 Bar::new(bar.timestamp, value, value, value, value, bar.volume)
36 }
37}
38
39impl<I: Indicator> Indicator for SourceMapped<I> {
40 fn name(&self) -> &str {
41 self.inner.name()
42 }
43 fn warmup_period(&self) -> usize {
44 self.inner.warmup_period()
45 }
46 fn reset(&mut self) {
47 self.inner.reset()
48 }
49 fn on_bar(&mut self, bar: &Bar) -> Option<IndicatorOutput> {
50 let mapped = self.map_bar(bar);
51 self.inner.on_bar(&mapped)
52 }
53 fn on_checked_bar(&mut self, bar: &Bar) -> Result<Option<IndicatorOutput>, BarValidationError> {
54 bar.validate()?;
55 Ok(self.on_bar(bar))
56 }
57 fn alerts(&self) -> Vec<IndicatorAlert> {
58 self.inner.alerts()
59 }
60}
61
62#[cfg(test)]
63mod tests {
64 use super::*;
65 use crate::indicator::moving_averages::SmaEngine;
66
67 #[test]
68 fn test_source_mapped_uses_selected_source_not_close() {
69 let mut close_sma = SmaEngine::new(2);
70 let mut open_sma = SourceMapped::new(SmaEngine::new(2), Source::Open);
71
72 let bars = [
74 Bar::new(0, 10.0, 12.0, 8.0, 11.0, 100.0),
75 Bar::new(60, 20.0, 22.0, 18.0, 21.0, 100.0),
76 ];
77
78 let mut close_out = None;
79 let mut open_out = None;
80 for bar in &bars {
81 close_out = close_sma.on_bar(bar);
82 open_out = open_sma.on_bar(bar);
83 }
84
85 assert_eq!(close_out.unwrap().value, (11.0 + 21.0) / 2.0);
86 assert_eq!(open_out.unwrap().value, (10.0 + 20.0) / 2.0);
87 }
88
89 #[test]
90 fn test_source_mapped_preserves_volume_and_delegates_metadata() {
91 let mut hl2_sma = SourceMapped::new(SmaEngine::new(1), Source::Hl2);
92 let bar = Bar::new(0, 10.0, 20.0, 0.0, 15.0, 250.0);
93 let out = hl2_sma.on_bar(&bar).unwrap();
95 assert_eq!(out.value, 10.0);
96 assert_eq!(hl2_sma.name(), "sma");
97 assert_eq!(hl2_sma.warmup_period(), SmaEngine::new(1).warmup_period());
98 }
99}