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}