1use ringkernel_derive::RingMessage;
14use rkyv::{Archive, Deserialize, Serialize};
15use rustkernel_core::messages::MessageId;
16
17#[derive(Debug, Clone, Archive, Serialize, Deserialize, RingMessage)]
23#[archive(check_bytes)]
24#[message(type_id = 600)]
25pub struct UpdatePositionRing {
26 pub id: MessageId,
28 pub asset_id: u64,
30 pub value_fp: i64,
32 pub expected_return_fp: i64,
34 pub volatility_fp: i64,
36}
37
38#[derive(Debug, Clone, Archive, Serialize, Deserialize, RingMessage)]
40#[archive(check_bytes)]
41#[message(type_id = 601)]
42pub struct UpdatePositionResponse {
43 pub request_id: u64,
45 pub asset_id: u64,
47 pub var_stale: bool,
49}
50
51#[derive(Debug, Clone, Archive, Serialize, Deserialize, RingMessage)]
53#[archive(check_bytes)]
54#[message(type_id = 602)]
55pub struct QueryVaRRing {
56 pub id: MessageId,
58 pub confidence_fp: i64,
60 pub holding_period: u32,
62}
63
64#[derive(Debug, Clone, Archive, Serialize, Deserialize, RingMessage)]
66#[archive(check_bytes)]
67#[message(type_id = 603)]
68pub struct QueryVaRResponse {
69 pub request_id: u64,
71 pub var_fp: i64,
73 pub es_fp: i64,
75 pub confidence_fp: i64,
77 pub holding_period: u32,
79 pub is_fresh: bool,
81}
82
83#[derive(Debug, Clone, Archive, Serialize, Deserialize, RingMessage)]
85#[archive(check_bytes)]
86#[message(type_id = 604)]
87pub struct RecalculateVaRRing {
88 pub id: MessageId,
90 pub n_simulations: u32,
92 pub confidence_fp: i64,
94 pub holding_period: u32,
96}
97
98#[derive(Debug, Clone, Archive, Serialize, Deserialize, RingMessage)]
100#[archive(check_bytes)]
101#[message(type_id = 605)]
102pub struct RecalculateVaRResponse {
103 pub request_id: u64,
105 pub var_fp: i64,
107 pub es_fp: i64,
109 pub compute_time_us: u64,
111 pub n_simulations: u32,
113}
114
115#[derive(Debug, Clone, Archive, Serialize, Deserialize, RingMessage)]
123#[archive(check_bytes)]
124#[message(type_id = 660)]
125pub struct K2KPositionBatch {
126 pub id: MessageId,
128 pub source_worker: u64,
130 pub batch_seq: u64,
132 pub position_count: u32,
134 pub asset_ids: [u64; 8],
136 pub values_fp: [i64; 8],
138}
139
140#[derive(Debug, Clone, Archive, Serialize, Deserialize, RingMessage)]
142#[archive(check_bytes)]
143#[message(type_id = 661)]
144pub struct K2KPartialVaR {
145 pub id: MessageId,
147 pub worker_id: u64,
149 pub correlation_id: u64,
151 pub partial_var_fp: i64,
153 pub partial_es_fp: i64,
155 pub positions_processed: u32,
157 pub cov_contribution_fp: i64,
159}
160
161#[derive(Debug, Clone, Archive, Serialize, Deserialize, RingMessage)]
165#[archive(check_bytes)]
166#[message(type_id = 662)]
167pub struct K2KVaRAggregation {
168 pub id: MessageId,
170 pub correlation_id: u64,
172 pub expected_workers: u32,
174 pub workers_reported: u32,
176 pub aggregated_var_fp: i64,
178}
179
180#[derive(Debug, Clone, Archive, Serialize, Deserialize, RingMessage)]
182#[archive(check_bytes)]
183#[message(type_id = 663)]
184pub struct K2KVaRAggregationResponse {
185 pub correlation_id: u64,
187 pub complete: bool,
189 pub final_var_fp: i64,
191 pub final_es_fp: i64,
193 pub diversification_benefit_fp: i64,
195}
196
197#[derive(Debug, Clone, Archive, Serialize, Deserialize, RingMessage)]
201#[archive(check_bytes)]
202#[message(type_id = 664)]
203pub struct K2KMarketUpdate {
204 pub id: MessageId,
206 pub timestamp_us: u64,
208 pub asset_id: u64,
210 pub price_fp: i64,
212 pub vol_delta_fp: i64,
214}
215
216#[derive(Debug, Clone, Archive, Serialize, Deserialize, RingMessage)]
218#[archive(check_bytes)]
219#[message(type_id = 665)]
220pub struct K2KMarketUpdateAck {
221 pub request_id: u64,
223 pub worker_id: u64,
225 pub var_impact_fp: i64,
227}
228
229#[derive(Debug, Clone, Archive, Serialize, Deserialize, RingMessage)]
233#[archive(check_bytes)]
234#[message(type_id = 666)]
235pub struct K2KRiskLimitAlert {
236 pub id: MessageId,
238 pub timestamp_us: u64,
240 pub severity: u8,
242 pub current_var_fp: i64,
244 pub var_limit_fp: i64,
246 pub breach_amount_fp: i64,
248 pub trigger_asset_id: u64,
250}
251
252#[inline]
258pub fn to_fixed_point(value: f64) -> i64 {
259 (value * 100_000_000.0) as i64
260}
261
262#[inline]
264pub fn from_fixed_point(fp: i64) -> f64 {
265 fp as f64 / 100_000_000.0
266}
267
268#[inline]
270pub fn to_currency_fp(value: f64) -> i64 {
271 (value * 100.0) as i64
272}
273
274#[inline]
276pub fn from_currency_fp(fp: i64) -> f64 {
277 fp as f64 / 100.0
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283
284 #[test]
285 fn test_fixed_point_conversion() {
286 let value = 0.95;
287 let fp = to_fixed_point(value);
288 let back = from_fixed_point(fp);
289 assert!((value - back).abs() < 1e-8);
290 }
291
292 #[test]
293 fn test_currency_conversion() {
294 let value = 50000.50;
296 let fp = to_currency_fp(value);
297 let back = from_currency_fp(fp);
298 assert!((value - back).abs() < 0.01);
299 assert_eq!(fp, 5000050); }
301
302 #[test]
303 fn test_update_position_ring() {
304 let msg = UpdatePositionRing {
305 id: MessageId(1),
306 asset_id: 100,
307 value_fp: to_currency_fp(50000.0),
308 expected_return_fp: to_fixed_point(0.08),
309 volatility_fp: to_fixed_point(0.20),
310 };
311 assert_eq!(msg.asset_id, 100);
312 assert!((from_currency_fp(msg.value_fp) - 50000.0).abs() < 0.01);
313 }
314
315 #[test]
316 fn test_k2k_partial_var() {
317 let msg = K2KPartialVaR {
318 id: MessageId(2),
319 worker_id: 1,
320 correlation_id: 12345,
321 partial_var_fp: to_currency_fp(10000.0),
322 partial_es_fp: to_currency_fp(12000.0),
323 positions_processed: 50,
324 cov_contribution_fp: to_fixed_point(0.0015),
325 };
326 assert_eq!(msg.worker_id, 1);
327 assert_eq!(msg.positions_processed, 50);
328 }
329
330 #[test]
331 fn test_k2k_risk_limit_alert() {
332 let msg = K2KRiskLimitAlert {
333 id: MessageId(3),
334 timestamp_us: 1234567890,
335 severity: 2,
336 current_var_fp: to_currency_fp(1_100_000.0),
337 var_limit_fp: to_currency_fp(1_000_000.0),
338 breach_amount_fp: to_currency_fp(100_000.0),
339 trigger_asset_id: 42,
340 };
341 assert_eq!(msg.severity, 2);
342 assert!((from_currency_fp(msg.breach_amount_fp) - 100_000.0).abs() < 0.01);
343 }
344}