rustkernel_risk/
messages.rs

1//! Message types for Risk Analytics kernels.
2//!
3//! This module defines input/output types for batch execution
4//! of risk analytics kernels and Ring kernel messages for K2K communication.
5
6use crate::types::{
7    CreditExposure, CreditFactors, CreditRiskResult, Portfolio, PortfolioRiskResult, Sensitivity,
8    StressScenario, StressTestResult, VaRParams, VaRResult,
9};
10use rustkernel_derive::KernelMessage;
11use serde::{Deserialize, Serialize};
12
13// ============================================================================
14// Credit Risk Scoring Messages
15// ============================================================================
16
17/// Input for credit risk scoring.
18///
19/// Ring message type_id: 3000 (RiskAnalytics domain)
20#[derive(Debug, Clone, Serialize, Deserialize, KernelMessage)]
21#[message(type_id = 3000, domain = "RiskAnalytics")]
22pub struct CreditRiskScoringInput {
23    /// Credit scoring factors for the obligor.
24    pub factors: CreditFactors,
25    /// Exposure at Default.
26    pub ead: f64,
27    /// Maturity in years.
28    pub maturity: f64,
29}
30
31impl CreditRiskScoringInput {
32    /// Create a new credit risk scoring input.
33    pub fn new(factors: CreditFactors, ead: f64, maturity: f64) -> Self {
34        Self {
35            factors,
36            ead,
37            maturity,
38        }
39    }
40}
41
42/// Output from credit risk scoring.
43///
44/// Ring message type_id: 3001 (RiskAnalytics domain)
45#[derive(Debug, Clone, Serialize, Deserialize, KernelMessage)]
46#[message(type_id = 3001, domain = "RiskAnalytics")]
47pub struct CreditRiskScoringOutput {
48    /// The credit risk result.
49    pub result: CreditRiskResult,
50    /// Computation time in microseconds.
51    pub compute_time_us: u64,
52}
53
54/// Input for batch credit risk scoring from exposures.
55#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct CreditRiskBatchInput {
57    /// List of credit exposures.
58    pub exposures: Vec<CreditExposure>,
59}
60
61impl CreditRiskBatchInput {
62    /// Create a new batch input from exposures.
63    pub fn new(exposures: Vec<CreditExposure>) -> Self {
64        Self { exposures }
65    }
66}
67
68/// Output from batch credit risk scoring.
69#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct CreditRiskBatchOutput {
71    /// Credit risk results for each exposure.
72    pub results: Vec<CreditRiskResult>,
73    /// Computation time in microseconds.
74    pub compute_time_us: u64,
75}
76
77// ============================================================================
78// Monte Carlo VaR Messages
79// ============================================================================
80
81/// Input for Monte Carlo VaR calculation.
82///
83/// Ring message type_id: 3010 (RiskAnalytics domain)
84#[derive(Debug, Clone, Serialize, Deserialize, KernelMessage)]
85#[message(type_id = 3010, domain = "RiskAnalytics")]
86pub struct MonteCarloVaRInput {
87    /// Portfolio to analyze.
88    pub portfolio: Portfolio,
89    /// VaR calculation parameters.
90    pub params: VaRParams,
91}
92
93impl MonteCarloVaRInput {
94    /// Create a new Monte Carlo VaR input.
95    pub fn new(portfolio: Portfolio, params: VaRParams) -> Self {
96        Self { portfolio, params }
97    }
98
99    /// Create with default parameters.
100    pub fn with_defaults(portfolio: Portfolio) -> Self {
101        Self {
102            portfolio,
103            params: VaRParams::default(),
104        }
105    }
106}
107
108/// Output from Monte Carlo VaR calculation.
109///
110/// Ring message type_id: 3011 (RiskAnalytics domain)
111#[derive(Debug, Clone, Serialize, Deserialize, KernelMessage)]
112#[message(type_id = 3011, domain = "RiskAnalytics")]
113pub struct MonteCarloVaROutput {
114    /// The VaR result.
115    pub result: VaRResult,
116    /// Computation time in microseconds.
117    pub compute_time_us: u64,
118}
119
120// ============================================================================
121// Portfolio Risk Aggregation Messages
122// ============================================================================
123
124/// Input for portfolio risk aggregation.
125///
126/// Ring message type_id: 3020 (RiskAnalytics domain)
127#[derive(Debug, Clone, Serialize, Deserialize, KernelMessage)]
128#[message(type_id = 3020, domain = "RiskAnalytics")]
129pub struct PortfolioRiskAggregationInput {
130    /// Portfolio to analyze.
131    pub portfolio: Portfolio,
132    /// Confidence level for VaR (e.g., 0.95).
133    pub confidence_level: f64,
134    /// Holding period in days.
135    pub holding_period: u32,
136}
137
138impl PortfolioRiskAggregationInput {
139    /// Create a new portfolio risk aggregation input.
140    pub fn new(portfolio: Portfolio, confidence_level: f64, holding_period: u32) -> Self {
141        Self {
142            portfolio,
143            confidence_level,
144            holding_period,
145        }
146    }
147
148    /// Create with standard parameters (99% confidence, 10-day holding).
149    pub fn standard(portfolio: Portfolio) -> Self {
150        Self {
151            portfolio,
152            confidence_level: 0.99,
153            holding_period: 10,
154        }
155    }
156}
157
158/// Output from portfolio risk aggregation.
159///
160/// Ring message type_id: 3021 (RiskAnalytics domain)
161#[derive(Debug, Clone, Serialize, Deserialize, KernelMessage)]
162#[message(type_id = 3021, domain = "RiskAnalytics")]
163pub struct PortfolioRiskAggregationOutput {
164    /// The portfolio risk result.
165    pub result: PortfolioRiskResult,
166    /// Computation time in microseconds.
167    pub compute_time_us: u64,
168}
169
170// ============================================================================
171// Stress Testing Messages
172// ============================================================================
173
174/// Input for stress testing.
175#[derive(Debug, Clone, Serialize, Deserialize)]
176pub struct StressTestingInput {
177    /// Portfolio to stress test.
178    pub portfolio: Portfolio,
179    /// Stress scenario to apply.
180    pub scenario: StressScenario,
181    /// Optional sensitivities for non-linear effects.
182    pub sensitivities: Option<Vec<Sensitivity>>,
183}
184
185impl StressTestingInput {
186    /// Create a new stress testing input.
187    pub fn new(portfolio: Portfolio, scenario: StressScenario) -> Self {
188        Self {
189            portfolio,
190            scenario,
191            sensitivities: None,
192        }
193    }
194
195    /// Create with sensitivities.
196    pub fn with_sensitivities(
197        portfolio: Portfolio,
198        scenario: StressScenario,
199        sensitivities: Vec<Sensitivity>,
200    ) -> Self {
201        Self {
202            portfolio,
203            scenario,
204            sensitivities: Some(sensitivities),
205        }
206    }
207}
208
209/// Output from stress testing.
210#[derive(Debug, Clone, Serialize, Deserialize)]
211pub struct StressTestingOutput {
212    /// The stress test result.
213    pub result: StressTestResult,
214    /// Computation time in microseconds.
215    pub compute_time_us: u64,
216}
217
218/// Input for batch stress testing.
219#[derive(Debug, Clone, Serialize, Deserialize)]
220pub struct StressTestingBatchInput {
221    /// Portfolio to stress test.
222    pub portfolio: Portfolio,
223    /// Stress scenarios to apply.
224    pub scenarios: Vec<StressScenario>,
225    /// Optional sensitivities for non-linear effects.
226    pub sensitivities: Option<Vec<Sensitivity>>,
227}
228
229impl StressTestingBatchInput {
230    /// Create a new batch stress testing input.
231    pub fn new(portfolio: Portfolio, scenarios: Vec<StressScenario>) -> Self {
232        Self {
233            portfolio,
234            scenarios,
235            sensitivities: None,
236        }
237    }
238
239    /// Create with sensitivities.
240    pub fn with_sensitivities(
241        portfolio: Portfolio,
242        scenarios: Vec<StressScenario>,
243        sensitivities: Vec<Sensitivity>,
244    ) -> Self {
245        Self {
246            portfolio,
247            scenarios,
248            sensitivities: Some(sensitivities),
249        }
250    }
251
252    /// Create with standard scenarios.
253    pub fn standard_scenarios(portfolio: Portfolio) -> Self {
254        use crate::stress::StressTesting;
255        Self {
256            portfolio,
257            scenarios: StressTesting::standard_scenarios(),
258            sensitivities: None,
259        }
260    }
261}
262
263/// Output from batch stress testing.
264#[derive(Debug, Clone, Serialize, Deserialize)]
265pub struct StressTestingBatchOutput {
266    /// Stress test results for each scenario.
267    pub results: Vec<StressTestResult>,
268    /// Worst-case scenario result.
269    pub worst_case: Option<StressTestResult>,
270    /// Computation time in microseconds.
271    pub compute_time_us: u64,
272}
273
274#[cfg(test)]
275mod tests {
276    use super::*;
277
278    fn create_test_portfolio() -> Portfolio {
279        Portfolio::new(
280            vec![1, 2],
281            vec![100_000.0, 50_000.0],
282            vec![0.08, 0.10],
283            vec![0.15, 0.20],
284            vec![1.0, 0.5, 0.5, 1.0],
285        )
286    }
287
288    fn create_test_factors() -> CreditFactors {
289        CreditFactors {
290            obligor_id: 1,
291            debt_to_income: 0.30,
292            loan_to_value: 0.70,
293            credit_utilization: 0.25,
294            payment_history: 85.0,
295            employment_years: 5.0,
296            recent_inquiries: 2,
297            delinquencies: 0,
298            credit_history_years: 8.0,
299        }
300    }
301
302    #[test]
303    fn test_credit_risk_scoring_input() {
304        let factors = create_test_factors();
305        let input = CreditRiskScoringInput::new(factors, 100_000.0, 5.0);
306        assert_eq!(input.ead, 100_000.0);
307        assert_eq!(input.maturity, 5.0);
308    }
309
310    #[test]
311    fn test_monte_carlo_var_input() {
312        let portfolio = create_test_portfolio();
313        let input = MonteCarloVaRInput::with_defaults(portfolio);
314        assert_eq!(input.params.confidence_level, 0.99);
315    }
316
317    #[test]
318    fn test_portfolio_risk_aggregation_input() {
319        let portfolio = create_test_portfolio();
320        let input = PortfolioRiskAggregationInput::standard(portfolio);
321        assert_eq!(input.confidence_level, 0.99);
322        assert_eq!(input.holding_period, 10);
323    }
324
325    #[test]
326    fn test_stress_testing_input() {
327        let portfolio = create_test_portfolio();
328        let scenario = StressScenario::equity_crash(-0.20);
329        let input = StressTestingInput::new(portfolio, scenario);
330        assert!(input.sensitivities.is_none());
331    }
332}