1use core::ptr;
2
3use crate::GreekOut;
4
5unsafe extern "C" {
6 #[link_name = "implied_belief_volatility_batch"]
7 fn ffi_implied_belief_volatility_batch(
8 bid_p: *const f64,
9 ask_p: *const f64,
10 q_t: *const f64,
11 gamma: *const f64,
12 tau: *const f64,
13 k: *const f64,
14 out_sigma_b: *mut f64,
15 n: usize,
16 );
17
18 #[link_name = "simulate_shock_logit_batch"]
19 fn ffi_simulate_shock_logit_batch(
20 x_t: *const f64,
21 q_t: *const f64,
22 sigma_b: *const f64,
23 gamma: *const f64,
24 tau: *const f64,
25 k: *const f64,
26 shock_p: *const f64,
27 out_r_x: *mut f64,
28 out_bid_p: *mut f64,
29 out_ask_p: *mut f64,
30 out_greeks: *mut GreekOut,
31 out_pnl_shift: *mut f64,
32 n: usize,
33 );
34
35 #[link_name = "adaptive_kelly_clip_batch"]
36 fn ffi_adaptive_kelly_clip_batch(
37 belief_p: *const f64,
38 market_p: *const f64,
39 q_t: *const f64,
40 gamma: *const f64,
41 risk_limit: *const f64,
42 max_clip: *const f64,
43 out_maker_clip: *mut f64,
44 out_taker_clip: *mut f64,
45 n: usize,
46 );
47
48 #[link_name = "order_book_microstructure_batch"]
49 fn ffi_order_book_microstructure_batch(
50 bid_p: *const f64,
51 ask_p: *const f64,
52 bid_vol: *const f64,
53 ask_vol: *const f64,
54 out_obi: *mut f64,
55 out_vwm_p: *mut f64,
56 out_vwm_x: *mut f64,
57 out_pressure: *mut f64,
58 n: usize,
59 );
60
61 #[link_name = "aggregate_portfolio_greeks"]
62 fn ffi_aggregate_portfolio_greeks(
63 positions: *const f64,
64 delta_x: *const f64,
65 gamma_x: *const f64,
66 weights: *const f64,
67 corr_matrix: *const f64,
68 n: usize,
69 out_net_delta: *mut f64,
70 out_net_gamma: *mut f64,
71 );
72}
73
74fn assert_len(label: &str, actual: usize, expected: usize) {
75 assert_eq!(
76 actual, expected,
77 "{label}: length mismatch (expected {expected}, got {actual})"
78 );
79}
80
81#[allow(clippy::too_many_arguments)]
82pub fn implied_belief_volatility_batch(
86 bid_p: &[f64],
87 ask_p: &[f64],
88 q_t: &[f64],
89 gamma: &[f64],
90 tau: &[f64],
91 k: &[f64],
92 out_sigma_b: &mut [f64],
93) {
94 let n = bid_p.len();
95 assert_len("implied_belief_volatility_batch: ask_p", ask_p.len(), n);
96 assert_len("implied_belief_volatility_batch: q_t", q_t.len(), n);
97 assert_len("implied_belief_volatility_batch: gamma", gamma.len(), n);
98 assert_len("implied_belief_volatility_batch: tau", tau.len(), n);
99 assert_len("implied_belief_volatility_batch: k", k.len(), n);
100 assert_len(
101 "implied_belief_volatility_batch: out_sigma_b",
102 out_sigma_b.len(),
103 n,
104 );
105
106 unsafe {
107 ffi_implied_belief_volatility_batch(
108 bid_p.as_ptr(),
109 ask_p.as_ptr(),
110 q_t.as_ptr(),
111 gamma.as_ptr(),
112 tau.as_ptr(),
113 k.as_ptr(),
114 out_sigma_b.as_mut_ptr(),
115 n,
116 );
117 }
118}
119
120#[allow(clippy::too_many_arguments)]
121pub fn simulate_shock_logit_batch(
122 x_t: &[f64],
123 q_t: &[f64],
124 sigma_b: &[f64],
125 gamma: &[f64],
126 tau: &[f64],
127 k: &[f64],
128 shock_p: &[f64],
129 out_r_x: &mut [f64],
130 out_bid_p: &mut [f64],
131 out_ask_p: &mut [f64],
132 out_greeks: &mut [GreekOut],
133 out_pnl_shift: &mut [f64],
134) {
135 let n = x_t.len();
136 assert_len("simulate_shock_logit_batch: q_t", q_t.len(), n);
137 assert_len("simulate_shock_logit_batch: sigma_b", sigma_b.len(), n);
138 assert_len("simulate_shock_logit_batch: gamma", gamma.len(), n);
139 assert_len("simulate_shock_logit_batch: tau", tau.len(), n);
140 assert_len("simulate_shock_logit_batch: k", k.len(), n);
141 assert_len("simulate_shock_logit_batch: shock_p", shock_p.len(), n);
142 assert_len("simulate_shock_logit_batch: out_r_x", out_r_x.len(), n);
143 assert_len("simulate_shock_logit_batch: out_bid_p", out_bid_p.len(), n);
144 assert_len("simulate_shock_logit_batch: out_ask_p", out_ask_p.len(), n);
145 assert_len(
146 "simulate_shock_logit_batch: out_greeks",
147 out_greeks.len(),
148 n,
149 );
150 assert_len(
151 "simulate_shock_logit_batch: out_pnl_shift",
152 out_pnl_shift.len(),
153 n,
154 );
155
156 unsafe {
157 ffi_simulate_shock_logit_batch(
158 x_t.as_ptr(),
159 q_t.as_ptr(),
160 sigma_b.as_ptr(),
161 gamma.as_ptr(),
162 tau.as_ptr(),
163 k.as_ptr(),
164 shock_p.as_ptr(),
165 out_r_x.as_mut_ptr(),
166 out_bid_p.as_mut_ptr(),
167 out_ask_p.as_mut_ptr(),
168 out_greeks.as_mut_ptr(),
169 out_pnl_shift.as_mut_ptr(),
170 n,
171 );
172 }
173}
174
175#[allow(clippy::too_many_arguments)]
176pub fn adaptive_kelly_clip_batch(
177 belief_p: &[f64],
178 market_p: &[f64],
179 q_t: &[f64],
180 gamma: &[f64],
181 risk_limit: &[f64],
182 max_clip: &[f64],
183 out_maker_clip: &mut [f64],
184 out_taker_clip: &mut [f64],
185) {
186 let n = belief_p.len();
187 assert_len("adaptive_kelly_clip_batch: market_p", market_p.len(), n);
188 assert_len("adaptive_kelly_clip_batch: q_t", q_t.len(), n);
189 assert_len("adaptive_kelly_clip_batch: gamma", gamma.len(), n);
190 assert_len("adaptive_kelly_clip_batch: risk_limit", risk_limit.len(), n);
191 assert_len("adaptive_kelly_clip_batch: max_clip", max_clip.len(), n);
192 assert_len(
193 "adaptive_kelly_clip_batch: out_maker_clip",
194 out_maker_clip.len(),
195 n,
196 );
197 assert_len(
198 "adaptive_kelly_clip_batch: out_taker_clip",
199 out_taker_clip.len(),
200 n,
201 );
202
203 unsafe {
204 ffi_adaptive_kelly_clip_batch(
205 belief_p.as_ptr(),
206 market_p.as_ptr(),
207 q_t.as_ptr(),
208 gamma.as_ptr(),
209 risk_limit.as_ptr(),
210 max_clip.as_ptr(),
211 out_maker_clip.as_mut_ptr(),
212 out_taker_clip.as_mut_ptr(),
213 n,
214 );
215 }
216}
217
218#[allow(clippy::too_many_arguments)]
219pub fn order_book_microstructure_batch(
220 bid_p: &[f64],
221 ask_p: &[f64],
222 bid_vol: &[f64],
223 ask_vol: &[f64],
224 out_obi: &mut [f64],
225 out_vwm_p: &mut [f64],
226 out_vwm_x: &mut [f64],
227 out_pressure: &mut [f64],
228) {
229 let n = bid_p.len();
230 assert_len("order_book_microstructure_batch: ask_p", ask_p.len(), n);
231 assert_len("order_book_microstructure_batch: bid_vol", bid_vol.len(), n);
232 assert_len("order_book_microstructure_batch: ask_vol", ask_vol.len(), n);
233 assert_len("order_book_microstructure_batch: out_obi", out_obi.len(), n);
234 assert_len(
235 "order_book_microstructure_batch: out_vwm_p",
236 out_vwm_p.len(),
237 n,
238 );
239 assert_len(
240 "order_book_microstructure_batch: out_vwm_x",
241 out_vwm_x.len(),
242 n,
243 );
244 assert_len(
245 "order_book_microstructure_batch: out_pressure",
246 out_pressure.len(),
247 n,
248 );
249
250 unsafe {
251 ffi_order_book_microstructure_batch(
252 bid_p.as_ptr(),
253 ask_p.as_ptr(),
254 bid_vol.as_ptr(),
255 ask_vol.as_ptr(),
256 out_obi.as_mut_ptr(),
257 out_vwm_p.as_mut_ptr(),
258 out_vwm_x.as_mut_ptr(),
259 out_pressure.as_mut_ptr(),
260 n,
261 );
262 }
263}
264
265pub fn aggregate_portfolio_greeks(
266 positions: &[f64],
267 delta_x: &[f64],
268 gamma_x: &[f64],
269 weights: Option<&[f64]>,
270 corr_matrix: Option<&[f64]>,
271) -> (f64, f64) {
272 let n = positions.len();
273 assert_len("aggregate_portfolio_greeks: delta_x", delta_x.len(), n);
274 assert_len("aggregate_portfolio_greeks: gamma_x", gamma_x.len(), n);
275
276 if let Some(w) = weights {
277 assert_len("aggregate_portfolio_greeks: weights", w.len(), n);
278 }
279
280 if let Some(corr) = corr_matrix {
281 let expected = n
282 .checked_mul(n)
283 .expect("aggregate_portfolio_greeks: n*n overflow");
284 assert_len(
285 "aggregate_portfolio_greeks: corr_matrix",
286 corr.len(),
287 expected,
288 );
289 }
290
291 let mut net_delta = 0.0;
292 let mut net_gamma = 0.0;
293
294 unsafe {
295 ffi_aggregate_portfolio_greeks(
296 positions.as_ptr(),
297 delta_x.as_ptr(),
298 gamma_x.as_ptr(),
299 weights.map_or(ptr::null(), <[f64]>::as_ptr),
300 corr_matrix.map_or(ptr::null(), <[f64]>::as_ptr),
301 n,
302 &mut net_delta,
303 &mut net_gamma,
304 );
305 }
306
307 (net_delta, net_gamma)
308}
309
310#[cfg(test)]
311mod tests {
312 use super::*;
313
314 unsafe extern "C" {
315 #[link_name = "pm_internal_order_book_microstructure_batch_portable"]
316 fn ffi_internal_order_book_microstructure_batch_portable(
317 bid_p: *const f64,
318 ask_p: *const f64,
319 bid_vol: *const f64,
320 ask_vol: *const f64,
321 out_obi: *mut f64,
322 out_vwm_p: *mut f64,
323 out_vwm_x: *mut f64,
324 out_pressure: *mut f64,
325 n: usize,
326 );
327 }
328
329 fn assert_close(actual: f64, expected: f64, tol: f64) {
330 let diff = (actual - expected).abs();
331 assert!(
332 diff <= tol,
333 "expected {expected:.15}, got {actual:.15}, diff {diff:.15} > {tol:.15}"
334 );
335 }
336
337 fn has_avx512f() -> bool {
338 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
339 {
340 std::arch::is_x86_feature_detected!("avx512f")
341 }
342
343 #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
344 {
345 false
346 }
347 }
348
349 fn run_order_book_portable(
350 bid_p: &[f64],
351 ask_p: &[f64],
352 bid_vol: &[f64],
353 ask_vol: &[f64],
354 ) -> (Vec<f64>, Vec<f64>, Vec<f64>, Vec<f64>) {
355 let n = bid_p.len();
356 let mut obi = vec![0.0; n];
357 let mut vwm_p = vec![0.0; n];
358 let mut vwm_x = vec![0.0; n];
359 let mut pressure = vec![0.0; n];
360
361 unsafe {
362 ffi_internal_order_book_microstructure_batch_portable(
363 bid_p.as_ptr(),
364 ask_p.as_ptr(),
365 bid_vol.as_ptr(),
366 ask_vol.as_ptr(),
367 obi.as_mut_ptr(),
368 vwm_p.as_mut_ptr(),
369 vwm_x.as_mut_ptr(),
370 pressure.as_mut_ptr(),
371 n,
372 );
373 }
374
375 (obi, vwm_p, vwm_x, pressure)
376 }
377
378 #[test]
379 fn order_book_microstructure_clamps_invalid_inputs() {
380 let bid_p = [0.0, 0.8, -0.2];
381 let ask_p = [1.2, 0.7, 0.4];
382 let bid_vol = [-5.0, 0.0, 3.0];
383 let ask_vol = [0.0, -2.0, 0.0];
384
385 let mut obi = vec![0.0; bid_p.len()];
386 let mut vwm_p = vec![0.0; bid_p.len()];
387 let mut vwm_x = vec![0.0; bid_p.len()];
388 let mut pressure = vec![0.0; bid_p.len()];
389
390 order_book_microstructure_batch(
391 &bid_p,
392 &ask_p,
393 &bid_vol,
394 &ask_vol,
395 &mut obi,
396 &mut vwm_p,
397 &mut vwm_x,
398 &mut pressure,
399 );
400
401 let (expected_obi, expected_vwm_p, expected_vwm_x, expected_pressure) =
402 run_order_book_portable(&bid_p, &ask_p, &bid_vol, &ask_vol);
403
404 for i in 0..bid_p.len() {
405 assert!(obi[i].is_finite());
406 assert!(vwm_p[i].is_finite());
407 assert!(vwm_x[i].is_finite());
408 assert!(pressure[i].is_finite());
409 assert_close(obi[i], expected_obi[i], 1e-12);
410 assert_close(vwm_p[i], expected_vwm_p[i], 1e-12);
411 assert_close(vwm_x[i], expected_vwm_x[i], 1e-12);
412 assert_close(pressure[i], expected_pressure[i], 1e-12);
413 }
414 }
415
416 #[test]
417 fn order_book_dispatch_matches_portable_reference() {
418 if !has_avx512f() {
419 return;
420 }
421
422 for len in [3usize, 8, 17] {
423 let bid_p: Vec<_> = (0..len).map(|i| 0.15 + (i as f64) * 0.03).collect();
424 let ask_p: Vec<_> = (0..len)
425 .map(|i| 0.20 + (i as f64) * 0.03 - if i % 5 == 0 { 0.07 } else { 0.0 })
426 .collect();
427 let bid_vol: Vec<_> = (0..len)
428 .map(|i| if i % 4 == 0 { -2.0 } else { 1.0 + i as f64 })
429 .collect();
430 let ask_vol: Vec<_> = (0..len)
431 .map(|i| {
432 if i % 6 == 0 {
433 -1.0
434 } else {
435 0.5 + (i as f64) * 0.5
436 }
437 })
438 .collect();
439
440 let mut obi = vec![0.0; len];
441 let mut vwm_p = vec![0.0; len];
442 let mut vwm_x = vec![0.0; len];
443 let mut pressure = vec![0.0; len];
444
445 order_book_microstructure_batch(
446 &bid_p,
447 &ask_p,
448 &bid_vol,
449 &ask_vol,
450 &mut obi,
451 &mut vwm_p,
452 &mut vwm_x,
453 &mut pressure,
454 );
455
456 let (expected_obi, expected_vwm_p, expected_vwm_x, expected_pressure) =
457 run_order_book_portable(&bid_p, &ask_p, &bid_vol, &ask_vol);
458
459 for i in 0..len {
460 assert_close(obi[i], expected_obi[i], 1e-12);
461 assert_close(vwm_p[i], expected_vwm_p[i], 1e-12);
462 assert_close(vwm_x[i], expected_vwm_x[i], 1e-12);
463 assert_close(pressure[i], expected_pressure[i], 1e-12);
464 }
465 }
466 }
467}