rustkernel_risk/
types.rs

1//! Risk analytics types and data structures.
2
3use serde::{Deserialize, Serialize};
4
5// ============================================================================
6// Portfolio Types
7// ============================================================================
8
9/// A portfolio of assets for risk analysis.
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct Portfolio {
12    /// Asset identifiers.
13    pub asset_ids: Vec<u64>,
14    /// Position values (notional or market value).
15    pub values: Vec<f64>,
16    /// Expected returns per asset.
17    pub expected_returns: Vec<f64>,
18    /// Volatilities per asset.
19    pub volatilities: Vec<f64>,
20    /// Correlation matrix (flattened, row-major).
21    pub correlation_matrix: Vec<f64>,
22}
23
24impl Portfolio {
25    /// Create a new portfolio.
26    pub fn new(
27        asset_ids: Vec<u64>,
28        values: Vec<f64>,
29        expected_returns: Vec<f64>,
30        volatilities: Vec<f64>,
31        correlation_matrix: Vec<f64>,
32    ) -> Self {
33        Self {
34            asset_ids,
35            values,
36            expected_returns,
37            volatilities,
38            correlation_matrix,
39        }
40    }
41
42    /// Get the number of assets in the portfolio.
43    pub fn n_assets(&self) -> usize {
44        self.asset_ids.len()
45    }
46
47    /// Get total portfolio value.
48    pub fn total_value(&self) -> f64 {
49        self.values.iter().sum()
50    }
51
52    /// Get portfolio weights.
53    pub fn weights(&self) -> Vec<f64> {
54        let total = self.total_value();
55        if total.abs() < 1e-10 {
56            vec![0.0; self.n_assets()]
57        } else {
58            self.values.iter().map(|v| v / total).collect()
59        }
60    }
61
62    /// Get correlation between two assets.
63    pub fn correlation(&self, i: usize, j: usize) -> f64 {
64        let n = self.n_assets();
65        if i >= n || j >= n {
66            return 0.0;
67        }
68        self.correlation_matrix[i * n + j]
69    }
70}
71
72// ============================================================================
73// Credit Risk Types
74// ============================================================================
75
76/// Credit exposure for a single obligor.
77#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct CreditExposure {
79    /// Obligor ID.
80    pub obligor_id: u64,
81    /// Exposure at Default (EAD).
82    pub ead: f64,
83    /// Probability of Default (PD).
84    pub pd: f64,
85    /// Loss Given Default (LGD).
86    pub lgd: f64,
87    /// Maturity in years.
88    pub maturity: f64,
89    /// Credit rating (1=best, higher=worse).
90    pub rating: u8,
91}
92
93impl CreditExposure {
94    /// Create a new credit exposure.
95    pub fn new(obligor_id: u64, ead: f64, pd: f64, lgd: f64, maturity: f64, rating: u8) -> Self {
96        Self {
97            obligor_id,
98            ead,
99            pd,
100            lgd,
101            maturity,
102            rating,
103        }
104    }
105
106    /// Calculate expected loss.
107    pub fn expected_loss(&self) -> f64 {
108        self.pd * self.lgd * self.ead
109    }
110}
111
112/// Credit scoring factors for an obligor.
113#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct CreditFactors {
115    /// Obligor ID.
116    pub obligor_id: u64,
117    /// Debt-to-income ratio.
118    pub debt_to_income: f64,
119    /// Loan-to-value ratio (for secured loans).
120    pub loan_to_value: f64,
121    /// Credit utilization (0-1).
122    pub credit_utilization: f64,
123    /// Payment history score (0-100).
124    pub payment_history: f64,
125    /// Time with current employer (years).
126    pub employment_years: f64,
127    /// Number of credit inquiries in last 12 months.
128    pub recent_inquiries: u32,
129    /// Number of delinquencies in history.
130    pub delinquencies: u32,
131    /// Years of credit history.
132    pub credit_history_years: f64,
133}
134
135/// Result of credit risk scoring.
136#[derive(Debug, Clone, Serialize, Deserialize)]
137pub struct CreditRiskResult {
138    /// Obligor ID.
139    pub obligor_id: u64,
140    /// Probability of Default.
141    pub pd: f64,
142    /// Loss Given Default.
143    pub lgd: f64,
144    /// Expected Loss.
145    pub expected_loss: f64,
146    /// Risk-weighted assets (for capital calculation).
147    pub rwa: f64,
148    /// Credit score (internal rating).
149    pub credit_score: f64,
150    /// Factor contributions to score.
151    pub factor_contributions: Vec<(String, f64)>,
152}
153
154// ============================================================================
155// Market Risk Types
156// ============================================================================
157
158/// Value at Risk calculation parameters.
159#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
160pub struct VaRParams {
161    /// Confidence level (e.g., 0.95 for 95% VaR).
162    pub confidence_level: f64,
163    /// Holding period in days.
164    pub holding_period: u32,
165    /// Number of Monte Carlo simulations.
166    pub n_simulations: u32,
167}
168
169impl Default for VaRParams {
170    fn default() -> Self {
171        Self {
172            confidence_level: 0.99,
173            holding_period: 10,
174            n_simulations: 10_000,
175        }
176    }
177}
178
179impl VaRParams {
180    /// Create new VaR parameters.
181    pub fn new(confidence_level: f64, holding_period: u32, n_simulations: u32) -> Self {
182        Self {
183            confidence_level,
184            holding_period,
185            n_simulations,
186        }
187    }
188}
189
190/// Result of VaR calculation.
191#[derive(Debug, Clone, Serialize, Deserialize)]
192pub struct VaRResult {
193    /// Value at Risk (loss at confidence level).
194    pub var: f64,
195    /// Expected Shortfall (Conditional VaR).
196    pub expected_shortfall: f64,
197    /// Confidence level used.
198    pub confidence_level: f64,
199    /// Holding period in days.
200    pub holding_period: u32,
201    /// Component VaR by asset (if available).
202    pub component_var: Vec<f64>,
203    /// Marginal VaR by asset (if available).
204    pub marginal_var: Vec<f64>,
205    /// P&L distribution percentiles.
206    pub percentiles: Vec<(f64, f64)>,
207}
208
209/// Portfolio risk aggregation result.
210#[derive(Debug, Clone, Serialize, Deserialize)]
211pub struct PortfolioRiskResult {
212    /// Total portfolio VaR.
213    pub portfolio_var: f64,
214    /// Portfolio Expected Shortfall.
215    pub portfolio_es: f64,
216    /// Undiversified VaR (sum of individual VaRs).
217    pub undiversified_var: f64,
218    /// Diversification benefit (undiversified - portfolio).
219    pub diversification_benefit: f64,
220    /// Individual asset VaRs.
221    pub asset_vars: Vec<f64>,
222    /// Risk contributions by asset.
223    pub risk_contributions: Vec<f64>,
224    /// Correlation-adjusted covariance matrix.
225    pub covariance_matrix: Vec<f64>,
226}
227
228// ============================================================================
229// Stress Testing Types
230// ============================================================================
231
232/// A stress scenario definition.
233#[derive(Debug, Clone, Serialize, Deserialize)]
234pub struct StressScenario {
235    /// Scenario name.
236    pub name: String,
237    /// Scenario description.
238    pub description: String,
239    /// Risk factor shocks (factor_name, shock_percentage).
240    pub shocks: Vec<(String, f64)>,
241    /// Scenario probability (for expected loss calculation).
242    pub probability: f64,
243}
244
245impl StressScenario {
246    /// Create a new stress scenario.
247    pub fn new(
248        name: &str,
249        description: &str,
250        shocks: Vec<(String, f64)>,
251        probability: f64,
252    ) -> Self {
253        Self {
254            name: name.to_string(),
255            description: description.to_string(),
256            shocks,
257            probability,
258        }
259    }
260
261    /// Create a simple equity shock scenario.
262    pub fn equity_crash(shock_pct: f64) -> Self {
263        Self::new(
264            "Equity Crash",
265            "Severe equity market decline",
266            vec![("equity".to_string(), shock_pct)],
267            0.01,
268        )
269    }
270
271    /// Create an interest rate shock scenario.
272    pub fn rate_shock(shock_bps: f64) -> Self {
273        Self::new(
274            "Rate Shock",
275            "Parallel shift in yield curve",
276            vec![("interest_rate".to_string(), shock_bps / 10000.0)],
277            0.05,
278        )
279    }
280
281    /// Create a credit spread widening scenario.
282    pub fn credit_spread_widening(shock_bps: f64) -> Self {
283        Self::new(
284            "Credit Spread Widening",
285            "Credit spreads widen across all sectors",
286            vec![("credit_spread".to_string(), shock_bps / 10000.0)],
287            0.03,
288        )
289    }
290}
291
292/// Result of stress testing.
293#[derive(Debug, Clone, Serialize, Deserialize)]
294pub struct StressTestResult {
295    /// Scenario name.
296    pub scenario_name: String,
297    /// P&L impact (negative = loss).
298    pub pnl_impact: f64,
299    /// Impact as percentage of portfolio value.
300    pub pnl_impact_pct: f64,
301    /// Impact by asset.
302    pub asset_impacts: Vec<(u64, f64)>,
303    /// Impact by risk factor.
304    pub factor_impacts: Vec<(String, f64)>,
305    /// Post-stress portfolio value.
306    pub post_stress_value: f64,
307}
308
309// ============================================================================
310// Risk Factor Types
311// ============================================================================
312
313/// Market risk factor.
314#[derive(Debug, Clone, Serialize, Deserialize)]
315pub struct RiskFactor {
316    /// Factor name.
317    pub name: String,
318    /// Current value.
319    pub value: f64,
320    /// Historical volatility (annualized).
321    pub volatility: f64,
322    /// Factor type.
323    pub factor_type: RiskFactorType,
324}
325
326/// Type of risk factor.
327#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
328pub enum RiskFactorType {
329    /// Equity index or stock price.
330    Equity,
331    /// Interest rate.
332    InterestRate,
333    /// FX rate.
334    ForeignExchange,
335    /// Credit spread.
336    CreditSpread,
337    /// Commodity price.
338    Commodity,
339    /// Volatility (e.g., VIX).
340    Volatility,
341}
342
343/// Sensitivity of a position to risk factors.
344#[derive(Debug, Clone, Serialize, Deserialize)]
345pub struct Sensitivity {
346    /// Asset ID.
347    pub asset_id: u64,
348    /// Delta (first-order sensitivity).
349    pub delta: f64,
350    /// Gamma (second-order sensitivity).
351    pub gamma: f64,
352    /// Vega (volatility sensitivity).
353    pub vega: f64,
354    /// Theta (time decay).
355    pub theta: f64,
356    /// Rho (interest rate sensitivity).
357    pub rho: f64,
358}
359
360impl Default for Sensitivity {
361    fn default() -> Self {
362        Self {
363            asset_id: 0,
364            delta: 1.0,
365            gamma: 0.0,
366            vega: 0.0,
367            theta: 0.0,
368            rho: 0.0,
369        }
370    }
371}