quantwave_core/indicators/patterns/
candle_settings.rs1use crate::indicators::incremental::utils::RingBuffer;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4pub enum RangeType {
5 RealBody,
6 HighLow,
7 Shadows,
8}
9
10#[derive(Debug, Clone, Copy)]
11pub struct CandleSetting {
12 pub range_type: RangeType,
13 pub avg_period: usize,
14 pub factor: f64,
15}
16
17pub const BODY_LONG: CandleSetting = CandleSetting {
18 range_type: RangeType::RealBody,
19 avg_period: 10,
20 factor: 1.0,
21};
22pub const BODY_VERY_LONG: CandleSetting = CandleSetting {
23 range_type: RangeType::RealBody,
24 avg_period: 10,
25 factor: 3.0,
26};
27pub const BODY_SHORT: CandleSetting = CandleSetting {
28 range_type: RangeType::RealBody,
29 avg_period: 10,
30 factor: 1.0,
31};
32pub const BODY_DOJI: CandleSetting = CandleSetting {
33 range_type: RangeType::HighLow,
34 avg_period: 10,
35 factor: 0.1,
36};
37pub const SHADOW_LONG: CandleSetting = CandleSetting {
38 range_type: RangeType::RealBody,
39 avg_period: 0,
40 factor: 1.0,
41};
42pub const SHADOW_VERY_LONG: CandleSetting = CandleSetting {
43 range_type: RangeType::RealBody,
44 avg_period: 0,
45 factor: 2.0,
46};
47pub const SHADOW_SHORT: CandleSetting = CandleSetting {
48 range_type: RangeType::Shadows,
49 avg_period: 10,
50 factor: 1.0,
51};
52pub const SHADOW_VERY_SHORT: CandleSetting = CandleSetting {
53 range_type: RangeType::HighLow,
54 avg_period: 10,
55 factor: 0.1,
56};
57pub const NEAR: CandleSetting = CandleSetting {
58 range_type: RangeType::HighLow,
59 avg_period: 5,
60 factor: 0.2,
61};
62pub const FAR: CandleSetting = CandleSetting {
63 range_type: RangeType::HighLow,
64 avg_period: 5,
65 factor: 0.6,
66};
67pub const EQUAL: CandleSetting = CandleSetting {
68 range_type: RangeType::HighLow,
69 avg_period: 5,
70 factor: 0.05,
71};
72
73#[inline(always)]
74pub fn real_body(open: f64, close: f64) -> f64 {
75 (close - open).abs()
76}
77
78#[inline(always)]
79pub fn upper_shadow(open: f64, high: f64, close: f64) -> f64 {
80 high - open.max(close)
81}
82
83#[inline(always)]
84pub fn lower_shadow(open: f64, low: f64, close: f64) -> f64 {
85 open.min(close) - low
86}
87
88#[inline(always)]
89pub fn candle_color(open: f64, close: f64) -> i32 {
90 if close >= open { 1 } else { -1 }
91}
92
93#[inline(always)]
94pub fn candle_range(setting: CandleSetting, open: f64, high: f64, low: f64, close: f64) -> f64 {
95 match setting.range_type {
96 RangeType::RealBody => real_body(open, close),
97 RangeType::HighLow => high - low,
98 RangeType::Shadows => upper_shadow(open, high, close) + lower_shadow(open, low, close),
99 }
100}
101
102#[inline(always)]
103pub fn candle_average(
104 setting: CandleSetting,
105 sum: f64,
106 open: f64,
107 high: f64,
108 low: f64,
109 close: f64,
110) -> f64 {
111 let divisor = match setting.range_type {
112 RangeType::Shadows => 2.0,
113 _ => 1.0,
114 };
115 if setting.avg_period > 0 {
116 setting.factor * (sum / setting.avg_period as f64) / divisor
117 } else {
118 setting.factor * candle_range(setting, open, high, low, close) / divisor
119 }
120}
121
122#[derive(Debug, Clone, Copy, Default)]
123pub struct Candle {
124 pub open: f64,
125 pub high: f64,
126 pub low: f64,
127 pub close: f64,
128}
129
130#[derive(Debug, Clone)]
131pub struct CandleWindow {
132 window: RingBuffer<Candle>,
133 capacity: usize,
134}
135
136impl CandleWindow {
137 pub fn new(capacity: usize) -> Self {
138 Self {
139 window: RingBuffer::with_capacity(capacity),
140 capacity,
141 }
142 }
143
144 #[inline(always)]
145 pub fn push(&mut self, open: f64, high: f64, low: f64, close: f64) {
146 if self.window.len() == self.capacity {
147 self.window.pop_front();
148 }
149 self.window.push_back(Candle {
150 open,
151 high,
152 low,
153 close,
154 });
155 }
156
157 #[inline(always)]
163 pub fn bar(&self, idx: usize) -> Candle {
164 let len = self.window.len();
165 if idx >= len {
166 return Candle::default();
167 }
168 self.window.get(len - 1 - idx).copied().unwrap_or_default()
169 }
170
171 #[inline(always)]
172 pub fn len(&self) -> usize {
173 self.window.len()
174 }
175
176 #[inline(always)]
177 pub fn is_empty(&self) -> bool {
178 self.window.is_empty()
179 }
180}
181
182#[derive(Debug, Clone)]
183pub struct RollingCandleAvg {
184 pub setting: CandleSetting,
185 range_window: RingBuffer<f64>,
186 sum: f64,
187 val_window: RingBuffer<f64>,
188}
189
190impl RollingCandleAvg {
191 pub fn new(setting: CandleSetting) -> Self {
192 Self {
193 setting,
194 range_window: RingBuffer::with_capacity(if setting.avg_period > 0 {
195 setting.avg_period
196 } else {
197 1
198 }),
199 sum: 0.0,
200 val_window: RingBuffer::with_capacity(15), }
202 }
203
204 #[inline(always)]
207 fn calc_val(&self, open: f64, high: f64, low: f64, close: f64) -> f64 {
208 candle_average(self.setting, self.sum, open, high, low, close)
209 }
210
211 #[inline(always)]
212 pub fn push(&mut self, open: f64, high: f64, low: f64, close: f64) {
213 let val = self.calc_val(open, high, low, close);
215 if self.val_window.len() == 15 {
216 self.val_window.pop_front();
217 }
218 self.val_window.push_back(val);
219
220 let cr = candle_range(self.setting, open, high, low, close);
222 if self.setting.avg_period > 0 {
223 if self.range_window.len() == self.setting.avg_period
224 && let Some(old) = self.range_window.pop_front()
225 {
226 self.sum -= old;
227 }
228 self.range_window.push_back(cr);
229 self.sum += cr;
230 } else {
231 self.sum = cr;
232 }
233 }
234
235 #[inline(always)]
237 pub fn val(&self, idx: usize) -> f64 {
238 let len = self.val_window.len();
239 if idx >= len {
240 return 0.0;
241 }
242 self.val_window.get(len - 1 - idx).copied().unwrap_or(0.0)
243 }
244}