quantwave_core/indicators/
kinematic_kalman.rs1use crate::indicators::metadata::{IndicatorMetadata, ParamDef};
2use crate::traits::Next;
3
4#[derive(Debug, Clone)]
12pub struct KinematicKalmanFilter {
13 q_pos: f64,
14 q_vel: f64,
15 r: f64,
16 x: f64,
18 v: f64,
19 p00: f64,
21 p01: f64,
22 p11: f64,
23 initialized: bool,
24}
25
26impl KinematicKalmanFilter {
27 pub fn new(q_pos: f64, q_vel: f64, r: f64) -> Self {
28 Self {
29 q_pos,
30 q_vel,
31 r,
32 x: 0.0,
33 v: 0.0,
34 p00: 1.0,
35 p01: 0.0,
36 p11: 1.0,
37 initialized: false,
38 }
39 }
40}
41
42impl Next<f64> for KinematicKalmanFilter {
43 type Output = f64;
44
45 fn next(&mut self, input: f64) -> Self::Output {
46 if !self.initialized {
47 self.x = input;
48 self.v = 0.0;
49 self.initialized = true;
50 return self.x;
51 }
52
53 let x_pred = self.x + self.v;
58 let v_pred = self.v;
59
60 let p00_pred = self.p00 + 2.0 * self.p01 + self.p11 + self.q_pos;
63 let p01_pred = self.p01 + self.p11;
64 let p11_pred = self.p11 + self.q_vel;
65
66 let s = p00_pred + self.r;
70
71 let k0 = p00_pred / s;
73 let k1 = p01_pred / s;
74
75 let innovation = input - x_pred;
77 self.x = x_pred + k0 * innovation;
78 self.v = v_pred + k1 * innovation;
79
80 self.p00 = (1.0 - k0) * p00_pred;
85 self.p01 = (1.0 - k0) * p01_pred;
86 self.p11 = -k1 * p01_pred + p11_pred;
87
88 self.x
89 }
90}
91
92pub const KINEMATIC_KALMAN_METADATA: IndicatorMetadata = IndicatorMetadata {
93 name: "Kinematic Kalman Filter",
94 description: "A 2D Kalman filter tracking price and velocity to reduce lag in trends.",
95 usage: "Optimized for trend-following strategies where lag reduction is critical. q_pos controls price sensitivity, q_vel controls momentum sensitivity, and r controls overall smoothing.",
96 keywords: &[
97 "kalman",
98 "adaptive",
99 "kinematic",
100 "momentum",
101 "lag-reduction",
102 ],
103 ehlers_summary: "The Kinematic Kalman Filter extends the 1D model by incorporating a velocity state. This allows the filter to 'anticipate' the next price based on current momentum, providing a zero-lag-like response during strong trends while maintaining smoothness via its optimal error-correction logic.",
104 params: &[
105 ParamDef {
106 name: "q_pos",
107 default: "0.001",
108 description: "Process noise for position (price)",
109 },
110 ParamDef {
111 name: "q_vel",
112 default: "0.0001",
113 description: "Process noise for velocity (momentum)",
114 },
115 ParamDef {
116 name: "r",
117 default: "0.1",
118 description: "Measurement noise (smoothing strength)",
119 },
120 ],
121 formula_source: "https://www.cs.unc.edu/~welch/kalman/media/pdf/Kalman1960.pdf",
122 formula_latex: r#"
123\[
124\hat{x}_{k|k-1} = \Phi \hat{x}_{k-1|k-1}
125\]
126\[
127P_{k|k-1} = \Phi P_{k-1|k-1} \Phi^T + Q
128\]
129\[
130K_k = P_{k|k-1} H^T (H P_{k|k-1} H^T + R)^{-1}
131\]
132\[
133\hat{x}_{k|k} = \hat{x}_{k|k-1} + K_k (z_k - H \hat{x}_{k|k-1})
134\]
135\[
136P_{k|k} = (I - K_k H) P_{k|k-1}
137\]
138"#,
139 gold_standard_file: "kinematic_kalman.json",
140 category: "ML Features",
141};
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146 use crate::traits::Next;
147 use proptest::prelude::*;
148
149 #[test]
150 fn test_kinematic_kalman_basic() {
151 let mut kf = KinematicKalmanFilter::new(0.001, 0.0001, 0.1);
152 let res = kf.next(100.0);
153 assert_eq!(res, 100.0); let res2 = kf.next(101.0);
157 assert!(res2 > 100.0 && res2 < 101.0);
158
159 let res3 = kf.next(102.0);
160 assert!(res3 > res2);
161 }
162
163 proptest! {
164 #[test]
165 fn test_kinematic_kalman_parity(
166 inputs in prop::collection::vec(10.0..200.0, 50..100),
167 ) {
168 let q_pos = 0.001;
169 let q_vel = 0.0001;
170 let r = 0.1;
171
172 let mut kf = KinematicKalmanFilter::new(q_pos, q_vel, r);
173 let streaming_results: Vec<f64> = inputs.iter().map(|&x| kf.next(x)).collect();
174
175 let mut batch_results = Vec::with_capacity(inputs.len());
177 let mut x = inputs[0];
178 let mut v = 0.0;
179 let mut p00 = 1.0;
180 let mut p01 = 0.0;
181 let mut p11 = 1.0;
182 batch_results.push(x);
183
184 for i in 1..inputs.len() {
185 let x_pred = x + v;
187 let v_pred = v;
188 let p00_pred = p00 + 2.0 * p01 + p11 + q_pos;
189 let p01_pred = p01 + p11;
190 let p11_pred = p11 + q_vel;
191
192 let s = p00_pred + r;
194 let k0 = p00_pred / s;
195 let k1 = p01_pred / s;
196
197 let innovation = inputs[i] - x_pred;
198 x = x_pred + k0 * innovation;
199 v = v_pred + k1 * innovation;
200
201 p00 = (1.0 - k0) * p00_pred;
202 p01 = (1.0 - k0) * p01_pred;
203 p11 = -k1 * p01_pred + p11_pred;
204
205 batch_results.push(x);
206 }
207
208 for (s, b) in streaming_results.iter().zip(batch_results.iter()) {
209 approx::assert_relative_eq!(s, b, epsilon = 1e-10);
210 }
211 }
212 }
213}