1use std::collections::{HashMap, VecDeque};
6
7use crate::error::IndicatorError;
8use crate::indicator::{Indicator, IndicatorOutput};
9use crate::registry::param_usize;
10use crate::types::Candle;
11
12#[derive(Debug, Clone)]
15pub struct ConfluenceParams {
16 pub fast_len: usize,
17 pub slow_len: usize,
18 pub trend_len: usize,
19 pub rsi_len: usize,
20 pub adx_len: usize,
21}
22
23impl Default for ConfluenceParams {
24 fn default() -> Self {
25 Self {
26 fast_len: 8,
27 slow_len: 21,
28 trend_len: 50,
29 rsi_len: 14,
30 adx_len: 14,
31 }
32 }
33}
34
35#[derive(Debug, Clone)]
42pub struct ConfluenceIndicator {
43 pub params: ConfluenceParams,
44}
45
46impl ConfluenceIndicator {
47 pub fn new(params: ConfluenceParams) -> Self {
48 Self { params }
49 }
50}
51
52impl Indicator for ConfluenceIndicator {
53 fn name(&self) -> &'static str {
54 "Confluence"
55 }
56 fn required_len(&self) -> usize {
57 self.params.trend_len + 1
58 }
59 fn required_columns(&self) -> &[&'static str] {
60 &["high", "low", "close", "volume"]
61 }
62
63 fn calculate(&self, candles: &[Candle]) -> Result<IndicatorOutput, IndicatorError> {
64 self.check_len(candles)?;
65 let p = &self.params;
66 let mut eng =
67 ConfluenceEngine::new(p.fast_len, p.slow_len, p.trend_len, p.rsi_len, p.adx_len);
68 let n = candles.len();
69 let mut bull = vec![f64::NAN; n];
70 let mut bear = vec![f64::NAN; n];
71 for (i, c) in candles.iter().enumerate() {
72 eng.update(c);
73 bull[i] = eng.bull_score;
74 bear[i] = eng.bear_score;
75 }
76 Ok(IndicatorOutput::from_pairs([
77 ("confluence_bull", bull),
78 ("confluence_bear", bear),
79 ]))
80 }
81}
82
83pub fn factory<S: ::std::hash::BuildHasher>(
86 params: &HashMap<String, String, S>,
87) -> Result<Box<dyn Indicator>, IndicatorError> {
88 let fast_len = param_usize(params, "fast_len", 8)?;
89 let slow_len = param_usize(params, "slow_len", 21)?;
90 let trend_len = param_usize(params, "trend_len", 50)?;
91 let rsi_len = param_usize(params, "rsi_len", 14)?;
92 let adx_len = param_usize(params, "adx_len", 14)?;
93 Ok(Box::new(ConfluenceIndicator::new(ConfluenceParams {
94 fast_len,
95 slow_len,
96 trend_len,
97 rsi_len,
98 adx_len,
99 })))
100}
101
102#[derive(Debug)]
103pub struct ConfluenceEngine {
104 fast_len: usize,
105 slow_len: usize,
106 trend_len: usize,
107 rsi_len: usize,
108 adx_len: usize,
109
110 closes: VecDeque<f64>,
111 volumes: VecDeque<f64>,
112 highs: VecDeque<f64>,
113 lows: VecDeque<f64>,
114
115 ema_f: Option<f64>,
117 ema_s: Option<f64>,
118 ema_t: Option<f64>,
119 macd_ema12: Option<f64>,
121 macd_ema26: Option<f64>,
122 macd_sig: Option<f64>,
123 rsi_prev_c: Option<f64>,
125 rsi_gain: Option<f64>,
126 rsi_loss: Option<f64>,
127 adx_prev_h: Option<f64>,
129 adx_prev_l: Option<f64>,
130 adx_prev_c: Option<f64>,
131 adx_val: Option<f64>,
132 di_plus: Option<f64>,
133 di_minus: Option<f64>,
134 atr_adx: Option<f64>,
135
136 pub bull_score: f64,
137 pub bear_score: f64,
138 pub ema_fast: Option<f64>,
139 pub ema_slow: Option<f64>,
140}
141
142impl ConfluenceEngine {
143 pub fn new(fast: usize, slow: usize, trend: usize, rsi_len: usize, adx_len: usize) -> Self {
144 let maxlen = (slow * 3).max(trend + 10).max(300);
145 Self {
146 fast_len: fast,
147 slow_len: slow,
148 trend_len: trend,
149 rsi_len,
150 adx_len,
151 closes: VecDeque::with_capacity(maxlen),
152 volumes: VecDeque::with_capacity(maxlen),
153 highs: VecDeque::with_capacity(maxlen),
154 lows: VecDeque::with_capacity(maxlen),
155 ema_f: None,
156 ema_s: None,
157 ema_t: None,
158 macd_ema12: None,
159 macd_ema26: None,
160 macd_sig: None,
161 rsi_prev_c: None,
162 rsi_gain: None,
163 rsi_loss: None,
164 adx_prev_h: None,
165 adx_prev_l: None,
166 adx_prev_c: None,
167 adx_val: None,
168 di_plus: None,
169 di_minus: None,
170 atr_adx: None,
171 bull_score: 0.0,
172 bear_score: 0.0,
173 ema_fast: None,
174 ema_slow: None,
175 }
176 }
177
178 #[inline]
179 fn ema_step(prev: Option<f64>, val: f64, len: usize) -> f64 {
180 let k = 2.0 / (len as f64 + 1.0);
181 prev.map_or(val, |p| val * k + p * (1.0 - k))
182 }
183
184 #[inline]
185 fn rma_step(prev: Option<f64>, val: f64, len: usize) -> f64 {
186 let k = 1.0 / len as f64;
187 prev.map_or(val, |p| val * k + p * (1.0 - k))
188 }
189
190 fn update_rsi(&mut self, close: f64) -> f64 {
191 let Some(prev) = self.rsi_prev_c else {
192 self.rsi_prev_c = Some(close);
193 return 50.0;
194 };
195 let delta = close - prev;
196 self.rsi_prev_c = Some(close);
197 self.rsi_gain = Some(Self::rma_step(self.rsi_gain, delta.max(0.0), self.rsi_len));
198 self.rsi_loss = Some(Self::rma_step(
199 self.rsi_loss,
200 (-delta).max(0.0),
201 self.rsi_len,
202 ));
203 let gain = self.rsi_gain.unwrap_or(0.0);
204 let loss = self.rsi_loss.unwrap_or(1e-9).max(1e-9);
205 100.0 - 100.0 / (1.0 + gain / loss)
206 }
207
208 fn update_adx(&mut self, high: f64, low: f64, close: f64) {
209 let (Some(ph), Some(pl), Some(pc)) = (self.adx_prev_h, self.adx_prev_l, self.adx_prev_c)
210 else {
211 self.adx_prev_h = Some(high);
212 self.adx_prev_l = Some(low);
213 self.adx_prev_c = Some(close);
214 return;
215 };
216
217 let tr = (high - low).max((high - pc).abs()).max((low - pc).abs());
218 let up = high - ph;
219 let down = pl - low;
220 let dm_p = if up > down && up > 0.0 { up } else { 0.0 };
221 let dm_m = if down > up && down > 0.0 { down } else { 0.0 };
222
223 self.atr_adx = Some(Self::rma_step(self.atr_adx, tr, self.adx_len));
224 let atr = self.atr_adx.unwrap_or(1e-9).max(1e-9);
225
226 self.di_plus = Some(Self::rma_step(
227 self.di_plus,
228 dm_p / atr * 100.0,
229 self.adx_len,
230 ));
231 self.di_minus = Some(Self::rma_step(
232 self.di_minus,
233 dm_m / atr * 100.0,
234 self.adx_len,
235 ));
236
237 let dip = self.di_plus.unwrap_or(0.0);
238 let dim = self.di_minus.unwrap_or(0.0);
239 let di_sum = (dip + dim).max(1e-9);
240 let dx = (dip - dim).abs() / di_sum * 100.0;
241 self.adx_val = Some(Self::rma_step(self.adx_val, dx, self.adx_len));
242
243 self.adx_prev_h = Some(high);
244 self.adx_prev_l = Some(low);
245 self.adx_prev_c = Some(close);
246 }
247
248 pub fn update(&mut self, candle: &Candle) {
249 let (cl, vol, h, lo) = (candle.close, candle.volume, candle.high, candle.low);
250
251 let cap = self.closes.capacity();
252 if self.closes.len() == cap {
253 self.closes.pop_front();
254 }
255 if self.volumes.len() == cap {
256 self.volumes.pop_front();
257 }
258 if self.highs.len() == cap {
259 self.highs.pop_front();
260 }
261 if self.lows.len() == cap {
262 self.lows.pop_front();
263 }
264 self.closes.push_back(cl);
265 self.volumes.push_back(vol);
266 self.highs.push_back(h);
267 self.lows.push_back(lo);
268
269 self.ema_f = Some(Self::ema_step(self.ema_f, cl, self.fast_len));
270 self.ema_s = Some(Self::ema_step(self.ema_s, cl, self.slow_len));
271 self.ema_t = Some(Self::ema_step(self.ema_t, cl, self.trend_len));
272 self.ema_fast = self.ema_f;
273 self.ema_slow = self.ema_s;
274
275 self.macd_ema12 = Some(Self::ema_step(self.macd_ema12, cl, 12));
276 self.macd_ema26 = Some(Self::ema_step(self.macd_ema26, cl, 26));
277 let macd_line = self.macd_ema12.unwrap_or(cl) - self.macd_ema26.unwrap_or(cl);
278 self.macd_sig = Some(Self::ema_step(self.macd_sig, macd_line, 9));
279 let macd_hist = macd_line - self.macd_sig.unwrap_or(0.0);
280
281 let rsi_val = self.update_rsi(cl);
282 self.update_adx(h, lo, cl);
283
284 let adx = self.adx_val.unwrap_or(0.0);
285 let dip = self.di_plus.unwrap_or(0.0);
286 let dim = self.di_minus.unwrap_or(0.0);
287
288 let vols: Vec<f64> = self.volumes.iter().copied().collect();
290 let vol_sma = if vols.len() >= 20 {
291 vols[vols.len() - 20..].iter().sum::<f64>() / 20.0
292 } else {
293 vol
294 };
295 let vol_ok = vol > vol_sma * 1.2;
296
297 let ef = self.ema_f.unwrap_or(cl);
298 let es = self.ema_s.unwrap_or(cl);
299 let et = self.ema_t.unwrap_or(cl);
300 let sig = self.macd_sig.unwrap_or(0.0);
301
302 let mut b = 0.0_f64;
303 b += if ef > es { 1.0 } else { 0.0 };
304 b += if cl > et { 1.0 } else { 0.0 };
305 b += if (50.0..75.0).contains(&rsi_val) {
306 1.0
307 } else {
308 0.0
309 };
310 b += if macd_hist > 0.0 { 1.0 } else { 0.0 };
311 b += if macd_line > sig { 1.0 } else { 0.0 };
312 b += if vol_ok { 1.0 } else { 0.0 };
313 b += if adx > 20.0 && dip > dim { 1.0 } else { 0.0 };
314 b += if cl > ef { 0.5 } else { 0.0 };
315 self.bull_score = b;
316
317 let mut s = 0.0_f64;
318 s += if ef < es { 1.0 } else { 0.0 };
319 s += if cl < et { 1.0 } else { 0.0 };
320 s += if (25.0..50.0).contains(&rsi_val) {
321 1.0
322 } else {
323 0.0
324 };
325 s += if macd_hist < 0.0 { 1.0 } else { 0.0 };
326 s += if macd_line < sig { 1.0 } else { 0.0 };
327 s += if vol_ok { 1.0 } else { 0.0 };
328 s += if adx > 20.0 && dim > dip { 1.0 } else { 0.0 };
329 s += if cl < ef { 0.5 } else { 0.0 };
330 self.bear_score = s;
331 }
332
333 pub fn grade(score: f64) -> &'static str {
334 if score >= 8.0 {
335 "A+"
336 } else if score >= 6.5 {
337 "A"
338 } else if score >= 5.0 {
339 "B"
340 } else {
341 "C"
342 }
343 }
344}