orderpulse 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,190 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "OrderPluse"
7
+ version = "0.1.0"
8
+ dependencies = [
9
+ "memmap2",
10
+ "pyo3",
11
+ ]
12
+
13
+ [[package]]
14
+ name = "autocfg"
15
+ version = "1.5.0"
16
+ source = "registry+https://github.com/rust-lang/crates.io-index"
17
+ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
18
+
19
+ [[package]]
20
+ name = "cfg-if"
21
+ version = "1.0.4"
22
+ source = "registry+https://github.com/rust-lang/crates.io-index"
23
+ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
24
+
25
+ [[package]]
26
+ name = "heck"
27
+ version = "0.5.0"
28
+ source = "registry+https://github.com/rust-lang/crates.io-index"
29
+ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
30
+
31
+ [[package]]
32
+ name = "indoc"
33
+ version = "2.0.7"
34
+ source = "registry+https://github.com/rust-lang/crates.io-index"
35
+ checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
36
+ dependencies = [
37
+ "rustversion",
38
+ ]
39
+
40
+ [[package]]
41
+ name = "libc"
42
+ version = "0.2.186"
43
+ source = "registry+https://github.com/rust-lang/crates.io-index"
44
+ checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
45
+
46
+ [[package]]
47
+ name = "memmap2"
48
+ version = "0.9.10"
49
+ source = "registry+https://github.com/rust-lang/crates.io-index"
50
+ checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3"
51
+ dependencies = [
52
+ "libc",
53
+ ]
54
+
55
+ [[package]]
56
+ name = "memoffset"
57
+ version = "0.9.1"
58
+ source = "registry+https://github.com/rust-lang/crates.io-index"
59
+ checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
60
+ dependencies = [
61
+ "autocfg",
62
+ ]
63
+
64
+ [[package]]
65
+ name = "once_cell"
66
+ version = "1.21.4"
67
+ source = "registry+https://github.com/rust-lang/crates.io-index"
68
+ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
69
+
70
+ [[package]]
71
+ name = "portable-atomic"
72
+ version = "1.13.1"
73
+ source = "registry+https://github.com/rust-lang/crates.io-index"
74
+ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
75
+
76
+ [[package]]
77
+ name = "proc-macro2"
78
+ version = "1.0.106"
79
+ source = "registry+https://github.com/rust-lang/crates.io-index"
80
+ checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
81
+ dependencies = [
82
+ "unicode-ident",
83
+ ]
84
+
85
+ [[package]]
86
+ name = "pyo3"
87
+ version = "0.22.6"
88
+ source = "registry+https://github.com/rust-lang/crates.io-index"
89
+ checksum = "f402062616ab18202ae8319da13fa4279883a2b8a9d9f83f20dbade813ce1884"
90
+ dependencies = [
91
+ "cfg-if",
92
+ "indoc",
93
+ "libc",
94
+ "memoffset",
95
+ "once_cell",
96
+ "portable-atomic",
97
+ "pyo3-build-config",
98
+ "pyo3-ffi",
99
+ "pyo3-macros",
100
+ "unindent",
101
+ ]
102
+
103
+ [[package]]
104
+ name = "pyo3-build-config"
105
+ version = "0.22.6"
106
+ source = "registry+https://github.com/rust-lang/crates.io-index"
107
+ checksum = "b14b5775b5ff446dd1056212d778012cbe8a0fbffd368029fd9e25b514479c38"
108
+ dependencies = [
109
+ "once_cell",
110
+ "target-lexicon",
111
+ ]
112
+
113
+ [[package]]
114
+ name = "pyo3-ffi"
115
+ version = "0.22.6"
116
+ source = "registry+https://github.com/rust-lang/crates.io-index"
117
+ checksum = "9ab5bcf04a2cdcbb50c7d6105de943f543f9ed92af55818fd17b660390fc8636"
118
+ dependencies = [
119
+ "libc",
120
+ "pyo3-build-config",
121
+ ]
122
+
123
+ [[package]]
124
+ name = "pyo3-macros"
125
+ version = "0.22.6"
126
+ source = "registry+https://github.com/rust-lang/crates.io-index"
127
+ checksum = "0fd24d897903a9e6d80b968368a34e1525aeb719d568dba8b3d4bfa5dc67d453"
128
+ dependencies = [
129
+ "proc-macro2",
130
+ "pyo3-macros-backend",
131
+ "quote",
132
+ "syn",
133
+ ]
134
+
135
+ [[package]]
136
+ name = "pyo3-macros-backend"
137
+ version = "0.22.6"
138
+ source = "registry+https://github.com/rust-lang/crates.io-index"
139
+ checksum = "36c011a03ba1e50152b4b394b479826cad97e7a21eb52df179cd91ac411cbfbe"
140
+ dependencies = [
141
+ "heck",
142
+ "proc-macro2",
143
+ "pyo3-build-config",
144
+ "quote",
145
+ "syn",
146
+ ]
147
+
148
+ [[package]]
149
+ name = "quote"
150
+ version = "1.0.45"
151
+ source = "registry+https://github.com/rust-lang/crates.io-index"
152
+ checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
153
+ dependencies = [
154
+ "proc-macro2",
155
+ ]
156
+
157
+ [[package]]
158
+ name = "rustversion"
159
+ version = "1.0.22"
160
+ source = "registry+https://github.com/rust-lang/crates.io-index"
161
+ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
162
+
163
+ [[package]]
164
+ name = "syn"
165
+ version = "2.0.117"
166
+ source = "registry+https://github.com/rust-lang/crates.io-index"
167
+ checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
168
+ dependencies = [
169
+ "proc-macro2",
170
+ "quote",
171
+ "unicode-ident",
172
+ ]
173
+
174
+ [[package]]
175
+ name = "target-lexicon"
176
+ version = "0.12.16"
177
+ source = "registry+https://github.com/rust-lang/crates.io-index"
178
+ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
179
+
180
+ [[package]]
181
+ name = "unicode-ident"
182
+ version = "1.0.24"
183
+ source = "registry+https://github.com/rust-lang/crates.io-index"
184
+ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
185
+
186
+ [[package]]
187
+ name = "unindent"
188
+ version = "0.2.4"
189
+ source = "registry+https://github.com/rust-lang/crates.io-index"
190
+ checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3"
@@ -0,0 +1,18 @@
1
+ [package]
2
+ name = "OrderPluse"
3
+ version = "0.1.0"
4
+ edition = "2021"
5
+ readme = "README.md"
6
+
7
+
8
+
9
+ [lib]
10
+ name = "fastorderbook"
11
+ crate-type = ["cdylib"]
12
+
13
+
14
+
15
+ [dependencies]
16
+ memmap2 = "0.9"
17
+
18
+ pyo3 = { version = "0.22", features = ["extension-module"] }
@@ -0,0 +1,4 @@
1
+ Metadata-Version: 2.4
2
+ Name: OrderPulse
3
+ Version: 0.1.0
4
+ Requires-Python: >=3.9
File without changes
@@ -0,0 +1,6 @@
1
+ def main():
2
+ print("Hello from myrustlib!")
3
+
4
+
5
+ if __name__ == "__main__":
6
+ main()
@@ -0,0 +1,8 @@
1
+ [build-system]
2
+ requires = ["maturin>=1.0,<2.0"]
3
+ build-backend = "maturin"
4
+
5
+ [project]
6
+ name = "OrderPulse"
7
+ version = "0.1.0"
8
+ requires-python = ">=3.9"
@@ -0,0 +1,147 @@
1
+ use pyo3::prelude::*;
2
+
3
+ mod structure;
4
+ mod read_trd_ord_only;
5
+ mod tsc;
6
+
7
+ use read_trd_ord_only::read_messages;
8
+ use structure::Message;
9
+
10
+
11
+
12
+ #[pyfunction]
13
+ fn total_messages(path: String) -> PyResult<usize> {
14
+
15
+ let messages = read_messages(&path)
16
+ .map_err(|e| {
17
+ pyo3::exceptions::PyRuntimeError::new_err(
18
+ e.to_string()
19
+ )
20
+ })?;
21
+
22
+ Ok(messages.len())
23
+ }
24
+
25
+
26
+
27
+ #[pyfunction]
28
+ fn total_trades(path: String) -> PyResult<usize> {
29
+
30
+ let messages = read_messages(&path)
31
+ .map_err(|e| {
32
+ pyo3::exceptions::PyRuntimeError::new_err(
33
+ e.to_string()
34
+ )
35
+ })?;
36
+
37
+ let mut count = 0;
38
+
39
+ for msg in messages {
40
+
41
+ match msg {
42
+
43
+ Message::Trade(_) => {
44
+ count += 1;
45
+ }
46
+
47
+ _ => {}
48
+ }
49
+ }
50
+
51
+ Ok(count)
52
+ }
53
+
54
+
55
+
56
+ #[pyfunction]
57
+ fn total_orders(path: String) -> PyResult<usize> {
58
+
59
+ let messages = read_messages(&path)
60
+ .map_err(|e| {
61
+ pyo3::exceptions::PyRuntimeError::new_err(
62
+ e.to_string()
63
+ )
64
+ })?;
65
+
66
+ let mut count = 0;
67
+
68
+ for msg in messages {
69
+
70
+ match msg {
71
+
72
+ Message::Order(_) => {
73
+ count += 1;
74
+ }
75
+
76
+ _ => {}
77
+ }
78
+ }
79
+
80
+ Ok(count)
81
+ }
82
+
83
+
84
+
85
+ #[pyfunction]
86
+ fn summary(path: String) -> PyResult<(usize, usize, usize)> {
87
+
88
+ let messages = read_messages(&path)
89
+ .map_err(|e| {
90
+ pyo3::exceptions::PyRuntimeError::new_err(
91
+ e.to_string()
92
+ )
93
+ })?;
94
+
95
+ let total_messages = messages.len();
96
+
97
+ let mut total_trades = 0;
98
+ let mut total_orders = 0;
99
+
100
+ for msg in messages {
101
+
102
+ match msg {
103
+
104
+ Message::Trade(_) => {
105
+ total_trades += 1;
106
+ }
107
+
108
+ Message::Order(_) => {
109
+ total_orders += 1;
110
+ }
111
+
112
+ }
113
+ }
114
+
115
+ Ok((
116
+ total_messages,
117
+ total_trades,
118
+ total_orders
119
+ ))
120
+ }
121
+
122
+
123
+
124
+ #[pymodule]
125
+ fn fastorderbook(
126
+ _py: Python,
127
+ m: &Bound<PyModule>
128
+ ) -> PyResult<()> {
129
+
130
+ m.add_function(
131
+ wrap_pyfunction!(total_messages, m)?
132
+ )?;
133
+
134
+ m.add_function(
135
+ wrap_pyfunction!(total_trades, m)?
136
+ )?;
137
+
138
+ m.add_function(
139
+ wrap_pyfunction!(total_orders, m)?
140
+ )?;
141
+
142
+ m.add_function(
143
+ wrap_pyfunction!(summary, m)?
144
+ )?;
145
+
146
+ Ok(())
147
+ }
@@ -0,0 +1,240 @@
1
+ use std::collections::HashSet;
2
+ use std::io::{Error, ErrorKind};
3
+ use std::io::Result;
4
+ use std::mem::size_of;
5
+ use std::path::Path;
6
+ use std::time::Instant;
7
+
8
+ mod structure;
9
+ mod read_trd_ord_only;
10
+ mod orderbook;
11
+
12
+ use crate::orderbook::OrderBookManager;
13
+ use crate::read_trd_ord_only::read_messages;
14
+ use crate::structure::{Message, OrderPacket, TradePacket};
15
+ use orderbook_processing::{clobber, cycle_end, cycle_start, Harness};
16
+
17
+ fn main() -> Result<()> {
18
+ let _trade_size = size_of::<TradePacket>();
19
+ let _order_size = size_of::<OrderPacket>();
20
+
21
+ let args: Vec<String> = std::env::args().collect();
22
+ const DEFAULT_PATH: &str = "/nas/50.30/NSE_CM/Feed_CM_StreamID_2_10_10_2026.bin";
23
+ let path = if args.len() > 1 {
24
+ &args[1]
25
+ } else {
26
+ DEFAULT_PATH
27
+ };
28
+
29
+ if !Path::new(path).exists() {
30
+ return Err(Error::new(
31
+ ErrorKind::NotFound,
32
+ format!(
33
+ "Input file not found: {}. Pass a valid file path as arg1. Default path: {}",
34
+ path, DEFAULT_PATH
35
+ ),
36
+ ));
37
+ }
38
+
39
+ let filter_token: Option<u32> = if args.len() > 2 {
40
+ Some(args[2].parse().map_err(|e| {
41
+ Error::new(
42
+ ErrorKind::InvalidInput,
43
+ format!("Invalid token '{}': {}", args[2], e),
44
+ )
45
+ })?)
46
+ } else {
47
+ None
48
+ };
49
+
50
+ let max_messages: Option<usize> = if args.len() > 3 {
51
+ Some(args[3].parse().map_err(|e| {
52
+ Error::new(
53
+ ErrorKind::InvalidInput,
54
+ format!("Invalid max_messages '{}': {}", args[3], e),
55
+ )
56
+ })?)
57
+ } else {
58
+ None
59
+ };
60
+
61
+ let skip_bench = std::env::var("LOB_SKIP_BENCH")
62
+ .map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
63
+ .unwrap_or(false);
64
+
65
+ eprintln!("processing file: {}", path);
66
+ if let Some(token) = filter_token {
67
+ eprintln!("Filtering for token: {}", token);
68
+ }
69
+ if let Some(limit) = max_messages {
70
+ eprintln!("Max messages to process: {}", limit);
71
+ }
72
+ if skip_bench {
73
+ eprintln!("Benchmark phase disabled via LOB_SKIP_BENCH");
74
+ }
75
+
76
+ eprintln!("\nread_messages");
77
+ let timer1 = Instant::now();
78
+ let messages = read_messages(path)?;
79
+ let read_time = timer1.elapsed();
80
+ eprintln!("Duration: {:.2?}", read_time);
81
+
82
+ // Collect token statistics for diagnostics
83
+ let mut order_tokens: HashSet<u32> = HashSet::new();
84
+ let mut trade_tokens: HashSet<i32> = HashSet::new();
85
+ for msg in &messages {
86
+ match msg {
87
+ Message::Order(order) => { order_tokens.insert(order.ord.token); }
88
+ Message::Trade(trade) => { trade_tokens.insert(trade.trd.token); }
89
+ }
90
+ }
91
+
92
+ eprintln!("\nfilter_messages");
93
+ let timer2 = Instant::now();
94
+ let mut filtered_messages: Vec<Message> = if let Some(filter) = filter_token {
95
+ let mut out = Vec::with_capacity(messages.len() / 4);
96
+ for msg in messages {
97
+ match &msg {
98
+ Message::Order(order) => {
99
+ if order.ord.token == filter {
100
+ out.push(msg);
101
+ }
102
+ }
103
+ Message::Trade(trade) => {
104
+ if trade.trd.token as u32 == filter {
105
+ out.push(msg);
106
+ }
107
+ }
108
+ }
109
+ }
110
+ out
111
+ } else {
112
+ messages
113
+ };
114
+ let filter_time = timer2.elapsed();
115
+
116
+ if let Some(limit) = max_messages {
117
+ if filtered_messages.len() > limit {
118
+ filtered_messages.truncate(limit);
119
+ }
120
+ }
121
+
122
+ eprintln!("Duration: {:.2?}", filter_time);
123
+ eprintln!("Total filtered messages: {}", filtered_messages.len());
124
+
125
+ if filtered_messages.is_empty() {
126
+ eprintln!("No filtered messages to process. Exiting.");
127
+ return Ok(());
128
+ }
129
+
130
+ eprintln!("\ninitialize_manager");
131
+ let timer3 = Instant::now();
132
+ let mut manager = OrderBookManager::new();
133
+ let init_time = timer3.elapsed();
134
+ eprintln!("initialize_manager duration: {:.2?}", init_time);
135
+
136
+ println!(
137
+ "local_ts,exch_ts,mid_price,\
138
+ bid_price_0,bid_qty_0,ask_price_0,ask_qty_0,\
139
+ bid_price_1,bid_qty_1,ask_price_1,ask_qty_1,\
140
+ bid_price_2,bid_qty_2,ask_price_2,ask_qty_2,\
141
+ bid_price_3,bid_qty_3,ask_price_3,ask_qty_3,\
142
+ bid_price_4,bid_qty_4,ask_price_4,ask_qty_4"
143
+ );
144
+
145
+ eprintln!("\nprocess_all_messages");
146
+ let timer4 = Instant::now();
147
+
148
+ let mut message_count: u64 = 0;
149
+ let mut order_count: u64 = 0;
150
+ let mut trade_count: u64 = 0;
151
+
152
+ for message in &filtered_messages {
153
+ message_count += 1;
154
+ match message {
155
+ Message::Order(order) => {
156
+ order_count += 1;
157
+ manager.process_order_message(order);
158
+
159
+ let token = order.ord.token;
160
+ if let Some((mid_price, mut bids, mut asks)) = manager.get_top_levels(token, 5) {
161
+ while bids.len() < 5 { bids.push((0,0)); }
162
+ while asks.len() < 5 { asks.push((0,0)); }
163
+ // Copy packed struct fields into aligned local variables
164
+ let local_ts = order.local_ts;
165
+ let exch_ts = order.ord.exch_ts;
166
+ println!(
167
+ "{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}, {}",
168
+ local_ts,
169
+ exch_ts,
170
+ mid_price,
171
+ bids[0].0, bids[0].1, asks[0].0, asks[0].1,
172
+ bids[1].0, bids[1].1, asks[1].0, asks[1].1,
173
+ bids[2].0, bids[2].1, asks[2].0, asks[2].1,
174
+ bids[3].0, bids[3].1, asks[3].0, asks[3].1,
175
+ bids[4].0, bids[4].1, asks[4].0, asks[4].1
176
+ );
177
+ }
178
+ }
179
+ Message::Trade(trade) => {
180
+ trade_count += 1;
181
+ manager.process_trade_message(trade);
182
+
183
+ let token = trade.trd.token as u32;
184
+ if let Some((mid_price, mut bids, mut asks)) = manager.get_top_levels(token, 5) {
185
+ while bids.len() < 5 { bids.push((0,0)); }
186
+ while asks.len() < 5 { asks.push((0,0)); }
187
+ // Copy packed struct fields into aligned local variables
188
+ let local_ts = trade.local_ts;
189
+ let exch_ts = trade.trd.exch_ts;
190
+ println!(
191
+ "{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}",
192
+ local_ts, exch_ts, mid_price,
193
+ bids[0].0, bids[0].1, asks[0].0, asks[0].1,
194
+ bids[1].0, bids[1].1, asks[1].0, asks[1].1,
195
+ bids[2].0, bids[2].1, asks[2].0, asks[2].1,
196
+ bids[3].0, bids[3].1, asks[3].0, asks[3].1,
197
+ bids[4].0, bids[4].1, asks[4].0, asks[4].1
198
+ );
199
+ }
200
+ }
201
+ }
202
+ }
203
+
204
+ let process_time = timer4.elapsed();
205
+ eprintln!("Duration: {:.2?}", process_time);
206
+ eprintln!(
207
+ "Processed messages: total={}, orders={}, trades={}",
208
+ message_count, order_count, trade_count
209
+ );
210
+
211
+ if skip_bench {
212
+ return Ok(());
213
+ }
214
+
215
+ let mut harness = Harness::new();
216
+ let filtered_messages_clone = filtered_messages.clone();
217
+ harness.add_benchmark(
218
+ "BM_RustOrderBook",
219
+ filtered_messages_clone.len() as u64,
220
+ move |iterations| -> u64 {
221
+ let mut total_cycles: u64 = 0;
222
+ for _ in 0..iterations {
223
+ let mut book = OrderBookManager::new();
224
+ let start = cycle_start();
225
+ clobber();
226
+ filtered_messages_clone.iter().for_each(|message| {
227
+ match message {
228
+ Message::Order(order) => { book.process_order_message(order); }
229
+ Message::Trade(trade) => { book.process_trade_message(trade); }
230
+ }
231
+ });
232
+ let end = cycle_end();
233
+ total_cycles = total_cycles.saturating_add(end.saturating_sub(start));
234
+ }
235
+ total_cycles
236
+ },
237
+ 21,
238
+ );
239
+ std::process::exit(harness.run());
240
+ }
@@ -0,0 +1,356 @@
1
+ use rustc_hash::FxHashMap;
2
+ use crate::structure::{OrderPacket, TradePacket};
3
+
4
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
5
+ pub enum Side {
6
+ Buy,
7
+ Sell,
8
+ }
9
+
10
+ #[derive(Debug, Clone, Copy)]
11
+ struct Order {
12
+ side: Side,
13
+ price: u32,
14
+ qty: u32,
15
+ }
16
+
17
+ #[derive(Debug, Clone, Copy)]
18
+ pub struct PriceLevel {
19
+ pub price: u32,
20
+ pub total_qty: u64,
21
+ }
22
+
23
+ const DEFAULT_PRICE_TICK_SIZE: u32 = 1;
24
+ const INITIAL_DPR_WINDOW_WIDTH: u32 = 20_000;
25
+
26
+ #[derive(Debug)]
27
+ pub(crate) struct OrderBook {
28
+ orders: FxHashMap<u64, Order>,
29
+ tick_size: u32,
30
+ dpr_min: u32,
31
+ dpr_max: u32,
32
+ bid_levels: Vec<u64>,
33
+ ask_levels: Vec<u64>,
34
+ }
35
+
36
+ impl OrderBook {
37
+ #[inline]
38
+ fn new(first_seen_price: u32) -> Self {
39
+ let mut orders = FxHashMap::default();
40
+ orders.reserve(100_000);
41
+
42
+ let tick_size = DEFAULT_PRICE_TICK_SIZE;
43
+ let initial_half_window = INITIAL_DPR_WINDOW_WIDTH / 2;
44
+
45
+ let mut dpr_min = first_seen_price.saturating_sub(initial_half_window);
46
+ let mut dpr_max = first_seen_price.saturating_add(initial_half_window);
47
+
48
+ dpr_min = (dpr_min / tick_size) * tick_size;
49
+ dpr_max = if dpr_max % tick_size == 0 {
50
+ dpr_max
51
+ } else {
52
+ ((dpr_max / tick_size) + 1) * tick_size
53
+ };
54
+
55
+ if dpr_max < dpr_min {
56
+ dpr_max = dpr_min;
57
+ }
58
+
59
+ let levels = ((dpr_max - dpr_min) / tick_size + 1) as usize;
60
+ Self {
61
+ orders,
62
+ tick_size,
63
+ dpr_min,
64
+ dpr_max,
65
+ bid_levels: vec![0u64; levels],
66
+ ask_levels: vec![0u64; levels],
67
+ }
68
+ }
69
+
70
+ #[inline(always)]
71
+ fn in_range(&self, price: u32) -> bool {
72
+ price >= self.dpr_min && price <= self.dpr_max
73
+ }
74
+
75
+ #[inline(always)]
76
+ fn idx(&self, price: u32) -> usize {
77
+ ((price - self.dpr_min) / self.tick_size) as usize
78
+ }
79
+
80
+ #[inline(always)]
81
+ fn align_down_to_tick(&self, p: u32) -> u32 {
82
+ (p / self.tick_size) * self.tick_size
83
+ }
84
+
85
+ #[inline(always)]
86
+ fn round_up_to_10(p: u32) -> u32 {
87
+ if p % 10 == 0 {
88
+ p
89
+ } else {
90
+ p + (10 - (p % 10))
91
+ }
92
+ }
93
+
94
+ fn load_new_dpr_settings(&mut self, price: u32) {
95
+ let old_min = self.dpr_min;
96
+ let mut new_min = self.dpr_min;
97
+ let mut new_max = self.dpr_max;
98
+
99
+ if price >= new_max {
100
+ new_max = price.saturating_add(price / 4);
101
+ new_max = Self::round_up_to_10(new_max);
102
+ new_max = self.align_down_to_tick(new_max);
103
+ } else if price < new_min {
104
+ new_min = price.saturating_sub(price / 4);
105
+ let diff = old_min.saturating_sub(new_min);
106
+ new_min = new_min.saturating_sub(diff % 10);
107
+ new_min = self.align_down_to_tick(new_min);
108
+ if new_min == 0 {
109
+ new_min = self.tick_size;
110
+ }
111
+ } else {
112
+ return;
113
+ }
114
+
115
+ if new_max < new_min {
116
+ new_max = new_min;
117
+ }
118
+
119
+ let new_levels = ((new_max - new_min) / self.tick_size + 1) as usize;
120
+ let mut new_bid = vec![0u64; new_levels];
121
+ let mut new_ask = vec![0u64; new_levels];
122
+
123
+ for old_i in 0..self.bid_levels.len() {
124
+ let abs_price = old_min + (old_i as u32 * self.tick_size);
125
+ if abs_price < new_min || abs_price > new_max {
126
+ continue;
127
+ }
128
+ let new_i = ((abs_price - new_min) / self.tick_size) as usize;
129
+ new_bid[new_i] = self.bid_levels[old_i];
130
+ new_ask[new_i] = self.ask_levels[old_i];
131
+ }
132
+
133
+ self.dpr_min = new_min;
134
+ self.dpr_max = new_max;
135
+ self.bid_levels = new_bid;
136
+ self.ask_levels = new_ask;
137
+ }
138
+
139
+ #[inline(always)]
140
+ fn ensure_price(&mut self, price: u32) {
141
+ if !self.in_range(price) {
142
+ self.load_new_dpr_settings(price);
143
+ }
144
+ }
145
+
146
+ #[inline(always)]
147
+ fn add_order(&mut self, order_id: u64, side: Side, price: u32, qty: u32) {
148
+ if qty == 0 {
149
+ return;
150
+ }
151
+ self.ensure_price(price);
152
+ if !self.in_range(price) {
153
+ return;
154
+ }
155
+ let i = self.idx(price);
156
+ self.orders.insert(order_id, Order { side, price, qty });
157
+
158
+ unsafe {
159
+ match side {
160
+ Side::Buy => *self.bid_levels.get_unchecked_mut(i) += qty as u64,
161
+ Side::Sell => *self.ask_levels.get_unchecked_mut(i) += qty as u64,
162
+ }
163
+ }
164
+ }
165
+
166
+ #[inline(always)]
167
+ fn reduce_order(&mut self, order_id: u64, qty: u32) {
168
+ let (side, price, traded, remaining_qty) = {
169
+ let order = match self.orders.get_mut(&order_id) {
170
+ Some(o) => o,
171
+ None => return,
172
+ };
173
+ let traded = qty.min(order.qty);
174
+ order.qty -= traded;
175
+ (order.side, order.price, traded, order.qty)
176
+ };
177
+ if traded > 0 && self.in_range(price) {
178
+ let i = self.idx(price);
179
+ unsafe {
180
+ match side {
181
+ Side::Buy => *self.bid_levels.get_unchecked_mut(i) -= traded as u64,
182
+ Side::Sell => *self.ask_levels.get_unchecked_mut(i) -= traded as u64,
183
+ }
184
+ }
185
+ }
186
+ if remaining_qty == 0 {
187
+ self.orders.remove(&order_id);
188
+ }
189
+ }
190
+
191
+ #[inline(always)]
192
+ fn cancel_order(&mut self, order_id: u64) {
193
+ let order = match self.orders.remove(&order_id) {
194
+ Some(o) => o,
195
+ None => return,
196
+ };
197
+ if self.in_range(order.price) {
198
+ let i = self.idx(order.price);
199
+ unsafe {
200
+ match order.side {
201
+ Side::Buy => *self.bid_levels.get_unchecked_mut(i) -= order.qty as u64,
202
+ Side::Sell => *self.ask_levels.get_unchecked_mut(i) -= order.qty as u64,
203
+ }
204
+ }
205
+ }
206
+ }
207
+
208
+ #[inline(always)]
209
+ fn modify_order(&mut self, order_id: u64, side: Side, new_price: u32, new_qty: u32) {
210
+ self.cancel_order(order_id);
211
+ self.add_order(order_id, side, new_price, new_qty);
212
+ }
213
+
214
+ #[inline(always)]
215
+ pub(crate) fn bid_levels(&self) -> &[u64] {
216
+ &self.bid_levels
217
+ }
218
+
219
+ #[inline(always)]
220
+ pub(crate) fn ask_levels(&self) -> &[u64] {
221
+ &self.ask_levels
222
+ }
223
+ }
224
+
225
+ pub struct OrderBookManager {
226
+ books: FxHashMap<u32, OrderBook>,
227
+ }
228
+
229
+ impl OrderBookManager {
230
+ pub fn reset(&mut self) {
231
+ self.books.clear();
232
+ }
233
+
234
+ #[inline]
235
+ pub fn new() -> Self {
236
+ let mut books = FxHashMap::default();
237
+ books.reserve(20_000);
238
+ Self { books }
239
+ }
240
+
241
+ #[inline(always)]
242
+ fn book_mut(&mut self, token: u32, first_seen_price: u32) -> &mut OrderBook {
243
+ self.books
244
+ .entry(token)
245
+ .or_insert_with(|| OrderBook::new(first_seen_price))
246
+ }
247
+
248
+ #[inline(always)]
249
+ pub fn add_order(&mut self, token: u32, order_id: u64, side: Side, price: u32, qty: u32) {
250
+ self.book_mut(token, price).add_order(order_id, side, price, qty);
251
+ }
252
+
253
+ #[inline(always)]
254
+ pub fn reduce_order(&mut self, token: u32, order_id: u64, qty: u32) {
255
+ if let Some(book) = self.books.get_mut(&token) {
256
+ book.reduce_order(order_id, qty);
257
+ }
258
+ }
259
+
260
+ #[inline(always)]
261
+ pub fn cancel_order(&mut self, token: u32, order_id: u64) {
262
+ if let Some(book) = self.books.get_mut(&token) {
263
+ book.cancel_order(order_id);
264
+ }
265
+ }
266
+
267
+ #[inline(always)]
268
+ pub fn modify_order(
269
+ &mut self,
270
+ token: u32,
271
+ order_id: u64,
272
+ new_price: u32,
273
+ new_qty: u32,
274
+ side: Side,
275
+ ) {
276
+ self.book_mut(token, new_price)
277
+ .modify_order(order_id, side, new_price, new_qty);
278
+ }
279
+
280
+ #[inline(always)]
281
+ pub(crate) fn get_book(&self, token: u32) -> Option<&OrderBook> {
282
+ self.books.get(&token)
283
+ }
284
+
285
+ #[inline(always)]
286
+ pub fn process_order_message(&mut self, order: &OrderPacket) {
287
+ let token = order.ord.token;
288
+ let order_id = order.ord.order_id;
289
+ let price = order.ord.price;
290
+ let qty = order.ord.quantity;
291
+ let order_type = order.ord.order_type;
292
+ let msg_type = order.ord.msg_type;
293
+
294
+ let side = if order_type == b'B' {
295
+ Side::Buy
296
+ } else {
297
+ Side::Sell
298
+ };
299
+
300
+ match msg_type {
301
+ b'N' => self.add_order(token, order_id, side, price, qty),
302
+ b'M' => self.modify_order(token, order_id, price, qty, side),
303
+ b'X' => self.cancel_order(token, order_id),
304
+ _ => {}
305
+ }
306
+ }
307
+
308
+ #[inline(always)]
309
+ pub fn process_trade_message(&mut self, trade: &TradePacket) {
310
+ let token = trade.trd.token as u32;
311
+ let buy_order_id = trade.trd.buy_order_id;
312
+ let sell_order_id = trade.trd.sell_order_id;
313
+ let qty = trade.trd.trade_quantity.max(0) as u32;
314
+
315
+ self.reduce_order(token, buy_order_id, qty);
316
+ self.reduce_order(token, sell_order_id, qty);
317
+ }
318
+
319
+ /// Return `mid_price`, `Vec` of top N bid levels, `Vec` of top N ask levels, for a given token.
320
+ /// Each `Vec` element is `(price, qty)`, ordered best-to-worst.
321
+ pub fn get_top_levels(
322
+ &self,
323
+ token: u32,
324
+ levels: usize,
325
+ ) -> Option<(u32, Vec<(u32, u64)>, Vec<(u32, u64)>)> {
326
+ let book = self.books.get(&token)?;
327
+ let mut bids = Vec::new();
328
+ let mut asks = Vec::new();
329
+ // Bids: iterate highest to lowest price (right to left)
330
+ for (i, &qty) in book.bid_levels.iter().enumerate().rev() {
331
+ if qty > 0 {
332
+ let price = book.dpr_min + (i as u32) * book.tick_size;
333
+ bids.push((price, qty));
334
+ }
335
+ if bids.len() >= levels {
336
+ break;
337
+ }
338
+ }
339
+ // Asks: iterate lowest to highest price (left to right)
340
+ for (i, &qty) in book.ask_levels.iter().enumerate() {
341
+ if qty > 0 {
342
+ let price = book.dpr_min + (i as u32) * book.tick_size;
343
+ asks.push((price, qty));
344
+ }
345
+ if asks.len() >= levels {
346
+ break;
347
+ }
348
+ }
349
+ let mid_price = if let (Some((b, _)), Some((a, _))) = (bids.first(), asks.first()) {
350
+ (b + a) / 2
351
+ } else {
352
+ 0
353
+ };
354
+ Some((mid_price, bids, asks))
355
+ }
356
+ }
@@ -0,0 +1,90 @@
1
+ use std::fs::File;
2
+ use std::io::Result;
3
+ use std::mem::size_of;
4
+
5
+ use memmap2::Mmap;
6
+
7
+ use crate::structure::{Message, OrderPacket, PeekStructure, TradePacket};
8
+
9
+ #[inline]
10
+ pub fn read_messages(path: &str) -> Result<Vec<Message>> {
11
+ let file = File::open(path)?;
12
+ let mmap = unsafe { Mmap::map(&file)? };
13
+ let buf = &mmap[..];
14
+
15
+ // Heuristic capacity to reduce reallocs.
16
+ let estimated_msg_count = buf.len() / size_of::<OrderPacket>();
17
+ let mut messages = Vec::with_capacity(estimated_msg_count);
18
+
19
+ let mut i: usize = 0;
20
+ while i < buf.len() {
21
+ // skip spaces
22
+ while i < buf.len() && buf[i] == b' ' {
23
+ i += 1;
24
+ }
25
+
26
+ if i + size_of::<PeekStructure>() > buf.len() {
27
+ break;
28
+ }
29
+
30
+ let peek_buf = &buf[i..i + size_of::<PeekStructure>()];
31
+ let peek_struct: PeekStructure =
32
+ unsafe { std::ptr::read_unaligned(peek_buf.as_ptr() as *const _) };
33
+
34
+ match peek_struct.msg_type {
35
+ b'T' => {
36
+ if i + size_of::<TradePacket>() > buf.len() {
37
+ break;
38
+ }
39
+
40
+ let trade_buf = &buf[i..i + size_of::<TradePacket>()];
41
+ let mut trade_packet: TradePacket =
42
+ unsafe { std::ptr::read_unaligned(trade_buf.as_ptr() as *const _) };
43
+
44
+ // endian fixups
45
+ trade_packet.hdr.msg_len = u16::from_le(trade_packet.hdr.msg_len);
46
+ trade_packet.hdr.stream_id = u16::from_le(trade_packet.hdr.stream_id);
47
+ trade_packet.hdr.seq_no = u32::from_le(trade_packet.hdr.seq_no);
48
+
49
+ trade_packet.trd.exch_ts = u64::from_le(trade_packet.trd.exch_ts);
50
+ trade_packet.trd.buy_order_id = u64::from_le(trade_packet.trd.buy_order_id);
51
+ trade_packet.trd.sell_order_id = u64::from_le(trade_packet.trd.sell_order_id);
52
+ trade_packet.trd.token = i32::from_le(trade_packet.trd.token);
53
+ trade_packet.trd.trade_price = i32::from_le(trade_packet.trd.trade_price);
54
+ trade_packet.trd.trade_quantity = i32::from_le(trade_packet.trd.trade_quantity);
55
+
56
+ messages.push(Message::Trade(trade_packet));
57
+ i += size_of::<TradePacket>();
58
+ }
59
+ b'N' | b'M' | b'X' => {
60
+ if i + size_of::<OrderPacket>() > buf.len() {
61
+ break;
62
+ }
63
+
64
+ let order_buf = &buf[i..i + size_of::<OrderPacket>()];
65
+ let mut order_packet: OrderPacket =
66
+ unsafe { std::ptr::read_unaligned(order_buf.as_ptr() as *const _) };
67
+
68
+ // endian fixups
69
+ order_packet.hdr.msg_len = u16::from_le(order_packet.hdr.msg_len);
70
+ order_packet.hdr.stream_id = u16::from_le(order_packet.hdr.stream_id);
71
+ order_packet.hdr.seq_no = u32::from_le(order_packet.hdr.seq_no);
72
+
73
+ order_packet.ord.exch_ts = u64::from_le(order_packet.ord.exch_ts);
74
+ order_packet.ord.order_id = u64::from_le(order_packet.ord.order_id);
75
+ order_packet.ord.token = u32::from_le(order_packet.ord.token);
76
+ order_packet.ord.price = u32::from_le(order_packet.ord.price);
77
+ order_packet.ord.quantity = u32::from_le(order_packet.ord.quantity);
78
+
79
+ messages.push(Message::Order(order_packet));
80
+ i += size_of::<OrderPacket>();
81
+ }
82
+ _ => {
83
+ // resync
84
+ i += 1;
85
+ }
86
+ }
87
+ }
88
+
89
+ Ok(messages)
90
+ }
@@ -0,0 +1,62 @@
1
+ #[repr(C, packed)]
2
+ #[derive(Clone, Copy, Debug)]
3
+ pub struct StreamHeader {
4
+ pub msg_len: u16,
5
+ pub stream_id: u16,
6
+ pub seq_no: u32,
7
+ }
8
+
9
+ #[repr(C, packed)]
10
+ #[derive(Clone, Copy, Debug)]
11
+ pub struct PeekStructure {
12
+ pub global_header: StreamHeader,
13
+ pub msg_type: u8,
14
+ }
15
+
16
+ #[repr(C, packed)]
17
+ #[derive(Clone, Copy, Debug)]
18
+ pub struct OrderMessage {
19
+ pub msg_type: u8,
20
+ pub exch_ts: u64,
21
+ pub order_id: u64,
22
+ pub token: u32,
23
+ pub order_type: u8,
24
+ pub price: u32,
25
+ pub quantity: u32,
26
+ }
27
+
28
+ #[repr(C, packed)]
29
+ #[derive(Clone, Copy, Debug)]
30
+ pub struct TradeMessage {
31
+ pub msg_type: u8,
32
+ pub exch_ts: u64,
33
+ pub buy_order_id: u64,
34
+ pub sell_order_id: u64,
35
+ pub token: i32,
36
+ pub trade_price: i32,
37
+ pub trade_quantity: i32,
38
+ }
39
+
40
+ #[repr(C, packed)]
41
+ #[derive(Clone, Copy, Debug)]
42
+ pub struct OrderPacket {
43
+ pub hdr: StreamHeader,
44
+ pub ord: OrderMessage,
45
+ pub local_ts: u64,
46
+ pub flags: bool,
47
+ }
48
+
49
+ #[repr(C, packed)]
50
+ #[derive(Clone, Copy, Debug)]
51
+ pub struct TradePacket {
52
+ pub hdr: StreamHeader,
53
+ pub trd: TradeMessage,
54
+ pub local_ts: u64,
55
+ pub flags: bool,
56
+ }
57
+
58
+ #[derive(Debug, Clone, Copy)]
59
+ pub enum Message {
60
+ Order(OrderPacket),
61
+ Trade(TradePacket),
62
+ }
@@ -0,0 +1,47 @@
1
+ #[cfg(target_arch = "x86_64")]
2
+ use core::arch::x86_64::{__cpuid, __rdtscp, _rdtsc};
3
+
4
+ #[cfg(target_arch = "x86_64")]
5
+ #[inline]
6
+ pub fn cycle_start() -> u64 {
7
+ unsafe {
8
+ __cpuid(0);
9
+ _rdtsc() as u64
10
+ }
11
+ }
12
+
13
+ #[cfg(target_arch = "x86_64")]
14
+ #[inline]
15
+ pub fn cycle_end() -> u64 {
16
+ unsafe {
17
+ let mut _aux: u32 = 0;
18
+ let tsc = __rdtscp(&mut _aux);
19
+ __cpuid(0);
20
+ tsc
21
+ }
22
+ }
23
+
24
+ #[cfg(target_arch = "aarch64")]
25
+ #[inline]
26
+ pub fn cycle_start() -> u64 {
27
+ let val: u64;
28
+ unsafe {
29
+ core::arch::asm!("mrs {}, cntvct_el0", out(reg) val);
30
+ }
31
+ val
32
+ }
33
+ #[cfg(target_arch = "aarch64")]
34
+ #[inline]
35
+ pub fn cycle_end() -> u64 { cycle_start() }
36
+
37
+ #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
38
+ #[inline]
39
+ pub fn cycle_start() -> u64 {
40
+ use std::time::Instant;
41
+ static START: std::sync::OnceLock<Instant> = std::sync::OnceLock::new();
42
+ let start = START.get_or_init(Instant::now);
43
+ start.elapsed().as_nanos() as u64
44
+ }
45
+ #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
46
+ #[inline]
47
+ pub fn cycle_end() -> u64 { cycle_start() }
@@ -0,0 +1,39 @@
1
+ version = 1
2
+ revision = 3
3
+ requires-python = ">=3.14"
4
+
5
+ [[package]]
6
+ name = "maturin"
7
+ version = "1.13.1"
8
+ source = { registry = "https://pypi.org/simple" }
9
+ sdist = { url = "https://files.pythonhosted.org/packages/39/16/b284a7bc4af3dd87717c784278c1b8cb18606ad1f6f7a671c47bfd9c3df0/maturin-1.13.1.tar.gz", hash = "sha256:9a87ff3b8e4d1c6eac33ebfe8e261e8236516d98d45c0323550621819b5a1a2f", size = 340369, upload-time = "2026-04-09T15:14:07.026Z" }
10
+ wheels = [
11
+ { url = "https://files.pythonhosted.org/packages/43/4d/a23fc95be881aa8c7a6ea353410417872e4d7065df03d7f3db8f0dbed4a7/maturin-1.13.1-py3-none-linux_armv6l.whl", hash = "sha256:416e4e01cb88b798e606ee43929df897e42c1647b722ef68283816cca99a8742", size = 10102444, upload-time = "2026-04-09T15:13:48.393Z" },
12
+ { url = "https://files.pythonhosted.org/packages/a6/1e/65c385d65bae95cf04895d52f39dbed8b1453ae55da2903d252ade40a774/maturin-1.13.1-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:72888e87819ce546d0d2df900e4b385e4ef299077d92ee37b48923a5602dae94", size = 19576043, upload-time = "2026-04-09T15:14:08.685Z" },
13
+ { url = "https://files.pythonhosted.org/packages/8f/13/f6bc868d0bfecd9314870b97f530a167e31f7878ac4945c78245c6eef69c/maturin-1.13.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:98b5fcf1a186c217830a8295ecc2989c6b1cf50945417adfc15252107b9475b7", size = 10117339, upload-time = "2026-04-09T15:13:40.559Z" },
14
+ { url = "https://files.pythonhosted.org/packages/51/58/279e081305c11c1c1c4fccacf77df8959646c5d4de7a57ec7e787653e270/maturin-1.13.1-py3-none-manylinux_2_12_i686.manylinux2010_i686.musllinux_1_1_i686.whl", hash = "sha256:3da18cccf2f683c0977bff9146a0908d6ffce836d600665736ac01679f588cb9", size = 10139689, upload-time = "2026-04-09T15:13:38.291Z" },
15
+ { url = "https://files.pythonhosted.org/packages/00/94/69391af5396c6aab723932240803f49e5f3de3dd7c57d32f02d237a0ce32/maturin-1.13.1-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.musllinux_1_1_x86_64.whl", hash = "sha256:6b1e5916a253243e8f5f9e847b62bbc98420eec48c9ce2e2e8724c6da89d359b", size = 10551141, upload-time = "2026-04-09T15:13:42.887Z" },
16
+ { url = "https://files.pythonhosted.org/packages/9e/bf/4edac2667b49e3733438062ae416413b8fc8d42e1bd499ba15e1fb02fc55/maturin-1.13.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:dc91031e0619c1e28730279ef9ee5f106c9b9ec806b013f888676b242f892eb7", size = 9983094, upload-time = "2026-04-09T15:13:56.868Z" },
17
+ { url = "https://files.pythonhosted.org/packages/79/94/a6d651cfe8fc6bf2e892c90e3cdbb25c06d81c9115140d03ea1a68a97575/maturin-1.13.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:001741c6cff56aa8ea59a0d78ae990c0550d0e3e82b00b683eedb4158a8ef7e6", size = 9949980, upload-time = "2026-04-09T15:13:59.185Z" },
18
+ { url = "https://files.pythonhosted.org/packages/b5/d1/82c067464f848e38af9910bce55eb54302b1c1284a279d515dbfcf5994f5/maturin-1.13.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.musllinux_1_1_ppc64le.whl", hash = "sha256:01c845825c917c07c1d0b2c9032c59c16a7d383d1e649a46481d3e5693c2750f", size = 13186276, upload-time = "2026-04-09T15:13:45.725Z" },
19
+ { url = "https://files.pythonhosted.org/packages/7c/f4/25367baf1025580f047f9b37598bb3fadc416e24536afd4f28e190335c73/maturin-1.13.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f69093ed4a0e6464e52a7fc26d714f859ce15630ec8070743398c6bf41f38a9e", size = 10891837, upload-time = "2026-04-09T15:13:35.68Z" },
20
+ { url = "https://files.pythonhosted.org/packages/af/be/caafad8ce74974b7deafdf144d12f758993dfea4c66c9905b138f51a7792/maturin-1.13.1-py3-none-manylinux_2_31_riscv64.musllinux_1_1_riscv64.whl", hash = "sha256:c1490584f3c70af45466ee99065b49e6657ebdccac6b10571bb44681309c9396", size = 10351032, upload-time = "2026-04-09T15:14:01.632Z" },
21
+ { url = "https://files.pythonhosted.org/packages/66/0e/970a721d27cfa410e8bfa0a1e32e6ef52cb8169692110a5fdabe1af3f570/maturin-1.13.1-py3-none-win32.whl", hash = "sha256:c6a720b252c99de072922dbe4432ab19662b6f80045b0355fec23bdfccb450da", size = 8855465, upload-time = "2026-04-09T15:13:51.122Z" },
22
+ { url = "https://files.pythonhosted.org/packages/88/70/7c1e0d65fa147d5479055a171541c82b8cdfc1c825d85a82240470f14176/maturin-1.13.1-py3-none-win_amd64.whl", hash = "sha256:a2017d2281203d0c6570240e7d746564d766d756105823b7de68bda6ae722711", size = 10230471, upload-time = "2026-04-09T15:13:53.89Z" },
23
+ { url = "https://files.pythonhosted.org/packages/c5/2a/afe0193b673a79ffd2e01ad999511b7e9e6b49af02bb3759d82a78c3043d/maturin-1.13.1-py3-none-win_arm64.whl", hash = "sha256:2839024dcd65776abb4759e5bca29941971e095574162a4d335191da4be9ff24", size = 8905575, upload-time = "2026-04-09T15:14:03.891Z" },
24
+ ]
25
+
26
+ [[package]]
27
+ name = "myrustlib"
28
+ version = "0.1.0"
29
+ source = { virtual = "." }
30
+
31
+ [package.dev-dependencies]
32
+ dev = [
33
+ { name = "maturin" },
34
+ ]
35
+
36
+ [package.metadata]
37
+
38
+ [package.metadata.requires-dev]
39
+ dev = [{ name = "maturin", specifier = ">=1.13.1" }]