rustyqlib/risk/
backtest.rs1#[derive(Debug, Clone, Copy)]
5pub struct KupiecTest {
6 pub exceptions: usize,
8 pub observations: usize,
9 pub expected: f64,
11 pub lr_statistic: f64,
13 pub rejected: bool,
16}
17
18pub fn kupiec_pof(exceptions: usize, observations: usize, confidence: f64) -> KupiecTest {
24 assert!(observations > 0 && exceptions <= observations);
25 assert!(confidence > 0.5 && confidence < 1.0);
26 let p = 1.0 - confidence;
27 let n = observations as f64;
28 let x = exceptions as f64;
29 let expected = p * n;
30 let ll = |prob: f64| -> f64 {
32 let mut v = 0.0;
33 if x > 0.0 {
34 v += x * prob.ln();
35 }
36 if x < n {
37 v += (n - x) * (1.0 - prob).ln();
38 }
39 v
40 };
41 let observed_rate = (x / n).clamp(1e-12, 1.0 - 1e-12);
42 let lr = -2.0 * (ll(p) - ll(observed_rate));
43 KupiecTest {
44 exceptions,
45 observations,
46 expected,
47 lr_statistic: lr,
48 rejected: lr > 3.841,
49 }
50}
51
52#[cfg(test)]
53mod tests {
54 use super::*;
55
56 #[test]
57 fn correct_coverage_passes_and_bad_coverage_fails() {
58 let ok = kupiec_pof(10, 1000, 0.99);
60 assert!(!ok.rejected, "{ok:?}");
61 assert!(ok.lr_statistic < 0.1);
62 let bad = kupiec_pof(30, 1000, 0.99);
64 assert!(bad.rejected, "{bad:?}");
65 let conservative = kupiec_pof(0, 1000, 0.99);
67 assert!(conservative.rejected, "{conservative:?}");
68 }
69}