1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum Signal {
11 Buy,
13 Sell,
15 Neutral,
17}
18
19impl Signal {
20 #[must_use]
22 pub const fn letter(self) -> &'static str {
23 match self {
24 Self::Buy => "↑ B",
25 Self::Sell => "↓ S",
26 Self::Neutral => "- N",
27 }
28 }
29}
30
31#[must_use]
33pub const fn crosses_up(x1: f64, x2: f64, y1: f64, y2: f64) -> bool {
34 x1 > y1 && x2 < y2
35}
36
37#[must_use]
39pub const fn adx(
40 adx_value: f64,
41 dminus: f64,
42 dplus: f64,
43 dminus_old: f64,
44 dplus_old: f64,
45) -> Signal {
46 if crosses_up(dplus, dplus_old, dminus, dminus_old) && adx_value > 20.0 {
47 Signal::Buy
48 } else if crosses_up(dminus, dminus_old, dplus, dplus_old) && adx_value > 20.0 {
49 Signal::Sell
50 } else {
51 Signal::Neutral
52 }
53}
54
55#[must_use]
57pub const fn ao(ao_value: f64, ao_old_1: f64, ao_old_2: f64) -> Signal {
58 if is_ao_bullish_saucer(ao_value, ao_old_1, ao_old_2)
59 || is_ao_bullish_cross(ao_value, ao_old_1, ao_old_2)
60 {
61 Signal::Buy
62 } else if is_ao_bearish_saucer(ao_value, ao_old_1, ao_old_2)
63 || is_ao_bearish_cross(ao_value, ao_old_1, ao_old_2)
64 {
65 Signal::Sell
66 } else {
67 Signal::Neutral
68 }
69}
70
71#[must_use]
73pub const fn bb_lower(low_limit: f64, close: f64) -> Signal {
74 if close < low_limit {
75 Signal::Buy
76 } else {
77 Signal::Neutral
78 }
79}
80
81#[must_use]
83pub const fn bb_upper(up_limit: f64, close: f64) -> Signal {
84 if close > up_limit {
85 Signal::Sell
86 } else {
87 Signal::Neutral
88 }
89}
90
91const fn is_ao_bearish_cross(ao_value: f64, ao_old_1: f64, ao_old_2: f64) -> bool {
92 ao_value < 0.0 && ao_old_1 > 0.0 && ao_old_2 > 0.0
93}
94
95const fn is_ao_bullish_cross(ao_value: f64, ao_old_1: f64, ao_old_2: f64) -> bool {
96 ao_value > 0.0 && ao_old_1 < 0.0 && ao_old_2 < 0.0
97}
98
99const fn is_ao_bullish_saucer(ao_value: f64, ao_old_1: f64, ao_old_2: f64) -> bool {
100 ao_value > 0.0 && ao_value > ao_old_1 && ao_old_1 < ao_old_2
101}
102
103const fn is_ao_bearish_saucer(ao_value: f64, ao_old_1: f64, ao_old_2: f64) -> bool {
104 ao_value < 0.0 && ao_value < ao_old_1 && ao_old_1 > ao_old_2
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110
111 #[test]
112 fn adx_buy_on_plus_di_cross_with_strength() {
113 assert_eq!(adx(25.0, 10.0, 15.0, 12.0, 8.0), Signal::Buy);
115 }
116
117 #[test]
118 fn adx_sell_on_minus_di_cross_with_strength() {
119 assert_eq!(adx(25.0, 15.0, 10.0, 8.0, 12.0), Signal::Sell);
120 }
121
122 #[test]
123 fn adx_neutral_when_weak() {
124 assert_eq!(adx(10.0, 10.0, 15.0, 12.0, 8.0), Signal::Neutral);
125 }
126
127 #[test]
128 fn ao_bullish_zero_cross() {
129 assert_eq!(ao(0.5, -0.2, -0.4), Signal::Buy);
130 }
131
132 #[test]
133 fn ao_bearish_zero_cross() {
134 assert_eq!(ao(-0.5, 0.2, 0.4), Signal::Sell);
135 }
136
137 #[test]
138 fn bb_signals() {
139 assert_eq!(bb_lower(100.0, 99.0), Signal::Buy);
140 assert_eq!(bb_lower(100.0, 101.0), Signal::Neutral);
141 assert_eq!(bb_upper(100.0, 101.0), Signal::Sell);
142 assert_eq!(bb_upper(100.0, 99.0), Signal::Neutral);
143 }
144}