rustkernel_temporal/
ring_messages.rs

1//! Ring message types for Temporal Analysis domain kernels.
2//!
3//! These messages implement the `RingMessage` trait for GPU-native persistent
4//! actor communication in volatility analysis and temporal operations.
5//!
6//! Type ID range: 400-499 (Temporal Analysis domain)
7//!
8//! ## Type ID Assignments
9//! - 400-409: VolatilityAnalysis messages
10//! - 410-419: Reserved for ARIMA
11//! - 420-429: Reserved for ChangePointDetection
12//! - 430-439: Reserved for SeasonalDecomposition
13
14use ringkernel_core::message::{CorrelationId, MessageId};
15use ringkernel_derive::RingMessage;
16use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
17
18// ============================================================================
19// Volatility Analysis Ring Messages (400-409)
20// ============================================================================
21
22/// Ring message for updating volatility model with new return data.
23///
24/// Type ID: 400
25#[derive(Debug, Clone, Archive, RkyvSerialize, RkyvDeserialize, RingMessage)]
26#[message(type_id = 400)]
27#[archive(check_bytes)]
28pub struct UpdateVolatilityRing {
29    /// Message ID.
30    #[message(id)]
31    pub id: MessageId,
32    /// Correlation ID.
33    #[message(correlation)]
34    pub correlation_id: CorrelationId,
35    /// Asset/instrument ID.
36    pub asset_id: u64,
37    /// Return value (fixed-point, 8 decimals).
38    pub return_value: i64,
39    /// Timestamp (nanoseconds since epoch).
40    pub timestamp: u64,
41}
42
43impl UpdateVolatilityRing {
44    /// Create a new volatility update message.
45    pub fn new(asset_id: u64, return_value: f64, timestamp: u64) -> Self {
46        Self {
47            id: MessageId::generate(),
48            correlation_id: CorrelationId::generate(),
49            asset_id,
50            return_value: (return_value * 100_000_000.0) as i64,
51            timestamp,
52        }
53    }
54
55    /// Get return value as f64.
56    pub fn return_f64(&self) -> f64 {
57        self.return_value as f64 / 100_000_000.0
58    }
59}
60
61/// Response from volatility update.
62///
63/// Type ID: 401
64#[derive(Debug, Clone, Archive, RkyvSerialize, RkyvDeserialize, RingMessage)]
65#[message(type_id = 401)]
66#[archive(check_bytes)]
67pub struct UpdateVolatilityResponse {
68    /// Correlation ID.
69    #[message(correlation)]
70    pub correlation_id: CorrelationId,
71    /// Asset ID.
72    pub asset_id: u64,
73    /// Current volatility estimate (fixed-point, 8 decimals).
74    pub current_volatility: i64,
75    /// Current variance (fixed-point, 8 decimals).
76    pub current_variance: i64,
77    /// Number of observations in model.
78    pub observation_count: u32,
79}
80
81impl UpdateVolatilityResponse {
82    /// Get volatility as f64.
83    pub fn volatility_f64(&self) -> f64 {
84        self.current_volatility as f64 / 100_000_000.0
85    }
86
87    /// Get variance as f64.
88    pub fn variance_f64(&self) -> f64 {
89        self.current_variance as f64 / 100_000_000.0
90    }
91}
92
93/// Ring message for querying current volatility forecast.
94///
95/// Type ID: 402
96#[derive(Debug, Clone, Archive, RkyvSerialize, RkyvDeserialize, RingMessage)]
97#[message(type_id = 402)]
98#[archive(check_bytes)]
99pub struct QueryVolatilityRing {
100    /// Message ID.
101    #[message(id)]
102    pub id: MessageId,
103    /// Correlation ID.
104    #[message(correlation)]
105    pub correlation_id: CorrelationId,
106    /// Asset ID to query.
107    pub asset_id: u64,
108    /// Forecast horizon (number of periods).
109    pub horizon: u32,
110}
111
112impl QueryVolatilityRing {
113    /// Create a new volatility query message.
114    pub fn new(asset_id: u64, horizon: u32) -> Self {
115        Self {
116            id: MessageId::generate(),
117            correlation_id: CorrelationId::generate(),
118            asset_id,
119            horizon,
120        }
121    }
122}
123
124/// Response with volatility forecast.
125///
126/// Type ID: 403
127#[derive(Debug, Clone, Archive, RkyvSerialize, RkyvDeserialize, RingMessage)]
128#[message(type_id = 403)]
129#[archive(check_bytes)]
130pub struct QueryVolatilityResponse {
131    /// Correlation ID.
132    #[message(correlation)]
133    pub correlation_id: CorrelationId,
134    /// Asset ID.
135    pub asset_id: u64,
136    /// Current volatility (fixed-point).
137    pub current_volatility: i64,
138    /// Forecasted volatilities (up to 10 periods, fixed-point).
139    pub forecast: [i64; 10],
140    /// Number of valid forecast periods.
141    pub forecast_count: u8,
142    /// GARCH persistence (alpha + beta).
143    pub persistence: i32, // Fixed-point, 4 decimals
144}
145
146impl QueryVolatilityResponse {
147    /// Get forecast as `Vec<f64>`.
148    pub fn forecast_f64(&self) -> Vec<f64> {
149        self.forecast[..self.forecast_count as usize]
150            .iter()
151            .map(|&v| v as f64 / 100_000_000.0)
152            .collect()
153    }
154}
155
156/// Volatility spike alert.
157///
158/// Type ID: 404
159#[derive(Debug, Clone, Archive, RkyvSerialize, RkyvDeserialize, RingMessage)]
160#[message(type_id = 404)]
161#[archive(check_bytes)]
162pub struct VolatilitySpikeAlert {
163    /// Message ID.
164    #[message(id)]
165    pub id: MessageId,
166    /// Asset ID.
167    pub asset_id: u64,
168    /// Current volatility (fixed-point).
169    pub current_volatility: i64,
170    /// Previous volatility (fixed-point).
171    pub previous_volatility: i64,
172    /// Spike ratio (current / previous, fixed-point 4 decimals).
173    pub spike_ratio: i32,
174    /// Timestamp.
175    pub timestamp: u64,
176    /// Alert severity (1-5).
177    pub severity: u8,
178}
179
180// ============================================================================
181// EWMA Volatility Ring Messages
182// ============================================================================
183
184/// Ring message for EWMA volatility update.
185///
186/// Type ID: 405
187#[derive(Debug, Clone, Archive, RkyvSerialize, RkyvDeserialize, RingMessage)]
188#[message(type_id = 405)]
189#[archive(check_bytes)]
190pub struct UpdateEWMAVolatilityRing {
191    /// Message ID.
192    #[message(id)]
193    pub id: MessageId,
194    /// Correlation ID.
195    #[message(correlation)]
196    pub correlation_id: CorrelationId,
197    /// Asset ID.
198    pub asset_id: u64,
199    /// Return value (fixed-point).
200    pub return_value: i64,
201    /// Lambda decay factor (fixed-point, 4 decimals).
202    /// Default: 9400 (0.94)
203    pub lambda: u16,
204    /// Timestamp.
205    pub timestamp: u64,
206}
207
208impl UpdateEWMAVolatilityRing {
209    /// Create a new EWMA update message with default lambda (0.94).
210    pub fn new(asset_id: u64, return_value: f64, timestamp: u64) -> Self {
211        Self {
212            id: MessageId::generate(),
213            correlation_id: CorrelationId::generate(),
214            asset_id,
215            return_value: (return_value * 100_000_000.0) as i64,
216            lambda: 9400, // 0.94
217            timestamp,
218        }
219    }
220
221    /// Create with custom lambda.
222    pub fn with_lambda(asset_id: u64, return_value: f64, lambda: f64, timestamp: u64) -> Self {
223        Self {
224            id: MessageId::generate(),
225            correlation_id: CorrelationId::generate(),
226            asset_id,
227            return_value: (return_value * 100_000_000.0) as i64,
228            lambda: (lambda * 10000.0) as u16,
229            timestamp,
230        }
231    }
232
233    /// Get lambda as f64.
234    pub fn lambda_f64(&self) -> f64 {
235        self.lambda as f64 / 10000.0
236    }
237}
238
239/// Response from EWMA update.
240///
241/// Type ID: 406
242#[derive(Debug, Clone, Archive, RkyvSerialize, RkyvDeserialize, RingMessage)]
243#[message(type_id = 406)]
244#[archive(check_bytes)]
245pub struct UpdateEWMAVolatilityResponse {
246    /// Correlation ID.
247    #[message(correlation)]
248    pub correlation_id: CorrelationId,
249    /// Asset ID.
250    pub asset_id: u64,
251    /// Current EWMA variance (fixed-point).
252    pub ewma_variance: i64,
253    /// Current EWMA volatility (fixed-point).
254    pub ewma_volatility: i64,
255}
256
257// ============================================================================
258// Model Coefficients Messages
259// ============================================================================
260
261/// Ring message to set GARCH coefficients.
262///
263/// Type ID: 407
264#[derive(Debug, Clone, Archive, RkyvSerialize, RkyvDeserialize, RingMessage)]
265#[message(type_id = 407)]
266#[archive(check_bytes)]
267pub struct SetGARCHCoefficientsRing {
268    /// Message ID.
269    #[message(id)]
270    pub id: MessageId,
271    /// Correlation ID.
272    #[message(correlation)]
273    pub correlation_id: CorrelationId,
274    /// Asset ID.
275    pub asset_id: u64,
276    /// Omega (constant term, fixed-point 8 decimals).
277    pub omega: i64,
278    /// Alpha (ARCH coefficient, fixed-point 4 decimals).
279    pub alpha: i32,
280    /// Beta (GARCH coefficient, fixed-point 4 decimals).
281    pub beta: i32,
282}
283
284impl SetGARCHCoefficientsRing {
285    /// Create a new set coefficients message.
286    pub fn new(asset_id: u64, omega: f64, alpha: f64, beta: f64) -> Self {
287        Self {
288            id: MessageId::generate(),
289            correlation_id: CorrelationId::generate(),
290            asset_id,
291            omega: (omega * 100_000_000.0) as i64,
292            alpha: (alpha * 10000.0) as i32,
293            beta: (beta * 10000.0) as i32,
294        }
295    }
296}
297
298/// Response from setting coefficients.
299///
300/// Type ID: 408
301#[derive(Debug, Clone, Archive, RkyvSerialize, RkyvDeserialize, RingMessage)]
302#[message(type_id = 408)]
303#[archive(check_bytes)]
304pub struct SetGARCHCoefficientsResponse {
305    /// Correlation ID.
306    #[message(correlation)]
307    pub correlation_id: CorrelationId,
308    /// Asset ID.
309    pub asset_id: u64,
310    /// Whether update succeeded.
311    pub success: bool,
312    /// Long-run variance implied by coefficients (fixed-point).
313    pub long_run_variance: i64,
314}
315
316#[cfg(test)]
317mod tests {
318    use super::*;
319
320    #[test]
321    fn test_update_volatility_ring() {
322        let msg = UpdateVolatilityRing::new(1, 0.015, 1234567890);
323        assert_eq!(msg.asset_id, 1);
324        assert_eq!(msg.return_value, 1_500_000); // 0.015 * 10^8
325        assert!((msg.return_f64() - 0.015).abs() < 1e-10);
326    }
327
328    #[test]
329    fn test_query_volatility_ring() {
330        let msg = QueryVolatilityRing::new(42, 10);
331        assert_eq!(msg.asset_id, 42);
332        assert_eq!(msg.horizon, 10);
333    }
334
335    #[test]
336    fn test_ewma_with_lambda() {
337        let msg = UpdateEWMAVolatilityRing::with_lambda(1, 0.02, 0.97, 1234567890);
338        assert_eq!(msg.lambda, 9700);
339        assert!((msg.lambda_f64() - 0.97).abs() < 1e-4);
340    }
341
342    #[test]
343    fn test_garch_coefficients() {
344        let msg = SetGARCHCoefficientsRing::new(1, 0.00001, 0.1, 0.85);
345        assert_eq!(msg.asset_id, 1);
346        assert_eq!(msg.alpha, 1000); // 0.1 * 10000
347        assert_eq!(msg.beta, 8500); // 0.85 * 10000
348    }
349}