kiteticker_async_manager/models/tick_raw.rs
1//! Zero-copy raw views for Kite tick packets.
2//!
3//! This module provides endian-safe, zero-allocation views over WebSocket packet bodies
4//! without copying, built on `zerocopy::Ref` and big-endian integer wrappers.
5//! All structs derive `Unaligned`, ensuring that references are valid even when the
6//! underlying buffer is not naturally aligned.
7//!
8//! Highlights:
9//! - `TickRaw` — 184-byte Full quote (header + 10-depth)
10//! - `IndexQuoteRaw32` — 32-byte index quote snapshot
11//! - `InstHeaderRaw64` — 64-byte instrument header (no depth)
12//! - `as_*` helpers return `Option<zerocopy::Ref<&[u8], T>>` after validating slice size
13//!
14//! Example:
15//! ```rust
16//! # use kiteticker_async_manager::as_tick_raw;
17//! # let bytes = [0u8; 184];
18//! if let Some(view_ref) = as_tick_raw(&bytes) {
19//! let v = &*view_ref; // &TickRaw
20//! let token = v.header.instrument_token.get();
21//! let ltp = v.header.last_price.get();
22//! let b0_qty = v.depth.buy[0].qty.get();
23//! let b0_price = v.depth.buy[0].price.get();
24//! let _ = (token, ltp, b0_qty, b0_price);
25//! }
26//! ```
27
28use zerocopy::big_endian::{I32 as BeI32, U16 as BeU16, U32 as BeU32};
29use zerocopy::{FromBytes, Immutable, KnownLayout, Ref, Unaligned};
30
31/// Size of a full quote packet body used by parser Tick (not including the 2-byte length prefix).
32/// Our raw view targets the 184-byte payload region per packet for Mode::Full on equities.
33pub const TICK_FULL_SIZE: usize = 184;
34
35/// Size of index quote packet body (common snapshot when market closed)
36pub const INDEX_QUOTE_SIZE: usize = 32;
37/// Size of instrument header (non-index) without depth
38pub const INST_HEADER_SIZE: usize = 64;
39
40/// First 64 bytes of Full payload contain header/meta before market depth.
41#[repr(C)]
42#[derive(
43 Clone, Copy, Debug, Default, Unaligned, KnownLayout, Immutable, FromBytes,
44)]
45pub struct TickHeaderRaw {
46 pub instrument_token: BeU32, // 0..4
47 pub last_price: BeI32, // 4..8 (scaled by exchange divisor)
48 pub last_traded_qty: BeU32, // 8..12
49 pub avg_traded_price: BeI32, // 12..16
50 pub volume_traded: BeU32, // 16..20
51 pub total_buy_qty: BeU32, // 20..24
52 pub total_sell_qty: BeU32, // 24..28
53 pub ohlc_be: [u8; 16], // 28..44 (open high low close - order depends on index/equity)
54 pub last_traded_ts: BeU32, // 44..48 secs
55 pub oi: BeU32, // 48..52
56 pub oi_day_high: BeU32, // 52..56
57 pub oi_day_low: BeU32, // 56..60
58 pub exchange_ts: BeU32, // 60..64 secs
59}
60
61/// A single depth entry: qty(u32), price_be(`[u8; 4]` i32), orders(u16), pad(u16)
62#[repr(C)]
63#[derive(
64 Clone, Copy, Debug, Default, Unaligned, KnownLayout, Immutable, FromBytes,
65)]
66pub struct DepthItemRaw {
67 pub qty: BeU32,
68 pub price: BeI32,
69 pub orders: BeU16,
70 pub _pad: BeU16, // protocol packs 12 bytes per entry; we keep struct at 12 bytes
71}
72
73/// 5 buy + 5 sell entries = 120 bytes
74#[repr(C)]
75#[derive(
76 Clone, Copy, Debug, Default, Unaligned, KnownLayout, Immutable, FromBytes,
77)]
78pub struct DepthRaw {
79 pub buy: [DepthItemRaw; 5],
80 pub sell: [DepthItemRaw; 5],
81}
82
83/// Complete 184-byte Full packet body
84#[repr(C)]
85#[derive(
86 Clone, Copy, Debug, Default, Unaligned, KnownLayout, Immutable, FromBytes,
87)]
88pub struct TickRaw {
89 pub header: TickHeaderRaw, // 64 bytes
90 pub depth: DepthRaw, // 120 bytes
91}
92
93// No inherent methods needed; prefer free functions that return zerocopy::Ref
94
95/// Try get a fixed array reference of 184 bytes from a slice (for APIs that prefer arrays)
96#[inline]
97pub fn as_184(slice: &[u8]) -> Option<&[u8; TICK_FULL_SIZE]> {
98 <&[u8; TICK_FULL_SIZE]>::try_from(slice).ok()
99}
100
101/// Try view as `TickRaw` from a slice (zero-copy, unaligned-safe).
102///
103/// Returns `None` if the slice is not exactly 184 bytes.
104/// The resulting `Ref` dereferences to `&TickRaw` and is valid as long as the input slice lives.
105#[inline]
106pub fn as_tick_raw(slice: &[u8]) -> Option<Ref<&[u8], TickRaw>> {
107 Ref::<_, TickRaw>::from_bytes(slice).ok()
108}
109
110/// 32-byte Index Quote packet (token + LTP + HLOC + price_change + exch ts)
111#[repr(C)]
112#[derive(
113 Clone, Copy, Debug, Default, Unaligned, KnownLayout, Immutable, FromBytes,
114)]
115pub struct IndexQuoteRaw32 {
116 pub token: BeU32, // 0..4
117 pub ltp: BeI32, // 4..8
118 pub high: BeI32, // 8..12
119 pub low: BeI32, // 12..16
120 pub open: BeI32, // 16..20
121 pub close: BeI32, // 20..24
122 pub price_change: BeI32, // 24..28
123 pub exch_ts: BeU32, // 28..32
124}
125
126#[inline]
127/// Try view as `IndexQuoteRaw32` from a 32-byte slice.
128/// Returns `None` if the length is not 32 bytes.
129pub fn as_index_quote_32(slice: &[u8]) -> Option<Ref<&[u8], IndexQuoteRaw32>> {
130 Ref::<_, IndexQuoteRaw32>::from_bytes(slice).ok()
131}
132
133/// 64-byte instrument header (equity/derivative) without depth
134#[repr(C)]
135#[derive(
136 Clone, Copy, Debug, Default, Unaligned, KnownLayout, Immutable, FromBytes,
137)]
138pub struct InstHeaderRaw64 {
139 pub instrument_token: BeU32, // 0..4
140 pub ltp: BeI32, // 4..8
141 pub ltq: BeU32, // 8..12 (qty)
142 pub atp: BeI32, // 12..16
143 pub vol: BeU32, // 16..20
144 pub tbq: BeU32, // 20..24
145 pub tsq: BeU32, // 24..28
146 pub open: BeI32, // 28..32
147 pub high: BeI32, // 32..36
148 pub low: BeI32, // 36..40
149 pub close: BeI32, // 40..44
150 pub last_traded_ts: BeU32, // 44..48
151 pub oi: BeU32, // 48..52
152 pub oi_day_high: BeU32, // 52..56
153 pub oi_day_low: BeU32, // 56..60
154 pub exch_ts: BeU32, // 60..64
155}
156
157#[inline]
158/// Try view as `InstHeaderRaw64` from a 64-byte slice.
159/// Returns `None` if the length is not 64 bytes.
160pub fn as_inst_header_64(slice: &[u8]) -> Option<Ref<&[u8], InstHeaderRaw64>> {
161 Ref::<_, InstHeaderRaw64>::from_bytes(slice).ok()
162}