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}