longbridge_candlesticks/
merger.rs

1use rust_decimal::{prelude::FromPrimitive, Decimal};
2use time::{macros::time, Date, Duration, Month, OffsetDateTime, Time, Weekday};
3use time_tz::OffsetDateTimeExt;
4
5use crate::{market::UpdateFields, Market, Period, Type};
6
7#[derive(Debug, Copy, Clone, Eq, PartialEq)]
8pub struct Candlestick {
9    pub time: OffsetDateTime,
10    pub open: Decimal,
11    pub high: Decimal,
12    pub low: Decimal,
13    pub close: Decimal,
14    pub volume: i64,
15    pub turnover: Decimal,
16}
17
18#[derive(Debug, Copy, Clone, Eq, PartialEq)]
19pub struct Trade<'a> {
20    pub time: OffsetDateTime,
21    pub price: Decimal,
22    pub volume: i64,
23    pub trade_type: &'a str,
24}
25
26#[derive(Debug, Copy, Clone, Eq, PartialEq)]
27pub enum UpdateAction {
28    UpdateLast(Candlestick),
29    AppendNew(Candlestick),
30    None,
31}
32
33pub trait IsHalfTradeDay: Copy {
34    fn is_half(&self, date: Date) -> bool;
35}
36
37impl IsHalfTradeDay for bool {
38    #[inline]
39    fn is_half(&self, _date: Date) -> bool {
40        *self
41    }
42}
43
44pub struct Merger<T> {
45    market: Market,
46    period: Period,
47    is_half_trade_day: T,
48}
49
50impl<T> Merger<T>
51where
52    T: IsHalfTradeDay,
53{
54    #[inline]
55    pub fn new(market: Market, period: Period, is_half_trade_day: T) -> Self {
56        Self {
57            market,
58            period,
59            is_half_trade_day,
60        }
61    }
62
63    fn round_time(
64        &self,
65        mut time: OffsetDateTime,
66        trade_sessions: &[(Time, Time)],
67    ) -> OffsetDateTime {
68        for (idx, (start, end)) in trade_sessions.iter().enumerate() {
69            if time.time() < *start {
70                time = if idx == 0 {
71                    time.replace_time(*start)
72                } else {
73                    time.replace_time(trade_sessions[idx - 1].1)
74                };
75                break;
76            } else if time.time() < *end {
77                break;
78            } else if idx == trade_sessions.len() - 1 {
79                time = time.replace_time(*end);
80                break;
81            }
82        }
83
84        time
85    }
86
87    pub fn candlestick_time(&self, ty: Type, time: OffsetDateTime) -> OffsetDateTime {
88        let Merger {
89            market,
90            period,
91            is_half_trade_day,
92        } = self;
93        let trade_sessions = if !is_half_trade_day.is_half(time.date()) {
94            market.trade_sessions(ty)
95        } else {
96            market.half_trade_sessions(ty)
97        };
98        match period {
99            Period::Min_1 => self
100                .round_time(time, trade_sessions)
101                .replace_second(0)
102                .unwrap(),
103            Period::Min_5 | Period::Min_15 | Period::Min_30 => {
104                let time = self.round_time(time, trade_sessions);
105                let n = period.minutes() as i64;
106                let minutes = time.hour() as i64 * 60 + time.minute() as i64 - 1;
107                let minutes = (minutes / n + 1) * n;
108                let mut time = time.replace_time(
109                    Time::from_hms((minutes / 60) as u8, (minutes % 60) as u8, 0).unwrap(),
110                );
111                for (start, end) in trade_sessions {
112                    let s = time.replace_time(*start);
113                    if time < s + Duration::minutes(n) {
114                        time = s + Duration::minutes(n);
115                        break;
116                    } else if time <= time.replace_time(*end) {
117                        break;
118                    }
119                }
120                time
121            }
122            Period::Min_60 => {
123                let time = self.round_time(time, trade_sessions);
124                let (start, end) = trade_sessions
125                    .iter()
126                    .find(|ts| time.time() >= ts.0 && time.time() <= ts.1)
127                    .unwrap();
128                let start_minutes = start.hour() as i64 * 60 + start.minute() as i64;
129                let curr_minutes = time.hour() as i64 * 60 + time.minute() as i64 - 1;
130                let offset_minutes = ((curr_minutes - start_minutes) / 60 + 1) * 60;
131                time.replace_time((*start + Duration::minutes(offset_minutes)).min(*end))
132            }
133            Period::Day => time.replace_time(time!(00:00:00)),
134            Period::Week => {
135                let week = time.iso_week();
136                Date::from_iso_week_date(time.year(), week, Weekday::Monday)
137                    .and_then(|date| date.with_hms(0, 0, 0))
138                    .unwrap()
139                    .assume_utc()
140            }
141            Period::Month => time
142                .replace_day(1)
143                .map(|time| time.replace_time(time!(00:00:00)))
144                .unwrap(),
145            Period::Year => time
146                .replace_month(Month::January)
147                .and_then(|time| time.replace_day(1))
148                .map(|time| time.replace_time(time!(00:00:00)))
149                .and_then(|time| time.replace_day(1))
150                .unwrap(),
151        }
152    }
153
154    #[must_use]
155    pub fn merge(&self, ty: Type, prev: Option<&Candlestick>, trade: Trade<'_>) -> UpdateAction {
156        let Merger { market, .. } = self;
157        let tz = market.timezone();
158        let time = self.candlestick_time(ty, trade.time.to_timezone(tz));
159        let update_fields = market.update_fields(trade.trade_type);
160
161        match prev {
162            Some(prev) if time == prev.time => {
163                let mut candlestick = *prev;
164
165                if update_fields.contains(UpdateFields::PRICE) {
166                    candlestick.high = candlestick.high.max(trade.price);
167                    candlestick.low = candlestick.low.min(trade.price);
168                    candlestick.close = trade.price;
169                }
170
171                if update_fields.contains(UpdateFields::VOLUME) {
172                    candlestick.volume += trade.volume;
173                    candlestick.turnover += trade.price
174                        * Decimal::from_i64(self.market.num_shares(trade.volume))
175                            .unwrap_or_default();
176                }
177
178                UpdateAction::UpdateLast(candlestick)
179            }
180            Some(prev) if time < prev.time => UpdateAction::None,
181            _ => {
182                if update_fields.contains(UpdateFields::PRICE) {
183                    let new_candlestick = Candlestick {
184                        time: time.to_timezone(time_tz::timezones::db::UTC),
185                        open: trade.price,
186                        high: trade.price,
187                        low: trade.price,
188                        close: trade.price,
189                        volume: trade.volume,
190                        turnover: trade.price
191                            * Decimal::from_i64(self.market.num_shares(trade.volume))
192                                .unwrap_or_default(),
193                    };
194                    UpdateAction::AppendNew(new_candlestick)
195                } else {
196                    UpdateAction::None
197                }
198            }
199        }
200    }
201}
202
203#[cfg(test)]
204mod tests {
205    use time::macros::datetime;
206
207    use super::*;
208
209    #[test]
210    fn test_round_time() {
211        let trade_sessions = Market::HK.trade_sessions(Type::Normal);
212        let merger = Merger::new(Market::HK, Period::Day, false);
213
214        assert_eq!(
215            merger.round_time(datetime!(2022-1-1 9:28:0 UTC), trade_sessions),
216            datetime!(2022-1-1 9:30:0 UTC)
217        );
218        assert_eq!(
219            merger.round_time(datetime!(2022-1-1 9:31:0 UTC), trade_sessions),
220            datetime!(2022-1-1 9:31:0 UTC)
221        );
222        assert_eq!(
223            merger.round_time(datetime!(2022-1-1 12:0:0 UTC), trade_sessions),
224            datetime!(2022-1-1 12:0:0 UTC)
225        );
226        assert_eq!(
227            merger.round_time(datetime!(2022-1-1 12:5:0 UTC), trade_sessions),
228            datetime!(2022-1-1 12:0:0 UTC)
229        );
230        assert_eq!(
231            merger.round_time(datetime!(2022-1-1 13:0:0 UTC), trade_sessions),
232            datetime!(2022-1-1 13:0:0 UTC)
233        );
234        assert_eq!(
235            merger.round_time(datetime!(2022-1-1 14:0:0 UTC), trade_sessions),
236            datetime!(2022-1-1 14:0:0 UTC)
237        );
238        assert_eq!(
239            merger.round_time(datetime!(2022-1-1 16:0:0 UTC), trade_sessions),
240            datetime!(2022-1-1 16:0:0 UTC)
241        );
242        assert_eq!(
243            merger.round_time(datetime!(2022-1-1 16:2:0 UTC), trade_sessions),
244            datetime!(2022-1-1 16:0:0 UTC)
245        );
246    }
247
248    #[test]
249    fn test_time_min1() {
250        let merger = Merger::new(Market::HK, Period::Min_1, false);
251
252        assert_eq!(
253            merger.candlestick_time(Type::Normal, datetime!(2022-1-1 9:28:0 UTC)),
254            datetime!(2022-1-1 9:30:0 UTC)
255        );
256        assert_eq!(
257            merger.candlestick_time(Type::Normal, datetime!(2022-1-1 9:30:25 UTC)),
258            datetime!(2022-1-1 9:30:0 UTC)
259        );
260        assert_eq!(
261            merger.candlestick_time(Type::Normal, datetime!(2022-1-1 9:31:0 UTC)),
262            datetime!(2022-1-1 9:31:0 UTC)
263        );
264        assert_eq!(
265            merger.candlestick_time(Type::Normal, datetime!(2022-1-1 12:05:0 UTC)),
266            datetime!(2022-1-1 12:0:0 UTC)
267        );
268        assert_eq!(
269            merger.candlestick_time(Type::Normal, datetime!(2022-1-1 13:0:0 UTC)),
270            datetime!(2022-1-1 13:0:0 UTC)
271        );
272        assert_eq!(
273            merger.candlestick_time(Type::Normal, datetime!(2022-1-1 16:0:0 UTC)),
274            datetime!(2022-1-1 16:0:0 UTC)
275        );
276        assert_eq!(
277            merger.candlestick_time(Type::Normal, datetime!(2022-1-1 16:2:0 UTC)),
278            datetime!(2022-1-1 16:0:0 UTC)
279        );
280    }
281
282    #[test]
283    fn test_time_min5() {
284        let merger = Merger::new(Market::HK, Period::Min_5, false);
285
286        assert_eq!(
287            merger.candlestick_time(Type::Normal, datetime!(2022-1-1 9:28:0 UTC)),
288            datetime!(2022-1-1 9:35:0 UTC)
289        );
290        assert_eq!(
291            merger.candlestick_time(Type::Normal, datetime!(2022-1-1 9:30:25 UTC)),
292            datetime!(2022-1-1 9:35:0 UTC)
293        );
294        assert_eq!(
295            merger.candlestick_time(Type::Normal, datetime!(2022-1-1 9:35:59 UTC)),
296            datetime!(2022-1-1 9:35:0 UTC)
297        );
298        assert_eq!(
299            merger.candlestick_time(Type::Normal, datetime!(2022-1-1 9:36:0 UTC)),
300            datetime!(2022-1-1 9:40:0 UTC)
301        );
302        assert_eq!(
303            merger.candlestick_time(Type::Normal, datetime!(2022-1-1 12:05:0 UTC)),
304            datetime!(2022-1-1 12:0:0 UTC)
305        );
306        assert_eq!(
307            merger.candlestick_time(Type::Normal, datetime!(2022-1-1 13:0:0 UTC)),
308            datetime!(2022-1-1 13:5:0 UTC)
309        );
310        assert_eq!(
311            merger.candlestick_time(Type::Normal, datetime!(2022-1-1 16:0:0 UTC)),
312            datetime!(2022-1-1 16:0:0 UTC)
313        );
314        assert_eq!(
315            merger.candlestick_time(Type::Normal, datetime!(2022-1-1 16:2:0 UTC)),
316            datetime!(2022-1-1 16:0:0 UTC)
317        );
318    }
319
320    #[test]
321    fn test_time_min15() {
322        let merger = Merger::new(Market::HK, Period::Min_15, false);
323
324        assert_eq!(
325            merger.candlestick_time(Type::Normal, datetime!(2022-1-1 9:28:0 UTC)),
326            datetime!(2022-1-1 9:45:0 UTC)
327        );
328        assert_eq!(
329            merger.candlestick_time(Type::Normal, datetime!(2022-1-1 9:30:25 UTC)),
330            datetime!(2022-1-1 9:45:0 UTC)
331        );
332        assert_eq!(
333            merger.candlestick_time(Type::Normal, datetime!(2022-1-1 9:35:59 UTC)),
334            datetime!(2022-1-1 9:45:0 UTC)
335        );
336        assert_eq!(
337            merger.candlestick_time(Type::Normal, datetime!(2022-1-1 9:36:0 UTC)),
338            datetime!(2022-1-1 9:45:0 UTC)
339        );
340        assert_eq!(
341            merger.candlestick_time(Type::Normal, datetime!(2022-1-1 12:05:0 UTC)),
342            datetime!(2022-1-1 12:0:0 UTC)
343        );
344        assert_eq!(
345            merger.candlestick_time(Type::Normal, datetime!(2022-1-1 13:0:0 UTC)),
346            datetime!(2022-1-1 13:15:0 UTC)
347        );
348        assert_eq!(
349            merger.candlestick_time(Type::Normal, datetime!(2022-1-1 16:0:0 UTC)),
350            datetime!(2022-1-1 16:0:0 UTC)
351        );
352        assert_eq!(
353            merger.candlestick_time(Type::Normal, datetime!(2022-1-1 16:2:0 UTC)),
354            datetime!(2022-1-1 16:0:0 UTC)
355        );
356    }
357
358    #[test]
359    fn test_time_min30() {
360        let merger = Merger::new(Market::HK, Period::Min_30, false);
361
362        assert_eq!(
363            merger.candlestick_time(Type::Normal, datetime!(2022-1-1 9:28:0 UTC)),
364            datetime!(2022-1-1 10:00:0 UTC)
365        );
366        assert_eq!(
367            merger.candlestick_time(Type::Normal, datetime!(2022-1-1 9:30:25 UTC)),
368            datetime!(2022-1-1 10:00:0 UTC)
369        );
370        assert_eq!(
371            merger.candlestick_time(Type::Normal, datetime!(2022-1-1 9:35:59 UTC)),
372            datetime!(2022-1-1 10:00:0 UTC)
373        );
374        assert_eq!(
375            merger.candlestick_time(Type::Normal, datetime!(2022-1-1 9:36:0 UTC)),
376            datetime!(2022-1-1 10:00:0 UTC)
377        );
378        assert_eq!(
379            merger.candlestick_time(Type::Normal, datetime!(2022-1-1 12:05:0 UTC)),
380            datetime!(2022-1-1 12:0:0 UTC)
381        );
382        assert_eq!(
383            merger.candlestick_time(Type::Normal, datetime!(2022-1-1 13:0:0 UTC)),
384            datetime!(2022-1-1 13:30:0 UTC)
385        );
386        assert_eq!(
387            merger.candlestick_time(Type::Normal, datetime!(2022-1-1 16:0:0 UTC)),
388            datetime!(2022-1-1 16:0:0 UTC)
389        );
390        assert_eq!(
391            merger.candlestick_time(Type::Normal, datetime!(2022-1-1 16:2:0 UTC)),
392            datetime!(2022-1-1 16:0:0 UTC)
393        );
394    }
395
396    #[test]
397    fn test_time_min60() {
398        let merger = Merger::new(Market::HK, Period::Min_60, false);
399
400        assert_eq!(
401            merger.candlestick_time(Type::Normal, datetime!(2022-1-1 9:28:0 UTC)),
402            datetime!(2022-1-1 10:30:0 UTC)
403        );
404        assert_eq!(
405            merger.candlestick_time(Type::Normal, datetime!(2022-1-1 9:30:25 UTC)),
406            datetime!(2022-1-1 10:30:0 UTC)
407        );
408        assert_eq!(
409            merger.candlestick_time(Type::Normal, datetime!(2022-1-1 9:35:59 UTC)),
410            datetime!(2022-1-1 10:30:0 UTC)
411        );
412        assert_eq!(
413            merger.candlestick_time(Type::Normal, datetime!(2022-1-1 9:36:0 UTC)),
414            datetime!(2022-1-1 10:30:0 UTC)
415        );
416        assert_eq!(
417            merger.candlestick_time(Type::Normal, datetime!(2022-1-1 10:30:59 UTC)),
418            datetime!(2022-1-1 10:30:0 UTC)
419        );
420        assert_eq!(
421            merger.candlestick_time(Type::Normal, datetime!(2022-1-1 10:31:0 UTC)),
422            datetime!(2022-1-1 11:30:0 UTC)
423        );
424        assert_eq!(
425            merger.candlestick_time(Type::Normal, datetime!(2022-1-1 12:05:0 UTC)),
426            datetime!(2022-1-1 12:0:0 UTC)
427        );
428        assert_eq!(
429            merger.candlestick_time(Type::Normal, datetime!(2022-1-1 13:0:0 UTC)),
430            datetime!(2022-1-1 14:0:0 UTC)
431        );
432        assert_eq!(
433            merger.candlestick_time(Type::Normal, datetime!(2022-1-1 14:2:0 UTC)),
434            datetime!(2022-1-1 15:0:0 UTC)
435        );
436        assert_eq!(
437            merger.candlestick_time(Type::Normal, datetime!(2022-1-1 16:0:0 UTC)),
438            datetime!(2022-1-1 16:0:0 UTC)
439        );
440        assert_eq!(
441            merger.candlestick_time(Type::Normal, datetime!(2022-1-1 16:2:0 UTC)),
442            datetime!(2022-1-1 16:0:0 UTC)
443        );
444    }
445
446    #[test]
447    fn test_time_min60_usoq() {
448        let merger = Merger::new(Market::US, Period::Min_60, false);
449
450        assert_eq!(
451            merger.candlestick_time(Type::USOQ, datetime!(2022-1-1 9:28:0 UTC)),
452            datetime!(2022-1-1 10:30:0 UTC)
453        );
454        assert_eq!(
455            merger.candlestick_time(Type::USOQ, datetime!(2022-1-1 9:30:25 UTC)),
456            datetime!(2022-1-1 10:30:0 UTC)
457        );
458        assert_eq!(
459            merger.candlestick_time(Type::USOQ, datetime!(2022-1-1 9:35:59 UTC)),
460            datetime!(2022-1-1 10:30:0 UTC)
461        );
462        assert_eq!(
463            merger.candlestick_time(Type::USOQ, datetime!(2022-1-1 9:36:0 UTC)),
464            datetime!(2022-1-1 10:30:0 UTC)
465        );
466        assert_eq!(
467            merger.candlestick_time(Type::USOQ, datetime!(2022-1-1 10:30:59 UTC)),
468            datetime!(2022-1-1 10:30:0 UTC)
469        );
470        assert_eq!(
471            merger.candlestick_time(Type::USOQ, datetime!(2022-1-1 10:31:0 UTC)),
472            datetime!(2022-1-1 11:30:0 UTC)
473        );
474        assert_eq!(
475            merger.candlestick_time(Type::USOQ, datetime!(2022-1-1 12:05:0 UTC)),
476            datetime!(2022-1-1 12:30:0 UTC)
477        );
478        assert_eq!(
479            merger.candlestick_time(Type::USOQ, datetime!(2022-1-1 13:0:0 UTC)),
480            datetime!(2022-1-1 13:30:0 UTC)
481        );
482        assert_eq!(
483            merger.candlestick_time(Type::USOQ, datetime!(2022-1-1 14:2:0 UTC)),
484            datetime!(2022-1-1 14:30:0 UTC)
485        );
486        assert_eq!(
487            merger.candlestick_time(Type::USOQ, datetime!(2022-1-1 15:30:59 UTC)),
488            datetime!(2022-1-1 15:30:0 UTC)
489        );
490        assert_eq!(
491            merger.candlestick_time(Type::USOQ, datetime!(2022-1-1 15:31:0 UTC)),
492            datetime!(2022-1-1 16:15:0 UTC)
493        );
494        assert_eq!(
495            merger.candlestick_time(Type::USOQ, datetime!(2022-1-1 16:2:0 UTC)),
496            datetime!(2022-1-1 16:15:0 UTC)
497        );
498    }
499
500    #[test]
501    fn test_time_day() {
502        let merger = Merger::new(Market::HK, Period::Day, false);
503
504        assert_eq!(
505            merger.candlestick_time(Type::Normal, datetime!(2022-1-1 9:28:0 UTC)),
506            datetime!(2022-1-1 0:0:0 UTC)
507        );
508        assert_eq!(
509            merger.candlestick_time(Type::Normal, datetime!(2022-1-1 10:0:0 UTC)),
510            datetime!(2022-1-1 0:0:0 UTC)
511        );
512        assert_eq!(
513            merger.candlestick_time(Type::Normal, datetime!(2022-1-3 10:0:0 UTC)),
514            datetime!(2022-1-3 0:0:0 UTC)
515        );
516    }
517
518    #[test]
519    fn test_time_week() {
520        let merger = Merger::new(Market::HK, Period::Week, false);
521
522        assert_eq!(
523            merger.candlestick_time(Type::Normal, datetime!(2022-1-6 9:28:0 UTC)),
524            datetime!(2022-1-3 0:0:0 UTC)
525        );
526        assert_eq!(
527            merger.candlestick_time(Type::Normal, datetime!(2022-1-10 9:28:0 UTC)),
528            datetime!(2022-1-10 0:0:0 UTC)
529        );
530        assert_eq!(
531            merger.candlestick_time(Type::Normal, datetime!(2022-6-8 9:28:0 UTC)),
532            datetime!(2022-6-6 0:0:0 UTC)
533        );
534    }
535
536    #[test]
537    fn test_time_month() {
538        let merger = Merger::new(Market::HK, Period::Month, false);
539
540        assert_eq!(
541            merger.candlestick_time(Type::Normal, datetime!(2022-1-6 9:28:0 UTC)),
542            datetime!(2022-1-1 0:0:0 UTC)
543        );
544        assert_eq!(
545            merger.candlestick_time(Type::Normal, datetime!(2022-1-10 9:28:0 UTC)),
546            datetime!(2022-1-1 0:0:0 UTC)
547        );
548        assert_eq!(
549            merger.candlestick_time(Type::Normal, datetime!(2022-6-8 9:28:0 UTC)),
550            datetime!(2022-6-1 0:0:0 UTC)
551        );
552    }
553
554    #[test]
555    fn test_time_year() {
556        let merger = Merger::new(Market::HK, Period::Year, false);
557
558        assert_eq!(
559            merger.candlestick_time(Type::Normal, datetime!(2022-1-6 9:28:0 UTC)),
560            datetime!(2022-1-1 0:0:0 UTC)
561        );
562        assert_eq!(
563            merger.candlestick_time(Type::Normal, datetime!(2022-3-10 9:28:0 UTC)),
564            datetime!(2022-1-1 0:0:0 UTC)
565        );
566        assert_eq!(
567            merger.candlestick_time(Type::Normal, datetime!(2022-6-8 9:28:0 UTC)),
568            datetime!(2022-1-1 0:0:0 UTC)
569        );
570    }
571}