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    // Parse LTP fields (first 8 bytes)
72    tick.set_instrument_token(input);
73    if let Some(bs) = input.get(4..8) {
74      tick.mode = Mode::LTP;
75      tick.last_price = price(bs, &tick.exchange);
76    }
77
78    let is_index = !tick.exchange.is_tradable();
79    tick.is_index = is_index;
80    tick.is_tradable = !is_index;
81
82    // Parse Quote section
83    if is_index {
84      if let Some(bs) = input.get(8..28) {
85        tick.mode = Mode::Quote;
86        // 8 - 24 bytes : ohlc (indices use HLOC order)
87        tick.ohlc = OHLC::from_index(&bs[0..16], &tick.exchange);
88        // 24 - 28 bytes : price change (provided only for indices)
89        tick.net_change = price(&bs[16..20], &tick.exchange);
90      }
91    } else if let Some(bs) = input.get(8..44) {
92      tick.mode = Mode::Quote;
93      // 8 - 12 bytes : last traded quantity
94      tick.last_traded_qty = value(&bs[0..4]);
95      // 12 - 16 bytes : avg traded price
96      tick.avg_traded_price = price(&bs[4..8], &tick.exchange);
97      // 16 - 20 bytes : volume traded today
98      tick.volume_traded = value(&bs[8..12]);
99      // 20 - 24 bytes : total buy quantity
100      tick.total_buy_qty = value(&bs[12..16]);
101      // 24 - 28 bytes : total sell quantity
102      tick.total_sell_qty = value(&bs[16..20]);
103      // 28 - 44 bytes : ohlc
104      tick.ohlc = OHLC::from(&bs[20..36], &tick.exchange);
105    }
106
107    // Parse Full section
108    if is_index {
109      if let Some(bs) = input.get(28..32) {
110        tick.mode = Mode::Full;
111        // 28 - 32 bytes : exchange time
112        tick.exchange_timestamp =
113          value(bs).map(|x| Duration::from_secs(x.into()));
114      }
115    } else if let Some(bs) = input.get(44..184) {
116      tick.mode = Mode::Full;
117      tick.set_change();
118
119      // 44 - 48 bytes : last traded timestamp
120      tick.last_traded_timestamp =
121        value(&bs[0..4]).map(|x| Duration::from_secs(x.into()));
122
123      // 48 - 52 bytes : oi
124      tick.oi = value(&bs[4..8]);
125      // 52 - 56 bytes : oi day high
126      tick.oi_day_high = value(&bs[8..12]);
127      // 56 - 60 bytes : oi day low
128      tick.oi_day_low = value(&bs[12..16]);
129      // 60 - 64 bytes : exchange time
130      tick.exchange_timestamp =
131        value(&bs[16..20]).map(|x| Duration::from_secs(x.into()));
132      // 64 - 184 bytes : market depth
133      tick.depth = Depth::from(&bs[20..140], &tick.exchange);
134    }
135
136    tick
137  }
138}
139
140impl TryFrom<&[u8]> for Tick {
141  type Error = ParseTickError;
142  fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
143    match value.len() {
144      8 | 28 | 32 | 44 | 184 => Ok(Tick::from_bytes(value)),
145      len => Err(ParseTickError(format!("invalid tick size: {}", len))),
146    }
147  }
148}