1use crate::structs::{AnalysisOptions, AnalysisResult, BBValues, Candle, CandleMasterCode};
2use serde::{Deserialize, Serialize};
3use std::collections::VecDeque;
4
5#[derive(Serialize, Deserialize, Debug, Clone)]
6pub struct GeneratorState {
7 pub last_ema_1: f64,
8 pub last_ema_2: f64,
9 pub last_ema_3: f64,
10 pub last_atr: f64,
11 pub rsi_avg_gain: f64,
12 pub rsi_avg_loss: f64,
13 pub up_con_medium_ema: usize,
14 pub down_con_medium_ema: usize,
15 pub up_con_long_ema: usize,
16 pub down_con_long_ema: usize,
17 pub last_ema_cut_index: Option<usize>,
18 pub prev_analysis: Option<AnalysisResult>,
19 pub last_analysis: Option<AnalysisResult>,
20 pub last_candle: Option<Candle>,
21
22 pub rsi_period: usize,
24
25 pub atr_period: usize,
27
28 pub tr_sum: f64,
30 pub pdm_sum: f64,
31 pub mdm_sum: f64,
32 pub adx_val: f64,
33 pub dx_count: usize,
34 pub adx_period: usize,
35
36 pub bb_window: VecDeque<f64>,
38 pub bb_period: usize,
39
40 pub ci_window: VecDeque<Candle>,
42 pub ci_atr_window: VecDeque<f64>,
43 pub ci_period: usize,
44
45 pub ema_1_k: f64,
47 pub ema_2_k: f64,
48 pub ema_3_k: f64,
49
50 pub close_window: VecDeque<f64>, pub max_ma_period: usize,
58}
59
60impl GeneratorState {
61 pub fn new(options: &AnalysisOptions) -> Self {
62 let max_period = options
63 .ema1_period
64 .max(options.ema2_period)
65 .max(options.ema3_period);
66 let buffer_size = max_period * 2;
68
69 Self {
70 last_ema_1: 0.0,
71 last_ema_2: 0.0,
72 last_ema_3: 0.0,
73 last_atr: 0.0,
74 rsi_avg_gain: 0.0,
75 rsi_avg_loss: 0.0,
76 up_con_medium_ema: 0,
77 down_con_medium_ema: 0,
78 up_con_long_ema: 0,
79 down_con_long_ema: 0,
80 last_ema_cut_index: None,
81 prev_analysis: None,
82 last_analysis: None,
83 last_candle: None,
84 atr_period: options.atr_period,
85 rsi_period: options.rsi_period,
86 tr_sum: 0.0,
87 pdm_sum: 0.0,
88 mdm_sum: 0.0,
89 adx_val: 0.0,
90 dx_count: 0,
91 adx_period: options.adx_period,
92 bb_window: VecDeque::with_capacity(options.bb_period),
93 bb_period: options.bb_period,
94 ci_window: VecDeque::with_capacity(options.ci_period),
95 ci_atr_window: VecDeque::with_capacity(options.ci_period),
96 ci_period: options.ci_period,
97 ema_1_k: 2.0 / (options.ema1_period as f64 + 1.0),
98 ema_2_k: 2.0 / (options.ema2_period as f64 + 1.0),
99 ema_3_k: 2.0 / (options.ema3_period as f64 + 1.0),
100
101 close_window: VecDeque::with_capacity(buffer_size),
102 max_ma_period: buffer_size,
103 }
104 }
105}
106
107#[derive(Clone)]
108pub struct AnalysisGenerator {
109 options: AnalysisOptions,
110 pub state: GeneratorState,
111 pub analysis_array: Vec<AnalysisResult>,
112 pub candle_data: Vec<Candle>,
113 pub current_candle: Option<Candle>,
114 pub master_codes: std::sync::Arc<Vec<CandleMasterCode>>,
115}
116
117impl AnalysisGenerator {
118 pub fn new(
119 options: AnalysisOptions,
120 master_codes: std::sync::Arc<Vec<CandleMasterCode>>,
121 ) -> Self {
122 let state = GeneratorState::new(&options);
123 Self {
124 options,
125 state,
126 analysis_array: Vec::new(),
127 candle_data: Vec::new(),
128 current_candle: None,
129 master_codes,
130 }
131 }
132
133 fn calculate_wma(data: &[f64], period: usize) -> f64 {
135 if data.len() < period {
136 return 0.0;
137 }
138 let mut num = 0.0;
139 let mut den = 0.0;
140 for j in 0..period {
141 let val = data[data.len() - 1 - j];
142 let w = (period - j) as f64;
143 num += val * w;
144 den += w;
145 }
146 num / den
147 }
148
149 fn calculate_ema_slice(data: &[f64], period: usize) -> f64 {
151 if data.is_empty() {
152 return 0.0;
153 }
154 let k = 2.0 / (period as f64 + 1.0);
155 let mut ema = data[0];
156 for i in 1..data.len() {
157 ema = data[i] * k + ema * (1.0 - k);
158 }
159 ema
160 }
161
162 fn calculate_ma(
165 &self,
166 ma_type: &str,
167 period: usize,
168 current_price: f64,
169 last_ema_state: f64,
170 ema_k: f64,
171 ) -> f64 {
172 match ma_type {
173 "EMA" => current_price * ema_k + last_ema_state * (1.0 - ema_k),
174 "HMA" | "EHMA" => {
175 if self.state.close_window.len() + 1 < period {
188 return current_price;
189 }
190
191 let iter = self
193 .state
194 .close_window
195 .iter()
196 .chain(std::iter::once(¤t_price));
197 let data: Vec<f64> = iter.copied().collect();
200
201 if ma_type == "HMA" {
202 let half = (period / 2).max(1);
203 let sqrt = (period as f64).sqrt() as usize;
204
205 let mut raw_series = Vec::new();
210 let needed = sqrt; let len = data.len();
216 if len < period {
217 return current_price;
218 }
219
220 for i in 0..needed {
221 let end_idx = len - (needed - 1 - i); if end_idx < period {
224 continue;
225 }
226
227 let slice = &data[0..end_idx];
228 let wma_half = Self::calculate_wma(slice, half);
233 let wma_full = Self::calculate_wma(slice, period);
234 let raw = 2.0 * wma_half - wma_full;
235 raw_series.push(raw);
236 }
237
238 Self::calculate_wma(&raw_series, sqrt)
239 } else {
240 let half = (period / 2).max(1);
242 let sqrt = (period as f64).sqrt() as usize;
243
244 let len = data.len();
252 if len < period {
253 return current_price;
254 }
255
256 let mut raw_series = Vec::new();
261 let k_half = 2.0 / (half as f64 + 1.0);
264 let k_full = 2.0 / (period as f64 + 1.0);
265
266 let mut val_half = data[0];
267 let mut val_full = data[0];
268
269 for i in 1..len {
271 val_half = data[i] * k_half + val_half * (1.0 - k_half);
272 val_full = data[i] * k_full + val_full * (1.0 - k_full);
273
274 let raw = 2.0 * val_half - val_full;
275 raw_series.push(raw);
276 }
277
278 Self::calculate_ema_slice(&raw_series, sqrt)
281 }
282 }
283 _ => current_price * ema_k + last_ema_state * (1.0 - ema_k),
284 }
285 }
286
287 fn get_ema_direction(&self, prev: f64, curr: f64) -> String {
289 let diff = prev - curr;
290 if diff.abs() <= self.options.flat_threshold {
291 "Flat".to_string()
292 } else if prev < curr {
293 "Up".to_string()
294 } else {
295 "Down".to_string()
296 }
297 }
298
299 pub fn append_candle(&mut self, new_candle: Candle) -> AnalysisResult {
300 let i = self.analysis_array.len();
301 let prev_candle = self.state.last_candle; self.candle_data.push(new_candle);
305
306 self.state.close_window.push_back(new_candle.close);
309 if self.state.close_window.len() > self.state.max_ma_period {
310 self.state.close_window.pop_front();
311 }
312
313 let close = new_candle.close;
314
315 let new_ema_1 = if i == 0 {
317 close
318 } else {
319 self.calculate_ma(
320 &self.options.ema1_type,
321 self.options.ema1_period,
322 close,
323 self.state.last_ema_1,
324 self.state.ema_1_k,
325 )
326 };
327 let new_ema_2 = if i == 0 {
328 close
329 } else {
330 self.calculate_ma(
331 &self.options.ema2_type,
332 self.options.ema2_period,
333 close,
334 self.state.last_ema_2,
335 self.state.ema_2_k,
336 )
337 };
338 let new_ema_3 = if i == 0 {
339 close
340 } else {
341 self.calculate_ma(
342 &self.options.ema3_type,
343 self.options.ema3_period,
344 close,
345 self.state.last_ema_3,
346 self.state.ema_3_k,
347 )
348 };
349
350 let ema_1_dir = if i > 0 {
352 self.get_ema_direction(self.state.last_ema_1, new_ema_1)
353 } else {
354 "Flat".to_string()
355 };
356 let ema_2_dir = if i > 0 {
357 self.get_ema_direction(self.state.last_ema_2, new_ema_2)
358 } else {
359 "Flat".to_string()
360 };
361 let ema_3_dir = if i > 0 {
362 self.get_ema_direction(self.state.last_ema_3, new_ema_3)
363 } else {
364 "Flat".to_string()
365 };
366
367 let mut ema_short_turn_type = "-".to_string();
369 if let Some(prev_analysis) = &self.state.prev_analysis {
370 if let Some(prev_prev_val) = prev_analysis.ema_short_value {
383 if let Some(_prev_val) = self
384 .state
385 .last_analysis
386 .as_ref()
387 .map(|a| a.ema_short_value)
388 .flatten()
389 {
390 let curr_diff = new_ema_1 - self.state.last_ema_1;
399 let prev_diff = self.state.last_ema_1 - prev_prev_val;
400
401 let curr_dir_calc = if curr_diff > 0.0001 {
402 "Up"
403 } else if curr_diff < -0.0001 {
404 "Down"
405 } else {
406 "Flat"
407 };
408 let prev_dir_calc = if prev_diff > 0.0001 {
409 "Up"
410 } else if prev_diff < -0.0001 {
411 "Down"
412 } else {
413 "Flat"
414 };
415
416 if curr_dir_calc == "Up" && prev_dir_calc == "Down" {
417 ema_short_turn_type = "TurnUp".to_string();
418 } else if curr_dir_calc == "Down" && prev_dir_calc == "Up" {
419 ema_short_turn_type = "TurnDown".to_string();
420 }
421 }
422 }
423 }
424
425 let mut up_con_medium_ema = self.state.up_con_medium_ema;
427 let mut down_con_medium_ema = self.state.down_con_medium_ema;
428
429 if ema_2_dir == "Up" {
430 up_con_medium_ema += 1;
431 down_con_medium_ema = 0;
432 } else if ema_2_dir == "Down" {
433 down_con_medium_ema += 1;
434 up_con_medium_ema = 0;
435 }
436
437 let mut up_con_long_ema = self.state.up_con_long_ema;
438 let mut down_con_long_ema = self.state.down_con_long_ema;
439
440 if ema_3_dir == "Up" {
441 up_con_long_ema += 1;
442 down_con_long_ema = 0;
443 } else if ema_3_dir == "Down" {
444 down_con_long_ema += 1;
445 up_con_long_ema = 0;
446 }
447
448 let ema_above = if new_ema_1 > new_ema_2 {
450 "ShortAbove"
451 } else {
452 "MediumAbove"
453 }
454 .to_string();
455 let ema_long_above = if new_ema_2 > new_ema_3 {
456 "MediumAbove"
457 } else {
458 "LongAbove"
459 }
460 .to_string();
461
462 let macd_12 = (new_ema_1 - new_ema_2).abs();
463 let macd_23 = (new_ema_2 - new_ema_3).abs();
464
465 let prev_macd_12 = self.state.last_analysis.as_ref().and_then(|a| a.macd_12);
466 let prev_macd_23 = self.state.last_analysis.as_ref().and_then(|a| a.macd_23);
467
468 let ema_convergence_type = if let Some(prev) = prev_macd_12 {
469 if macd_12 > prev {
470 "divergence".to_string()
471 } else if macd_12 < prev {
472 "convergence".to_string()
473 } else {
474 "neutral".to_string()
475 }
476 } else {
477 "neutral".to_string()
478 }; let ema_long_convergence_type = if let Some(prev) = prev_macd_23 {
481 if macd_23 > prev {
482 "D".to_string()
483 } else if macd_23 < prev {
484 "C".to_string()
485 } else {
486 "N".to_string()
487 }
488 } else {
489 "N".to_string()
490 };
491
492 let mut ema_cut_long_type = None;
494 if i > 0 {
495 let prev_medium_above = self.state.last_ema_2 > self.state.last_ema_3;
497 let curr_medium_above = new_ema_2 > new_ema_3;
498 if curr_medium_above != prev_medium_above {
499 ema_cut_long_type = Some(if curr_medium_above {
500 "UpTrend".to_string()
501 } else {
502 "DownTrend".to_string()
503 });
504 }
505 }
506
507 let mut last_ema_cut_index = self.state.last_ema_cut_index;
508 if ema_cut_long_type.is_some() {
509 last_ema_cut_index = Some(i);
510 }
511 let candles_since_ema_cut = last_ema_cut_index.map(|idx| i - idx);
512
513 let tr = if let Some(p) = prev_candle {
515 (new_candle.high - new_candle.low)
516 .max((new_candle.high - p.close).abs())
517 .max((new_candle.low - p.close).abs())
518 } else {
519 new_candle.high - new_candle.low
520 };
521
522 let new_atr = if i < self.state.atr_period {
523 if i == 0 {
524 tr
525 } else {
526 ((self.state.last_atr * i as f64) + tr) / (i as f64 + 1.0)
527 }
528 } else {
529 ((self.state.last_atr * (self.state.atr_period as f64 - 1.0)) + tr)
530 / self.state.atr_period as f64
531 };
532
533 let mut rsi_value = None;
535 let mut new_rsi_avg_gain = self.state.rsi_avg_gain;
536 let mut new_rsi_avg_loss = self.state.rsi_avg_loss;
537
538 if let Some(p) = prev_candle {
539 let change = close - p.close;
548 let gain = if change > 0.0 { change } else { 0.0 };
549 let loss = if change < 0.0 { change.abs() } else { 0.0 };
550
551 if i < self.state.rsi_period {
552 if i == 0 {
562 new_rsi_avg_gain = gain;
563 new_rsi_avg_loss = loss;
564 } else {
565 new_rsi_avg_gain += gain;
567 new_rsi_avg_loss += loss;
568 }
569
570 if i + 1 == self.state.rsi_period {
571 new_rsi_avg_gain /= self.state.rsi_period as f64;
573 new_rsi_avg_loss /= self.state.rsi_period as f64;
574 let rs = if new_rsi_avg_loss == 0.0 {
576 100.0
577 } else {
578 new_rsi_avg_gain / new_rsi_avg_loss
579 };
580 rsi_value = Some(100.0 - (100.0 / (1.0 + rs)));
581 }
582 } else {
583 new_rsi_avg_gain = (self.state.rsi_avg_gain * (self.state.rsi_period as f64 - 1.0)
585 + gain)
586 / self.state.rsi_period as f64;
587 new_rsi_avg_loss = (self.state.rsi_avg_loss * (self.state.rsi_period as f64 - 1.0)
588 + loss)
589 / self.state.rsi_period as f64;
590
591 let rs = if new_rsi_avg_loss == 0.0 {
592 100.0
593 } else {
594 new_rsi_avg_gain / new_rsi_avg_loss
595 };
596 rsi_value = Some(100.0 - (100.0 / (1.0 + rs)));
597 }
598 }
599
600 self.state.bb_window.push_back(close);
602 if self.state.bb_window.len() > self.state.bb_period {
603 self.state.bb_window.pop_front();
604 }
605
606 let (bb_upper, bb_middle, bb_lower) = if self.state.bb_window.len() >= self.state.bb_period
607 {
608 let sum: f64 = self.state.bb_window.iter().sum();
609 let avg = sum / self.state.bb_period as f64;
610 let variance: f64 = self.state.bb_window.iter().map(|x| (x - avg).powi(2)).sum();
611 let std = (variance / self.state.bb_period as f64).sqrt();
612 (Some(avg + 2.0 * std), Some(avg), Some(avg - 2.0 * std))
613 } else {
614 (None, None, None)
615 };
616
617 let bb_position = if let (Some(u), Some(l)) = (bb_upper, bb_lower) {
618 let range = u - l;
619 let upper_zone = u - (range * 0.33);
620 let lower_zone = l + (range * 0.33);
621 if close >= upper_zone {
622 "NearUpper".to_string()
623 } else if close <= lower_zone {
624 "NearLower".to_string()
625 } else {
626 "Middle".to_string()
627 }
628 } else {
629 "Unknown".to_string()
630 };
631
632 self.state.ci_window.push_back(new_candle);
634 if self.state.ci_window.len() > self.state.ci_period {
635 self.state.ci_window.pop_front();
636 }
637 self.state.ci_atr_window.push_back(new_atr);
638 if self.state.ci_atr_window.len() > self.state.ci_period {
639 self.state.ci_atr_window.pop_front();
640 }
641
642 let choppy_indicator = if self.state.ci_window.len() >= self.state.ci_period {
643 let high_max = self
644 .state
645 .ci_window
646 .iter()
647 .map(|c| c.high)
648 .fold(f64::NEG_INFINITY, f64::max);
649 let low_min = self
650 .state
651 .ci_window
652 .iter()
653 .map(|c| c.low)
654 .fold(f64::INFINITY, f64::min);
655 let sum_atr: f64 = self.state.ci_atr_window.iter().sum();
656
657 if (high_max - low_min) > 0.0 {
658 Some(
659 100.0 * (sum_atr / (high_max - low_min)).log10()
660 / (self.state.ci_period as f64).log10(),
661 )
662 } else {
663 Some(0.0)
664 }
665 } else {
666 None
667 };
668
669 let mut adx_value = None;
671 if let Some(p) = prev_candle {
672 let up_move = new_candle.high - p.high;
673 let down_move = p.low - new_candle.low;
674 let pdm = if up_move > down_move && up_move > 0.0 {
675 up_move
676 } else {
677 0.0
678 };
679 let mdm = if down_move > up_move && down_move > 0.0 {
680 down_move
681 } else {
682 0.0
683 };
684
685 if i < self.state.adx_period {
687 self.state.tr_sum += tr;
689 self.state.pdm_sum += pdm;
690 self.state.mdm_sum += mdm;
691 } else {
692 self.state.tr_sum =
693 self.state.tr_sum - (self.state.tr_sum / self.state.adx_period as f64) + tr;
694 self.state.pdm_sum =
695 self.state.pdm_sum - (self.state.pdm_sum / self.state.adx_period as f64) + pdm;
696 self.state.mdm_sum =
697 self.state.mdm_sum - (self.state.mdm_sum / self.state.adx_period as f64) + mdm;
698 }
699
700 if i >= self.state.adx_period && self.state.tr_sum > 0.0 {
701 let di_plus = (self.state.pdm_sum / self.state.tr_sum) * 100.0;
702 let di_minus = (self.state.mdm_sum / self.state.tr_sum) * 100.0;
703 let sum_di = di_plus + di_minus;
704 let dx = if sum_di == 0.0 {
705 0.0
706 } else {
707 (di_plus - di_minus).abs() / sum_di * 100.0
708 };
709
710 self.state.dx_count += 1;
711
712 if self.state.dx_count <= self.state.adx_period {
713 if self.state.dx_count == 1 {
717 self.state.adx_val = dx; self.state.adx_val += dx / self.state.adx_period as f64;
725 } else {
728 self.state.adx_val += dx / self.state.adx_period as f64;
729 }
730
731 } else {
737 self.state.adx_val =
740 ((self.state.adx_val * (self.state.adx_period as f64 - 1.0)) + dx)
741 / self.state.adx_period as f64;
742 }
743
744 if self.state.dx_count >= self.state.adx_period {
745 adx_value = Some(self.state.adx_val);
746 }
747 }
748 }
749
750 let color = if close > new_candle.open {
752 "Green"
753 } else if close < new_candle.open {
754 "Red"
755 } else {
756 "Equal"
757 }
758 .to_string();
759 let pip_size = (close - new_candle.open).abs();
760 let body_top = new_candle.open.max(close);
761 let body_bottom = new_candle.open.min(close);
762 let u_wick = new_candle.high - body_top;
763 let body = (close - new_candle.open).abs();
764 let l_wick = body_bottom - new_candle.low;
765 let full_candle_size = new_candle.high - new_candle.low;
766
767 let body_percent = if full_candle_size > 0.0 {
768 (body / full_candle_size) * 100.0
769 } else {
770 0.0
771 };
772 let u_wick_percent = if full_candle_size > 0.0 {
773 (u_wick / full_candle_size) * 100.0
774 } else {
775 0.0
776 };
777 let l_wick_percent = if full_candle_size > 0.0 {
778 (l_wick / full_candle_size) * 100.0
779 } else {
780 0.0
781 };
782
783 let is_abnormal_candle = if let Some(p) = prev_candle {
784 let tr_val = (new_candle.high - new_candle.low)
785 .max((new_candle.high - p.close).abs())
786 .max((new_candle.low - p.close).abs());
787 tr_val > (new_atr * self.options.atr_multiplier)
788 } else {
789 false
790 };
791
792 let is_abnormal_atr = if new_atr > 0.0 {
793 (body > new_atr * self.options.atr_multiplier)
794 || (full_candle_size > new_atr * self.options.atr_multiplier * 1.5)
795 } else {
796 false
797 };
798
799 let ema_cut_position = if new_ema_1 > new_candle.high {
801 Some("1".to_string())
802 } else if new_ema_1 >= body_top && new_ema_1 <= new_candle.high {
803 Some("2".to_string())
804 } else if new_ema_1 >= body_bottom && new_ema_1 < body_top {
805 let body_range = body_top - body_bottom;
806 if body_range > 0.0 {
807 let pos = (new_ema_1 - body_bottom) / body_range;
808 if pos >= 0.66 {
809 Some("B1".to_string())
810 } else if pos >= 0.33 {
811 Some("B2".to_string())
812 } else {
813 Some("B3".to_string())
814 }
815 } else {
816 Some("B2".to_string())
817 }
818 } else if new_ema_1 >= new_candle.low && new_ema_1 < body_bottom {
819 Some("3".to_string())
820 } else if new_ema_1 < new_candle.low {
821 Some("4".to_string())
822 } else {
823 None
824 };
825
826 let _ema_long_above_char =
828 if ema_long_above != "LongAbove" && ema_long_above != "MediumAbove" {
829 "-"
830 } else {
831 &ema_long_above[0..1]
832 }; let c1 = if ema_long_above == "MediumAbove" {
836 "M"
837 } else if ema_long_above == "LongAbove" {
838 "L"
839 } else {
840 "-"
841 };
842 let c2 = if ema_2_dir == "Up" {
843 "U"
844 } else if ema_2_dir == "Down" {
845 "D"
846 } else {
847 "F"
848 };
849 let c3 = if ema_3_dir == "Up" {
850 "U"
851 } else if ema_3_dir == "Down" {
852 "D"
853 } else {
854 "F"
855 };
856 let c4 = &color[0..1];
857 let c5 = if ema_long_convergence_type != "" {
858 &ema_long_convergence_type
859 } else {
860 "-"
861 };
862
863 let status_desc = format!("{}-{}-{}-{}-{}", c1, c2, c3, c4, c5);
866
867 let mut status_code = "".to_string();
868 for code in self.master_codes.iter() {
869 if code.status_desc == status_desc {
870 status_code = code.status_code.clone();
871 break;
872 }
873 }
874
875 let _display_time = ""; let mut smc_ind = crate::smc::SmcIndicator::new(crate::smc::SmcConfig::default());
889 let smc_result = smc_ind.calculate(self.candle_data.as_slice());
890
891 let mut analysis_obj = AnalysisResult {
892 index: i,
893 candletime: new_candle.time,
894 candletime_display: "".to_string(), open: new_candle.open,
896 high: new_candle.high,
897 low: new_candle.low,
898 close: new_candle.close,
899 color: color.clone(),
900 next_color: None,
901 pip_size,
902 ema_short_value: Some(new_ema_1),
903 ema_short_direction: ema_1_dir,
904 ema_short_turn_type: ema_short_turn_type,
905 ema_medium_value: Some(new_ema_2),
906 ema_medium_direction: ema_2_dir,
907 ema_long_value: Some(new_ema_3),
908 ema_long_direction: ema_3_dir,
909 ema_above: Some(ema_above),
910 ema_long_above: Some(ema_long_above),
911 macd_12: Some(macd_12),
912 macd_23: Some(macd_23),
913 previous_ema_short_value: self
914 .state
915 .last_analysis
916 .as_ref()
917 .and_then(|a| a.ema_short_value),
918 previous_ema_medium_value: self
919 .state
920 .last_analysis
921 .as_ref()
922 .and_then(|a| a.ema_medium_value),
923 previous_ema_long_value: self
924 .state
925 .last_analysis
926 .as_ref()
927 .and_then(|a| a.ema_long_value),
928 previous_macd_12: prev_macd_12,
929 previous_macd_23: prev_macd_23,
930 ema_convergence_type: Some(ema_convergence_type),
931 ema_long_convergence_type,
932 choppy_indicator,
933 adx_value,
934 rsi_value,
935 bb_values: BBValues {
936 upper: bb_upper,
937 middle: bb_middle,
938 lower: bb_lower,
939 },
940 bb_position,
941 atr: Some(new_atr),
942 is_abnormal_candle,
943 is_abnormal_atr,
944 u_wick,
945 u_wick_percent,
946 body,
947 body_percent,
948 l_wick,
949 l_wick_percent,
950 ema_cut_position,
951 ema_cut_long_type,
952 candles_since_ema_cut,
953 up_con_medium_ema,
954 down_con_medium_ema,
955 up_con_long_ema,
956 down_con_long_ema,
957 is_mark: "n".to_string(),
958 status_code,
959 status_desc: status_desc.clone(),
960 status_desc_0: status_desc,
961 hint_status: "".to_string(),
962 suggest_color: "".to_string(),
963 win_status: "".to_string(),
964 win_con: 0,
965 loss_con: 0,
966 smc: Some(smc_result),
967 };
968
969 if let Some(last) = self.analysis_array.last_mut() {
971 last.next_color = Some(color);
972 }
973
974 self.analysis_array.push(analysis_obj.clone());
975
976 self.state.last_ema_1 = new_ema_1;
978 self.state.last_ema_2 = new_ema_2;
979 self.state.last_ema_3 = new_ema_3;
980 self.state.last_atr = new_atr;
981 self.state.rsi_avg_gain = new_rsi_avg_gain;
982 self.state.rsi_avg_loss = new_rsi_avg_loss;
983 self.state.up_con_medium_ema = up_con_medium_ema;
984 self.state.down_con_medium_ema = down_con_medium_ema;
985 self.state.up_con_long_ema = up_con_long_ema;
986 self.state.down_con_long_ema = down_con_long_ema;
987 self.state.last_ema_cut_index = last_ema_cut_index;
988
989 self.state.prev_analysis = self.state.last_analysis.clone();
990 self.state.last_analysis = Some(analysis_obj.clone());
991 self.state.last_candle = Some(new_candle);
992
993 analysis_obj
994 }
995
996 pub fn append_tick(&mut self, price: f64, time: u64) -> Option<AnalysisResult> {
997 let tick_minute = (time / 60) * 60;
998
999 if let Some(mut current) = self.current_candle {
1000 if time >= current.time + 60 {
1001 let completed_candle = current; let result = self.append_candle(completed_candle);
1007
1008 self.current_candle = Some(Candle {
1010 time: tick_minute,
1011 open: price,
1012 high: price,
1013 low: price,
1014 close: price,
1015 });
1016
1017 Some(result)
1018 } else {
1019 current.high = current.high.max(price);
1021 current.low = current.low.min(price);
1022 current.close = price;
1023 self.current_candle = Some(current);
1024 None
1025 }
1026 } else {
1027 self.current_candle = Some(Candle {
1029 time: tick_minute,
1030 open: price,
1031 high: price,
1032 low: price,
1033 close: price,
1034 });
1035 None
1036 }
1037 }
1038}