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.
- orderpulse-0.1.0/Cargo.lock +190 -0
- orderpulse-0.1.0/Cargo.toml +18 -0
- orderpulse-0.1.0/PKG-INFO +4 -0
- orderpulse-0.1.0/README.md +0 -0
- orderpulse-0.1.0/main.py +6 -0
- orderpulse-0.1.0/pyproject.toml +8 -0
- orderpulse-0.1.0/src/lib.rs +147 -0
- orderpulse-0.1.0/src/main.rs +240 -0
- orderpulse-0.1.0/src/orderbook.rs +356 -0
- orderpulse-0.1.0/src/read_trd_ord_only.rs +90 -0
- orderpulse-0.1.0/src/structure.rs +62 -0
- orderpulse-0.1.0/src/tsc.rs +47 -0
- orderpulse-0.1.0/uv.lock +39 -0
|
@@ -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"] }
|
|
File without changes
|
orderpulse-0.1.0/main.py
ADDED
|
@@ -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() }
|
orderpulse-0.1.0/uv.lock
ADDED
|
@@ -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" }]
|