sdb_core/postprocessing/candle/
dollar_bars.rs

1use super::{Candle, Sampler};
2use crate::dtf::update::Update;
3
4/// sample by dollar traded
5pub struct DollarSampler {
6    interval: f32,
7    elapsed: f32,
8}
9
10impl DollarSampler {
11    /// create a new Dollar sampler
12    pub fn new(interval: f32) -> Self {
13        Self {
14            elapsed: 0.,
15            interval,
16        }
17    }
18}
19
20impl Sampler for DollarSampler {
21    fn reset(&mut self) {
22        self.elapsed = 0.;
23    }
24
25    fn is_sample(&mut self, trade: &Update) -> bool {
26        self.elapsed += trade.price * trade.size;
27
28        if self.elapsed > self.interval {
29            self.elapsed = 0.;
30            true
31        } else {
32            false
33        }
34    }
35}
36
37/// Iterator for Bars sampled by dollars traded
38pub struct DollarBarsIter<I:Iterator<Item=Update>> {
39    it: I,
40    current_candle: Option<Candle>,
41    sampler: DollarSampler,
42}
43
44impl<I:Iterator<Item=Update>> DollarBarsIter<I> {
45    /// Create a new iterator for time bars
46    pub fn new(it: I, dollar_interval: f32) -> Self {
47        Self {
48            it,
49            current_candle: None,
50            sampler: DollarSampler::new(dollar_interval),
51        }
52    }
53}
54
55fn new_candle(trade: Update) -> Candle {
56    Candle {
57        start: trade.ts,
58        end: trade.ts,
59        volume: trade.size,
60        high: trade.price,
61        low: trade.price,
62        close: trade.price,
63        open: trade.price,
64    }
65}
66
67impl<I:Iterator<Item=Update>> Iterator for DollarBarsIter<I> {
68    type Item = Candle;
69    fn next(&mut self) -> Option<Self::Item> {
70        while let Some(trade) = self.it.next() {
71            if !trade.is_trade {
72                continue;
73            }
74
75            if let Some(c) = self.current_candle {
76                if self.sampler.is_sample(&trade) {
77                    self.current_candle = Some(new_candle(trade));
78                    return Some(c);
79                };
80            }
81
82            self.current_candle = if let Some(c) = self.current_candle {
83                Some(Candle {
84                    start: c.start,
85                    end: trade.ts,
86                    volume: c.volume + trade.size,
87                    high: trade.price.max(c.high),
88                    low: trade.price.min(c.low),
89                    close: trade.price,
90                    open: c.open,
91                })
92            } else {
93                Some(new_candle(trade))
94            };
95        }
96        if let Some(x) = self.current_candle {
97            self.current_candle = None;
98            return Some(x)
99        } else {
100            None
101        }
102    }
103}
104
105
106#[derive(Clone, Debug, PartialEq)]
107/// utilities for rebinning candlesticks
108pub struct DollarBars {
109    v: Vec<Candle>,
110}
111
112impl DollarBars {
113
114    /// Generate a vector of candles sampled by dollar traded.
115    pub fn from_updates(ups: &[Update], dollar_interval: f32) -> DollarBars {
116        let v = DollarBarsIter::new(ups.iter().copied(), dollar_interval).collect();
117        DollarBars { v }
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124    use std::f32;
125    #[test]
126    fn test_dollar_bar() {
127        let trades = (0..10).map(|i| Update {
128            is_trade: true,
129            is_bid: true,
130            price: i as f32,
131            size: f32::abs(i as f32),
132            ts: i,
133            seq: 0,
134        })
135        .collect::<Vec<_>>();
136
137        let ret = DollarBars::from_updates(&trades, 100.);
138
139        assert_eq!(DollarBars {v: vec![Candle {
140                start: 0,
141                end: 6,
142                open: 0.0,
143                high: 6.0,
144                low: 0.0,
145                close: 6.0,
146                volume: 21.0,
147            }, Candle {
148                start: 7,
149                end: 8,
150                open: 7.0,
151                high: 8.0,
152                low: 7.0,
153                close: 8.0,
154                volume: 15.0,
155            }, Candle {
156                start: 9,
157                end: 9,
158                open: 9.0,
159                high: 9.0,
160                low: 9.0,
161                close: 9.0,
162                volume: 9.0,
163            }]}, ret);
164    }
165}