Skip to main content

polymarket_kernel/
analytics.rs

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)]
82/// `q_t` is retained in the API for model-shape consistency with the rest of the
83/// analytics surface, but the current calibration formula depends only on the
84/// observed spread, `gamma`, `tau`, and `k`.
85pub 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}