kiteticker_async_manager/models/
tick.rs

1use std::time::Duration;
2
3use crate::{
4  errors::ParseTickError,
5  parser::{price, value},
6  Depth, Exchange, Mode, OHLC,
7};
8
9#[derive(Debug, Clone, Default, PartialEq)]
10///
11/// Quote packet structure
12///
13pub struct Tick {
14  pub mode: Mode,
15  pub instrument_token: u32,
16  pub exchange: Exchange,
17  pub is_tradable: bool,
18  pub is_index: bool,
19
20  pub last_traded_qty: Option<u32>,
21  pub avg_traded_price: Option<f64>,
22  pub last_price: Option<f64>,
23  pub volume_traded: Option<u32>,
24  pub total_buy_qty: Option<u32>,
25  pub total_sell_qty: Option<u32>,
26  pub ohlc: Option<OHLC>,
27
28  pub last_traded_timestamp: Option<Duration>,
29  pub oi: Option<u32>,
30  pub oi_day_high: Option<u32>,
31  pub oi_day_low: Option<u32>,
32  pub exchange_timestamp: Option<Duration>,
33
34  pub net_change: Option<f64>,
35  pub depth: Option<Depth>,
36}
37
38impl Tick {
39  fn set_instrument_token(&mut self, input: &[u8]) -> &mut Self {
40    self.instrument_token = value(&input[0..=3]).unwrap();
41    self.exchange = ((self.instrument_token & 0xFF) as usize).into();
42    self
43  }
44
45  fn set_change(&mut self) -> &mut Self {
46    self.net_change = self
47      .ohlc
48      .as_ref()
49      .map(|o| o.close)
50      .map(|close_price| {
51        if let Some(last_price) = self.last_price {
52          if close_price == 0_f64 {
53            return None;
54          } else {
55            // Some(((last_price - close_price) * 100.0).div(close_price))
56            Some(last_price - close_price)
57          }
58        } else {
59          None
60        }
61      })
62      .unwrap_or_default();
63    self
64  }
65}
66
67impl Tick {
68  pub(crate) fn from_bytes(input: &[u8]) -> Self {
69    let mut tick = Tick::default();
70
71    let parse_ltp = |t: &mut Tick, i: &[u8]| {
72      // 0 - 4 bytes : instrument token
73      t.set_instrument_token(i);
74      // 4 - 8 bytes : ltp
75      if let Some(bs) = i.get(4..8) {
76        t.mode = Mode::LTP;
77        t.last_price = price(bs, &t.exchange);
78      }
79    };
80
81    let parse_quote = |t: &mut Tick, i: &[u8], is_index: bool| {
82      if is_index {
83        if let Some(bs) = i.get(8..28) {
84          t.mode = Mode::Quote;
85          // 8 - 24 bytes : ohlc
86          t.ohlc = OHLC::from(&bs[0..16], &t.exchange);
87          // 24 - 28 bytes : Price change
88          // t.net_change = price(&bs[16..=19], &t.exchange);
89          t.set_change();
90        }
91      } else {
92        if let Some(bs) = i.get(8..44) {
93          t.mode = Mode::Quote;
94          // 8 - 12 bytes : last traded quantity
95          t.last_traded_qty = value(&bs[0..4]);
96          // 12 - 16 bytes : avg traded price
97          t.avg_traded_price = price(&bs[4..8], &t.exchange);
98          // 16 - 20 bytes : volume traded today
99          t.volume_traded = value(&bs[8..12]);
100          // 20 - 24 bytes : total buy quantity
101          t.total_buy_qty = value(&bs[12..16]);
102          // 24 - 28 bytes : total sell quantity
103          t.total_sell_qty = value(&bs[16..20]);
104          // 28 - 44 bytes : ohlc
105          t.ohlc = OHLC::from(&bs[20..36], &t.exchange);
106        }
107      }
108    };
109
110    let parse_full = |t: &mut Tick, i: &[u8], is_index: bool| {
111      if is_index {
112        if let Some(bs) = i.get(28..32) {
113          t.mode = Mode::Full;
114          // 28 - 32 bytes : exchange time
115          t.exchange_timestamp =
116            value(bs).map(|x| Duration::from_secs(x.into()));
117        }
118      } else {
119        if let Some(bs) = i.get(44..184) {
120          t.mode = Mode::Full;
121          t.set_change();
122
123          // 44 - 48 bytes : last traded timestamp
124          t.last_traded_timestamp =
125            value(&bs[0..4]).map(|x| Duration::from_secs(x.into()));
126
127          // 48 - 52 bytes : oi
128          t.oi = value(&bs[4..8]);
129          // 52 - 56 bytes : oi day high
130          t.oi_day_high = value(&bs[8..12]);
131          // 56 - 60 bytes : oi day low
132          t.oi_day_low = value(&bs[12..16]);
133          // 60 - 64 bytes : exchange time
134          t.exchange_timestamp =
135            value(&bs[16..20]).map(|x| Duration::from_secs(x.into()));
136          // 64 - 184 bytes : market depth
137          t.depth = Depth::from(&bs[20..140], &t.exchange);
138        }
139      }
140    };
141
142    parse_ltp(&mut tick, input);
143    if !tick.exchange.is_tradable() {
144      tick.is_index = true;
145      tick.is_tradable = false;
146
147      parse_quote(&mut tick, input, true);
148      parse_full(&mut tick, input, true);
149    } else {
150      tick.is_index = false;
151      tick.is_tradable = true;
152
153      parse_quote(&mut tick, input, false);
154      parse_full(&mut tick, input, false);
155    }
156
157    tick
158  }
159}
160
161impl TryFrom<&[u8]> for Tick {
162  type Error = ParseTickError;
163  fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
164    crate::parser::parse_tick(value)
165  }
166}