1use crate::types::Candle;
6use std::collections::VecDeque;
7
8pub struct ConfluenceEngine {
9 fast_len: usize,
10 slow_len: usize,
11 trend_len: usize,
12 rsi_len: usize,
13 adx_len: usize,
14
15 closes: VecDeque<f64>,
16 volumes: VecDeque<f64>,
17 highs: VecDeque<f64>,
18 lows: VecDeque<f64>,
19
20 ema_f: Option<f64>,
22 ema_s: Option<f64>,
23 ema_t: Option<f64>,
24 macd_ema12: Option<f64>,
26 macd_ema26: Option<f64>,
27 macd_sig: Option<f64>,
28 rsi_prev_c: Option<f64>,
30 rsi_gain: Option<f64>,
31 rsi_loss: Option<f64>,
32 adx_prev_h: Option<f64>,
34 adx_prev_l: Option<f64>,
35 adx_prev_c: Option<f64>,
36 adx_val: Option<f64>,
37 di_plus: Option<f64>,
38 di_minus: Option<f64>,
39 atr_adx: Option<f64>,
40
41 pub bull_score: f64,
42 pub bear_score: f64,
43 pub ema_fast: Option<f64>,
44 pub ema_slow: Option<f64>,
45}
46
47impl ConfluenceEngine {
48 pub fn new(fast: usize, slow: usize, trend: usize, rsi_len: usize, adx_len: usize) -> Self {
49 let maxlen = (slow * 3).max(trend + 10).max(300);
50 Self {
51 fast_len: fast,
52 slow_len: slow,
53 trend_len: trend,
54 rsi_len,
55 adx_len,
56 closes: VecDeque::with_capacity(maxlen),
57 volumes: VecDeque::with_capacity(maxlen),
58 highs: VecDeque::with_capacity(maxlen),
59 lows: VecDeque::with_capacity(maxlen),
60 ema_f: None,
61 ema_s: None,
62 ema_t: None,
63 macd_ema12: None,
64 macd_ema26: None,
65 macd_sig: None,
66 rsi_prev_c: None,
67 rsi_gain: None,
68 rsi_loss: None,
69 adx_prev_h: None,
70 adx_prev_l: None,
71 adx_prev_c: None,
72 adx_val: None,
73 di_plus: None,
74 di_minus: None,
75 atr_adx: None,
76 bull_score: 0.0,
77 bear_score: 0.0,
78 ema_fast: None,
79 ema_slow: None,
80 }
81 }
82
83 #[inline]
84 fn ema_step(prev: Option<f64>, val: f64, len: usize) -> f64 {
85 let k = 2.0 / (len as f64 + 1.0);
86 prev.map_or(val, |p| val * k + p * (1.0 - k))
87 }
88
89 #[inline]
90 fn rma_step(prev: Option<f64>, val: f64, len: usize) -> f64 {
91 let k = 1.0 / len as f64;
92 prev.map_or(val, |p| val * k + p * (1.0 - k))
93 }
94
95 fn update_rsi(&mut self, close: f64) -> f64 {
96 let Some(prev) = self.rsi_prev_c else {
97 self.rsi_prev_c = Some(close);
98 return 50.0;
99 };
100 let delta = close - prev;
101 self.rsi_prev_c = Some(close);
102 self.rsi_gain = Some(Self::rma_step(self.rsi_gain, delta.max(0.0), self.rsi_len));
103 self.rsi_loss = Some(Self::rma_step(
104 self.rsi_loss,
105 (-delta).max(0.0),
106 self.rsi_len,
107 ));
108 let gain = self.rsi_gain.unwrap_or(0.0);
109 let loss = self.rsi_loss.unwrap_or(1e-9).max(1e-9);
110 100.0 - 100.0 / (1.0 + gain / loss)
111 }
112
113 fn update_adx(&mut self, high: f64, low: f64, close: f64) {
114 let (Some(ph), Some(pl), Some(pc)) = (self.adx_prev_h, self.adx_prev_l, self.adx_prev_c)
115 else {
116 self.adx_prev_h = Some(high);
117 self.adx_prev_l = Some(low);
118 self.adx_prev_c = Some(close);
119 return;
120 };
121
122 let tr = (high - low).max((high - pc).abs()).max((low - pc).abs());
123 let up = high - ph;
124 let down = pl - low;
125 let dm_p = if up > down && up > 0.0 { up } else { 0.0 };
126 let dm_m = if down > up && down > 0.0 { down } else { 0.0 };
127
128 self.atr_adx = Some(Self::rma_step(self.atr_adx, tr, self.adx_len));
129 let atr = self.atr_adx.unwrap_or(1e-9).max(1e-9);
130
131 self.di_plus = Some(Self::rma_step(
132 self.di_plus,
133 dm_p / atr * 100.0,
134 self.adx_len,
135 ));
136 self.di_minus = Some(Self::rma_step(
137 self.di_minus,
138 dm_m / atr * 100.0,
139 self.adx_len,
140 ));
141
142 let dip = self.di_plus.unwrap_or(0.0);
143 let dim = self.di_minus.unwrap_or(0.0);
144 let di_sum = (dip + dim).max(1e-9);
145 let dx = (dip - dim).abs() / di_sum * 100.0;
146 self.adx_val = Some(Self::rma_step(self.adx_val, dx, self.adx_len));
147
148 self.adx_prev_h = Some(high);
149 self.adx_prev_l = Some(low);
150 self.adx_prev_c = Some(close);
151 }
152
153 pub fn update(&mut self, candle: &Candle) {
154 let (cl, vol, h, lo) = (candle.close, candle.volume, candle.high, candle.low);
155
156 let cap = self.closes.capacity();
157 if self.closes.len() == cap {
158 self.closes.pop_front();
159 }
160 if self.volumes.len() == cap {
161 self.volumes.pop_front();
162 }
163 if self.highs.len() == cap {
164 self.highs.pop_front();
165 }
166 if self.lows.len() == cap {
167 self.lows.pop_front();
168 }
169 self.closes.push_back(cl);
170 self.volumes.push_back(vol);
171 self.highs.push_back(h);
172 self.lows.push_back(lo);
173
174 self.ema_f = Some(Self::ema_step(self.ema_f, cl, self.fast_len));
175 self.ema_s = Some(Self::ema_step(self.ema_s, cl, self.slow_len));
176 self.ema_t = Some(Self::ema_step(self.ema_t, cl, self.trend_len));
177 self.ema_fast = self.ema_f;
178 self.ema_slow = self.ema_s;
179
180 self.macd_ema12 = Some(Self::ema_step(self.macd_ema12, cl, 12));
181 self.macd_ema26 = Some(Self::ema_step(self.macd_ema26, cl, 26));
182 let macd_line = self.macd_ema12.unwrap_or(cl) - self.macd_ema26.unwrap_or(cl);
183 self.macd_sig = Some(Self::ema_step(self.macd_sig, macd_line, 9));
184 let macd_hist = macd_line - self.macd_sig.unwrap_or(0.0);
185
186 let rsi_val = self.update_rsi(cl);
187 self.update_adx(h, lo, cl);
188
189 let adx = self.adx_val.unwrap_or(0.0);
190 let dip = self.di_plus.unwrap_or(0.0);
191 let dim = self.di_minus.unwrap_or(0.0);
192
193 let vols: Vec<f64> = self.volumes.iter().copied().collect();
195 let vol_sma = if vols.len() >= 20 {
196 vols[vols.len() - 20..].iter().sum::<f64>() / 20.0
197 } else {
198 vol
199 };
200 let vol_ok = vol > vol_sma * 1.2;
201
202 let ef = self.ema_f.unwrap_or(cl);
203 let es = self.ema_s.unwrap_or(cl);
204 let et = self.ema_t.unwrap_or(cl);
205 let sig = self.macd_sig.unwrap_or(0.0);
206
207 let mut b = 0.0_f64;
208 b += if ef > es { 1.0 } else { 0.0 };
209 b += if cl > et { 1.0 } else { 0.0 };
210 b += if (50.0..75.0).contains(&rsi_val) {
211 1.0
212 } else {
213 0.0
214 };
215 b += if macd_hist > 0.0 { 1.0 } else { 0.0 };
216 b += if macd_line > sig { 1.0 } else { 0.0 };
217 b += if vol_ok { 1.0 } else { 0.0 };
218 b += if adx > 20.0 && dip > dim { 1.0 } else { 0.0 };
219 b += if cl > ef { 0.5 } else { 0.0 };
220 self.bull_score = b;
221
222 let mut s = 0.0_f64;
223 s += if ef < es { 1.0 } else { 0.0 };
224 s += if cl < et { 1.0 } else { 0.0 };
225 s += if (25.0..50.0).contains(&rsi_val) {
226 1.0
227 } else {
228 0.0
229 };
230 s += if macd_hist < 0.0 { 1.0 } else { 0.0 };
231 s += if macd_line < sig { 1.0 } else { 0.0 };
232 s += if vol_ok { 1.0 } else { 0.0 };
233 s += if adx > 20.0 && dim > dip { 1.0 } else { 0.0 };
234 s += if cl < ef { 0.5 } else { 0.0 };
235 self.bear_score = s;
236 }
237
238 pub fn grade(score: f64) -> &'static str {
239 if score >= 8.0 {
240 "A+"
241 } else if score >= 6.5 {
242 "A"
243 } else if score >= 5.0 {
244 "B"
245 } else {
246 "C"
247 }
248 }
249}