1use super::anchor_period::AnchorPeriod;
18use super::lower_interval;
19use crate::model::{Bar, BarClock, LineStyle, Timeframe};
20use crate::scales::StudyFormat;
21use crate::studies::palette;
22use crate::studies::{
23 Indicator, IndicatorValue, IndicatorValues, InputKind, InputValue, PlotStyle, PlotType,
24 StudyCandle, StudyData, StudyDataRequest, StudyInput,
25};
26use egui::Color32;
27
28const PERIODS: [AnchorPeriod; 7] = [
29 AnchorPeriod::Session,
30 AnchorPeriod::Week,
31 AnchorPeriod::Month,
32 AnchorPeriod::Quarter,
33 AnchorPeriod::Year,
34 AnchorPeriod::Decade,
35 AnchorPeriod::Century,
36];
37
38#[derive(Clone)]
39struct DeltaCandles {
40 clock: BarClock,
41 lower_interval: Option<Timeframe>,
43 anchor: Option<AnchorPeriod>,
45 candles: Vec<StudyCandle>,
46 values: IndicatorValues,
47 colors: [Color32; 3],
48 visible: bool,
49}
50
51impl DeltaCandles {
52 fn new(anchor: Option<AnchorPeriod>) -> Self {
53 Self {
54 clock: BarClock::new(Timeframe::Min1),
55 lower_interval: None,
56 anchor,
57 candles: Vec::new(),
58 values: IndicatorValues::new(),
59 colors: [palette::BLUE, palette::UP, palette::DOWN],
60 visible: true,
61 }
62 }
63
64 fn lower(&self) -> Option<Timeframe> {
65 lower_interval::lower_timeframe(&self.clock, self.lower_interval)
66 }
67
68 fn inputs(&self) -> Vec<StudyInput> {
69 let mut inputs = vec![lower_interval::input(self.lower_interval)];
70 if let Some(anchor) = self.anchor {
71 inputs.push(
72 StudyInput::new(
73 "anchor",
74 "Anchor period",
75 InputValue::Choice(PERIODS.iter().position(|p| *p == anchor).unwrap_or(0)),
76 InputKind::Choice(PERIODS.iter().map(|p| format!("{p:?}")).collect()),
77 )
78 .with_default(InputValue::Choice(0)),
79 );
80 }
81 inputs
82 }
83
84 fn set_input(&mut self, id: &str, value: InputValue) -> Result<(), crate::studies::StudyError> {
85 match (id, value) {
86 ("lower_interval", InputValue::Timeframe(chosen)) => self.lower_interval = chosen,
87 ("anchor", InputValue::Choice(at)) if self.anchor.is_some() && at < PERIODS.len() => {
88 self.anchor = Some(PERIODS[at]);
89 }
90 _ => return Err(crate::studies::StudyError::rejected(&self.inputs(), id)),
91 }
92 self.refresh(&[], 0, &[]);
93 Ok(())
94 }
95
96 fn update_data(&mut self, data: &[Bar], from: usize, sources: &[StudyData<'_>]) {
97 let from = from.min(self.values.len()).min(data.len());
98 let candles = match self.lower() {
99 Some(lower) => {
100 match lower_interval::reply(&lower_interval::request(lower), from, sources) {
101 Some(bars) => bars,
102 None => return,
103 }
104 }
105 None => &[],
106 };
107 self.refresh(data, from, candles);
108 }
109
110 fn refresh(&mut self, data: &[Bar], from: usize, candles: &[Bar]) {
112 let from = if candles.is_empty() {
113 0
114 } else {
115 from.min(self.values.len()).min(data.len())
116 };
117 self.candles.truncate(
118 self.candles
119 .partition_point(|candle| candle.bars.start < from),
120 );
121 if from == 0 {
122 self.values = IndicatorValues::missing(data.len());
123 } else {
124 self.values.truncate(from);
125 self.values.resize(data.len(), IndicatorValue::None);
126 }
127 let Some(lower) = self.lower().filter(|_| !candles.is_empty()) else {
128 return;
129 };
130 let sessions = self.clock.sessions();
131 let anchor = self.anchor;
132 let period = |bar: &Bar| anchor.map(|anchor| anchor.key(sessions.trading_date(bar.time)));
133 let mut last = self
134 .candles
135 .last()
136 .map(|candle| (period(&data[candle.bars.start]), candle.bar.close));
137 let ranges = lower_interval::nest(&self.clock, lower, data, candles, from);
138 for (i, range) in (from..).zip(ranges) {
139 let bar = &data[i];
140 if range.is_empty() || !bar.close.is_finite() {
141 continue;
142 }
143 let flow = lower_interval::flow(candles, range);
144 let key = period(bar);
145 let open = match last {
146 Some((at, total)) if anchor.is_some() && at == key => total,
147 _ => 0.0,
148 };
149 let (high, low, close) = (open + flow.high, open + flow.low, open + flow.delta);
150 if ![high, low, close].iter().all(|value| value.is_finite()) {
151 continue;
152 }
153 let down = close < open;
154 self.values.set(
155 i,
156 IndicatorValue::Multiple(if down {
157 vec![close, f64::NAN, close]
158 } else {
159 vec![close, close, f64::NAN]
160 }),
161 );
162 self.candles.push(StudyCandle {
163 bars: i..i + 1,
164 bar: Bar::new(bar.time, open, high, low, close, flow.up - flow.down),
165 plot: 1 + usize::from(down),
166 });
167 last = Some((key, close));
168 }
169 }
170}
171
172macro_rules! delta_study {
173 ($(#[$meta:meta])* $ty:ident, $name:literal, $desc:literal, $anchor:expr) => {
174 $(#[$meta])*
175 #[derive(Clone)]
176 pub struct $ty {
177 core: DeltaCandles,
178 }
179
180 impl $ty {
181 pub fn new() -> Self {
184 Self {
185 core: DeltaCandles::new($anchor),
186 }
187 }
188 }
189
190 impl Default for $ty {
191 fn default() -> Self {
192 Self::new()
193 }
194 }
195
196 impl Indicator for $ty {
197 fn name(&self) -> &str {
198 $name
199 }
200 fn desc(&self) -> &str {
201 $desc
202 }
203 fn inputs(&self) -> Vec<StudyInput> {
204 self.core.inputs()
205 }
206 fn set_input(&mut self, id: &str, value: InputValue) -> Result<(), crate::studies::StudyError> {
207 self.core.set_input(id, value)
208 }
209 fn data_requests(&self) -> Vec<StudyDataRequest> {
210 self.core
211 .lower()
212 .map(lower_interval::request)
213 .into_iter()
214 .collect()
215 }
216 fn update_data(&mut self, data: &[Bar], from: usize, sources: &[StudyData<'_>]) {
217 self.core.update_data(data, from, sources);
218 }
219 fn set_timeframe(&mut self, timeframe: Timeframe) {
220 self.set_bar_clock(&self.core.clock.for_timeframe(timeframe));
221 }
222 fn set_bar_clock(&mut self, clock: &BarClock) -> bool {
223 let changed = self.core.clock != *clock;
224 self.core.clock = clock.clone();
225 changed
226 }
227 fn calculate(&mut self, data: &[Bar]) {
228 self.core.refresh(data, 0, &[]);
229 }
230 fn is_incremental(&self) -> bool {
231 true
232 }
233 fn is_overlay(&self) -> bool {
234 false
235 }
236 fn value_format(&self) -> StudyFormat {
237 StudyFormat::Volume
238 }
239 fn plot_style(&self, line: usize) -> PlotStyle {
240 PlotStyle {
241 color: self.core.colors.get(line).copied().unwrap_or(Color32::WHITE),
242 width: self.line_style(line).width,
243 pattern: LineStyle::Solid,
244 plot_type: PlotType::Line,
245 visible: line != 0,
246 }
247 }
248 fn values(&self) -> &IndicatorValues {
249 &self.core.values
250 }
251 fn candles(&self) -> &[StudyCandle] {
252 &self.core.candles
253 }
254 fn colors(&self) -> Vec<Color32> {
255 self.core.colors.to_vec()
256 }
257 fn set_colors(&mut self, colors: Vec<Color32>) {
258 for (old, new) in self.core.colors.iter_mut().zip(colors) {
259 *old = new;
260 }
261 }
262 fn is_visible(&self) -> bool {
263 self.core.visible
264 }
265 fn set_visible(&mut self, visible: bool) {
266 self.core.visible = visible;
267 }
268 fn clone_box(&self) -> Box<dyn Indicator> {
269 Box::new(self.clone())
270 }
271 fn line_names(&self) -> Vec<String> {
272 ["Delta", "Up", "Down"].map(String::from).to_vec()
273 }
274 }
275 };
276}
277
278delta_study!(
279 VolumeDelta,
282 "Volume Delta",
283 "Volume Delta - Up minus down volume of the lower-interval candles in each bar, as a candle from zero",
284 None
285);
286delta_study!(
287 CumulativeVolumeDelta,
290 "Cumulative Volume Delta",
291 "Cumulative Volume Delta - Volume delta of lower-interval candles added up over each anchor period",
292 Some(AnchorPeriod::Session)
293);
294
295#[cfg(test)]
296mod tests {
297 use super::*;
298 use crate::studies::IndicatorFactory;
299 use chrono::{TimeZone, Utc};
300
301 #[test]
305 fn delta_candles_follow_the_running_delta_of_lower_interval_candles() {
306 let factory = IndicatorFactory::new();
307 for name in ["Volume Delta", "Cumulative Volume Delta"] {
308 assert_eq!(factory.category(name), Some("Volume"), "{name}");
309 }
310 let bar = |s: i64, o: f64, c: f64, v: f64| {
311 Bar::new(
312 Utc.timestamp_opt(s, 0).unwrap(),
313 o,
314 o.max(c),
315 o.min(c),
316 c,
317 v,
318 )
319 };
320 let update = |study: &mut dyn Indicator, data: &[Bar], from: usize, candles: &[Bar]| {
321 study.update_data(
322 data,
323 from,
324 &[StudyData {
325 request: StudyDataRequest {
326 metric: None,
327 symbol: String::new(),
328 timeframe: Some(Timeframe::Min1),
329 },
330 bars: candles,
331 clock: BarClock::new(Timeframe::Min1),
332 instrument: None,
333 timeframe: Timeframe::Min1,
334 }],
335 );
336 };
337 let drawn = |study: &dyn Indicator| -> Vec<(usize, usize, [f64; 4])> {
338 study
339 .candles()
340 .iter()
341 .map(|c| {
342 (
343 c.bars.start,
344 c.plot,
345 [c.bar.open, c.bar.high, c.bar.low, c.bar.close],
346 )
347 })
348 .collect()
349 };
350
351 let chart = [0, 300, 600, 900].map(|s| bar(s, 1.0, 1.0, 999.0));
353 let candles = [
354 bar(0, 10.0, 11.0, 5.0), bar(60, 11.0, 10.0, 8.0), bar(120, 10.0, 10.0, 5.0), bar(300, 10.0, 9.0, 4.0), bar(360, 9.0, 9.0, 1.0), bar(900, 9.0, 8.0, 0.0), ];
361 let mut delta = VolumeDelta::new();
362 delta.set_bar_clock(&BarClock::new(Timeframe::Min5));
363 assert_eq!(delta.data_requests()[0].timeframe, Some(Timeframe::Min1));
364 update(&mut delta, &chart, 0, &candles);
365 assert_eq!(
366 drawn(&delta),
367 [
368 (0, 1, [0.0, 5.0, -3.0, 2.0]),
369 (1, 2, [0.0, 0.0, -4.0, -3.0]),
370 (3, 1, [0.0; 4]),
371 ]
372 );
373 let values = delta.values();
375 assert_eq!(
376 (0..4).map(|i| values.value(0, i)).collect::<Vec<_>>(),
377 [Some(2.0), Some(-3.0), None, Some(0.0)]
378 );
379 assert_eq!(
380 [
381 values.value(1, 0),
382 values.value(2, 0),
383 values.value(2, 1),
384 values.value(1, 1)
385 ],
386 [Some(2.0), None, Some(-3.0), None]
387 );
388
389 let chart =
391 [79_200, 82_800, 86_400, 90_000, 93_600, 97_200].map(|s| bar(s, 1.0, 1.0, 999.0));
392 let mut candles = vec![
393 bar(79_200, 1.0, 2.0, 10.0), bar(79_260, 2.0, 1.0, 4.0), bar(82_800, 1.0, 1.0, 3.0), bar(82_860, 1.0, 0.0, 5.0), bar(86_400, 0.0, 2.0, 7.0), bar(90_000, 2.0, 1.0, 2.0), bar(97_200, 1.0, 2.0, 1.0), ];
401 let mut cumulative = CumulativeVolumeDelta::new();
402 cumulative.set_bar_clock(&BarClock::new(Timeframe::Hour1));
403 update(&mut cumulative, &chart, 0, &candles);
404 assert_eq!(
406 drawn(&cumulative),
407 [
408 (0, 1, [0.0, 10.0, 0.0, 6.0]),
409 (1, 2, [6.0, 9.0, 4.0, 4.0]),
410 (2, 1, [0.0, 7.0, 0.0, 7.0]),
411 (3, 2, [7.0, 7.0, 5.0, 5.0]),
412 (5, 1, [5.0, 6.0, 5.0, 6.0]),
413 ]
414 );
415 candles.insert(6, bar(90_060, 1.0, 3.0, 4.0));
417 update(&mut cumulative, &chart, 3, &candles);
418 assert_eq!(
419 drawn(&cumulative)[2..],
420 [
421 (2, 1, [0.0, 7.0, 0.0, 7.0]),
422 (3, 1, [7.0, 9.0, 5.0, 9.0]),
423 (5, 1, [9.0, 10.0, 9.0, 10.0]),
424 ]
425 );
426 cumulative
428 .set_input("anchor", InputValue::Choice(1))
429 .unwrap();
430 update(&mut cumulative, &chart, 0, &candles);
431 assert_eq!(
432 drawn(&cumulative)[2..],
433 [
434 (2, 1, [4.0, 11.0, 4.0, 11.0]),
435 (3, 1, [11.0, 13.0, 9.0, 13.0]),
436 (5, 1, [13.0, 14.0, 13.0, 14.0]),
437 ]
438 );
439 assert_eq!(cumulative.values().value(0, 5), Some(14.0));
440 }
441}