fin_primitives/signals/indicators/
close_midpoint_diff.rs1use crate::error::FinError;
4use crate::signals::{BarInput, Signal, SignalValue};
5use rust_decimal::Decimal;
6
7pub struct CloseMidpointDiff {
24 name: String,
25}
26
27impl CloseMidpointDiff {
28 pub fn new(name: impl Into<String>) -> Self {
30 Self { name: name.into() }
31 }
32}
33
34impl Signal for CloseMidpointDiff {
35 fn name(&self) -> &str {
36 &self.name
37 }
38
39 fn period(&self) -> usize {
40 1
41 }
42
43 fn is_ready(&self) -> bool {
44 true
45 }
46
47 fn update(&mut self, bar: &BarInput) -> Result<SignalValue, FinError> {
48 let midpoint = (bar.high + bar.low)
49 .checked_div(Decimal::from(2u32))
50 .ok_or(FinError::ArithmeticOverflow)?;
51 Ok(SignalValue::Scalar(bar.close - midpoint))
52 }
53
54 fn reset(&mut self) {}
55}
56
57#[cfg(test)]
58mod tests {
59 use super::*;
60 use crate::ohlcv::OhlcvBar;
61 use crate::signals::Signal;
62 use crate::types::{NanoTimestamp, Price, Quantity, Symbol};
63 use rust_decimal_macros::dec;
64
65 fn bar(h: &str, l: &str, c: &str) -> OhlcvBar {
66 let hp = Price::new(h.parse().unwrap()).unwrap();
67 let lp = Price::new(l.parse().unwrap()).unwrap();
68 let cp = Price::new(c.parse().unwrap()).unwrap();
69 OhlcvBar {
70 symbol: Symbol::new("X").unwrap(),
71 open: lp, high: hp, low: lp, close: cp,
72 volume: Quantity::zero(),
73 ts_open: NanoTimestamp::new(0),
74 ts_close: NanoTimestamp::new(1),
75 tick_count: 1,
76 }
77 }
78
79 #[test]
80 fn test_cmd_close_at_high() {
81 let mut cmd = CloseMidpointDiff::new("cmd");
82 let v = cmd.update_bar(&bar("110", "90", "110")).unwrap();
84 assert_eq!(v, SignalValue::Scalar(dec!(10)));
85 }
86
87 #[test]
88 fn test_cmd_close_at_low() {
89 let mut cmd = CloseMidpointDiff::new("cmd");
90 let v = cmd.update_bar(&bar("110", "90", "90")).unwrap();
92 assert_eq!(v, SignalValue::Scalar(dec!(-10)));
93 }
94
95 #[test]
96 fn test_cmd_close_at_midpoint() {
97 let mut cmd = CloseMidpointDiff::new("cmd");
98 let v = cmd.update_bar(&bar("110", "90", "100")).unwrap();
100 assert_eq!(v, SignalValue::Scalar(dec!(0)));
101 }
102
103 #[test]
104 fn test_cmd_always_ready() {
105 let cmd = CloseMidpointDiff::new("cmd");
106 assert!(cmd.is_ready());
107 }
108
109 #[test]
110 fn test_cmd_period_and_name() {
111 let cmd = CloseMidpointDiff::new("my_cmd");
112 assert_eq!(cmd.period(), 1);
113 assert_eq!(cmd.name(), "my_cmd");
114 }
115}