kiteticker_async_manager/models/
tick.rs

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