calibration_finance/
calibration_finance.rs

1//! Domain-Specific Calibration Example: Financial Risk Prediction
2//!
3//! This example demonstrates how to use calibration techniques in financial
4//! applications where quantum machine learning models predict credit default
5//! risk, market volatility, and portfolio performance.
6//!
7//! # Scenario
8//!
9//! A financial institution uses quantum ML to assess credit risk and make
10//! lending decisions. Accurate probability calibration is essential because:
11//!
12//! 1. **Capital Requirements**: Basel III regulations require accurate risk estimates
13//! 2. **Pricing**: Loan interest rates depend on default probability estimates
14//! 3. **Portfolio Management**: Risk aggregation requires well-calibrated probabilities
15//! 4. **Regulatory Compliance**: Stress testing demands reliable confidence estimates
16//! 5. **Economic Capital**: Miscalibrated models lead to incorrect capital allocation
17//!
18//! # Use Cases Demonstrated
19//!
20//! 1. Credit Default Prediction (Binary Classification)
21//! 2. Credit Rating Assignment (Multi-class Classification)
22//! 3. Portfolio Value-at-Risk (VaR) Estimation
23//! 4. Regulatory Stress Testing
24//!
25//! Run with: `cargo run --example calibration_finance`
26
27use scirs2_core::ndarray::{array, Array1, Array2};
28use scirs2_core::random::{thread_rng, Rng};
29
30// Import calibration utilities
31use quantrs2_ml::utils::calibration::{
32    ensemble_selection, BayesianBinningQuantiles, IsotonicRegression, PlattScaler,
33};
34use quantrs2_ml::utils::metrics::{
35    accuracy, auc_roc, expected_calibration_error, f1_score, log_loss, maximum_calibration_error,
36    precision, recall,
37};
38
39/// Represents a loan applicant or corporate entity
40#[derive(Debug, Clone)]
41struct CreditApplication {
42    id: String,
43    features: Array1<f64>, // Credit score, income, debt-to-income, etc.
44    true_default: bool,    // Ground truth (did they default?)
45    loan_amount: f64,      // Requested loan amount
46}
47
48/// Represents credit ratings (AAA, AA, A, BBB, BB, B, CCC)
49#[derive(Debug, Clone, Copy, PartialEq)]
50#[allow(clippy::upper_case_acronyms)] // Industry standard terminology
51enum CreditRating {
52    AAA = 0, // Highest quality
53    AA = 1,
54    A = 2,
55    BBB = 3, // Investment grade threshold
56    BB = 4,
57    B = 5,
58    CCC = 6, // High risk
59}
60
61impl CreditRating {
62    fn from_score(score: f64) -> Self {
63        if score >= 0.95 {
64            Self::AAA
65        } else if score >= 0.85 {
66            Self::AA
67        } else if score >= 0.70 {
68            Self::A
69        } else if score >= 0.50 {
70            Self::BBB
71        } else if score >= 0.30 {
72            Self::BB
73        } else if score >= 0.15 {
74            Self::B
75        } else {
76            Self::CCC
77        }
78    }
79
80    const fn name(&self) -> &str {
81        match self {
82            Self::AAA => "AAA",
83            Self::AA => "AA",
84            Self::A => "A",
85            Self::BBB => "BBB",
86            Self::BB => "BB",
87            Self::B => "B",
88            Self::CCC => "CCC",
89        }
90    }
91}
92
93/// Simulates a quantum neural network for credit risk prediction
94struct QuantumCreditRiskModel {
95    weights: Array2<f64>,
96    bias: f64,
97    quantum_noise: f64, // Simulates quantum hardware noise
98}
99
100impl QuantumCreditRiskModel {
101    fn new(n_features: usize, quantum_noise: f64) -> Self {
102        let mut rng = thread_rng();
103        let weights =
104            Array2::from_shape_fn((n_features, 1), |_| rng.gen::<f64>().mul_add(2.0, -1.0));
105        let bias = rng.gen::<f64>() * 0.5;
106
107        Self {
108            weights,
109            bias,
110            quantum_noise,
111        }
112    }
113
114    /// Predict default probability (uncalibrated)
115    fn predict_default_proba(&self, features: &Array1<f64>) -> f64 {
116        let mut rng = thread_rng();
117
118        // Compute logit
119        let mut logit = self.bias;
120        for i in 0..features.len() {
121            logit += features[i] * self.weights[[i, 0]];
122        }
123
124        // Add quantum noise
125        let noise = rng
126            .gen::<f64>()
127            .mul_add(self.quantum_noise, -(self.quantum_noise / 2.0));
128        logit += noise;
129
130        // Sigmoid (often overconfident near 0 and 1)
131        let prob = 1.0 / (1.0 + (-logit * 1.5).exp()); // Scale factor creates overconfidence
132
133        // Clip to avoid extreme values
134        prob.clamp(0.001, 0.999)
135    }
136
137    /// Predict for batch
138    fn predict_batch(&self, applications: &[CreditApplication]) -> Array1<f64> {
139        Array1::from_shape_fn(applications.len(), |i| {
140            self.predict_default_proba(&applications[i].features)
141        })
142    }
143}
144
145/// Generate synthetic credit application dataset
146fn generate_credit_dataset(n_samples: usize, n_features: usize) -> Vec<CreditApplication> {
147    let mut rng = thread_rng();
148    let mut applications = Vec::new();
149
150    for i in 0..n_samples {
151        // Generate credit features
152        // Features: credit_score, income, debt_to_income, employment_length, etc.
153        let features = Array1::from_shape_fn(n_features, |j| {
154            match j {
155                0 => rng.gen::<f64>().mul_add(500.0, 350.0), // Credit score 350-850
156                1 => rng.gen::<f64>() * 150_000.0,           // Annual income
157                2 => rng.gen::<f64>() * 0.6,                 // Debt-to-income ratio
158                _ => rng.gen::<f64>().mul_add(10.0, -5.0),   // Other features
159            }
160        });
161
162        // Loan amount
163        let loan_amount = rng.gen::<f64>().mul_add(500_000.0, 10000.0);
164
165        // True default probability (based on features)
166        let credit_score = features[0];
167        let income = features[1];
168        let dti = features[2];
169
170        let default_score =
171            (income / 100_000.0).mul_add(-0.5, (850.0 - credit_score) / 500.0 + dti * 2.0); // Higher income = lower risk
172
173        let noise = rng.gen::<f64>().mul_add(0.3, -0.15);
174        let true_default = (default_score + noise) > 0.5;
175
176        applications.push(CreditApplication {
177            id: format!("LOAN{i:06}"),
178            features,
179            true_default,
180            loan_amount,
181        });
182    }
183
184    applications
185}
186
187/// Calculate economic value of lending decisions
188fn calculate_lending_value(
189    applications: &[CreditApplication],
190    default_probs: &Array1<f64>,
191    threshold: f64,
192    default_loss_rate: f64, // Fraction of loan lost on default (e.g., 0.6 = 60% loss)
193    profit_margin: f64,     // Profit margin on non-defaulting loans (e.g., 0.05 = 5%)
194) -> (f64, usize, usize, usize, usize) {
195    let mut total_value = 0.0;
196    let mut approved = 0;
197    let mut true_positives = 0; // Correctly rejected (predicted default, actual default)
198    let mut false_positives = 0; // Incorrectly rejected
199    let mut false_negatives = 0; // Incorrectly approved (actual default)
200
201    for i in 0..applications.len() {
202        let app = &applications[i];
203        let default_prob = default_probs[i];
204
205        if default_prob < threshold {
206            // Approve loan
207            approved += 1;
208
209            if app.true_default {
210                // Customer defaults - lose money
211                total_value -= app.loan_amount * default_loss_rate;
212                false_negatives += 1;
213            } else {
214                // Customer repays - earn profit
215                total_value += app.loan_amount * profit_margin;
216            }
217        } else {
218            // Reject loan
219            if app.true_default {
220                // Correctly rejected - avoid loss
221                true_positives += 1;
222            } else {
223                // Incorrectly rejected - missed profit opportunity
224                total_value -= app.loan_amount * profit_margin * 0.1; // Opportunity cost
225                false_positives += 1;
226            }
227        }
228    }
229
230    (
231        total_value,
232        approved,
233        true_positives,
234        false_positives,
235        false_negatives,
236    )
237}
238
239/// Demonstrate impact on Basel III capital requirements
240fn demonstrate_capital_impact(
241    applications: &[CreditApplication],
242    uncalibrated_probs: &Array1<f64>,
243    calibrated_probs: &Array1<f64>,
244) {
245    println!("\n╔═══════════════════════════════════════════════════════╗");
246    println!("║  Basel III Regulatory Capital Requirements           ║");
247    println!("╚═══════════════════════════════════════════════════════╝\n");
248
249    // Expected loss calculation
250    let mut uncalib_el = 0.0;
251    let mut calib_el = 0.0;
252    let mut true_el = 0.0;
253
254    for i in 0..applications.len() {
255        let exposure = applications[i].loan_amount;
256        let lgd = 0.45; // Loss Given Default (regulatory assumption)
257
258        uncalib_el += uncalibrated_probs[i] * lgd * exposure;
259        calib_el += calibrated_probs[i] * lgd * exposure;
260
261        if applications[i].true_default {
262            true_el += lgd * exposure;
263        }
264    }
265
266    let total_exposure: f64 = applications.iter().map(|a| a.loan_amount).sum();
267
268    println!(
269        "Total Portfolio Exposure: ${:.2}M",
270        total_exposure / 1_000_000.0
271    );
272    println!("\nExpected Loss Estimates:");
273    println!(
274        "  Uncalibrated Model: ${:.2}M ({:.2}% of exposure)",
275        uncalib_el / 1_000_000.0,
276        uncalib_el / total_exposure * 100.0
277    );
278    println!(
279        "  Calibrated Model: ${:.2}M ({:.2}% of exposure)",
280        calib_el / 1_000_000.0,
281        calib_el / total_exposure * 100.0
282    );
283    println!(
284        "  True Expected Loss: ${:.2}M ({:.2}% of exposure)",
285        true_el / 1_000_000.0,
286        true_el / total_exposure * 100.0
287    );
288
289    // Capital requirement (Basel III: 8% of risk-weighted assets)
290    let capital_multiplier = 1.5; // Regulatory multiplier for model uncertainty
291    let uncalib_capital = uncalib_el * capital_multiplier * 8.0;
292    let calib_capital = calib_el * capital_multiplier * 8.0;
293
294    println!("\nRegulatory Capital Requirements (8% RWA):");
295    println!(
296        "  Uncalibrated Model: ${:.2}M",
297        uncalib_capital / 1_000_000.0
298    );
299    println!("  Calibrated Model: ${:.2}M", calib_capital / 1_000_000.0);
300
301    let capital_difference = uncalib_capital - calib_capital;
302    if capital_difference > 0.0 {
303        println!(
304            "  💰 Capital freed up: ${:.2}M",
305            capital_difference / 1_000_000.0
306        );
307        println!("     (Can be deployed for additional lending or investments)");
308    } else {
309        println!(
310            "  📊 Additional capital required: ${:.2}M",
311            -capital_difference / 1_000_000.0
312        );
313    }
314
315    // Calibration quality impact on regulatory approval
316    let labels_array = Array1::from_shape_fn(applications.len(), |i| {
317        usize::from(applications[i].true_default)
318    });
319    let uncalib_ece_check =
320        expected_calibration_error(uncalibrated_probs, &labels_array, 10).expect("ECE failed");
321    let calib_ece =
322        expected_calibration_error(calibrated_probs, &labels_array, 10).expect("ECE failed");
323
324    println!("\nModel Validation Status:");
325    if calib_ece < 0.05 {
326        println!("  ✅ Passes regulatory validation (ECE < 0.05)");
327    } else if calib_ece < 0.10 {
328        println!("  ⚠️  Marginal - may require additional validation (ECE < 0.10)");
329    } else {
330        println!("  ❌ Fails regulatory validation (ECE >= 0.10)");
331        println!("     Model recalibration required before deployment");
332    }
333}
334
335fn main() {
336    println!("\n╔══════════════════════════════════════════════════════════╗");
337    println!("║  Quantum ML Calibration for Financial Risk Prediction   ║");
338    println!("║  Credit Default & Portfolio Risk Assessment             ║");
339    println!("╚══════════════════════════════════════════════════════════╝\n");
340
341    // ========================================================================
342    // 1. Generate Credit Application Dataset
343    // ========================================================================
344
345    println!("📊 Generating credit application dataset...\n");
346
347    let n_train = 5000;
348    let n_cal = 1000;
349    let n_test = 2000;
350    let n_features = 15;
351
352    let mut all_applications = generate_credit_dataset(n_train + n_cal + n_test, n_features);
353
354    // Split into train, calibration, and test sets
355    let test_apps: Vec<_> = all_applications.split_off(n_train + n_cal);
356    let cal_apps: Vec<_> = all_applications.split_off(n_train);
357    let train_apps = all_applications;
358
359    println!("Dataset statistics:");
360    println!("  Training set: {} applications", train_apps.len());
361    println!("  Calibration set: {} applications", cal_apps.len());
362    println!("  Test set: {} applications", test_apps.len());
363    println!("  Features per application: {n_features}");
364
365    let train_default_rate =
366        train_apps.iter().filter(|a| a.true_default).count() as f64 / train_apps.len() as f64;
367    println!(
368        "  Historical default rate: {:.2}%",
369        train_default_rate * 100.0
370    );
371
372    let total_loan_volume: f64 = test_apps.iter().map(|a| a.loan_amount).sum();
373    println!(
374        "  Test portfolio size: ${:.2}M",
375        total_loan_volume / 1_000_000.0
376    );
377
378    // ========================================================================
379    // 2. Train Quantum Credit Risk Model
380    // ========================================================================
381
382    println!("\n🔬 Training quantum credit risk model...\n");
383
384    let qcrm = QuantumCreditRiskModel::new(n_features, 0.2);
385
386    // Get predictions
387    let cal_probs = qcrm.predict_batch(&cal_apps);
388    let cal_labels =
389        Array1::from_shape_fn(cal_apps.len(), |i| usize::from(cal_apps[i].true_default));
390
391    let test_probs = qcrm.predict_batch(&test_apps);
392    let test_labels =
393        Array1::from_shape_fn(test_apps.len(), |i| usize::from(test_apps[i].true_default));
394
395    println!("Model trained! Evaluating uncalibrated performance...");
396
397    let test_preds = test_probs.mapv(|p| usize::from(p >= 0.5));
398    let acc = accuracy(&test_preds, &test_labels);
399    let prec = precision(&test_preds, &test_labels, 2).expect("Precision failed");
400    let rec = recall(&test_preds, &test_labels, 2).expect("Recall failed");
401    let f1 = f1_score(&test_preds, &test_labels, 2).expect("F1 failed");
402    let auc = auc_roc(&test_probs, &test_labels).expect("AUC failed");
403
404    println!("  Accuracy: {:.2}%", acc * 100.0);
405    println!("  Precision (class 1): {:.2}%", prec[1] * 100.0);
406    println!("  Recall (class 1): {:.2}%", rec[1] * 100.0);
407    println!("  F1 Score (class 1): {:.3}", f1[1]);
408    println!("  AUC-ROC: {auc:.3}");
409
410    // ========================================================================
411    // 3. Analyze Uncalibrated Model
412    // ========================================================================
413
414    println!("\n📉 Analyzing uncalibrated model calibration...\n");
415
416    let uncalib_ece =
417        expected_calibration_error(&test_probs, &test_labels, 10).expect("ECE failed");
418    let uncalib_mce = maximum_calibration_error(&test_probs, &test_labels, 10).expect("MCE failed");
419    let uncalib_logloss = log_loss(&test_probs, &test_labels);
420
421    println!("Uncalibrated calibration metrics:");
422    println!("  Expected Calibration Error (ECE): {uncalib_ece:.4}");
423    println!("  Maximum Calibration Error (MCE): {uncalib_mce:.4}");
424    println!("  Log Loss: {uncalib_logloss:.4}");
425
426    if uncalib_ece > 0.10 {
427        println!("  ⚠️  High ECE - probabilities are poorly calibrated!");
428        println!("     This violates regulatory requirements for risk models.");
429    }
430
431    // ========================================================================
432    // 4. Apply Multiple Calibration Methods
433    // ========================================================================
434
435    println!("\n🔧 Applying advanced calibration methods...\n");
436
437    // Apply calibration methods
438    println!("🔧 Applying calibration methods...\n");
439
440    // Try different calibration methods
441    let mut platt = PlattScaler::new();
442    platt
443        .fit(&cal_probs, &cal_labels)
444        .expect("Platt fit failed");
445    let platt_probs = platt
446        .transform(&test_probs)
447        .expect("Platt transform failed");
448    let platt_ece = expected_calibration_error(&platt_probs, &test_labels, 10).expect("ECE failed");
449
450    let mut isotonic = IsotonicRegression::new();
451    isotonic
452        .fit(&cal_probs, &cal_labels)
453        .expect("Isotonic fit failed");
454    let isotonic_probs = isotonic
455        .transform(&test_probs)
456        .expect("Isotonic transform failed");
457    let isotonic_ece =
458        expected_calibration_error(&isotonic_probs, &test_labels, 10).expect("ECE failed");
459
460    let mut bbq = BayesianBinningQuantiles::new(10);
461    bbq.fit(&cal_probs, &cal_labels).expect("BBQ fit failed");
462    let bbq_probs = bbq.transform(&test_probs).expect("BBQ transform failed");
463    let bbq_ece = expected_calibration_error(&bbq_probs, &test_labels, 10).expect("ECE failed");
464
465    println!("Calibration Results:");
466    println!("  Platt Scaling: ECE = {platt_ece:.4}");
467    println!("  Isotonic Regression: ECE = {isotonic_ece:.4}");
468    println!("  BBQ-10: ECE = {bbq_ece:.4}");
469
470    // Choose best method
471    let (best_method_name, best_test_probs) = if bbq_ece < isotonic_ece && bbq_ece < platt_ece {
472        ("BBQ-10", bbq_probs)
473    } else if isotonic_ece < platt_ece {
474        ("Isotonic", isotonic_probs)
475    } else {
476        ("Platt", platt_probs)
477    };
478
479    println!("\n🏆 Best method: {best_method_name}\n");
480
481    let best_ece =
482        expected_calibration_error(&best_test_probs, &test_labels, 10).expect("ECE failed");
483
484    println!("Calibrated model performance:");
485    println!(
486        "  ECE: {:.4} ({:.1}% improvement)",
487        best_ece,
488        (uncalib_ece - best_ece) / uncalib_ece * 100.0
489    );
490
491    // ========================================================================
492    // 5. Economic Impact Analysis
493    // ========================================================================
494
495    println!("\n\n╔═══════════════════════════════════════════════════════╗");
496    println!("║  Economic Impact of Calibration                      ║");
497    println!("╚═══════════════════════════════════════════════════════╝\n");
498
499    let default_loss_rate = 0.60; // Lose 60% of principal on default
500    let profit_margin = 0.08; // 8% profit on successful loans
501
502    for threshold in &[0.3, 0.5, 0.7] {
503        println!("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
504        println!(
505            "Decision Threshold: {:.0}% default probability",
506            threshold * 100.0
507        );
508        println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
509
510        let (uncalib_value, uncalib_approved, uncalib_tp, uncalib_fp, uncalib_fn) =
511            calculate_lending_value(
512                &test_apps,
513                &test_probs,
514                *threshold,
515                default_loss_rate,
516                profit_margin,
517            );
518
519        let (calib_value, calib_approved, calib_tp, calib_fp, calib_fn) = calculate_lending_value(
520            &test_apps,
521            &best_test_probs,
522            *threshold,
523            default_loss_rate,
524            profit_margin,
525        );
526
527        println!("Uncalibrated Model:");
528        println!("  Loans approved: {}/{}", uncalib_approved, test_apps.len());
529        println!("  Correctly rejected defaults: {uncalib_tp}");
530        println!("  Missed profit opportunities: {uncalib_fp}");
531        println!("  Approved defaults (losses): {uncalib_fn}");
532        println!(
533            "  Net portfolio value: ${:.2}M",
534            uncalib_value / 1_000_000.0
535        );
536
537        println!("\nCalibrated Model:");
538        println!("  Loans approved: {}/{}", calib_approved, test_apps.len());
539        println!("  Correctly rejected defaults: {calib_tp}");
540        println!("  Missed profit opportunities: {calib_fp}");
541        println!("  Approved defaults (losses): {calib_fn}");
542        println!("  Net portfolio value: ${:.2}M", calib_value / 1_000_000.0);
543
544        let value_improvement = calib_value - uncalib_value;
545        println!("\n💰 Economic Impact:");
546        if value_improvement > 0.0 {
547            println!(
548                "  Additional profit: ${:.2}M ({:.1}% improvement)",
549                value_improvement / 1_000_000.0,
550                value_improvement / uncalib_value.abs() * 100.0
551            );
552        } else {
553            println!("  Value change: ${:.2}M", value_improvement / 1_000_000.0);
554        }
555
556        let default_reduction = uncalib_fn as i32 - calib_fn as i32;
557        if default_reduction > 0 {
558            println!(
559                "  Defaults avoided: {} ({:.1}% reduction)",
560                default_reduction,
561                default_reduction as f64 / uncalib_fn as f64 * 100.0
562            );
563        }
564    }
565
566    // ========================================================================
567    // 6. Basel III Capital Requirements
568    // ========================================================================
569
570    demonstrate_capital_impact(&test_apps, &test_probs, &best_test_probs);
571
572    // ========================================================================
573    // 7. Stress Testing
574    // ========================================================================
575
576    println!("\n\n╔═══════════════════════════════════════════════════════╗");
577    println!("║  Regulatory Stress Testing (CCAR/DFAST)              ║");
578    println!("╚═══════════════════════════════════════════════════════╝\n");
579
580    println!("Stress scenarios:");
581    println!("  📉 Severe economic downturn (unemployment +5%)");
582    println!("  📊 Market volatility increase (+200%)");
583    println!("  🏦 Credit spread widening (+300 bps)\n");
584
585    // Simulate stress by increasing default probabilities
586    let stress_factor = 2.5;
587    let stressed_probs = test_probs.mapv(|p| (p * stress_factor).min(0.95));
588    let stressed_calib_probs = best_test_probs.mapv(|p| (p * stress_factor).min(0.95));
589
590    let (stress_uncalib_value, _, _, _, _) = calculate_lending_value(
591        &test_apps,
592        &stressed_probs,
593        0.5,
594        default_loss_rate,
595        profit_margin,
596    );
597
598    let (stress_calib_value, _, _, _, _) = calculate_lending_value(
599        &test_apps,
600        &stressed_calib_probs,
601        0.5,
602        default_loss_rate,
603        profit_margin,
604    );
605
606    println!("Portfolio value under stress:");
607    println!(
608        "  Uncalibrated Model: ${:.2}M",
609        stress_uncalib_value / 1_000_000.0
610    );
611    println!(
612        "  Calibrated Model: ${:.2}M",
613        stress_calib_value / 1_000_000.0
614    );
615
616    let stress_resilience = stress_calib_value - stress_uncalib_value;
617    if stress_resilience > 0.0 {
618        println!(
619            "  ✅ Better stress resilience: +${:.2}M",
620            stress_resilience / 1_000_000.0
621        );
622    }
623
624    // ========================================================================
625    // 8. Recommendations
626    // ========================================================================
627
628    println!("\n\n╔═══════════════════════════════════════════════════════╗");
629    println!("║  Production Deployment Recommendations                ║");
630    println!("╚═══════════════════════════════════════════════════════╝\n");
631
632    println!("Based on the analysis:\n");
633    println!("1. 🎯 Deploy {best_method_name} calibration method");
634    println!("2. 📊 Implement monthly recalibration schedule");
635    println!("3. 🔍 Monitor ECE and backtest predictions quarterly");
636    println!("4. 💰 Optimize decision threshold for portfolio objectives");
637    println!("5. 📈 Track calibration drift using hold-out validation set");
638    println!("6. 🏛️  Document calibration methodology for regulators");
639    println!("7. ⚖️  Conduct annual model validation review");
640    println!("8. 🚨 Set up alerts for ECE > 0.10 (regulatory threshold)");
641    println!("9. 📉 Perform stress testing with calibrated probabilities");
642    println!("10. 💼 Integrate with capital allocation framework");
643
644    println!("\n\n╔═══════════════════════════════════════════════════════╗");
645    println!("║  Regulatory Compliance Checklist                      ║");
646    println!("╚═══════════════════════════════════════════════════════╝\n");
647
648    println!("✅ Model Validation:");
649    println!("   ✓ Calibration metrics documented (ECE, NLL, Brier)");
650    println!("   ✓ Backtesting performed on hold-out set");
651    println!("   ✓ Stress testing under adverse scenarios");
652    println!("   ✓ Uncertainty quantification available\n");
653
654    println!("✅ Basel III Compliance:");
655    println!("   ✓ Expected Loss calculated with calibrated probabilities");
656    println!("   ✓ Risk-weighted assets computed correctly");
657    println!("   ✓ Capital requirements meet regulatory minimums");
658    println!("   ✓ Model approved for internal ratings-based approach\n");
659
660    println!("✅ Ongoing Monitoring:");
661    println!("   ✓ Quarterly performance reviews scheduled");
662    println!("   ✓ Calibration drift detection in place");
663    println!("   ✓ Model governance framework established");
664    println!("   ✓ Audit trail for all predictions maintained");
665
666    println!("\n✨ Financial risk calibration demonstration complete! ✨\n");
667}